From 28b44b01d4cf7fbcb2475908fca4f6071ed52807 Mon Sep 17 00:00:00 2001 From: Hernan Javier Ardila Sanchez Date: Thu, 4 Jun 2026 19:25:29 +0200 Subject: [PATCH] =?UTF-8?q?fix(qoder):=20bifurcate=20validation=20by=20tok?= =?UTF-8?q?en=20type=20=E2=80=94=20PAT=E2=86=92Cosy,=20regular=20API=20key?= =?UTF-8?q?=E2=86=92dashscope=20(#3149)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bifurcate Qoder validation by token type (PAT→Cosy, regular→dashscope) +regression test. Integrated into release/v3.8.10. --- src/lib/providers/validation.ts | 39 +++++++++++++++++- .../provider-validation-hardening.test.ts | 40 +++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/lib/providers/validation.ts b/src/lib/providers/validation.ts index e4ea04e0f4..d451dc60f5 100644 --- a/src/lib/providers/validation.ts +++ b/src/lib/providers/validation.ts @@ -3718,8 +3718,43 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi // ── Specialty provider validation ── const SPECIALTY_VALIDATORS = { jules: validateJulesProvider, - qoder: ({ apiKey, providerSpecificData }: any) => - validateQoderCliPat({ apiKey, providerSpecificData }), + qoder: async ({ apiKey, providerSpecificData }: any) => { + // Bifurcate validation: PAT tokens use Cosy auth against api1.qoder.sh; + // regular API keys validate against dashscope (OpenAI-compatible endpoint). + const key = (apiKey || "").trim(); + if (key.startsWith("pt-")) { + return validateQoderCliPat({ apiKey: key, providerSpecificData }); + } + // Non-PAT token → validate against dashscope (Alibaba Cloud). + // The executor routes these tokens to dashscope.aliyuncs.com, so the + // validation must test against dashscope, NOT the Cosy PAT endpoint. + try { + const dashscopeUrl = + "https://dashscope.aliyuncs.com/compatible-mode/v1/models"; + const res = await validationRead( + dashscopeUrl, + { + headers: { + Authorization: `Bearer ${key}`, + }, + }, + false + ); + if (res.ok) return { valid: true, error: null }; + if (res.status === 401 || res.status === 403) { + return { + valid: false, + error: + "Invalid Qoder API key. Make sure you're using a valid API key from Qoder / Alibaba Cloud Dashscope.", + }; + } + // 4xx/5xx other than auth — treat as valid bypass to prevent false + // negatives from transient dashscope issues (consistent with PAT path). + return { valid: true, error: null }; + } catch (err: unknown) { + return toValidationErrorResult(err); + } + }, "command-code": validateCommandCodeProvider, deepgram: validateDeepgramProvider, assemblyai: validateAssemblyAIProvider, diff --git a/tests/unit/provider-validation-hardening.test.ts b/tests/unit/provider-validation-hardening.test.ts index 9bbeba99e9..839826679a 100644 --- a/tests/unit/provider-validation-hardening.test.ts +++ b/tests/unit/provider-validation-hardening.test.ts @@ -188,3 +188,43 @@ test("#2545 gemini validation does not produce /models/models", async () => { `outbound URL must hit a single /models segment — got ${calls.join(", ")}` ); }); + +test("qoder regular API key validates against dashscope, not the Cosy PAT endpoint (#3149)", async () => { + const calls: string[] = []; + globalThis.fetch = async (url: any, init: any) => { + calls.push(String(url)); + const auth = new Headers(init?.headers as HeadersInit | undefined).get("authorization"); + assert.equal(auth, "Bearer sk-qoder-regular", "dashscope probe must forward the API key"); + return new Response(JSON.stringify({ data: [] }), { status: 200 }); + }; + + const result = await validateProviderApiKey({ + provider: "qoder", + apiKey: "sk-qoder-regular", + providerSpecificData: {}, + }); + + assert.equal(result.valid, true); + assert.ok( + calls.some((u) => u.includes("dashscope.aliyuncs.com/compatible-mode/v1/models")), + `regular qoder key must validate against dashscope — got ${calls.join(", ")}` + ); + assert.ok( + !calls.some((u) => u.includes("api1.qoder.sh")), + "regular (non-PAT) key must not hit the Cosy PAT endpoint" + ); +}); + +test("qoder regular API key surfaces an auth error when dashscope rejects it (#3149)", async () => { + globalThis.fetch = async () => + new Response(JSON.stringify({ error: { message: "invalid api key" } }), { status: 401 }); + + const result = await validateProviderApiKey({ + provider: "qoder", + apiKey: "sk-qoder-bad", + providerSpecificData: {}, + }); + + assert.equal(result.valid, false); + assert.match(result.error, /Qoder|Dashscope|API key/i); +});