Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@
# OS files
.DS_Store
Thumbs.db

# Build artifacts & local-only scripts
bin/command-code-proxy-updated
bin/command-code-proxy-new.exe
run-proxy.bat
run-proxy.vbs
Binary file modified bin/command-code-proxy.exe
Binary file not shown.
1 change: 1 addition & 0 deletions internal/api/commandcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type CCChatParams struct {
MaxTokens int `json:"max_tokens"`
Temperature float64 `json:"temperature"`
Stream bool `json:"stream"`
Reasoning string `json:"reasoning,omitempty"`
}

type CCConfig struct {
Expand Down
27 changes: 23 additions & 4 deletions internal/api/openai.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,17 @@ type OpenAIChatRequest struct {
PresencePenalty *float64 `json:"presence_penalty,omitempty"`
FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"`
User string `json:"user,omitempty"`
Reasoning *ReasoningEffort `json:"reasoning,omitempty"`
}

type ReasoningEffort string

const (
ReasoningEffortLow ReasoningEffort = "low"
ReasoningEffortMedium ReasoningEffort = "medium"
ReasoningEffortHigh ReasoningEffort = "high"
)

type OpenAIResponsesRequest struct {
Model string `json:"model"`
Input any `json:"input"`
Expand Down Expand Up @@ -131,10 +140,20 @@ type OpenAIError struct {
}

type OpenAIModel struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
OwnedBy string `json:"owned_by"`
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
OwnedBy string `json:"owned_by"`
ContextLength int `json:"context_length,omitempty"`
Pricing *ModelPricing `json:"pricing,omitempty"`
}

// ModelPricing describes deal/pricing information for a model.
// Only populated when an active deal exists.
type ModelPricing struct {
Multiplier string `json:"multiplier,omitempty"` // e.g. "4x", "2x", "99% off"
Description string `json:"description,omitempty"` // human-readable description
Status string `json:"status,omitempty"` // "permanent" or expiration date
}

type OpenAIModelList struct {
Expand Down
112 changes: 112 additions & 0 deletions internal/proxy/contextmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package proxy

// contextLengthMap maps model IDs to their context window size in tokens.
// Command Code's /v1/models endpoint does not expose context windows, so we
// maintain a local override. Unknown models fall back to 128K (safe default).
//
// Sources:
// - Upstream provider documentation (Anthropic, OpenAI, Google, etc.)
// - Command Code CLI docs (https://commandcode.ai/docs/reference/cli/models)
// - Model release notes
var contextLengthMap = map[string]int{
// MoonshotAI Kimi
"moonshotai/Kimi-K2.7-Code": 262144,
"moonshotai/Kimi-K2.7-Code-Highspeed": 262144,
"moonshotai/Kimi-K2.6": 262144,
"moonshotai/Kimi-K2.5": 262144,

// ZhipuAI GLM
"zai-org/GLM-5.2": 1048576, // 1M
"zai-org/GLM-5.1": 202752,
"zai-org/GLM-5": 202752,

// MiniMax
"MiniMaxAI/MiniMax-M3": 1000000, // 1M
"MiniMaxAI/MiniMax-M3-Promo": 1000000, // 1M
"MiniMaxAI/MiniMax-M2.7": 204800,
"MiniMaxAI/MiniMax-M2.5": 204800,

// DeepSeek
"deepseek/deepseek-v4-pro": 1000000, // 1M
"deepseek/deepseek-v4-flash": 1000000, // 1M

// Qwen
"Qwen/Qwen3.6-Max-Preview": 1048576, // 1M
"Qwen/Qwen3.6-Plus": 1048576, // 1M
"Qwen/Qwen3.7-Max": 1048576, // 1M
"Qwen/Qwen3.7-Plus": 1048576, // 1M

// StepFun
"stepfun/Step-3.7-Flash": 262144,
"stepfun/Step-3.5-Flash": 262144,

// Xiaomi MiMo
"xiaomi/mimo-v2.5-pro": 1048576, // 1M
"xiaomi/mimo-v2.5": 1048576, // 1M

// NVIDIA Nemotron
"nvidia/nemotron-3-ultra-550b-a55b": 131072,

// Anthropic Claude
"claude-sonnet-4-6": 1000000, // 1M
"claude-fable-5": 1000000, // 1M
"claude-opus-4-8": 1000000, // 1M
"claude-opus-4-7": 1000000, // 1M
"claude-opus-4-6": 200000,
"claude-haiku-4-5": 200000,
"claude-haiku-4-5-20251001": 200000,

// OpenAI GPT
"gpt-5.5": 1050000, // ~1M with reasoning overhead
"gpt-5.4": 1050000,
"gpt-5.3-codex": 400000,
"gpt-5.4-mini": 400000,

// Google Gemini
"google/gemini-3.5-flash": 1048576, // 1M
"google/gemini-3.1-flash-lite": 1048576, // 1M
}

// defaultContextLength is returned for unknown models when we can't determine
// their context window. Conservative — better to over-estimate and trigger
// compression early than to under-estimate and blow the window.
const defaultContextLength = 131072 // 128K

// tasteOneModelID is Command Code's internal model that ships free with all plans.
// Not exposed via upstream /v1/models API; hardcoded here so it's discoverable
// through the proxy.
const tasteOneModelID = "taste-1"

// tasteOneContextLength is the context window for taste-1. Set to a reasonable
// coding-agent window — tune if Command Code publishes a different number.
const tasteOneContextLength = 262144 // 256K

// ContextLengthFor returns the context window for a model ID.
// Falls back to defaultContextLength if the model is not in the map.
func ContextLengthFor(modelID string) int {
if n, ok := contextLengthMap[modelID]; ok {
return n
}
// Try matching by suffix (model names can have provider prefixes we don't track)
for knownID, n := range contextLengthMap {
if modelMatches(modelID, knownID) {
return n
}
}
return defaultContextLength
}

// modelMatches does fuzzy matching — strips common prefixes and compares.
// e.g. "minimax/MiniMax-M3" matches "MiniMaxAI/MiniMax-M3"
func modelMatches(query, known string) bool {
// Extract model name after the last "/"
getSuffix := func(s string) string {
for i := len(s) - 1; i >= 0; i-- {
if s[i] == '/' {
return s[i+1:]
}
}
return s
}
return getSuffix(query) == getSuffix(known)
}
44 changes: 44 additions & 0 deletions internal/proxy/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ func MapModel(name string) string {
return "MiniMaxAI/MiniMax-M2.7"
case "minimax-m2.5", "minimax2.5", "minimax":
return "MiniMaxAI/MiniMax-M2.5"
case "minimax-m3", "minimax3":
return "MiniMaxAI/MiniMax-M3"
case "minimax-m3-promo", "minimax3-promo":
return "MiniMaxAI/MiniMax-M3-Promo"
case "glm-5.2", "glm-52":
return "zai-org/GLM-5.2"
case "glm-5.1":
return "zai-org/GLM-5.1"
case "glm-5":
Expand All @@ -21,14 +27,52 @@ func MapModel(name string) string {
return "moonshotai/Kimi-K2.6"
case "kimi-k2.5", "kimi2.5":
return "moonshotai/Kimi-K2.5"
case "kimi-k2.7-code", "kimi2.7-code":
return "moonshotai/Kimi-K2.7-Code"
case "kimi-k2.7-code-highspeed", "kimi2.7-code-highspeed":
return "moonshotai/Kimi-K2.7-Code-Highspeed"
case "qwen-3.6-max-preview", "qwen3.6-max":
return "Qwen/Qwen3.6-Max-Preview"
case "qwen-3.6-plus", "qwen3.6-plus", "qwen3.6":
return "Qwen/Qwen3.6-Plus"
case "qwen-3.7-max", "qwen3.7-max":
return "Qwen/Qwen3.7-Max"
case "qwen-3.7-plus", "qwen3.7-plus", "qwen3.7":
return "Qwen/Qwen3.7-Plus"
case "step-3.5-flash", "step3.5":
return "stepfun/Step-3.5-Flash"
case "step-3.7-flash", "step3.7":
return "stepfun/Step-3.7-Flash"
case "mimo-v2.5-pro", "mimo2.5-pro":
return "xiaomi/mimo-v2.5-pro"
case "mimo-v2.5", "mimo2.5", "mimo":
return "xiaomi/mimo-v2.5"
case "nemotron-3-ultra", "nemotron":
return "nvidia/nemotron-3-ultra-550b-a55b"
case "claude-sonnet-4-6", "sonnet-4-6", "sonnet":
return "claude-sonnet-4-6"
case "claude-fable-5", "fable-5", "fable":
return "claude-fable-5"
case "claude-opus-4-8", "opus-4-8", "opus":
return "claude-opus-4-8"
case "claude-opus-4-7", "opus-4-7":
return "claude-opus-4-7"
case "claude-opus-4-6", "opus-4-6":
return "claude-opus-4-6"
case "claude-haiku-4-5", "haiku-4-5", "haiku":
return "claude-haiku-4-5"
case "gpt-5.5":
return "gpt-5.5"
case "gpt-5.4":
return "gpt-5.4"
case "gpt-5.3-codex", "codex":
return "gpt-5.3-codex"
case "gpt-5.4-mini", "gpt-mini":
return "gpt-5.4-mini"
case "gemini-3.1-flash-lite", "gemini-flash-lite":
return "google/gemini-3.1-flash-lite"
case "gemini-3.5-flash", "gemini-flash":
return "google/gemini-3.5-flash"
default:
return name // pass through as-is
}
Expand Down
Loading