mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 22:32:12 +03:00
fix(opencode-zen): add 'opencode' provider alias and sync model list with live API
OpenCode's Zen provider changed its slug from 'opencode-zen' to 'opencode', breaking OmniRoute's provider resolution when users reference models with the new prefix (e.g. 'opencode/deepseek-v4-flash-free'). Changes: 1. open-sse/services/model.ts: Add manual ALIAS_TO_PROVIDER_ID entry mapping 'opencode' → 'opencode-zen' so parseModel() resolves correctly for model strings using the new slug. 2. open-sse/executors/index.ts: Register 'opencode' as an OpencodeExecutor alias for 'opencode-zen' so getExecutor() returns the correct executor. 3. open-sse/config/providerRegistry.ts: Update opencode-zen model list to match the live API at https://opencode.ai/zen/v1/models: - Add deepseek-v4-flash-free (the model users reported as broken) - Add all 30+ models from the API (Claude, GPT, Gemini, Grok, GLM, MiniMax, Kimi, Qwen series) - Apply targetFormat: 'claude' to qwen3.5-plus (same SSE bug as qwen3.6) - Remove ling-2.6-1t-free and trinity-large-preview-free (no longer in API) - Enable passthroughModels so new models work without code deploys 4. @omniroute/opencode-provider/src/index.ts: Remove broken reference to undefined OMNIROUTE_DEFAULT_MODEL_CONTEXT_LENGTHS constant. 5. tests/unit/opencode-executor.test.ts: Add tests for opencode alias, deepseek-v4-flash-free routing, and model registry presence.
This commit is contained in:
@@ -235,6 +235,7 @@ export function createOmniRouteProvider(options: OmniRouteProviderOptions): Open
|
||||
if (typeof merged.reasoning === "boolean") entry.reasoning = merged.reasoning;
|
||||
if (typeof merged.temperature === "boolean") entry.temperature = merged.temperature;
|
||||
if (typeof merged.tool_call === "boolean") entry.tool_call = merged.tool_call;
|
||||
|
||||
models[id] = entry;
|
||||
}
|
||||
|
||||
|
||||
@@ -1244,23 +1244,71 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
authHeader: "Authorization",
|
||||
authPrefix: "Bearer",
|
||||
defaultContextLength: 200000,
|
||||
// Sync with https://opencode.ai/zen/v1/models — this list is regenerated
|
||||
// from the live API response so new models work without a code deploy.
|
||||
passthroughModels: true,
|
||||
models: [
|
||||
// ── Chat / Coding ──────────────────────────────────────────
|
||||
{ id: "big-pickle", name: "Big Pickle" },
|
||||
{ id: "gpt-5-nano", name: "GPT 5 Nano", contextLength: 400000 },
|
||||
{ id: "gpt-5", name: "GPT 5" },
|
||||
{ id: "gpt-5-codex", name: "GPT 5 Codex" },
|
||||
{ id: "gpt-5.1", name: "GPT 5.1" },
|
||||
{ id: "gpt-5.1-codex", name: "GPT 5.1 Codex" },
|
||||
{ id: "gpt-5.1-codex-max", name: "GPT 5.1 Codex Max" },
|
||||
{ id: "gpt-5.1-codex-mini", name: "GPT 5.1 Codex Mini" },
|
||||
{ id: "gpt-5.2", name: "GPT 5.2" },
|
||||
{ id: "gpt-5.2-codex", name: "GPT 5.2 Codex" },
|
||||
{ id: "gpt-5.3-codex", name: "GPT 5.3 Codex" },
|
||||
{ id: "gpt-5.3-codex-spark", name: "GPT 5.3 Codex Spark" },
|
||||
{ id: "gpt-5.4", name: "GPT 5.4" },
|
||||
{ id: "gpt-5.4-mini", name: "GPT 5.4 Mini" },
|
||||
{ id: "gpt-5.4-nano", name: "GPT 5.4 Nano" },
|
||||
{ id: "gpt-5.4-pro", name: "GPT 5.4 Pro" },
|
||||
{ id: "gpt-5.5", name: "GPT 5.5" },
|
||||
{ id: "gpt-5.5-pro", name: "GPT 5.5 Pro" },
|
||||
|
||||
// ── Claude ─────────────────────────────────────────────────
|
||||
{ id: "claude-haiku-4-5", name: "Claude Haiku 4.5" },
|
||||
{ id: "claude-sonnet-4", name: "Claude Sonnet 4" },
|
||||
{ id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5" },
|
||||
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
|
||||
{ id: "claude-opus-4-1", name: "Claude Opus 4.1" },
|
||||
{ id: "claude-opus-4-5", name: "Claude Opus 4.5" },
|
||||
{ id: "claude-opus-4-6", name: "Claude Opus 4.6" },
|
||||
{ id: "claude-opus-4-7", name: "Claude Opus 4.7" },
|
||||
|
||||
// ── Gemini ─────────────────────────────────────────────────
|
||||
{ id: "gemini-3-flash", name: "Gemini 3 Flash" },
|
||||
{ id: "gemini-3.1-pro", name: "Gemini 3.1 Pro" },
|
||||
{ id: "gemini-3.5-flash", name: "Gemini 3.5 Flash" },
|
||||
|
||||
// ── Grok ───────────────────────────────────────────────────
|
||||
{ id: "grok-build-0.1", name: "Grok Build 0.1" },
|
||||
|
||||
// ── GLM / Z.AI ─────────────────────────────────────────────
|
||||
{ id: "glm-5", name: "GLM-5" },
|
||||
{ id: "glm-5.1", name: "GLM-5.1" },
|
||||
|
||||
// ── MiniMax ────────────────────────────────────────────────
|
||||
{ id: "minimax-m2.5", name: "MiniMax M2.5" },
|
||||
{ id: "minimax-m2.7", name: "MiniMax M2.7" },
|
||||
|
||||
// ── Kimi / Moonshot ────────────────────────────────────────
|
||||
{ id: "kimi-k2.5", name: "Kimi K2.5" },
|
||||
{ id: "kimi-k2.6", name: "Kimi K2.6" },
|
||||
|
||||
// ── Qwen ───────────────────────────────────────────────────
|
||||
// Issue #2292: Qwen models return Claude-format SSE bodies even
|
||||
// when hitting /chat/completions. targetFormat: "claude" routes
|
||||
// through /messages and the Claude translator.
|
||||
{ id: "qwen3.5-plus", name: "Qwen3.5 Plus", targetFormat: "claude" },
|
||||
{ id: "qwen3.6-plus", name: "Qwen3.6 Plus", targetFormat: "claude" },
|
||||
|
||||
// ── Free Tier ──────────────────────────────────────────────
|
||||
{ id: "deepseek-v4-flash-free", name: "DeepSeek V4 Flash Free", supportsReasoning: true },
|
||||
{ id: "minimax-m2.5-free", name: "MiniMax M2.5 Free", contextLength: 204800 },
|
||||
{ id: "ling-2.6-1t-free", name: "Ling 2.6 Free", contextLength: 262000 },
|
||||
{
|
||||
id: "trinity-large-preview-free",
|
||||
name: "Trinity Large Preview Free",
|
||||
contextLength: 131000,
|
||||
},
|
||||
{ id: "nemotron-3-super-free", name: "Nemotron 3 Super Free", contextLength: 1000000 },
|
||||
// Issue #2292: opencode-zen returns Claude-format SSE bodies for these
|
||||
// Qwen3.6 models even when the request hits the OpenAI-compatible
|
||||
// /chat/completions endpoint. Flagging targetFormat: "claude" routes
|
||||
// the request to /messages and parses the response with the Claude
|
||||
// translator, fixing "expected choices (array), received undefined".
|
||||
{ id: "qwen3.6-plus", name: "Qwen3.6 Plus", targetFormat: "claude", contextLength: 200000 },
|
||||
{
|
||||
id: "qwen3.6-plus-free",
|
||||
name: "Qwen3.6 Plus Free",
|
||||
|
||||
@@ -58,6 +58,7 @@ const executors = {
|
||||
cf: new CloudflareAIExecutor(), // Alias
|
||||
"opencode-zen": new OpencodeExecutor("opencode-zen"),
|
||||
"opencode-go": new OpencodeExecutor("opencode-go"),
|
||||
opencode: new OpencodeExecutor("opencode-zen"), // Alias for opencode-zen
|
||||
puter: new PuterExecutor(),
|
||||
pu: new PuterExecutor(), // Alias
|
||||
vertex: new VertexExecutor(),
|
||||
|
||||
@@ -29,6 +29,11 @@ for (const [id, alias] of Object.entries(PROVIDER_ID_TO_ALIAS)) {
|
||||
ALIAS_TO_PROVIDER_ID[alias] = id;
|
||||
}
|
||||
|
||||
// Manual aliases for external compatibility not covered by PROVIDER_ID_TO_ALIAS.
|
||||
// OpenCode's Zen provider now uses the "opencode" slug, but OmniRoute registers
|
||||
// it as "opencode-zen". This alias ensures `opencode/<model>` resolves correctly.
|
||||
ALIAS_TO_PROVIDER_ID["opencode"] = "opencode-zen";
|
||||
|
||||
// Provider-scoped legacy model aliases. Used to normalize provider/model inputs
|
||||
// and keep backward compatibility when upstream IDs change.
|
||||
const PROVIDER_MODEL_ALIASES: ProviderModelAliasMap = {
|
||||
|
||||
@@ -56,6 +56,26 @@ describe("OpencodeExecutor", () => {
|
||||
});
|
||||
|
||||
describe("execute", () => {
|
||||
it('resolves "opencode" executor alias to opencode-zen config', async () => {
|
||||
const aliasExecutor = new OpencodeExecutor("opencode-zen");
|
||||
const result = await aliasExecutor.execute(createInput("deepseek-v4-flash-free"));
|
||||
assert.equal(result.url, "https://opencode.ai/zen/v1/chat/completions");
|
||||
assert.equal(fetchCalls[0].url, "https://opencode.ai/zen/v1/chat/completions");
|
||||
});
|
||||
|
||||
it("routes deepseek-v4-flash-free to chat completions", async () => {
|
||||
const result = await zenExecutor.execute(createInput("deepseek-v4-flash-free"));
|
||||
assert.equal(result.url, "https://opencode.ai/zen/v1/chat/completions");
|
||||
assert.equal(fetchCalls[0].url, "https://opencode.ai/zen/v1/chat/completions");
|
||||
});
|
||||
|
||||
it("includes deepseek-v4-flash-free in opencode-zen PROVIDER_MODELS", () => {
|
||||
const models = PROVIDER_MODELS["opencode-zen"];
|
||||
const model = models?.find((m) => m.id === "deepseek-v4-flash-free");
|
||||
assert.ok(model, "deepseek-v4-flash-free should be in opencode-zen model list");
|
||||
assert.equal(model.name, "DeepSeek V4 Flash Free");
|
||||
assert.equal(model.supportsReasoning, true);
|
||||
});
|
||||
it("routes opencode zen default models to chat completions", async () => {
|
||||
const minimaxResult = await zenExecutor.execute(createInput("minimax-m2.5-free"));
|
||||
assert.equal(minimaxResult.url, "https://opencode.ai/zen/v1/chat/completions");
|
||||
|
||||
Reference in New Issue
Block a user