From b834c740d1a00bca9b5703c8bdff418f2ccd5efe Mon Sep 17 00:00:00 2001 From: Ankit <177378174+anki1kr@users.noreply.github.com> Date: Sun, 5 Jul 2026 11:54:32 +0530 Subject: [PATCH] fix(providers): register zed in OAuth PROVIDERS to fix Unknown provider error (#6041) (#6078) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Registers a minimal import_token entry for the existing Zed IDE keychain-import provider so getProvider("zed") no longer throws "Unknown provider: zed" when the UI probes the OAuth capability endpoint; generateAuthData returns { supported: false }. Test runner fix: the regression test imported from "vitest" but lives in tests/unit/ (node:test territory, outside the vitest include globs) — it ran in no runner. Converted to node:test + node:assert so it actually executes (8/8 green). Co-authored-by: diegosouzapw --- src/lib/oauth/constants/oauth.ts | 11 +++++ src/lib/oauth/providers.ts | 16 ++++-- src/lib/oauth/providers/index.ts | 3 ++ src/lib/oauth/providers/zed.ts | 35 ++++++++++++++ tests/unit/zed-oauth-provider.test.ts | 70 +++++++++++++++++++++++++++ 5 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 src/lib/oauth/providers/zed.ts create mode 100644 tests/unit/zed-oauth-provider.test.ts diff --git a/src/lib/oauth/constants/oauth.ts b/src/lib/oauth/constants/oauth.ts index 4f279e4d70..c682a6a0cd 100644 --- a/src/lib/oauth/constants/oauth.ts +++ b/src/lib/oauth/constants/oauth.ts @@ -429,6 +429,17 @@ export const WINDSURF_CONFIG = { extensionVersion: "3.14.0", }; +// Zed IDE credential import — no standard OAuth flow. +// Credentials are extracted from the OS keychain via POST /api/providers/zed/import. +// Docker environments fall back to manual token paste via POST /api/providers/zed/manual-import. +// This config is a placeholder so that getProvider("zed") doesn't throw +// "Unknown provider: zed" when the UI probes the OAuth capability endpoint. +export const ZED_CONFIG = { + importUrl: "/api/providers/zed/import", + discoverUrl: "/api/providers/zed/discover", + manualImportUrl: "/api/providers/zed/manual-import", +}; + // OAuth timeout (5 minutes) export const OAUTH_TIMEOUT = 300000; diff --git a/src/lib/oauth/providers.ts b/src/lib/oauth/providers.ts index 0a3ff407ec..9cd77b557d 100644 --- a/src/lib/oauth/providers.ts +++ b/src/lib/oauth/providers.ts @@ -113,10 +113,18 @@ export function generateAuthData(providerName, redirectUri) { const { codeVerifier, codeChallenge, state } = generatePKCE(); if (provider.flowType === "import_token") { - const error = - providerName === "windsurf" || providerName === "devin-cli" - ? "Browser login disabled — paste token from https://windsurf.com/show-auth-token instead. Phase 2 will restore Firebase OAuth via app.devin.ai successor." - : `Browser login is disabled for ${providerName}. Use the import-token flow instead.`; + let error: string; + if (providerName === "windsurf" || providerName === "devin-cli") { + error = + "Browser login disabled — paste token from https://windsurf.com/show-auth-token instead. Phase 2 will restore Firebase OAuth via app.devin.ai successor."; + } else if (providerName === "zed") { + error = + "Zed does not use a browser OAuth flow. Use the Zed provider page to import credentials " + + "directly from the OS keychain (POST /api/providers/zed/import), or paste a token manually " + + "via POST /api/providers/zed/manual-import for Docker environments."; + } else { + error = `Browser login is disabled for ${providerName}. Use the import-token flow instead.`; + } return { authUrl: undefined, state: undefined, diff --git a/src/lib/oauth/providers/index.ts b/src/lib/oauth/providers/index.ts index 534f42a1a1..54f87a94fd 100644 --- a/src/lib/oauth/providers/index.ts +++ b/src/lib/oauth/providers/index.ts @@ -27,6 +27,7 @@ import { cline } from "./cline"; import { windsurf } from "./windsurf"; import { grokCli } from "./grok-cli"; import { codebuddyCn } from "./codebuddy-cn"; +import { zed } from "./zed"; export const PROVIDERS = { claude, @@ -49,6 +50,8 @@ export const PROVIDERS = { "devin-cli": windsurf, "grok-cli": grokCli, "codebuddy-cn": codebuddyCn, + // Zed IDE credential bridge — uses keychain import, not standard OAuth + zed, }; export default PROVIDERS; diff --git a/src/lib/oauth/providers/zed.ts b/src/lib/oauth/providers/zed.ts new file mode 100644 index 0000000000..d01389a787 --- /dev/null +++ b/src/lib/oauth/providers/zed.ts @@ -0,0 +1,35 @@ +import { ZED_CONFIG } from "../constants/oauth"; + +/** + * Zed IDE credential bridge — import_token flow only. + * + * Zed stores AI provider API keys (Anthropic, OpenAI, Google, …) in the OS + * keychain. OmniRoute reads them via POST /api/providers/zed/import (keychain) + * or POST /api/providers/zed/manual-import (Docker / paste fallback). + * + * There is no standard OAuth browser flow for "zed" itself. Registering it + * here with flowType "import_token" prevents getProvider("zed") from throwing + * "Unknown provider: zed" when the OAuth capability endpoint is probed, and + * lets generateAuthData() return a clean { supported: false, error } instead + * of a 500. The actual import UI lives at + * /dashboard/providers/zed → ZedImportCard component. + */ +export const zed = { + config: ZED_CONFIG, + flowType: "import_token" as const, + + validateImportToken(token: string): { valid: boolean; reason?: string } { + const trimmed = (token ?? "").trim(); + if (!trimmed) return { valid: false, reason: "Token is empty" }; + if (trimmed.length < 8) return { valid: false, reason: "Token is too short" }; + return { valid: true }; + }, + + mapTokens(tokens: { accessToken: string }) { + return { + accessToken: tokens.accessToken, + refreshToken: null, + expiresIn: null as number | null, + }; + }, +}; diff --git a/tests/unit/zed-oauth-provider.test.ts b/tests/unit/zed-oauth-provider.test.ts new file mode 100644 index 0000000000..35c4c53697 --- /dev/null +++ b/tests/unit/zed-oauth-provider.test.ts @@ -0,0 +1,70 @@ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { PROVIDERS } from "../../src/lib/oauth/providers/index"; +import { getProvider, generateAuthData } from "../../src/lib/oauth/providers"; + +// The import_token providers expose validateImportToken/mapTokens beyond the base +// OAuth provider shape; narrow to just what these assertions touch. +type ImportTokenProvider = { + flowType: string; + validateImportToken: (token: string) => { valid: boolean }; + mapTokens: (t: { accessToken: string }) => { + accessToken: string; + refreshToken: string | null; + expiresIn: number | null; + }; +}; +const asImportProvider = (id: string) => getProvider(id) as unknown as ImportTokenProvider; + +/** + * Guards fix for issue #6041: + * GET /api/oauth/zed/[action] was throwing "Unknown provider: zed" because + * "zed" was not registered in the PROVIDERS map. The fix registers a minimal + * import_token entry so that getProvider("zed") returns cleanly and + * generateAuthData returns { supported: false } instead of a 500. + */ +describe("Zed OAuth provider registration", () => { + it("PROVIDERS map includes zed", () => { + assert.ok("zed" in PROVIDERS, "PROVIDERS must include zed"); + }); + + it("getProvider('zed') does not throw", () => { + assert.doesNotThrow(() => getProvider("zed")); + }); + + it("zed provider has flowType import_token", () => { + const provider = getProvider("zed"); + assert.equal(provider.flowType, "import_token"); + }); + + it("generateAuthData returns supported:false for zed", () => { + const authData = generateAuthData("zed", "http://localhost:8080/callback"); + assert.equal(authData.supported, false); + assert.equal(authData.authUrl, undefined); + assert.match(authData.error, /zed/i); + }); + + it("generateAuthData error message mentions the keychain import path", () => { + const authData = generateAuthData("zed", "http://localhost:8080/callback"); + assert.ok(authData.error.includes("/api/providers/zed/import")); + }); + + it("zed validateImportToken rejects empty tokens", () => { + const provider = asImportProvider("zed"); + assert.equal(provider.validateImportToken("").valid, false); + assert.equal(provider.validateImportToken(" ").valid, false); + }); + + it("zed validateImportToken accepts valid tokens", () => { + const provider = asImportProvider("zed"); + assert.equal(provider.validateImportToken("sk-ant-api03-abc123def456").valid, true); + }); + + it("zed mapTokens returns accessToken and null refresh/expiry", () => { + const provider = asImportProvider("zed"); + const result = provider.mapTokens({ accessToken: "sk-ant-test" }); + assert.equal(result.accessToken, "sk-ant-test"); + assert.equal(result.refreshToken, null); + assert.equal(result.expiresIn, null); + }); +});