From eee3515e5419cd0824bedb4beb0e0d91dd662b50 Mon Sep 17 00:00:00 2001 From: decolua Date: Thu, 10 Sep 2026 21:08:32 +0700 Subject: [PATCH 1/2] feat(opencode-go): add newly published Go models Add the models the provider docs now list but the registry lacked: chat/completions glm-5.3, kimi-k3, deepseek-flash, longcat-2.0, hy4-preview, hy3 + /messages qwen3.8-max, qwen3.8-flash responses only grok-4.6, gpt-5.6-luna Endpoints follow the table at https://opencode.ai/docs/go/. chat-only models stay on the sourceFormat-matched transport guard so a Claude client is never routed to /messages for a model that lacks it. Co-Authored-By: Claude Code --- open-sse/providers/registry/opencode-go.js | 15 +++++++++++++-- tests/unit/opencode-go-models.test.js | 18 +++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/open-sse/providers/registry/opencode-go.js b/open-sse/providers/registry/opencode-go.js index c6673223..a715a98e 100644 --- a/open-sse/providers/registry/opencode-go.js +++ b/open-sse/providers/registry/opencode-go.js @@ -33,25 +33,36 @@ export default { { format: "claude", baseUrl: "https://opencode.ai/zen/go/v1/messages", auth: { combined: true, header: "x-api-key", scheme: "raw", anthropicVersion: true } }, { format: "openai-responses", baseUrl: "https://opencode.ai/zen/go/v1/responses", auth: { combined: true, header: "Authorization", scheme: "bearer" } }, ], + // supportedFormats follow the endpoint table in https://opencode.ai/docs/go/ models: [ { id: "glm-5.3-flash", name: "GLM 5.3 Flash (Vision)", supportedFormats: ["openai"] }, + { id: "glm-5.3", name: "GLM 5.3", supportedFormats: ["openai"] }, { id: "glm-5.2", name: "GLM 5.2", supportedFormats: ["openai"] }, { id: "glm-5.1", name: "GLM 5.1", supportedFormats: ["openai"] }, { id: "kimi-k2.7-code", name: "Kimi K2.7 Code", supportedFormats: ["openai"] }, { id: "kimi-k2.6", name: "Kimi K2.6", supportedFormats: ["openai"] }, + { id: "kimi-k3", name: "Kimi K3", supportedFormats: ["openai"] }, { id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", supportedFormats: ["openai", "claude", "openai-responses"] }, { id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", supportedFormats: ["openai", "claude", "openai-responses"] }, { id: "deepseek-v4-flash-vision-exp", name: "DeepSeek V4 Flash Vision (Exp)", supportedFormats: ["openai", "claude", "openai-responses"] }, + { id: "deepseek-flash", name: "DeepSeek V4.1 Flash", supportedFormats: ["openai"] }, + { id: "longcat-2.0", name: "LongCat 2.0", supportedFormats: ["openai"] }, { id: "mimo-v2.5", name: "MiMo V2.5", supportedFormats: ["openai"] }, { id: "mimo-v2.5-pro", name: "MiMo V2.5 Pro", supportedFormats: ["openai"] }, { id: "minimax-m3", name: "MiniMax M3", supportedFormats: ["openai", "claude"] }, { id: "minimax-m2.7", name: "MiniMax M2.7", supportedFormats: ["openai", "claude"] }, { id: "minimax-m2.5", name: "MiniMax M2.5", supportedFormats: ["openai", "claude"] }, + { id: "qwen3.8-max", name: "Qwen 3.8 Max", supportedFormats: ["openai", "claude"] }, + { id: "qwen3.8-flash", name: "Qwen 3.8 Flash", supportedFormats: ["openai", "claude"] }, { id: "qwen3.7-max", name: "Qwen 3.7 Max", supportedFormats: ["openai", "claude"] }, { id: "qwen3.7-plus", name: "Qwen 3.7 Plus", supportedFormats: ["openai", "claude"] }, { id: "qwen3.6-plus", name: "Qwen 3.6 Plus", supportedFormats: ["openai", "claude"] }, - // Muse Spark is served by /zen/go/v1/responses only — responses-only entry forces - // chatCore past the sourceFormat-matched transports into translation (see chatCore guard). + { id: "hy4-preview", name: "Hy4 Preview", supportedFormats: ["openai"] }, + { id: "hy3", name: "Hy3", supportedFormats: ["openai"] }, + // Served by /zen/go/v1/responses only — the responses-only entry forces chatCore + // past the sourceFormat-matched transports into translation (see chatCore guard). + { id: "grok-4.6", name: "Grok 4.6", targetFormat: "openai-responses", supportedFormats: ["openai-responses"] }, + { id: "gpt-5.6-luna", name: "GPT 5.6 Luna", targetFormat: "openai-responses", supportedFormats: ["openai-responses"] }, { id: "muse-spark-1.2-contributor", name: "Muse Spark 1.2 Contributor", targetFormat: "openai-responses", supportedFormats: ["openai-responses"] }, { id: "muse-spark-1.3-contributor", name: "Muse Spark 1.3 Contributor", targetFormat: "openai-responses", supportedFormats: ["openai-responses"] }, ], diff --git a/tests/unit/opencode-go-models.test.js b/tests/unit/opencode-go-models.test.js index 7ece100a..0ed0804b 100644 --- a/tests/unit/opencode-go-models.test.js +++ b/tests/unit/opencode-go-models.test.js @@ -4,9 +4,11 @@ import { PROVIDERS } from "../../open-sse/config/providers.js"; import { resolveTransport } from "../../open-sse/services/provider.js"; // Chat-only models (no /messages, no /responses support on opencode-go) -const CHAT_ONLY = ["glm-5.2", "glm-5.1", "kimi-k2.7-code", "kimi-k2.6", "mimo-v2.5", "mimo-v2.5-pro"]; +const CHAT_ONLY = ["glm-5.3", "glm-5.2", "glm-5.1", "kimi-k2.7-code", "kimi-k2.6", "kimi-k3", + "deepseek-flash", "longcat-2.0", "mimo-v2.5", "mimo-v2.5-pro", "hy4-preview", "hy3"]; // Models that also expose the Anthropic /messages endpoint -const CLAUDE_CAPABLE = ["minimax-m3", "minimax-m2.7", "minimax-m2.5", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus"]; +const CLAUDE_CAPABLE = ["minimax-m3", "minimax-m2.7", "minimax-m2.5", + "qwen3.8-max", "qwen3.8-flash", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus"]; // Models that also expose the OpenAI /responses endpoint const RESPONSES_CAPABLE = ["deepseek-v4-pro", "deepseek-v4-flash"]; @@ -22,11 +24,13 @@ describe("OpenCode Go model catalog", () => { it("matches the documented model IDs", () => { const ids = (PROVIDER_MODELS["opencode-go"] || []).map((m) => m.id); expect(ids).toEqual([ - "glm-5.3-flash", "glm-5.2", "glm-5.1", "kimi-k2.7-code", "kimi-k2.6", - "deepseek-v4-pro", "deepseek-v4-flash", "deepseek-v4-flash-vision-exp", - "mimo-v2.5", "mimo-v2.5-pro", + "glm-5.3-flash", "glm-5.3", "glm-5.2", "glm-5.1", "kimi-k2.7-code", "kimi-k2.6", "kimi-k3", + "deepseek-v4-pro", "deepseek-v4-flash", "deepseek-v4-flash-vision-exp", "deepseek-flash", + "longcat-2.0", "mimo-v2.5", "mimo-v2.5-pro", "minimax-m3", "minimax-m2.7", "minimax-m2.5", - "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", + "qwen3.8-max", "qwen3.8-flash", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", + "hy4-preview", "hy3", + "grok-4.6", "gpt-5.6-luna", "muse-spark-1.2-contributor", "muse-spark-1.3-contributor", ]); }); @@ -91,7 +95,7 @@ describe("OpenCode Go per-model transport guard (chatCore logic)", () => { }); it("routes Muse Spark (responses-only) to /responses, never to /messages", () => { - for (const m of ["muse-spark-1.2-contributor", "muse-spark-1.3-contributor"]) { + for (const m of ["muse-spark-1.2-contributor", "muse-spark-1.3-contributor", "grok-4.6", "gpt-5.6-luna"]) { expect(getModelSupportedFormats("opencode-go", m)).toEqual(["openai-responses"]); expect(pickTransport("opencode-go", "openai-responses", "opencode-go", m)?.baseUrl).toBe("https://opencode.ai/zen/go/v1/responses"); expect(pickTransport("opencode-go", "claude", "opencode-go", m)).toBeNull(); From a7047a07d475207a6685b1d288f1aa57c027f405 Mon Sep 17 00:00:00 2001 From: decolua Date: Thu, 10 Sep 2026 21:23:24 +0700 Subject: [PATCH 2/2] fix(codex): restore Version header and single-source the CLI version The image handler's `version` header was commented out, so Codex image requests reached chatgpt.com without the Version identity the backend expects. Restore it and route every Codex identity header through one constant. The CLI version now lives on registry codex.transport as `cliVersion` (the same pattern gemini-cli uses) and is re-exported as CODEX_CLI_VERSION, so the registry User-Agent, the image handler and the connection test can no longer drift apart. Bumped 0.136.0 -> 0.154.0 (current stable). Co-Authored-By: Claude Code --- open-sse/config/appConstants.js | 3 +++ open-sse/handlers/imageProviders/codex.js | 6 +++--- open-sse/providers/registry/codex.js | 7 ++++++- src/app/api/providers/[id]/test/testUtils.js | 3 ++- tests/__baseline__/providers-baseline.json | 3 ++- tests/unit/image-generation.test.js | 2 +- 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/open-sse/config/appConstants.js b/open-sse/config/appConstants.js index 3e18633e..4df1463e 100644 --- a/open-sse/config/appConstants.js +++ b/open-sse/config/appConstants.js @@ -7,6 +7,9 @@ import { createRequire } from "module"; export const GEMINI_CLI_VERSION = PROVIDERS["gemini-cli"]?.cliVersion; export const GEMINI_CLI_API_CLIENT = PROVIDERS["gemini-cli"]?.apiClient; +// === Codex CLI === derive từ registry codex.transport +export const CODEX_CLI_VERSION = PROVIDERS["codex"]?.cliVersion; + // Map Node arch to Gemini CLI arch string (x64/x86/arm64/...) function geminiCLIArch() { const a = arch(); diff --git a/open-sse/handlers/imageProviders/codex.js b/open-sse/handlers/imageProviders/codex.js index 218302ab..385ec5b6 100644 --- a/open-sse/handlers/imageProviders/codex.js +++ b/open-sse/handlers/imageProviders/codex.js @@ -2,10 +2,10 @@ import { randomUUID } from "node:crypto"; import { nowSec } from "./_base.js"; import { PROVIDERS } from "../../config/providers.js"; +import { CODEX_CLI_VERSION } from "../../config/appConstants.js"; const CODEX_RESPONSES_URL = PROVIDERS["codex"].baseUrl; -const CODEX_USER_AGENT = "codex_cli_rs/0.136.0"; -const CODEX_VERSION = "0.136.0"; +const CODEX_USER_AGENT = `codex_cli_rs/${CODEX_CLI_VERSION}`; const CODEX_ORIGINATOR = "codex_cli_rs"; const CODEX_MODEL_SUFFIX = "-image"; const CODEX_REF_DETAIL = "high"; @@ -157,7 +157,7 @@ export default { "originator": CODEX_ORIGINATOR, "session_id": randomUUID(), "user-agent": CODEX_USER_AGENT, - "version": CODEX_VERSION, + "version": CODEX_CLI_VERSION, "x-client-request-id": randomUUID(), }; }, diff --git a/open-sse/providers/registry/codex.js b/open-sse/providers/registry/codex.js index 1909fe97..bf436a10 100644 --- a/open-sse/providers/registry/codex.js +++ b/open-sse/providers/registry/codex.js @@ -1,5 +1,9 @@ import { withCodexReviewModels } from "../models/helpers.js"; +// Codex CLI version seen by OpenAI's backend — single source for the Version / +// User-Agent identity headers. Bump when the installed codex CLI is upgraded. +const CODEX_CLI_VERSION = "0.154.0"; + export default { id: "codex", priority: 30, @@ -34,9 +38,10 @@ export default { baseUrl: "https://chatgpt.com/backend-api/codex/responses", format: "openai-responses", forceStream: true, + cliVersion: CODEX_CLI_VERSION, headers: { originator: "codex_cli_rs", - "User-Agent": "codex_cli_rs/0.136.0", + "User-Agent": `codex_cli_rs/${CODEX_CLI_VERSION}`, }, usage: { url: "https://chatgpt.com/backend-api/wham/usage", diff --git a/src/app/api/providers/[id]/test/testUtils.js b/src/app/api/providers/[id]/test/testUtils.js index bd0c4782..9572e69e 100644 --- a/src/app/api/providers/[id]/test/testUtils.js +++ b/src/app/api/providers/[id]/test/testUtils.js @@ -4,6 +4,7 @@ import { testProxyUrl } from "@/lib/network/proxyTest"; import { isOpenAICompatibleProvider, isAnthropicCompatibleProvider } from "@/shared/constants/providers"; import { getDefaultModel } from "open-sse/config/providerModels.js"; import { resolveOllamaLocalHost, PROVIDERS } from "open-sse/config/providers.js"; +import { CODEX_CLI_VERSION } from "open-sse/config/appConstants.js"; import { refreshProviderCredentials, shouldRefreshCredentials, @@ -27,7 +28,7 @@ const OAUTH_TEST_CONFIG = { method: "POST", authHeader: "Authorization", authPrefix: "Bearer ", - extraHeaders: { "Content-Type": "application/json", "originator": "codex_cli_rs", "User-Agent": "codex_cli_rs/0.136.0" }, + extraHeaders: { "Content-Type": "application/json", "originator": "codex_cli_rs", "User-Agent": `codex_cli_rs/${CODEX_CLI_VERSION}` }, // Minimal invalid body — triggers fast 400 without consuming quota body: JSON.stringify({ model: "gpt-5.3-codex", input: [], stream: false, store: false }), // 400 (bad request) means auth succeeded; only 401/403 means token is bad diff --git a/tests/__baseline__/providers-baseline.json b/tests/__baseline__/providers-baseline.json index db0b8bad..2dd09f55 100644 --- a/tests/__baseline__/providers-baseline.json +++ b/tests/__baseline__/providers-baseline.json @@ -192,9 +192,10 @@ "baseUrl": "https://chatgpt.com/backend-api/codex/responses", "format": "openai-responses", "forceStream": true, + "cliVersion": "0.154.0", "headers": { "originator": "codex_cli_rs", - "User-Agent": "codex_cli_rs/0.136.0" + "User-Agent": "codex_cli_rs/0.154.0" }, "usage": { "url": "https://chatgpt.com/backend-api/wham/usage", diff --git a/tests/unit/image-generation.test.js b/tests/unit/image-generation.test.js index 12dce95d..4df94afc 100644 --- a/tests/unit/image-generation.test.js +++ b/tests/unit/image-generation.test.js @@ -351,7 +351,7 @@ describe("handleImageGenerationCore", () => { headers: expect.objectContaining({ authorization: "Bearer codex-token", "chatgpt-account-id": "account-123", - version: "0.136.0", + version: "0.154.0", }), }) );