Qwen OAuth flow (portal.qwen.ai) stopped working reliably; drop the
executor, registry entry, OAuth provider/service, token refresh
profile, usage handler, and related test coverage and baselines.
PAT-to-job-token exchange was duplicated between the executor and the model service, each with its own cache. Consolidate into qoderModels.js and have the executor import it.
Also add a qoder case to the API-key validate route - the generic OpenAI-compat probe cannot validate a PAT (needs job-token exchange + COSY signing first), so bulk-add always reported unknown for qoder keys.
Adds pt-... token auth as an alternative to OAuth device flow. A PAT can't
sign COSY requests directly, so it's exchanged for a short-lived job token
(jt-...) plus userId via openapi.qoder.sh, then used for signing.
Also fixes job-token traffic (jt-...) being rejected by api3.qoder.sh with
403 "Login expired" — the official qodercli serves jt- traffic from
api2.qoder.sh instead, so buildUrl/model-list routing now branches on it.
Quota usage and the dashboard add-key modal are updated to resolve PAT
credentials and label the field correctly, and bulk-add now validates
each key so it gets a real testStatus instead of a hardcoded "unknown".
Exchange Personal Access Tokens for short-lived job tokens, close the
SSE stream on terminal frames so non-streaming clients do not hang,
re-enable OAuth plus API-key auth modes, and replace the model catalog
with the current Qoder aliases.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Real Cursor IDE now uses AgentService at agent.api5.cursor.sh (HTTP/2-only)
while 9router still spoke the retired ChatService at api2.cursor.sh with
outdated headers, producing HTTP 429 "Update Required". Add an executeAgent
path that builds an agent.v1.RunRequest Connect RPC over a raw http2 stream
and fetches the account-specific usable model catalog via GetUsableModels.
Also implement MCP tool calling over AgentService: encode OpenAI tools as
AgentRunRequest.mcp_tools (McpToolDefinition with google.protobuf.Value
input_schema), decode McpArgs tool calls, and forward them to the client as
OpenAI tool_calls so the client runs the tool and resumes in the next turn.
Reply to request_context_args with a non-empty RequestContext, to server
heartbeats with client_heartbeat, and to KV blob get/set with empty results,
so action queries no longer stall the stream. Fold the client system prompt
into the user message (custom_system_prompt makes the server return an empty
turn). Bump clientVersion to 3.12.17 and add the x-cursor-client-commit
header so the gateway identifies as a current Cursor IDE release.
Bump client_version to 0.144.6 (above the 0.144.0 gate in codex CLI's
manifest) so /codex/models no longer returns 200 with newest entries
silently filtered out. Add the originator: codex_cli_rs header used by
every other codex call site, and move the entry onto buildOAuthResolver
so token refresh on 401/403 and a warning field on empty results are
wired in, matching gemini-cli and grok-cli.
8b9cac1 swapped alicode-intl to the DashScope compatible-mode endpoint to
fix#2591 for standard DashScope keys, but that broke Coding Plan keys
(sk-sp-...) which only work on coding-intl.dashscope.aliyuncs.com. The two
key types use two different hosts and are not interchangeable.
- alicode-intl: revert to coding-intl endpoint (Coding Plan keys)
- alims-intl: new provider for dashscope-intl/compatible-mode (standard keys)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Normalize every provider to one cache-inclusive convention via
canonicalizeUsage() before persist, and price cached + cache_creation as
subsets of prompt_tokens in calculateCostFromTokens() to stop
double-counting. usageRepo now delegates cost math to a single source.
Surface Cached tokens/cost across dashboard (overview, tokens, cost,
details). Merge Claude message_start cache with message_delta output so
cache counts survive. Compatible LLM nodes now allow multiple API-key
connections (key pool).
Co-authored-by: Cursor <cursoragent@cursor.com>
Add Kimchi as a browser-token OAuth provider routed through its
OpenAI-compatible gateway. Discover live models for /v1/models and
provider models, normalize Claude-compatible requests, and wire up
provider connection tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
Two additions on top of the merged CodeBuddy CN OAuth provider:
1. API key auth — connect with a direct API key (sent as
Authorization: Bearer), not only via OAuth device-code flow.
- registry: add authModes ["oauth","apikey"] + hasOAuth; combined Bearer
auth already forwards the key, token-refresh skips key connections.
- providers POST: accept dual-auth providers (authModes includes
"apikey") that live under category "oauth" — previously rejected as
"Invalid provider". Also fixes the same latent gap for xai.
2. Quota tracker — surface CodeBuddy CN credit balance on the usage
dashboard for both OAuth and API-key connections.
- registry: add transport.usage.url (Tencent billing endpoint) +
features.usage/usageApikey so the connection is quota-eligible.
- new CN-scoped handler services/usage/codebuddy-cn.js: POST the billing
meter endpoint, unwrap data.Response.Data.Accounts[]. The payload mixes
two credit types that must not be merged:
* refill/base ("基础体验包") — recurring allowance; cycle resets well
before the resource expires (CycleEndTime << DeductionEndTime).
Reads the *Cycle* balance, resetAt = next refresh. Cadence-labelled.
* bonus ("活动赠送包") — one-shot credits that expire at CycleEndTime.
Reads the plain Capacity balance. Labelled "Bonus Pack N".
One quota row per package, soonest-expiring first.
- register handler under "codebuddy-cn" in USAGE_HANDLERS.
Frontend needs no change — USAGE_SUPPORTED_PROVIDERS/USAGE_APIKEY_PROVIDERS
and the generic parseQuotaData branch already cover this shape.
Co-authored-by: Cursor <cursoragent@cursor.com>
GET /models is not part of the Anthropic API spec and many compatible
proxies do not implement it, causing the connection test to always fail
even with a valid API key.
Switch to POST /v1/messages with max_tokens=1 — the same approach used
for the built-in anthropic provider — and treat any non-401/403 response
as valid, since 400/529 still confirms the key was accepted.
Use node.defaultModel / connection.defaultModel when set so the test
respects the configured model rather than always falling back to
claude-3-haiku-20240307.
Co-authored-by: Rehan Choirul <rehanchrl@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Perplexity deprecated /models (404); switch to /v1/models for provider
test connection and models route. (#1895, fixes#1894)
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds long-lived API-key (ksk_) authentication for Kiro/AWS CodeWhisperer
and a direct claude:kiro / kiro:claude translation route that avoids the
lossy OpenAI two-hop pivot.
- translator: claude-to-kiro request + kiro-to-claude response translators,
registered on the exact source:target pair (direct route ahead of the
OpenAI pivot in index.js). claude-to-kiro uses shared schema constants
(ROLE/CLAUDE_BLOCK/DEFAULT_IMAGE_MIME) per app convention.
- auth: POST /api/oauth/kiro/api-key imports + validates a key via
ListAvailableProfiles, persists authMethod="api_key" (no refresh token).
- executor: send tokentype: API_KEY header and try *.amazonaws.com hosts
first for api-key creds; OAuth keeps kiro.dev first.
- fix: never inject the default placeholder profileArn for api-key auth
(CodeWhisperer 403s an ARN not owned by the key's account).
- ui: API Key method in the Kiro connect modal; surface api-key accounts
on the Quota Tracker and provider count.
- stream: env-overridable TTFT vs stall timeouts + Kiro keepalive frame.
- tests: claude-kiro-direct + kiro-profile-arn (11 tests).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Switch all siliconflow endpoints from api.siliconflow.cn to .com (chat,
validate, test, models) — the .cn host rejected valid .com-issued keys.
Replace the model list with 13 strong models verified live against the API.
Co-authored-by: Cursor <cursoragent@cursor.com>
Bootstrap → JWT → chat flow with auto-retry. Inject the MiMoCode
system marker required to pass the upstream 403 anti-abuse gate,
derive JWT expiry from the exp claim, and use a stable per-machine
fingerprint as the bootstrap client.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add shared OAuth credential lifecycle manager with provider-aware refresh
decisions. Implement CodexExecutor.refreshCredentials so 401/403 retry
refresh works for Codex, track lastRefreshAt and refresh before the
upstream stale-token window, preserve omitted idToken, and add
per-connection single-flight refresh to avoid refresh-token rotation races.
Merged from PR #1664.
Co-authored-by: Cursor <cursoragent@cursor.com>
OpenAI/Anthropic Compatible and Custom Embedding nodes allow exactly one
connection each. The guards were dropped during the bun:sqlite refactor
(v0.4.28), so duplicate POSTs were accepted (201) instead of rejected (400).
Restore the per-node existing-connection check in the POST handler.
Test: tests/unit/compatible-provider-connections.test.js now passes.
Co-authored-by: Cursor <cursoragent@cursor.com>
The "opencode-free" suggested-models filter only kept models whose id ends
in "-free", so free-but-unsuffixed models like `big-pickle` (routed via
open-sse/executors/opencode.js MESSAGES_MODELS) never appeared in the
import UI.
Extract the filters into a testable module and include a
KNOWN_FREE_OPENCODE_MODELS allowlist (big-pickle) alongside the "-free"
suffix check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Correctness:
- testUtils: drop checkExpiry so the userinfo URL probe actually runs (revoked
tokens used to look "active" until local 30-day expiry passed)
- auth.parseExpiry: handle numeric expiresAt, swap parseInt before Date.parse
so "2026" doesn't get interpreted as year-2026, treat expires_in:0 as
already-expired instead of fabricating a 30-day default
- providers.mapTokens: synthesize email from userId when fetchUserInfo fails
so OAuth dedup works (re-logins no longer accumulate "Account N" rows)
SSE wrapper:
- wrapQoderSSE: add !doneEmitted guard on success branch (chunks could leak
past [DONE] when an error envelope shared a TCP packet with a valid one)
- flush(): finalize TextDecoder + drain trailing buffer so the chunk carrying
finish_reason is delivered when upstream closes without a final \n
- sanitize literal \n inside inner OpenAI body so SSE framing stays intact
Robustness:
- executor: wrap buildCosyHeaders in try/catch so a missing accessToken
returns 401 (re-auth) instead of bubbling as 500
- executor: short-circuit on missing accessToken before signing
- executor: plumb proxyOptions/signal through buildQoderRequestBody so
proxy-only networks can fetch the model_config catalog
- qoderModels: dedupe concurrent first-time misses with an in-flight Promise
map (parallel chat windows now do 1 upstream fetch instead of N)
- qoderModels: check signal.aborted before addEventListener so a pre-aborted
parent signal cancels the inner fetch immediately
- auth: AbortController + 15s timeout on pollDeviceToken / fetchUserInfo to
prevent hung sockets when openapi.qoder.sh stalls mid-response
UX:
- OAuthModal: derive polling deadline from device-code expires_in (qoder
publishes 300s; the previous fixed 120s caused timeouts when users took
more than 2 minutes on the consent page)
Cleanup:
- delete src/lib/oauth/services/qoder.js — referenced removed config fields
(clientId/clientSecret/tokenUrl/authorizeUrl) and was re-exported from
services/index.js, so any future caller would TypeError on first use
## Features
- Xiaomi MiMo Token Plan: region selector (Singapore / China / Europe) — keys are cluster-specific
- Antigravity: risk confirmation dialog before first connection
- Gemini CLI: surface upstream retry delay on 429 errors
## Fixes
- MITM: cannot kill process on macOS under sudo (lsof not found in PATH)
- Stream: false-positive stall timeout on Claude reasoning / Kiro responses
- Tunnel: cannot re-enable after disable (stuck state)
- Tunnel: cloudflared error messages now include log tail for easier debugging
- Language switcher: applies selected locale immediately on close (#1234)
- Antigravity OAuth: metadata now matches the official client
## Improvements
- Gemini CLI: bump engine to 0.34.0
- Re-hide `qwen` (OAuth EOL) and `iflow` (not ready) providers
Internal model test routes fetched 127.0.0.1:UPDATER_CONFIG.appPort
(hardcoded 20128). When PORT env is set to a different value, the app
listens on PORT but the internal fetch still targets 20128, causing
"fetch failed" on /api/models/test and /api/providers/[id]/test-models.
Fall back to UPDATER_CONFIG.appPort only when process.env.PORT is unset.
## Features
- Add Cline & Kilo Code tool cards
- Tailscale TUN mode for stable Funnel TLS
- Sort APIKEY providers by usage, collapse to top 20
## Improvements
- Local Material Symbols font (no Google Fonts)
- Docker base: Bun → Node 22-alpine
- MITM reads aliases from JSON cache (no native sqlite)
- Stream stall timeout (2 min) in open-sse
## Fixes
- Fal.ai key test: use stable models endpoint
## Features
- Add bun:sqlite adapter with automatic runtime detection (Bun/Node)
- Add bulk API key import (format: `name|sk-key`, one per line)
## Fixes
- Fix add API key for custom providers