The version label now renders 'v0.5.86 (<shortsha>)' when the flake build
injects NEXT_PUBLIC_GIT_SHA (git commit identity of the deployed artifact),
so any running dashboard traces to its exact source commit. Falls back to
semver-only when the env var is absent (local dev). Also fixes the profile
page repo link back to asepharyana/9router (MIBP merge target), replacing
the mhiqrambg fork label left over from the merge.
Reproduce the MiMo Desktop login surface server-side so headless/Docker
deployments can link a Xiaomi account without the Desktop client. The
account session (passToken) is captured during the proxied login and
stored per connection.
- Five account clusters (cn/sgp/ams/ru/in): per-region mimo-server host
and SSO sid, unknown region falls back to sgp
- mimo-v2.6-pro/flash/pro-ultraspeed dual-route models: account-service
route when desktop credentials exist, cloud API (sk- key) otherwise;
drops obsolete mimo-x-*-preview ids
- Desktop ServiceTokenManager 2-phase handshake (single serviceLogin with
target sid, raw 64-bit nonce preserved), per-region session cache
- reasoning_effort bridged to output_config.effort; i18n runtime now
observes characterData mutations so React text rewrites get translated
- Security hardening on the login proxy: session travels only in the
httpOnly cookie (never in the URL), proxy branch requires dashboard
auth, authorization/proxy-authorization never forwarded upstream, and
upstream Set-Cookie is not replayed onto the app origin
Preserve request headers through the Vercel/Cloudflare/Deno relay.
- proxyFetch: normalize options.headers via Object.fromEntries before
spreading into the relay headers. Spreading a Headers instance yields
{} and silently dropped every entry (auth + content-type), which is why
the same pool worked on one path and failed on another.
- vercel relay: build the forwarded header object from req.headers.entries()
instead of new Headers(req.headers), avoiding edge-runtime normalization
of casing/duplicate keys that some providers reject.
- Add "all" period option to usage dashboard and chart API
- Aggregate all days in getChartData when period is "all"
- Center overview card metrics and adjust font size to prevent truncation
Co-Authored-By: Claude Code <noreply@anthropic.com>
getUsageStats("all") shipped the entire usageHistory table to JS just to
refine lastUsed (~2s on 290K rows, on every statsEmitter update per SSE
listener). Bound the overlay to a 2-day indexed range scan; older entries
keep day-level lastUsed from usageDaily aggregates. Totals unaffected.
budgetToLevel now maps budgets > 80384 (midpoint of 32768/128000) to
"max" instead of clamping to "xhigh", so the top reasoning tier is
reachable from large budget_tokens requests.
- Add dedicated settings API routes for pi, omp, crush, forge, smelt, codewhale
- Integrate GenericCliToolCard with multi-model support for Pi and auto-discovery for OMP
- Register tools in cliTools catalog and all-statuses route
- Add official logos for all new CLI tools
Co-Authored-By: Claude Code <noreply@anthropic.com>
OpenRouter serves TypeSafe Jev at POST /api/v1/systemone with the same
request/response shape, so it plugs into systemoneConfig directly with
model typesafe/jev-1.13. Mark the System One media kind isNew and
render a New badge on the sidebar kind item and the Media Providers
accordion when any visible kind is new.
Co-Authored-By: Claude Code <noreply@anthropic.com>
The inline model test sent a chat-completions payload and failed with
500 on decision models. Add a systemone branch to pingModelByKind that
submits a native state+questions probe, and restore the test button
that was hidden for System One models.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Allow customizing evaluation instructions in the System One example
card, and disable the inline model probe button for System One models
since decision models do not accept chat completion probes.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Expose System One in the Media Providers sidebar accordion, set
kind to "systemone" on Jev models for ModelsCard filtering, wire
systemoneConfig into ProviderInfoCard, and configure GenericExampleCard
for interactive testing of decision models.
Co-Authored-By: Claude Code <noreply@anthropic.com>
New /v1/systemone pass-through route for Jev decision models (jev-1.13,
jev-1.13-free) on OpenCode Zen and the free lane. Follows the media-route
pattern: systemoneConfig in the registry drives URL/headers, the handler
mirrors the embeddings account-fallback + usage flow, and the dashboard
gains a System One media-provider kind. No chat-pipeline changes.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Register mimo-v2.6-flash-free on opencode-zen (chat lane) with a v2.6
capability pattern, and switch the vision adapter default from the old
mimo-v2.5-free.
Co-Authored-By: Claude Code <noreply@anthropic.com>
- Track both weekly and 5-hour session buckets in parseWeeklyQuotaSummary,
distinguishing sliding-window limits from multi-day weekly limits
- Preserve disabled session buckets at 0% rather than dropping them when weekly limits are reached
- Target 5-hour session rows (not weekly rows) during family exhaustion reconciliation in getAntigravityUsage
- Suppress synthesized per-model duplicate rows in dashboard normalization when family summaries are present
- Add unit test coverage for multi-bucket extraction, reconciliation isolation, and dashboard deduplication
- Add Cursor Default / Claude Default on Dashboard -> Combos to generate
unprefixed combo names that match Cursor/Claude client model IDs,
seeded with cu/... or cc/... so those clients can route through 9Router.
- Add multi-select bulk Delete and bulk Set strategy (Fallback / Round Robin / Fusion).
- Docs and unit tests for preset builder.
- Export aggregateComboCapabilities: union for vision/audio/search/pdf,
intersection for tools, primary-model for reasoning fields, min
contextWindow, max maxOutput
- Support nested combo resolution in aggregateComboCapabilities via
comboLookup with depth guard (max 6)
- Wire capability metadata to all /v1/models entries and combos
- Show aggregated ctx/max metadata line and capability badges on combo chips
- Pattern fixes: MiMo v2.5/omni reasoning, qwen max/plus vision, minimax m2.x vision
- Sync commandcode model catalog and add openai gpt-5.5
- Add unit tests for capability patterns and combo capability aggregation
Introduce AGENTS.md (root, primary agent instruction file) documenting six
hard-won fixes with explicit DO NOT / WHY, plus executable enforcement so a
future AI cannot delete or reintroduce them:
1. package-lock.json must be generated with npm 10 (Docker's npm 10.9.8).
npm 11 drops the top-level @emnapi/core + @emnapi/runtime entries npm 10
needs, breaking the tag-triggered Docker build at `npm ci` (happened on
v1.0.14). Add scripts/verify-lockfile-npm10.mjs + .npmrc + a Dockerfile
fail-fast check + a CI step + tests/unit/lockfile-npm10-guard.test.js.
Also re-fix the lockfile itself (regenerated with npm 10.9.8).
2. Tests must never write to the real ~/.9router DB (isolateDataDir).
3. Hidden providers must not leak into Usage (usageProviders !p.hidden).
4. codebuddy-intl connection test + OAuth identity.
5. Fork-only features that must survive upstream syncs.
6. Upstream sync procedure.
Each marker cross-references AGENTS.md and the covering test. CLAUDE.md now
points to AGENTS.md at the top. Verified: build ok, guard script passes,
full suite leaves the real DB count unchanged (38), 0 new regressions.
The Usage page auto-adds every noAuth free provider so connectionless
providers (opencode) still appear. It did not filter the registry's
hidden flag, so devin-cli and mimo-free — both category:"free" with
noAuth:true and hidden:true — showed up in Usage despite having no
connection and being absent from the Providers page (which does filter
hidden).
Extract the list assembly into buildUsageProviderList (shared/utils/
usageProviders.js) and skip hidden free providers there. Behavior for
visible noAuth providers (opencode) and dedup of active connections is
unchanged; covered by tests/unit/usage-provider-list.test.js.
Two bugs on codebuddy-intl connections:
1. Test Connection always failed with "Provider test not supported":
codebuddy-intl was missing from OAUTH_TEST_CONFIG, so testOAuthConnection
bailed before probing. Add a real probe against the Keycloak realm's
userinfo endpoint (URL derived from the token's iss claim), and wire
refreshable so an expired token is rotated via refreshCodebuddyIntlToken.
2. OAuth logins were named "Account N" with no email: mapTokens returned no
identity, even though the access token is a Keycloak JWT carrying
email/name claims. Extract email + displayName in mapTokens (new shared
extractDisplayNameFromAccessToken helper) so fresh logins are named and
deduped by identity.
Also add a run-once backfill (backfillCodeBuddyIntlIdentity) invoked from
GET /api/providers and /api/providers/client to self-heal existing rows
(backfill email/displayName, rename the generic "Account N" placeholder).
Verified live: the real connection now returns valid:true and the row is
renamed to the account email.
Replace the retired api-inference.huggingface.co host with the Inference
Providers router (router.huggingface.co): imageConfig.modelMap resolves
Hub ids to provider-resolved ids, image-to-image models receive the
source image in inputs with the prompt under parameters.prompt, and a
new sttConfig wires the hf-inference ASR route. The image catalog grows
to 23 models, dead whisper-small is replaced by whisper-large-v3-turbo,
the unusable "language" param is dropped, and edit models declare the
edit capability so the dashboard offers a source image. Adds unit and
end-to-end coverage plus a model-id guard on custom endpoints.
- Force stream:true and cloak decoy tools (bash, read) for OpenCode free tier
- Support connection testing for opencode in testUtils
- Expand error message slice limits in auth and ping to preserve workspace link
- Add concise China region link chip in provider detail page
Co-Authored-By: Claude Code <noreply@anthropic.com>
- executors/zed.js: use exact wire values (anthropic, open_ai, google, x_ai)
and strip incompatible Vertex safetySettings on the Google path
- shared/zedAuth.js: robust callback query parsing, reject garbage PKCS#1 v1.5
decryptions, and thread proxyOptions when fetching LLM tokens
- oauth: preserve systemId across authorize/register/exchange lifecycle,
renew proxy idle timeout on reuse, and ignore non-callback localhost requests
- shared/OAuthModal.js: track owned proxy in flowRef and stop at most once
- api/providers/[id]/models: add connection-scoped live Zed model resolver
- registry: unhide provider in dashboard
- tests: add unit coverage for wire format, native auth, and live models
- Declare deepseek-v4.1-flash and deepseek-flash as vision-capable in MODEL_CAPABILITIES
- Share installed catalogSource across route chunks via globalThis.__9rCatalogSource
- Scope catalog modality keys by provider:model to prevent cross-gateway collisions
- Upgrade catalog format to v2 with automatic rebuild of older schemas
Command Code dropped vision and ignored client effort through the router:
image blocks became "[image omitted]", HTTP image URLs were never inlined,
and reasoning_effort landed on the envelope wrapper instead of params (so the
DeepSeek family mapping remapped low -> high). The catalog also treated
deepseek/deepseek-v4.1-flash as text-only, so the vision adapter stole those
requests to another provider.
- Map OpenAI image_url / Claude image blocks (base64 or data-URI) to the
native {type:"image", image:"data:...;base64,...", mimeType} generate block.
- Add FORMATS.COMMANDCODE to TARGETS_NEED_BASE64 so remote http(s) images are
inlined by the existing SSRF-safe fetcher before translation.
- Write reasoning_effort inside params for targetFormat commandcode and pass
low|medium|high|xhigh|max through unmapped; allow it in thinkingLevels.
- Provider-scoped capabilities for commandcode/cmc: vision except the CLI
text-only denylist, thinkingFormat commandcode, so family patterns
(deepseek-v4 -> thinkingFormat deepseek, vision false) no longer win.
- Quota Tracker: whoami + billing credits/subscriptions (credits vs plan cap,
5h and weekly windows), labels from AI_PROVIDERS[].name.
The "Context window" dropdown wrote CLAUDE_CODE_MAX_CONTEXT_TOKENS, which
Claude Code ignores for any model it recognizes: its window resolver returns
the env value only when the id is unknown to the model table, so every
claude-* mapping kept the built-in 200K and the dropdown did nothing. It was
never the compaction threshold either.
- Replace it with CLAUDE_CODE_AUTO_COMPACT_WINDOW — the documented trigger
(100K–1M, clamped to the model window, env beats the autoCompactWindow
setting) — and relabel the field Auto-compact. The 1M preset becomes 700K,
which no longer collides with the marker it depends on.
- Add a "1M context" checkbox that appends the `[1m]` marker to the
ANTHROPIC_DEFAULT_*_MODEL envs. Claude Code assumes 200K unless the name
carries the marker — the resolver is a plain /\[1m\]/i test on the string,
so it applies to any id and no model lookup is involved; the user decides
which models are worth declaring as 1M.
- Toggling rewrites the model inputs immediately, and Apply writes them
verbatim, so a marker typed by hand is not stripped.
Rename maxContextTokens -> autoCompactWindow through the POST body and
RESET_ENV_KEYS so a reset clears the key actually written.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Adds the Desktop-exclusive Preview models and the Xiaomi account-session
route to the existing xiaomi-mimo provider instead of a separate
xiaomi-desktop provider, so the dashboard shows one MiMo entry rather than
three overlapping ones.
Dual auth, same pattern as kimi — API key (sk-) covers the cloud API,
Desktop/OAuth adds the account session used by the Preview models:
- registry: category oauth, authModes [oauth, apikey], oauth block, the two
mimo-x-*-preview models, and the invite signupUrl
- executor: routes Preview models to the account-service route with a Cookie
session, everything else keeps the sourceFormat-matched transport
- oauth: custom ECDH encrypted-callback flow (X25519 -> SHA256 -> AES-256-GCM)
with a loopback callback proxy, plus one-click import of the local Desktop
auth.json
- usage: weekly quota from the account session
Fixes found while merging:
- the OAuth browser flow was dead: poll-status cleared the session before the
client could POST /exchange, so every exchange returned 400
- a Claude-format client was sent to /v1/chat/completions instead of the
declared /anthropic/v1/messages transport, because buildUrl ignored
runtimeTransport
- stopXiaomiMimoProxy leaked every pending session (each holding an X25519
private key) for the process lifetime
- the OAuth exchange did not persist the Desktop passToken, so the Preview
models could never work after a browser sign-in
Removes dead code: the local engine token minting (mimoEngine, never called
on the request path), the model-catalog and usage routes, engineToken/
engineUrl plumbing, and an unread top-level usage block.
Adds tests/unit/xiaomi-mimo-{executor,oauth-session,oauth-proxy}.test.js —
the provider previously had none.
Cline (api.cline.bot) wraps non-stream chat completions in
{"success":true,"data":{...choices...}}, which both the dashboard model-test
ping and the proxy non-stream path read at top level, producing "Provider
returned no completion choices for this model" (#3644). Unwrap the envelope
before usage extraction and response translation; the error envelope
({"success":false,...}) never matches and passes through untouched.
Scoped through `transport.quirks.clineEnvelope` so only cline/clinepass opt
in — no other provider's response body is ever rewritten.
Also adds a live Cline catalog: `fetchClineRawModels()` is shared between
`resolveClineModels()` (full catalog, including free-tier ids such as
z-ai/glm-5.3-flash) and `resolveClinepassModels()` (cline-pass/* only), wired
into /v1/models, the per-provider models route, and the combo selector's
model picker with the static catalog kept as fallback.
Refreshes the dead api-airforce free models (anthropic/claude-3.7-sonnet,
moonshot/kimi-k2.6, google/gemini-2.5-flash) with the live gpt-oss-120b,
gpt-oss-20b and kimi-k2.7-code, plus passthroughModels, forceStream and a
suggested-models filter.
Cline/ClinePass requests failed with HTTP 401 ("Please make sure you are using
the latest version of Cline and re-authenticate your Cline account", #3230 /
#2333 / #3644). `getClineAccessToken()` unconditionally prefixed every token
with `workos:`, which is correct for Cline OAuth access tokens (WorkOS JWTs)
but wrong for ClinePass API keys — those are opaque strings (e.g. `clp_…`)
that the API accepts only verbatim, so the `workos:`-prefixed value was
rejected.
Only prefix tokens that look like a WorkOS JWT (`eyJ…`); API keys and other
opaque tokens pass through untouched, and an existing `workos:` prefix is
never doubled.
Also register `clinepass` in the token-refresh handlers. ClinePass shares
Cline's WorkOS auth endpoints, but without the entry expired ClinePass OAuth
tokens were never rotated, so every request kept 401ing. Finally, list
`apikey` first in the ClinePass `authModes` (ClinePass is meant to be used
with an API key from app.cline.bot/settings/api-keys), and add an "Import
from /models" button that pulls the live Cline catalog into custom models.
- Coalesce Qoder's empty finish-in-delta frame with the later choices:[] usage
frame so OpenAI and Claude clients receive prompt_tokens, completion_tokens
and cache-hit tokens (the dashboard already saw them)
- Upload inlined images through /api/v2/image/upload like qodercli, and stub
oversized non-image files instead of stuffing 30MB+ data URIs into
agent_chat_generation
- Emit response.completed -> response.usage for chat-native upstreams so
/v1/responses clients (Codex CLI, sub2api) no longer log 0/0/0
- Keep Claude message_delta.usage working when usage arrives without choices[0]
- Escalate to the smallest advertised Qoder context tier (200K/400K/1M) when
the estimated prompt no longer fits max_input_tokens
- Pass apiKey for PAT connections and list hidden enable:false catalog keys
from /v1/models
Video generation was xAI-only. Adds an adapter layer under
open-sse/handlers/videoProviders/ so /v1/videos/* can target OpenRouter or
Google Cloud credentials. A provider with no adapter keeps the exact previous
behaviour (raw body to {baseUrl}/{action}, poll {baseUrl}/{id}, verbatim
passthrough), so the xAI path is unchanged.
- openrouter: async job shape identical to xAI; creation POSTs to the /videos
collection root (no /generations suffix) and the registry HTTP-Referer /
X-Title headers are applied. Bodies pass through verbatim.
- vertex: two-way translation, since Veo does not speak the OpenAI-ish videos
shape. create -> :predictLongRunning { instances[], parameters{} }, poll ->
:fetchPredictOperation (Veo has no REST GET poll). The operation resource
name is base64url-encoded into the job id so GET /v1/videos/{id} stays a
flat path. Access tokens are minted from Service Account JSON via the
existing refreshVertexToken; raw API keys are rejected up front. The
operation response maps back onto the { id, status, video, videos } shape
clients already poll.
- videoCore: the request plan is rebuilt per attempt, so the 401 -> refresh
once -> retry once path picks up the refreshed token. Adapter validation
errors return 400 before any upstream call, so a malformed request can never
create a billable job.
- videoGeneration: GET /v1/videos/{id} resolves the provider from the pinned
x-connection-id connection, then ?provider=, then falls back to the xAI
default.
- registry: openrouter and vertex gain videoConfig, the video serviceKind and
video-kind models (Veo 3.1 / 3 / 2, Sora 2 Pro, Seedance 2.0).
The image handler's `version` header was commented out, so Codex image
requests reached chatgpt.com without the Version identity the backend
expects. Restore it and route every Codex identity header through one
constant.
The CLI version now lives on registry codex.transport as `cliVersion`
(the same pattern gemini-cli uses) and is re-exported as CODEX_CLI_VERSION,
so the registry User-Agent, the image handler and the connection test can
no longer drift apart. Bumped 0.136.0 -> 0.154.0 (current stable).
Co-Authored-By: Claude Code <noreply@anthropic.com>
Clear stale connection health state (modelLock_*, backoffLevel,
rateLimitedUntil, errorCode) whenever a connection is explicitly
marked active after successful validation or OAuth re-login.
Closes#3810
- Recognize Fable weekly windows and normalize to weekly fable (7d)
- Fall back to 100% available weekly Fable window when Anthropic payload omits it
- Forward remaining percentages and enforce canonical Claude quota order in Quota Tracker
Co-Authored-By: Claude Code <noreply@anthropic.com>
- Group Antigravity Gemini text models into single 'Gemini (Flash / Pro)' quota
- Group Claude models into single 'Claude (Sonnet / Opus)' quota
- Prune stale or legacy model keys from hidden quota visibility list
Co-Authored-By: Claude Code <noreply@anthropic.com>