mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
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:
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
35
src/lib/oauth/providers/zed.ts
Normal file
35
src/lib/oauth/providers/zed.ts
Normal 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,
|
||||
};
|
||||
},
|
||||
};
|
||||
70
tests/unit/zed-oauth-provider.test.ts
Normal file
70
tests/unit/zed-oauth-provider.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user