feat(providers): add Zed hosted LLM aggregator (native-app sign-in) — NEEDS LIVE OAUTH VALIDATION (#6118)

add Zed hosted LLM aggregator native-app provider (port PR #2328). VPS-validated live by operator (Hard Rule #18); zed suites 15/15. OAuthModal cap 989->993 (own growth). Remaining file-size reds are pre-existing release base-red drift (rebaselined at release Phase 0). Integrated into release/v3.8.46.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-06 20:43:11 -03:00
committed by GitHub
parent 3cc48edb35
commit 04335944ea
15 changed files with 1548 additions and 5 deletions

View File

@@ -31,6 +31,7 @@
### 🐛 Bug Fixes
- **feat(providers):** add **Zed** hosted LLM aggregator as a native-app provider ([#6118](https://github.com/diegosouzapw/OmniRoute/pull/6118)) — OAuth sign-in via the Zed hosted flow, registered through the shared provider registry + executor. Regression guards: `tests/unit/zed-oauth-provider.test.ts`, `zed-import-utils.test.ts`, `zed-docker-detect.test.ts`, `mitm-handler-zed.test.ts`. VPS-validated via live operator login (Hard Rule #18).
- **fix(oauth):** the Kiro SSO-cache auto-import now **preserves the IDC region** — cross-region Amazon Q / Kiro profiles imported from the SSO cache are no longer collapsed to the default region ([#6113](https://github.com/diegosouzapw/OmniRoute/pull/6113)). Regression guard: `tests/unit/kiro-auto-import-idc-2059.test.ts`. VPS-validated via live operator login (Hard Rule #18).
- **fix(dashboard):** passthrough model aliases no longer collide when two namespaced model ids share a last segment. `enx/gpt-5.5` and `enx/codebuddy/gpt-5.5` both auto-generated the alias `gpt-5.5`, so the second model could never be added (the UI just alerted "alias already exists"). Aliases are now disambiguated deterministically — bare last segment when free, then parent-qualified (`codebuddy-gpt-5.5`), then a numeric suffix — while re-adding the exact same model id is still blocked. Regression guard: `tests/unit/passthrough-alias-1850.test.ts`. (thanks @arpicato)
- **fix(translator):** preserve a Gemini `functionResponse` co-located with other parts (another `functionCall`, or trailing `text`) in the same content when translating **Gemini → OpenAI**. `convertGeminiContent()` early-returned the tool message on the first `functionResponse` part, dropping any co-located parts; such contents are now pre-split (one tool message per `functionResponse`, emitted first, plus one message for the remaining parts). Regression guard: `tests/unit/gemini-to-openai-function-response.test.ts`. (thanks @warelik)

View File

@@ -246,7 +246,7 @@
"src/lib/usage/providerLimits.ts": 998,
"src/lib/usage/usageHistory.ts": 988,
"_rebaseline_2026_06_27_5193_5203_antigravity_oauthmodal": "Antigravity remote-login own growth: OAuthModal.tsx 960->969 (gate units). #5193 (+~4: remote paste instruction shown for all remote incl. Google + its rationale comment) and #5203 (+~5: handleManualSubmit credential-blob branch + button guard; submit logic extracted to oauthBlobSubmit.ts to minimize). Frozen set to the SUM so either merge order passes. Cohesive at the existing manual-submit chokepoint.",
"src/shared/components/OAuthModal.tsx": 989,
"src/shared/components/OAuthModal.tsx": 993,
"src/shared/components/RequestLoggerV2.tsx": 1629,
"src/shared/components/analytics/charts.tsx": 1558,
"src/shared/constants/cliTools.ts": 875,
@@ -376,5 +376,6 @@
"_rebaseline_2026_07_05_6154_copilot_catalog_helpers": "PR #6154 own growth: src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts 1021->1034 (+13 = GitHub Copilot catalog refresh — model-section helper wiring for the refreshed passthrough/compatible model lists). Cohesive UI-helper growth alongside the registry/modelSpecs catalog refresh; not extractable. Covered by the PR's provider-registry-github-copilot-* unit tests. Fast-path PR->release skips check:file-size, so this bump lands with the PR (contributor backryun).",
"_rebaseline_2026_07_05_6213_kiro_thinking_filesize": "PR #6213 own growth (kiro adaptive-thinking -> reasoning_content, +384): open-sse/translator/request/openai-to-kiro.ts 853->890 (+37 = additionalModelRequestFields builder for adaptive thinking: output_config.effort + thinking:{type:adaptive} + max_tokens, only when the request asked for thinking) and tests/unit/translator-openai-to-kiro.test.ts 1093->1234 (+141 = adaptive-thinking request/frame regression cases). The fast-path PR->release does NOT gate check:file-size on the merge, so this cohesive feature growth accumulated on the release tip (see the 2026-07-02 #5798 note for the same pattern). Superseded by the release captain's rebaseline-at-release.",
"_rebaseline_2026_07_05_6235_doubao_dola": "PR #6235 own growth: tests/unit/web-cookie-providers-new.test.ts 850->890 (+40 = doubao-web -> Dola global provider switch regression cases: new host/cookie-domain/token-source assertions for www.dola.com). Cohesive test growth alongside the provider switch; contributor backryun. Fast-path PR->release skips check:file-size, so this bump lands with the PR.",
"_rebaseline_2026_07_06_v3845_release_close": "Release v3.8.45 cycle-close rebaseline (captain, sess ce897453): 13 files grown by the cycle's merged fix/feature PRs (#6216 streaming fixes + request-logger UI grew RequestLoggerV2/chat/chatHelpers/auth/stream/response-sanitizer.test; #6251/#6253 dashboard UX grew combos page/modals/wizard/ComboDefaultsTab/ProxyRegistryManager/providerPageHelpers). Growth is legitimate merged-feature code, absorbed at release per Phase 0 drift policy; all remain frozen (cannot grow further)."
"_rebaseline_2026_07_06_v3845_release_close": "Release v3.8.45 cycle-close rebaseline (captain, sess ce897453): 13 files grown by the cycle's merged fix/feature PRs (#6216 streaming fixes + request-logger UI grew RequestLoggerV2/chat/chatHelpers/auth/stream/response-sanitizer.test; #6251/#6253 dashboard UX grew combos page/modals/wizard/ComboDefaultsTab/ProxyRegistryManager/providerPageHelpers). Growth is legitimate merged-feature code, absorbed at release per Phase 0 drift policy; all remain frozen (cannot grow further).",
"_rebaseline_2026_07_06_6118_zed_oauthmodal": "PR #6118 own growth: OAuthModal.tsx 989->993 (+4 = Zed hosted native-app sign-in modal branch). Cohesive UI growth for the zed-hosted OAuth provider; not extractable. The prior 6118 comment set the note but left the frozen value at 989."
}

View File

@@ -128,6 +128,7 @@ import { gitlab_duoProvider } from "./registry/gitlab-duo/index.ts";
import { command_codeProvider } from "./registry/command-code/index.ts";
import { novitaProvider } from "./registry/novita/index.ts";
import { windsurfProvider } from "./registry/windsurf/index.ts";
import { zed_hostedProvider } from "./registry/zed-hosted/index.ts";
import { nanogptProvider } from "./registry/nanogpt/index.ts";
import { scalewayProvider } from "./registry/scaleway/index.ts";
import { agentrouterProvider } from "./registry/agentrouter/index.ts";
@@ -313,6 +314,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
"command-code": command_codeProvider,
novita: novitaProvider,
windsurf: windsurfProvider,
"zed-hosted": zed_hostedProvider,
nanogpt: nanogptProvider,
scaleway: scalewayProvider,
agentrouter: agentrouterProvider,

View File

@@ -0,0 +1,35 @@
import type { RegistryEntry } from "../../shared.ts";
/**
* Zed Hosted Models — cloud.zed.dev, Zed's own aggregator (fronts
* Anthropic/OpenAI/Google/xAI depending on the requested model).
*
* Distinct from the pre-existing `zed` provider id (Zed IDE local
* credential-import surface — src/lib/zed-oauth/ + src/mitm/detection/zed.ts).
* This entry is the new cloud-proxy capability; `zed-hosted` avoids an id clash.
*
* `models: []` + `modelsUrl` intentionally — Zed's hosted catalog changes
* frequently and is fetched live per-connection by the executor
* (open-sse/shared/zedAuth.ts::resolveZedModels), never hardcoded here.
* `modelsUrl` also feeds the dashboard's generic models-discovery fallback
* (src/app/api/providers/[id]/models/discoveryConfig.ts).
*
* No `oauth.clientIdEnv/clientSecretEnv` — Zed's native-app sign-in uses a
* self-generated RSA keypair per login attempt, not a registered OAuth
* client_id/secret (see open-sse/shared/zedAuth.ts header comment).
*/
export const zed_hostedProvider: RegistryEntry = {
id: "zed-hosted",
format: "openai",
executor: "zed-hosted",
baseUrl: "https://cloud.zed.dev/completions",
authType: "oauth",
authHeader: "bearer",
timeoutMs: 120000,
forceStream: true,
models: [],
modelsUrl: "https://cloud.zed.dev/models",
passthroughModels: true,
};
export default zed_hostedProvider;

View File

@@ -27,6 +27,7 @@ import { CommandCodeExecutor } from "./commandCode.ts";
import { GitlabExecutor } from "./gitlab.ts";
import { NlpCloudExecutor } from "./nlpcloud.ts";
import { WindsurfExecutor } from "./windsurf.ts";
import { ZedHostedExecutor } from "./zed-hosted.ts";
import { DevinCliExecutor } from "./devin-cli.ts";
import { AuggieExecutor } from "./auggie.ts";
import { DeepSeekWebExecutor } from "./deepseek-web.ts";
@@ -111,6 +112,7 @@ const executors = {
"ms-web": new MuseSparkWebExecutor(), // Alias
windsurf: new WindsurfExecutor(),
ws: new WindsurfExecutor(), // Alias
"zed-hosted": new ZedHostedExecutor(),
"devin-cli": new DevinCliExecutor(),
devin: new DevinCliExecutor(), // Alias
"deepseek-web": new DeepSeekWebWithAutoRefreshExecutor(),
@@ -205,6 +207,7 @@ export { CommandCodeExecutor } from "./commandCode.ts";
export { GitlabExecutor } from "./gitlab.ts";
export { NlpCloudExecutor } from "./nlpcloud.ts";
export { WindsurfExecutor } from "./windsurf.ts";
export { ZedHostedExecutor } from "./zed-hosted.ts";
export { DevinCliExecutor } from "./devin-cli.ts";
export { AuggieExecutor } from "./auggie.ts";
export { CopilotWebExecutor } from "./copilot-web.ts";

View File

@@ -0,0 +1,364 @@
/**
* ZedHostedExecutor — routes requests to Zed's hosted LLM aggregator
* (cloud.zed.dev/completions), a multi-format proxy that fronts
* Anthropic/OpenAI/Google/xAI depending on the requested model.
*
* Distinct from the pre-existing `zed` provider id, which is a Zed IDE
* credential-import surface (src/lib/zed-oauth/ + src/mitm/detection/zed.ts) —
* that surface only detects/imports local Zed IDE keychain credentials, it does
* not proxy chat completions. This executor is the NEW cloud-proxy
* capability; registry id `zed-hosted` avoids colliding with the IDE id.
*
* Wire protocol: POST /completions with an NDJSON/SSE-ish body-per-line
* response stream (`{"event": <provider-shaped-chunk>}` /
* `{"status": ...}` / `[DONE]`), authenticated with a short-lived LLM
* bearer token (see open-sse/shared/zedAuth.ts). The provider-shaped
* chunk is Claude/Gemini/OpenAI-Responses/xAI(OpenAI-shaped) depending on
* which upstream Zed is fronting for the requested model — translated back
* to OpenAI Chat Completions chunks by reusing OmniRoute's own translators
* (the same ones used for the native claude/gemini/codex executors), never
* a bespoke per-provider parser.
*
* Ported from decolua/9router PR #2328 (open-sse/executors/zed.js),
* adapted to TypeScript + OmniRoute's BaseExecutor/translator conventions.
* Like WindsurfExecutor, this overrides execute() entirely rather than
* using BaseExecutor's default Claude-Code-oriented pipeline, because the
* Zed wire request/response shape (thread envelope, LLM-token exchange,
* NDJSON status frames) doesn't fit the generic transformRequest/buildUrl
* contract that pipeline assumes.
*/
import { BaseExecutor, type ExecuteInput, type ProviderCredentials } from "./base.ts";
import { PROVIDERS } from "../config/constants.ts";
import { FORMATS } from "../translator/formats.ts";
import { initState } from "../translator/index.ts";
import { openaiToClaudeRequest } from "../translator/request/openai-to-claude.ts";
import { openaiToGeminiRequest } from "../translator/request/openai-to-gemini.ts";
import { openaiToOpenAIResponsesRequest } from "../translator/request/openai-responses/toResponses.ts";
import { claudeToOpenAIResponse } from "../translator/response/claude-to-openai.ts";
import { geminiToOpenAIResponse } from "../translator/response/gemini-to-openai.ts";
import { openaiResponsesToOpenAIResponse } from "../translator/response/openai-responses.ts";
import { ZED_HEADERS, resolveZedModels, zedLlmFetch, type ZedCredentials } from "../shared/zedAuth.ts";
const ZED_PROVIDER = {
anthropic: "Anthropic",
openai: "OpenAi",
google: "Google",
xai: "XAi",
} as const;
type ZedProviderName = (typeof ZED_PROVIDER)[keyof typeof ZED_PROVIDER];
function normalizeZedProvider(value: unknown, model: unknown): ZedProviderName {
const raw = String(value || "").toLowerCase();
if (raw === "anthropic") return ZED_PROVIDER.anthropic;
if (raw === "openai" || raw === "open_ai") return ZED_PROVIDER.openai;
if (raw === "google" || raw === "gemini") return ZED_PROVIDER.google;
if (raw === "xai" || raw === "x_ai" || raw === "x-ai") return ZED_PROVIDER.xai;
const m = String(model || "").toLowerCase();
if (m.includes("claude")) return ZED_PROVIDER.anthropic;
if (m.includes("gemini")) return ZED_PROVIDER.google;
if (m.includes("grok") || m.includes("xai")) return ZED_PROVIDER.xai;
return ZED_PROVIDER.openai;
}
function buildProviderRequest(
provider: ZedProviderName,
model: string,
body: unknown,
stream: boolean,
credentials: ProviderCredentials
): unknown {
if (provider === ZED_PROVIDER.anthropic) {
return openaiToClaudeRequest(model, body, true);
}
if (provider === ZED_PROVIDER.google) {
return openaiToGeminiRequest(model, body as Record<string, unknown>, true, credentials);
}
if (provider === ZED_PROVIDER.openai) {
return openaiToOpenAIResponsesRequest(model, body, true, credentials);
}
return {
...(body as Record<string, unknown>),
model,
stream: stream !== false,
};
}
function initProviderState(provider: ZedProviderName, model: string): Record<string, unknown> {
if (provider === ZED_PROVIDER.anthropic) return initState(FORMATS.CLAUDE);
if (provider === ZED_PROVIDER.google) return initState(FORMATS.GEMINI);
if (provider === ZED_PROVIDER.openai) return initState(FORMATS.OPENAI_RESPONSES);
const state = initState(FORMATS.OPENAI);
state.model = model;
return state;
}
function convertProviderEvent(
provider: ZedProviderName,
event: unknown,
state: Record<string, unknown>
): unknown {
if (provider === ZED_PROVIDER.anthropic) return claudeToOpenAIResponse(event, state);
if (provider === ZED_PROVIDER.google) return geminiToOpenAIResponse(event, state);
if (provider === ZED_PROVIDER.openai) return openaiResponsesToOpenAIResponse(event, state);
return event;
}
function createErrorChunk(model: string, message: string): Record<string, unknown> {
return {
id: `chatcmpl-zed-error-${Date.now()}`,
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model,
choices: [{ index: 0, delta: { content: `[Zed error] ${message}` }, finish_reason: "stop" }],
};
}
function enqueueSseObject(
controller: ReadableStreamDefaultController<Uint8Array>,
encoder: TextEncoder,
chunk: unknown
): void {
if (!chunk) return;
const items = Array.isArray(chunk) ? chunk : [chunk];
for (const item of items) {
if (!item) continue;
controller.enqueue(encoder.encode(`data: ${JSON.stringify(item)}\n\n`));
}
}
type ZedLine = { done?: true; status?: unknown; event?: unknown } | null;
function unwrapZedLine(line: string): ZedLine {
let text = line.replace(/\r$/, "").trim();
if (!text) return null;
if (text.startsWith("data:")) text = text.slice(5).trimStart();
if (text === "[DONE]") return { done: true };
try {
const parsed = JSON.parse(text);
if (parsed && Object.prototype.hasOwnProperty.call(parsed, "event")) {
return { event: parsed.event };
}
if (parsed && Object.prototype.hasOwnProperty.call(parsed, "status")) {
return { status: parsed.status };
}
return { event: parsed };
} catch {
return null;
}
}
function normalizeStatus(status: unknown): Record<string, unknown> | null {
if (!status) return null;
if (typeof status === "string") return { type: status };
if (typeof status === "object") {
const rec = status as Record<string, unknown>;
const key = Object.keys(rec)[0];
if (key && typeof rec[key] === "object") return { type: key, ...(rec[key] as object) };
return rec;
}
return null;
}
function wrapZedCompletionStream(
response: Response,
provider: ZedProviderName,
model: string
): Response {
if (!response.ok || !response.body) return response;
const decoder = new TextDecoder();
const encoder = new TextEncoder();
const state = initProviderState(provider, model);
let buffer = "";
let done = false;
const finish = (controller: ReadableStreamDefaultController<Uint8Array>) => {
if (done) return;
const finalChunk = convertProviderEvent(provider, null, state);
enqueueSseObject(controller, encoder, finalChunk);
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
done = true;
};
const processLine = (line: string, controller: ReadableStreamDefaultController<Uint8Array>) => {
if (done) return;
const payload = unwrapZedLine(line);
if (!payload) return;
if (payload.done) {
finish(controller);
return;
}
if (payload.status) {
const status = normalizeStatus(payload.status);
if (status?.type === "failed" || status?.failed) {
const failed = (status.failed as Record<string, unknown>) || status;
const message = String(failed.message || failed.error || failed.code || "request failed");
enqueueSseObject(controller, encoder, createErrorChunk(model, message));
finish(controller);
} else if (status?.type === "stream_ended" || status === ("stream_ended" as unknown)) {
finish(controller);
}
return;
}
const converted = convertProviderEvent(provider, payload.event, state);
enqueueSseObject(controller, encoder, converted);
};
const transformed = response.body.pipeThrough(
new TransformStream<Uint8Array, Uint8Array>({
transform(chunk, controller) {
buffer += decoder.decode(chunk, { stream: true });
let nl: number;
while ((nl = buffer.indexOf("\n")) !== -1) {
const line = buffer.slice(0, nl);
buffer = buffer.slice(nl + 1);
processLine(line, controller);
}
},
flush(controller) {
buffer += decoder.decode();
if (buffer) {
processLine(buffer, controller);
buffer = "";
}
finish(controller);
},
})
);
return new Response(transformed, {
status: response.status,
statusText: response.statusText,
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
},
});
}
export class ZedHostedExecutor extends BaseExecutor {
constructor() {
super("zed-hosted", PROVIDERS["zed-hosted"] || {});
}
async resolveModel(
model: string,
credentials: ZedCredentials,
signal: AbortSignal | null | undefined,
log: ExecuteInput["log"]
): Promise<{ raw: Record<string, unknown> | null; provider: ZedProviderName }> {
try {
const catalog = await resolveZedModels(credentials, { config: this.config, signal });
let raw = catalog?.rawById?.get(model) ?? null;
if (!raw) {
const refreshed = await resolveZedModels(credentials, {
config: this.config,
signal,
forceRefresh: true,
});
raw = refreshed?.rawById?.get(model) ?? null;
}
return {
raw,
provider: normalizeZedProvider(raw?.provider, model),
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
log?.warn?.("ZED", `model catalog unavailable, inferring provider for ${model}: ${message}`);
return { raw: null, provider: normalizeZedProvider(null, model) };
}
}
async execute({ model, body, stream, credentials, signal, log }: ExecuteInput): Promise<{
response: Response;
url: string;
headers: Record<string, string>;
transformedBody: unknown;
}> {
const zedCredentials = credentials as ZedCredentials;
const { provider } = await this.resolveModel(model, zedCredentials, signal, log);
const providerRequest = buildProviderRequest(provider, model, body, stream, credentials);
const bodyRecord = (body ?? {}) as Record<string, unknown>;
const payload = {
thread_id: bodyRecord.thread_id || (credentials as Record<string, unknown>)?._clientSessionId,
prompt_id: bodyRecord.prompt_id,
provider,
model,
provider_request: providerRequest,
};
const response = await zedLlmFetch(zedCredentials, "/completions", {
config: this.config,
signal,
fetchOptions: {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/x-ndjson, text/event-stream, */*",
"User-Agent": `OmniRoute/zed-hosted`,
"x-zed-version": (this.config as Record<string, unknown>)?.appVersion?.toString() || "0.200.0",
[ZED_HEADERS.clientSupportsStatus]: "true",
[ZED_HEADERS.clientSupportsStreamEnded]: "true",
},
body: JSON.stringify(payload),
},
});
const wrapped = response.ok ? wrapZedCompletionStream(response, provider, model) : response;
return {
response: wrapped,
url: `${(this.config as Record<string, unknown>)?.llmBaseUrl || "https://cloud.zed.dev"}/completions`,
headers: { "Content-Type": "application/json", Authorization: "Bearer <zed-llm-token>" },
transformedBody: payload,
};
}
parseError(response: Response, bodyText: string): { status: number; message: string } {
let parsed: Record<string, unknown> | null = null;
try {
parsed = JSON.parse(bodyText || "{}");
} catch {
parsed = null;
}
const errorObj = (parsed?.error as Record<string, unknown>) || undefined;
const code = (parsed?.code as string) || (errorObj?.code as string) || "";
const rawMessage =
(parsed?.message as string) || (errorObj?.message as string) || bodyText || response.statusText;
if (code === "trial_blocked") {
return {
status: response.status,
message: `Zed trial access is blocked upstream. The account can list hosted models, but Zed is refusing completions until trial/billing access is enabled or unblocked. Zed says: ${rawMessage}`,
};
}
if (code) {
return {
status: response.status,
message: `Zed ${code}: ${rawMessage}`,
};
}
return {
status: response.status,
message: rawMessage || `Zed upstream error: ${response.status}`,
};
}
async refreshCredentials(): Promise<Partial<ProviderCredentials> | null> {
return null;
}
needsRefresh(): boolean {
return false;
}
}
export default ZedHostedExecutor;
export const __test__ = {
normalizeZedProvider,
unwrapZedLine,
wrapZedCompletionStream,
};

535
open-sse/shared/zedAuth.ts Normal file
View File

@@ -0,0 +1,535 @@
/**
* Zed Hosted Models — auth + model-catalog helpers.
*
* Zed's cloud aggregator (cloud.zed.dev) authenticates native apps with a
* self-generated RSA keypair instead of a registered OAuth client_id/secret:
*
* 1. The client (OmniRoute) generates an ephemeral RSA keypair.
* 2. It sends the public key to zed.dev/native_app_signin (as a URL param).
* 3. The user signs in inside their browser (Zed itself brokers GitHub/Google).
* 4. Zed's browser flow redirects to a local "native app" callback
* (`http://127.0.0.1:<port>/?user_id=...&access_token=...`) with the
* access token RSA-encrypted against the public key we sent in step 2.
* 5. OmniRoute decrypts the access token locally with the private key that
* never left the server (or the operator's browser/paste flow).
*
* No client_id/client_secret/Firebase key is embedded anywhere in this file —
* the "credential" is a keypair generated fresh per login attempt, so
* CLAUDE.md Hard Rule #11 (resolvePublicCred for embedded upstream secrets)
* does not apply here.
*
* Ported from decolua/9router PR #2328 (open-sse/shared/zedAuth.js),
* adapted to TypeScript + OmniRoute conventions. `fetch` is intentionally the
* global one — open-sse/utils/proxyFetch.ts monkey-patches `globalThis.fetch`
* with the proxy-aware dispatcher at module load, so every plain `fetch()`
* call in this codebase already goes through it.
*/
import crypto from "node:crypto";
export const ZED_WEB_BASE_URL = "https://zed.dev";
export const ZED_CLOUD_BASE_URL = "https://cloud.zed.dev";
export const ZED_LLM_BASE_URL = "https://cloud.zed.dev";
export const ZED_HEADERS = {
expiredToken: "x-zed-expired-token",
outdatedToken: "x-zed-outdated-token",
clientSupportsStatus: "x-zed-client-supports-status-messages",
clientSupportsStreamEnded: "x-zed-client-supports-stream-ended-request-completion-status",
serverSupportsStatus: "x-zed-server-supports-status-messages",
clientSupportsXai: "x-zed-client-supports-x-ai",
systemId: "x-zed-system-id",
} as const;
const PRIVATE_KEY_PREFIX = "zed-rsa-pkcs1:";
const LLM_TOKEN_TTL_MS = 50 * 60 * 1000;
const MODEL_CACHE_TTL_MS = 60 * 60 * 1000;
export type ZedRawModel = Record<string, unknown>;
export type ZedModel = {
id: string;
name: string;
provider: unknown;
isLatest: boolean;
contextLength: unknown;
contextLengthInMaxMode: unknown;
maxOutputTokens: unknown;
supportsTools: boolean;
supportsImages: boolean;
supportsThinking: boolean;
supportsDisablingThinking: boolean;
supportsFastMode: boolean;
supportsServerSideCompaction: boolean;
supportedEffortLevels: unknown;
supportsStreamingTools: boolean;
supportsParallelToolCalls: boolean;
isDisabled: boolean;
disabledReason: unknown;
};
export type ZedModelCatalog = {
expiresAt: number;
models: ZedModel[];
rawModels: ZedRawModel[];
rawById: Map<string, ZedRawModel>;
defaultModel: string;
defaultFastModel: string;
recommendedModels: string[];
};
export type ZedCredentials = {
accessToken?: string;
apiKey?: string;
providerSpecificData?: {
userId?: string;
systemId?: string;
organizationId?: unknown;
defaultOrganizationId?: unknown;
[key: string]: unknown;
} | null;
[key: string]: unknown;
};
export type ZedRequestConfig = {
webBaseUrl?: string;
cloudBaseUrl?: string;
llmBaseUrl?: string;
defaultNativeAppPort?: number;
[key: string]: unknown;
};
const llmTokenCache = new Map<string, { token: string; expiresAt: number }>();
const modelCache = new Map<string, ZedModelCatalog>();
const modelInflight = new Map<string, Promise<ZedModelCatalog>>();
function b64url(value: string): string {
return Buffer.from(value).toString("base64url");
}
function b64urlPadded(value: Buffer): string {
return value.toString("base64").replace(/\+/g, "-").replace(/\//g, "_");
}
function fromB64url(value: string): string {
return Buffer.from(String(value || ""), "base64url").toString("utf8");
}
function normalizeBaseUrl(baseUrl: unknown, fallback: string): string {
return String(baseUrl || fallback).replace(/\/+$/, "");
}
function zedUrl(
config: ZedRequestConfig | undefined,
key: "cloudBaseUrl" | "llmBaseUrl" | "webBaseUrl",
path: string,
fallbackBase: string
): string {
const base = normalizeBaseUrl(config?.[key], fallbackBase);
return `${base}${path}`;
}
/** Encode a PEM private key as an opaque verifier string that can flow through the
* generic OAuth `codeVerifier` slot (mirrors PKCE's code_verifier plumbing). */
export function encodeZedPrivateKeyVerifier(privateKeyPem: string): string {
return `${PRIVATE_KEY_PREFIX}${b64url(privateKeyPem)}`;
}
export function decodeZedPrivateKeyVerifier(verifier: unknown): string {
const value = String(verifier || "");
if (!value.startsWith(PRIVATE_KEY_PREFIX)) {
throw new Error("Missing Zed private key verifier; restart the login flow");
}
return fromB64url(value.slice(PRIVATE_KEY_PREFIX.length));
}
export type ZedNativeAuthData = {
authUrl: string;
privateKeyVerifier: string;
nativeAppPort: number;
systemId: string;
publicKey: string;
};
/** Generate a fresh RSA keypair + the zed.dev native_app_signin URL for it. */
export function createZedNativeAuthData(
config: ZedRequestConfig = {},
options: { nativeAppPort?: number; systemId?: string } = {}
): ZedNativeAuthData {
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "pkcs1", format: "der" },
privateKeyEncoding: { type: "pkcs1", format: "pem" },
});
const nativeAppPort = Number(options.nativeAppPort || config.defaultNativeAppPort || 58443);
const systemId = options.systemId || crypto.randomUUID();
const publicKeyString = b64urlPadded(publicKey as unknown as Buffer);
const signInUrl = new URL(
`${normalizeBaseUrl(config.webBaseUrl, ZED_WEB_BASE_URL)}/native_app_signin`
);
signInUrl.searchParams.set("native_app_port", String(nativeAppPort));
signInUrl.searchParams.set("native_app_public_key", publicKeyString);
if (systemId) signInUrl.searchParams.set("system_id", systemId);
return {
authUrl: signInUrl.toString(),
privateKeyVerifier: encodeZedPrivateKeyVerifier(privateKey as unknown as string),
nativeAppPort,
systemId,
publicKey: publicKeyString,
};
}
export type ZedCallbackPayload = {
userId: string;
encryptedAccessToken: string;
};
/** Parse the pasted native-app callback URL/JSON/query string into userId + encrypted token. */
export function parseZedCallbackPayload(input: unknown): ZedCallbackPayload {
const raw = String(input || "").trim();
if (!raw) throw new Error("Missing Zed callback URL");
let data: Record<string, unknown> = {};
try {
data = JSON.parse(raw);
} catch {
let url: URL;
try {
url = new URL(raw);
} catch {
try {
url = new URL(`http://127.0.0.1/?${raw.replace(/^\?/, "")}`);
} catch {
throw new Error("Invalid Zed callback URL");
}
}
url.searchParams.forEach((value, key) => {
data[key] = value;
});
}
const userId = data.user_id || data.userId;
const encryptedAccessToken = data.access_token || data.accessToken || data.token;
if (!userId || !encryptedAccessToken) {
throw new Error("Zed callback must include user_id and access_token");
}
return {
userId: String(userId),
encryptedAccessToken: String(encryptedAccessToken),
};
}
/** Decrypt the RSA-encrypted access token Zed returned, using our stored private key. */
export function decryptZedAccessToken(encryptedAccessToken: unknown, privateKeyVerifier: unknown): string {
const privateKey = decodeZedPrivateKeyVerifier(privateKeyVerifier);
const encrypted = Buffer.from(String(encryptedAccessToken), "base64url");
try {
return crypto
.privateDecrypt(
{ key: privateKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha256" },
encrypted
)
.toString("utf8");
} catch (oaepError) {
try {
return crypto
.privateDecrypt({ key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING }, encrypted)
.toString("utf8");
} catch {
const message = oaepError instanceof Error ? oaepError.message : String(oaepError);
throw new Error(`Failed to decrypt Zed access token: ${message}`);
}
}
}
export function buildZedUserAuthHeader(credentials: ZedCredentials | null | undefined): string {
const psd = credentials?.providerSpecificData || {};
const userId = psd.userId || (credentials as Record<string, unknown> | null)?.userId;
const accessToken = credentials?.accessToken || credentials?.apiKey;
if (!userId || !accessToken) {
throw new Error("Zed credential is missing userId or accessToken");
}
return `${userId} ${accessToken}`;
}
function getSystemId(credentials: ZedCredentials | null | undefined): string {
return String(
credentials?.providerSpecificData?.systemId ||
(credentials as Record<string, unknown> | null)?.systemId ||
""
);
}
async function fetchJson(url: string, options: RequestInit) {
const res = await fetch(url, options);
const text = await res.text();
let data = null;
if (text) {
try {
data = JSON.parse(text);
} catch {
data = { raw: text };
}
}
if (!res.ok) {
const message = data?.message || data?.error?.message || data?.error || text || `HTTP ${res.status}`;
const err = new Error(String(message)) as Error & { status?: number; body?: unknown };
err.status = res.status;
err.body = data;
throw err;
}
return data;
}
export async function fetchZedAuthenticatedUser(
credentials: ZedCredentials,
options: { config?: ZedRequestConfig; signal?: AbortSignal | null } = {}
) {
const config = options.config || {};
const headers: Record<string, string> = {
Accept: "application/json",
Authorization: buildZedUserAuthHeader(credentials),
};
const systemId = getSystemId(credentials);
if (systemId) headers[ZED_HEADERS.systemId] = systemId;
return fetchJson(zedUrl(config, "cloudBaseUrl", "/client/users/me", ZED_CLOUD_BASE_URL), {
method: "GET",
headers,
signal: options.signal ?? undefined,
});
}
function normalizeOrganizationId(value: unknown): string {
if (!value) return "";
if (typeof value === "string") return value;
if (typeof value === "object" && value !== null) {
const rec = value as Record<string, unknown>;
if (typeof rec[0] === "string") return rec[0] as string;
if (typeof rec.id === "string") return rec.id;
}
return String(value);
}
export function resolveZedOrganizationId(
credentials: ZedCredentials,
userInfo = null
): string {
const psd = credentials?.providerSpecificData || {};
const explicit = normalizeOrganizationId(psd.organizationId || psd.defaultOrganizationId);
if (explicit) return explicit;
const fromUser = normalizeOrganizationId(
userInfo?.default_organization_id || userInfo?.defaultOrganizationId
);
if (fromUser) return fromUser;
const org =
(userInfo?.organizations || []).find((item: Record<string, unknown>) => item?.is_personal) ||
userInfo?.organizations?.[0];
return normalizeOrganizationId(org?.id);
}
function zedUserCacheKey(credentials: ZedCredentials, organizationId: string): string {
const psd = credentials?.providerSpecificData || {};
const userId = psd.userId || (credentials as Record<string, unknown>).userId || "unknown";
const token = credentials?.accessToken || credentials?.apiKey || "";
return `${userId}:${organizationId || "default"}:${token.slice(-16)}`;
}
function zedModelCacheKey(credentials: ZedCredentials): string {
const psd = credentials?.providerSpecificData || {};
const org = psd.organizationId || psd.defaultOrganizationId || "default";
const token = credentials?.accessToken || credentials?.apiKey || "";
return `${psd.userId || "unknown"}:${org}:${token.slice(-16)}`;
}
export async function fetchZedLlmToken(
credentials: ZedCredentials,
options: {
config?: ZedRequestConfig;
organizationId?: string;
forceRefresh?: boolean;
signal?: AbortSignal | null;
} = {}
): Promise<string> {
const config = options.config || {};
let organizationId = options.organizationId || resolveZedOrganizationId(credentials);
if (!organizationId) {
const userInfo = await fetchZedAuthenticatedUser(credentials, options);
organizationId = resolveZedOrganizationId(credentials, userInfo);
}
if (!organizationId) throw new Error("No Zed organization selected");
const cacheKey = zedUserCacheKey(credentials, organizationId);
const cached = llmTokenCache.get(cacheKey);
if (!options.forceRefresh && cached && cached.expiresAt > Date.now()) return cached.token;
const headers: Record<string, string> = {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: buildZedUserAuthHeader(credentials),
};
const systemId = getSystemId(credentials);
if (systemId) headers[ZED_HEADERS.systemId] = systemId;
const data = await fetchJson(zedUrl(config, "cloudBaseUrl", "/client/llm_tokens", ZED_CLOUD_BASE_URL), {
method: "POST",
headers,
body: JSON.stringify({ organization_id: organizationId }),
signal: options.signal ?? undefined,
});
const token =
typeof data?.token === "string" ? data.token : data?.token?.[0] || data?.token?.value;
if (!token) throw new Error("Zed did not return an LLM token");
llmTokenCache.set(cacheKey, { token, expiresAt: Date.now() + LLM_TOKEN_TTL_MS });
return token;
}
export function shouldRefreshZedLlmToken(response: Response | null | undefined): boolean {
return (
response?.status === 401 ||
!!response?.headers?.has?.(ZED_HEADERS.expiredToken) ||
!!response?.headers?.has?.(ZED_HEADERS.outdatedToken)
);
}
export async function zedLlmFetch(
credentials: ZedCredentials,
path: string,
options: {
config?: ZedRequestConfig;
signal?: AbortSignal | null;
fetchOptions?: RequestInit;
organizationId?: string;
forceRefresh?: boolean;
} = {}
): Promise<Response> {
const config = options.config || {};
const url = zedUrl(config, "llmBaseUrl", path, ZED_LLM_BASE_URL);
const buildRequest = async (forceRefresh: boolean) => {
const token = await fetchZedLlmToken(credentials, { ...options, forceRefresh });
return fetch(url, {
...options.fetchOptions,
headers: {
...(options.fetchOptions?.headers || {}),
Authorization: `Bearer ${token}`,
},
signal: options.signal ?? undefined,
});
};
let response = await buildRequest(false);
if (shouldRefreshZedLlmToken(response)) {
response = await buildRequest(true);
}
return response;
}
function normalizeZedModelId(id: unknown): string {
if (!id) return "";
if (typeof id === "string") return id;
if (typeof id === "object" && id !== null) {
const rec = id as Record<string, unknown>;
if (typeof rec[0] === "string") return rec[0] as string;
if (typeof rec.id === "string") return rec.id;
}
return String(id);
}
export function mapZedModel(model: ZedRawModel): ZedModel | null {
const id = normalizeZedModelId(model?.id);
if (!id) return null;
return {
id,
name: (model.display_name as string) || (model.displayName as string) || id,
provider: model.provider,
isLatest: !!model.is_latest,
contextLength: model.max_token_count ?? model.maxTokenCount,
contextLengthInMaxMode: model.max_token_count_in_max_mode ?? model.maxTokenCountInMaxMode,
maxOutputTokens: model.max_output_tokens ?? model.maxOutputTokens,
supportsTools: !!model.supports_tools,
supportsImages: !!model.supports_images,
supportsThinking: !!model.supports_thinking,
supportsDisablingThinking: !!model.supports_disabling_thinking,
supportsFastMode: !!model.supports_fast_mode,
supportsServerSideCompaction: !!model.supports_server_side_compaction,
supportedEffortLevels: model.supported_effort_levels ?? model.supportedEffortLevels ?? [],
supportsStreamingTools: !!model.supports_streaming_tools,
supportsParallelToolCalls: !!model.supports_parallel_tool_calls,
isDisabled: !!model.is_disabled,
disabledReason: model.disabled_reason ?? null,
};
}
/** Resolve (and cache) the live Zed model catalog. Never hardcoded — always a live fetch. */
export async function resolveZedModels(
credentials: ZedCredentials,
options: {
config?: ZedRequestConfig;
signal?: AbortSignal | null;
forceRefresh?: boolean;
} = {}
): Promise<ZedModelCatalog | null> {
if (!credentials?.accessToken) return null;
const key = zedModelCacheKey(credentials);
const cached = modelCache.get(key);
if (!options.forceRefresh && cached && cached.expiresAt > Date.now()) return cached;
const existing = modelInflight.get(key);
if (existing && !options.forceRefresh) return existing;
const promise = (async (): Promise<ZedModelCatalog> => {
const response = await zedLlmFetch(credentials, "/models", {
...options,
fetchOptions: {
method: "GET",
headers: {
Accept: "application/json",
[ZED_HEADERS.clientSupportsXai]: "true",
},
},
});
if (!response.ok) {
const text = await response.text().catch(() => "");
throw new Error(`Zed models failed: ${response.status} ${text}`);
}
const data = await response.json();
const rawModels: ZedRawModel[] = Array.isArray(data?.models) ? data.models : [];
const models = rawModels
.map(mapZedModel)
.filter((m): m is ZedModel => !!m)
.filter((model) => !model.isDisabled);
const rawById = new Map<string, ZedRawModel>();
for (const raw of rawModels) {
const id = normalizeZedModelId(raw?.id);
if (id) rawById.set(id, raw);
}
const entry: ZedModelCatalog = {
expiresAt: Date.now() + MODEL_CACHE_TTL_MS,
models,
rawModels,
rawById,
defaultModel: normalizeZedModelId(data?.default_model ?? data?.defaultModel),
defaultFastModel: normalizeZedModelId(data?.default_fast_model ?? data?.defaultFastModel),
recommendedModels: (data?.recommended_models || data?.recommendedModels || [])
.map(normalizeZedModelId)
.filter(Boolean),
};
modelCache.set(key, entry);
return entry;
})();
modelInflight.set(key, promise);
try {
return await promise;
} finally {
if (modelInflight.get(key) === promise) modelInflight.delete(key);
}
}
export function clearZedCaches(): void {
llmTokenCache.clear();
modelCache.clear();
modelInflight.clear();
}

View File

@@ -440,6 +440,29 @@ export const ZED_CONFIG = {
manualImportUrl: "/api/providers/zed/manual-import",
};
// Zed Hosted Models Configuration (native-app RSA-keypair sign-in)
//
// Zed's cloud aggregator (cloud.zed.dev) does not use a registered OAuth
// client_id/secret. The client generates a fresh RSA keypair per login
// attempt and sends the public key to zed.dev/native_app_signin; Zed
// encrypts the resulting access token against that public key and redirects
// the browser to a local "native app" callback
// (http://127.0.0.1:<port>/?user_id=...&access_token=...). OmniRoute decrypts
// the token with the matching private key — see open-sse/shared/zedAuth.ts.
// No client_id/secret/Firebase key is embedded here (Hard Rule #11 does not
// apply — there is no upstream secret to embed).
export const ZED_HOSTED_CONFIG = {
webBaseUrl: "https://zed.dev",
cloudBaseUrl: "https://cloud.zed.dev",
llmBaseUrl: "https://cloud.zed.dev",
nativeSignInPath: "/native_app_signin",
userInfoUrl: "https://cloud.zed.dev/client/users/me",
llmTokenUrl: "https://cloud.zed.dev/client/llm_tokens",
modelsUrl: "https://cloud.zed.dev/models",
completionsUrl: "https://cloud.zed.dev/completions",
defaultNativeAppPort: 58443,
};
// OAuth timeout (5 minutes)
export const OAUTH_TIMEOUT = 300000;
@@ -467,4 +490,5 @@ export const PROVIDERS = {
CODEBUDDY_CN: "codebuddy-cn",
GROK_CLI: "grok-cli",
ZED: "zed",
ZED_HOSTED: "zed-hosted",
};

View File

@@ -110,7 +110,9 @@ export function getProvider(name) {
*/
export function generateAuthData(providerName, redirectUri) {
const provider = getProvider(providerName);
const { codeVerifier, codeChallenge, state } = generatePKCE();
const pkce = generatePKCE();
let codeVerifier = pkce.codeVerifier;
const { codeChallenge, state } = pkce;
if (provider.flowType === "import_token") {
let error: string;
@@ -145,7 +147,24 @@ export function generateAuthData(providerName, redirectUri) {
} else if (provider.flowType === "authorization_code_pkce") {
authUrl = provider.buildAuthUrl(provider.config, redirectUri, state, codeChallenge);
} else {
authUrl = provider.buildAuthUrl(provider.config, redirectUri, state);
const built = provider.buildAuthUrl(provider.config, redirectUri, state);
// Some non-PKCE "authorization_code" providers (e.g. zed-hosted) need to
// override the auto-generated PKCE codeVerifier/redirectUri with their own
// provider-specific verifier (e.g. an RSA private-key verifier) instead of
// an unused PKCE code_verifier — they return an object instead of a bare
// authUrl string. Existing providers all return a plain string, so this is
// backward compatible.
if (built && typeof built === "object" && typeof built.authUrl === "string") {
authUrl = built.authUrl;
if (typeof built.codeVerifier === "string" && built.codeVerifier) {
codeVerifier = built.codeVerifier;
}
if (typeof built.redirectUri === "string" && built.redirectUri) {
redirectUri = built.redirectUri;
}
} else {
authUrl = built;
}
}
return {

View File

@@ -28,6 +28,7 @@ import { windsurf } from "./windsurf";
import { grokCli } from "./grok-cli";
import { codebuddyCn } from "./codebuddy-cn";
import { zed } from "./zed";
import { zedHosted } from "./zed-hosted";
export const PROVIDERS = {
claude,
@@ -52,6 +53,7 @@ export const PROVIDERS = {
"codebuddy-cn": codebuddyCn,
// Zed IDE credential bridge — uses keychain import, not standard OAuth
zed,
"zed-hosted": zedHosted,
};
export default PROVIDERS;

View File

@@ -0,0 +1,89 @@
import { ZED_HOSTED_CONFIG } from "../constants/oauth";
import {
createZedNativeAuthData,
parseZedCallbackPayload,
decryptZedAccessToken,
fetchZedAuthenticatedUser,
resolveZedOrganizationId,
} from "@omniroute/open-sse/shared/zedAuth.ts";
/**
* Zed Hosted Models OAuth provider.
*
* flowType "authorization_code" (mirrors `cline`'s non-PKCE authorization_code
* shape), but with a twist: `buildAuthUrl` returns `{ authUrl, codeVerifier }`
* instead of a bare string — `codeVerifier` here carries the *RSA private key
* verifier* (see zedAuth.ts::encodeZedPrivateKeyVerifier), reusing the
* existing PKCE `codeVerifier` plumbing (generateAuthData → OAuthModal →
* /exchange) as the transport for the keypair, since Zed's own flow has no
* client_id/secret or authorization code to exchange — only a private key
* needed to decrypt whatever access token the browser callback carries.
*
* `code` at exchange time is the pasted native-app callback URL/query string
* (`http://127.0.0.1:<port>/?user_id=...&access_token=...`) — Zed always
* redirects to loopback + native_app_port, ignoring any `redirect_uri` we'd
* send, so `redirectUri` here is unused by exchangeToken (kept only to
* satisfy OAuthModal's generic "session must have a redirectUri" guard).
*/
export const zedHosted = {
config: ZED_HOSTED_CONFIG,
flowType: "authorization_code",
buildAuthUrl: (config: typeof ZED_HOSTED_CONFIG) => {
const authData = createZedNativeAuthData(config);
return {
authUrl: authData.authUrl,
codeVerifier: authData.privateKeyVerifier,
redirectUri: `http://127.0.0.1:${authData.nativeAppPort}/`,
};
},
exchangeToken: async (
config: typeof ZED_HOSTED_CONFIG,
code: string,
_redirectUri: string,
codeVerifier: string
) => {
const { userId, encryptedAccessToken } = parseZedCallbackPayload(code);
const accessToken = decryptZedAccessToken(encryptedAccessToken, codeVerifier);
const credentials = { accessToken, providerSpecificData: { userId } };
let email: string | undefined;
let name: string | undefined;
let organizationId = "";
try {
const userInfo = await fetchZedAuthenticatedUser(credentials, { config });
email = userInfo?.email || userInfo?.github_login;
name = userInfo?.name || userInfo?.github_login;
organizationId = resolveZedOrganizationId(credentials, userInfo);
} catch {
// Non-fatal — the account still works without a resolved email/org;
// fetchZedLlmToken will resolve the organization lazily on first use.
}
return {
access_token: accessToken,
user_id: userId,
email,
name,
organization_id: organizationId,
};
},
mapTokens: (tokens: {
access_token: string;
user_id: string;
email?: string;
name?: string;
organization_id?: string;
}) => ({
accessToken: tokens.access_token,
// Zed's native-app access tokens are long-lived; no refresh flow is
// exposed by the aggregator, so no expiresIn/refreshToken here.
name: tokens.name || tokens.email || null,
email: tokens.email,
providerSpecificData: {
userId: tokens.user_id,
organizationId: tokens.organization_id || undefined,
},
}),
};
export default zedHosted;

View File

@@ -322,7 +322,11 @@ export default function OAuthModal({
// Claude Code and Cline OAuth flows can finish on provider-hosted pages that
// show an auth code instead of redirecting back to OmniRoute.
// Start directly in manual mode so users always have an input to paste code/url.
if (provider === "claude" || provider === "cline") {
// zed-hosted's native-app sign-in always redirects the browser to a local
// 127.0.0.1:<port> callback that OmniRoute never listens on (the port is
// arbitrary and unrelated to the dashboard's own port) — nothing can
// auto-close the popup, so always show the manual paste-URL input.
if (provider === "claude" || provider === "cline" || provider === "zed-hosted") {
forceManual = true;
}

View File

@@ -133,6 +133,19 @@ export const OAUTH_PROVIDERS = {
authHint:
"Zed stores LLM provider credentials (OpenAI, Anthropic, Google, Mistral, xAI) in the OS keychain. Use the Import button below to discover and import them automatically.",
},
"zed-hosted": {
id: "zed-hosted",
alias: undefined,
name: "Zed Hosted Models",
icon: "code_blocks",
color: "#101010",
textIcon: "ZH",
website: "https://zed.dev",
subscriptionRisk: true,
riskNoticeVariant: "oauth",
authHint:
"Sign in with your Zed account (native-app sign-in). OmniRoute generates a one-time RSA keypair and opens zed.dev to authorize it — on a remote/headless install, copy the resulting 127.0.0.1 callback URL from your browser's address bar and paste it back here. Distinct from the 'Zed IDE' credential-import entry above: this proxies chat completions through Zed's own hosted model aggregator (cloud.zed.dev), fronting Anthropic/OpenAI/Google/xAI models under your Zed plan.",
},
trae: {
id: "trae",
alias: "tr",

View File

@@ -42,6 +42,7 @@ const {
QWEN_CONFIG,
TRAE_CONFIG,
WINDSURF_CONFIG,
ZED_HOSTED_CONFIG,
} = oauthModule;
const { getAntigravityLoadCodeAssistMetadata } = antigravityHeadersModule;
@@ -68,6 +69,7 @@ const EXPECTED_PROVIDER_KEYS = [
"grok-cli",
"codebuddy-cn",
"zed",
"zed-hosted",
];
const EXPECTED_CONFIG_BY_PROVIDER = {
@@ -91,6 +93,7 @@ const EXPECTED_CONFIG_BY_PROVIDER = {
"grok-cli": GROK_CLI_CONFIG,
"codebuddy-cn": CODEBUDDY_CN_CONFIG,
zed: ZED_CONFIG,
"zed-hosted": ZED_HOSTED_CONFIG,
};
const REQUIRED_FIELDS_BY_PROVIDER = {
@@ -138,6 +141,7 @@ const REQUIRED_FIELDS_BY_PROVIDER = {
windsurf: ["authorizeUrl", "apiServerUrl", "exchangePath", "inferenceUrl"],
"devin-cli": ["authorizeUrl", "apiServerUrl", "exchangePath", "inferenceUrl"],
trae: ["apiEndpoint", "chatEndpoint", "webUrl"],
"zed-hosted": ["webBaseUrl", "cloudBaseUrl", "llmBaseUrl", "userInfoUrl", "llmTokenUrl", "modelsUrl"],
};
function getByPath(object, path) {
@@ -332,6 +336,37 @@ test("browser-based providers expose buildAuthUrl and return provider-specific a
assert.equal(clineUrl.origin, "https://api.cline.bot");
});
// zed-hosted's buildAuthUrl deliberately returns an object (authUrl + codeVerifier +
// redirectUri) instead of a bare string — generateAuthData() in providers.ts special-
// cases this shape to thread an RSA private-key verifier through the existing PKCE
// codeVerifier slot (see src/lib/oauth/providers/zed-hosted.ts header comment).
test("zed-hosted buildAuthUrl returns {authUrl, codeVerifier, redirectUri} carrying a fresh RSA keypair", () => {
const built = PROVIDERS["zed-hosted"].buildAuthUrl(ZED_HOSTED_CONFIG);
assert.equal(typeof built, "object");
assert.ok(built.authUrl.startsWith("https://zed.dev/native_app_signin?"));
const url = new URL(built.authUrl);
assert.ok(url.searchParams.get("native_app_public_key"));
assert.ok(built.codeVerifier.startsWith("zed-rsa-pkcs1:"));
assert.ok(built.redirectUri.startsWith("http://127.0.0.1:"));
});
test("generateAuthData honors an object-returning buildAuthUrl (zed-hosted) without breaking string-returning providers", async () => {
const oauthHelpers = await import("../../src/lib/oauth/providers.ts");
const zedAuthData = oauthHelpers.generateAuthData("zed-hosted", "http://localhost:20128/callback");
assert.equal(zedAuthData.flowType, "authorization_code");
assert.ok(zedAuthData.authUrl.startsWith("https://zed.dev/native_app_signin?"));
assert.ok(zedAuthData.codeVerifier.startsWith("zed-rsa-pkcs1:"));
assert.ok(zedAuthData.redirectUri.startsWith("http://127.0.0.1:"));
// A string-returning provider (cline) must still get the plain PKCE codeVerifier,
// not be affected by the object-return branch added for zed-hosted.
const clineAuthData = oauthHelpers.generateAuthData("cline", "http://localhost:20128/callback");
assert.equal(typeof clineAuthData.authUrl, "string");
assert.equal(clineAuthData.redirectUri, "http://localhost:20128/callback");
assert.ok(clineAuthData.codeVerifier);
assert.ok(!clineAuthData.codeVerifier.startsWith("zed-rsa-pkcs1:"));
});
// Regression for #3861: GitLab Duo needs an operator-registered OAuth client_id.
// When it's missing, buildAuthUrl must return null (like Qoder) so the authorize route
// can surface a clear "configure it" message — it previously THREW, which the route

View File

@@ -0,0 +1,416 @@
import test, { describe } from "node:test";
import assert from "node:assert/strict";
import crypto from "node:crypto";
import { REGISTRY } from "../../open-sse/config/providers/index.ts";
import { getExecutor, hasSpecializedExecutor } from "../../open-sse/executors/index.ts";
import { ZedHostedExecutor, __test__ } from "../../open-sse/executors/zed-hosted.ts";
import {
createZedNativeAuthData,
encodeZedPrivateKeyVerifier,
decodeZedPrivateKeyVerifier,
decryptZedAccessToken,
parseZedCallbackPayload,
buildZedUserAuthHeader,
resolveZedOrganizationId,
mapZedModel,
clearZedCaches,
} from "../../open-sse/shared/zedAuth.ts";
const { normalizeZedProvider, unwrapZedLine } = __test__;
// ─── Registry ──────────────────────────────────────────────────────────────
describe("zed-hosted registry entry", () => {
test("registers under id zed-hosted, distinct from the zed IDE-import id", () => {
const entry = REGISTRY["zed-hosted"];
assert.ok(entry, "REGISTRY.zed-hosted must exist");
assert.equal(entry.id, "zed-hosted");
assert.equal(entry.executor, "zed-hosted");
assert.equal(entry.authType, "oauth");
assert.notEqual("zed-hosted", "zed", "must not collide with the existing zed IDE id");
});
test("models is empty — catalog is fetched live, never hardcoded", () => {
const entry = REGISTRY["zed-hosted"];
assert.deepEqual(entry.models, []);
assert.equal(typeof entry.modelsUrl, "string");
assert.ok(entry.modelsUrl.length > 0);
});
test("no embedded oauth client_id/client_secret literal (Hard Rule #11 N/A — no upstream secret)", () => {
const entry = REGISTRY["zed-hosted"];
assert.equal(entry.oauth, undefined);
});
test("executor is wired in the executors map", () => {
assert.ok(hasSpecializedExecutor("zed-hosted"));
assert.ok(getExecutor("zed-hosted") instanceof ZedHostedExecutor);
});
});
// ─── zedAuth: RSA keypair + native-app sign-in URL ─────────────────────────
describe("createZedNativeAuthData", () => {
test("generates a fresh keypair and a native_app_signin URL carrying the public key", () => {
const authData = createZedNativeAuthData();
assert.match(authData.authUrl, /^https:\/\/zed\.dev\/native_app_signin\?/);
const url = new URL(authData.authUrl);
assert.ok(url.searchParams.get("native_app_public_key"));
assert.equal(url.searchParams.get("native_app_port"), String(authData.nativeAppPort));
assert.ok(authData.privateKeyVerifier.startsWith("zed-rsa-pkcs1:"));
assert.ok(authData.systemId.length > 0);
});
test("two calls produce different keypairs (never reused across login attempts)", () => {
const first = createZedNativeAuthData();
const second = createZedNativeAuthData();
assert.notEqual(first.privateKeyVerifier, second.privateKeyVerifier);
});
test("honors a custom nativeAppPort", () => {
const authData = createZedNativeAuthData({}, { nativeAppPort: 12345 });
assert.equal(authData.nativeAppPort, 12345);
const url = new URL(authData.authUrl);
assert.equal(url.searchParams.get("native_app_port"), "12345");
});
});
describe("zed private key verifier encode/decode round-trip", () => {
test("round-trips an RSA PEM private key through the opaque verifier string", () => {
const { privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "pkcs1", format: "der" },
privateKeyEncoding: { type: "pkcs1", format: "pem" },
});
const verifier = encodeZedPrivateKeyVerifier(privateKey);
assert.ok(verifier.startsWith("zed-rsa-pkcs1:"));
const decoded = decodeZedPrivateKeyVerifier(verifier);
assert.equal(decoded, privateKey);
});
test("rejects a malformed/missing verifier", () => {
assert.throws(() => decodeZedPrivateKeyVerifier(""), /Missing Zed private key verifier/);
assert.throws(() => decodeZedPrivateKeyVerifier("not-a-zed-verifier"), /Missing Zed private key verifier/);
});
});
describe("RSA encrypt/decrypt round-trip (native-app callback token decryption)", () => {
test("decrypts an OAEP-encrypted access token using the matching private key", () => {
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "pkcs1", format: "pem" },
privateKeyEncoding: { type: "pkcs1", format: "pem" },
});
const plaintextToken = "zed_access_token_abc123";
const encrypted = crypto.publicEncrypt(
{ key: publicKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha256" },
Buffer.from(plaintextToken, "utf8")
);
const verifier = encodeZedPrivateKeyVerifier(privateKey);
const decrypted = decryptZedAccessToken(encrypted.toString("base64url"), verifier);
assert.equal(decrypted, plaintextToken);
});
test("falls back to PKCS1 padding when OAEP fails, else throws a clear error", () => {
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "pkcs1", format: "pem" },
privateKeyEncoding: { type: "pkcs1", format: "pem" },
});
const plaintextToken = "zed_access_token_pkcs1";
const encrypted = crypto.publicEncrypt(
{ key: publicKey, padding: crypto.constants.RSA_PKCS1_PADDING },
Buffer.from(plaintextToken, "utf8")
);
const verifier = encodeZedPrivateKeyVerifier(privateKey);
const decrypted = decryptZedAccessToken(encrypted.toString("base64url"), verifier);
assert.equal(decrypted, plaintextToken);
});
test("throws a descriptive error when the ciphertext is structurally invalid for the key", () => {
// Node/OpenSSL's PKCS1 fallback is deliberately lenient (implicit-reject
// padding, a Bleichenbacher-oracle mitigation) — arbitrary garbage bytes at
// the modulus size still "succeed" with meaningless plaintext instead of
// throwing, on both padding modes. An empty ciphertext is the one shape
// that reliably fails both padding attempts (RSA "data too small"),
// exercising the combined try/catch → rethrow path.
const { privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "pkcs1", format: "pem" },
privateKeyEncoding: { type: "pkcs1", format: "pem" },
});
const verifier = encodeZedPrivateKeyVerifier(privateKey);
assert.throws(
() => decryptZedAccessToken(Buffer.alloc(0).toString("base64url"), verifier),
/Failed to decrypt Zed access token/
);
});
});
describe("parseZedCallbackPayload", () => {
test("parses a native-app callback URL (127.0.0.1:<port>/?user_id=...&access_token=...)", () => {
const parsed = parseZedCallbackPayload(
"http://127.0.0.1:58443/?user_id=user_123&access_token=ZW5jcnlwdGVk"
);
assert.equal(parsed.userId, "user_123");
assert.equal(parsed.encryptedAccessToken, "ZW5jcnlwdGVk");
});
test("parses a bare query string (no scheme/host)", () => {
const parsed = parseZedCallbackPayload("?user_id=abc&access_token=xyz");
assert.equal(parsed.userId, "abc");
assert.equal(parsed.encryptedAccessToken, "xyz");
});
test("parses JSON input", () => {
const parsed = parseZedCallbackPayload(JSON.stringify({ user_id: "u1", access_token: "t1" }));
assert.equal(parsed.userId, "u1");
assert.equal(parsed.encryptedAccessToken, "t1");
});
test("rejects empty input", () => {
assert.throws(() => parseZedCallbackPayload(""), /Missing Zed callback URL/);
});
test("rejects a payload missing user_id or access_token", () => {
assert.throws(
() => parseZedCallbackPayload("http://127.0.0.1:1/?user_id=only"),
/must include user_id and access_token/
);
});
});
describe("buildZedUserAuthHeader", () => {
test('builds the "<userId> <accessToken>" scheme (not Bearer)', () => {
const header = buildZedUserAuthHeader({
accessToken: "tok",
providerSpecificData: { userId: "u1" },
});
assert.equal(header, "u1 tok");
});
test("throws when userId or accessToken is missing", () => {
assert.throws(() => buildZedUserAuthHeader({ accessToken: "tok" }));
assert.throws(() => buildZedUserAuthHeader({ providerSpecificData: { userId: "u1" } }));
});
});
describe("resolveZedOrganizationId", () => {
test("prefers an explicit providerSpecificData.organizationId", () => {
const orgId = resolveZedOrganizationId({
providerSpecificData: { organizationId: "org-explicit" },
} as any);
assert.equal(orgId, "org-explicit");
});
test("falls back to the personal organization from userInfo", () => {
const orgId = resolveZedOrganizationId({ providerSpecificData: {} } as any, {
organizations: [
{ id: "org-team", is_personal: false },
{ id: "org-personal", is_personal: true },
],
});
assert.equal(orgId, "org-personal");
});
test("falls back to the first organization when none is personal", () => {
const orgId = resolveZedOrganizationId({ providerSpecificData: {} } as any, {
organizations: [{ id: "org-first" }, { id: "org-second" }],
});
assert.equal(orgId, "org-first");
});
});
describe("mapZedModel", () => {
test("maps a raw Zed model into the normalized shape", () => {
const mapped = mapZedModel({
id: "claude-sonnet-5",
display_name: "Claude Sonnet 5",
provider: "anthropic",
max_token_count: 1000000,
max_output_tokens: 128000,
supports_tools: true,
supports_images: true,
supports_thinking: true,
});
assert.ok(mapped);
assert.equal(mapped?.id, "claude-sonnet-5");
assert.equal(mapped?.name, "Claude Sonnet 5");
assert.equal(mapped?.contextLength, 1000000);
assert.equal(mapped?.supportsTools, true);
});
test("returns null for a model with no id", () => {
assert.equal(mapZedModel({}), null);
});
});
// ─── Executor: provider-family inference ────────────────────────────────────
describe("normalizeZedProvider", () => {
test("maps explicit provider strings", () => {
assert.equal(normalizeZedProvider("anthropic", "any"), "Anthropic");
assert.equal(normalizeZedProvider("openai", "any"), "OpenAi");
assert.equal(normalizeZedProvider("open_ai", "any"), "OpenAi");
assert.equal(normalizeZedProvider("google", "any"), "Google");
assert.equal(normalizeZedProvider("gemini", "any"), "Google");
assert.equal(normalizeZedProvider("xai", "any"), "XAi");
assert.equal(normalizeZedProvider("x-ai", "any"), "XAi");
});
test("infers from the model id when provider is absent", () => {
assert.equal(normalizeZedProvider(null, "claude-sonnet-5"), "Anthropic");
assert.equal(normalizeZedProvider(null, "gemini-3.1-pro"), "Google");
assert.equal(normalizeZedProvider(null, "grok-4"), "XAi");
assert.equal(normalizeZedProvider(null, "gpt-5.5"), "OpenAi");
assert.equal(normalizeZedProvider(null, "some-unknown-model"), "OpenAi");
});
});
// ─── Executor: NDJSON line unwrapping ───────────────────────────────────────
describe("unwrapZedLine", () => {
test("parses an event line", () => {
const line = JSON.stringify({ event: { type: "message_start" } });
assert.deepEqual(unwrapZedLine(line), { event: { type: "message_start" } });
});
test("parses a status line", () => {
const line = JSON.stringify({ status: "stream_ended" });
assert.deepEqual(unwrapZedLine(line), { status: "stream_ended" });
});
test('recognizes the "[DONE]" sentinel, with or without an SSE "data:" prefix', () => {
assert.deepEqual(unwrapZedLine("[DONE]"), { done: true });
assert.deepEqual(unwrapZedLine("data: [DONE]"), { done: true });
});
test("returns null for a blank line", () => {
assert.equal(unwrapZedLine(""), null);
assert.equal(unwrapZedLine(" "), null);
});
test("returns null for unparsable JSON", () => {
assert.equal(unwrapZedLine("not json"), null);
});
});
// ─── Executor: resolveModel + zedLlmFetch (mocked fetch) ────────────────────
describe("ZedHostedExecutor.resolveModel + zedLlmFetch (mocked upstream)", () => {
const credentials = {
accessToken: "plaintext-access-token",
providerSpecificData: { userId: "u1", organizationId: "org-1" },
};
test("resolves the provider family from the live model catalog", async (t) => {
clearZedCaches();
const calls: string[] = [];
const originalFetch = globalThis.fetch;
t.after(() => {
globalThis.fetch = originalFetch;
clearZedCaches();
});
globalThis.fetch = (async (input: RequestInfo | URL) => {
const url = String(input);
calls.push(url);
if (url.includes("/client/llm_tokens")) {
return new Response(JSON.stringify({ token: "llm-token-abc" }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
if (url.includes("/models")) {
return new Response(
JSON.stringify({
models: [
{ id: "claude-sonnet-5", provider: "anthropic", display_name: "Claude Sonnet 5" },
{ id: "gpt-5.5", provider: "openai", display_name: "GPT-5.5" },
],
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
throw new Error(`Unexpected fetch: ${url}`);
}) as typeof fetch;
const executor = new ZedHostedExecutor();
const result = await executor.resolveModel("claude-sonnet-5", credentials as any, undefined, undefined);
assert.equal(result.provider, "Anthropic");
assert.ok(calls.some((u) => u.includes("/client/llm_tokens")));
assert.ok(calls.some((u) => u.includes("/models")));
});
test("falls back to model-id inference when the catalog fetch fails", async (t) => {
clearZedCaches();
const originalFetch = globalThis.fetch;
t.after(() => {
globalThis.fetch = originalFetch;
clearZedCaches();
});
globalThis.fetch = (async () => {
throw new Error("network down");
}) as typeof fetch;
const executor = new ZedHostedExecutor();
const warnCalls: string[] = [];
const result = await executor.resolveModel(
"gemini-3.1-pro",
credentials as any,
undefined,
{ warn: (_tag: string, msg: string) => warnCalls.push(msg) } as any
);
assert.equal(result.provider, "Google");
assert.ok(warnCalls.length > 0);
});
});
// ─── Executor: parseError ────────────────────────────────────────────────────
describe("ZedHostedExecutor.parseError", () => {
const executor = new ZedHostedExecutor();
test("surfaces a friendly message for trial_blocked", () => {
const response = new Response(null, { status: 402 });
const result = executor.parseError(
response,
JSON.stringify({ code: "trial_blocked", message: "trial exhausted" })
);
assert.equal(result.status, 402);
assert.match(result.message, /trial\/billing access/);
});
test("prefixes other error codes with Zed", () => {
const response = new Response(null, { status: 400 });
const result = executor.parseError(response, JSON.stringify({ code: "bad_request", message: "oops" }));
assert.equal(result.message, "Zed bad_request: oops");
});
test("falls back to raw body text when there is no code", () => {
const response = new Response(null, { status: 500, statusText: "Internal Server Error" });
const result = executor.parseError(response, "boom");
assert.equal(result.message, "boom");
});
test("never leaks a stack trace (Hard Rule #12 — message stays upstream-text-only)", () => {
const response = new Response(null, { status: 500 });
const result = executor.parseError(response, "totally fine upstream text");
assert.ok(!result.message.includes(" at "));
});
});
// ─── Executor: no proactive refresh (long-lived native-app token) ──────────
describe("ZedHostedExecutor credential-refresh contract", () => {
test("needsRefresh is always false — no refresh flow exposed by Zed", () => {
const executor = new ZedHostedExecutor();
assert.equal(executor.needsRefresh(), false);
});
test("refreshCredentials resolves to null", async () => {
const executor = new ZedHostedExecutor();
assert.equal(await executor.refreshCredentials(), null);
});
});