Merge pull request #669 from magimetal/opencode-zen-model-defaults
feat(opencode-zen): update models with sensible defaults
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
- Onboarding/Models: add first-class Z.AI (GLM) auth choice (`zai-api-key`) + `--zai-api-key` flag.
|
||||
- Agents: add human-delay pacing between block replies (modes: off/natural/custom, per-agent configurable). (#446) — thanks @tony-freedomology.
|
||||
- Onboarding/Models: add catalog-backed default model picker to onboarding + configure. (#611) — thanks @jonasjancarik.
|
||||
- Agents/OpenCode Zen: update fallback models + defaults, keep legacy alias mappings. (#669) — thanks @magimetal.
|
||||
|
||||
### Fixes
|
||||
- Auto-reply: prefer `RawBody` for command/directive parsing (WhatsApp + Discord) and prevent fallback runs from clobbering concurrent session updates. (#643) — thanks @mcinteerj.
|
||||
|
||||
@@ -12,6 +12,14 @@ describe("resolveOpencodeZenAlias", () => {
|
||||
expect(resolveOpencodeZenAlias("opus")).toBe("claude-opus-4-5");
|
||||
});
|
||||
|
||||
it("keeps legacy aliases working", () => {
|
||||
expect(resolveOpencodeZenAlias("sonnet")).toBe("claude-opus-4-5");
|
||||
expect(resolveOpencodeZenAlias("haiku")).toBe("claude-opus-4-5");
|
||||
expect(resolveOpencodeZenAlias("gpt4")).toBe("gpt-5.1");
|
||||
expect(resolveOpencodeZenAlias("o1")).toBe("gpt-5.2");
|
||||
expect(resolveOpencodeZenAlias("gemini-2.5")).toBe("gemini-3-pro");
|
||||
});
|
||||
|
||||
it("resolves gpt5 alias", () => {
|
||||
expect(resolveOpencodeZenAlias("gpt5")).toBe("gpt-5.2");
|
||||
});
|
||||
@@ -51,16 +59,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 +91,17 @@ 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");
|
||||
|
||||
// Legacy aliases (kept for backward compatibility).
|
||||
expect(OPENCODE_ZEN_MODEL_ALIASES.sonnet).toBe("claude-opus-4-5");
|
||||
expect(OPENCODE_ZEN_MODEL_ALIASES.haiku).toBe("claude-opus-4-5");
|
||||
expect(OPENCODE_ZEN_MODEL_ALIASES.gpt4).toBe("gpt-5.1");
|
||||
expect(OPENCODE_ZEN_MODEL_ALIASES.o1).toBe("gpt-5.2");
|
||||
expect(OPENCODE_ZEN_MODEL_ALIASES["gemini-2.5"]).toBe("gemini-3-pro");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,32 +24,57 @@ 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
|
||||
// Legacy Claude aliases (OpenCode Zen rotates model catalogs; keep old keys working).
|
||||
sonnet: "claude-opus-4-5",
|
||||
"sonnet-4": "claude-opus-4-5",
|
||||
haiku: "claude-opus-4-5",
|
||||
"haiku-3.5": "claude-opus-4-5",
|
||||
|
||||
// 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",
|
||||
// Legacy GPT aliases (keep old config/docs stable; map to closest current equivalents).
|
||||
gpt4: "gpt-5.1",
|
||||
"gpt-4": "gpt-5.1",
|
||||
"gpt-mini": "gpt-5.1-codex-mini",
|
||||
|
||||
// Gemini aliases
|
||||
// Legacy O-series aliases (no longer in the Zen catalog; map to a strong default).
|
||||
o1: "gpt-5.2",
|
||||
o3: "gpt-5.2",
|
||||
"o3-mini": "gpt-5.1-codex-mini",
|
||||
|
||||
// Codex family
|
||||
codex: "gpt-5.1-codex",
|
||||
"codex-mini": "gpt-5.1-codex-mini",
|
||||
"codex-max": "gpt-5.1-codex-max",
|
||||
|
||||
// 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",
|
||||
|
||||
// Legacy Gemini 2.5 aliases (map to the nearest current Gemini tier).
|
||||
"gemini-2.5": "gemini-3-pro",
|
||||
"gemini-2.5-pro": "gemini-3-pro",
|
||||
"gemini-2.5-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 +94,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 +183,10 @@ function buildModelDefinition(modelId: string): ModelDefinitionConfig {
|
||||
id: modelId,
|
||||
name: formatModelName(modelId),
|
||||
api: resolveOpencodeZenModelApi(modelId),
|
||||
reasoning: isReasoningModel(modelId),
|
||||
// Treat Zen models as reasoning-capable so defaults pick thinkLevel="low" unless users opt out.
|
||||
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 +195,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 +222,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