The native menu now lists Claude Code, Claude Desktop, Codex, Gemini CLI
and Grok Build (hidden apps are skipped; additive apps stay out). Each
app row reads "name · mode · provider · quota" and opens a submenu that
states the mode and only offers providers that switching can actually
use: providers that need routing open the app page with the same
needs-routing dialog, official subscriptions are disabled under routing,
and the menu is read-only while failover is on instead of silently
turning failover off. Projects move into the app submenus.
Quota text matches the cards (remaining, "running low" under 10%, no
emoji). A problems section appears only when something needs action:
the routing service is down while apps use it, an app fell back to
direct at startup, or a tray switch failed.
On Windows a left click opens the window and a right click shows the
menu; macOS and Linux keep the menu on left click.
Pages (phase 4):
- Usage: v7 layout (filter row, four metrics with more on demand,
switchable trend, request log / provider / model / pricing tabs, data
sources sheet). Latency becomes output speed: output / (duration -
first token), shown only for requests with at least 100 output tokens;
provider totals sum output and generation time instead of averaging.
- Sessions: single-column list grouped by project or time, drilling into
a reader with resume, find, question jump and copy actions.
- MCP and Skills share one app matrix (neutral checks, retry on failed
writes, column bulk actions with undo); MCP add/edit moves into a
drawer with form/JSON modes; Skills merges installed and discover and
always shows a Pi column.
- Prompts: one page for eight apps including Hermes SOUL.md, enable and
disable as buttons, delete with undo instead of a confirm, copy to
other apps, import existing files; app pages show the active prompt.
- Add provider is two steps: pick a preset (always-on search including
Chinese names and domains, categories inferred for display, sorted by
name with no promoted entries), then fill the form. Edit pages name
the provider and warn when it is the one in use.
- App page menu links to that app's usage.
Visual pass (phase 6):
- Secondary pages cover only the content area so the sidebar stays.
- Cmd/Ctrl+K jumps to apps, pages and settings sections.
- Legacy button variants, primary color, dialogs, toasts and sheets
follow v7 tokens; hard-coded palette classes and 10px text across
the remaining components are replaced with tokens.
Quota on provider cards now reads as remaining ("5-hour 69% left", "Balance 82.10") in the secondary text color, keeping the two tiers with the least left. A tier under 10% turns warning, used up / expired / failed turns danger, and a failed query shows the reason on a second gray line. Reset times and the last update move into the hover text; clicking the quota checks again. Expanded views use one bar style where a shorter bar means less left. Green is gone from quota everywhere.
Switch-style apps (Claude Code, Codex, Gemini CLI, Grok Build) get a mode
row (Direct / Routing / Stack). Tabs only change what is viewed; taking
effect goes through buttons that state the consequence, with a confirm
dialog for entering a proxy mode and a one-step, undoable return to
direct. Provider cards are drawn from a per-mode presentation model
(status, buttons, chips, queue order, disabled reasons) instead of each
card inspecting proxy state, and additive apps get Added / Available
sections.
Backend: entering a mode can pick the route target in the same step,
`get_app_mode` reports the effective mode per app, and startup attach
failures are kept for the UI to surface with a retry.
Settings gains a Local Routing section (service, listen address, apps
using routing, failover tuning) replacing the old proxy tab and toggles.
Claude Desktop gets an access bar for model mapping and service state.
Replace the header toolbar and icon-only app switcher with the v7 shell:
- Sidebar (200px, collapses to a 72px icon rail under 960px, Cmd/Ctrl+\
toggles and remembers the choice): the app list with mode tags and
"needs attention" dots, then Usage (today's cost), Accounts (re-login
dot), MCP, Skills, Prompts and Sessions pinned to the bottom, and an
Apps / Settings footer. Settings swaps the sidebar for its own
directory.
- Every screen gets a 52px page header; Linux in-app window controls
sit at its far right. macOS traffic lights move down to centre in the
44px sidebar top bar.
- Usage, Accounts, MCP, Skills, Prompts (with an app picker) and
Sessions become global pages instead of settings tabs or per-app
toolbar buttons. OpenClaw and Hermes switch to their own pages with a
segmented control; OpenClaw's env, tools and agents merge into one
Config page.
- New Apps page: per-app install / upgrade with path and install
source, batch upgrade, conflict details, manual commands, and the
"show in sidebar" switch that used to live in settings. Tool
management moves out of About into a shared hook; a read-only
list_tool_installations command feeds the page so it does not share
calls with the upgrade preflight.
- Settings regroup into General, App config, Local routing, Network,
Data and About, using row/card layout. Path settings save per
section; switches still save immediately. Skills storage and sync
move to a sheet on the Skills page.
- New opt-in "check app updates on startup" setting (off by default):
when on, a background check puts a neutral dot on Apps.
- Switch adopts the v7 track and action colour; env-conflict banner
joins the page flow instead of overlaying the header.
The old route / Stack / failover toggles stay in the provider page
header until the mode tabs land.
Foundation for the v7 redesign; no layout changes yet.
- Add the v7 tokens (surfaces, text, action, inverse, direct / route / stack,
status, chart, shadows) to index.css and expose them in Tailwind together
with v7 radius, shadow and type-role scales; teach tailwind-merge the new
names so text-body no longer cancels text-fg-1.
- Align the shadcn HSL tokens (background, card, muted, border, ring) with the
v7 palette and use the neutral ring for focus outlines.
- Rename light / dark to Light / Dark Side of the Moon with System first, and
pause transitions while switching so colors don't fade in piece by piece.
- Add v7 button variants (solid, neutral, quiet, direct, route, stack) and
sizes, plus HelpTip / DisabledReason (the "?" card), SegmentedControl,
Notice / NoticeSlot and Sheet.
- Respect the OS reduced-motion setting for framer-motion.
Claude Desktop model-mapping providers send every request through the local
routing service, but the service lifecycle only counted the four routing apps:
when Claude Code left routing, stop_server_if_unused stopped the service Desktop
was still using, and nothing started it on launch.
- stop_server_if_unused now also treats a current Desktop model-mapping
provider as a user of the service.
- ensure_desktop_mapping_service starts the service when that provider is in
use; it runs on startup, after switching a Desktop provider and after
applying a project that covers Claude Desktop.
- The frontend no longer warns that routing must be started when switching to
a Desktop model-mapping provider, since the backend starts it; drop the now
unused isProxyRunning hook argument and proxyReasonClaudeDesktop string.
- Always show the usage button in the header and add an Accounts button; the
usage dashboard has data from session logs even without routing.
- Cmd+, now always opens the General settings tab.
- Remove the dead agents and universal views, which had no entry point but
could still be restored from localStorage.
- Restore thin always-visible scrollbars; keep a .no-scrollbar opt-out.
- Keep the provider card's main action visible; only the secondary icon
buttons still reveal on hover.
- Show the needs-routing badge for every app providerNeedsRouting covers,
which adds the missing Grok Build badge.
- Replace leftover "proxy takeover" wording with Routing mode and refresh the
outdated Accounts description in all four locales.
Opening any Hermes session in the Session Manager failed with "no such
column: created_at": the messages table stores `timestamp` (REAL epoch
seconds). Rework the SQLite reader to follow Hermes' own display
projection:
- Select columns by what the store actually has, so older state.db files
without active/compacted/display_metadata still load.
- Show live rows plus compaction-archived rows (active = 1 OR
compacted = 1); rewound/regenerated rows stay hidden. Collapse the
copies compression re-inserts with Hermes' display identity (role,
content, timestamp, tool_call_id, tool_calls, tool_name), keeping the
first position and the most live copy, and skip model_only rows.
- Order by id: timestamps regress across clock skew and compaction.
- Decode "\0json:" structured content (text + image parts) to text.
- Render assistant tool calls as [Tool: name], matching Codex.
The session list now uses the first user message as summary (and as the
title when Hermes has none) and prefers last_activity_at for recency. The
provider filter dropdown gains a Hermes entry.
Supersedes #6694.
Co-authored-by: h6rime <127593600+h6rime@users.noreply.github.com>
Projecting an app's MCP servers stopped at the first server that could
not be written or removed, so one bad entry left every server after it
out of sync: newly enabled ones were never written and disabled ones
stayed in the client's config.
Sync each server independently, log the failures, and report them
together at the end, grouping server ids that failed for the same
reason.
Co-authored-by: AnxForever <130662349+AnxForever@users.noreply.github.com>
A spec without `type` was always written as stdio. The panel's JSON mode
and deeplinks both accept a bare `{"url": …}`, so such a server ended up
as `command = ""` plus `url`. Codex rejects that entry with "url is not
supported for stdio" and refuses to load the whole config.toml, taking
every other MCP server down with it.
Infer the transport on the write side the same way the import side does:
no `type`, or an explicit stdio with no command, is HTTP when a `url` is
present.
Codex applies the same all-or-nothing check to every field that belongs
to the other transport, so write only the chosen transport's fields.
Stdio entries no longer carry `url`, `http_headers`, `env_http_headers`,
`http_headers_helper`, `bearer_token_env_var` or `oauth_resource`; url
entries no longer carry `command`, `args`, `env`, `env_vars` or `cwd`.
Each field is still written where it is valid. The lists were checked
field by field against Codex 0.160.
Refs #6719
Co-authored-by: ChrisPan <39005916+szupzj18@users.noreply.github.com>
Drop the ETok.ai entry from the four-language README sponsor table and
remove its presets from all nine apps along with the partnerPromotion.etok
copy. The etok icon stays in the icon index so existing user providers
that reference it keep rendering.
first_token_ms was stamped when the usage collector accepted its first
event, so the usage filter decided what "first token" meant. When an
upstream's opening event carried no usage key (Responses passthrough
without usage on response.created, Chat passthrough with usage only on
the final chunk), the first accepted event was the terminal one, the
generation window shrank to a few milliseconds and TPS jumped into the
thousands.
Mark the first output on every SSE data line instead, independent of the
usage filter, skipping only metadata events (response.created,
response.in_progress, ping, keepalive), the same semantic TTFT rule
Sub2API uses.
On the frontend, hide TPS when the generation window is under 100ms:
relays that buffer and flush at once, or short replies landing in one
packet, measure a transport burst rather than generation speed. This
also cleans up rows recorded before the backend fix.
CodexKeychainLogin::Found and ::Missing are only constructed by the macOS keychain reader, so non-macOS lib builds never construct them and clippy -D warnings fails on Linux and Windows. Allow dead_code on those two variants outside macOS only, keeping the lint active on macOS.
Stack ids are only resolved while the app is in proxy mode with Stack
mode on. In routing mode the list and keys are kept but not published,
so a client still holding an old Stack id gets an error instead of being
forwarded to a list member. The Stack bit is left as is when leaving
proxy mode, so direct mode is gated too.
Rename the Stack mode to 叠加 (zh-TW 疊加) in the Chinese UI so it reads
alongside 路由 and 映射. Tags and tabs say 叠加; sentences say 叠加模式
or 叠加的模型.
- zh / zh-TW locale strings, the form defaultValues and the matching
test assertions
- the Chinese half of user-facing backend errors in mode/controller.rs,
mode/stack.rs and codex_direct.rs; the English halves are unchanged
English and Japanese keep "Stack". Code identifiers, persisted keys
(enableStackMode, the live-state stack mode), commands and log text are
unchanged.
Once `model_catalog_json` is set, Codex only knows the models in that file.
Third-party GPT rows were built from the neutral native template (a one-line
"You are Codex" prompt, `model_messages` stripped) or, on ProxyChat, from a
gpt-5.5 clone regardless of the model name, so GPT models lost their own
harness prompt, freeform apply_patch and reasoning levels.
Rows now look up Codex's bundled official list with Codex's own matching rule
(longest prefix, then one simple namespace segment, case-sensitive) and mirror
the matched entry verbatim; row overrides do not apply, and a narrower relay
window goes in `model_context_window` in config.toml. Fields owned by the
official account or backend are reset: Responses Lite off, speed tiers,
upgrade and NUX cleared, always listed, and ProxyChat keeps
`supports_image_detail_original` off. Anthropic rows and the DeepSeek vendor
catalog are unchanged. Stack members get the same entries under their
prefixed ids.
The reverse parser only collapses a row to its bare model name when it
regenerates identically, so older ProxyChat gpt-5.5 clones keep the user's
display name and window.
A native ChatGPT login has no managed-account marker, which is the normal
state, but the ownership check logged "Failed to read Codex managed OAuth
auth marker ... file does not exist" on every switch and token lookup.
Check that the marker exists first, as the other marker readers in the
same file do. A marker that exists but cannot be read still warns, and
the result is unchanged: no marker never establishes ownership.
Opening the Codex add dialog projects the custom template onto the live
config to show what config.toml will look like. The template has an empty
API key and `requires_openai_auth = true`, and the projection ran the same
check a switch does, so the dialog failed with "this Codex config has no
usable API key" before the user could type one. Third-party presets with
the same defaults failed the same way. The editor base then stayed empty,
so saving fell back to storing the row only and global edits made in the
dialog never reached the live config.
The editor now projects such a draft as if a key were present, using an
in-memory placeholder that is removed before display and before the row
is stored. Only the missing-key error is retried; other errors surface as
before. A keyless row can be saved, as the non-editor add path already
allowed once a current provider exists. Every write to the live config
still projects the real row and refuses it: switching or routing to it,
editing the current provider in direct mode, and adding it as the first
provider, with the row rolled back.
Codex groups sessions by the model_provider id in config.toml: the CLI's
resume picker and `resume --last` only list sessions recorded under the
current id, and resuming a thread needs its recorded id to still resolve.
With the official provider as the proxy (or Stack) route, CC Switch wrote
`model_provider = "cc-switch-official"`, so those sessions went into a
third bucket, separate from both the official (`openai`) and third-party
(`custom`) history, and the table was deleted as soon as the route moved
away.
The official route now uses the same id as direct official use:
- Unified session history off: no model_provider; a top-level
`openai_base_url` points the built-in openai provider at the local proxy,
so sessions stay under `openai`. A leftover custom table is kept in its
dormant form, as before. The key is updated in place, not moved.
- Unified session history on: the custom official mirror table, pointed at
the proxy with websockets off.
The two shapes produce different contracts, so toggling the setting while
routed to the official provider rewrites the client. `cc-switch-official`
is no longer written; an existing table is only removed.
`openai_base_url` is written only for this route and only with the local
proxy address. The official route already sent the client's own login to
the proxy, so nothing new is exposed. Direct third-party routes still never
use it.
Supporting changes:
- The proxy answers GET on the Responses paths with 426. The built-in
openai provider tries a WebSocket first and falls back to HTTP for the
session right away on 426; other errors used up its retries first.
- The built-in provider's `version` header is stripped from requests sent
to third parties, along with the other Codex client headers.
- The new shapes carry no placeholder key, so `live_has_proxy_placeholder`
also recognizes a live config that routes the official provider to the
configured proxy address. The port must match; with port 0 only the host
and path are checked. Other local gateways that point openai_base_url at
a different port are not treated as leftover takeover state.
Claude Code fetches the model list from the proxy once at startup, so a
change to the published Stack models only shows up after a restart.
Adding or removing a member already said so in its saved toast, but
editing a member's models or deleting a member did not.
In Claude Code Stack mode the provider list now compares the published
model ids each time the Stack list is refetched and shows "Stack models
changed. Restart Claude Code to apply" when the set differs. The refetch
right after adding or removing a member is skipped, since that toast
already asked for a restart; the skip is used up by that refetch even
when nothing changed, so it cannot swallow a later edit. Reordering
models or editing fields that are not published (keys, addresses) does
not trigger it. Codex is left to its process-based banner.
Codex's app-server reads the model catalog once at startup and keeps it,
while config.toml is re-read per request. Since 0.159 the codex CLI talks
to a managed daemon that only restarts on upgrade, so reopening codex after
a Stack change still shows the old model list; the desktop app and editor
extensions have the same problem with their own app-servers.
In Codex Stack mode the provider list now shows a banner when a running
Codex client started before the current catalog was written:
- CLI daemon: a button restarts it through Codex's own
`codex app-server daemon restart`, after a confirmation that running
tasks will be interrupted. Nothing runs when the daemon is not up.
- Desktop app / editor extensions: the banner explains how to fully quit
and reopen them; CC Switch does not restart them.
Staleness is judged against a device-local history of catalog generations
(codex-catalog-history.json) rather than file times: an unchanged catalog
is not rewritten, and quitting CC Switch removes the catalog pointer and
restores it on the next start, so a daemon that survived a CC Switch
restart still has the current catalog. Every Codex write and startup
records the catalog a newly started Codex would read; processes that
started before the oldest recorded generation count as stale.
Processes are read with ps on macOS and Linux only; Windows shows no
banner for now.
In Stack mode every added provider now stays highlighted like an added
provider in additive apps. The card and the Stack badge use violet,
matching the Stack icon in the header, so Stack mode is easy to tell apart
from routing and failover, which stay green.
In Stack mode the default provider no longer hides behind the routing
aliases. Claude Code's four role aliases (startup default, background
tasks, subagent aliases) all point at the first model of the default
provider's list, as its Stack id. Haiku drops the [1M] marker like the
routing contract does, and CLAUDE_CODE_SUBAGENT_MODEL is not written so
subagents follow the model picked in /model. The default provider's
whole list is published like any other member; Codex keeps publishing
the default's catalog as the route's own rows.
Claude providers get an explicit Stack model list (meta.stackModels).
Unset means the list follows the model mapping (ANTHROPIC_MODEL first,
then the tiers); an empty list is a deliberate choice and publishes
nothing.
In Stack mode the provider form opens a simplified layout: the model
list with a star for the default, plus the fields needed to reach the
provider, with a link to the full form. Both layouts share one form
state, so the default is synced when the user acts, not derived from
the layout at save time:
- Claude: when the first model of the list changes, ANTHROPIC_MODEL
follows it; an unchanged first model leaves ANTHROPIC_MODEL alone.
- Codex: the star follows config.toml's model. Starring, renaming or
removing the default row updates model on the spot; save only fills
model from the first row when it is empty. A default outside the list
is flagged with an action to add it.
The mode that publishes several providers' models in one client model list
is now called Stack in both the UI and the code. The feature is unreleased,
so persisted names change without aliases: the live-state entry, the pending
op, the settings flag and the two commands now use "stack".
The proxy's own "attached" state (attached_route, mode.attached, op::ATTACH)
keeps its name; it means the proxy contract is in place, not this mode.
When the official ChatGPT route is the default, Codex treats every model
under its "OpenAI" provider table as capable of remote compaction (V2): a
normal /responses stream ending in a compaction_trigger, which must return
exactly one compaction item or Codex fails the turn. Attached third-party
models could not do that, and history mixed across providers carried state
that the other side rejects.
Compaction bridge:
- Chat and Anthropic conversions and native Responses passthrough turn the
trigger into a tool-free summary turn and return the summary as a single
compaction item wrapped as ccswitch-compaction-v1.
- Only a normal stop counts as a finished summary. Truncated or filtered
turns report response.incomplete; empty summaries report response.failed.
- On replay, CC Switch summaries become a user message for every upstream.
Foreign blobs become a readable note for Chat/Anthropic conversions and go
to native Responses upstreams untouched, since they may have issued them.
Official passthrough: before the ChatGPT backend, drop reasoning items that
carry CC Switch envelopes or no encrypted content, and remove item ids that
do not match their type. Pure official history stays byte-identical.
Opaque state rectifier: when a native Responses upstream rejects encrypted
state it cannot verify (invalid_encrypted_content, "could not be verified",
"not issued to this caller", store:false lookups, xAI blob errors), strip
reasoning items and encrypted parts, plus compaction where warranted, and
retry the same provider once. A second failure goes through the normal
error classification. Governed by the rectifier master switch.
Also strip x-oai-*, x-openai-* and x-responsesapi-* client fingerprint
headers from third-party requests, and drop stale entity headers when a
streamed SSE body is rewritten (this also fixes the xAI namespace restore
stream, which kept the upstream Content-Length).
Attached models were a per-card toggle inside routing mode. Turn them
into an attached mode that sits beside routing: settings show either
the routing switch or the attached-mode switch on the main page, for
Claude Code and Codex only (Gemini CLI and Grok Build keep routing).
Turning one on first sends Claude Code and Codex apps that are in the
other mode back to direct.
In attached mode the provider list works like the additive apps: Add
and Remove put a provider's models into the client's model picker, and
"Set as default" picks the provider for unprefixed requests. The
default is always on the list and cannot be removed; a new default set
from the card, the tray or a project joins it. Official accounts can
only be the default. There is no failover: default requests go to the
default only, enabling failover is refused under the switch lock, and a
late failover result from a request sent before entering attached mode
is dropped. The queue is kept for routing mode.
Internally attached mode is proxy mode plus PoolState.enabled, written
on every proxy entry and committed with the client files. With the bit
off nothing is published and the list is kept for next time.
Also fix issues found reviewing the first two commits:
- strip ChatGPT fingerprint headers only from Codex attached requests
- recompute the Codex catalog when an attached member's row is edited
- skip a member whose row cannot be projected instead of failing every
write, and refuse to attach such rows
- keep the gateway discovery switch as an exclusive field so a user's
global value survives direct switches
- tell the user when the route or config.toml owns the model catalog
- read the Codex keychain entry by account key with a three-state
result, outside the app lock and off the async runtime
- retry the Codex rewrite on the next check when a refreshed official
list could not be written
Merge attached providers' models into the Codex catalog as
ccs-<key>/<model> rows after the route's own rows. Each row keeps its
provider's tool profile, a fixed comp_hash and its own window; the
global window keys move into the rows while models are attached.
Attached requests keep their model, never carry the ChatGPT identity
headers to a third party, and drop hosted web_search for native
Responses gateways that reject it.
With the official account as the route, the catalog lists every
official model: fetched with the login Codex will use after the
operation, cached per workspace, user and Codex version, refreshed in
the background, with Codex's bundled list as the fallback. Logins CC
Switch does not manage are only read, never refreshed.
Let proxy mode publish extra providers' models to Claude Code's /model
picker as ccs-claude-<key>--<model> ids. A request that selects one goes
straight to that provider and never touches routing state: no breaker
permit or result, no status counters, no in-use update, no failover
switch, no model mapping, single-provider timeouts.
The list and a permanent key registry live in live-state.json and are
committed with the client file in one operation; a failed change reports
whether it was dropped or will be rolled forward. Unresolvable prefixed
ids are rejected instead of falling back to the route. /v1/models serves
the Anthropic-shaped list for gateway model discovery.
Saving any disabled prompt emptied the app's prompt file whenever no prompt
was enabled, so adding or importing a prompt wiped a hand-written AGENTS.md
or Hermes SOUL.md without a backup. A deeplink import also lost the file
before enable_prompt could back it up.
Follow the MiniMax Code branch: read the previous row before saving and
clear the file only when this prompt was enabled, is now disabled, and no
other prompt is enabled.
A vendor that also hosts other vendors' models (NVIDIA lists
minimaxai/minimax-m2.7 without a canonical id) was counted as a second
vendor next to the entry relays point at, so the fallback gave up and
models like MiniMax-M2.7, gpt-5.4 and kimi-k2.6 got no metadata through
unrecognized relays. Use the entries relays point at first and only fall
back to a vendor's own entry when nothing points anywhere.
- Export modelsDevQueryOptions so the metadata fill hook and both usage
pricing dialogs use one query definition, including the one-hour
gcTime the fill hook needs for its prefetch-only cache.
- Add useCommittableRef for the "latest value + synchronous commit"
pattern repeated in the Codex, Hermes, OpenClaw, and OpenCode forms.
- Drop an unused field from the models.dev index.
Without a thinking map, Pi shows off..high for a custom model and hides
xhigh and max, even when the upstream supports them. When a fetched
model is picked and has no map yet, fill one:
- Use the reviewed map of the Pi preset at the same endpoint, but only
under the same API and when the compat it relies on does not conflict
with what the user wrote. Missing compat keys are added to the model,
and for openai-completions the thinking format and reasoning_effort
support that Pi detects must match the preset's.
- Otherwise, when Pi sends levels verbatim as reasoning_effort
(openai-responses, or openai-completions detected as the OpenAI
thinking format), derive the map from models.dev effort levels using
Pi's own getEffortThinkingLevelMap rule.
A model with its own baseUrl is now looked up by that address. Update
the Pi design docs accordingly.
Apply the same lookup to Pi custom providers: picking a fetched model
fills contextWindow, maxTokens, reasoning, and image input when they are
still empty. thinkingLevelMap is left untouched.
Update the Pi design docs, which previously forbade inferring model
capabilities. That rule came from an earlier implementation that voted
across providers by model name and produced wrong values; the docs now
keep the lesson as constraints (match the provider by endpoint first,
fill only empty fields, never generate a thinking map).
A provider's /models endpoint only returns model IDs, so a model picked
from the fetched list left its context window, output limit, reasoning
levels, and modalities empty, while the same model in a preset had them.
When a model is picked in the Codex, OpenClaw, Hermes, OpenCode, or
MiniMax Code form, look up its metadata field by field:
1. the preset model with the same endpoint (hand-verified values);
2. the models.dev provider whose API endpoint matches;
3. the vendor's own models.dev entry, without its price.
Endpoints match only when host and path are equal after dropping a
trailing version segment or request path, so plans that share a host
(z.ai standard vs Coding Plan, OpenCode Zen vs Go) never borrow each
other's values. Models are never matched by name across providers: the
same model ID often has very different context windows per deployment.
Only empty fields are filled, and reasoning or image input is only ever
turned on. models.dev is prefetched once the model list is fetched and
shared with the usage pricing import; when it arrives late the row is
filled again, located by its stable row key. A single toast asks the
user to review what was filled.
Move the models.dev download, response types, and model ID normalization
out of the pricing module into src/lib/modelsDev.ts so other features can
reuse the same data. The query key becomes ["models-dev"] because the
cache now holds the raw catalog rather than pricing-only data.
Switch the Kimi Open Platform invite links to the new campaign trackers:
mainland platform.kimi.com links now use track-7cf2b91dcde043eda6ef9a95951a042c
and global platform.kimi.ai links use track-674ed6e2af924a5682a87421f7cf753a.
In the four READMEs this replaces the previous track IDs in the sponsor banner
and API call to action. The Kimi and Kimi Global presets for Claude Code,
Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi now carry the same
tracked links in websiteUrl, so sign-ups from inside the app are attributed as
well. The aff=cc-switch tag is kept everywhere; the console API-key deep links
and the Kimi Code subscription links are unchanged.
Success, provider failure, neutral failure and terminal failure each
had copies of the breaker/status/in-use/failover-switch updates inside
forward_with_retry_inner. Move them into note_attempt, finish_success,
record_provider_failure, finish_neutral_failure and
record_failed_request so the loop no longer writes routing state
directly.
Add characterization tests that drive forward_with_retry against a
scripted upstream and assert ProxyStatus, current_providers and
circuit breaker counts on every exit path. No behavior change.
MiniMax Code shows custom_provider:<key>/<model> in its model picker, so the
UUID CC Switch used as the key leaked into the UI. New MCode providers now
take a user-entered key, prefilled from the preset, like OpenCode. Existing
providers keep their UUID and the key is locked when editing, since MCode's
default-model references embed it.
- Duplicating an MCode provider generates an unused <key>-copy key.
- Adding rejects a key already used by a database row or any live
custom_provider node, including MCode's own account providers. The check
and save run under the MCode switch lock, and the live write re-checks the
key while holding config.yaml.lock so it never replaces a node MCode
created in the meantime.
The reachability probe treats any HTTP response as reachable, so its timeout, retry and slow-threshold knobs barely change the outcome. Remove the Advanced settings panel and its get/save commands; the probe now always uses the built-in defaults (8s timeout, 1 retry, 6000ms slow threshold).
Also remove dead code: the batch check command had no frontend caller, and stream_check_logs was written on every check but never read. Stop writing it and drop the always-empty model_used/error_category result fields. The table, its migrations and the periodic cleanup stay so existing rows age out.
- Resolve three-way editor conflicts in one place (ConflictPolicy::resolve)
and share JsonChange between the Claude and Gemini editors
- Move the duplicated Codex/Grok editor helpers into editor_toml and chain
TOML steps through TomlSteps instead of per-app wrapper patches
- Start every engine write through AppWrite::begin so the lock and the
pre-write recovery check cannot be skipped (the Codex edits-only path
missed it)
- Look up the direct provider, the in-use provider and the proxy origin
through single helpers; read the mode file once per status poll, settle
mode and written records in one state update, and query one row for the
provider existence check
- Drop the common-config merge chain in live.rs that no longer changed
anything, the unused config API and snippet helpers on the frontend
- Share the live-edit conflict retry flow, the inactive-fields panel and
the editor-view app list across dialogs and editors
- A multi-file operation records that it has started publishing in
live-state.json before the first file is replaced; if that cannot be
recorded nothing is published. Recovery rolls such an operation forward
even when a published file was changed again afterwards, so an
unpublished Codex login stash is no longer discarded.
- controller::lock_settled fails when the previous operation cannot be
finished, and entry points refuse to save or switch instead of writing
rows the leftover operation cannot restore. Startup, detach and exit
log the failure per app and continue.
- Saving, adding and syncing the current provider take the proxy switch
lock before reading mode and pointer, so entering proxy mode can no
longer be overwritten by direct key fields. A new proxy address is
written to every attached app even if one of them fails.
- Codex: the row-level model catalog pointer leaves with its provider; a
profile selecting the built-in openai provider counts as the official
route; exclusive fields the editor shows only because live has them are
compared by value, so an untouched field changed by the client stays
with the client. The add dialog sends the preset draft it projected, and
the backend uses it as the field origin like an existing row.
- Claude editor keeps a legacy Bedrock apiKey at the top level where older
versions read it; the Gemini editor recognizes a legacy Google card the
same way switching does.
- The request path passes the provider it already resolved to the router
instead of reading live-state.json twice per request.
- Exclusive fields shown in the editor only because live already had them
are no longer adopted into the row when left untouched; deleting one now
removes it from live instead of being a no-op. Modified values still
belong to the row. The Codex add dialog compares against a projection of
live with an empty card, since its base already carries the preset.
- Codex editor diffs skip the route tables selected by both the base and the
edited document, so a table renamed away from `custom` no longer leaks
into config.toml as a global setting.
- The Claude editor takes the proxy switch lock, saves the row before
writing live and restores the row when the write fails without leaving a
pending operation, matching the other editors. Row rollback is shared
through ProviderService::restore_row.
- commit_staged keeps the temp file when the rename fails, so a pending
operation can still be finished later; one-shot StagedWrite::commit
removes it itself.
- Recovery rolls forward once any file carries its planned bytes: files
still at their old bytes are published, files changed by someone else
are left alone, and the pointer/mode still land (RolledForwardExcept).
Only an operation that never published anything is discarded.
- Entry points take the switch lock and settle the previous operation
before reading mode, pointer, auth or stash (controller::lock_settled);
each app writer refuses to continue after finishing a leftover
operation so the caller retries with fresh state.
- Codex proxy contract includes the official login identity, so two
official routes for different accounts swap auth.json.
- An unreadable Codex login stash is never overwritten by a write that
would store a login into it.
Gemini CLI loads ~/.gemini/.env only at startup (and when validating the
auth method), and never overwrites a variable already in its environment,
so a running session keeps the old key, endpoint and model after a
switch. The switch toast now asks for a restart, as it already does for
Codex and Grok Build.
The user manual said the opposite in three places (the quick start
table, the switch page and the FAQ), claiming Gemini CLI re-reads .env on
every request; all three languages now say a restart is needed. The
README and the routing page now list Gemini CLI among the apps that show
a restart notice.
Brings the README FAQ (English, Japanese, German) and the English and
Japanese user manuals in line with the Chinese master text.
- README FAQ: switching no longer touches plugins, hooks and other shared
settings; a model changed in the client resets on switching back;
routing keeps the direct provider and writes it back on exit; the
device-local files; the restart note now covers Gemini CLI.
- Manual: the config editor section replaces backfill; the add dialog
follows the same rules; quick toggles are marked global or per
provider; the keys each app's switch changes; config ownership,
malformed files and the first-write backup; routing writes back the
direct provider; the Codex login is restored when switching back to an
official card; Gemini auth follows the provider type.
- The routing feature bullet and the quick start still said only Codex
and Grok Build may need a restart when a switch changes the model;
Gemini CLI does too, as the FAQ already says.
- The routing page still said routing backs up the original config and
restores it; disabling routing now writes back the direct provider,
and the only backup is the one taken before the first write.
Chinese master text for the new switching behavior; translations follow
once it is final.
- README FAQ: switching no longer touches plugins, hooks and other shared
settings (replaces the common-config snippet answer); a model changed
in the client resets on switching back; routing keeps the direct
provider and writes it back on exit; the device-local files
(live-state.json, the Codex login stash, first-write backups) and the
restart note now covering Gemini CLI.
- Manual: the editor section (what it shows, global vs per-provider
fields, edit conflicts, inactive fields) replaces backfill; the add
dialog follows the same rules; quick toggles are marked global or
per-provider; the keys each app's switch changes; the config ownership
and malformed-file sections; routing writes back the direct provider;
the Codex login is restored when switching back to an official card;
Gemini auth follows the provider type.
The add dialog for these apps now follows the same rules as the editor
and Claude Code's add dialog. Picking a preset (or the custom template)
projects it onto the current client files, so the editor shows what the
files will look like after switching to the new provider. On save, key
fields and provider-exclusive fields go to the row and other edits go to
live as global settings with a three-way check. Before, anything typed
there besides key fields was stored in the row and never took effect.
- Adding and editing share one backend path per app. When the new
provider is the first one, its key fields and the direct pointer are
committed in the same write; the Gemini and Grok editor writers take a
set_pointer flag for this, as Codex's already did.
- The form reports each projection to the dialog as the three-way base.
Only the latest request counts; while a projection is pending or after
it fails, the add saves the row only. Every reset of the shown content
re-projects or clears the base, otherwise global settings missing from
the editor would be written to live as deletions. Grok's official card
has no editor and keeps its seed flow.
- Remove six unused common-config strings and rewrite the empty-state
hint that still described the snippet; it now explains key-field
switching and also shows for Grok Build.
Switching, adding, editing, sync and entering or leaving local routing now
write Gemini CLI and Grok Build through the write engine. Whole-file
rewrites, backfill before switching and snippet merging are gone.
Gemini CLI:
- Only the key-field lines in .env change; comments, order and the user's
own variables stay. In settings.json only security.auth.selectedType and
model.name change. Both files commit as one operation.
- selectedType follows the provider type (Google login for official cards,
API key otherwise) instead of the row, so a /auth choice an older version
stored in a row can no longer send a third-party card through Google
login.
- Local routing writes the route's model name along with the local address
and placeholder key, so switching to a route with another model rewrites
the client, as Claude Code does.
- Vertex rows pass validation with GOOGLE_API_KEY or
GOOGLE_GENAI_USE_VERTEXAI instead of GEMINI_API_KEY.
- The common-config snippet is frozen like Claude's and Codex's; its UI and
the explainer dialog are removed. New rows keep commonConfigEnabled: true
for older versions that sync them.
Grok Build:
- Only models.default and the [model."<name>"] table CC Switch wrote
change; MCP servers and the user's other tables stay.
- The written table name is recorded in live-state.json in the same
operation, and switching away removes the recorded table rather than
whatever models.default points to now, so a default changed in Grok's
/settings no longer leaves the old provider's table behind. Without a
record (just upgraded), the table is inferred from the current row.
Tables holding the proxy placeholder key are always removed.
- Local routing writes the route's table pointing at the local
/grokbuild/v1 endpoint with the placeholder key through the engine. An
official card cannot be a route.
Editors:
- Both editors show what the client files will look like after switching.
On save, key fields go to the row and other edits go to live as global
settings with a three-way check. Gemini row fields that never reach live
can be added to the global settings; Grok's can be copied, as in Codex.
The TOML diffing shared with Codex moves to editor_toml.rs.
Also:
- First-run import no longer moves key fields (GEMINI_MODEL, Codex
model_reasoning_effort and the like) from the default row into the
snippet, where a frozen snippet would lose them.
- The .env patch can remove keys unconditionally and parse entries.
Switching, adding, editing, sync, universal providers and entering or
leaving local routing now write Codex through the write engine. Only
routing, credential, model, reasoning, context-window and compatibility
fields are replaced; MCP servers, [projects], comments and the user's own
provider tables stay byte-for-byte. auth.json, config.toml, the model
catalog, the managed-account marker and a device-local login stash commit
as one operation, so a crash at any point rolls forward on startup.
- Third-party routes are always written as [model_providers.custom] with
the key in experimental_bearer_token. Rows that use another id, a
top-level openai_base_url or a legacy reserved table are normalized on
the way out. Switching to an official card leaves a dormant custom table
(local proxy address plus placeholder credential) so older third-party
sessions can still resume.
- requires_openai_auth follows the route's auth and whether a login is on
disk; keyring and ephemeral credential stores are handled separately.
- A switch is refused before anything is written when the active profile
overrides the route, the selected table is missing or built in, a
keyless row would fall back to the official login, or the row has no
auth.
- Provider tables an older version wrote are removed only when they can
be proven to be CC Switch's. Tables a profile references are kept, and
reserved ids that cannot be proven are renamed.
- Provider-exclusive fields, including web_search and a row's own
model_catalog_json, are removed on the way out only when live still
holds the previous provider's value.
- No more backfill. A native ChatGPT login that CC Switch deletes or
overwrites is kept in ~/.cc-switch/codex-login-stash.json (0600, not
synced) and restored when switching back to an official card; logging
out on an official card discards it. Login snapshots already in
official rows seed the stash once. A leftover API key in auth.json is
deleted only when it matches a third-party row's key.
- Managed accounts fetch their token before taking the lock. If the CLI
refreshes the login during a switch, the switch stops before writing
anything. Once files are published, a later failure no longer rolls the
row back; startup finishes the switch.
- Local routing: third-party routes get a fixed local-proxy table and the
official route uses cc-switch-official plus the client's own login. The
contract covers every field written, so a route switch with the same
contract touches no files.
- The Codex editor shows what config.toml will look like after switching.
On save, key fields go to the row and other edits go to live as global
settings with a three-way check. Row fields that never reach live are
listed read-only and can be copied.
- The Codex common-config snippet is frozen like Claude's; its UI and the
TOML snippet command are removed. New Codex rows keep
commonConfigEnabled: true for older versions that sync them.
- The write engine can delete a file as part of an operation.
- Integration-test mutexes tolerate poisoning so one failure no longer
cascades into unrelated tests.
Local routing no longer takes over a client by backing up its live config
and restoring it later. Each routable app (Claude Code, Codex, Gemini CLI,
Grok Build) now has a device-local mode in live-state.json: direct or
proxy, whether it is attached, the proxy route, and the contract written
into the client. proxy_config.enabled is kept only as a mirror for older
versions.
- Entering proxy mode writes the route's contract (local proxy address,
placeholder credential, model aliases, provider-exclusive fields) into
the client; leaving re-projects the direct provider. No backups.
- The proxy route is independent of the direct provider. Switching while
routing changes only the route; if the contract is unchanged the client
file is neither read nor written. Failover records the new route and
never touches client files.
- Current-provider reads go through mode::current::provider_for with an
explicit purpose (direct pointer vs. the provider in use); a unit test
rejects direct calls to the old getters.
- Quitting detaches (writes the direct config back, keeps the mode);
startup re-attaches. Legacy takeover state is migrated on startup:
backup rows are saved to ~/.cc-switch/backups/proxy-live-backup/ and
deleted so an older build cannot replay them.
- "Stop routing" returns every app to direct and then stops the server.
Applying a project no longer disables routing; it moves the direct
provider or the route depending on the mode. A provider used as the
route cannot be deleted.
- Codex and Grok Build still use their whole-file writers for proxy
writes, and all three non-Claude apps for leaving; the direct row is
backfilled from live before entering (never for the official Codex
card), and leaving to an unbound official Codex card writes config.toml
only.
- The provider list marks the direct provider while routing.
- Removes the backup/restore/hot-switch/crash-recovery paths from the
proxy service, the unused tray Auto item, and the downgrade golden test.
Switching, adding the first provider, editing the current provider,
sync, universal providers and restoring after the local router all
write settings.json through the write engine now. Only endpoint,
credential, model and protocol fields plus the provider's own switches
are replaced; hooks, plugins, permissions and everything else stay
byte-for-byte. The file write and the current-provider pointer commit
as one operation, so a crash in between rolls forward on startup.
- No more backfill, no common-config snippet and no context-window
injection for Claude. Window values left behind by the old injection
are cleaned up from a frozen list.
- The provider editor shows what settings.json will look like after
switching. On save, provider fields go back to the row and other
edits go to live as global settings, with a three-way check against
external changes and a dialog to resolve conflicts. Row fields that
never reach live are listed with a one-click add.
- New Claude rows keep commonConfigEnabled: true so older versions that
sync the row still merge their snippet.
- Presets drop timeout and telemetry keys; a test now requires Claude
presets to contain only key or provider-owned fields.
Client config files are now changed through one engine instead of being
rebuilt from provider snapshots:
- Key-field definitions live in live/floor.rs and are shared with the
common-config extraction.
- JSON, TOML and dotenv patchers change only the keys they name. Other
keys, values, order, comments, indentation, line endings and BOM stay as
they are, and writing the same target twice is byte-stable.
- A file that cannot be parsed is refused and left untouched; it is never
rebuilt from an empty document.
- Each write is staged next to the target, re-read and compared before the
rename, and replanned on top of the new content when another writer got
there first. The first engine write of every file keeps a byte-for-byte
backup under ~/.cc-switch/backups/live-first-write/.
- Multi-file operations record their intent in ~/.cc-switch/live-state.json
(0600) before publishing. After a crash the operation is rolled forward
from the staged files, discarded if nothing was published, or abandoned
if the files were changed externally. Recovery runs at startup and before
every operation.
Claude Desktop is the first client moved onto the engine. Its four files
are written in one operation; the gateway profile keeps the user's own
settings, seeds disableDeploymentModeChooser and coworkEgressAllowedHosts
only when missing, and is written owner-only. Restoring the official app
now clears only the CC Switch key fields instead of deleting the profile,
and a meta file that cannot be parsed is refused instead of replaced.
PRAGMA incremental_vacuum frees one page per result row, and the pages
are only released while the statement is being stepped. execute_batch
steps it once, so startup cleanup and periodic maintenance reclaimed a
single page each time and the database file never shrank after pruning.
Add Database::incremental_vacuum_on_conn, which drains every result
row, and use it in both places.
Relays already price in "official price x their own multiplier", and the
real spend is visible from the relay's own balance or usage script. A
second copy of that multiplier in CC Switch only drifts out of sync, and
it only ever applied to requests going through local routing (session
log imports always recorded 1.0), so users setting it in direct mode saw
no effect at all.
- Remove the pricing section from the provider editor (it was also shown
for Pi and MiniMax Code, which never go through local routing).
- Remove the default multiplier column from the usage pricing defaults;
keep the global pricing model source (request vs response model) and
say it only applies to routed requests.
- Remove the get/set_default_cost_multiplier commands and DAO methods.
- Routed requests are now always recorded at multiplier 1. Existing log
rows keep the multiplier they were recorded with.
- Keep costMultiplier / pricingModelSource in ProviderMeta, unread, so
older devices syncing the same rows do not lose their values; the
proxy_config column stays as well, so no schema migration is needed.
The upcoming refactor replaces whole-file live writes with key-field
replacement and splits direct and proxy modes. These tests pin what must
survive it, and pass on the current code.
Backend (`cargo test --test golden`, snapshots under
src-tauri/tests/golden/snapshots, refresh with CC_SWITCH_UPDATE_GOLDEN=1):
- mcp_bytes: MCP projection bytes in ~/.claude.json, Codex config.toml
and Gemini settings.json, and that switching providers leaves the MCP
section untouched.
- import_rows: raw DB rows written by deep-link import and first-run
import (including seeded official rows and the generated snippet).
- file_modes (Unix): which client files a switch writes and their
permission bits; existing 0600 files are never loosened.
- claude_key_fields: after a switch, live key fields are exactly the
target provider's, across third-party, Bedrock, Vertex, apiKeyHelper
and official providers, with or without common config.
- codex_red_lines: 25 Codex invariants (index at the top of the file),
asserted as outcomes: the config loads on Codex 0.149, the official
login never reaches a third-party URL, refused switches leave files,
current provider and rows untouched.
- downgrade_contract: the previous release detects takeover by the
PROXY_MANAGED literal, rebuilds live from current rows without backup
rows, and replays leftover backup rows.
Frontend: ProviderForm submit payloads for ten representative Claude and
Codex presets.
Three spots are sorted before comparing because their output order is
not deterministic today (MCP maps written from a HashMap, Gemini .env
parsed into a HashMap). One ignored test records a known gap: exiting
the proxy restores a login the user logged out of while on a
third-party Codex route.
Common config snippet (Claude Code):
- Treat custom request headers (`*_CUSTOM_HEADERS`, `HEADERS`) and any
key containing COOKIE or AUTHORIZATION as credentials. Header names in
general stay shareable (CLAUDE_CODE_ATTRIBUTION_HEADER,
OTEL_EXPORTER_OTLP_HEADERS).
- Never share key fields: all ANTHROPIC_* settings, the protocol
selectors (CLAUDE_CODE_USE_BEDROCK/VERTEX/...), AWS_* and Vertex
regions, and top-level model/apiKeyHelper/fallbackModel. Switching
from Bedrock to the official provider no longer keeps Claude Code on
Bedrock, and a /model choice no longer follows every switch.
- Entries an existing snippet already shared are stripped from the
current provider's row on its next switch-away, so they cannot get
stuck in the wrong provider. A provider carrying AWS or Google
credentials keeps that family's selectors and regions.
Live files holding keys are now written owner-only (0600): Codex
auth.json and config.toml, Claude Code settings.json and Grok Build
config.toml. New files used to follow the umask (0644).
Presets:
- E-FlowCode and PIPELLM no longer ship personal preferences; Codex
E-FlowCode drops `personality`.
- AWS Bedrock (API Key) stores the key in env.AWS_BEARER_TOKEN_BEDROCK,
which Claude Code reads. The key field and the proxy read it too;
existing rows with a top-level apiKey keep working and are edited in
place.
- A preset scan keeps preference keys out of Claude and Codex presets.
Remove dead code only reached from tests: ProxyService::start_with_takeover
and its helpers, and the ConfigService sync_current_providers_to_live
family.
Behavior changes: ANTHROPIC_* settings such as ANTHROPIC_LOG are no
longer shared; a Bedrock or Vertex provider whose selectors only lived in
the shared snippet may need them re-entered once.
Grok Build's /settings "Default model" rewrites models.default in
~/.grok/config.toml. Selecting a built-in model while a third-party
provider was active left the provider's own [model.*] table
unreferenced; the switch backfill stored that config verbatim, and
switching back then failed validation with "missing [model.\"...\"]".
When models.default is missing or dangling during backfill and the
provider's original default table is still present, restore it.
- Cover Grok Build, Pi and MiniMax Code across install, interface,
settings, presets, MCP, Skills, prompts, sessions, local routing,
config files and env conflict pages
- Rename proxy/takeover wording to local routing and update settings
paths to the Routing / Auth / Usage Statistics tabs
- Rewrite 4.5 as the connectivity check that replaced model test in
v3.16.3 (no real model request, no test model to configure)
- Fix stale facts: official subscription quota is opt-in via the
Official Subscription template since v3.16.2, Codex keys live only in
config.toml experimental_bearer_token, deep link content/config must
be Base64, SQL export includes usage logs, onboarding skip writes
hasCompletedOnboarding in ~/.claude.json
- Backfill the website-only Codex preset section (native Responses vs
Chat Completions) and align UI labels with the v3.20.4 locales
- Retranslate en/ja from the approved zh master, keeping all three
languages line-aligned with matching anchors
- Backfill website-only edits: Kimi direct-connection section, desktop FAQ cross-links, stray </content>
- Retitle the DeepSeek guide around Chat-format providers; v4-pro, Kimi, GLM and MiniMax now connect directly
- Rename the toggle to "Keep official login for direct switches"; document auth.json removal and takeover preservation
- Prefill gpt-5.6-sol, mention the Kimi Global presets, and use routing takeover terminology
- Remove the outdated, unreferenced proxy-guide-zh.md
- Reorder sections (download and quick start before features), add a per-tool
capability table, and document local routing, projects, the OAuth auth
center (with a ToS risk note), quota display, usage without routing,
S3 sync, and the CLI tool manager
- Rewrite the data-storage FAQ; add FAQs for routing addresses, OpenAI/Gemini
upstreams, connectivity checks, WSL, and the community CLI
- Fix first-launch import (Claude Desktop is not imported), Pi's Enable
button, and the non-existent Markdown preview; unify on "local routing"
- Move architecture/dev guide/project structure into CONTRIBUTING.md and
refresh it (Node/pnpm/Rust versions, CI-aligned checks, test isolation,
four locales, no ESLint/pnpm lint)
- Add Pi (10 managed apps), scope tray/MCP/Skills per app, document additive apps
- Fix "signature bypass" wording (Hide AI Attribution), common-config FAQ labels,
official-login/restart FAQs, and first-launch auto-import note
- Correct Linux requirements (glibc 2.35 + WebKitGTK 4.1) and release asset names
- Update dev env (Node 20.19+/22.12+, pnpm 10, Rust 1.95), drop broken coverage cmd
- Point release notes to v3.20.4 and SSSAiCode to sssaicodeapi.com
- JA: replace the stale unsigned-macOS instructions; fix Chinese FAQ anchors in
SUPPORT.md and issue templates
Updating a CLI installed outside npm fell back to a bare `npm i -g`, which
silently installed a second, npm-managed copy instead of upgrading the one
the command line actually runs.
- Codex standalone installer (install.sh / install.ps1): rerun the official
installer with CODEX_INSTALL_DIR / CODEX_HOME pinned to the detected
install. `codex update` is not used: it pipes `curl | sh` without
pipefail and reports success even when the download fails.
- Keep the npm self-heal for broken Codex installs away from standalone
installs whose entry lives in a Homebrew/nvm bin directory.
- When the default install is a native binary outside node_modules and its
channel is unknown (winget, Scoop, Nix, manual download), stop falling
back to npm: the probe reports it as unmanaged, the About page skips it
with a localized hint, and the backend refuses to run it.
- Add a Claude Code cask regression test on top of the Homebrew cask
support from #6572.
Fixes#7650
MiniMax Code 0.5.x reads a whitelist of `models.<id>.compat` keys from
config.yaml and layers them over the compat it synthesizes, so Pi's
per-model compat can be reused instead of dropping every preset that
carries one.
- Keep a model when every compat key is one MiniMax Code reads, and write
the compat verbatim. kimi-k3 stays out: `deferredToolsMode` is unknown
to MiniMax Code's embedded pi-ai.
- Map adaptive-thinking Claude models to `thinking.effortOptions`, since
MiniMax Code ignores Pi's thinkingLevelMap and compat-blind versions
would otherwise fall back to budget_tokens.
- Presets grow from 40 to 77; only AWS Bedrock remains excluded.
MiniMax Code intentionally has no "Import Current Config" button (same as
Pi): custom providers in config.yaml are merged into the list on every
load, and the official MiniMax account provider plus its sign-in state
are owned by MiniMax Code, which CC Switch refuses to write. The empty
state still showed the generic copy asking users to click that button.
Give mcode its own empty-state title and description in all four
locales, route Pi and mcode through their own namespaces in
ProviderEmptyState, and add a regression test.
Add MiniMax Code (mcode) to the About page tool list: version probing,
latest version from the npm registry (@minimax-ai/code), and silent
install/update actions.
- Install runs the official installer (install.sh / install.ps1) with an
npm fallback. npm installs pass --ignore-scripts=false,
--include=optional and --allow-scripts for @minimax-ai/code and
better-sqlite3, since npm 12 blocks dependency install scripts by
default and SQLite breaks without them.
- `mcode update` is deliberately not used: without a TTY it only prints
the command and exits 0 without installing, which would mask failures
in silent lifecycle runs.
- Script installs are updated in place by re-running the official
installer with MCODE_INSTALL_DIR set to the detected root. Custom
install dirs are claimed via the installer's install.json receipt
(UTF-8 BOM tolerated, as Windows PowerShell 5.1 writes one), falling
back to the default .minimax-code directory name. The Windows layout
keeps mcode.cmd at the install root, so only the POSIX layout strips
a bin segment.
- npm global installs anchor to the sibling npm with the same flags.
- Search the installer launcher dirs ($MCODE_INSTALL_DIR,
~/.minimax-code) ahead of generic Node locations.
- Update the one-click install hint in all four locales, also adding the
previously missing Grok Build.
Colored brand icons (Claude, Gemini, OpenCode, OpenClaw, Hermes,
MiniMax Code) kept their full color when inactive, so only the pill
background distinguished selected vs unselected. Monochrome icons
(Codex, Grok Build, Pi) already dimmed via currentColor.
Apply grayscale + reduced opacity to inactive colored icons, restoring
full color on hover or when active.
Both confirmed against the vendors' official pricing pages (docs.x.ai,
docs.z.ai) and match models.dev. Grok 4.7 records the base (<200K) tier
like 4.5/4.6.
Downloads and updates now go through ccswitch.io, so users no longer
pass through the GitHub repo and rarely star it. Surface the ask where
they still look: between the app title and the action buttons.
- Show a one-line prompt in the primary color that opens the repo,
followed by a pointer toward the GitHub button
- Move the GitHub button to the front of the row so the pointer lands
on it, tint it with the primary color (kept below the solid "check
for updates" action), and add a slowly spinning star that stops
under prefers-reduced-motion
- Add settings.starPrompt to all four locales
Add non-sponsor presets for Soshow Model Market (maas.so-show.com) across
Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi, plus
its icon.
- Claude Code sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS, which the vendor's
Model API doc requires for the Claude protocol, and points the Haiku tier at
claude-sonnet-5 since the market offers no Haiku model.
- Claude Desktop declares only the sonnet/opus routes: direct mode cannot map
models, so passthroughRoutes() would expose a Haiku route that 404s.
- Codex uses native Responses at /v1. The route is not in the vendor docs but
exists (unknown paths return 404, /v1/responses returns 401 without a key).
- OpenCode, OpenClaw, Hermes and Pi use the Anthropic protocol with the two
Claude models, matching the vendor's OpenClaw guide.
- Gemini CLI is skipped: the native generateContent routes return 404.
The FluxA sponsor row only linked from the banner image. Append the same
"via this link" call to action the other sponsor rows use, pointing at
the TokenPlan marketplace page, and collapse the blank line between the
two paragraphs into a single line break to save vertical space.
The DeepSeek V4 family has a 1M context window - every Codex catalog in
this repo records 1048576 for these ids - but the Claude Desktop presets
that pin them left supports1m off, so the 1M variant never appeared in
Claude Desktop's model list. The [1m] suffix is a Claude Desktop local
marker: it is translated to the supports1m field at the import boundary
and stripped before upstream matching, so turning it on does not change
what is sent to the provider.
Affects Baidu Qianfan Token Plan, DeepSeek and OpenCode Go. PPIO already
had it on.
FluxA AgentMarket resells Baidu AI Cloud's international Token Plan at
partner pricing. The key it issues is used directly against Baidu's
overseas endpoint, which is a separate deployment from the existing
domestic personal plan on qianfan.baidubce.com.
- Claude and Claude Desktop use the Anthropic-compatible base
api.baiduqianfan.ai/anthropic/tokenplan/team; Codex, OpenCode,
OpenClaw, Hermes and Pi use the OpenAI-compatible
api.baiduqianfan.ai/v2/tokenplan/team.
- The model lineup follows FluxA's product page table, excluding the two
entries it marks "Coming soon". kimi-k2.6 is added on top because the
sponsor copy names Kimi; its id and 262144 context window come from
FluxA's own baidu-ai-cloud catalog and the domestic Token Plan preset.
- glm-5.2 is pinned to the 500k window FluxA publishes for this
international deployment, not the 1M the domestic platform reports.
- DeepSeek V4 rows are declared text-only, matching the other hosted
Token Plan presets, and the preset joins the existing regression guard
that keeps those rows from failing open to image input.
- Registers the fluxa icon, the partner promotion string in all four
locales, and the sponsor row in all four READMEs.
The AICodeWith site owner confirmed the Responses endpoint is
https://api.aicodewith.ai/v1. The /chatgpt/v1 path published in their
blog posts is a documentation error.
Update the Codex preset (base_url and endpointCandidates) and the
Hermes, OpenClaw and Pi presets, which reuse the same Responses
endpoint.
Existing providers created from the old preset keep the stored URL and
need to be edited manually.
Icons that paint with currentColor inherited the preset button's
text-muted-foreground, so the same icon looked mid-gray in the preset
selector but near-black in the form's icon preview and on the provider
card. The mismatch hit all 28 monochrome icons and was most visible on
9527CODE, whose 0.82-opacity base layer lightened the gray further.
Unselected preset icons now take text-foreground; selected ones still
inherit the button's text-white. The universal preset row gets the same
treatment. Brand-colored icons are unaffected because ProviderIcon sets
their color inline.
Baidu Qianfan and StepFun now ship native Responses presets for their
pay-as-you-go APIs, so the Chat-only examples name the offerings that
still route through Chat: Baidu Qianfan Coding Plan and StepFun Step
Plan. Synced across the zh/en/ja manuals.
- deepseek-v4-pro returns to its peak list price 1.32/3.96/0.044. The
2026-09-11 move to the V4.1 Flash price anticipated a cutover that
DeepSeek retracted on 2026-09-12. Replace that repair entry with one
that restores rows still at 0.3/1.2/0.006; keeping both would rewrite
the row back and forth on every start.
- Seed hy4-preview (CNY 6/18/0.3 at 7.14), qwen3.8-2.4t-a95b and
qwen3.8-27b (Alibaba Cloud international list prices).
- MiniMax CN: the remaining apps switch from api.minimaxi.com to
api.minimax.cn (Anthropic root /anthropic). Usage detection recognizes
both hosts and now matches on host labels (frontend, and the global
host in the backend), so look-alike hosts and URLs that only carry the
domain in their path are ignored. The quota query keeps
api.minimaxi.com, the only CN host documented for that endpoint.
- BaiLing: the remaining apps switch from api.tbox.cn to
api.ant-ling.com (/v1 and /anthropic) and default to Ling-2.6-1T,
since Ling-2.5-1T is no longer listed in the current docs. Ling-2.6-1T
is registered as text-only (vision ships as separate -VL models).
- Reword the DeepSeek V4 Pro note in the text-only registry: the vendor
keeps serving V4 Pro after September 14, 2026.
- Kimi For Coding: kimi-for-coding now serves K2.8 Preview (up to 1M,
text+image, low/high/max); the highspeed variant stays at 256K.
- DeepSeek: default to deepseek-flash (V4.1 Flash, text+image) and
declare deepseek-v4-pro text-only, matching the bundled official
catalog.
- Add native Responses presets for StepFun API (CN/intl,
step-3.7-flash), Baidu Qianfan pay-as-you-go (/v2) and iFlytek Astron
Coding Plan; subscription plans on the same hosts stay on Chat.
- Add hy4-preview to Tencent Hunyuan, and the open-weight
qwen3.8-2.4t-a95b / qwen3.8-27b to both pay-as-you-go Qwen platforms.
- MiniMax CN moves to api.minimax.cn and BaiLing to api.ant-ling.com.
- Disable Codex web_search on StepFun (function tools only), and
conservatively on Qianfan and Astron, whose Responses docs document no
hosted tools. The new hosts stay out of CODEX_NATIVE_RESPONSES_HOSTS
so Chat plans on the same hosts are never reclassified as native.
- Tencent Enterprise Token Plans stay on Chat: /plan/v3/responses is
listed, but TokenHub marks Kimi K2.7 Code unsupported and several
models as server-side Chat conversions, and the catalog levels were
verified on Chat only.
Follow-up to #7331 (Claude Desktop 3P configuration on Linux):
- Compile the Linux config-dir resolver and its three unit tests under
any unix test build so they run on macOS as well as CI ubuntu. The
gate is unix rather than test because the assertions use POSIX
absolute paths, which are not absolute on Windows.
- Open the provider -> Claude Desktop profile sync integration tests to
Linux: add the Linux profile path helper and pin XDG_CONFIG_HOME under
the temporary home (saved on construction, restored on drop).
- Update the support-scope table in the en/zh/ja user manuals to list
Linux and note that the Flatpak build always uses the host ~/.config.
A disabled React Query observer still returns the existing cache entry for
the same key, so a provider switched to the official subscription template
(or with its usage script disabled) kept showing the multi-plan chevron and
the expanded tiers left over from a previous script result.
Gate hasMultiplePlans with the same predicate that enables the usage query,
extracted as scriptUsageActive so the two cannot drift apart again.
DeepSeek retired V4 Flash and announced that V4 Pro is routed to V4.1
Flash and billed at the Flash price from 2026-09-14 12:00 (Beijing).
Executing that callback early per maintainer decision.
- Seed `deepseek-flash` (the only currently recommended id) and
`deepseek-v4-flash-vision-exp` so the `LIKE '{id}-%'` prefix fallback
cannot silently bill them at zero.
- Reprice `deepseek-v4-flash` / `-0731` / `-pro` to 0.3 / 1.2 / 0.006
with chained repair guards ordered after the 2026-08-16 peak/off-peak
rows; `deepseek-chat` / `deepseek-reasoner` stay untouched for lack of
an authoritative source.
- Drop `deepseek-v4-pro` from the confirmed text-only registry: it now
lands on a vision-capable model, so the image sanitizer must fail open.
Fixtures that relied on it being text-only switch to `qwen3-coder-plus`,
including the two media_sanitizer tests #7286 had just pointed at it.
DeepSeek's official endpoint now routes the legacy deepseek-v4-flash id to
the vision-capable V4.1 Flash, so #7286 removed it from the confirmed
text-only registry and the id fails open everywhere. Baidu Qianfan and
Tencent Token Plan still host the text-only V4 deployments (Qianfan's
Coding Plan doc states image input returns 400; Tencent's 2026-09-10 plan
rosters list V4 only), so their preset rows must carry an explicit
inputModalities: ["text"] declaration instead of relying on the registry.
Covers the flash and pro families incl. -0731/-0813/-202605/-202606 rows,
and adds a preset test that also locks the official DeepSeek preset to
stay undeclared (fail-open).
Follow-up to #7283.
Rename the DouBaoSeed provider preset across all seven app preset files
and localize its display name: zh/zh-TW show "火山 豆包AI", en/ja show
"Volcengine Doubao" via the new providerForm.presets.doubaoseed key.
Following the existing Qiniu/Compshare convention, the top-level name
holds the latin fallback (so the preset stays searchable in both
scripts) and the localized name lives in the locale files. OpenCode and
Pi settingsConfig.name, which is written into the client config files,
keeps the ASCII form.
Live-config identifiers are deliberately untouched: the Codex TOML
provider name, the Hermes node key doubao_seed, Pi's providerKey, the
OpenClaw model-ref prefix, partnerPromotionKey and icon.
Follow-up to #7219. The parser counted only fully consumed records toward
the persisted byte length, so a rollout that ends mid-record or in
whitespace (a crashed session that was never resumed) always looked
shorter than its file size and was fully reparsed on every sync pass.
Count the bytes of the incomplete tail as observed too. The line cursor
still stops before the incomplete record, so it is retried as soon as the
file grows, while an unchanged file is skipped on the mtime+size gate.
Tests prove the skip via total_changes() rather than a zero import count.
Since #6537 the tray deliberately dropped the usage suffix for Codex
official cards bound to a managed ChatGPT account: the tray only had an
app-wide subscription cache fed by whatever login the Codex CLI holds
(macOS Keychain first, then auth.json), which cannot represent
account-scoped quota safely once several accounts are bound. The provider
card queried the bound account through `get_codex_oauth_quota`, but that
result never reached the tray, so users with multiple ChatGPT logins saw
`Codex · <name>` with no quota at all.
- UsageCache: add an account-keyed `codex_oauth` snapshot map
(`put_codex_oauth` / `with_codex_oauth`), cleared by `invalidate_all`.
- `get_codex_oauth_quota`: write the `Ok` snapshot through to the cache
under the resolved account id and schedule a tray label refresh.
Transport errors keep the last good snapshot; auth/HTTP failures replace
it so the tray hides an invalid quota instead of a stale one.
- Tray: introduce `TrayUsageSource` so the refresh path and the display
path share one decision. Managed Codex cards read only their bound
account's snapshot (never a provider-scoped or CLI snapshot as fallback),
default to enabled when no usage toggle has been saved (matching
ProviderCard), and the fixed official card takes the managed path too
when it carries a binding. Rebinding a card switches to the new
account's snapshot immediately and a late response for the previous
account cannot overwrite the label.
- Tests: source selection across binding/toggle/legacy shapes, label
rendering across switch, rebind, late response, disabled and expired
states, and the native fixed card still using the CLI subscription.
Fixes#7267
Some third-party Anthropic-compatible gateways (e.g. DeepSeek) validate
tool JSON Schemas with a strict regex checker and reject the Unicode
property escapes (\p{Cc}...) that Claude Code's Artifact tool emits when
its str_replace db capability is rolled out, failing every request with
400 "Invalid schema for function 'Artifact'". The failure is independent
of the model selected.
Add a sixth checkbox next to the existing Claude quick toggles that sets
env.CLAUDE_CODE_DISABLE_ARTIFACT="1" on the provider, which keeps the
Artifact tool out of the tools array entirely. Unchecking removes the key
like the other toggles do. Labels added for zh/en/ja/zh-TW and the user
manual toggle table updated in zh/en/ja.