Wire Qoder credits into the Quota Tracker card grid:
- Add `qoder` to USAGE_SUPPORTED_PROVIDERS so the connection passes the
isUsageEligible filter at /api/providers/client and shows up in
providerOptions on the dashboard.
- Reshape getQoderUsage so quota records (user, organization) live under
`quotas` and scalar metadata (totalUsagePercentage, isQuotaExceeded,
expiresAt) are siblings — the parser used to walk Object.entries(quotas)
and would have rendered `totalUsagePercentage: 0.42` as a "0/0" row.
- Surface Qoder's expiresAt as resetAt on each quota record so the card
shows when credits reset.
- Add a parser branch in ProviderLimits/utils.js: rename internal keys
(user → "Personal", organization → "Organization"), drop empty org
buckets so personal accounts don't render a misleading "0/0 Organization"
row, and forward remaining/unit so the QuotaProgressBar can use them.
- Add Qoder's brand color (#EC4899) to ProviderLimitCard's color map.
42 tests still pass; build clean.
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
Fixes#1226
The Antigravity OAuth flow sent inconsistent client metadata between
the token acquisition phase and the API usage phase. String enum values
(IDE_UNSPECIFIED, PLATFORM_UNSPECIFIED) were used during OAuth token
exchange + loadCodeAssist + onboardUser, while numeric enums (ideType: 9,
platform: <computed>, pluginType: 2) were used in runtime API calls.
Google detected this fingerprint mismatch and blocked 9router accounts.
Replace all string enum occurrences with the correct numeric values:
- src/lib/oauth/constants/oauth.js: loadCodeAssistClientMetadata now
uses getOAuthPlatformEnum() for platform and numeric 9/2 for
ideType/pluginType, matching getOAuthClientMetadata()
- src/lib/oauth/services/antigravity.js: getMetadata() now delegates
to getOAuthClientMetadata() instead of returning hardcoded strings
- src/lib/oauth/providers.js: postExchange metadata now uses
getOAuthClientMetadata() instead of inline string enums
- open-sse/services/usage.js: getGeminiSubscriptionInfo body now uses
CLIENT_METADATA (already imported from appConstants.js) instead of
inline string enums
- Introduced a caching mechanism for in-flight token refresh requests to prevent race conditions and reduce unnecessary API calls.
- Added error handling for unrecoverable refresh errors, ensuring that the application can gracefully handle token reuse and invalidation scenarios.
- Updated the MITM server management to handle port 443 conflicts, allowing users to kill processes occupying the port before starting the server.
- Improved user feedback in the MitmServerCard component regarding port conflicts and admin privileges.
- Refactored the ComboList component to streamline the display of media provider combos.
This update aims to enhance the reliability and user experience of the token management and MITM functionalities.
- Introduced Xiaomi MiMo as a new provider in providerModels.js and providers.js.
- Updated model alias mapping in model.js to include Xiaomi MiMo.
- Enhanced validation route to support Xiaomi MiMo API endpoints.
- Added Xiaomi MiMo to APIKEY_PROVIDERS with relevant details.
This update expands the range of supported providers, improving integration capabilities.
- Updated refreshCredentials methods in various executors (Antigravity, Base, Default, Github, Kiro) to accept optional proxyOptions for improved proxy handling.
- Modified token refresh logic to utilize proxy-aware fetch for better network management.
- Enhanced usage retrieval functions to support proxy options, ensuring seamless integration with proxy configurations.
- Updated ModelSelectModal and ProviderInfoCard components to incorporate kind filtering for improved user experience in model selection.
- Added validation for API keys in the provider validation route, including support for webSearch/webFetch providers.
- Introduced Cloudflare AI as a new provider with specific configurations in providerModels.js and providers.js.
- Updated DefaultExecutor to handle account ID resolution for Cloudflare AI connections.
- Enhanced AddApiKeyModal and EditConnectionModal to include account ID input for Cloudflare AI.
- Implemented validation for Cloudflare AI API key connections in testUtils.js and route.js.
- Updated UI components to reflect changes in provider management and connection handling.
Add Volcengine Ark as a first-class API key provider with official model presets, endpoint configuration, API key validation, model discovery, connection testing, provider logo, and runtime alias mapping for `ark/*` model IDs.
Made-with: Cursor
Co-authored-by: kingsy <kingsylin@vip.qq.com>
- Introduced OpenCode Go provider with relevant configurations.
- Enhanced model management by allowing users to add and delete custom models.
- Updated UI components to support model selection for image types.
- Adjusted sidebar visibility to include image media kinds.
* fix: add multi-model support for Factory Droid CLI tool (closes#521)
* fix: show quota auth expired message for Kiro social auth accounts (closes#588)
The github provider in open-sse/config/providers.js was missing clientId,
causing refreshGitHubToken() to send client_id=undefined on 401 retry.
Also guard against undefined clientSecret in both refresh implementations.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds OpenCode (https://github.com/opencode-ai/opencode) as a supported
provider. OpenCode is an open-source terminal AI coding assistant with
an OpenAI-compatible API running locally.
Changes:
- open-sse/config/providers.js: add opencode baseUrl (localhost:4096)
with openai format (fully compatible, no custom headers needed)
- open-sse/services/model.js: add 'oc' alias → opencode
- src/shared/constants/providers.js: add opencode to subscription
providers with alias 'oc', icon 'terminal', color #E87040
Usage after setup: use model prefix 'oc/<model>' to route through
a running OpenCode instance (e.g. oc/claude-sonnet-4-5).
Closes#378
- Add comboRotationState Map to track rotation per combo
- Add getRotatedModels() to rotate model order based on strategy
- Pass comboName and comboStrategy to handleComboChat()
- Add comboStrategy setting (default: fallback)
- Add UI toggle for Combo Round Robin in profile settings
When enabled, each request to a combo starts with a different provider
instead of always starting with the first one, distributing load evenly.
Co-authored-by: Antigravity Agent <antigravity@example.com>
Kiro returns HTTP 400 with 'Improperly formed request (reset after Xs)'
when a model is not available on that account's subscription tier.
Previously this fell through to COOLDOWN_MS.transient (30s), causing
rapid retries on all accounts before failing — all accounts get locked
simultaneously with no actual fallback.
Treating this as paymentRequired (2min cooldown) ensures:
1. The model is locked on that account for 2min (proper cooldown)
2. The next available account is tried immediately
3. If all accounts hit the same 400, 9Router falls through to the
next provider in the combo
Fixes#384
- fixes#335: on transient 503/502/504, wait for short cooldown (up to
5s) before falling to next combo model, giving the provider a chance
to recover rather than immediately skipping it
- fixes#334: when all combo models have no active credentials, return
503 (Service Unavailable) instead of 406 (Not Acceptable), which is
more accurate and retriable by clients
Co-authored-by: Quan <quanle96@outlook.com>
PR: https://github.com/decolua/9router/pull/298
Thanks to @kwanLeeFrmVi for the original implementation. Here is a summary
of changes made during review integration:
- Replaced google-auth-library with jose (already a project dependency)
for SA JSON -> OAuth2 Bearer token minting (RS256 JWT assertion flow)
- Moved auth logic (parseSaJson, refreshVertexToken, token cache) from
executor into open-sse/services/tokenRefresh.js to match project pattern
- Fixed executor to use proxyAwareFetch instead of raw fetch (proxy support)
- Simplified buildUrl: use global aiplatform.googleapis.com endpoint for
both vertex (Gemini) and vertex-partner; removed region/modelFamily fields
- Added auto-detection of GCP project_id from raw API key via probe request
(vertex-partner only, cached per key)
- Added vertex/vertex-partner cases to /api/providers/validate/route.js
- Updated model lists based on live testing:
- vertex: gemini-3.1-pro-preview, gemini-3.1-flash-lite-preview,
gemini-3-flash-preview, gemini-2.5-flash (removed gemini-2.5-pro: 404)
- vertex-partner: deepseek-v3.2, qwen3-next-80b (instruct+thinking),
glm-5 (removed Mistral/Llama: not enabled in test project)
- gemini provider: added gemini-3.1-pro-preview, gemini-3.1-flash-lite-preview
- Removed bun.lock (project uses npm/package-lock.json)
- Removed region and modelFamily UI fields (global endpoint, auto-detect)
- Kiro token auto-refresh on AccessDeniedException (from commit 2)
Made-with: Cursor