fix(providers): register zed in OAuth PROVIDERS to fix Unknown provider error (#6041) (#6078)

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 <diegosouza.pw@gmail.com>
This commit is contained in:
Ankit
2026-07-05 11:54:32 +05:30
committed by GitHub
parent 44e85a7d40
commit b834c740d1
5 changed files with 131 additions and 4 deletions

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,
};
},
};

View File

@@ -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);
});
});