- AddCustomModelModal lets users pick vision/reasoning caps when adding a model
- POST /api/models/custom whitelists caps to booleans
- aliasRepo.addCustomModel upserts — re-adding updates caps/name in place
- /api/models includes custom llm models with stored caps overriding the heuristic
- useModelCaps refetches on customModelChanged instead of trusting a stale cache
Co-Authored-By: Claude Code <noreply@anthropic.com>
Apply a 15s AbortSignal timeout in fetchWithConnectionProxy when the
caller supplies none, so provider connection tests stop hanging and
exhausting the browser socket pool. Also make matchSearch return false
for falsy provider names instead of crashing the providers page.
The 3000 ms timeout on /v1/compress was fixed, so busy or slow machines
timed out often and sent the LLM an inconsistently compressed body,
hurting prompt caching. Add a headroomTimeoutMs setting, thread it from
the chat handler down to compressWithHeadroom, expose it in the Token
Saver dashboard, and normalize invalid values back to the 3000 ms default.
Add a Zed usage handler so connected Zed accounts appear on
/dashboard/quota. Reads GET /client/users/me for plan, edit
predictions, optional hosted model requests and billing-cycle reset.
Render unlimited rows as "N used · Unlimited" instead of 0 / ∞, and
surface overdue-invoice / token-billing messages.
Each card kept its own copy of the localStorage preset logic inside
BaseUrlSelect, so an endpoint saved on one card was invisible to the
others until a reload, and a URL typed into the custom field was
forgotten the moment the card collapsed.
Move the store into cliEndpointPresets.js and publish a change event
so open cards resync live. Applying settings now remembers the
endpoint unless it matches a built-in option, and each card passes
its configured URL as currentUrl so BaseUrlSelect can preselect the
matching preset instead of always falling back to 127.0.0.1.
Deleting a preset falls back to the first real option rather than
clearing the field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex only authenticates a custom model provider from env_key,
http_headers, env_http_headers or a token command — auth.json is
read solely by the built-in openai provider. Writing OPENAI_API_KEY
there left every request unauthenticated (401 Missing API key) while
clobbering an existing ChatGPT login.
Put the key in [model_providers.9router.http_headers] instead, and
drop the auth.json write. Also move the subagent model to the
agents.default_subagent_model scalar: agents.<role> now declares a
custom role and requires a description, so the old [agents.subagent]
table was discarded with a startup warning. DELETE still clears
auth.json to repair machines configured by the previous version.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract Spark rate limit windows from the Codex usage response and expose
them as spark_session/spark_weekly quotas, reusing the existing prefix
mechanism. Map codex quota types to readable dashboard labels.
Fixes#3431
Add a "Bulk Add" flow for the grok-cli provider, mirroring the existing
codex one: paste a JSON array/object or drag-drop multiple .json files,
then create all OAuth connections in a single call.
- BulkImportGrokCliModal: flexible JSON parsing (array, single object,
{accounts:[...]}, concatenated objects) + multi-file upload
- POST /api/oauth/grok-cli/bulk-import: serial createProviderConnection,
snake_case/camelCase token fields, email backfilled from id_token or
access_token, authMethod "device_code" to match the login flow
Manual refresh (↻) sends ?force=1 so it bypasses the Claude quota cache (dedup + TTL) added in cd4003bc. Auto-refresh and multi-tab stays cached, so Anthropic's usage endpoint is no longer hammered.
Co-Authored-By: Claude <noreply@anthropic.com>
Multiple tabs/accounts/auto-refresh funneled straight to Anthropic and tripped 429. Add a 120s TTL cache keyed by access token with in-flight promise dedup, serve the last good read on soft failure, and thread a force flag through getUsageForProvider for manual refresh. Also lower the dashboard poll cadence (180s to 600s) and stable group-by-provider so connection order stops jumping.
Co-Authored-By: Claude <noreply@anthropic.com>
Toggle was checked={headroomEnabled && headroomRunning} and disabled when the proxy was down, so a downed proxy showed OFF while headroomEnabled stayed true in the DB. The engine only checks headroomEnabled, so it kept calling /v1/compress. Toggle now reflects the user setting; proxy up/down stays visible via the status chip.
Co-Authored-By: Claude <noreply@anthropic.com>
- Endpoint page auto-creates a "Default Key" when no keys exist yet,
so /v1 works out of the box without a manual dashboard step
- Show/copy key buttons stay visible instead of opacity-0 by default
- Enable vision + audioInput capacity-adapter pools by default for new
and existing users (mergeWithDefaults backward-compat)
- Fall back to oc/mimo-v2.5-free when an enabled pool has no models
configured, both in the backend resolver and the combos UI (auto
refill on removing the last model from a pool)
- Hide PDF/Video from the Vision Adapter UI (PDF never implemented,
Video lacks translator support) while keeping the settings shape
- Exclude combos from the model picker when opened from the Vision
Adapter section
- mimo-v2.5 registry entry now declares audioInput/videoInput
- Simplify combo strategy and Vision Adapter descriptions
Adds mimo-v2.5-tts as a Media Provider TTS through the existing
OpenAI-compatible chat-completions endpoint. Voice is selected via the
top-level audio.voice field, and an optional style/language hint is
threaded through tts.js -> ttsCore.js -> the new adapter.
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".
getAllAvailableModels() only consulted the static PROVIDER_MODELS catalog,
which has no entry for dynamically-registered compatible providers
(id like openai-compatible-chat-uuid). Fall back to the connection's
own defaultModel/customModels/placeholder, mirroring ModelSelectModal.js.
Free-tier and apikey providers (e.g. cloudflare-ai, byteplus, ollama, vertex) whose registry entry omits authModes were treated as oauth-only, hiding their apikey connections on the providers grid card.
Ollama: replace informational stub with real quota tracker hitting ollama.com/api/usage (session 5h + weekly 7d, 0..1 ratio) and /api/me plan label; bind handler to apiKey + add features.usageApikey so apikey connections work.
Token refresh: add backgroundTokenRefresh scheduler that refreshes OAuth connections within max(provider lead, 30min) of expiry, independent of inbound traffic (10s after boot, then every 5min, unref'd timers, DISABLE_BACKGROUND_TOKEN_REFRESH kill-switch, fail-open per tick/connection). Registered from custom-server.js (listening) and initializeApp.js. checkAndRefreshToken gains opt-in {force} for the scheduler; request path unchanged.
Free-tier cards (e.g. kimchi, oauth-only) hardcoded "apikey" for stats and
toggle, so oauth connections were invisible on /dashboard/providers despite
showing on the detail page. Use dualAuthTypes per provider instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the fixed-table quota layout with flex rows that shrink cleanly,
keep the hidden-quota chip row from overflowing, and use thin mac-like
scrollbars app-wide.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a context-window selector on ClaudeToolCard that writes
CLAUDE_CODE_MAX_CONTEXT_TOKENS into settings.json (nudged 2K under the
labeled cap), and clear it on reset/default.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wire /v1/usages for Kimi (OAuth + API key) and balance API for DeepSeek,
flag both providers with usage/usageApikey, and normalize their quotas
in the dashboard ProviderLimits parser.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Use w-full instead of min-w-0/flex-1 + overflow-x-auto so the hidden
quota chips wrap cleanly instead of stretching the row.
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.
Add separate model selectors for Grok Build main, general-purpose,
explore, and plan agents. Each override gets an independent 9Router
custom-model slot and context_window derived from 9Router model
capabilities. Preserve and restore pre-existing config on reset.
Share one /api/models fetch via useModelCaps cache, mount ModelSelectModal
only when open, stop double fetchModelAliases on CLI tool cards, and resolve
provider icons through a session 404 cache with missing PNGs + loading=lazy.
Also include Claude Exa MCP toggle (claude-settings + ClaudeToolCard) that
was already in the working tree.
Co-authored-by: Cursor <cursoragent@cursor.com>
Bulk-add named auto-generated keys by paste-line index, blind to existing
connection names. The backend upserts apikey connections by exact name
(connectionsRepo), so a colliding generated name silently replaced an
existing key instead of inserting a new one.
Add a collision-aware planner (src/shared/utils/bulkAdd.js) that gap-fills
the smallest free "<base> <n>" against both existing connection names and
names assigned earlier in the same batch, so a generated name is never
reused and the backend always inserts. Applies to auto-named lines, custom
name|apiKey lines, and Cloudflare name|apiKey|accountId lines.
Wire the planner into AddApiKeyModal and pass existing connection names
from the provider detail page. Add unit tests covering gap-fill, custom
names, Cloudflare 3-part format, and robustness.
Add Grok Build to Dashboard → CLI Tools. Apply writes a [model.9router]
custom model to ~/.grok/config.toml and sets [models].default, routing
the xAI Grok TUI through 9Router. Reset removes the slot and restores
the previous default.
Add pxpipe as an experimental fifth Token Saver: Claude-format request
bodies above a configurable size threshold are rendered as dense PNGs
via the pxpipe-proxy library API (transformAnthropicMessages) before
dispatch, cutting estimated input tokens by ~35-60% on token-dense
contexts. Integration follows the Headroom pattern: applied to the final
body in chatCore just before dispatch, fail-open on any error/timeout.
Managed npm install into DATA_DIR/pxpipe, dynamic loader with per-version
cache-bust, JSONL event log with rotation, /api/pxpipe/* endpoints, Token
Saver card (marked experimental) + /dashboard/pxpipe page, and per-request
Activated/Skipped annotation in Request Details. Disabled by default.
- add Headroom extras status + install endpoints
- show Headroom version + code/ml extras in Token Saver UI
- fix Windows interpreter selection to read from env with headroom-ai
Bulk import now parses name|apiKey|accountId lines for Cloudflare AI and
forwards accountId via providerSpecificData, with a provider-specific
placeholder and format hint.
Expose max in the Codex thinking dropdown for gpt-5.6-sol only (maps to
xhigh on wire; live probe rejected ultra). Include custom/kilo models
when computing provider thinking options so manually added gpt-5.6-sol
contributes its max level.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add a 9Router-side proxy so the Headroom dashboard and its data
endpoints (/stats, /health, /stats-history, /transformations/feed)
stay same-origin when opened remotely through the 9Router app, and
add an "Open Headroom Dashboard" link in the Token Saver modal.
Gate /api/headroom/proxy as LOCAL_ONLY (loopback + CLI token) to
match start/stop, and strip cookie/authorization when the Headroom
target is non-loopback to avoid leaking viewer credentials.
Co-authored-by: Cursor <cursoragent@cursor.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 read-only GET to inspect per-credit reset inventory (status, granted,
expiry, remaining) with a Quota Tracker modal. DRY the route via shared
connection/refresh helpers; keep existing consume POST unchanged.
Co-authored-by: Cursor <cursoragent@cursor.com>
CodeBuddy CN bonus packs ("Bonus Pack N") are one-shot credits whose
CycleEndTime equals DeductionEndTime — they expire for good and never
replenish. The dashboard rendered their resetAt as "Reset in Xd",
implying a monthly refill.
Tag bonus packs recurring:false (refill packs recurring:true) in the
usage handler, forward the flag through parseQuotaData, and word the
quota table / progress bar as "Expires in" / "Expires at" for
one-shot packs.