From 8c4364a5975251becd453fbae706bf3f658da9a9 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Sun, 12 Jul 2026 01:59:44 -0300 Subject: [PATCH] fix(providers): give v0-vercel-web its own alias so credentials are detected (#6343) (#6891) * fix(providers): give v0-vercel-web its own alias so credentials are detected (#6343) * test(6343): type casts to satisfy no-explicit-any gate * test(6343): register v0-web + cliproxyapi tests in stryker tap.testFiles The two unit tests added on this branch cover mutated modules (src/sse/services/auth.ts, comboContextCache.ts) but were missing from stryker.conf.json tap.testFiles, tripping check:mutation-test-coverage --strict. Co-authored-by: diegosouzapw --- changelog.d/fixes/6343-6343-v0web-creds.md | 1 + src/shared/constants/providers/web-cookie.ts | 8 +- stryker.conf.json | 1 + .../issue-6343-v0-web-alias-collision.test.ts | 90 +++++++++++++++++++ 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 changelog.d/fixes/6343-6343-v0web-creds.md create mode 100644 tests/unit/issue-6343-v0-web-alias-collision.test.ts diff --git a/changelog.d/fixes/6343-6343-v0web-creds.md b/changelog.d/fixes/6343-6343-v0web-creds.md new file mode 100644 index 0000000000..e939d0df24 --- /dev/null +++ b/changelog.d/fixes/6343-6343-v0web-creds.md @@ -0,0 +1 @@ +- fix(providers): give v0-vercel-web its own alias so its credentials are detected (#6343) diff --git a/src/shared/constants/providers/web-cookie.ts b/src/shared/constants/providers/web-cookie.ts index 0eeba7b86b..e284720fa9 100644 --- a/src/shared/constants/providers/web-cookie.ts +++ b/src/shared/constants/providers/web-cookie.ts @@ -242,7 +242,13 @@ export const WEB_COOKIE_PROVIDERS = { }, "v0-vercel-web": { id: "v0-vercel-web", - alias: "v0", + // #6343: was "v0", colliding with the unrelated "v0-vercel" API-key provider's + // alias. Aliases resolve 1:1 to a provider id, so the dashboard's model-string + // routing always picked v0-vercel, silently hiding this provider's own + // credentials. Follows the established secondary-web-variant convention (see + // kimi-web / qwen-web / huggingchat in tests/unit/provider-alias-uniqueness.test.ts): + // the web/secondary variant uses its own id as alias instead of a short prefix. + alias: "v0-vercel-web", name: "v0 Vercel Web (Code Gen)", icon: "auto_awesome", color: "#000000", diff --git a/stryker.conf.json b/stryker.conf.json index 33810ac370..e8aeb10135 100644 --- a/stryker.conf.json +++ b/stryker.conf.json @@ -179,6 +179,7 @@ "tests/unit/headroom-codex-quota-snapshot-6379.test.ts", "tests/unit/headroom-proxy-lifecycle.test.ts", "tests/unit/idempotency-fusion-collision.test.ts", + "tests/unit/issue-6343-v0-web-alias-collision.test.ts", "tests/unit/issue-6638-ollama-quota.test.ts", "tests/unit/issue-6686-quota-preflight-coverage.test.ts", "tests/unit/livews-forward-backoff-4604.test.ts", diff --git a/tests/unit/issue-6343-v0-web-alias-collision.test.ts b/tests/unit/issue-6343-v0-web-alias-collision.test.ts new file mode 100644 index 0000000000..15e672e3b2 --- /dev/null +++ b/tests/unit/issue-6343-v0-web-alias-collision.test.ts @@ -0,0 +1,90 @@ +// Regression test for #6343: "v0 Web cannot detect added credentials". +// +// Root cause: 'v0 Vercel Web (Code Gen)' (id v0-vercel-web, cookie auth) and the +// unrelated 'v0 (Vercel)' API-key provider (id v0-vercel) both declared alias +// "v0". The dashboard's per-model Test button builds the test model string from +// the provider's ALIAS (not its canonical id), and open-sse's alias->id map is +// necessarily 1:1, so "v0/" always resolved to v0-vercel (which the user +// has no credentials for) instead of v0-vercel-web (the provider they actually +// configured). See _tasks/pipeline/bugs/2-implementing/6343-*.plan.md. +import { describe, it, after } from "node:test"; +import assert from "node:assert/strict"; +import os from "node:os"; +import path from "node:path"; +import fs from "node:fs"; + +const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-6343-")); +process.env.DATA_DIR = tmpDir; +process.env.JWT_SECRET = "test-jwt-secret-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; +process.env.API_KEY_SECRET = "0123456789abcdef0123456789abcdef"; + +describe("#6343: v0-vercel-web credential detection (alias collision)", () => { + after(async () => { + try { + const { resetDbInstance } = await import("../../src/lib/db/core.ts"); + resetDbInstance(); + } catch { + // best-effort cleanup + } + fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + it("v0-vercel and v0-vercel-web no longer share an alias", async () => { + const { getProviderAlias } = await import("../../src/shared/constants/providers.ts"); + const apiKeyAlias = getProviderAlias("v0-vercel"); + const webAlias = getProviderAlias("v0-vercel-web"); + assert.notEqual( + webAlias, + apiKeyAlias, + "v0-vercel and v0-vercel-web must have distinct aliases so alias->id resolution is unambiguous" + ); + }); + + it("a saved v0-vercel-web connection is found by getProviderCredentials", async () => { + const { createProviderConnection } = await import("../../src/lib/db/providers.ts"); + const { getProviderCredentials } = await import("../../src/sse/services/auth.ts"); + const conn = await createProviderConnection({ + provider: "v0-vercel-web", + authType: "cookie", + name: "test-v0-web", + apiKey: "__vercel_session=faketoken123", + priority: 1, + isActive: true, + testStatus: "active", + }); + const creds = await getProviderCredentials("v0-vercel-web", null, null, "v0-fable-5"); + assert.ok(creds); + assert.equal((creds as { connectionId?: string }).connectionId, conn.id); + }); + + it("dashboard 'Test Model' fullModel (built from provider ALIAS) resolves back to v0-vercel-web and finds the real credential", async () => { + const { getProviderAlias } = await import("../../src/shared/constants/providers.ts"); + const { parseModel } = await import("../../open-sse/services/model.ts"); + const { getProviderCredentials } = await import("../../src/sse/services/auth.ts"); + + const providerId = "v0-vercel-web"; + // Mirrors ProviderDetailPageClient.tsx:266 for a non-"compatible" provider. + const providerDisplayAlias = getProviderAlias(providerId); + // Mirrors ProviderModelsSection.tsx:461. + const fullModelFromUi = `${providerDisplayAlias}/v0-fable-5`; + + const parsed = parseModel(fullModelFromUi); + assert.equal(parsed.provider, providerId); + + const creds = await getProviderCredentials(String(parsed.provider), null, null, parsed.model); + assert.ok(creds); + }); + + it("v0-vercel and v0-vercel-web are the only two AI_PROVIDERS entries with alias 'v0'", async () => { + // Guards against a future re-introduction of the exact collision this bug fixed + // (see AI_PROVIDERS scan in the plan-file / triage). Deliberately scoped to the + // "v0" alias only — other pre-existing alias collisions (e.g. poe / poe-web) are + // out of scope for #6343 and are not asserted here. + const { AI_PROVIDERS } = await import("../../src/shared/constants/providers.ts"); + const idsWithV0Alias = Object.values(AI_PROVIDERS) + .filter((p: { alias?: string; id: string }) => p.alias === "v0") + .map((p: { alias?: string; id: string }) => p.id) + .sort(); + assert.deepEqual(idsWithV0Alias, ["v0-vercel"]); + }); +});