feat(zed): add Zed Hosted AI provider support - #2823
Conversation
Wire Zed cloud.zed.dev into the gateway so users can import Zed Editor credentials, mint/refresh LLM tokens, and route OpenAI-compatible chat through Zed's hosted models. Closes decolua#2821. Co-authored-by: Cursor <cursoragent@cursor.com>
| if (/(claude|anthropic)/i.test(m)) return "anthropic"; | ||
| if (/(gemini|google)/i.test(m)) return "google"; | ||
| if (/(grok|x[_-]?ai)/i.test(m)) return "x_ai"; |
There was a problem hiding this comment.
Hardcoded model-routing regex — should use config constants.
Per open-sse/AGENTS.md : "NEVER hardcode values, models, or block/role strings — use config/ + schema/ constants."
These provider-detection rules should live in open-sse/config/ (e.g. a zedProviderPatterns map in the Zed provider config), not inline in the translator.
| async refreshCredentials(credentials, log, proxyOptions = null) { | ||
| const psd = credentials?.providerSpecificData || {}; | ||
| const userId = psd.userId; | ||
| const zedAccessToken = psd.zedAccessToken || credentials?.refreshToken; | ||
| const organizationId = psd.organizationId; | ||
| if (!userId || !zedAccessToken) { | ||
| log?.warn?.("TOKEN_REFRESH", "Zed missing userId/zedAccessToken for LLM token refresh"); | ||
| return null; | ||
| } | ||
| try { | ||
| const base = (this.config.baseUrl || "https://cloud.zed.dev").replace(/\/$/, ""); | ||
| const path = PROVIDER_OAUTH.zed?.llmTokensPath || "/client/llm_tokens"; | ||
| const body = organizationId ? { organization_id: organizationId } : {}; | ||
| const res = await proxyAwareFetch(`${base}${path}`, { | ||
| method: "POST", | ||
| headers: { | ||
| Authorization: `${userId} ${zedAccessToken}`, | ||
| "Content-Type": "application/json", | ||
| Accept: "application/json", | ||
| }, | ||
| body: JSON.stringify(body), | ||
| }, proxyOptions); | ||
| const text = await res.text(); | ||
| if (!res.ok) { | ||
| log?.error?.("TOKEN_REFRESH", `Zed LLM token refresh failed (${res.status}): ${text.slice(0, 200)}`); | ||
| return null; | ||
| } | ||
| const data = JSON.parse(text); | ||
| const raw = data?.token; | ||
| const token = | ||
| typeof raw === "string" | ||
| ? raw | ||
| : raw && typeof raw === "object" | ||
| ? raw["0"] || raw.token || Object.values(raw)[0] | ||
| : null; | ||
| if (!token) return null; | ||
| return { | ||
| accessToken: token, | ||
| expiresIn: 3600, | ||
| providerSpecificData: { | ||
| llmToken: token, | ||
| lastLlmTokenAt: new Date().toISOString(), | ||
| }, | ||
| }; | ||
| } catch (err) { | ||
| log?.error?.("TOKEN_REFRESH", `Zed LLM token refresh failed: ${err.message}`); | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
Duplicated token refresh logic
ZedExecutor.refreshCredentials() and refreshZedToken() in tokenRefresh/providers.js contain near-identical logic:
- Same HTTP call to
POST /client/llm_tokens - Same Authorization header format (
${userId} ${zedAccessToken}) - Same CBOR-ish token unwrap (
raw["0"] || raw.token || Object.values(raw)[0])
A bug fix to one won't propagate to the other. Recommendation: have ZedExecutor.refreshCredentials() delegate to refreshZedToken() from tokenRefresh/providers.js, or extract the shared HTTP+unwrap logic into ZedService.refreshLlmToken() (which already exists in src/lib/oauth/services/zed.js:164) and call it from both places.
| // OpenAI chat.completion.chunk | ||
| if (event.choices?.[0]) { | ||
| const choice = event.choices[0]; | ||
| const delta = choice.delta || choice.message || {}; |
There was a problem hiding this comment.
Missing tool_call/tool_use handling — breaks agent workflows
The response parser handles choices.delta, Responses API, Gemini, and Anthropic content shapes, but has no branch for tool_calls or tool_use content blocks.
When a Zed-hosted model returns tool calls (Claude tool_use, OpenAI tool_calls), they'll be silently dropped. This breaks multi-turn agent conversations (Claude Code, Codex, Cline, etc.) that rely on tool-call round-trips.
At minimum, pass through tool_calls/tool_use deltas unchanged so downstream handlers can process them. Other executors (cursor, kiro) handle this — worth checking their implementations for the right pattern.
Move model→provider patterns into zedConstants, delegate executor token refresh to refreshZedToken, pass through/convert tool_calls/tool_use in the JSONL→SSE parser, and regenerate registry/index.js. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Addressed review feedback in 1e714e4:
|
Upstream landed RSA native-app Zed auth (zedAuth, OAuthModal, executor). Keep complementary CLI import/MITM, route provider patterns through zedConstants, unhide Zed in the registry, and align import credentials with the long-lived user-token shape expected by zedLlmFetch.
|
Synced this branch with upstream Context: Upstream already landed Zed via RSA native-app OAuth ( What this PR still adds / adjusts:
Ready for another look @sunba91-su. |
PascalCase CompletionBody.provider values caused opaque /completions 500s; restore HTTP wire tags (anthropic/open_ai) and align headers with the working executor path. Add ZedOAuthWrapper, live model catalog, OAuth callback fixes, plan-aware empty-catalog messages, and unit tests for the wire protocol. Co-authored-by: Cursor <cursoragent@cursor.com>
Register a /client/users/me handler so connected Zed accounts appear on /dashboard/quota with edit-prediction usage, plan labels, and unlimited rows. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Synced this branch with upstream Also stacked Zed quota tracking ( Ready for another look @decolua @sunba91-su. |

Summary
cloud.zed.dev) as an OAuth provider: registry, executor, OpenAI↔Zed translators, and live model catalogzedAuth, and MITM CLI capture for Zed credentials/completions500: restore snake_caseCompletionBody.providerwire tags (anthropic,open_ai,google,x_ai) — PascalCase values parse but fail at runtimeZedOAuthWrapper(browser vs import), OAuth session/callback fixes, plan-aware empty-catalog messages, and unit tests (tests/unit/zed-constants.test.js)masterv0.5.55/dashboard/quota(also feat(usage): show Zed plan quota on the dashboard #3407)Closes #2821
Related: #3406, #3407
cc @decolua
Test plan
/modelscatalog (student/Pro accounts)/v1/chat/completionsrequest through a Zed model and confirm streaming workscd tests && npx vitest run unit/zed-constants.test.js unit/zed-usage.test.jsnode tests/__baseline__/verify-oauth-urls.mjs/dashboard/quotawith a connected Zed account and confirm plan + edit-prediction rows