feat(opencode-zen): update models with sensible defaults
- Replace model catalog with 11 current models: gpt-5.1-codex, claude-opus-4-5, gemini-3-pro, 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 - Add accurate per-token costs from OpenCode Zen pricing - Add accurate context windows and output limits - Update aliases for new model families (codex, glm, minimax) - Remove deprecated models (sonnet, haiku, o-series, gemini-2.5)
This commit is contained in:
committed by
Peter Steinberger
parent
9b5ce2530a
commit
738269eb74
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<string, string> = {
|
||||
// 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<string, number> = {
|
||||
"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<string, number> = {
|
||||
"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<string, string> = {
|
||||
"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<string, string> = {
|
||||
"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);
|
||||
|
||||
Reference in New Issue
Block a user