diff --git a/src/agents/opencode-zen-models.test.ts b/src/agents/opencode-zen-models.test.ts index e176bd309..208ac18bb 100644 --- a/src/agents/opencode-zen-models.test.ts +++ b/src/agents/opencode-zen-models.test.ts @@ -51,16 +51,19 @@ describe("getOpencodeZenStaticFallbackModels", () => { it("returns an array of models", () => { const models = getOpencodeZenStaticFallbackModels(); expect(Array.isArray(models)).toBe(true); - expect(models.length).toBeGreaterThan(0); + expect(models.length).toBe(11); }); - it("includes Claude, GPT, and Gemini models", () => { + it("includes Claude, GPT, Gemini, GLM, and MiniMax models", () => { const models = getOpencodeZenStaticFallbackModels(); const ids = models.map((m) => m.id); expect(ids).toContain("claude-opus-4-5"); expect(ids).toContain("gpt-5.2"); + expect(ids).toContain("gpt-5.1-codex"); expect(ids).toContain("gemini-3-pro"); + expect(ids).toContain("glm-4.7-free"); + expect(ids).toContain("minimax-m2.1-free"); }); it("returns valid ModelDefinitionConfig objects", () => { @@ -80,9 +83,10 @@ describe("getOpencodeZenStaticFallbackModels", () => { describe("OPENCODE_ZEN_MODEL_ALIASES", () => { it("has expected aliases", () => { expect(OPENCODE_ZEN_MODEL_ALIASES.opus).toBe("claude-opus-4-5"); - expect(OPENCODE_ZEN_MODEL_ALIASES.sonnet).toBe("claude-sonnet-4-20250514"); + expect(OPENCODE_ZEN_MODEL_ALIASES.codex).toBe("gpt-5.1-codex"); expect(OPENCODE_ZEN_MODEL_ALIASES.gpt5).toBe("gpt-5.2"); - expect(OPENCODE_ZEN_MODEL_ALIASES.o1).toBe("o1-2025-04-16"); expect(OPENCODE_ZEN_MODEL_ALIASES.gemini).toBe("gemini-3-pro"); + expect(OPENCODE_ZEN_MODEL_ALIASES.glm).toBe("glm-4.7-free"); + expect(OPENCODE_ZEN_MODEL_ALIASES.minimax).toBe("minimax-m2.1-free"); }); }); diff --git a/src/agents/opencode-zen-models.ts b/src/agents/opencode-zen-models.ts index d00ddfd1b..4df00c4d9 100644 --- a/src/agents/opencode-zen-models.ts +++ b/src/agents/opencode-zen-models.ts @@ -24,32 +24,35 @@ const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour * Users can use "opus" instead of "claude-opus-4-5", etc. */ export const OPENCODE_ZEN_MODEL_ALIASES: Record = { - // Claude aliases + // Claude opus: "claude-opus-4-5", "opus-4.5": "claude-opus-4-5", - "opus-4": "claude-opus-4-5", - sonnet: "claude-sonnet-4-20250514", - "sonnet-4": "claude-sonnet-4-20250514", - haiku: "claude-haiku-3-5-20241022", - "haiku-3.5": "claude-haiku-3-5-20241022", - // GPT aliases + // GPT-5.x family gpt5: "gpt-5.2", "gpt-5": "gpt-5.2", - gpt4: "gpt-4.1", - "gpt-4": "gpt-4.1", - "gpt-mini": "gpt-4.1-mini", + "gpt-5.1": "gpt-5.1", - // O-series aliases - o1: "o1-2025-04-16", - o3: "o3-2025-04-16", - "o3-mini": "o3-mini-2025-04-16", + // Codex family + codex: "gpt-5.1-codex", + "codex-mini": "gpt-5.1-codex-mini", + "codex-max": "gpt-5.1-codex-max", - // Gemini aliases + // Gemini gemini: "gemini-3-pro", "gemini-pro": "gemini-3-pro", "gemini-3": "gemini-3-pro", - "gemini-2.5": "gemini-2.5-pro", + flash: "gemini-3-flash", + "gemini-flash": "gemini-3-flash", + + // GLM (free + alpha) + glm: "glm-4.7-free", + "glm-free": "glm-4.7-free", + "alpha-glm": "alpha-glm-4.7", + + // MiniMax + minimax: "minimax-m2.1-free", + "minimax-free": "minimax-m2.1-free", }; /** @@ -69,65 +72,85 @@ export function resolveOpencodeZenModelApi(_modelId: string): ModelApi { return "openai-completions"; } -/** - * Check if a model is a reasoning model (extended thinking). - */ -function isReasoningModel(modelId: string): boolean { - const lower = modelId.toLowerCase(); - return ( - lower.includes("opus") || - lower.startsWith("o1-") || - lower.startsWith("o3-") || - lower.startsWith("o4-") || - lower.includes("-thinking") - ); -} - /** * Check if a model supports image input. */ function supportsImageInput(modelId: string): boolean { const lower = modelId.toLowerCase(); - // Most modern models support images, except some reasoning-only models - if (lower.startsWith("o1-") || lower.startsWith("o3-")) { + if (lower.includes("glm") || lower.includes("minimax")) { return false; } return true; } -// Default cost structure (per million tokens, in USD cents) -// These are approximate; actual costs depend on OpenCode Zen pricing -const DEFAULT_COST = { - input: 0, // Included in subscription - output: 0, - cacheRead: 0, - cacheWrite: 0, +const MODEL_COSTS: Record< + string, + { input: number; output: number; cacheRead: number; cacheWrite: number } +> = { + "gpt-5.1-codex": { + input: 1.07, + output: 8.5, + cacheRead: 0.107, + cacheWrite: 0, + }, + "claude-opus-4-5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 }, + "gemini-3-pro": { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 }, + "alpha-glm-4.7": { input: 0.6, output: 2.2, cacheRead: 0.6, cacheWrite: 0 }, + "gpt-5.1-codex-mini": { + input: 0.25, + output: 2, + cacheRead: 0.025, + cacheWrite: 0, + }, + "gpt-5.1": { input: 1.07, output: 8.5, cacheRead: 0.107, cacheWrite: 0 }, + "glm-4.7-free": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + "gemini-3-flash": { input: 0.5, output: 3, cacheRead: 0.05, cacheWrite: 0 }, + "gpt-5.1-codex-max": { + input: 1.25, + output: 10, + cacheRead: 0.125, + cacheWrite: 0, + }, + "minimax-m2.1-free": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + "gpt-5.2": { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 }, +}; + +const DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }; + +const MODEL_CONTEXT_WINDOWS: Record = { + "gpt-5.1-codex": 400000, + "claude-opus-4-5": 200000, + "gemini-3-pro": 1048576, + "alpha-glm-4.7": 204800, + "gpt-5.1-codex-mini": 400000, + "gpt-5.1": 400000, + "glm-4.7-free": 204800, + "gemini-3-flash": 1048576, + "gpt-5.1-codex-max": 400000, + "minimax-m2.1-free": 204800, + "gpt-5.2": 400000, }; -// Default context windows by model family function getDefaultContextWindow(modelId: string): number { - const lower = modelId.toLowerCase(); - if (lower.includes("opus")) return 200000; - if (lower.includes("sonnet")) return 200000; - if (lower.includes("haiku")) return 200000; - if (lower.includes("gpt-5")) return 256000; - if (lower.includes("gpt-4")) return 128000; - if (lower.startsWith("o1-") || lower.startsWith("o3-")) return 200000; - if (lower.includes("gemini-3")) return 1000000; - if (lower.includes("gemini-2.5")) return 1000000; - if (lower.includes("gemini")) return 128000; - return 128000; // Conservative default + return MODEL_CONTEXT_WINDOWS[modelId] ?? 128000; } +const MODEL_MAX_TOKENS: Record = { + "gpt-5.1-codex": 128000, + "claude-opus-4-5": 64000, + "gemini-3-pro": 65536, + "alpha-glm-4.7": 131072, + "gpt-5.1-codex-mini": 128000, + "gpt-5.1": 128000, + "glm-4.7-free": 131072, + "gemini-3-flash": 65536, + "gpt-5.1-codex-max": 128000, + "minimax-m2.1-free": 131072, + "gpt-5.2": 128000, +}; + function getDefaultMaxTokens(modelId: string): number { - const lower = modelId.toLowerCase(); - if (lower.includes("opus")) return 32000; - if (lower.includes("sonnet")) return 16000; - if (lower.includes("haiku")) return 8192; - if (lower.startsWith("o1-") || lower.startsWith("o3-")) return 100000; - if (lower.includes("gpt")) return 16384; - if (lower.includes("gemini")) return 8192; - return 8192; + return MODEL_MAX_TOKENS[modelId] ?? 8192; } /** @@ -138,9 +161,9 @@ function buildModelDefinition(modelId: string): ModelDefinitionConfig { id: modelId, name: formatModelName(modelId), api: resolveOpencodeZenModelApi(modelId), - reasoning: isReasoningModel(modelId), + reasoning: true, input: supportsImageInput(modelId) ? ["text", "image"] : ["text"], - cost: DEFAULT_COST, + cost: MODEL_COSTS[modelId] ?? DEFAULT_COST, contextWindow: getDefaultContextWindow(modelId), maxTokens: getDefaultMaxTokens(modelId), }; @@ -149,28 +172,25 @@ function buildModelDefinition(modelId: string): ModelDefinitionConfig { /** * Format a model ID into a human-readable name. */ -function formatModelName(modelId: string): string { - // Handle common patterns - const replacements: Record = { - "claude-opus-4-5": "Claude Opus 4.5", - "claude-sonnet-4-20250514": "Claude Sonnet 4", - "claude-haiku-3-5-20241022": "Claude Haiku 3.5", - "gpt-5.2": "GPT-5.2", - "gpt-4.1": "GPT-4.1", - "gpt-4.1-mini": "GPT-4.1 Mini", - "o1-2025-04-16": "O1", - "o3-2025-04-16": "O3", - "o3-mini-2025-04-16": "O3 Mini", - "gemini-3-pro": "Gemini 3 Pro", - "gemini-2.5-pro": "Gemini 2.5 Pro", - "gemini-2.5-flash": "Gemini 2.5 Flash", - }; +const MODEL_NAMES: Record = { + "gpt-5.1-codex": "GPT-5.1 Codex", + "claude-opus-4-5": "Claude Opus 4.5", + "gemini-3-pro": "Gemini 3 Pro", + "alpha-glm-4.7": "Alpha GLM-4.7", + "gpt-5.1-codex-mini": "GPT-5.1 Codex Mini", + "gpt-5.1": "GPT-5.1", + "glm-4.7-free": "GLM-4.7", + "gemini-3-flash": "Gemini 3 Flash", + "gpt-5.1-codex-max": "GPT-5.1 Codex Max", + "minimax-m2.1-free": "MiniMax M2.1", + "gpt-5.2": "GPT-5.2", +}; - if (replacements[modelId]) { - return replacements[modelId]; +function formatModelName(modelId: string): string { + if (MODEL_NAMES[modelId]) { + return MODEL_NAMES[modelId]; } - // Generic formatting: capitalize and replace dashes return modelId .split("-") .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) @@ -179,29 +199,20 @@ function formatModelName(modelId: string): string { /** * Static fallback models when API is unreachable. - * These are the most commonly used models. */ export function getOpencodeZenStaticFallbackModels(): ModelDefinitionConfig[] { const modelIds = [ - // Claude models + "gpt-5.1-codex", "claude-opus-4-5", - "claude-sonnet-4-20250514", - "claude-haiku-3-5-20241022", - - // GPT models - "gpt-5.2", - "gpt-4.1", - "gpt-4.1-mini", - - // O-series reasoning models - "o1-2025-04-16", - "o3-2025-04-16", - "o3-mini-2025-04-16", - - // Gemini models "gemini-3-pro", - "gemini-2.5-pro", - "gemini-2.5-flash", + "alpha-glm-4.7", + "gpt-5.1-codex-mini", + "gpt-5.1", + "glm-4.7-free", + "gemini-3-flash", + "gpt-5.1-codex-max", + "minimax-m2.1-free", + "gpt-5.2", ]; return modelIds.map(buildModelDefinition);