Add an xAI OAuth manager using the OAuth 2.0 Device Authorization Grant
with endpoints resolved from xAI's OIDC discovery document. All HTTP goes
through the app-managed proxy client.
- Managed provider kind xai_oauth: forced openai_responses wire format,
pinned api.x.ai base URL, bearer injection gated to the xAI origin,
tokens registered for log redaction, single-auth-key takeover policy.
- Token cache cannot bypass account state: cache hits re-validate account
usability, refresh commits run under the mutation lock with a
refresh-token CAS check, and pending logins are re-checked before an
account is persisted.
- Refresh classification: 401/403 with any body and 400 with a non-JSON
body mark the account for re-auth; 429/5xx stay transient.
- Shared auth_* commands dispatch to xAI with guard types mirroring the
Copilot/Codex branches.
Bump every claude-sonnet-4-6/claude-sonnet-4.6 default pin to claude-sonnet-5 across all provider presets (claude, claudeDesktop, hermes, openclaw, opencode, universal NEWAPI_DEFAULT_MODELS) and the Claude Desktop DEFAULT_PROXY_ROUTES sonnet route id. Update route-derivation test expectations accordingly. Non-Anthropic pins (gpt/gemini/glm/sonnet-4-5) left untouched.
- Wire claude-fable-5 as a fourth tier on both proxy paths, with a
fable -> opus -> default fallback mirroring the official downgrade.
- Whitelist the fable- prefix for the Desktop 1.12603.1+ validator.
- Clarify fallbackModelHint (zh/en/ja/zh-TW): a blank tier on
third-party endpoints forwards the literal model name and 404s.
Refs #3980, #4026, #4049.
When listen_port is 0 the OS assigns the port at bind time, so the
configured value can no longer be trusted for building takeover URLs.
- server: read listener.local_addr() after bind and propagate the
actual port to the global proxy port, status, and ProxyServerInfo
- services: start the proxy before takeover when port is 0 so live
configs get the real port instead of :0, and persist the resolved
port back to the DB for DB-only URL paths; stop the pre-started
server on any takeover failure
- claude_desktop: reject an unresolved :0 port instead of emitting a
broken gateway URL
- build_proxy_urls: prefer the running server's port and error out if
the port is still 0
Add tests for takeover with an ephemeral port and the claude_desktop
:0 rejection; switch existing codex takeover tests to an ephemeral
port for isolation.
Claude Desktop appends a local [1m] marker to the model name when the
1M-context beta is active (e.g. claude-opus-4-8[1m]). The proxy route
matcher compared this raw name against clean route IDs, so every match
tier failed and the is_claude_safe_model_id guard also blocked the role
keyword fallback, surfacing as route_unknown (HTTP 400) when switching
to a 1M-capable model mid-conversation.
Strip the [1m] suffix inside map_proxy_request_model before lookup so
exact/alias/legacy/role matching all see the clean ID, while keeping the
original name in the route_unknown error for diagnostics. The upstream
request still carries the mapped real model; 1M capability is negotiated
via the anthropic-beta header, not the model-name suffix.
Fixes#3588
Claude Desktop's 3P validation only accepts claude-{sonnet,opus,haiku}-*
role IDs, so providers must map every tier. Bring the Desktop mapping flow
in line with Claude Code and fix the fallout that broke sub-agent Haiku calls.
- Proxy form: replace the dynamic route list with fixed Sonnet/Opus/Haiku
tiers; blank tiers backfill from the first filled tier (Sonnet first) on
submit and inherit its supports1m flag
- Backend: add a role-keyword fallback in map_proxy_request_model so dated
official names (e.g. claude-haiku-4-5-20251001) resolve to the right tier,
guarded by is_claude_safe_model_id so [1m]-suffixed IDs stay rejected
- Tighten is_claude_safe_model_id / isClaudeSafeRoute to reject degenerate
role IDs like "claude-sonnet-"
- Fix the seed-effect race where normalizing empty routes to three blank
tiers blocked the default-route backfill
- Sync switch hints, placeholders, and the zh/en/ja/zh-TW locales to the
three-role-ID rule
- Update zh/en/ja user manual (2.1, 2.6), calling out legacy Claude IDs
(claude-3-5-sonnet-...) as a rejected example
Tests: 282 frontend + 34 backend claude_desktop; typecheck, clippy, fmt clean.
Bump the default Opus route/model from claude-opus-4-7 to claude-opus-4-8
across provider presets (claude, claudeDesktop, hermes, openclaw, opencode,
universal), i18n locales (zh/en/ja/zh-TW), pricing seed data, and the
user-manual docs.
- Add claude-opus-4-8 pricing row ($5/$25/$0.50/$6.25); keep the 4-7 row
for historical usage stats (seeded via INSERT OR IGNORE).
- Claude Desktop proxy: accept bidirectional opus 4-7 <-> 4-8 route alias
during rollout so previously saved routes keep resolving.
- thinking_optimizer: route opus-4-8 through adaptive thinking and normalize
dotted model ids (also fixes dotted 4-6/4-7 falling back to legacy).
- usage_stats: normalize Bedrock/Vertex/aggregator opus-4-8 ids to base
pricing.
Also merge role:"system" messages into the Gemini systemInstruction in the
Anthropic->Gemini transform.
* feat: add MiMo reasoning_content support for Claude Code proxy
MiMo requires reasoning_content to be passed back in multi-turn
conversations with tool calls. Add "mimo" and "xiaomimimo" to the
preserve_reasoning_content whitelist so thinking blocks are converted
to reasoning_content when proxying Claude Code → MiMo.
Also handle redacted_thinking blocks (encrypted by Claude Code across
turns) by injecting a placeholder to prevent MiMo 400 errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: support MiMo reasoning histories
- Fix the PR CI clippy failure for redacted thinking handling.
- Preserve MiMo reasoning content on the OpenAI Chat proxy path.
- Normalize Claude Desktop Anthropic thinking history for MiMo local routes.
- Reject unsupported Claude Desktop direct model remaps.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
* stream_check: thread Result from get_auth_headers via map_err so
the workspace builds again
* forwarder: scope rectifier / budget-rectifier flags per-provider so
failover can still apply rectification on the next attempt
* forwarder: categorize before record_result; route NonRetryable and
ClientAbort through release_permit_neutral so client-side failures
don't pollute circuit breaker or DB health
* handler_context: parse Gemini model from uri.path() and strip both
?query and :action verb defensively in extract_gemini_model_from_path
* forwarder + response_processor + handlers: introduce
ActiveConnectionGuard (RAII) so active_connections decrement covers
the full streaming body lifetime, not just response headers
* claude_desktop_config: use sort_by_key to clear the clippy gate
- Replace the four flat env inputs with a Sonnet/Opus/Haiku role table.
Each row exposes ANTHROPIC_DEFAULT_*_MODEL plus a new display name
field ANTHROPIC_DEFAULT_*_MODEL_NAME, and Sonnet/Opus gain a
"Declare 1M" checkbox that toggles the [1M] suffix.
- Strip the [1M] context-capability marker before forwarding non-Copilot
requests upstream. Copilot keeps its existing [1m]->-1m normalization.
- Claude Desktop import now consumes ANTHROPIC_DEFAULT_*_MODEL_NAME as
label_override, closing the Claude Code -> Claude Desktop displayName
pipeline; add_route's merge logic is shared between hashmap branches.
- Unify the [1M] marker as ONE_M_CONTEXT_MARKER across
claude_desktop_config and proxy::model_mapper; rename the strip
helper to strip_one_m_suffix_for_upstream.
- Collapse useModelState's seven duplicated useState initializers and
the useEffect parse block into a single parseModelsFromConfig call.
- Add tests/hooks/useModelState.test.tsx and a Claude Desktop import
test covering Kimi K2 -> label_override. i18n (en/ja/zh) updated.
Adapt to Claude Desktop 1.6259.1+ fail-all validation which only
accepts claude-(sonnet|opus|haiku)-* route IDs. Branded model names
(DeepSeek, Kimi, GLM, etc.) now live in a new labelOverride field
instead of being embedded in route IDs.
- Backend auto-repairs legacy unsafe routes to the next free
sonnet/opus/haiku slot instead of erroring
- Frontend swaps the free-form route input for a role dropdown plus
menu display name field
- Add CLAUDE_DESKTOP_ROLE_ROUTE_IDS as the single source of truth
for role-to-route mapping; presets and form both consume it
- Drop the dead displayName alias on ClaudeDesktopModelRoute and the
ineffective /v1/models display_name injection (UI ignores it)
- Update i18n (en/ja/zh) and form focus test for the new fields
- Derive route keys from the upstream model name (pass-through style)
instead of fixed Claude aliases, and translate the legacy [1M] suffix
into the supports1m field at the import boundary. Three Claude aliases
mapped to the same upstream now collapse to a single route (e.g.
MiniMax-M2 across SONNET/OPUS/HAIKU env produces one
claude-MiniMax-M2 -> MiniMax-M2 row), with [1M] OR-aggregated.
- Add an import-time safety net that rebuilds claude-desktop-official
when missing, so users who deleted it can recover via the normal
import button without losing customizations on other providers.
- Hide API key and endpoint URL inputs in the official provider edit
form to mirror Claude Code's behavior and prevent user confusion.
- Reword the empty-state import button label for clarity.
inferenceModels entries now emit {name, supports1m: true} objects when
1M is enabled (plain strings otherwise), instead of appending a " [1M]"
suffix to model IDs. Route IDs and upstream model IDs are stored
verbatim; the suffix is rejected on input rather than silently stripped,
and proxy request mapping now requires an exact route_id match.
Claude Desktop's new model menu reads model IDs directly and ignores the
display_name field, so a separate displayName slot added UI noise without
any product value. Collapse the routeId / model / displayName tuple down
to routeId / model, and let the route ID carry the user-visible name
through a non-editable claude- prefix rendered next to the input.
Drop display_name from ClaudeDesktopModelRoute, ClaudeDesktopDefaultRoute,
and ResolvedModelRoute on the Rust side plus the matching TS interfaces,
stop emitting it in /v1/models responses, derive route IDs from upstream
model IDs when picked via the model dropdown, and update zh/en/ja copy to
describe the new two-field layout.
- Apply rustfmt diffs in claude_desktop_config.rs
- Allow needless_return on current_platform_paths (cfg-mirrored arms)
- Allow too_many_arguments on RequestForwarder::forward
- Replace `let mut + reassign` with struct literals in tests
(settings, backup, provider, response_processor)
- Use Path::new instead of PathBuf::from to fix cmp_owned in misc tests
- Replace 3.14 with 3.5 in config test to avoid approx_constant lint
Claude Desktop strips the [1M] suffix from model IDs when sending
requests, causing route lookup to fail with "model route is not
configured". Fall back to base-name comparison when exact match misses.
- services/proxy.rs: collapse 10 repeated `OpenCode | OpenClaw | Hermes |
ClaudeDesktop` match arms into `_` fallthroughs.
- claude_desktop_config.rs: extract a `with_rollback` closure shared by
apply_provider_to_paths and restore_official_at_paths.
- useProviderActions.ts: replace the triple-nested ternary picking the
switch-success toast message with a flat let/if/else block.
Net -36 lines. No behavior change; cargo test and pnpm typecheck pass.
Adds a new ClaudeDesktop AppType that writes Claude Desktop's third-party
inference profile under configLibrary/, sharing _meta.json with other
launchers (Ollama-compatible) so cc-switch can coexist with them.
Two switch modes:
- direct: provider already exposes claude-* / anthropic/claude-* model
ids on Anthropic Messages, Claude Desktop connects to it directly.
- proxy: cc-switch's local proxy acts as the inference gateway,
presenting only claude-* route names to Claude Desktop and mapping
them to real upstream models. Required after Anthropic restricted
Claude Desktop to claude-family ids.
Backend:
- New module claude_desktop_config with snapshot/rollback, official seed
bypass, /claude-desktop/v1/{models,messages} routes, and a single
source of truth for default proxy routes.
- Gateway token persisted in SQLite, validated on every proxied request.
- get_claude_desktop_status surfaces drift signals (stale models,
missing routes, proxy stopped, base URL mismatch, missing token).
Frontend:
- Slim ClaudeDesktopProviderForm independent from ProviderForm,
controlled by a top-level appId guard.
- ProviderList banner consumes the status query (5s polling) and
renders actionable diagnostics.
- ClaudeDesktopRouteToggle in the header to start/stop the local
gateway without touching takeover state.
- Three-locale i18n synchronised.