diff --git a/CHANGELOG.md b/CHANGELOG.md index af055b7e56..a3c327a8af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ ### 🐛 Fixed +- **fix(providers): register BytePlus ModelArk so its API key can be added** — adding a BytePlus (`ark-…`) key reported "invalid". `byteplus` was present in the provider catalog (`APIKEY_PROVIDERS`) but **never registered in the routing registry**, so key validation fell through to `{ unsupported: true }` → HTTP 400 → the UI rendered every key as invalid (and the provider was unusable for inference). Added a registry entry modeled on the existing Volcengine Ark provider: OpenAI-compatible format, base `https://ark.ap-southeast.bytepluses.com/api/v3` (region `ap-southeast-1`), `Authorization: Bearer` auth, seeded with the catalog's advertised models (Seed 2.0, Kimi K2 Thinking, GLM 4.7, GPT-OSS-120B). ([#3877](https://github.com/diegosouzapw/OmniRoute/issues/3877) — thanks @nikohd12) - **fix(providers): Nous Research key validation no longer fails on a stale probe model** — adding a valid Nous Research API key reported "invalid" even though the same key worked via the portal's copy-shell `curl`. The validation probe sent `model: "nousresearch/hermes-4-70b"`, which Nous does not serve, so the API returned `400` and the validator (which only treated `200`/`429` as success) reported the key invalid. The probe now uses the real `Hermes-4-70B` slug, and any non-auth 4xx (`400`/`404`/`422`) is treated as a valid key (the request shape was wrong, not the credentials) — mirroring the longcat/nvidia validators so a future model rename can't re-break key validation. ([#3881](https://github.com/diegosouzapw/OmniRoute/issues/3881) — thanks @FerLuisxd) - **test(oauth): prove refresh_token preservation for the real gemini-cli / antigravity dispatch** — the #3679/#3766 regression test used a synthetic provider that routes through the generic `tokenUrl` path, so the fix was never proven for the actual Google-family providers, which dispatch through `refreshGoogleToken()` against the hardcoded `OAUTH_ENDPOINTS.google.token`. Added a test that drives `checkConnection` through the real `gemini-cli`/`antigravity` path (redirecting the Google token endpoint to a local server returning `invalid_grant`) and asserts the `refresh_token` is preserved (not nulled) — confirming these connections are not spuriously destroyed on a failed refresh. ([#3850](https://github.com/diegosouzapw/OmniRoute/issues/3850) — thanks @3xa228148) - **fix(oauth): clear setup message for GitLab Duo instead of "Internal server error"** — adding a GitLab Duo connection without a registered OAuth client returned an opaque `Internal server error` at the Add Connection step. `buildAuthUrl` **threw** when `GITLAB_DUO_OAUTH_CLIENT_ID` was missing, and the route swallowed it into a generic 500. It now returns `null` (mirroring the Qoder provider) and the authorize route surfaces an actionable message: register an OAuth app at `https://gitlab.com/-/profile/applications` with redirect URI `http://localhost:20128/callback` and scopes `ai_features read_user`, then set `GITLAB_DUO_OAUTH_CLIENT_ID`. ([#3861](https://github.com/diegosouzapw/OmniRoute/issues/3861) — thanks @sidinsearch) diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json index 76b38306df..e344448af6 100644 --- a/config/quality/file-size-baseline.json +++ b/config/quality/file-size-baseline.json @@ -10,9 +10,10 @@ "_rebaseline_2026_06_15_3871_empty_pool": "PR #3871 own growth: combo.ts 5203->5204 (+1 = guard expandAutoComboCandidatePool against an empty candidatePool array — Array.isArray(pool) && pool.length > 0 so [] falls through to active-connection expansion instead of early-returning). One-line correctness fix; not extractable.", "_rebaseline_2026_06_15_3911_sse_role": "PR #3911 own growth: openai-responses.ts 878->903 (+25 = withAssistantRoleOnFirstDelta wrapper that synthesizes role=assistant on the first Responses->Chat delta so strict streaming clients (langchain/n8n) do not drop tool_call deltas). Cohesive translator fix; not extractable.", "_rebaseline_2026_06_15_3881_nous_validator": "Issue #3881 own growth: validation.ts 4394->4407 (+13 = treat any non-auth 4xx (400/404/422) from the Nous Research probe as a valid key — the request shape/model was wrong, not the credentials — mirroring the longcat/nvidia validators, plus the corrected Hermes-4-70B probe model). Cohesive validator branch; not extractable.", + "_rebaseline_2026_06_15_3877_byteplus": "Issue #3877 own growth: providerRegistry.ts 4708->4730 (+22 = a byteplus (BytePlus ModelArk) registry entry — OpenAI-compatible, Ark base ap-southeast-1, Bearer, 4 seed models — modeled on the volcengine entry; byteplus was in APIKEY_PROVIDERS but unregistered here so validation returned {unsupported:true}). Cohesive provider registration; not extractable.", "cap": 800, "frozen": { - "open-sse/config/providerRegistry.ts": 4708, + "open-sse/config/providerRegistry.ts": 4730, "open-sse/executors/antigravity.ts": 1649, "open-sse/executors/base.ts": 1218, "open-sse/executors/chatgpt-web.ts": 2870, diff --git a/open-sse/config/providerRegistry.ts b/open-sse/config/providerRegistry.ts index 07311134d3..b66fed0310 100644 --- a/open-sse/config/providerRegistry.ts +++ b/open-sse/config/providerRegistry.ts @@ -4251,6 +4251,28 @@ const _REGISTRY_EAGER: Record = { ], }, + // #3877: byteplus was in the APIKEY_PROVIDERS catalog but never registered here, so key + // validation fell through to {unsupported:true} → the UI reported every key "invalid". + // BytePlus ModelArk is an OpenAI-compatible surface (region ap-southeast-1) authed with a + // Bearer ark-... key — same shape as the volcengine (Volcengine Ark) entry, different host. + byteplus: { + id: "byteplus", + alias: "bpm", + format: "openai", + executor: "default", + baseUrl: "https://ark.ap-southeast.bytepluses.com/api/v3/chat/completions", + modelsUrl: "https://ark.ap-southeast.bytepluses.com/api/v3/models", + authType: "apikey", + authHeader: "bearer", + defaultContextLength: 128000, + models: [ + { id: "seed-2.0", name: "Seed 2.0" }, + { id: "kimi-k2-thinking", name: "Kimi K2 Thinking", supportsReasoning: true }, + { id: "glm-4.7", name: "GLM 4.7" }, + { id: "gpt-oss-120b", name: "GPT-OSS-120B" }, + ], + }, + bluesminds: { id: "bluesminds", alias: "bm", diff --git a/tests/unit/chat-openai-compat-providers.test.ts b/tests/unit/chat-openai-compat-providers.test.ts index 72ca9e31d7..0e31b5183a 100644 --- a/tests/unit/chat-openai-compat-providers.test.ts +++ b/tests/unit/chat-openai-compat-providers.test.ts @@ -40,6 +40,7 @@ const CHAT_OPENAI_COMPAT_PROVIDER_IDS = [ "predibase", "bytez", "reka", + "byteplus", ]; test("chat-openai-compat providers are registered across provider metadata, registry and local catalog", () => { diff --git a/tests/unit/provider-validation-specialty.test.ts b/tests/unit/provider-validation-specialty.test.ts index 991f117628..af8255ba02 100644 --- a/tests/unit/provider-validation-specialty.test.ts +++ b/tests/unit/provider-validation-specialty.test.ts @@ -1802,6 +1802,33 @@ test("specialty validator accepts Nous Research credentials on chat completions" assert.equal(nous.method, "nous_chat_completions"); }); +test("BytePlus key validation reaches the Ark endpoint instead of 'not supported' (#3877)", async () => { + // #3877: byteplus was in APIKEY_PROVIDERS but never registered in the routing + // registry, so validation returned {unsupported:true} → UI showed "invalid" for any + // key. With the registry entry, a valid ark-... key probes the Ark /models endpoint + // with Bearer auth and validates. + let probedModelsUrl: string | null = null; + globalThis.fetch = async (url, init = {}) => { + const target = String(url); + if (target === "https://ark.ap-southeast.bytepluses.com/api/v3/models") { + probedModelsUrl = target; + const headers = toPlainHeaders(init.headers); + assert.equal(headers.Authorization, "Bearer ark-test-key"); + return new Response(JSON.stringify({ data: [{ id: "kimi-k2-thinking" }] }), { status: 200 }); + } + throw new Error(`unexpected fetch: ${target}`); + }; + + const result = await validateProviderApiKey({ + provider: "byteplus", + apiKey: "ark-test-key", + }); + + assert.equal(result.unsupported, undefined, "byteplus must not be 'validation not supported'"); + assert.equal(result.valid, true); + assert.equal(probedModelsUrl, "https://ark.ap-southeast.bytepluses.com/api/v3/models"); +}); + test("specialty validator rejects invalid Nous Research credentials", async () => { globalThis.fetch = async (url, init = {}) => { const target = String(url);