Update OpenCode Go model catalog to match the official Go docs.
Route Qwen 3.7 and MiniMax models through /v1/messages with
Anthropic-compatible headers instead of OpenAI-compatible chat.
Keep GLM, Kimi, DeepSeek, and MiMo on /chat/completions; use
kimi-k2.7-code because the live Go API rejects kimi-k2.7 for chat
while the docs config example uses the code model ID.
PR #1931 by nguyenha935
Co-authored-by: Cursor <cursoragent@cursor.com>
Build on the optional Headroom Token Saver from Carmelo Campos
(PR: feat: add optional Headroom token saver). Add managed start/stop
of the local headroom proxy from the dashboard, install detection,
status probing, and a simplified Token Saver UI.
- detect headroom CLI + python>=3.10, probe proxy /health
- spawn/stop proxy as a detached, pid-tracked process
- /api/headroom/{status,start,stop} routes, gated local-only in dashboardGuard
- one-click Start/Stop Headroom modal, no manual config needed
- claude<->openai shape conversion for /v1/compress via 9router translators
Thanks to Carmelo Campos (@carmelogunsroses) for the original Headroom integration.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add config-driven stripUnsupportedParams helper and use it in the default and github executors. Removes the deprecated temperature param for claude-opus-4 models (Anthropic 400) and consolidates github's scattered capability checks into one rule table.
Fixes#1748
Co-Authored-By: fjia <fjia@suntekcorps.com>
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>
CHAT_URL and opencode buildUrl base now read from PROVIDERS instead of repeating
the literal. Values identical; providers byte-for-byte + gate clean.
Co-authored-by: Cursor <cursoragent@cursor.com>
Cerebras returns 400 (wrong_api_format) and Mistral returns 422
(extra_forbidden) when the forwarded body contains client_metadata.
Remove it for these two providers before sending downstream.
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>
Accept Google Application Default Credentials (authorized_user) in the
Vertex apiKey field as an alternative to Service Account JSON, for orgs
that block SA key creation. Refreshes a Bearer token via the existing
refreshGoogleToken flow and requires a project_id (quota_project_id or
providerSpecificData.projectId). SA JSON and raw key flows unchanged.
Co-authored-by: Cursor <cursoragent@cursor.com>
Third-party Anthropic-compatible gateways that require Authorization: Bearer
(in addition to x-api-key) returned 401 missing_api_key on the forward path.
For non-official upstreams, also send Bearer <apiKey> alongside x-api-key.
Official api.anthropic.com behavior is unchanged.
Co-authored-by: Cursor <cursoragent@cursor.com>
Wire the Kiro provider into BaseExecutor baseUrls fallback so a request
advances across the three CodeWhisperer surfaces (runtime kiro.dev,
codewhisperer, q) on 429 and network/5xx errors.
- providers.js: add baseUrls[] (newest endpoint first); keep baseUrl as default
- kiro.js: replace the hand-rolled single-endpoint loop with super.execute()
delegation plus EventStream to SSE transform on success
Note: the three hosts are alternate DNS surfaces of one regional service. AWS
throttles per authenticated identity (token plus profileArn), not per hostname,
so this is edge-level failover, not extra 429 quota.
Co-authored-by: thienpv <pvtcwd@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
CommandCode upstream only speaks NDJSON streaming; the executor always
wraps it as OpenAI SSE. Force body.stream=true so non-stream client
requests still produce a parseable upstream stream (router aggregates
SSE→JSON downstream).
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>
- Add qmodel_latest to QODER_MODEL_MAP
- Expose qmodel_latest in static Qoder provider catalog (qd)
- Generalize executor comment so model set does not go stale
- Add unit coverage for the new model key + catalog
Closes#1638
Co-authored-by: Cursor <cursoragent@cursor.com>
Add mimo-v2.5-pro-claude alias routing to the Xiaomi TokenPlan Anthropic-compatible
/anthropic/v1/messages endpoint. Logic lives in a dedicated XiaomiTokenplanExecutor
(config-driven via targetFormat) instead of the shared DefaultExecutor.
Co-authored-by: Cursor <cursoragent@cursor.com>
## Features
- Add Qoder provider: device-flow OAuth, COSY signing, WAF-bypass body encoding, live model catalog, dashboard quota tracker, 11 models (#1372)
- Add new models: Claude Opus 4.8 (Claude Code), GPT 5.4 Mini (Codex)
## Fixes
- DeepSeek thinking mode: echo `reasoning_content` back on follow-up/tool-call turns so OpenCode-free and custom providers no longer 400 with "reasoning_content must be passed back" (#1543)
- Reasoning injector: match deepseek/kimi model ids case-insensitively (covers custom providers using capitalized model names)
- OpenCode suggested-models: include free models without the `-free` suffix, e.g. `big-pickle` (#1535)
## Improvements
- Codex: trim sunset models, keep gpt-5.5 / gpt-5.4 / gpt-5.3-codex family, add gpt-5.4-mini
- volcengine-ark: refresh model list (add DeepSeek-V4-Flash/Pro, drop EOL entries)
- Lower stream stall timeout 35s → 30s for faster hang detection
Adds 18 new tests covering the bugs fixed in the previous commit so they
can't silently regress:
- parseExpiry (7 tests): numeric ms-epoch input, numeric strings handled
before Date.parse so "1700000000" doesn't get year-interpreted, RFC3339
strings, expires_in:0 honored as already-expired, 30-day fallback only
when both inputs are missing/invalid
- normalizeMessages (4 tests): system hoisting, multipart text flatten,
multiple system joining, empty input
- wrapQoderSSE (6 tests): the fixed cases — trailing partial line drained
in flush(), no chunks forwarded after [DONE], embedded newlines stripped
from inner body, error envelope produces error chunk + [DONE], non-ok
responses returned unchanged
- expose parseExpiry from auth.js, expose normalizeMessages/wrapQoderSSE
via __test__ from the executor (internals only — not part of the public
API). Marked with comment so the surface is intentional.
42 tests total (24 original + 18 new). Build still 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
GitHub Copilot's /responses endpoint only serves OpenAI (gpt/codex)
models. gemini-3.1-pro-preview was failing on /chat/completions with a
"not supported" error, getting cached as a codex model, then escalated
to /responses where it 400s with "does not support Responses API".
Add GithubExecutor.supportsResponsesEndpoint() and gate both the cached
/responses route and the 400-fallback on it, so Gemini/Claude always
stay on /chat/completions and the real upstream error surfaces.
Adds tests/unit/github-responses-routing.test.js (5 tests).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
When json_schema response_format is sent to models that do not natively
support Structured Output (e.g. DeepSeek/Ollama/local LLMs via
openai-compatible-* providers), they often return empty or malformed
content.
Detect response_format.type === "json_schema", inject the schema into the
system prompt, and downgrade response_format to json_object so Structured
Output works transparently across openai-compatible providers.
Gated to provider.startsWith("openai-compatible-") so providers with
native Structured Output support are not downgraded.
Co-authored-by: Cursor <cursoragent@cursor.com>
## 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
- Introduced OllamaLocalExecutor to handle requests for the "ollama-local" provider.
- Removed the direct URL construction for "ollama-local" from BaseExecutor.
- Updated index.js to include the new OllamaLocalExecutor in the executors mapping.
- Enhanced the ProvidersPage component to support dynamic addition of OpenAI/Anthropic compatible providers.
- Refactored handleChatCore to include Caveman functionality, allowing for terse-style system prompts to reduce output token usage.
- Updated APIPageClient to manage Caveman settings, including enabling/disabling and selecting compression levels.
- Adjusted AntigravityExecutor to consolidate function declarations for compatibility with Gemini.
- Removed unnecessary console logs during translator initialization across multiple routes.
- 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.
- github.js: split thinking vs reasoning_effort stripping
- thinking (Claude-native format) still stripped for all Claude on Copilot
- reasoning_effort now passed through for Opus 4.6 and Sonnet 4.6
- still stripped for Haiku 4.5 and Opus 4.7 (rejected upstream)
- reasoning_effort "none" stripped for all models (not all support it)
- openai-to-claude.js: map reasoning_effort → thinking.budget_tokens
for direct Anthropic backend (none→skip, low→4096, medium→8192,
high→16384, xhigh→32768)
Previously reasoning_effort was stripped for ALL Claude models,
meaning Opus 4.6 via Copilot never received thinking configuration.
AI-generated commit by Claude Opus 4.6 (Anthropic)
- 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.
Add a shared resolveOllamaLocalHost() helper and wire it through the
executor, models/validate/test routes, so users can point ollama-local
at a remote Ollama instance instead of being locked to localhost:11434.
Also expose the host as an "Ollama Host URL" field in AddApiKeyModal
(empty = default localhost:11434), making the option reachable from the
dashboard without hand-editing db.json.
Co-authored-by: anuragg-saxenaa <anuragg.saxenaa@gmail.com>
Made-with: Cursor
The previous blanket strip in GithubExecutor.transformRequest removed
`thinking` AND `reasoning_effort` for every GitHub-routed model to avoid
Claude-on-Copilot 400s from OpenClaw. That regressed GPT-5 family support
(gh/gpt-5-mini honors reasoning_effort: low/medium/high).
Make supportsThinking(model) model-aware — return false only for Claude
models, so the strip fires only where the upstream actually rejects these
fields.
Benchmarks on /v1/chat/completions via GitHub Copilot:
effort=(none) → 64 reasoning_tokens, ~2.0s
effort=low → 0 reasoning_tokens, ~1.55s
effort=medium → 64 reasoning_tokens, ~1.9s
effort=high → 128 reasoning_tokens, ~2.2s
Made-with: Cursor
GitHub Copilot /chat/completions endpoint does not support the thinking
or reasoning_effort fields. OpenClaw sends thinking: { type: "enabled" }
for Claude models which causes a 400 Bad Request.
Added supportsThinking() and strip both fields in transformRequest before
sending to the upstream endpoint.
Co-authored-by: anuragg-saxenaa <anuragg.saxenaa@gmail.com>
Made-with: Cursor
Remote HTTP(S) image URLs are fetched and inlined as base64 data URIs
in a new prefetchImages() step run before super.execute(), so the body
sent to Codex contains resolved image bytes instead of URLs the backend
cannot access.
Scope is limited to the Codex executor — base executor and other
providers are untouched.
Co-authored-by: anuragg-saxenaa <anuragg.saxenaa@gmail.com>
Made-with: Cursor
- Add claudeHeaderCache.js to intercept and cache live Claude Code client headers
- Forward cached headers dynamically to api.anthropic.com via default.js
- Strip first-party identity headers (x-app, claude-code-* beta) for non-Anthropic upstreams
- Validate and sanitize tool call IDs to match Anthropic pattern (^[a-zA-Z0-9_-]+$)
- Skip thinking blocks when applying cache_control; fix max_tokens buffer (+1024)
- Strip cache_control from thinking blocks in openai-to-claude translator
- Comment out thoughtSignature in Gemini translator (kept for reference)
- Expand .gitignore to match all deploy*.sh variants
Co-authored-by: kwanLeeFrmVi <quanle96@outlook.com>
Closes#433
Made-with: Cursor
The BaseExecutor's buildUrl() and buildHeaders() methods only handled
openai-compatible-* providers but not anthropic-compatible-* providers.
This caused Anthropic-compatible synthetic providers to fail API testing
by hitting the wrong endpoint (returning documentation instead of valid
API responses) and using incorrect auth headers.
Changes:
- Added buildUrl() handling for anthropic-compatible-* providers
to append /messages path
- Added buildHeaders() handling for x-api-key header and
anthropic-version for anthropic-compatible providers
Fixes #XXX
Co-authored-by: Bitgineer <bitgineer@bitgineer.shop>
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>
When using SA JSON + Bearer token, Vertex AI requires a project-scoped URL.
The old code used the global publishers endpoint which only works with a raw API key,
causing RESOURCE_PROJECT_INVALID errors.
Changes in open-sse/executors/vertex.js buildUrl():
- SA JSON path: projects/{projectId}/locations/{location}/publishers/google/models/{model}:{action}
- Appends ?alt=sse for streaming on SA JSON path
- Location defaults to us-central1, overridable via providerSpecificData.location
- Raw API key path unchanged (global publishers + ?key= param)
Co-authored-by: anuragg-saxenaa <anuragg.saxenaa@gmail.com>
Made-with: Cursor
Add stream_options: { include_usage: true } to iFlow streaming requests
to get token usage data in the final streaming chunk. This fixes token
counts showing as 0 for iFlow streaming requests.
Only injected when streaming is enabled and body.messages exists (OpenAI
format), and the client hasn't already set stream_options.
Note: Applied only to iFlow executor instead of BaseExecutor to avoid
affecting all providers globally. This gives us more control and allows
testing with iFlow first.
Fixes#74
Co-authored-by: Ibrahim Ryan <ryan@nuevanext.com>
Made-with: Cursor
- Simplify ANTIGRAVITY_HEADERS to dynamic User-Agent only
- Use IDE_TYPE, PLUGIN_TYPE enums and getPlatformEnum() in metadata
- Update antigravity baseUrl to sandbox endpoint
- Bump User-Agent version from 1.104.0 to 1.107.0
- Remove redundant header spread in AntigravityExecutor
Made-with: Cursor
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
- Guard data: [DONE] in github.js TransformStream with stream === true
- Inject response_format as system prompt for Claude models via GitHub executor
Note: stream.js guards skipped, createSSEStream is only called for true streaming paths.
Cherry-picked and adapted from PR #286 by @rothnic
https://github.com/decolua/9router/pull/286
Made-with: Cursor
Match native GeminiCLI client fingerprint to avoid upstream rejection.
Also fix base executor to call transformRequest before buildHeaders so
subclasses can store model context for header generation.
Made-with: Cursor
- Centralize proxy management with reusable proxy pools
- Per-connection proxy binding with legacy fallback
- Add strictProxy option: fail hard instead of silently falling back to direct
- Resolve alicode-intl conflict: keep alicode-intl support + proxy support
Made-with: Cursor