Compare commits

...

134 Commits

Author SHA1 Message Date
Jason 878c26f31e feat(proxy): extend tool-result media handling to all conversion bridges
Generalize the Codex Responses-to-Chat tool media mechanism from 6c9d444c
to the remaining protocol bridges, so image-bearing tool results are never
tokenized as base64 text on any conversion path:

- Claude-to-Chat: tool_result images become native image_url parts,
  batched into one synthetic user message after the tool message batch.
- Claude-to-Responses: JSON-string, MCP, and nested variants are restored
  as native input_image parts inside function_call_output.
- Codex/GrokBuild-to-Anthropic: non-standard tool images are restored as
  Anthropic image blocks (case-insensitive data:/http prefix parsing).
- Claude-to-Gemini: Gemini 3 uses multimodal functionResponse.parts;
  older models get inlineData parts in the same user turn. The new
  InlineImagesOnly scope keeps remote URLs and malformed data URLs in the
  legacy text form instead of emitting fileData the API would reject.

Shared changes in tool_media:

- Centralize the plan/queue/flush helpers used by both Chat bridges.
- strip_and_clamp_media_from_tool_value clamps residual base64 inside
  parsed JSON strings at any nesting depth before re-serialization.
- Emitted Chat parts no longer carry cache_control or
  prompt_cache_breakpoint, preserving the strip-all-cache_control
  contract that strict upstreams (GLM/Qwen) depend on.
- Media detection now requires full convertibility, keeping detection
  and extraction symmetric by construction.

The media sanitizer detects and strips the new shapes symmetrically
(Chat string tool results, Anthropic string tool results, Gemini
inlineData/fileData and functionResponse.parts); the legacy typed-block
replacement runs first so replacement stays a superset of detection and
cache_control survives on replaced Anthropic blocks.

No-media tool outputs keep byte-identical legacy representations on all
bridges to protect prompt-cache prefixes.
2026-07-24 12:12:22 +08:00
Jason 6c9d444c8a fix(proxy): move Codex tool-result media out of stringified tool text
The Responses->Chat conversion serialized image-bearing *_output items
into role:"tool" text via canonical_json_string, so view_image results
were tokenized as base64 text (~9000x inflation). Codex replays full
history every turn, so sessions hit context-limit 400s and wedged
(#4465, #5663).

- add proxy/tool_media: shared detection/strip/clamp walker for tool
  output media (typed input_image / image_url / input_file /
  input_audio, Anthropic source and MCP data+mimeType image shapes,
  untyped data: image_url, whole-string bare data URLs)
- transform_codex_chat: replace media blocks in place with marker text
  so tool content stays a plain string, and flush the extracted media
  as one synthetic role:"user" message after each consecutive tool
  batch; media-free traffic stays byte-identical to keep prompt-cache
  prefixes stable
- media_sanitizer: detect and strip tool-output media symmetrically
  (including JSON-string outputs) so reactive image stripping can heal
  upstream modality rejections
- forwarder: regression tests pinning the reactive trigger and the
  context-limit-400 non-trigger

E2E against Kimi K3 through the proxy: the replayed turn stays ~12k
input tokens with 99% cache hit, versus ~85k+ of base64 text per
replay before.
2026-07-24 10:16:57 +08:00
Jason 34cbb375f0 feat(usage): surface Grok Build session source in usage UI
Add the grok_session icon to DataSourceBar and the Grok Build labels
for the app filter and data-source breakdown in all four locales
(zh/en/ja/zh-TW).
2026-07-23 17:00:09 +08:00
Jason cd161f4401 feat(usage): import Grok Build official-mode usage from session logs
Grok CLI's official OAuth mode cannot be routed through the local proxy
(empty config is the mode switch, so there is no injection point), which
left official-mode usage invisible. Add session_usage_grokbuild to
import usage from ~/.grok/sessions updates.jsonl:

- Only turn_completed events carry usage; each event is the independent
  per-turn total (accumulated across inference loops within one prompt),
  so events are imported at face value. Do not reintroduce differencing
  of adjacent events: counters reset every turn and differencing would
  massively under-record.
- Cost priority: reported costUsdTicks (1 tick = 1e-10 USD) wins when
  complete, because the backfill only repairs rows with total <= 0 and
  can never correct a positive mispriced value; local pricing fills the
  breakdown and raises a drift warning above max(1% of reported, 1e-6).
  costIsPartial marks the reported value a lower bound: prefer a full
  local recompute when the model is priced, else record the lower bound.
- Idempotency key grok_session:{session}:{prompt_id}:{model} anchors on
  the upstream per-turn UUID (index fallback only when prompt_id is
  empty), so rewind truncation cannot shift keys and double count;
  orphan rows from truncated turns are kept since the tokens were spent.
- Anti double-count vs proxy takeover: 10min settle window plus a
  time-window guard over recent grokbuild proxy activity; guarded skips
  never mark files as synced.
- Seed grok-4.5-build pricing 2/6/0.30, back-derived from exact
  costUsdTicks samples (cache read bills at 0.30, not the listed 0.50).
- Map _grok_session to a friendly provider display name and refresh the
  takeover-capability comment in services/proxy.rs.
2026-07-23 17:00:03 +08:00
Jason 3cf84ca362 fix(usage): centralize cache-inclusive app set and cover grokbuild in cost backfill
The cost backfill hardcoded codex|gemini as cache-inclusive apps, so
grokbuild TOTAL-semantics rows were priced on full input tokens with
cache reads double-counted. Converge the writer (proxy logger and
calculator) and the backfill recompute onto a single
sql_helpers::is_cache_inclusive_app predicate backed by the existing
CACHE_INCLUSIVE_APP_TYPES constant, and add a regression test for the
grokbuild backfill path.
2026-07-23 16:59:41 +08:00
Jason 15d5dbe065 feat: add Grok official subscription quota query
Add SuperGrok subscription usage display, following the existing
Claude Code / Codex official-subscription pattern (protocol ported
from steipete/CodexBar):

- New subscription_grok service: reads Grok CLI credentials from
  ~/.grok/auth.json, calls the grok.com GrokBuildBilling gRPC-web
  endpoint, and parses the response via heuristic protobuf scanning
  (used percent, reset time, zero-usage special case)
- Transient failures (network errors, HTTP 408, gRPC deadline/
  cancelled) propagate as Err so the frontend retries and keeps the
  last good value; auth failures map to Expired with a re-login hint
- Tier naming by reset distance: weekly limit, monthly, or a new
  "credits" tier (i18n added for zh/en/ja/zh-TW; tray shows "c")
- New get_xai_oauth_quota command: xai_oauth providers (managed
  SuperGrok OAuth accounts) query the same billing endpoint with
  their bound account token; ProviderCard auto-renders the quota
  footer for them and hides the usage-script entry, and the tray /
  usage-script path routes xai_oauth providers to the managed
  account instead of the host app's CLI credentials
- UsageScriptModal: drop the config-content heuristic for official
  detection; category === "official" is the single source of truth

Claude-Session: https://claude.ai/code/session_01LSNvhEfuoJHaQLZcYQgBU5
2026-07-23 11:13:07 +08:00
Jason a377d79303 docs: sync v3.18.0 release notes and guide cross-links to new guide titles 2026-07-22 12:58:11 +08:00
Jason 846fbdd1c0 docs(guides): retitle Codex Claude guide to match 2026-07-22 12:13:57 +08:00
Jason 3a9fb13a0b docs(guides): add en/ja translations for Claude Code GPT guide and retitle 2026-07-22 12:13:57 +08:00
Jason 606e7bbe75 docs(release): add v3.18.0 release notes 2026-07-21 23:14:45 +08:00
Jason df1751a893 chore(release): v3.18.0 2026-07-21 22:23:17 +08:00
Jason f3108bf754 feat(providers): group presets into sponsors and non-sponsors
- Original sort mode now shows official > prime partner > sponsors
  (preset file order, aligned with the README sponsor table) >
  non-sponsors sorted by display name
- Physically reorder sponsor entries in all 8 preset files to match
  the README sponsor table order; add group marker comments
- Drop stale section comments in openclaw/hermes preset files
- Update ProviderPresetSelector tests for the new ordering
2026-07-21 21:10:54 +08:00
Jason b3f3aee3ee docs(readme): move Unity2.ai sponsor row ahead of Shengsuanyun 2026-07-21 21:10:54 +08:00
Jason 2bfca54801 docs(readme): reorder sponsors and refresh RunAPI benefit copy
- Reorder the sponsor table into the new priority order across all four
  locale READMEs (en/zh/ja/de).
- Update RunAPI's benefit line to a 10% first-top-up discount (was
  "contact support for a ¥14 credit").
2026-07-21 20:11:06 +08:00
Jason 107eade30e chore(presets): add RunAPI referral link runapi.co/register
Point the RunAPI invite link at https://runapi.co/register?aff=iOKB
across all 7 app presets (apiKeyUrl) and the four README sponsor rows
(banner + CTA), which previously used the bare runapi.co homepage with
no referral tag. The websiteUrl and runapi.co API endpoints stay bare,
so the aff code never enters a request URL.
2026-07-21 18:41:08 +08:00
Jason 3537076af2 chore(presets): update ClaudeCN referral link to claudecn.ai/register
Move the ClaudeCN invite link to https://claudecn.ai/register?aff=HEL9
across all 7 app presets (apiKeyUrl) and the four README sponsor rows
(banner + CTA, which previously pointed at the bare .top domain with no
referral tag). The websiteUrl and claudecn.top API endpoints are kept
unchanged, so only the registration/referral link moves to the new
domain and code.
2026-07-21 18:39:39 +08:00
Jason 72efd64a3d docs(readme): serve Kimi sponsor banners from Moonshot CDN
Point the pinned Kimi K2.7 Code banner at the Moonshot growth CDN
(kimi-zh.png for the ZH README, kimi-en.png for en/ja/de) instead of
the in-repo assets. Alt text and the aff=cc-switch links are unchanged.
2026-07-21 18:22:00 +08:00
Jason f0b7b81c11 chore(presets): update ZetaAPI referral link to go/u117
Refresh the ZetaAPI invite link from /go/ccs to /go/u117 across all
7 app presets (apiKeyUrl) and the four README locales. The historical
CHANGELOG entry and the api.zetaapi.ai endpoints / bare websiteUrl are
left untouched so the referral code stays out of request URLs.
2026-07-21 18:19:51 +08:00
Jason b660061b8a chore(presets): update APINebula referral link to VjM74M
Refresh the APINebula invite link from /02rw5X to /VjM74M across all
8 app presets (apiKeyUrl) and the four README locales. Bare-domain
websiteUrl and /v1 API endpoints are left untouched so the referral
code never leaks into request URLs.
2026-07-21 18:14:03 +08:00
Jason 200aa14295 style(providers): fix prettier formatting in ProviderForm 2026-07-21 17:20:45 +08:00
Jason bc8001238a docs(readme): update pinned Kimi sponsor copy to K3 2026-07-21 16:39:34 +08:00
Jason 325ba48486 feat(grokbuild): curate standalone provider presets
Grok Build previously reused the whole Codex preset list, leaking
cn_official providers and Codex default models into the Grok CLI form.
Replace the inline filter with a standalone, independently maintained
preset module (no data linkage to codexProviderPresets):

- drop cn_official direct providers and open-source-only hosts
  (SiliconFlow/ModelScope/Novita/Nvidia/AtlasCloud/OpenCode Go) that
  have no Grok models upstream
- keep aggregators and third-party relays with grok-4.5 as the default
  model (x-ai/grok-4.5 for OpenRouter-style namespaced routers)
- move the Grok Official seed entry into the presets module
- add standalone integrity tests; retarget the form's chat-mapping test
  since no openai_chat preset remains in the list
2026-07-21 16:39:34 +08:00
Jason a8daf7daad fix(codex): add missing AiHubMix preset icon
Every other app's AiHubMix preset already sets icon "aihubmix" with
iconColor #006FFB; the Codex entry was the only one missing it.
2026-07-21 16:39:34 +08:00
Jason f733def452 feat(grokbuild): add Grok Official provider with official-state import
Add a "Grok Official" preset and seed (grokbuild-official) whose empty
config represents the official login state: no custom [model.*] tables
are written, so Grok CLI falls back to its built-in xAI OAuth login and
cc-switch never touches those credentials.

Backend:
- Seed entry in OFFICIAL_SEEDS plus ensure_grokbuild_official_provider
  command for on-demand repair (the one-shot master seeding flag is
  already set for existing databases).
- Split validation into syntax-only (empty allowed) for live reads,
  writes and official snapshots, keeping the full custom-model shape
  check for non-official provider writes and imports. Backup/restore
  can now round-trip an official-state live file.
- Manual import (command layer only) recognizes an official-state live
  config and imports it as the official entry set as current, matching
  the Codex official-login import outcome. Startup auto-import keeps
  rejecting official-state live so a deleted official entry is never
  resurrected on launch: startup import only captures real user data
  as "default" and never manufactures official entries.
- Manual import also ensures the official entry before importing
  (claude-desktop precedent) and after a successful custom import, so
  first-time users end up with default + official like other apps.
- Proxy takeover guards skip or reject official-state live configs in
  all three takeover paths, consistent with the official-provider
  takeover ban.

Frontend:
- Grok Official preset entry in the GrokBuild form: official category
  hides connection fields and passes the raw config through untouched.
- Filter managed-OAuth presets out of the GrokBuild preset list; they
  were never wired for this app and produced keyless broken configs.

Tests cover seed presence, official round-trip, ensure-after-deletion,
and the four import scenarios including startup non-resurrection.
2026-07-21 16:39:34 +08:00
Jason a5aa1fd82b fix(providers): surface import errors and refresh list on failed import
Tauri invoke rejects with the backend's serialized error string, not an
Error object, so reading `.message` produced an empty toast for every
failed live-config import. Route the rejection through
extractErrorMessage with a generic i18n fallback.

Also invalidate the providers query on import failure: the import
command can have visible side effects before erroring (e.g. GrokBuild
ensures its official entry prior to a failing import), and the list
must reflect them without a manual refresh.
2026-07-21 16:39:34 +08:00
Jason 6428e993a3 feat(proxy): flag managed-OAuth providers as routing-required
Managed-OAuth providers (github_copilot / codex_oauth / xai_oauth) need
proxy takeover to inject credentials, but the "needs routing" badge and
the switch-time warning only keyed off apiFormat — missing OAuth providers
whose upstream format is native (e.g. the new Codex xAI Grok OAuth preset
on openai_responses).

- Add isOAuthProviderType / OAUTH_PROVIDER_TYPES as the SSOT for OAuth types
- Extract providerNeedsRouting() as the authoritative predicate: managed
  OAuth always needs routing (the backend rejects these accounts' direct
  connection) regardless of apiFormat or Claude Desktop mode
- ProviderCard badges (claude / codex / claude-desktop) and the switch
  warning consume the shared predicate instead of raw apiFormat
- Gate the warning on per-app routing readiness: claude-desktop uses
  isProxyRunning (backend takeover status has no such field), other apps
  use isProxyTakeover — fixes false warnings on Desktop and missing
  warnings when the proxy runs but the app isn't taken over
- Force proxy mode for all managed-OAuth providers in the Claude Desktop
  form (lock the mode toggle), not only xai_oauth
- Add unit tests for providerNeedsRouting, the switch routing gate, and
  Desktop OAuth preset enforcement
- i18n(zh/en/ja/zh-TW): add notifications.proxyReasonManagedOAuth
2026-07-21 16:39:34 +08:00
Jason dbb5bd1537 feat(codex): xAI (Grok) OAuth managed provider with native Responses compat
Add a managed "xAI (Grok) OAuth" Codex provider that routes through the
local proxy to api.x.ai via the shared Grok CLI OAuth identity, plus the
native-Responses compatibility layer that makes Codex 0.142+ traffic work
against xAI's strict upstream serde parser.

Provider:
- codex.rs: recognize the xai_oauth placeholder in extract_auth, hard-pin
  the base URL to api.x.ai and the tool profile to native Responses
- forwarder.rs: treat xAI OAuth auth failures as non-retryable
- presets + ProviderForm/CodexFormFields: managed OAuth preset that hides
  the api key/endpoint fields and derives the provider type across apps

Native Responses compatibility (gated on is_xai_oauth, so no other
provider is affected):
- transform_codex_responses_namespace: flatten Codex's private
  namespace/plugin tool declarations into top-level function tools on the
  request; restore the flat function_call names back to {name, namespace}
  on the response (streaming and non-streaming) so the client matches its
  own namespaced tool registry
- transform_codex_responses_xai_sanitize: strip the OpenAI-backend-private
  fields xAI rejects (external_web_access, prompt_cache_retention,
  safety_identifier, the additional_tools carrier, tool_search, ...) with
  deterministic removals that keep the prompt-cache prefix stable
- wire both into the native passthrough after the request transform;
  response restore runs in a dedicated handler so the generic passthrough
  hot path is untouched

Ports the proven approach of sub2api's Grok Responses gateway. Verified
with a 4-round codex -> xAI OAuth workload: all tasks green, zero upstream
errors.
2026-07-21 16:39:34 +08:00
Jason 8dcedbc062 feat(tools): prefer xAI native Grok installer with npm fallback
Treat Grok like Claude/OpenCode: install via the official xAI installer
(POSIX shell / Windows PowerShell), keep npm as fallback, discover
~/.grok/bin, and anchor updates to `grok update` only for native installs.
2026-07-21 16:39:34 +08:00
Jason eccb296afe docs(changelog): expand unreleased Codex usage fix notes to house style 2026-07-21 16:39:34 +08:00
Jason eff1e0ccfc feat(db): rebuild Codex usage on upgrade and via maintenance action
Schema v16 wipes codex_session detail rows, _codex_session rollups and
Codex rollout cursors inside the migration savepoint (cursor deletion
uses pure shape matching so CODEX_HOME drift cannot orphan cursors);
the next session sync re-imports history from source JSONL under the
corrected importer. Fresh installs traverse the same branch as a no-op.

Add a manual "Rebuild Codex usage" maintenance action (single-flight,
hard-fail backup before reset, unconditional refresh notification even
when reimport is empty or fails after reset) with a destructive confirm
dialog, result toast and four-locale strings. Historical proxy-side
duplicate rows are intentionally left untouched; history whose source
JSONL was already deleted cannot be reconstructed.
2026-07-21 16:39:34 +08:00
Jason c9ac6efd69 fix(proxy): add stable usage keys and idempotent raw-response logging
Derive request ids from upstream envelope ids (Codex/OpenAI top-level
id, Gemini responseId, Claude message id with non-empty filtering)
scoped as session:{app_type}:{provider_id}:{id} for non-Claude sources;
Claude keeps bare session:{id} to preserve session-log convergence.

The logger now queries and conditionally writes under a single
connection guard: identical semantic replays return without writing or
notifying, session_log rows may be upgraded by proxy, and same-id
different-semantic responses land on a deterministic SHA-256 collision
fallback key instead of being overwritten. Fixes the random-UUID
INSERT OR REPLACE duplication behind #5496.
2026-07-21 16:39:34 +08:00
Jason df3e07edbb fix(codex): strip forked history via parent-rollout token-prefix alignment
Rewrite the Codex session importer: identify forks by forked_from_id or
subagent thread_spawn.parent_thread_id (conflicts defer), anchor thread
identity to the rollout filename UUID, and align child token_count
signatures against the parent rollout as a strict prefix so replayed
parent history only establishes the cumulative baseline and is never
imported as child usage. Ambiguous alignments defer without advancing
the cursor and recover once the parent appears; parent signatures are
cached per (path, cutoff).

Fixes the 3x-20x double counting behind #5335/#5433 while keeping
reference-type subagents fully counted (#5381).
2026-07-21 16:39:34 +08:00
Jason a10b569a53 feat(usage): add suspected-duplicate probe for Codex session imports
Detect codex_session rows sharing the same model/token fingerprint
within the dedup window under a different request_id. Predicates reuse
the COALESCE(data_source,'proxy') shape so the existing expression
index serves the lookup. Wired up by the Codex importer rewrite.
2026-07-21 16:39:34 +08:00
Jason eb105eae76 perf(usage): coalesce session-sync notifications and serialize sync execution
Replace per-insert usage notifications with a single notification per
sync pass, guard all sync entry points (startup backfill, 60s loop,
manual sync) behind a process-wide single-flight tokio mutex, and move
blocking file/DB work onto spawn_blocking with MissedTickBehavior::Skip.

Extend SessionSyncResult with deferredFiles/suspectedDuplicates
observability fields (saturating aggregation) and add a test seam that
counts notifications even without an injected AppHandle.
2026-07-21 16:39:34 +08:00
Jason 01fca69641 test(updater): align grok npm anchor expectation with PATH prefix
bf047990 prefixed anchored npm invocations with their sibling bin
directory but missed updating the grok test expectation.
2026-07-21 16:39:34 +08:00
Jason db44484726 feat(codex): add xAI (Grok) native Responses preset
API-key preset pointing at https://api.x.ai/v1 with wire_api =
"responses": xAI serves the Responses API natively, so no route
takeover conversion is needed. Ships a grok-4.5 catalog entry
(500K context, parallel tools, text+image) so the model shows up
under Codex's strict catalog parsing. This is deliberately not a
managed-account preset: providerType stays absent, pinned by a
regression test alongside the Claude-side xAI OAuth presets.
2026-07-21 16:39:34 +08:00
Jason cdf0ee3491 fix(pricing): add missing grok-4.5 pricing row to seed
grok-4.5 is the default model of the xAI presets but had no pricing
row, so usage dashboards recorded zero cost for it. Official list
price: $2 input / $6 output / $0.50 cached read per 1M tokens.
Seed-only change: the startup INSERT OR IGNORE backfills existing
databases; no repair entry needed for a missing row.
2026-07-21 16:39:34 +08:00
Jason 09101e2a2d docs(xai): document Grok OAuth setup and disclose client identity reuse
Document the xAI OAuth flow (discovery, device, token, and API endpoints)
in the zh/en/ja manuals. State explicitly that the integration reuses the
public OAuth client identity and scopes registered for the official Grok
CLI rather than a CC Switch registration, and strengthen the shared
account-backed proxy note back to an explicit warning covering account
restriction and suspension risk.
2026-07-21 16:39:34 +08:00
Jason e9317f476e feat(xai): add Grok account management UI with four-locale strings
Add XaiOAuthSection (device-code login, account list, default selection)
backed by the generalized useManagedAuth hook, wire it into the Auth
Center and both Claude provider forms, and expose model fetching for
signed-in accounts.

- Accounts requiring re-auth stay visible in the account selector as
  disabled items with an expired badge instead of silently vanishing.
- Auth status refetches periodically so a revoked refresh token surfaces
  without reloading the panel.
- All strings ship in zh/en/ja/zh-TW; a locale coverage test pins every
  required key in all four locales.
2026-07-21 16:39:34 +08:00
Jason 615c99c62b feat(xai): add Grok OAuth presets for Claude and Claude Desktop
Register the xai_oauth provider type constant and add xAI (Grok) presets
targeting grok-4.5. The Desktop preset routes branded claude-* model IDs
to grok-4.5 upstream so it passes Desktop's third-party model validation.
2026-07-21 16:39:34 +08:00
Jason a35209a6e7 feat(xai): add Grok OAuth device-flow backend and proxy routing
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.
2026-07-21 16:39:33 +08:00
Jason c4795e98ff fix(codex): backfill parser-required catalog fields from static template
Codex >= 0.144.5 requires supports_reasoning_summaries in every external
model catalog entry and rejects the whole file at startup when it is
missing, which bricks both the CLI and the desktop app.

The ProxyChat catalog template is cloned from dynamic sources first
(models_cache.json, then `codex debug models --bundled`). models_cache.json
is shared by every Codex install on the machine and each version serializes
its own ModelInfo shape, so the cache's field set follows whichever process
wrote it last and cannot be assumed to satisfy the current external-catalog
schema. Deleting the generated catalog never helped: any provider save or
switch regenerated it from the same stale template.

Backfill parser-required fields (explicit whitelist, currently only
supports_reasoning_summaries) from the bundled static template when the
dynamic template lacks them. Optional capability fields are deliberately
NOT backfilled so their missing-means-parser-default semantics survive,
and existing values always win.
2026-07-21 16:39:33 +08:00
Jason 6fddcaa97c feat(pricing): add bare k3 alias for Kimi For Coding plan
The Kimi For Coding subscription reports its K3 model under the short
id `k3` (no `kimi-` prefix), whereas the regular Moonshot API still uses
the full `kimi-k3`. Seed a `k3` alias row right after `kimi-k3` at the
same official list price ($3/M in, $15/M out, $0.30/M cache read) so
coding-plan usage is priced correctly. Mirrors the existing
hunyuan-hy3/hy3 bare-alias precedent.

Pricing is matched by exact model id, so this does not touch the
`kimi-for-coding` endpoint alias (intentionally unpriced, subscription
semantics) nor risk prefix mismatches. Seed-only, no schema bump.
2026-07-21 16:39:33 +08:00
Jason aa0e441d07 docs: add Claude Code Codex routing guide
Document API key and ChatGPT OAuth routing flows for using Codex models in Claude Code, including configuration screenshots and troubleshooting guidance.
2026-07-21 16:39:33 +08:00
Jason 17b053ed94 fix(updater): resolve Node for anchored npm commands
Prefix anchored npm invocations with their sibling bin directory so env-based Node shebangs work under a minimal GUI PATH. Cover Codex repair commands and add a regression test.
2026-07-21 16:39:33 +08:00
Jason e356fc6efc fix(openclaw): price preset models at official list prices in $/M
The cost field on OpenClaw preset models is denominated in USD per
million tokens (as the Claude entries already were), but 15 entries
carried $/1K-style decimals or stale values. Reprice everything at the
model's official list price, mirroring the built-in pricing table, so
usage is always valued at standard rates regardless of channel:

- glm-5.1 (Zhipu CN/EN, ModelScope): 1.4/4.4, cache read 0.26
- qwen3.5-plus: 0.26/1.56, cache read 0.052
- kimi-k2.7-code and the kimi-for-coding alias (K2.7 Code per official
  docs): 0.95/4, cache read 0.19
- MiniMax-M2.7 (official + SiliconFlow): 0.3/1.2, cache read 0.06,
  cache write 0.375
- KAT-Coder-Pro: 0.3/1.2, cache read 0.06
- LongCat-2.0: 0.75/2.95, cache read 0.015
- Ling-2.5-1T: 0.56/2.24 (official CNY 4/16 at ~7.14)
- deepseek-v4-pro: 0.435/0.87 (old 1.68/3.36 was CNY 12/24 left
  unconverted), cache read 0.003625
- kimi-k2.5 (Nvidia NIM): 0.6/3, cache read 0.1

Subscription-plan endpoints and free-tier hosts intentionally show
list prices too, so plan users can see the standard value of their
usage.
2026-07-21 16:39:33 +08:00
Jason f20458229d feat(presets): add Kimi K3 to Kimi open-platform presets
Add kimi-k3 (1M context, 131K max output) to the Kimi provider preset
model lists for Codex, Hermes, OpenClaw, and OpenCode, appended after
kimi-k2.7-code so the catalog-first-row default backfill is unchanged.
The Kimi For Coding plan presets keep the kimi-for-coding endpoint
alias and are intentionally untouched.
2026-07-21 16:39:33 +08:00
Jason 2bfecead4f feat(pricing): add Kimi K3 to built-in model pricing table
Seed kimi-k3 at the official list price ($3/M input, $15/M output,
$0.30/M cache read; released 2026-07-16 with a 1M context window).
Cross-checked against the Moonshot pricing docs, OpenRouter, and
models.dev. Seed-only change: ensure_model_pricing_seeded picks it up
on next launch, no schema version bump needed.
2026-07-21 16:39:33 +08:00
Jason 6274705848 feat(logging): capture frontend errors to disk with structured redaction
Frontend half of the logging overhaul.

Capture:
- Add a global error boundary plus window error/unhandledrejection
  handlers that persist renderer errors via tauri-plugin-log (new
  @tauri-apps/plugin-log dependency + log:default capability), so a
  white-screen crash leaves a trace.

Redaction (two layers over every logged value):
- Structured serializer: redact by sensitive property name (arrays and
  nested values included) and by opaque value shape.
- Text layer, the universal final pass: redact URL credentials/query,
  auth schemes, known secret shapes, and named-key array/object values.
  The container rule closes prefix+JSON, double-encoded, and POJO-error
  bypasses regardless of which entrypoint the structured data came from.
- Render Errors as redacted message + native stack across V8 and WebKit
  (macOS/Linux) without depending on a single stack format.
- Bound all inputs and drop oversized JSON to keep the UI responsive.

i18n:
- zh / en / ja / zh-TW for the new log settings and error text.
2026-07-21 16:39:33 +08:00
Jason 22d2872c33 feat(logging): persist diagnostics across restarts and redact secrets
Backend half of the logging overhaul.

Retention:
- Keep the last 4 rotated files at 20MB each and stop deleting logs on
  startup, so a crash's prior-run logs survive a restart.
- Apply the persisted log level right after plugin registration instead
  of at the end of setup.
- Bound crash.log with size-based rotation (5MB x 2).

Secret redaction on every log path:
- Proxy: strip userinfo/query from upstream URLs (keep path for
  diagnostics), exact-match redact known auth.api_key/access_token,
  classify request/response bodies instead of logging them, header
  allowlist.
- Redact the Gemini `?key=` in cache-trace endpoints.
- Omit MCP custom-field values (headers may carry tokens).
- Redact deeplink and model-fetch URLs before logging.

Docs:
- FAQ points users to the persistent crash.log for support workflows.
2026-07-21 16:39:33 +08:00
Jason 7f028632b5 feat(presets): restore SudoCode.us to coexist with SudoCode.chat sponsor
The legacy sudocode.us provider was replaced in place by the sudocode.chat
sponsor (618723b4) because the two unrelated companies share the SudoCode
name. Bring the legacy provider back and distinguish the two by their
domain suffix.

- Rename the sponsor preset to "SudoCode.chat" across its six clients;
  partner status, promotion copy (key "sudocode"), and icon unchanged
- Re-add "SudoCode.us" across all seven clients (including the previously
  removed Gemini entry) with its pre-replacement config: gpt-5.5,
  sudocode.us endpoints, sudocode.run fallback candidates
- Mark SudoCode.us as partner (isPartner) without a partnerPromotionKey:
  promo copy is pending, and the promo box only renders when a key exists
- Give the SudoCode.us Hermes entry a distinct slug (sudocode_us) so both
  providers can coexist in the additive ~/.hermes/config.yaml
- Restore the legacy logo from git history as sudocode-us.png and register
  it in the hand-maintained icon index and metadata
2026-07-21 16:39:33 +08:00
Jason 3bc828aecc fix(windows): eliminate console flash and UI freeze on provider switch
Switching providers or toggling proxy takeover on Windows flashed a
transient cmd window and froze the UI for up to a couple of seconds.
Three fixes:

- Spawn `codex debug models --bundled` with CREATE_NO_WINDOW so the
  console child of the GUI-subsystem app (npm's codex.cmd runs via
  cmd.exe) no longer opens a visible window.
- Cache the ProxyChat model catalog template in a process-wide OnceCell:
  only successful loads are cached, failures stay retryable, so the
  Codex CLI starts at most once per app run instead of on every switch.
- Run switch_provider via spawn_blocking: sync Tauri commands execute on
  the main thread, so the blocking catalog generation (and the
  block_on'd per-app switch lock) froze the UI. Concurrency is already
  serialized by ProxyService::lock_switch_for_app.
2026-07-21 16:39:33 +08:00
Komi 08710d51fc fix(proxy): default Codex tool parameters to object schema (#5315)
Normalize function tool parameters on the Codex Responses -> Chat Completions bridge: default null/missing parameters (direct and nested function forms) to {"type":"object","properties":{}}, coerce explicit type:null, and add a root type for top-level oneOf schemas so strict OpenAI-compatible upstreams (DeepSeek etc.) no longer reject built-in Codex tools like codex_app__automation_update. Also hardens the Codex -> Anthropic tool path with the same object-type guarantee and adds regression coverage for all reported shapes.
2026-07-21 10:13:00 +08:00
ka79376046 613fef70bc fix(codex-chat): attach reasoning forward for responses bridge (#5508)
* fix(codex-chat): attach reasoning forward for responses bridge

* fix(codex-chat): preserve tail reasoning after embedded reasoning

* docs(codex-chat): align trailing reasoning comments
2026-07-19 11:39:29 +08:00
滅ü 997be22bfa fix(tray): detect system locale for first-run language instead of hardcoding zh (#4355)
The tray menu used a hardcoded zh (Simplified Chinese) fallback whenever
settings.language was unset (i.e. first install). On systems whose UI language
resolves to Traditional Chinese / Japanese / English via the frontend's
navigator-based detection, the tray therefore showed Simplified Chinese until
the user manually switched language once.

Detect the OS locale via the sys-locale crate and map it to a supported tray
language, mirroring the frontend getInitialLanguage precedence
(zh-TW/HK/MO/Hant -> zh-TW, other zh -> zh, ja -> ja, en -> en, otherwise zh).
This keeps the tray consistent with the UI from the first launch with no timing
window. An explicitly stored settings.language still takes precedence, so user
choices are never overridden. Adds unit tests for the locale mapping.

Co-authored-by: LaiYueTing <LaiYueTing@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
2026-07-17 21:35:10 +08:00
Allen Xu edea624a27 fix(skills): preserve deleted default repositories (#5356)
* fix(skills): preserve deleted default repositories

* fix(skills): persist default repo initialization state
2026-07-17 15:51:29 +08:00
Thefool 1c0ee0c58a feat(grokbuild): add first-class Grok Build support (#5453)
* feat(grokbuild): add backend integration

* feat(grokbuild): add provider and management UI

* test(grokbuild): cover configuration and integrations

* fix(grokbuild): address backend review feedback

* fix(grokbuild): complete UI review feedback

* feat(grokbuild): add CLI lifecycle management

* fix(grokbuild): align provider icon fallback

* test(grokbuild): cover provider icon persistence
2026-07-17 15:50:50 +08:00
zayoka f6e37ed994 fix(ci): run backend checks on Windows/macOS and repair platform-gated tests (#5138)
* fix(ci): run backend checks on Windows/macOS and repair platform-gated tests

The backend CI job ran only on ubuntu-22.04, so every #[cfg(target_os =
"windows")] / macOS path — tests and clippy lints alike — was never
compiled or run. As a result main shipped 8 failing Windows unit tests and
a hard `cargo clippy -- -D warnings` failure on Windows, all invisible to
CI because it only builds and tests Linux.

Changes:

- ci: run the backend job on a {ubuntu-22.04, windows-latest, macos-latest}
  matrix (fail-fast: false); gate the apt install step on runner.os ==
  'Linux' and use `shell: bash` for the dist placeholder so it works on all
  three runners.

- misc.rs: fix 6 stale `anchored_upgrade_windows` tests. Commit 5092fe51
  removed codex from `prefers_official_update`, so codex now anchors to the
  package manager with no `codex update ||` prefix; update the five
  package-manager expectations accordingly, and retarget the no-sibling
  "official-update-without-fallback" test to `claude` (still in the list) so
  that branch stays covered instead of being silently dropped.

- codex_state_db.rs / codex_history_migration.rs: the two sqlite_home tests
  built TOML basic strings from Windows paths, whose backslashes are invalid
  escape sequences and made the override fail to parse; switch to TOML
  literal (single-quoted) strings so the path is taken verbatim.

- clippy (13 Windows-only warnings): move `use std::io::Write` into the
  #[cfg(unix)] block that actually uses it; convert 3 unneeded `return`s in
  Windows-gated tail positions to expressions; mark 9 POSIX-shell helpers
  #[cfg_attr(windows, allow(dead_code))] since they are used only by the
  macOS/Linux terminal launchers.

Verified locally (Windows 11, Rust 1.95.0):
  cargo test --lib                        → 1748 passed; 0 failed (was 1740/8)
  cargo clippy -- -D warnings             → clean (was 13 errors)
  cargo fmt --check                       → clean

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(skills): resolve home via get_home_dir so tests isolate on Windows

services/skill.rs called dirs::home_dir() directly in five places. On
Windows dirs::home_dir() resolves through the Known Folder API and
ignores HOME/USERPROFILE, so the CC_SWITCH_TEST_HOME override set by the
test harness never applied: the skill_sync integration tests scanned the
runner's real user profile, found no skills, and failed (the first panic
then poisoned the shared test mutex, cascading to all seven). On Unix
the harness also sets HOME, which dirs::home_dir() honors, so the suite
passed there by accident.

Route all five call sites through crate::config::get_home_dir(), the
crate-wide home resolver that prefers CC_SWITCH_TEST_HOME. The three
now-unreachable GET_HOME_DIR_FAILED error branches are dropped:
get_home_dir() is infallible, matching every other config-dir resolver.
Production behavior is unchanged (CC_SWITCH_TEST_HOME is unset outside
tests, so get_home_dir falls through to dirs::home_dir).

Add a regression test that discriminates on every platform by pointing
CC_SWITCH_TEST_HOME at a temp dir different from $HOME; it is marked
#[serial_test::serial] to stay mutually exclusive with the other tests
mutating the same process-global variable. Verified the test fails
against the old code on macOS with the same failure mode as Windows CI.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
2026-07-15 10:33:22 +08:00
Jason 1cc52c7e73 docs(readme): add SubRouter sponsor entry across four locales 2026-07-14 22:46:19 +08:00
SaladDay 6d316c0bda fix(codex): preserve streamed tool call identity and order (#5310)
Keep non-empty call IDs across continuation deltas and release parallel tool calls in Chat index order when identity fields arrive late. Preserve valid sparse and later calls during finalization.
2026-07-14 20:01:10 +08:00
Ryan2128 9ca1a41f58 fix: normalize function parameters type to "object" for strict OpenAI-compatible providers (#4706)
* fix: normalize function parameters type to "object" for strict OpenAI-compatible providers

Some Responses tools carry parameters with `type: null` (e.g.
codex_app__automation_update), causing HTTP 400 from strict
OpenAI-compatible providers like DeepSeek that require
`{"type": "object", "properties": {...}}`.

This adds normalize_function_parameters() to ensure the type
field is always "object" in both branches of
responses_function_tool_to_chat_tool.

Closes #4705

* style: fix cargo fmt issues

* fix: handle null function parameters
2026-07-14 19:23:41 +08:00
Jason c8b0d60c2d docs(guides): add Codex + Claude local routing guide in three languages
Step-by-step guide for the v3.17.0 native Anthropic Messages upstream:
add a custom Codex provider pointed at a Claude-family /v1/messages
gateway, pick the anthropic upstream format, enable local routing, and
verify the chain. Covers the auth-field choice (Bearer vs x-api-key),
the Claude Code impersonation toggle, the 8192 max-output fallback, and
a FAQ entry for gateways that restrict keys to Claude Code only.

Includes four UI screenshots captured from the real 3.17.0 app with
sample data, and links the guide from the v3.17.0 release notes in all
three languages.
2026-07-14 11:53:55 +08:00
Jason 7e73a1ffb2 fix(i18n): add missing proxyReasonAnthropicMessages key across locales
The routing-required toast for Anthropic-format Codex providers only had
a Chinese defaultValue in code; non-Chinese locales showed a mixed-language
message. Add the reason string to zh/en/ja/zh-TW following the phrasing of
the sibling proxyReason* keys.
2026-07-14 11:53:39 +08:00
Jason 3d176b98cc docs(release): add v3.17.0 release notes 2026-07-13 23:41:54 +08:00
Jason c154d30b7a chore(release): v3.17.0 2026-07-13 21:24:58 +08:00
Jason 6eb217b242 revert(proxy): drop the 1-hour cache TTL option and TTL-bucketed write accounting
The forced 1-hour cache_control TTL (schema v14) was a mistake. Injected
breakpoints return to Anthropic's standard 5-minute TTL, caller-owned
markers are preserved verbatim instead of having their TTLs rewritten,
and the 5m/1h cache-write buckets are removed from usage parsing and
pricing (back to the single aggregate cache-creation rate). The cache
TTL selector is removed from the rectifier settings panel along with its
i18n keys in all four locales.

SCHEMA_VERSION returns to 13: the unreleased cache_creation_1h_tokens
column and the v13->v14 migration are removed. The feature never shipped
in a release and the introducing commit was never pushed, so no
databases were stamped v14 outside this machine (local DB verified at
user_version 13).
2026-07-13 17:55:33 +08:00
Jason ac52c851bf fix(codex): infer image capability for generated catalogs and resync takeover live on save
Mapped GPT models were rejected by Codex clients with "model does not
support image inputs". Two root causes:

- Catalog entries for native-Responses/Anthropic providers cloned a
  template whose input_modalities defaulted to ["text"], so every mapped
  model was advertised text-only. model_catalog_json replaces Codex's
  built-in model table wholesale, and both the TUI and the IDE extension
  block images pre-send when the current model is found without "image".
- Editing the current Codex provider during proxy takeover only refreshed
  the DB backup, so removing the mapping left a stale model_catalog_json
  pointer (and its text-only catalog file) active in live config.

Changes:

- New shared model_capabilities module: explicit row declaration first,
  then a confirmed text-only registry (exact tail match only — prefix
  matching removed, variants enumerated so future -vl/-vision models fail
  open), everything else unknown.
- Catalog generation writes input_modalities from that inference for all
  tool profiles: unknown models fail open to ["text","image"]; only
  confirmed text-only models are advertised as ["text"], giving users a
  clear client-side prompt instead of silent image stripping.
- Live catalog reverse-import collapses modalities that match current
  inference, so registry corrections are not frozen into hidden row
  overrides and the rectifier's heuristic opt-out keeps working.
- Saving the current Codex provider while takeover owns live now
  re-projects the live config (mirrors the hot-switch path), so mapping
  edits and removals take effect immediately.
- Media rectifier delegates to the shared module; its preflight toggle is
  documented (4 locales) as proxy-request-only, never affecting catalog
  capability declarations.
2026-07-13 16:48:22 +08:00
Jason 618723b42f feat(presets): promote SudoCode to paid sponsor across six clients
Replace the legacy sudocode.us provider (a name collision) with the new
paid sponsor SudoCode on sudocode.chat, updated in place across six
clients: Claude Code, Codex, Claude Desktop, OpenCode, OpenClaw, Hermes.

- Switch endpoints to api.sudocode.chat, add isPartner +
  partnerPromotionKey, and add four-locale partnerPromotion copy
- Claude Code / Desktop use direct passthrough (no model mapping)
- Codex / OpenCode / OpenClaw / Hermes use gpt-5.6-sol
- Remove the Gemini entry (outside sponsor scope)
- Replace the provider icon with the new brand PNG
2026-07-13 16:48:22 +08:00
Jason af58740bcd fix(proxy): align Codex OAuth client identity
ChatGPT's Codex backend routes model availability by the originator and version header pair. Requests identifying as cc-switch without a version were assigned to a cohort where gpt-5.6-luna resolved to an unavailable internal engine, resulting in a misleading 404 Model not found response.

Identify takeover requests as codex_cli_rs and send version 0.144.1, satisfying luna's minimal_client_version requirement of 0.144.0. A direct HTTP A/B test confirmed the existing headers returned 404 while the aligned identity completed successfully, so no WebSocket transport workaround is required.
2026-07-13 16:48:22 +08:00
Jason 99573d2242 refactor(presets): pin context window values instead of form fields
Drop the Max Context Tokens / Auto Compact Window template inputs from
the Codex and Kimi For Coding presets and hardcode the values in the
preset env (372000 / 262144). The rare user who wants different numbers
can edit them directly in the JSON editor.

Both keys stay on purpose: the compact window resolves as
min(model window, value), so matching the window is behavior-neutral
today, but the explicit env pins compaction locally against
remote-config experiments dialing it down.
2026-07-13 16:48:21 +08:00
Jason 940ddd332b feat(kimi): declare the 256K context window for Kimi For Coding
Claude Code caps unknown model ids at 200K, so the preset's standalone
CLAUDE_CODE_AUTO_COMPACT_WINDOW=262144 was clamped by min(window, value)
and never took effect. Pair it with CLAUDE_CODE_MAX_CONTEXT_TOKENS and
route the endpoint's kimi-for-coding alias on every tier, since both
context envs are ignored for claude-* prefixed model ids.

Saved providers get the same context defaults injected into effective
live settings at switch time, with a mirror-inverse strip on backfill so
injected values never harden into provider config. Explicit user values
always win over the injected defaults.
2026-07-13 16:48:21 +08:00
Jason 31ee42854a feat(pricing): seed gpt-5.6 alias rows and 1.25x cache-write rates
- Add the bare gpt-5.6 row (official Sol alias) plus effort-suffix variants
  mirroring the gpt-5.5 accounting shape, priced at Sol rates
- The GPT-5.6 family charges cache writes at 1.25x the uncached input rate
  (new for OpenAI; earlier GPT models keep their correct zero): set
  6.25 / 3.125 / 1.25 for Sol / Terra / Luna across seed rows
- Repair earlier zero-value gpt-5.6 tier seeds in existing databases,
  matching only untouched rows so user-customized prices survive
2026-07-13 16:48:21 +08:00
Jason 5c39dfbfbe feat(codex): declare gpt-5.6 context window for Claude Code takeover
- Update Codex OAuth presets to the gpt-5.6 family (haiku -> gpt-5.6-luna)
  and bump the custom Codex template default model
- Inject CLAUDE_CODE_MAX_CONTEXT_TOKENS / CLAUDE_CODE_AUTO_COMPACT_WINDOW
  (372000, the ChatGPT Codex catalog window with a ~353K effective budget,
  openai/codex#31860) into effective live settings so Claude Code stops
  assuming a 200K window and compacts before the upstream rejects the prompt
- Gate the injected defaults on every configured model being gpt-5.6*:
  gpt-5.5's upstream catalog oscillates between 272K and 372K and must not
  inherit them; explicit user values always win
- Strip the injected values on switch-away backfill (mirror-inverse of the
  injection conditions) so program defaults never harden into per-provider
  explicit values, and keep both keys out of the shared common-config snippet
2026-07-13 16:48:21 +08:00
Jason f15184edb0 feat(codex): expose official routing and restore the built-in provider
Let users switch between the built-in OpenAI provider and third-party Codex providers directly from the provider panel while takeover mode remains active.

Centralize the frontend capability predicate so only the fixed codex-official seed receives native-login routing support, and keep copied UUID-based official entries clearly marked as unsupported.

Add an idempotent backend command that recreates the deleted official seed, wire it into the add-provider flow, refresh localized guidance, and add mutation and provider-action regression coverage.
2026-07-13 16:48:21 +08:00
Jason f2c6d48e19 fix(providers): skip reachability probes for official OAuth entries
Do not derive unauthenticated health-check targets from runtime adapter defaults for official providers. Batch checks now skip official entries, and direct base-URL resolution fails explicitly instead of probing first-party endpoints without credentials.

Cover the Codex official provider in the stream-check regression tests so future adapter changes cannot silently reintroduce these probes.
2026-07-13 16:48:21 +08:00
Jason 51d6c458ff feat(codex): route native ChatGPT sessions through proxy takeover
Allow the built-in Codex official provider to participate in takeover mode while preserving Codex's native OAuth or API-key credentials instead of persisting them into provider records.

Project official routing into a dedicated TOML provider, normalize inline tables, clean stale managed placeholders, and fail closed when the live configuration cannot be transformed safely.

Validate forwarded authorization, make official 401/403 responses non-retryable, avoid circuit-breaker pollution, and share the first-party ChatGPT endpoint across the Codex and Claude adapters.
2026-07-13 16:48:21 +08:00
Jason 13e7c1fcc4 fix(usage): account for Anthropic cache write TTLs
Parse and retain Anthropic's ephemeral 5-minute and 1-hour cache-creation token buckets while preserving the existing aggregate cache-write metric for compatibility.

Price 1-hour writes at the documented premium relative to the configured 5-minute write rate, clamp inconsistent provider details safely, and include TTL buckets in usage diagnostics.

Persist 1-hour cache-write tokens with schema version 14 so zero-cost backfills and later pricing updates retain the original TTL semantics. Keep session import paths compatible through zero-valued detail fields.
2026-07-13 16:48:21 +08:00
Jason b9263a8040 fix(cache): strengthen prompt cache breakpoint injection
Honor both the optimizer master switch and the cache-injection sub-switch before mutating native optimizer requests.

Use the available four-breakpoint budget across tools, system content, the latest cacheable message, and an older user anchor for long tool-heavy conversations.

Preserve caller-owned breakpoint limits, avoid thinking blocks as cache targets, normalize configured TTLs, and add regression coverage for disabled optimization and long histories.
2026-07-13 16:48:21 +08:00
Jason 650905af2c fix(proxy): harden Responses and Anthropic protocol bridges
Fail closed on HTTP 2xx failure envelopes and pre-output SSE failures so semantic upstream errors can trigger failover instead of becoming empty successful replies.

Finalize incomplete and truncated streams explicitly, handle clean EOF and whole JSON responses, and keep tool-call stop reasons and terminal event ordering consistent.

Preserve structured tool results, URL images, documents, system roles, and signed thinking across both conversion directions. Drop incomplete historical tool calls safely and classify malformed completed arguments as non-retryable client requests.

Keep Codex-to-Anthropic prompt caching enabled by default while honoring the dedicated cache-injection switch.
2026-07-13 16:48:21 +08:00
Jason a078b4b207 feat(proxy): session-based prompt_cache_key routing for Codex Chat bridge
Codex always sends prompt_cache_key in its Responses requests, but the
Responses -> Chat Completions conversion dropped it, breaking session
cache affinity on upstreams that route by key (e.g. Kimi Coding).

- Re-inject prompt_cache_key after conversion in the forwarder: an
  explicit client key wins, otherwise a client-provided session ID;
  generated per-request UUIDs are never sent upstream.
- Provider-aware gating: "auto" enables only known-compatible upstreams
  (api.openai.com, api.kimi.com/coding) because strict gateways reject
  unknown fields with HTTP 400 (e.g. Fireworks); an advanced
  Auto/Enabled/Disabled override is available on the Codex form in all
  four locales.
- Kimi For Coding preset opts in explicitly.
2026-07-13 16:48:21 +08:00
Jason 0e563b50c5 fix(cache): surface unsupported breakpoint counts
Warn when caller-provided cache breakpoints already exceed the supported total of four while preserving the original markers and upgrading their TTLs.

Clarify that automatic injection is governed by the remaining breakpoint budget, and add regression coverage proving excess caller markers are never deleted or reordered.
2026-07-13 16:48:21 +08:00
Jason 27ce0a519c fix(proxy): harden Responses reasoning and tool-call conversion
Round-trip encrypted Responses reasoning items through bridge-owned Anthropic thinking blocks, discard orphaned reasoning-only history, and consume the official reasoning text event vocabulary.

Track concurrent streaming items by stable IDs and output indexes, reuse a dedicated fallback block for keyless legacy reasoning, recover tool arguments from done events, and close blocks in protocol order.

Normalize empty or incomplete non-streaming tool arguments, reject malformed completed calls, and persist upstream usage before returning terminal conversion errors.
2026-07-13 16:48:21 +08:00
Jason f991726ff0 fix(usage): account for cache-write tokens across schema versions
Parse cache_write_tokens from OpenAI usage details and preserve cache creation data across Chat, Responses, and Anthropic conversion paths.

Add explicit input-token semantics to request logs and rollups so legacy rows subtract cache reads only while new total-inclusive rows subtract both cache reads and writes. Migrate v12 databases, normalize rollups to fresh input, and cover historical backfill behavior with regression tests.
2026-07-13 16:48:21 +08:00
Jason 06039540ff refactor(health-check): remove per-provider test config 2026-07-13 16:48:21 +08:00
Jason 442799879d feat(profiles): add setting to toggle project switcher on main page
Add a show/hide toggle for the header project profile switcher under
the Homepage Display section in settings. Defaults to visible so
existing users keep the current behavior.
2026-07-13 16:48:21 +08:00
Jason 7479d10db7 feat(codex): add default model field to provider form
Expose the top-level `model` key of config.toml as an editable field in
the Codex provider form, so users can switch to newly released models
(e.g. gpt-5.6) without waiting for a preset update — preset updates only
affect newly added providers, existing ones keep their saved TOML.

The field syncs bidirectionally with the TOML editor (mirroring the
base-URL pattern), suggests models from the mapping catalog plus the
provider's /models endpoint, and offers a one-click "add to mapping"
action when the value is outside a non-empty catalog. Hidden for
official providers.

Details:

- Save-time catalog sync now only backfills the first mapping row into
  the top-level model when the field was left empty, so the explicit
  field wins over the implicit row-0 sync
- Escape model names (and base_url) with TOML basic-string escaping and
  strip control characters from field input: /models ids are remote
  data and unescaped interpolation could inject config.toml lines
  (e.g. a forged [mcp_servers.*] command)
- The strict model-line matcher now recognizes escaped output, empty
  strings and single-quoted literals, keeping extract/set round-trips
  stable; deliberately no key-only loose matching (it would misfire on
  assignment-looking text inside multiline strings)
- Invalidate the fetched model list whenever the request identity
  changes (base URL, full-URL toggle, API key, custom UA) and discard
  in-flight responses via a sequence guard, so the dropdown never shows
  a previous provider's models
- i18n for zh/en/ja/zh-TW
2026-07-13 16:48:21 +08:00
Jason 62e44c4838 feat(pricing): seed Tencent Hunyuan Hy3 pricing
Add Hunyuan Hy3 (released 2026-07-06, 256K context) to seed_model_pricing
under both the hunyuan-hy3 and hy3 ids, since the upstream billing id isn't
yet confirmed and one of the two should match logged usage.

Prices come from Tencent's launch-day list price (CNY 1 / 4 / 0.25 per 1M
input / output / cache-hit), converted at 1 USD ~= 7.14: 0.14 / 0.56 / 0.035.
cache_write is "0" (no published write rate). Hy3 actually uses input-length
tiered billing (<16K / 16-32K / >=32K); the single-price table holds the
lowest tier, so long-context requests are under-billed until this is revisited
against the official billing page. New model, so seed only.
2026-07-13 16:48:21 +08:00
Jason a7b4dd9405 feat(pricing): seed GPT-5.6 Sol/Terra/Luna pricing
Add the three GPT-5.6 tiers to seed_model_pricing, cross-checked against
OpenAI's official pricing page and OpenRouter:

  gpt-5.6-sol    5 / 30 / 0.50 (cache read)
  gpt-5.6-terra  2.50 / 15 / 0.25
  gpt-5.6-luna   1 / 6 / 0.10

cache_write is "0": OpenAI publishes only a cached-input (read) rate and no
separate cache-write price, matching the gpt-5.x family convention. Base ids
only for now — effort-suffix variants are deferred until the upstream logging
suffix set for 5.6 is confirmed. New model, so seed only (no repair branch,
no SCHEMA_VERSION bump).
2026-07-13 16:48:21 +08:00
风少1227 c6197ae324 fix(proxy): inject a single auth placeholder on managed Claude takeover (#5095)
Switching the Claude provider from a third-party (DeepSeek/MiMo/...) to a
managed Codex provider wrote both ANTHROPIC_API_KEY and
ANTHROPIC_AUTH_TOKEN as PROXY_MANAGED into ~/.claude/settings.json,
making Claude Code warn "Both ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY
set - auth may not work as expected" on every run.

The double key is an accretion artifact: the ManagedAccount takeover
policy originally injected only ANTHROPIC_API_KEY (Copilot, #1049), and
the Codex fix that preserved ANTHROPIC_AUTH_TOKEN to avoid the login
prompt (#3784 / PR #3789) added the token on top without removing the
API key insert.

Inject exactly one placeholder instead: AUTH_TOKEN for Codex-managed
providers (keeps the #3784 fix), API_KEY for Copilot (keeps the #1049
behavior). Local-proxy auth is unaffected - the forwarder resolves the
PROXY_MANAGED placeholder from either header.

Updates the three Codex takeover tests to assert API_KEY is absent and
adds a regression test for the exact third-party -> Codex switch from
the report.

Fixes #4919
2026-07-12 21:47:45 +08:00
白喵喵喵喵 f39d463c44 fix(usage): 修复 Codex 子代理使用量未计入统计 (#5187)
* fix(usage): count Codex subagent session tokens

* fix(usage): address Codex subagent review feedback

* fix(usage): preserve Codex migration history

* fix(usage): harden Codex subagent migration

* fix(usage): reimport late Codex subagents

* fix(usage): guard late Codex rollup reimport

* refactor(usage): simplify Codex subagent accounting fix
2026-07-11 20:11:14 +08:00
Komi ded0b63a8e fix: handle missing provider keys and tool schema types (#5069)
* fix: handle missing api keys and tool schema types

* fix: preserve nested tool schemas

---------

Co-authored-by: Jason <farion1231@gmail.com>
2026-07-11 16:57:46 +08:00
v2v 6245caa6c5 Fix/opencode known field editors (#2907)
* fix: add opencode model limit editor

* fix: add opencode headers editor

* style: refine opencode advanced field layout

* fix: preserve valid opencode headers

* fix: preserve opencode extra options and sync translations

---------

Co-authored-by: wzk <wx13571681304@outlook.com>
Co-authored-by: Jason Young <44939412+farion1231@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
2026-07-11 16:38:00 +08:00
Dawn 50270d5e42 fix: exclude Fable model env from Claude common config (#4272) (#5206)
* Add test for Fable model env key exclusion

Add regression test to ensure Fable model env keys are excluded from common config.

* Add Fable model to CLAUDE_MODEL_OVERRIDE_ENV_KEYS

* Add files via upload

* Delete .github/workflows/build-windows-unsigned.yml
2026-07-11 10:11:57 +08:00
Yeeyzy 99e11e0851 feat(codex): support native Anthropic Messages protocol as upstream (#5071)
* feat(codex): support native Anthropic Messages protocol as upstream

Allow gateways that only expose the native Anthropic Messages protocol
(/v1/messages) to be used by Codex: the local proxy performs bidirectional
request/response/streaming conversion between Responses and Anthropic.

Backend:
- Add two conversion modules: transform_codex_anthropic / streaming_codex_anthropic
- codex.rs: add routing detection and auth: ANTHROPIC_AUTH_TOKEN→Bearer (default),
  ANTHROPIC_API_KEY→x-api-key, mutually exclusive
- handlers.rs: add handle_codex_anthropic_to_responses_transform
- forwarder.rs: support optional Claude Code client fingerprint impersonation
  (User-Agent / anthropic-beta / x-app / system prompt first-line injection)
  and /responses→/v1/messages rewriting
- codex_config: the anthropic format reuses the NativeResponses profile to strip
  custom tools
- ProviderMeta: add impersonateClaudeCode

Frontend:
- CodexApiFormat: add "anthropic"; the form adds auth field selection and an
  impersonation toggle
- Add en/ja/zh/zh-TW copy

Robustness:
- Downgrade when tool history / forced tool_choice conflicts with extended
  thinking, avoiding upstream 400s
- Emit cache_creation_input_tokens in usage and use saturating_add to guard
  against overflow
- Append a unique suffix to non-streaming/streaming output-item ids to avoid
  multi-segment text/thinking overwriting each other

* fix(codex): harden Anthropic bridge against empty text blocks and truncated streams

- Drop empty/whitespace-only text content blocks when rebuilding Anthropic
  messages from Responses history; Anthropic 400s on empty text blocks (e.g. an
  empty assistant text emitted alongside a tool_use), which broke follow-up and
  tool-result requests. Also drop messages left without content.
- Do not report a truncated Anthropic stream as completed: when the SSE
  connection ends before message_stop with no stop_reason, emit an incomplete
  response if partial output exists, or a failed (stream_truncated) response
  otherwise, mirroring the chat converter's EOF handling.

* fix(codex): resolve Anthropic-bridge review findings

Blocking:
1. Defer stripping the [1m] long-context marker until after catalog matching and model write-back, re-stripping it on the final Codex→Anthropic body and setting a flag to emit the context-1m-2025-08-07 beta header, so the marker is no longer lost or overridden by the default model.
2. Gate Anthropic thinking on the trailing turn only (via trailing_turn_allows_thinking) instead of scanning full history, so a Codex session that resends history each round no longer permanently loses thinking after the first tool call.
3. Inject 5m ephemeral cache_control on the Codex→Anthropic body by reusing cache_injector (handling the system string→array conversion), so system/tools/history are cached instead of re-sent at full price every round.
4. Add a shared base_url_is_full_endpoint helper (normalizing whitespace/query/fragment/trailing slash) used by both the Anthropic and Chat paths, so a base URL already ending in /v1/messages is treated as a full endpoint instead of double-appending to /v1/messages/v1/messages.
5. Align the catalog tool-profile predicate with the routing predicate so resolve_codex_catalog_tool_profile returns the Anthropic profile whenever the request converts to Anthropic, preventing freeform tools like apply_patch from being silently filtered by a ProxyChat catalog.
6. Explicitly disable native web_search for the Anthropic profile (including the no-catalog branch) via set_codex_native_web_search_field, so Codex no longer treats it as available while the transform silently drops it.
7. Only forward tool_choice when tools survive filtering, dropping it otherwise, to avoid a non-retryable 400 from upstream when tool_choice is sent with no tools.
8. Lower the fallback default to max_tokens=8192 (only when max_output_tokens is omitted) and clamp the thinking budget to max_tokens/2, disabling thinking below the 1024 floor, to avoid hard 400s on low-output-ceiling models/gateways.

Minor:
9. Centralize the Codex/OpenAI fingerprint-header denylist in is_codex_client_fingerprint_header so impersonating Claude Code uniformly drops originator/session_id/conversation_id/chatgpt-account-id/x-client-request-id/openai-* and the x-stainless-*/x-codex-* prefixes.
10. Retain content_block_start.input as start_input and fall back to it at block close when no input_json_delta arrived, so a gateway that carries the full tool input on the start event no longer yields empty tool arguments.
11. Extract a shared codex_responses_sse module as the single Responses SSE envelope builder that both the chat and anthropic streaming emitters delegate to, with byte-for-byte-unchanged wire output, so future event-format fixes touch one place instead of two.

* fix(codex): add per-provider max_output_tokens override for Codex→Anthropic path

Codex does not forward model_max_output_tokens in the request body,
causing the proxy to fall back to a conservative 8192 default. This
truncates long or thinking-heavy responses (stop_reason=max_tokens).

- Add maxOutputTokens field to ProviderMeta (Rust + TypeScript)
- Inject the value into the Anthropic request body before transform,
  taking precedence over request-supplied and default values
- Add numeric input in Codex form fields (Anthropic format only)
- Add i18n entries for label, placeholder, and hint (en/zh/zh-TW/ja)
- Include roundtrip and omission unit tests for the new field

* fix(codex): harden Anthropic protocol bridge

* fix(codex): address Anthropic bridge review

* fix(codex): preserve flattened Anthropic inputs

* fix(codex): harden Anthropic recovery paths

* fix(ci): satisfy Rust clippy

---------

Co-authored-by: Jason <farion1231@gmail.com>
2026-07-10 23:06:30 +08:00
makoMakoGo 98ccde0050 fix(usage): persist dashboard refresh interval (#5057) 2026-07-09 10:02:23 +08:00
zhanxin.xu 95c917b337 feat(provider): add Zhipu team plan quota query support (#5128)
智谱团队套餐(Team Plan)的额度查询与个人版不同:同一 quota 端点加
`?type=2`,需额外 `bigmodel-organization` / `bigmodel-project` 请求头
(api_key + 组织 ID + 项目 ID 三者缺一不可),且仅存在于国内站
open.bigmodel.cn。参考 token-monitor/src/shared/zaiTeamLimits.js 实现。

- Backend (services/coding_plan.rs): 新增 query_zhipu_team(固定 CN 站、
  ?type=2、org/project 头);抽出 zhipu_quota_from_body 与个人版共用解析;
  入口 get_coding_plan_quota 靠显式 coding_plan_provider == "zhipu_team"
  路由(base_url 与个人版智谱相同,detect_provider 无法区分)。新字段经
  UsageScript、IPC 命令、后台查询路径(query_provider_usage_inner)透传。
- Frontend: UsageScriptModal 新增「Zhipu GLM Team」选项 + 组织/项目 ID
  输入;模板切换时保留 team 字段;测试与保存逻辑按 team 传参。
- i18n: en/zh/zh-TW/ja 四个 locale 更新。
- Tests: 凭据校验/路由(缺任一凭据 → NotFound,标识大小写不敏感)+ 本地
  server 验证 ?type=2 与 org/project 请求头形状。

Co-authored-by: XuZhanXin <1239576606@qq.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 09:21:10 +08:00
Salar 3538b39246 feat(claude): add 1M checkbox to fallback model field (#5124)
The fallback model (ANTHROPIC_MODEL) field was missing the 1M checkbox
that the role-specific model rows already have. Added the checkbox with
the same strip/set marker logic used by role rows.

Fixes #3679
2026-07-09 08:36:54 +08:00
Jason ba531ca222 docs(readme): add new-api as a sponsor
Add the open-source AI infrastructure project new-api as the latest
sponsor entry, appended to the end of the sponsor table across all four
localized READMEs (en/zh/ja/de). Includes the banner asset.
2026-07-08 22:46:09 +08:00
Jason 88d5ffba44 fix(codex): move common-config TOML merge off smol-toml to backend toml_edit
updateTomlCommonConfigSnippet re-serialized the whole document through
smol-toml (parse -> deepMerge -> stringify): comments dropped, keys
reordered, and empty parent table headers synthesized -- the
long-standing "config.toml keeps getting reordered" symptom (audit C5,
introduced in 083e48bf).

Replace it with a backend command backed by the same
merge_toml_table_like / remove_toml_table_like used when writing live
configs, so the form preview and the live write share one merge
semantic and user formatting survives edit-time merges. The frontend
sync helper is deleted outright to keep the pattern from coming back.

Making the form operations async exposes them to interleaving, so a
result is discarded unless it is still current when it lands:

- a per-hook sequence number (last operation wins) covers rapid
  toggle/save races where an earlier merge resolves after a later
  removal and would flip the switch back;
- a config-baseline check covers the user hand-editing the TOML while
  a merge is in flight -- the stale result must not clobber their edit.
  The checkbox state self-heals via the existing inference effect.

Regression tests pin both by resolving a suspended merge after a newer
operation / an external edit, plus backend tests locking comment and
key-order preservation, scalar override, and value-matched removal.
2026-07-08 22:46:09 +08:00
Jason 94fc1cc064 fix(mcp): surface per-app failures when importing MCP servers from apps
import_mcp_from_apps swallowed every importer error with unwrap_or(0),
so a corrupt config.toml surfaced as "imported 0 servers" with no hint
that anything went wrong.

Move the aggregation into McpService::import_from_all_apps: each app
imports best-effort (one bad file doesn't block the rest), and failures
are collected into a single error naming the failing apps alongside the
count that did import. The frontend now refreshes the server list on
settle rather than success, since a partial failure still means new
servers were persisted.
2026-07-08 22:46:09 +08:00
Jason 11c173c730 fix(mcp): stop cross-app failures from blocking MCP re-projection
sync_all_enabled iterated AppType::all() with `?`, so one app's corrupt
live file (e.g. a broken ~/.claude.json, which passes the existence
gate but fails to parse) blocked every app behind it in the iteration
order -- and bubbled the error into whatever operation triggered the
sync:

- switch/save had just rewritten only the target app's live file, yet a
  broken unrelated file failed the whole operation after DB and live
  were already updated, reporting a false "switch failed" to the user.
  Both now project only the target app (sync_enabled_for_app) and
  degrade projection failure to a warning: the primary operation has
  already taken effect, and the projection self-heals on the next
  switch or MCP toggle.
- sync_current_provider_for_app_to_live syncs a single app; it now
  projects that app only, keeping failures (which can only concern the
  target app) as errors.
- sync_current_to_live (config import / cloud-sync restore) keeps the
  all-apps sweep, but sync_all_enabled is now best-effort: it projects
  every app, collects failures, and reports them aggregated. Its error
  is held until after skill sync so an MCP failure no longer skips it.
2026-07-08 22:46:09 +08:00
Jason 1f36f0cfed feat(provider): extend switch-time common-config autosync to Codex
Claude already re-extracts the live config into the shared common-config
snippet right before switching away, so shared tweaks (plugins,
preferences) made in live propagate to all opted-in providers. Codex was
gated out because its TOML pipeline leaked provider-specific and
injected content into the snippet.

With the extractor now stripping all injection artifacts and routing
fields, and backfill stripping the MCP projection, open the gate to
Claude + Codex. The autosync-before-strip ordering also self-heals
stale snippet values previously baked into provider snapshots: the
re-extracted snippet matches the live values, so the value-match strip
removes them on the same switch.

End-to-end tests cover: new shared keys captured, deletions synced,
secrets/injected artifacts never entering the snippet, and provider A's
key not leaking into provider B's live.
2026-07-08 22:46:09 +08:00
Jason 6d2ee2472f fix(provider): re-project Codex MCP after unified-session toggle rewrites live config
Toggling unify_codex_session_history rewrites the current official
provider's live config.toml in full (intended design), which drops the
[mcp_servers] projection -- and nothing put it back, so enabled MCP
servers silently vanished until the next provider switch (#C2).

Re-project after the rewrite, with two deliberate choices:

- Project Codex only (new McpService::sync_enabled_for_app) instead of
  sync_all_enabled: the all-apps sync short-circuits in AppType::all()
  order, so a corrupt ~/.claude.json would error before Codex is ever
  reached and the freshly wiped [mcp_servers] would stay missing. Only
  Codex's live file was rewritten here, so only Codex needs
  re-projection.
- Degrade projection failure to a warning: by this point the live file
  already carries the new bucket state, so the toggle has taken effect.
  Propagating the error would make save_settings roll back the setting,
  creating the exact "setting=old, live=new bucket" session split the
  rollback exists to prevent. The projection self-heals on the next
  switch or any MCP toggle.
2026-07-08 22:46:09 +08:00
Jason 473c2aaa9f fix(provider): exclude injected artifacts and routing fields from Codex common-config extraction
extract_codex_common_config kept several fields in the shared snippet
that must never cross providers:

- [mcp_servers] and the legacy [mcp.servers] form: owned by the DB
  mcp_servers table; once in the snippet they get merged into every
  opted-in provider and no sync path can ever clean them up.
- top-level experimental_bearer_token: normally lives inside
  [model_providers.<id>] (stripped with the whole table), but three
  fallbacks write it at top level -- leaking the API key into the
  shared snippet.
- model_catalog_json: per-provider catalog projection pointer.
- web_search, only when it equals the injected "disabled" sentinel;
  a user-set value remains a shareable preference.
- top-level wire_api: same provider-routing semantics as top-level
  base_url (the fallback target when no model_provider is set); leaking
  it would rewrite the next provider's protocol selection.

This makes the extractor safe as the source for switch-time
common-config autosync.
2026-07-08 22:46:09 +08:00
Jason 93f56198da fix(codex): strip synced [mcp_servers] from provider snapshots on backfill
MCP servers are owned by the DB mcp_servers table; the [mcp_servers]
section in live config.toml is only a projection re-synced after every
live write. When switching away baked that projection into the stored
provider snapshot, servers deleted in the app were resurrected the next
time that provider was activated -- per-entry reconcile only knows rows
that still exist in the DB, so it can never clean up such orphans.

Strip [mcp_servers] (and the legacy [mcp.servers] form) from the live
settings during switch-away backfill. Previously polluted snapshots
self-heal the next time the user switches away from them.
2026-07-08 22:46:09 +08:00
Jason 8b1ce764f1 fix(mcp): fail closed when Codex config.toml is unparseable during MCP sync
sync_single_server_to_codex silently fell back to an empty DocumentMut
when the existing config.toml failed to parse, then wrote the whole file
back -- wiping every other section (model, model_providers, comments)
and leaving only the single synced [mcp_servers.<id>] entry. Introduced
by 3a548152, which fixed the removal path but left the destructive
fallback on the sync path.

Now the sync path returns an McpValidation error and leaves the file
untouched, matching the fail-closed behavior of the read/validate path.
2026-07-08 22:46:09 +08:00
Jason fad5b4c094 Revert "fix(presets): point Volcengine/Doubao/BytePlus website links to official sites"
The websiteUrl for 火山Agentplan, DouBaoSeed and BytePlus is intentionally the ccswitch-attributed apiKeyUrl link, per the Volcengine partner's request. Restore the invite/attribution links across all six app preset files.

This reverts the websiteUrl changes from 56248087; apiKeyUrl was already left intact there.
2026-07-08 22:46:09 +08:00
Jason bad3610d93 refactor(presets): drop redundant 'OpenAI Compatible' preset
Remove the 'OpenAI Compatible' custom-template preset from the OpenCode
and OpenClaw preset lists. It was a duplicate entry point: the built-in
'custom' provider flow already exposes an interface-format selector whose
default (@ai-sdk/openai-compatible / openai-completions) produces a
byte-identical starting config. Having both left two list items pointing
to the same place.

Existing providers are unaffected — presets only seed form defaults on
creation; saved providers store concrete npm/baseUrl/apiKey/models values.

The '@ai-sdk/openai-compatible' dropdown label is intentionally kept; it
is the format option users pick in the custom flow, not the deleted preset.
2026-07-08 22:46:09 +08:00
Allen Xu e78aa8a7c3 fix: sync openclaw and hermes live provider updates (#5098)
* fix: sync openclaw live provider updates

* fix: sync hermes live provider updates

* fix test: hermes live import stores models as array after denormalize

The test import_hermes_providers_from_live_updates_existing_provider_from_live
seeded models as a dict, but import_hermes_providers_from_live reads via
get_providers() which calls denormalize_provider_models_for_read(), converting
models from YAML dict to UI-friendly array. The test assertion accessed models
as dict -> Null -> assertion failure.

Fix: access models as array by index, matching the actual storage format after
live import. Also verify the id field is preserved in the denormalized output.
2026-07-08 12:09:56 +08:00
Allen Xu e191af4aa1 fix: OpenCode live provider import updates (#4712)
* fix: sync existing opencode providers from live config

* fix: preserve OpenCode provider display names

* docs: update comments and log messages to reflect new update behavior

- fix stale comment in lib.rs that said existing providers are skipped
- change log message from 'Imported' to 'Synced' since count now
  includes both new imports and updates
2026-07-08 10:34:49 +08:00
Jason d271d60cf9 docs: add Codex Kimi routing guides 2026-07-08 00:00:56 +08:00
Jason 358bf1e2b7 chore(pnpm): settle build-script approvals for esbuild and msw
pnpm 10.13+ appends "set this to true or false" allowBuilds
placeholders to pnpm-workspace.yaml on every install for unreviewed
dependencies with lifecycle scripts. Approve esbuild (postinstall
ensures the platform binary) and ignore msw (postinstall only prints
an integration notice) so the file stops mutating itself.
2026-07-07 21:03:20 +08:00
Jason 2df2212ceb fix(usage): reject transient transport failures so retry and keep-last-good work
Usage/quota queries frequently showed spurious "query failed" states that
manual refresh could not clear (#3820). Root cause: all transport-level
failures were folded into Ok(success:false), so react-query's retry never
fired and the failure body poisoned the cache as regular data.

Backend:
- balance/coding_plan/subscription services now return Err for send
  failures and body-read failures (read body via bytes() before
  serde_json::from_slice; reqwest's json() wraps read errors as Decode,
  making error-kind checks on it dead code). Auth/4xx/parse errors stay
  Ok(success:false) and surface immediately.
- Script path maps transient AppError keys (request_failed /
  read_response_failed) to Err; Volcengine adds a Transient call variant.
- Command layer skips snapshot persistence, usage-cache-updated emit and
  tray refresh on Err so the cache bridge cannot overwrite retained data.
- Expired-credential retry propagates transient errors instead of
  rewriting them as "OAuth token has expired".

Frontend:
- resolveDisplayUsage generalized to subscription quotas; 5th param is now
  an options object with a `rejected` flag: stale success data retained by
  react-query across rejections is re-anchored to dataUpdatedAt and expires
  through the same 10-minute keep-last-good window instead of being shown
  indefinitely (a total outage used to mask longer than a single 5xx).
- useSubscriptionQuota / useCodexOauthQuota gain keep-last-good with
  per-scope snapshot reset; rejected queries with no displayable value
  synthesize a failure placeholder carrying the real error message so
  footers keep rendering a retry entry point.
- HTTP 429 is classified transient (retry-later) alongside 5xx.
- UsageScriptModal surfaces string rejections via extractErrorMessage.

Tests: 6 backend behavior tests drive real HTTP against a local listener
to pin the Err/Ok channel semantics; frontend suite extends
keepLastGoodUsage coverage (405 passing).
2026-07-07 20:05:29 +08:00
Jason 468c93d409 ci: harden release supply chain
- Add CODEOWNERS so PRs to main require owner review (closes the gap
  where two collaborators could approve each other's changes).
- Gate the release job behind a 'release' environment, so signing
  secrets are only unlocked after manual approval.
- Stop ignoring the whole .github directory; this had been silently
  swallowing labeler.yml and workflows/labeler.yml, which are now tracked.

Paired with a tag ruleset (created out-of-band) that restricts creation
of v* tags to admins, this prevents a write collaborator from pushing a
tag to trigger a signed release build.
2026-07-07 16:02:16 +08:00
Jason 525346188b fix(proxy): close media fallback gaps for Volcano GLM 5.2 image 400s
Fixes #5025. The rectifier's media fallback missed Volcano Coding Plan's
GLM 5.2 on both paths:

- Preventive: known_text_only_model had glm-5.1 but not glm-5.2, so image
  blocks were forwarded verbatim. Added glm-5.2 as an exact tail match
  (not a prefix, to avoid stripping images from a future glm-5.2v
  multimodal variant following Zhipu's 4v/5v naming).
- Reactive: the upstream error "Model only support text input" never
  mentions image/media, so the mentions_image gate rejected it before
  hints ran; and the existing "only supports text" hint misses the
  gateway's missing third-person "s". Added a self-evident phrase list
  ("only support text" / "only supports text") that asserts a modality
  rejection on its own and bypasses the image-mention gate.

Includes regression tests using the verbatim #5025 error body and the
glm-5.2[1M] mapped-model form, plus a glm-5.2v negative assertion.
2026-07-07 12:57:11 +08:00
Jason afabe80167 test(profiles): gate desktop-scope assertion by platform in profile roundtrip
The desktop switch to d2 is cfg-gated to macOS/Windows, but the assertion
expecting d2 was not, so on Linux CI the desktop provider stays at the seeded
d1 and the assertion panics — poisoning the shared test mutex and cascading
into two more failures. Expect d1 on non-desktop platforms.
2026-07-07 11:57:26 +08:00
Jason 7fada72dd4 chore(code0): update partner invite link to agent register URL
Replace the ?source=ccswitch referral with the new agent register invite
link (https://code0.ai/agent/register/B2XHxGjGmRvqgznY) on code0's
apiKeyUrl across all seven presets and the sponsor rows in all four
READMEs. API endpoints stay bare per the referral red-line.
2026-07-07 10:49:32 +08:00
Jason 9f7642e29c refactor(profiles): drop manual snapshot update now that switching autosaves
Since switching projects automatically saves the current configuration
back to the project being left, the per-project "update from current"
button is redundant and even harmful: it allowed overwriting another
project's snapshot with the current state, breaking the invariant that
a project holds the state you last left it in.

- Remove the resnapshot button and confirm dialog from the manage
  dialog; drop the now-unused scope prop and PROFILE_SCOPE_LABELS
- Add a footer Close button to the manage dialog (overlay click is
  disabled app-wide, so it previously could only be closed via Esc)
- Update manageDescription in all four locales to describe the
  autosave behavior; remove the two dead i18n keys
- Fix a shadowed `warnings` vec in ProfileService::apply that silently
  dropped autosave-failure warnings before they reached the frontend
- Mention auto-capture on next switch in the "no snapshot for this
  scope yet" warning and doc comments

The backend update command keeps its resnapshot parameter; it is now
only exercised by the pre-switch autosave path.
2026-07-07 10:49:32 +08:00
Jason 22159430c6 fix(profiles): use camelCase keys for current profile ids in frontend 2026-07-07 10:49:32 +08:00
Jason 754af2cc31 feat(profiles): split Claude Desktop into independent profile scope 2026-07-07 10:49:32 +08:00
Jason 3ec83578f6 fix(profiles): stop proxy server when profile switch leaves no takeovers active 2026-07-07 10:49:32 +08:00
Jason f05ed3dbac fix(ui): invalidate proxy takeover status after profile switch 2026-07-07 10:49:32 +08:00
Jason 4f45601f9f feat(profiles): unconditionally disable proxy takeover before applying profile 2026-07-07 10:49:32 +08:00
Jason 4cf6f175a7 feat(profiles): autosave previous profile state on switch
When switching to a different project, the current configuration of the
previously active project is automatically captured into that project's
snapshot before the target project's snapshot is applied. Scope is limited
to the active group (Claude/Codex); autosave failures are collected as
warnings and do not block the switch.

- Move autosave logic into ProfileService::apply for consistent behavior
  across UI, tray, and tests
- Add integration test covering bidirectional autosave roundtrip

Refs: profile switch B方案
2026-07-07 10:49:32 +08:00
Jason dbb5999d1e refactor(profiles): shared project entity with per-scope switching
Projects are now global shared entities; Claude and Codex groups switch
independently via scoped current pointers and scoped payload slots.

- Remove scope column from profiles; keep current_profile_id_<scope>
- Use Option<Vec<String>> for mcp/skills to distinguish 'never captured'
  from 'captured empty', preventing cross-side accidental disable
- Update/apply operations scoped to the active group via merge_scope_from
- Tray menu nests same shared list under Claude Code/Codex groups
- Add i18n for per-scope tooltips and 'not saved for this side' hint

Refs: profile P1 shared-entity redesign
2026-07-07 10:49:32 +08:00
Jason 65a5464fcc feat(profiles): include Claude Desktop provider in project profiles
Claude Desktop's only dimension managed by cc-switch is its provider
(MCP/Skills are hardcoded unsupported and prompts have no live file),
so snapshots capture just the current desktop provider while the empty
MCP/Skills sets and None prompt make apply a natural no-op for the
other dimensions - no per-dimension special casing needed.

- Add claude-desktop slot to PROFILE_APPS and PerApp (serde key uses
  the hyphenated app id); old payloads without the key deserialize to
  None and leave Claude Desktop untouched on apply
- Gate the tray Projects submenu by iterating PROFILE_APPS instead of
  hardcoding Claude/Codex visibility
- Show the profile switcher on the Claude Desktop tab, mirror the
  PerApp type, invalidate the claude-desktop providers cache on apply,
  and extend the switcher tooltip in all four locales
- Extend the roundtrip integration test with the desktop provider
  dimension; the desktop switch is cfg-gated to macOS/Windows because
  desktop live writes error on Linux where CI runs cargo test
2026-07-07 10:49:32 +08:00
Jason 6179c18805 fix(profiles): scope switcher to supported app tabs and relocate it
- Render the profile switcher only when the active tab is Claude or
  Codex (frontend mirror of backend PROFILE_APPS), so viewing an
  unsupported app no longer suggests its config was switched
- Move the switcher from beside the logo to the right of the route
  toggles, where the flexible spacer absorbs its appearance and other
  header controls no longer shift when switching tabs
2026-07-07 10:49:32 +08:00
Jason 8f018a2d45 feat: add project profiles for snapshot-based config switching
Add a profile feature that captures the current provider, MCP,
skills and prompt state for Claude Code and Codex as a named
snapshot, and re-applies it in one click from the header switcher
or the tray Projects submenu.

- New profiles table (schema v12) with current marker in settings
- ProfileService orchestrates the four existing switch primitives
  (provider first, then MCP diff, skills diff, prompt enable)
- Best-effort apply: dangling references become warnings, no rollback
- Header combobox switcher + snapshot-style manage dialog
- Tray Projects submenu shared with the UI apply/event pipeline
- i18n for zh/en/ja/zh-TW under the new profiles domain
- Integration tests covering roundtrip, dangling refs and clear
2026-07-07 10:49:32 +08:00
秋澪Akimio b3e5e32c89 feat: add Claude subagent model config (#4830)
* feat: add Claude subagent takeover config

* feat: add Claude subagent model field

* i18n: add Claude subagent model labels

* fix(proxy): preserve configured subagent model mapping

* fix(providers): exclude subagent model from Claude common config

* style: format rust code
2026-07-07 10:28:36 +08:00
makoMakoGo e606adfae7 fix(codex): display renamed session titles (#4927)
* fix(codex): display renamed session titles

* fix(codex): resolve custom state db for titles

* refactor(codex): dedupe state-db resolution and harden title lookup

- Extract a shared `codex_state_db` module for `state_5.sqlite` path
  resolution (config `sqlite_home` / `CODEX_SQLITE_HOME`), previously
  copy-pasted verbatim between codex_history_migration and the codex
  session provider. `state_5.sqlite` is now a single source of truth.
- Add `busy_timeout` to the read-only title query; without it a read
  during a concurrent Codex write fails immediately (SQLITE_BUSY) and
  renamed titles silently fall back to the first user message.
- Push the `title == first_user_message` comparison into the SQL WHERE
  clause (NULL-safe, aligning with Codex's `distinct_thread_metadata_title`
  semantics) and stop SELECTing `first_user_message`, which can hold
  large values (openai/codex#29007) — the comparison belongs in SQL
  anyway, so the column no longer needs to cross into Rust.
2026-07-06 09:11:55 +08:00
SaladDay 7a7d41c873 fix(subscription): display Codex free-plan 30-day quota window (#3651) (#4886)
* fix(subscription): display Codex free-plan 30-day quota window (#3651)

Codex free accounts are metered on a rolling 30-day secondary window
(limit_window_seconds = 2,592,000) instead of the weekly window used by
paid plans. The backend already maps this correctly to the tier name
"30_day", but the frontend TIER_I18N_KEYS whitelist had no entry for it,
so SubscriptionQuotaView filtered the tier out. When that was the only
surviving tier (as happens for free accounts), the whole quota footer
rendered nothing — free accounts showed no remaining quota or reset time
while paid accounts worked.

- Add "30_day" -> subscription.thirtyDay to TIER_I18N_KEYS
- Add the thirtyDay label to all four locales (zh/en/ja/zh-TW)
- Add a unit test locking in window_seconds_to_tier_name mappings,
  including the 30-day case

Fixes #3651

* fix(tray): render Codex free-plan 30-day window in tray menu

The tray keeps its own tier-name whitelist (TIER_LABEL_GROUPS) independent
of the frontend TIER_I18N_KEYS. Its month group only listed "monthly", so a
free Codex account whose only tier is "30_day" produced empty labeled parts
→ format_subscription_summary returned None → the tray showed no quota, even
though the footer now does. That breaks the invariant the existing
gemini_summary_lite_only_still_renders test guards: any tier visible in the
footer must not leave the tray blank.

- Add TIER_THIRTY_DAY ("30_day") constant so the string is single-sourced
  across backend mapping, tray grouping, and the frontend whitelist
- Map 2_592_000s explicitly to it in window_seconds_to_tier_name
- Add TIER_THIRTY_DAY to the tray month ("m") group
- Add codex_summary_thirty_day_only_still_renders regression test

Follow-up to the review on #3651 / PR for the 30-day footer fix.
2026-07-05 22:54:20 +08:00
Butui Hu ffc22ea714 feat(universal-provider): Auto-sync after adding and drop unused addSuccess i18n key (#2811)
* feat(universal-provider): Auto-sync after adding and drop unused addSuccess i18n key

Signed-off-by: Hu Butui <hot123tea123@gmail.com>

* fix(i18n): sync zh-TW universal provider strings

---------

Signed-off-by: Hu Butui <hot123tea123@gmail.com>
Co-authored-by: Jason <farion1231@gmail.com>
2026-07-05 22:41:05 +08:00
Cheng Zi Feng 7a8b956253 Update Longcat presets to LongCat-2.0 (#4838)
* Update Longcat presets to LongCat-2.0

* fix(proxy): classify LongCat-2.0 as text-only for media sanitizer

The Longcat presets now use LongCat-2.0, but the known_text_only_model
allowlist still only matched the retired longcat-flash-chat tail. Without
this, images pasted into a text-only LongCat-2.0 session are forwarded
upstream instead of being replaced with the unsupported-image marker,
causing a hard rejection. Add longcat-2.0 (keeping the retired name for
saved configs) and a regression test.

---------

Co-authored-by: chengzifeng <chengzifeng@meituan.com>
Co-authored-by: Jason <farion1231@gmail.com>
2026-07-04 16:38:35 +08:00
makoMakoGo 0cda8d46c7 fix: 更新 OpenCode 会话恢复命令 (#2359) 2026-07-02 22:14:43 +08:00
322 changed files with 50070 additions and 8141 deletions
+14
View File
@@ -0,0 +1,14 @@
# Code owners for cc-switch
#
# Branch protection on `main` has "Require review from Code Owners" enabled.
# With this file present, a PR cannot merge to `main` without an approval from
# the owner below. This closes the gap where two collaborators could approve
# each other's malicious PRs (a single non-owner approval is no longer enough).
# Global fallback: every PR needs owner approval before merging to main.
* @farion1231
# Most sensitive paths — CI/signing/release and the Rust backend. Listed
# explicitly so they stay locked even if the global rule above is relaxed later.
/.github/ @farion1231
/src-tauri/ @farion1231
+74
View File
@@ -0,0 +1,74 @@
# Configuration for actions/labeler v5
# Automatically labels PRs based on changed file paths
frontend:
- changed-files:
- any-glob-to-any-file:
- "src/**"
- "index.html"
- "vite.config.ts"
- "vitest.config.ts"
- "tailwind.config.cjs"
- "postcss.config.cjs"
- "tsconfig.json"
- "tsconfig.node.json"
- "components.json"
- "tests/**"
backend:
- changed-files:
- any-glob-to-any-file:
- "src-tauri/**"
i18n:
- changed-files:
- any-glob-to-any-file:
- "src/locales/**"
actions:
- changed-files:
- any-glob-to-any-file:
- ".github/**"
dependencies:
- changed-files:
- any-glob-to-any-file:
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "src-tauri/Cargo.toml"
- "src-tauri/Cargo.lock"
documentation:
- changed-files:
- any-glob-to-any-file:
- "*.md"
- "docs/**"
- "LICENSE"
mcp:
- changed-files:
- any-glob-to-any-file:
- "src/components/mcp/**"
- "src/lib/api/mcp.ts"
- "src-tauri/src/mcp/**"
- "src-tauri/src/claude_mcp.rs"
- "src-tauri/src/gemini_mcp.rs"
- "src-tauri/src/commands/mcp.rs"
skills:
- changed-files:
- any-glob-to-any-file:
- "src/components/skills/**"
- "src/lib/api/skills.ts"
- "src-tauri/src/commands/skill.rs"
- "src-tauri/src/services/skill.rs"
- "src-tauri/src/database/dao/skills.rs"
proxy:
- changed-files:
- any-glob-to-any-file:
- "src/components/proxy/**"
- "src/lib/api/proxy.ts"
- "src-tauri/src/proxy/**"
- "src-tauri/src/commands/proxy.rs"
+7 -1
View File
@@ -55,7 +55,11 @@ jobs:
backend:
name: Backend Checks
runs-on: ubuntu-22.04
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, windows-latest, macos-latest]
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -66,6 +70,7 @@ jobs:
components: rustfmt, clippy
- name: Install Linux system deps
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
@@ -87,6 +92,7 @@ jobs:
restore-keys: ${{ runner.os }}-cargo-
- name: Create frontend dist placeholder
shell: bash
run: mkdir -p dist
- name: Check Rust formatting
+17
View File
@@ -0,0 +1,17 @@
name: Label PRs
on:
pull_request_target:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5
with:
sync-labels: true
+1
View File
@@ -15,6 +15,7 @@ concurrency:
jobs:
release:
runs-on: ${{ matrix.os }}
environment: release
strategy:
fail-fast: false
matrix:
-1
View File
@@ -28,5 +28,4 @@ flatpak-repo/
copilot-api
.history
CODEBUDDY.md
.github
mainWindow.js
+136
View File
@@ -5,6 +5,142 @@ All notable changes to CC Switch will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.18.0] - 2026-07-21
Development since v3.17.0 is headlined by Grok Build joining as the eighth managed app — full provider switching, proxy takeover on its own route namespace, MCP/Skills/prompts sync, a curated preset list, and a Grok Official entry with official-login import (schema v14/v15) — and by xAI Grok account sign-in over an OAuth device flow for Claude Code, Claude Desktop, and Codex, including a strict-gateway compatibility layer that lets codex 0.142+ drive a Grok subscription over native Responses. A usage-accounting repair wave fixes the v3.17.0 fork/sub-agent double count with a one-time automatic rebuild (schema v16) plus a manual rebuild action, makes proxy usage logging idempotent, and stops the usage page freezing during large imports. Diagnostics mature: logs persist across restarts under size rotation, every log egress redacts secrets, and renderer crashes are captured to disk behind an error boundary with a reload screen. The Codex conversion layer gets four correctness fixes — tool schemas normalized to object type, reasoning attached forward across turns, streamed tool-call identity and order preserved, and parser-required catalog fields backfilled so codex 0.144.5+ starts — while managed-OAuth providers are now reliably flagged as routing-required and Windows provider switches no longer flash a console window or freeze the UI. Rounded out by Kimi K3 presets and pricing, corrected OpenClaw preset costs, SudoCode.us restored beside SudoCode.chat, sponsor-grouped preset ordering, first-run tray language detection, and permanently deletable default Skill repositories.
**Stats**: 52 commits | 217 files changed | +21,452 insertions | -6,285 deletions
### Added
- **Grok Build Becomes the Eighth Managed App**: xAI's Grok CLI ("Grok Build", live config `~/.grok/config.toml`) is now a first-class managed app alongside Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw, and Hermes: full provider add/import/switch (with a "restart Grok Build to apply" toast), app visibility and config-directory override settings, session manager and usage dashboard coverage, prompts with first-launch auto-import, deep-link (`ccswitch://`) provider import, and proxy takeover with its own `/grokbuild/v1/responses` route namespace, failover queue, and per-app proxy settings row — it reuses the Codex Responses forwarding path but never shares Codex's provider namespace or circuit-breaker state. MCP servers sync bidirectionally into Grok's `[mcp_servers]` table with the dialect differences handled (Grok infers transport from `command`/`url` and uses `headers`, so exports strip the explicit `type` and rename `http_headers` to `headers`, while imports infer the transport back), and Skills gain a Grok Build enable flag. Instead of borrowing the Codex preset list (an early cut leaked China-official direct providers and Codex default models into the Grok form), Grok Build ships its own curated preset module: aggregators and relays that actually carry Grok models, default model normalized to `grok-4.5` (`x-ai/grok-4.5` on namespaced routers), with no `cn_official` category. The tools panel installs Grok via the official xAI installer (`x.ai/cli/install.sh` / `install.ps1`) with npm `@xai-official/grok` as fallback; installs detected as native (launcher in `~/.grok/bin` or `$GROK_BIN_DIR`, real target under `~/.grok/downloads/grok-*`) self-update via `grok update`, while npm installs keep npm-anchored updates — the self-update is gated on positive native detection so it can never mutate a different install. Localized in all four locales (zh/en/ja/zh-TW). (#5453)
- **Grok Official Provider With Official-Login Import**: A "Grok Official" entry lets Grok Build fall back to the CLI's own built-in xAI OAuth login: selecting it hides the connection fields and writes an empty `~/.grok/config.toml`, and CC Switch never stores or touches those credentials. Live-config reads, backups, and official-state writes now use syntax-only TOML validation, so an official-login (empty) config round-trips instead of failing; importing the live config while Grok is in official login state yields "Grok Official set as current" instead of an error, matching Codex behavior. Official-state detection is deliberately wired only into the manual import command — the startup auto-importer still rejects official-state configs, so a deleted Grok Official entry is never resurrected on launch. Proxy takeover of an official-login Grok config is skipped automatically and rejected with a clear message on the manual path, consistent with the existing ban on proxying official providers.
- **Sign In With Your xAI Grok Account (OAuth) in Claude Code and Claude Desktop**: New "xAI (Grok)" presets for Claude Code and Claude Desktop authenticate with an OAuth device-code login instead of an API key, routing requests through the local proxy which transforms Anthropic Messages traffic to the xAI Responses API and injects the access token per request (default model `grok-4.5` on every role; the Claude Desktop preset maps the branded `claude-*` role IDs to `grok-4.5` upstream so it passes Desktop's third-party model validation). A new xAI section in Settings → OAuth Auth Center handles device-code login (user code with copy button, verification link, waiting/cancel/retry), multiple accounts with a default-account picker, per-account removal, and re-auth badges — accounts whose refresh token was revoked stay visible as "expired" instead of disappearing, and auth status refetches every 15 s so server-side revocation surfaces on its own. The integration is pinned closed: upstream is always `https://api.x.ai/v1/responses` in Responses format regardless of what the editable endpoint/format fields say, OAuth endpoints are resolved via OIDC discovery but validated to `auth.x.ai` over https only, refresh tokens live in `~/.cc-switch/xai_oauth_auth.json` (`0600` on Unix; access tokens are memory-only), and OAuth error bodies are never embedded in error messages or logs. `grok-4.5` is also seeded into the built-in pricing table ($2 input / $6 output / $0.50 cache-read per 1M tokens), fixing the $0 cost the dashboard would otherwise record; existing databases pick the row up automatically on next launch. Localized in all four locales (zh/en/ja/zh-TW). See the upgrade notes for the client-identity disclosure.
- **xAI (Grok) Codex Presets: OAuth Managed and Native-Responses API Key**: Codex gains two ways to talk to xAI directly. The "xAI (Grok) OAuth" managed preset drives Codex against a Grok subscription through the local proxy — the form hides the key/endpoint/format fields and shows the account picker, "fetch models" uses the signed-in account, and the provider is pinned to native Responses with the base URL and per-request token enforced by the proxy (edited base URLs or formats are ignored, so the managed route cannot be redirected). Because codex 0.142+ emits ChatGPT-backend-private shapes that xAI's strict parser rejects (`type:"namespace"` tool declarations → 422, plus private fields like `prompt_cache_retention`, `safety_identifier`, `external_web_access`, the `additional_tools` carrier, and grok-4.5-unsupported sampling knobs), the OAuth route gets a compatibility layer on the native passthrough: namespace tools are flattened to top-level function tools (using the same sha256-truncated naming as the Chat path) and restored to namespaced form in both streaming and non-streaming responses, and unsupported fields are stripped — all deterministic field-removal/structural lifts, never semantic rewrites, so prompt-cache prefixes stay stable. The layer is gated exclusively on the xAI OAuth provider type: no other provider's traffic is touched, including the second new preset, a plain "xAI (Grok)" API-key preset (native Responses against `api.x.ai/v1` with a 500K-context `grok-4.5` catalog entry) that does not receive these xAI-specific compatibility transforms — API-key users on codex 0.142+ can still hit xAI's strict parser, and the OAuth preset is the fully-compatible path. xAI OAuth token failures are classified non-retryable so failover never silently moves a conversation onto a different Grok account.
- **Renderer Crash Capture With On-Disk Reports and a Reload Screen**: A React error boundary now wraps the UI (including the database-recovery screen), so a renderer crash shows a "Something went wrong in the interface" card with a Reload button instead of a blank white window, and global `error`/`unhandledrejection` handlers persist renderer errors to disk via the new `log:default` capability — previously a JS crash left zero on-disk evidence. Everything logged from the frontend passes two redaction layers: a structured serializer that redacts by sensitive property name (normalized so `tokens`/`apiKeys`/`credentials` variants all match, hiding the entire value including nested objects) and by value shape (token prefixes, PEM headers, high-entropy opaque strings), then a single text egress (`redactFrontendLogText`) whose ordered regex chain covers URL query values and credentials, auth headers and schemes, and named secret containers even in double-encoded JSON. JSON that arrives as a string is re-parsed and redacted structurally, and oversized structured input is dropped entirely rather than truncated — a truncated JSON string would fall back to the weaker text regexes and could leak. The settings toggles were also relabeled to say what they actually control: "Application Diagnostic Logs" (cc-switch.log) versus the proxy's "Record Request Usage" (the stats database, which never was a text log). Localized in all four locales (zh/en/ja/zh-TW).
- **"Rebuild Codex Usage" Maintenance Action**: The usage dashboard's maintenance section gains a "Rebuild Codex Usage" button that backs up the database, wipes only `codex_session`-sourced detail rows, their `_codex_session` daily rollups, and the Codex sync cursors, then re-imports every rollout file from scratch with the corrected parser — the recovery path for databases already inflated by the fork double-count bug below, and the retry path for deferred fork files whose parent log has since been restored. The manual rebuild fails hard when the pre-rebuild backup cannot be written (unlike the automatic migration variant, which only warns, since blocking startup on an unwritable backup directory would leave the app unable to launch after upgrade), holds the session-sync lock across the whole backup → reset → reimport sequence so the 60-second background sync cannot interleave with the wipe, and always sends exactly one frontend refresh notification on completion — including when the reimport returns zero rows or fails after the reset — so the dashboard never keeps showing pre-reset numbers (`finish_codex_rebuild` in `commands/usage.rs`, pinned by two regression tests). Cursor deletion matches rollout paths purely by shape (`rollout-{uuid}` filename under a `sessions`/`archived_sessions` segment), so cursors recorded under an old `CODEX_HOME` are still cleaned. Localized in all four locales (zh/en/ja/zh-TW).
- **Session Import Observability: Deferred Files and Suspected Duplicates**: Session sync results now report `filesScanned`, `deferredFiles` — fork rollouts whose parent log is missing or whose parent markers conflict, held back without writing a cursor so a later sync or manual rebuild retries them, instead of being imported on a guess — and `suspectedDuplicates`, backed by a post-insert probe that checks each imported row for a pre-existing same-fingerprint row (via the `idx_request_logs_dedup_lookup_expr` expression index) and logs a warning per hit, so any future regression of the double-count bug announces itself in the logs instead of silently inflating totals.
- **Kimi K3 in Presets and Built-In Pricing**: The Kimi open-platform presets for Codex, Hermes, OpenClaw, and OpenCode gain the Kimi K3 model (1M context window), appended after K2.7 Code so existing default-model behavior is unchanged, and the built-in pricing table gains `kimi-k3` at the official list price ($3 input / $15 output / $0.30 cache-read per 1M tokens) plus a bare `k3` alias — the Kimi For Coding subscription reports its model under the short id `k3`, which would otherwise match no pricing row (same precedent as the existing `hunyuan-hy3`/`hy3` pair). Existing databases pick both rows up automatically on next launch without touching user-edited pricing.
- **SudoCode.us Restored Alongside SudoCode.chat**: The two unrelated companies sharing the "SudoCode" name are now separate presets: the sponsor is renamed "SudoCode.chat", and the legacy "SudoCode.us" (which an earlier release had replaced in place) returns with its original endpoints and models, its own icon, and a distinct Hermes slug so both can coexist in the additive `~/.hermes/config.yaml`. Counting the new Grok Build preset list, SudoCode.chat now ships in seven apps and SudoCode.us in all eight.
### Changed
- **Diagnostic Logs Persist Across Restarts, Are Size-Rotated, and Never Record Secrets**: `cc-switch.log` is no longer wiped on every startup — the log that explained a crash used to be gone by the time the app reopened — and instead rotates at 20 MB with 4 archives kept (~100 MB cap, versus a single 1 GB file before); `crash.log`, previously unbounded, rotates at 5 MB with 2 archives, with the size-check/rotate/append sequence under one lock so concurrent panics cannot lose an archive. Because persistent logs make verbatim secrets a real exposure (users attach these files to public issues), every backend log egress was scrubbed in the same change: upstream URLs are logged with userinfo, query, and fragment stripped (origin-only when no known secret is available to scrub, since a credential could be embedded in the path), request and response bodies are never logged — replaced by byte counts, a short hash, or a safe classification (`sse`/`html`/`json-like`/`binary-or-encoded`/…) that preserves the transform-debugging signal without the content — response headers go through an allowlist (unlisted headers log name-only), the exact secret values in use (API key, access token) are scrubbed out of any logged URL that carries them, and MCP custom-field values are omitted. The log plugin also registers earlier (so updater/startup failures are diagnosable), the persisted log level applies as soon as the database opens and fails closed to Info, and the "Enable Diagnostic Logs" switch now gates frontend-originated log writes too. Pre-upgrade log files are not retroactively scrubbed — see upgrade notes.
- **Preset Picker Groups Sponsors and Alphabetizes the Rest**: The picker's default ordering now has four tiers — official first, then the prime partner, then sponsor presets (in the same order as the README sponsor table, which the preset files were physically reordered to match), then all remaining presets sorted alphabetically by display name instead of file order. An entry matching multiple tiers lands only in the earliest, so nothing is listed twice.
- **Preset "Get API Key" Links Updated to Current Referral URLs**: The key-application links on the RunAPI, ClaudeCN, ZetaAPI, and APINebula presets now open each provider's current registration/referral page (ClaudeCN also moved domains, claudecn.top → claudecn.ai). Referral tags remain confined to these links and the README — website links and API endpoints stay untouched.
### Fixed
- **Codex Fork/Sub-Agent Rollouts No Longer Import Replayed Parent History**: Fixes the v3.17.0 usage inflation where forking a Codex task or spawning a sub-agent in copy mode re-counted the parent conversation's token history as new usage — reports showed single days jumping by billions of tokens, duplicated parent/child rows that were byte-identical, and empty forks carrying usage they never consumed. Fork and sub-agent rollout files begin by replaying the parent thread's history, and the previous parser located the takeover boundary heuristically (first `thread_settings_applied` event, object-shaped `subagent` source markers): the boundary landed too early when the parent's own settings changes appeared in the replay, and was missed entirely for the current string-shaped source format, importing the whole replayed parent history. The parser now identifies a fork only by explicit parent identity — `forked_from_id` on the child's `session_meta` or `source.subagent.thread_spawn.parent_thread_id`, with a conflict between the two deferring the file — anchors each thread's identity to the rollout filename UUID, loads the parent rollout's own pre-fork token-count sequence, and strips the child's replayed prefix by aligning token signatures against it, so replayed events only restore the cumulative baseline and are never inserted as rows. Sub-agent logs that carry no replayed history are now counted as live usage, fixing the opposite-direction undercount where real sub-agent consumption was skipped as suspected replay. (#5335, #5433, #5381)
- **Proxy Usage Logging Is Now Idempotent With Stable Response-Scoped Keys**: When a terminal usage event carried no message id — the norm for Codex `/responses` traffic through the local proxy — the dedup key fell back to a random UUID, so every retry or replay of the same upstream response minted a fresh key and `INSERT OR REPLACE` stacked a new row each time; one reporter's database held the same usage combination 2,078 times. The parser now derives the key from the response envelope itself — the Codex `response.completed` event's `response.id` (`response.created` ids are discarded), Chat Completions `chatcmpl` ids, and the Gemini `responseId` — scoped as `session:{app_type}:{provider_id}:{id}` so the same response replayed against a different provider during failover still bills once per provider without cross-provider collisions (Claude keeps the bare `session:{id}` shape so proxy rows continue to converge with session-log imports). When no envelope id exists at all, the fallback is a deterministic SHA-256 over the response's usage semantics rather than a random UUID — an identical replay must collide into the same key for dedup to work — and the final database write is a guarded insert-if-absent within the dedup window instead of an unconditional REPLACE. (#5496)
- **Usage Page No Longer Freezes During Large Session Imports**: Opening the usage page while a big import ran could lock up the UI: every inserted row fired a refresh notification, each notification made the frontend re-run all ~10 usage queries, and those queries contended with the importer for the single database connection while it parsed rollout files tens of megabytes large line by line — on duplicate-inflated databases the row storm compounded all three. Session sync now notifies the frontend once per completed sync pass instead of once per row, all session importers are serialized behind a single-flight lock (a manual "sync now" queues behind the running pass instead of racing it), the blocking parse work runs on a dedicated blocking thread so it no longer starves the async runtime driving the UI's commands, and the 60-second background tick skips missed runs instead of bursting to catch up.
- **Codex 0.144.5+ No Longer Fails to Start on CC Switch-Generated Model Catalogs**: codex ≥ 0.144.5 parses external model catalogs strictly and rejects the whole file when an entry is missing `supports_reasoning_summaries` — so both the Codex CLI and desktop app failed to launch, and deleting the generated catalog didn't help because any provider save regenerated it the same way. The root cause is that CC Switch clones its catalog template from the machine-shared `models_cache.json`, whose field set is whatever the last-writing codex process produced — a coexisting older codex build kept rewriting the cache without the field the newer parser requires. Generated catalogs now backfill parser-required fields from the bundled static template, only when absent (dynamic values always win), and deliberately do not backfill optional capability fields whose "missing = parser default" semantics must survive.
- **Windows: No More Console Flash or UI Freeze When Switching Providers**: Switching providers or toggling takeover on Windows flashed a transient console window and froze the UI for up to ~2 seconds. Three causes, three fixes: the `codex debug models --bundled` probe launches `codex.cmd` through `cmd.exe`, which in a GUI-subsystem app spawns its own console — the child process is now created with `CREATE_NO_WINDOW`; the model-catalog template was regenerated on every switch — it is now cached process-wide after the first successful load (failures stay retryable so a bad first probe cannot poison the cache), so the Codex CLI starts at most once per app run; and `switch_provider` was a synchronous command running on the main thread — it is now async with the real work on a blocking thread, still serialized by the per-app switch lock. The freeze fix benefits all platforms; the console-flash fix is Windows-specific.
- **Codex Tool Schemas With Null, Missing, or Union Parameter Types Are Accepted by Strict Upstreams**: Built-in Codex tools such as `codex_app__automation_update` declare `parameters: null` (or `type: null`), which strict OpenAI-compatible upstreams like DeepSeek reject with a 400 for the entire request, killing tool-using sessions routed through the proxy. The Responses→Chat bridge now normalizes every tool's parameters to a `type:"object"` schema — null or missing parameters (including the nested-form missing case) become `{"type":"object","properties":{}}`, a non-object `type` (including `type: null`) is coerced to `"object"` in place, and top-level `oneOf` union schemas get a root `type:"object"` added while their branches are preserved untouched. The same object-type guarantee was extended to the Codex→Anthropic tool path's `input_schema`. Existing `properties`/`required` are never dropped. (#4706, #5315; issues #4705, #4783)
- **Reasoning Models Keep Their Thinking Across Multi-Turn Codex Chat Conversations**: With a reasoning model (e.g. kimi-k2-thinking) behind the proxy's Responses→Chat bridge, multi-turn history mangled the thinking: each turn's `reasoning` item was glued onto the tail of the _previous_ assistant message, leaving the following assistant turn with no `reasoning_content` — models would visibly break off mid-conversation. Responses semantics place reasoning _before_ the message it belongs to, so the bridge now attaches reasoning forward to the assistant message or tool call that follows it; genuine trailing reasoning back-attaches only at the true tail (end of input, or a turn boundary such as an incoming user message — where it was previously silently discarded), appending to any embedded reasoning already present, and pending reasoning is always consumed at boundaries so it can never leak across a user turn into a later assistant message. (#5508)
- **Streamed Parallel Tool Calls Keep Their IDs and Their Order**: Two bugs in the Chat→Responses streaming bridge corrupted parallel tool calls from upstreams that split identity across chunks: a continuation delta carrying an empty `id` overwrote the real `call_id` (the Codex client then saw `call_id:""` and couldn't match tool results to calls), and tool calls were emitted the moment they individually became ready, so a later index whose name arrived early could jump ahead of an earlier one — reordering parallel calls. Empty ids are now ignored, and emission goes through a consecutive-index gate that releases tool calls strictly in Chat `index` order, waiting on any not-yet-identified earlier index; no fake call id is ever synthesized mid-stream (only as a last resort at stream finalization, which also skips nameless calls defensively and still emits sparse indexes). (#5310)
- **Managed-OAuth Providers Are Reliably Flagged as Needing Local Routing**: The "needs routing" badge and switch-time warning were derived from the provider's API format, which is the wrong signal for managed-OAuth providers (Copilot, Codex OAuth, xAI) whose credential is injected by the proxy regardless of upstream format — a managed provider on a native format got no warning and failed silently without takeover. Routing need is now decided by a single shared predicate: official providers never need routing, managed-OAuth providers always do, and format-based rules apply only to the remaining cases per app. The switch-time gate also checks the right readiness signal per app: per-app takeover for most apps (the old gate looked only at a global proxy-running flag, missing the case where the proxy runs but the current app is not taken over), while Claude Desktop keeps watching the proxy process itself — the backend's takeover status has no Claude Desktop field, so a uniform per-app gate would have left Desktop warning forever. Claude Desktop provider forms now force proxy mode and lock the model-mapping toggle for every managed-OAuth type, not just xAI. Localized in all four locales (zh/en/ja/zh-TW).
- **Tool Updates Work When Node Lives in nvm/fnm/mise Under a GUI Launch**: Anchored npm update and repair commands invoked npm by absolute path, but npm's launcher resolves `node` via its `#!/usr/bin/env node` shebang against PATH — and a GUI-launched app inherits only the system PATH, not the user's version-manager directories, so updates silently failed for tools installed via nvm/fnm/mise. Every anchored npm invocation now prefixes PATH with npm's own sibling `bin` directory, so both npm and its shebang resolve to the same Node install; the Codex self-repair (uninstall + reinstall) path is covered too.
- **Deleted Default Skill Repositories Stay Deleted**: Default Skill repositories were re-seeded on every startup by a "supplement missing defaults" pass, so a default repo the user deleted silently returned on the next launch. Seeding is now one-time per database, tracked by a settings flag; databases that already contain repositories at upgrade time get the flag set without any re-seeding, so existing selections are untouched. (#5356)
- **First-Run Tray Language Follows the System Locale**: Before any language was chosen in settings, the tray menu was hardcoded to Simplified Chinese even on English/Japanese/Traditional-Chinese systems where the main UI correctly followed the OS locale — tray and UI disagreed until the user switched language once. The tray now derives its first-run language from the OS locale with the same precedence rules as the frontend (including `zh-TW`/`zh-HK`/`zh-Hant` → Traditional Chinese); an explicitly chosen language always wins, and unreadable locales fall back to Chinese as before. (#4355)
- **Failed Live-Config Imports Show the Real Error and Refresh the List**: Every failed "import from live config" produced an empty error toast, because Tauri's `invoke` rejects with the backend's error _string_ while the handler read `.message` off it; the actual backend message is now shown (with a localized generic fallback), and the provider list is refreshed even on failure so side effects committed before the error are visible immediately.
- **OpenClaw Preset Model Costs Corrected to Official List Prices**: Fifteen OpenClaw preset entries carried cost values in the wrong unit or unconverted currency — the `cost` field is USD per million tokens, but e.g. `glm-5.1` was listed at `0.001/0.001` (≈1000× undervalued, so its usage showed near-zero cost) while `deepseek-v4-pro` carried unconverted CNY values (overvalued). All entries now carry official list prices in $/M; subscription-plan and free-tier endpoints deliberately show list prices too, so plan users see the standard value of their usage. Providers created from these presets going forward get the corrected values; previously added providers keep the config they were created with.
- **AiHubMix Icon on the Codex Preset**: The Codex app's AiHubMix preset was the only one missing its brand icon fields and rendered a generic icon; it now matches the other apps.
- **Two Missing Locale Keys Backfilled in All Four Locales**: The Codex "needs routing because it uses Anthropic Messages format" toast rendered its reason fragment in Chinese inside an otherwise-localized sentence because `proxyReasonAnthropicMessages` existed in no locale file, and the provider-form key-status loading label had shipped only as a hardcoded default since April; both now exist in zh/en/ja/zh-TW.
### Docs
- **Codex + Claude Local Routing Guide (Three Languages)**: New guide in Chinese, English, and Japanese on pointing Codex at a Claude-family `/v1/messages` gateway via the native Anthropic Messages upstream format introduced in v3.17.0, with screenshots; the v3.17.0 release notes now link to it.
- **Claude Code via Codex Providers Guide (Chinese)**: New Chinese guide on using Responses-speaking providers (a gateway API key, or a ChatGPT subscription's Codex service) from Claude Code: Claude Code keeps talking Anthropic Messages to the local `/v1/messages` route, and the proxy converts each request to the upstream's Responses protocol. With screenshots.
- **README Sponsor Updates**: SubRouter added as a sponsor across the four README languages; the pinned Kimi sponsor copy refreshed to K3 with banners served from the Moonshot CDN; RunAPI benefit copy refreshed and sponsor rows reordered to match the in-app preset order.
### Internal
- **Backend CI Now Covers Linux, Windows, and macOS**: Backend checks previously ran only on Ubuntu, so Windows/macOS-gated code paths were never compiled or tested in CI — the matrix now spans all three platforms, with the platform-gated tests repaired to pass everywhere (TOML literal-string path escapes, stale anchored-update expectations, and skill tests honoring the test home-directory override). No shipped behavior changes. (#5138)
### Upgrade notes
- Upgrading from v3.17.0 runs three schema migrations (v13 → v16): v14 rebuilds the `proxy_config` table to admit Grok Build (existing per-app proxy settings are carried over and a `grokbuild` row is added), v15 adds Grok Build enablement columns to the MCP-server and Skills tables, and v16 triggers the Codex usage rebuild described next.
- Schema v16 performs a one-time automatic rebuild of Codex session usage on first launch after upgrading: the database is backed up under `backups/`, `codex_session` data and cursors are reset, and the normal startup sync re-imports everything with the corrected parser. Expect seconds for typical data; the heaviest dataset measured (1,801 rollout files / 1.5 GB) took about 65 seconds. Later launches are incremental as before.
- The rebuild recomputes usage from the rollout JSONL files, so history whose source log was already deleted cannot be reconstructed.
- Fork files whose parent rollout is missing are deferred and reported instead of guessed; restoring the parent log and running “Rebuild Codex Usage” imports them later.
- Historical proxy-source duplicate rows are permanently retained — the migration rebuilds only session-sourced data, and no cleanup pass for past proxy inflation exists; the idempotent logger prevents new duplicates from this point on.
- The xAI Grok OAuth integration reuses the official Grok CLI's public OAuth client identity and scopes (`client_id b1a00492-073a-47ea-816f-4c329264a828`, scope including `grok-cli:access`) rather than a CC Switch-registered application. xAI may not support this use, and it could lead to account restriction or suspension — use at your own risk. The feature is entirely opt-in; nothing changes unless you add an xAI provider. On first login it creates `~/.cc-switch/xai_oauth_auth.json` (refresh tokens only, `0600` on Unix; access tokens are held in memory) and contacts `auth.x.ai` and `api.x.ai` through the configured outbound proxy, with no local callback port.
- Diagnostic logs are no longer cleared at startup and now persist across restarts (up to ~100 MB of rotated runtime logs plus ~15 MB of crash logs). Log files written by earlier versions are not retroactively scrubbed and may contain API keys, tokens, or URLs with credentials — review pre-upgrade logs before sharing them publicly.
- Installing or reinstalling Grok Build now prefers the official xAI installer, fetching `x.ai/cli/install.sh` (or `install.ps1` on Windows) at install time, with npm as fallback; existing npm installs keep updating via npm.
- New built-in pricing rows (`grok-4.5`, `kimi-k3`, `k3`) are appended automatically on next launch via insert-if-absent; user-edited pricing rows are never overwritten.
## [3.17.0] - 2026-07-13
Development since v3.16.5 is headlined by project profiles — named snapshots of provider/MCP/Skills/prompt state, switchable per scope from a new header switcher or the tray (schema v12) — and a deep Codex push: official ChatGPT-subscription sessions can now route through the local proxy takeover with a corrected client identity, gpt-5.6 lands across context-window injection and Sol/Terra/Luna pricing with 1.25× cache-write rates, and a native Anthropic Messages upstream joins the Codex format options. A proxy-correctness wave makes the Responses↔Anthropic bridges fail closed and round-trip reasoning/tool results losslessly, strengthens prompt-cache breakpoint injection, and fixes cache-write accounting across historical token semantics (schema v13); a config.toml hardening batch stops deleted MCP servers from resurrecting, fails MCP sync closed on unparseable files, extends switch-time common-config autosync to Codex, and moves the common-config merge to backend toml_edit. Usage tooling gains Zhipu team-plan quota queries, Codex sub-agent and free-plan accounting, and transient-failure retry, while Kimi For Coding's 256K window finally takes effect — rounded out by a Codex default-model form field, renamed-session titles, OpenCode form and live-sync fixes, and preset updates (SudoCode sponsorship, LongCat-2.0, GPT-5.6 defaults, Hunyuan Hy3 pricing).
**Stats**: 69 commits | 172 files changed | +21,067 insertions | -2,464 deletions
### Added
- **Project Profiles for One-Click, Snapshot-Based Config Switching**: You can now save the current provider, MCP, Skills, and prompt state as a named "project" and re-apply it in one click from a new header switcher or the tray Projects submenu, so moving between per-project setups no longer means toggling each dimension by hand; it covers Claude Code, Claude Desktop, and Codex (Claude Desktop's only cc-switch-managed dimension is its provider, so its snapshots capture just that and leave the other dimensions untouched on apply). Projects are global shared entities, but capture and switching are per scope — Claude Code, Claude Desktop, and Codex each keep their own `current_profile_id_<scope>` marker and only touch their own payload slots, so picking a project on the Codex tab never disturbs the Claude config (slots use `Option` to distinguish "never captured" from "captured empty", preventing cross-side accidental disable). Applying is best-effort: `ProfileService::apply` (in `services/profile.rs`) reuses the existing switch primitives — provider switch, then minimal MCP/Skills toggle diffs, then prompt enable — and any dangling reference or per-item failure becomes a warning instead of rolling back. Switching first autosaves the leaving project's current state for the active scope, so a project always holds the configuration you last left it in, which is why there is no manual "update snapshot from current state" button. Before applying, proxy takeover is unconditionally disabled per app in the scope, the frontend invalidates takeover status, and the proxy server is stopped when the switch leaves no takeovers active so Claude Desktop's local-route master toggle reads off. Backed by a new `profiles` table introduced by the v11→v12 schema migration, wired into `commands/profile.rs`, `database/dao/profiles.rs`, the tray, and `components/profiles/`, localized in all four locales (zh/en/ja/zh-TW), with integration tests covering roundtrip, dangling refs, bidirectional autosave, and proxy-takeover teardown.
- **Setting to Show or Hide the Project Switcher on the Main Page**: The header project switcher can now be turned off from a new "Show project switcher" toggle under the Homepage Display section of Settings, for users who don't use projects and prefer a cleaner header. It defaults to on (`show_profile_switcher` / `showProfileSwitcher`) so existing users keep the current behavior, and disabling it only hides the header control on every tab — projects and the tray Projects submenu remain fully usable. Localized in all four locales.
- **Route Codex Official ChatGPT Sessions Through Proxy Takeover**: CC Switch can now route a Codex session authenticated with a ChatGPT subscription (native OAuth or API-key login, no stored API key) through the local proxy, so official-account traffic gets the same proxy routing, format conversion, and usage accounting that third-party providers already receive. The built-in `codex-official` "OpenAI Official" seed is restored if it was deleted (idempotent `ensure_codex_official_provider` command wired into the add-provider flow) and is now selectable during takeover from the provider panel and tray, with the provider card badge reading "Official Account Routing" while routing and "Codex Sign-in" otherwise; only that fixed seed qualifies (`is_codex_official_provider` / `official_provider_supports_proxy_takeover`, mirrored in the frontend `supportsOfficialProxyTakeover`), so copied UUID-based official entries stay blocked and still show "No Routing Support". Instead of writing an `OPENAI_API_KEY = PROXY_MANAGED` placeholder into `auth.json`, the official route projects a dedicated `cc-switch-official` `model_provider` into `config.toml` with `requires_openai_auth = true`, `wire_api = "responses"`, `supports_websockets = false`, and `base_url` pointing at the local proxy (`apply_codex_official_proxy_route`), so Codex forwards its own ChatGPT authorization to the proxy's `/responses` endpoint and the `codex-official` row never stores a credential (its `auth` stays `{}`). The forwarder passes the client's `Authorization` header through unchanged to the fixed first-party endpoint (`CHATGPT_CODEX_BASE_URL`, now shared by the Codex and Claude adapters), rejects a missing header or a stale `PROXY_MANAGED` placeholder with an actionable error (finish the ChatGPT login / restart Codex or start a new session), and treats official `401`/`403` and auth errors as non-retryable so failover never silently moves the conversation to another account or pollutes the circuit breaker. The native login is never overwritten — takeover preserves OAuth or API-key material into the backup (falling back to live `auth.json` when the backup is missing), config transforms in `codex_config.rs`/`services/proxy.rs` now fail closed instead of swallowing errors, and stale managed placeholders left in other provider tables are cleaned; the `preserveCodexOfficialAuthOnSwitch` setting copy was updated to clarify that takeover routing always preserves the official login and the toggle now only governs direct (non-routing) third-party switches.
- **GPT-5.6 Context Window for Claude Code Codex Takeover**: When Claude Code is routed to a ChatGPT Codex (Codex OAuth) backend via proxy takeover, CC Switch now injects `CLAUDE_CODE_MAX_CONTEXT_TOKENS` and `CLAUDE_CODE_AUTO_COMPACT_WINDOW` set to `372000` into the effective live `settings.json`, so Claude Code stops assuming its default 200K window for the unrecognized GPT model id and auto-compacts before the upstream rejects an oversized prompt. 372000 matches the ChatGPT Codex catalog window for gpt-5.6 — the catalog lists a ~353K effective budget, not the 1.05M API spec — and Claude Code's built-in output reserve and compact buffer then keep the actual compact trigger below that budget. The defaults are gated on every configured model env key (`ANTHROPIC_MODEL`, the haiku/sonnet/opus/fable defaults, and `CLAUDE_CODE_SUBAGENT_MODEL`) starting with `gpt-5.6`: gpt-5.5's upstream catalog oscillates between 272K and 372K and must not inherit them; any non-gpt-5.6 or mixed mapping is skipped (and any value a legacy shared snippet dragged in is stripped), while an explicit user value always wins. The Codex OAuth presets for both Claude Code and Claude Desktop bump their default routes to the gpt-5.6 family (haiku → `gpt-5.6-luna`, main/sonnet/opus → `gpt-5.6`), the custom Codex `config.toml` template default model moves to gpt-5.6, and the Claude Code preset ships both context env keys pinned at 372000 (see the corresponding Changed entry on preset-pinned context windows). On switch-away backfill the injected values are stripped as the mirror-inverse of the injection conditions so program defaults never harden into per-provider explicit values, and both keys are held out of the shared Claude common-config snippet (`services/provider/mod.rs` strip list plus a guard in `live.rs`) because context limits must follow the actual upstream model; six Rust integration tests cover injection, user overrides, legacy-snippet strip, non-gpt5.6 skip, and the backfill round-trip. (openai/codex#31860)
- **GPT-5.6 Sol/Terra/Luna Pricing With 1.25x Cache-Write Rate**: The usage dashboard now costs GPT-5.6 traffic using seeded pricing for the three tiers — Sol at 5 / 30 / 0.50, Terra at 2.50 / 15 / 0.25, and Luna at 1 / 6 / 0.10 USD per million input / output / cache-read tokens — cross-checked against OpenAI's pricing page and OpenRouter. Unlike GPT-5.5 and earlier, whose cache writes are seeded at zero, the 5.6 family bills prompt-cache writes at 1.25x the uncached input rate, so cache-write is seeded at 6.25 / 3.125 / 1.25 for Sol / Terra / Luna. The seed set also adds the bare `gpt-5.6` id as the official Sol alias plus effort-suffix variants (`-low`, `-medium`, `-high`, `-xhigh`, `-minimal`), all priced at Sol rates and mirroring the gpt-5.5 accounting shape. Rows are applied through the seed+repair dual-write path in `src-tauri/src/database/schema.rs`: a `repair_current_model_pricing` branch corrects the earlier zero cache-write seeds for the three tiers in existing databases, matching only rows still holding the exact old input/output/cache-read/cache-write values so user-customized prices survive, and no `SCHEMA_VERSION` bump is required.
- **Native Anthropic Messages Protocol as Codex Upstream**: Codex providers can now target a gateway that only exposes the native Anthropic Messages protocol (`/v1/messages`) via a new `anthropic` value on the upstream-format selector in the Codex form; the local proxy performs bidirectional Responses↔Anthropic request, response, and streaming conversion (new `transform_codex_anthropic` / `streaming_codex_anthropic` modules). The form adds an auth-field selector — `ANTHROPIC_AUTH_TOKEN` sends `Authorization: Bearer` (default) and `ANTHROPIC_API_KEY` sends `x-api-key`, mutually exclusive — an optional Claude Code client-impersonation toggle (`impersonateClaudeCode`, off unless explicitly enabled, spoofing User-Agent / `anthropic-beta` / `x-app` / system-prompt first line and dropping Codex/OpenAI fingerprint headers), and a per-provider `maxOutputTokens` override (Codex omits `model_max_output_tokens`, so without it the path falls back to a conservative 8192 that can truncate long or thinking-heavy replies). The bridge injects standard 5-minute ephemeral `cache_control` so system/tools/history are cached rather than resent at full price, defers stripping the `[1m]` long-context marker until after catalog matching and re-emits the `context-1m-2025-08-07` beta header, gates extended thinking on the trailing turn only so history-resending sessions don't permanently lose it after the first tool call, disables native `web_search` for this profile, drops `tool_choice` when no tools survive filtering, treats a base URL already ending in `/v1/messages` as a full endpoint, and reports truncated streams as incomplete/failed instead of completed. (#5071)
- **Default Model Field for Codex Provider Form**: The Codex provider form now exposes the top-level `model` key of `config.toml` as an editable field, so users can point an existing provider at a newly released model (e.g. `gpt-5.6`) without waiting for a preset update — preset changes only affect newly added providers, while existing ones keep their saved TOML. The field syncs bidirectionally with the TOML editor (mirroring the base-URL pattern), suggests models from the mapping catalog unioned with the provider's `/models` endpoint, offers a one-click "add to mapping" action when the value falls outside a non-empty catalog, and is hidden for official providers. Save-time catalog sync now backfills the first mapping row into the top-level `model` only when the field was left empty, so an explicit value always wins over the implicit row-0 fallback; model names and `base_url` are written with TOML basic-string escaping and control characters are stripped from field input, since `/models` ids are remote data and unescaped interpolation could inject `config.toml` lines such as a forged `[mcp_servers.*]`. The strict model-line matcher (`src/utils/providerConfigUtils.ts`) recognizes escaped output, empty strings, and single-quoted literals to keep extract/set round-trips stable, and the fetched model list is invalidated (with an in-flight sequence guard) whenever request identity changes — base URL, full-URL toggle, API key, or custom User-Agent — so the dropdown never shows a previous provider's models.
- **Switch-Time Common-Config Autosync Now Covers Codex**: When switching away from a Codex provider with `common_config_enabled`, the service now re-extracts the shareable portion of its live `config.toml` into the stored common-config snippet — the same live-to-snippet sync that previously ran only for Claude — so preferences you tweak directly in the running Codex config propagate to every opted-in provider and a removed key isn't silently re-injected on the next switch. Codex was gated out before because its TOML extractor leaked provider-specific and injected content; `extract_codex_common_config` now strips top-level `model`, `model_provider`, `base_url`, and `wire_api`, the entire `[model_providers]` table, `mcp_servers` and the legacy `[mcp.servers]` form, the top-level `experimental_bearer_token` fallback (which would otherwise leak the API key into the shared snippet), the `model_catalog_json` catalog pointer, and the injected `web_search = "disabled"` sentinel, while keeping a user-set `web_search` value as a shareable preference. The sync in `services/provider/mod.rs` is scoped strictly to Claude and Codex (Gemini is still excluded), skipped when the snippet was explicitly cleared, and all failures are warn-only and never block the switch; the autosync-before-strip ordering also self-heals stale snippet values previously baked into provider snapshots, since the re-extracted snippet matches the live values and the value-match strip removes them on the same switch.
- **Claude Subagent Model Configuration**: Claude providers can now pin a dedicated sub-agent model through a new "Subagent" row in the provider form, writing the `CLAUDE_CODE_SUBAGENT_MODEL` env key so Claude Code's spawned sub-agents run on a chosen (typically cheaper or faster) model. The row supports the `[1M]` marker but has no display-name field — it renders a "Not shown in /model" placeholder since the sub-agent model never appears in the `/model` menu — and the "quick set" button now fills it alongside the other tiers. On the proxy takeover path (`services/proxy.rs`), `CLAUDE_CODE_SUBAGENT_MODEL` is added to `CLAUDE_MODEL_OVERRIDE_ENV_KEYS` so it is written when a provider configures it and stale values are cleared when a provider omits it, and the model mapper (`proxy/model_mapper.rs`) gains a `subagent_model` field that passes a request through unchanged when its model (comparing with the 1M suffix stripped) matches the configured sub-agent model, instead of collapsing it to the default-model fallback. The key is also excluded from the shared Claude common-config snippet in `services/provider/mod.rs` so it can't leak across providers, and `modelRoleSubagent` / `modelNoDisplayName` labels were added in all four locales (zh/en/ja/zh-TW). (#4830)
- **1M Context Checkbox on the Claude Fallback Model Field**: The Claude provider form's fallback model field (`ANTHROPIC_MODEL`, the "default/fallback model") now carries the same 1M checkbox that the role-specific Sonnet/Opus/Fable rows already had, so a fallback model backed by a 1M-token window can declare it instead of silently being treated as 200K. Checking the box appends the `[1M]` marker to the fallback model id and unchecking strips it, reusing the shared `hasClaudeOneMMarker` / `setClaudeOneMMarker` / `stripClaudeOneMMarker` helpers, and the checkbox is a no-op when the model field is empty. The marker is written in the documented uppercase form (`[1M]`), which Claude Code parses case-insensitively. This is a frontend-only change to `ClaudeFormFields.tsx`. (#5124, fixes #3679)
- **Zhipu (智谱) Team Plan Quota Query Support**: Added quota-query support for Zhipu's team plan (团队套餐), which the personal-plan query could not reach: it hits the same `open.bigmodel.cn` quota endpoint with `?type=2` and requires two extra request headers, `bigmodel-organization` and `bigmodel-project`, so `detect_provider` cannot tell it apart from the personal plan by base URL alone. The usage-script modal gains a "Zhipu GLM Team" template with organization-ID and project-ID inputs (preserved when switching templates), and the backend routes on an explicit `coding_plan_provider == "zhipu_team"` identifier threaded through the usage script, IPC command, and background query path; the new `query_zhipu_team` in `services/coding_plan.rs` pins the CN site, appends `?type=2`, and shares response parsing (`zhipu_quota_from_body` / `parse_zhipu_token_tiers`) with the personal plan. All three of API key + organization ID + project ID are required — missing any one returns a NotFound that prompts the user to complete them (identifier match is case-insensitive) — and new copy was added across all four locales (zh/en/ja/zh-TW). (#5128)
- **OpenCode Provider Form Gains Headers and Per-Model Token-Limit Editors**: The OpenCode provider form now exposes two previously unreachable config fields as structured editors: a Headers section for the provider's `options.headers` (arbitrary HTTP headers like `HTTP-Referer` or `X-Title` sent with provider requests) with add/remove rows and case-insensitive duplicate-name rejection that reverts the input on conflict, and per-model Token Limits (Context and Output number inputs writing `model.limit.context` / `model.limit.output`, where clearing the field removes it and negative or non-finite values are rejected while valid ones are truncated to integers). The Extra Options block was reworked into a collapsible section (its i18n label stays "Extra Options") that auto-expands when options already exist. Both the headers and extra-options editors switched their placeholder draft keys to colon-bearing prefixes (`draft-header:` / `draft-option:`) — a code comment notes a colon is invalid in an HTTP field name so a draft key cannot collide with a legitimate header, and the same technique now guards option keys — and those drafts are stripped on save, fixing a bug where the old filter discarded any real option key literally starting with `option-` (the former placeholder prefix). New i18n keys were added across all four locales (zh/en/ja/zh-TW), and `aria-label`s were added for the header add/remove and model-details toggle controls in `OpenCodeFormFields.tsx`; the headers state and draft-stripping logic live in `useOpencodeFormState.ts`. (#2907)
- **Tencent Hunyuan Hy3 Model Pricing**: Seeded pricing for Tencent's Hunyuan Hy3 (released 2026-07-06, 256K context) so its usage is billed instead of showing $0. The rows are added to `seed_model_pricing` in `schema.rs` under both the `hunyuan-hy3` and `hy3` ids, since the upstream billing id isn't yet confirmed and one of the two should match logged usage. Prices follow Tencent's launch-day list rate (CNY 1 / 4 / 0.25 per Mtok input / output / cache-hit), converted at 1 USD ≈ 7.14 to $0.14 / $0.56 / $0.035, with `cache_write` left at `0` because no write rate is published. Hy3 actually uses input-length tiered billing (<16K / 16-32K / ≥32K) but the single-price table holds the lowest tier, so long-context requests are under-billed until this is revisited against the official billing page. Being a brand-new model it is seed-only, applied on next app start via `ensure_model_pricing_seeded` with no `SCHEMA_VERSION` bump.
### Changed
- **Codex Chat Prompt-Cache Routing**: Added provider-aware `prompt_cache_key` injection on the Codex Responses→Chat Completions bridge. Kimi Coding and OpenAI official endpoints are enabled automatically, Kimi's preset opts in explicitly, and unknown OpenAI-compatible gateways remain off to avoid strict-schema 400s. The key is taken from an explicit client value or a real client-provided session ID—never a generated per-request UUID—with an advanced Auto/Enabled/Disabled override in all four UI locales.
- **Provider Connectivity Configuration Simplified**: Removed the obsolete per-provider `testConfig` override (timeout, retry count, and degraded-latency threshold) from provider forms, frontend/backend provider metadata, and reachability-check merging. The lightweight `base_url` probe now always uses the single global connectivity-check configuration, while automatic failover remains driven exclusively by its separate proxy timeout and circuit-breaker settings. Also renamed the remaining settings UI and API modules from model-test terminology to connectivity-check terminology, removed the retired first-use confirmation flag, and deleted stale model/prompt/error copy across all four locales.
- **Codex Image Capabilities Are Inferred Without a User Toggle**: Generated Codex model catalogs now advertise only models in CC Switch's confirmed, exact text-only registry as `input_modalities = ["text"]`; GPT, aliases, new suffix variants, and all unknown models fail open to `["text", "image"]`. The rectifier's “Text-Only Model Preflight” switch continues to control only proactive proxy request-body replacement and does not change Codex's catalog declaration. Live catalog reverse-import also collapses inferred modalities instead of persisting them as hidden row overrides, so a later registry correction or a model's multimodal upgrade takes effect automatically; only declarations that differ from inference survive a DB-missing/import round-trip.
- **Context Window Values Pinned in Presets Instead of Editable Form Fields**: The `Codex` (ChatGPT/GPT-5.6) and `Kimi For Coding` presets no longer surface the "Max Context Tokens" and "Auto Compact Window" inputs in the provider form; the numbers are now hardcoded directly in the preset env (`372000`/`372000` for Codex, `262144`/`262144` for Kimi For Coding) instead of being exposed as editable `templateValues`. This drops two fields most users never need to touch while keeping both env keys present on purpose: since Claude Code resolves the compact window as `min(model window, value)`, setting it equal to the declared context window is behavior-neutral today, but pinning it explicitly shields the local compaction trigger against remote-config experiments that dial it down. The rare user who wants different numbers can still edit `CLAUDE_CODE_MAX_CONTEXT_TOKENS` / `CLAUDE_CODE_AUTO_COMPACT_WINDOW` directly in the provider's JSON editor. The change is confined to `claudeProviderPresets.ts` and its tests.
- **Universal Provider Auto-Syncs to Live Configs After Being Added**: Adding a universal (multi-app) provider through the Add Provider dialog now immediately pushes it to its live target configs instead of only saving it to the database, so the cross-app config lands in Claude/Codex/Gemini without a separate manual sync step. After `universalProvidersApi.upsert` succeeds, `AddProviderDialog` calls `universalProvidersApi.sync(provider.id)` and reports the combined outcome: a success toast (`universalProvider.addedAndSynced`) when the sync lands, or a non-blocking warning toast (`universalProvider.addedButSyncFailed`) when the provider was saved but the sync failed — while a save failure still aborts early with the existing error toast (`universalProvider.addFailed`) and never attempts the sync. The now-unused `universalProvider.addSuccess` key was removed from all four locales (zh/en/ja/zh-TW) and a new `universalProvider.addedButSyncFailed` key was added alongside the pre-existing `addedAndSynced` key. (#2811)
- **SudoCode Promoted to Paid Sponsor Across Six Clients**: The existing SudoCode preset — previously a `sudocode.us` provider that collided by name with an unrelated service — is replaced in place by the new paid sponsor SudoCode on `sudocode.chat`, now marked `isPartner` (gold star) with `partnerPromotionKey: "sudocode"` and a four-locale promo blurb (zh/en/ja/zh-TW). The preset spans six clients: Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, and Hermes; the Gemini entry was removed as outside sponsor scope. All endpoints move to `api.sudocode.chat` (the presets that carry an `endpointCandidates` list — Claude Code, Claude Desktop, Codex — collapse it to that single host, dropping the old `sudocode.run` fallback), and the `apiKeyUrl` points to the attributed `sudocode.chat/register?utm_source=ccswitch&utm_medium=partner` signup while `websiteUrl` moves to `sudocode.chat`. Claude Code and Claude Desktop use direct Anthropic passthrough with no model mapping, whereas Codex/OpenCode/OpenClaw/Hermes default to `gpt-5.6-sol` (replacing the old `gpt-5.5`) — Codex/Hermes/OpenClaw over the native Responses format and OpenCode via `@ai-sdk/openai`. The provider icon is refreshed to the new brand PNG, and the promo copy surfaces that one key drives Claude Opus 4.8 on the Claude apps and GPT-5.6 on Codex plus a CNY ¥10 trial-credit offer.
- **LongCat Presets Updated to LongCat-2.0**: The LongCat presets across Claude Code, Claude Desktop, Codex, Hermes, OpenClaw, and OpenCode now default to `LongCat-2.0` in place of the retired `LongCat-Flash-Chat` / `LongCat-2.0-Preview`, advertising the model's real 1M (1048576) context window and — for the Claude Code preset — a raised `CLAUDE_CODE_MAX_OUTPUT_TOKENS` of 131072 plus an added `ANTHROPIC_SMALL_FAST_MODEL` pin. OpenClaw's and OpenCode's base URL move to the `.../openai/v1` path (Codex and Hermes already used it), and the OpenClaw model entry gains `reasoning: false`, `input: ["text"]`, `maxTokens: 131072`, a bumped `contextWindow` of 1048576, and a new `compat.maxTokensField` (`max_tokens`) hint backed by a new optional `compat` field on the `OpenClawModel` type. Because LongCat-2.0 is text-only, the proxy's media sanitizer allowlist now classifies `longcat-2.0` (case-insensitively, keeping the retired `longcat-flash-chat` name for saved configs) so images pasted into a LongCat-2.0 session are replaced with the unsupported-image marker instead of being forwarded upstream and hard-rejected; a regression test covers the classification. (#4838)
- **Volcengine/Doubao/BytePlus Website Links Restored to Invite Pages**: Reverts the v3.16.5 change (`56248087`) that had pointed the `火山Agentplan`, `DouBaoSeed`, and `BytePlus` presets' `websiteUrl` at their product homepages. Per the commit, the `websiteUrl` for these three presets is intentionally the same ccswitch-attributed campaign/invite link as `apiKeyUrl`, so it is restored across all six app preset files — `火山Agentplan` back to its `activity/codingplan` link, `BytePlus` to its `product/modelark` link, and `DouBaoSeed` to the Ark console API-key page, each carrying the `utm_*` attribution params. The `apiKeyUrl` links were already left intact by the reverted commit and are unchanged.
- **Code0.ai Invite Link Updated to Agent Register URL**: The Code0.ai sponsor's `apiKeyUrl` moves from the `?source=ccswitch` referral to the new agent-register invite link `https://code0.ai/agent/register/B2XHxGjGmRvqgznY`, updated across all seven app presets and the Code0 sponsor rows in all four READMEs (`README.md`, `README_ZH.md`, `README_JA.md`, `README_DE.md`). The bare `code0.ai` API endpoint stays untouched.
- **Dropped the Redundant OpenAI Compatible Preset**: Removed the `OpenAI Compatible` custom-template preset from the OpenCode and OpenClaw preset lists so the picker no longer shows two entries pointing at the same place. It was a duplicate entry point — the built-in `custom` provider flow already exposes an interface-format selector whose default (`@ai-sdk/openai-compatible` / `openai-completions`) seeds a byte-identical starting config. Existing providers are unaffected, since presets only seed form defaults on creation and saved providers store concrete `npm`/`baseUrl`/`apiKey`/`models` values; the `@ai-sdk/openai-compatible` dropdown label is deliberately kept as the format option users pick in the custom flow, not the deleted preset.
### Fixed
- **Align Codex OAuth Client Identity to Fix 404s on Newest ChatGPT Models**: The newest ChatGPT-gated subscription models (e.g. `gpt-5.6-luna`) now resolve correctly when routed through the local proxy takeover with an official Codex OAuth account, instead of returning a misleading `404 Model not found` even though the account had access. ChatGPT's Codex backend performs model cohort routing by the `originator`+`version` header pair, and cc-switch previously identified takeover requests as `originator: cc-switch` with no version, landing them in a cohort where `gpt-5.6-luna` resolved to an undeployed internal engine. The `CodexOAuth` auth strategy in `proxy/providers/claude.rs` now sends `originator: codex_cli_rs` paired with `version: 0.144.1`, matching the real Codex CLI and satisfying luna's `minimal_client_version` requirement of `0.144.0`. Both headers must be sent together (dropping either re-triggers the 404), and the `CODEX_OAUTH_CLIENT_VERSION` constant must be bumped whenever a newer target model raises the minimum. A direct HTTP A/B test against the live backend confirmed the old identity 404ed while the aligned identity completed successfully, so no WebSocket transport workaround is required.
- **Fail Closed on Responses Upstream Failures Instead of Returning Empty Replies**: When the proxy bridges an Anthropic-format client (Claude Code / Claude Desktop pointed at a Responses gateway) to an OpenAI Responses upstream, a semantic upstream failure that arrives inside an HTTP 2xx body — a `{status:"failed"|"cancelled"}` object, an `error` envelope, or a `response.failed`/`error` SSE event before any output — is now surfaced as a real error so failover can select a different provider, instead of being converted into a silent, empty `end_turn`. The forwarder validates buffered/JSON bodies (`validate_responses_success_response`) and primes streaming responses up to 256 KB (`validate_responses_stream_start`) while still inside the retry loop, and the streaming converter emits an Anthropic `error` event then ignores every event after a terminal so a late delta cannot synthesize a spurious `message_start`. Streams that end cleanly after partial text now finalize as an explicit `max_tokens`-style incomplete turn with content blocks closed in protocol order, whereas a stream cut off mid tool-call or mid-reasoning (partial JSON or a missing thinking signature) yields a `stream_truncated` error rather than a corrupt turn, and on the Codex→Anthropic direction a truncated tool call is reported `incomplete` instead of `completed`. Gateways that ignore `stream:true` and return a single whole JSON document (even without a JSON content-type) are now recognized and expanded into a full Anthropic SSE lifecycle, and malformed client-supplied history is raised as an `InvalidRequest` — a `NonRetryable` category — so it fails fast instead of retrying across every provider. Touches `forwarder.rs`, `streaming_responses.rs`, `streaming_codex_anthropic.rs`, `transform_responses.rs`, and `transform_codex_anthropic.rs`.
- **Preserve Reasoning, Tool Results, and System Roles Across the Responses/Anthropic Bridge**: Content and token accounting that crosses the Responses↔Anthropic bridge in multi-turn tool loops is now preserved instead of being dropped or corrupted. Encrypted Responses `reasoning` items round-trip losslessly by being carried inside a versioned bridge-owned Anthropic thinking `signature` (or a `redacted_thinking` `data` field, prefixed `ccswitch-openai-reasoning-v1:`) and restored on replay, orphaned reasoning-only assistant turns are discarded so they cannot brick the next request with a missing-following-item error, and the streaming converter now consumes the official `response.reasoning_summary_text.*` / `response.reasoning_text.*` event vocabulary (keeping `response.reasoning.*` as a compatibility alias), tracks concurrent items by stable id/output-index, emits the signature before closing the block, and recovers tool arguments from `*.done` / `output_item.done` events when a gateway skips deltas. Non-streaming Responses→Anthropic tool calls with empty arguments normalize to `{}`, invalid arguments in an incomplete turn degrade to `{}` with a warning, and invalid arguments on a completed response are rejected, while on the Codex→Anthropic request path malformed or non-object arguments raise a non-retryable `InvalidRequest`, incomplete historical tool calls are dropped, and only signed thinking blocks are re-encoded as encrypted reasoning. Structured tool results now keep their `is_error` flag, text, base64/URL images, and PDF/`input_file` documents in both directions instead of collapsing to a canonical JSON string, and historical `system`/`developer` messages are hoisted into Anthropic `system` rather than being silently demoted to user turns. For accounting, usage from a successful upstream request is recorded even when the subsequent conversion fails, and the Codex→Anthropic bridge keeps standard 5-minute prompt caching on by default while honoring the dedicated `cache_injection` sub-switch. Touches `reasoning_bridge.rs`, `transform_responses.rs`, `transform_codex_anthropic.rs`, `streaming_responses.rs`, `handlers.rs`, and `forwarder.rs`.
- **Account for Cache-Write Tokens in Proxy Usage and Cost**: Prompt-cache write tokens are now billed correctly on the proxy usage dashboard. The parser reads cache writes from OpenAI/Codex-style usage details (`input_tokens_details.cache_write_tokens` and `prompt_tokens_details.cache_write_tokens`) and preserves them through every response-usage conversion path — Chat→Responses, Responses→Anthropic, Anthropic→Responses, and OpenAI/Chat→Anthropic (`transform_codex_chat.rs`, `transform_responses.rs`, `transform_codex_anthropic.rs`, `transform.rs`, `streaming.rs`) — so cache creation is no longer dropped when a request crosses a format boundary. Because a Codex/Gemini provider's reported `input_tokens` is inclusive of both cache reads and cache writes, the cost calculator now subtracts both before applying the input rate; previously only reads were removed, so cache-write tokens were double-charged at both the input rate and the cache-creation rate. To keep historical rows accurate across this shift, a new `input_token_semantics` column (schema v13; `0` legacy = subtract reads only, `1` total-inclusive = subtract reads and writes, `2` fresh = no subtraction) records how each row's `input_tokens` was stored, so the cost backfill subtracts reads only for pre-existing rows and reads-plus-writes for new total-inclusive rows, while daily rollups are normalized to fresh input and stamped `2`. v12 databases gain the column on both `proxy_request_logs` and `usage_daily_rollups` via `migrate_v12_to_v13`, and Claude-style rows stay fresh input with no subtraction.
- **Stronger Prompt-Cache Breakpoint Injection on the Proxy Bridge**: On the proxy paths that inject Anthropic `cache_control` breakpoints — the `codex_responses_to_anthropic` takeover bridge and the Bedrock native optimizer — the injector now spends its four-breakpoint budget more effectively so long, tool-heavy conversations keep hitting the prompt cache instead of re-sending system, tools, and history at full price every turn. Beyond marking the tools tail, system tail, and the latest cacheable message, it now adds a second older user anchor (`msgs-prior-user`) when at least four messages exist and budget remains, keeping the stable prefix inside Anthropic's 20-block lookback from the newest breakpoint. Injection is short-circuited unless both the optimizer master switch (`enabled`) and the `cache_injection` sub-switch are on, `thinking`/`redacted_thinking` blocks are never chosen as cache targets, and injected markers use Anthropic's standard 5-minute TTL. Caller-owned breakpoints are preserved verbatim — never deleted, reordered, or rewritten — and when a caller already ships more than the supported four, the injector logs a warning, adds no automatic breakpoints, and leaves the markers in place (`cache_injector.rs`, `forwarder.rs`).
- **Kimi For Coding's 256K Context Window Now Takes Effect**: The Kimi For Coding preset's standalone `CLAUDE_CODE_AUTO_COMPACT_WINDOW=262144` (added in #4401 and shipped in 3.16.4) never actually applied, because Claude Code caps unrecognized non-Claude model ids at a 200K window and resolves the compact window as `min(model window, value)`, clamping 262144 back down to 200K. The preset now pairs it with `CLAUDE_CODE_MAX_CONTEXT_TOKENS` (both pinned to 262144) and explicitly routes the endpoint's `kimi-for-coding` alias across `ANTHROPIC_MODEL` and all three tier keys (`ANTHROPIC_DEFAULT_HAIKU/SONNET/OPUS_MODEL`), since Claude Code ignores both context envs for `claude-`-prefixed ids — the non-Claude alias is what unlocks the enlarged window. For already-saved providers, `build_effective_settings_with_common_config` in `services/provider/live.rs` injects the same two context defaults (262144) into the effective live settings at switch time for any provider whose `ANTHROPIC_BASE_URL` is the Kimi coding endpoint (`https://api.kimi.com/coding`), with a mirror-inverse strip on backfill so an injected default never hardens into stored provider config and an explicit user value always wins. Because the alias routing lives only in the preset and not in the injection, a provider saved from the old preset — which set neither the alias nor `CLAUDE_CODE_MAX_CONTEXT_TOKENS` — stays effectively at 200K until the preset is re-applied. Three tests cover backfill injection, user-override preservation, and injected-only strip.
- **Deleted Codex MCP Servers No Longer Resurrected on Provider Activation**: MCP servers are owned by the database `mcp_servers` table and the `[mcp_servers]` section in Codex's live `config.toml` is only a projection re-synced after every write, but switching away used to bake that projection into the stored provider snapshot — so a server you deleted in the app came back to life the next time you activated that provider, and per-entry reconcile (which only knows rows still in the DB) could never clean up the orphan. Switch-away backfill now strips `[mcp_servers]` (and the legacy `[mcp.servers]` form) from the stored settings via `strip_codex_mcp_servers_from_settings`, and any snapshot already polluted self-heals the next time you switch away from it. One user-visible consequence: a hand-written `[mcp_servers.*]` section in a Codex provider's config is stripped out of the stored snapshot the first time you switch away, so going forward define Codex MCP servers through the MCP manager (the DB `mcp_servers` table) rather than by hand-editing the provider config.
- **Codex MCP Sync Fails Closed on an Unparseable config.toml**: When writing a single MCP server into Codex, `sync_single_server_to_codex` silently fell back to an empty `toml_edit` document if the existing `config.toml` failed to parse and then wrote the whole file back — wiping every other section (`model`, `model_providers`, comments) and leaving only the one synced `[mcp_servers.<id>]` entry, a destructive fallback left behind when an earlier fix (`3a548152`) hardened only the removal path. The sync path now returns an `McpValidation` error and leaves the file untouched, matching the fail-closed behavior already used on the read/validate path, so a temporarily malformed config can no longer be silently truncated to a single MCP table.
- **MCP Operations Now Report Per-App Failures Instead of Silently Blocking Others**: Importing MCP servers from apps used to swallow every importer error with `unwrap_or(0)`, so a corrupt Codex `config.toml` surfaced only as "imported 0 servers" with no hint anything went wrong; `import_from_all_apps` now imports each app (claude/codex/gemini/opencode/hermes) best-effort and, when any fail, reports an aggregated error naming the failing apps alongside the count that did import, with the frontend refreshing the server list on settle so partially-imported servers still appear. On the projection side `sync_all_enabled` iterated every app with `?`, so one app's unparseable live file (e.g. a broken `~/.claude.json` that passes the existence gate but fails to parse) blocked every app behind it in iteration order and bubbled a false "switch failed" back even though the target app's live file and the database were already updated; switch and save now re-project only the target app through the new `sync_enabled_for_app` and degrade projection failure to a warning, while config-import and cloud-restore keep the all-apps sweep but collect failures best-effort. Toggling `unify_codex_session_history`, which rewrites the current official provider's live `config.toml` in full and drops the `[mcp_servers]` projection, now re-projects Codex MCP through that same target-only path so enabled servers no longer silently vanish until the next provider switch. In every degraded case the projection self-heals on the next switch or MCP toggle.
- **Codex Common-Config Form Preserves Comments and Key Order**: Toggling "use common config" in the Codex provider form used to route the merge through the frontend `smol-toml` implementation, which re-serialized the whole document (parse → deep-merge → stringify): comments were dropped, keys were reordered, and empty parent table headers like `[model_providers]` were synthesized — the long-standing "config.toml keeps getting reordered" symptom. The merge now runs on a backend command backed by the same `merge_toml_table_like` / `remove_toml_table_like` used to write live configs, so the form preview and the live write share one merge semantic and hand-written formatting survives edit-time merges, and the frontend helper was deleted outright so the pattern can't come back. Because the operations are now async, a landing result is discarded unless it is still current: a per-hook sequence number covers rapid toggle/save races so an earlier merge resolving after a later removal can't flip the switch back (last operation wins), and a config-baseline check keeps a stale merge from clobbering a TOML the user hand-edited while the merge was in flight, with the checkbox self-healing via the existing inference effect.
- **Inject a Single Auth Placeholder on Managed Claude Takeover**: Switching the managed Claude provider from a third-party endpoint (DeepSeek/MiMo/…) to a Codex-managed provider wrote both `ANTHROPIC_API_KEY` and `ANTHROPIC_AUTH_TOKEN` as `PROXY_MANAGED` into `~/.claude/settings.json`, so Claude Code warned "Both ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY set - auth may not work as expected" on every run — an accretion artifact of the original API-key insert (#1049, Copilot) and the later `ANTHROPIC_AUTH_TOKEN` preservation that avoided a login prompt (#3784), which added the token without removing the key. The `ManagedAccount` branch of `apply_claude_takeover_fields_for_provider` now clears all token keys and injects exactly one placeholder: `ANTHROPIC_AUTH_TOKEN` for Codex-managed providers and `ANTHROPIC_API_KEY` for Copilot; local-proxy auth is unaffected because the forwarder resolves the `PROXY_MANAGED` placeholder from either header. Because enabling takeover short-circuits (`return Ok(())`) when the live config already matches the current proxy, users upgrading with an existing double-key `settings.json` may need to toggle Claude routing off and back on once (or switch providers) to trigger the rewrite. (#4919, #5095)
- **Skip Reachability Probes for Official Providers**: Health/connectivity checks no longer derive an unauthenticated probe target from an official provider's runtime adapter defaults. Batch `stream_check_all_providers` now skips any entry whose `category` is `official`, and `StreamCheckService::resolve_base_url` returns an explicit error for official providers instead of falling back to a first-party endpoint (e.g. `https://chatgpt.com/backend-api/codex`) and probing it without credentials — a request that is meaningless and always fails. The connectivity-check button is already hidden for these providers in `ProviderCard.tsx`, so this is defense-in-depth that also matters now that the built-in Codex official provider participates in takeover; a regression test covers the Codex official provider so a future adapter change cannot silently reintroduce the probe.
- **Usage and Quota Queries Reject Transient Transport Failures So Retry and Keep-Last-Good Work**: Usage and quota queries frequently showed spurious "query failed" states that a manual refresh could not clear, because every transport-level failure — including read-timeouts mid-body — was folded into `Ok(success:false)`, so react-query's retry never fired and the failure body poisoned the cache as if it were real data. The `balance`, `coding_plan`, and `subscription` services now return `Err` for send failures and body-read failures, reading the body via `bytes()` before `serde_json::from_slice` (reqwest's `json()` wraps read errors as `Decode`, which made error-kind checks on it dead code), while auth/4xx/parse errors stay `Ok(success:false)` and surface immediately; the script path maps transient `AppError` keys (`request_failed` / `read_response_failed`) to `Err`, Volcengine gains a `Transient` call variant, HTTP 429 is now classified transient alongside 5xx, and expired-credential retries propagate the transient error instead of rewriting it as "OAuth token has expired". The command layer skips snapshot persistence, the `usage-cache-updated` emit, and tray refresh on `Err` so the cache bridge cannot overwrite retained data. On the frontend, `resolveDisplayUsage` (generalized to subscription quotas via a new options object carrying a `rejected` flag) re-anchors stale success data retained by react-query across rejections to `dataUpdatedAt` so it expires through the same 10-minute keep-last-good window instead of masking a longer outage indefinitely, `useSubscriptionQuota` / `useCodexOauthQuota` gain keep-last-good with per-scope snapshot reset, rejected queries with no displayable value synthesize a failure placeholder carrying the real error message so the footer keeps a retry entry point, and `UsageScriptModal` surfaces string rejections via `extractErrorMessage`. (#3820)
- **Codex Sub-Agent Session Usage Now Counted in Local Statistics**: Token usage from Codex sub-agent (spawned agent) sessions was missing from local usage statistics because the parser keyed each session's synthetic `request_id` on `session_meta.session_id`, but a sub-agent's log carries the parent thread's `session_id` while its own unique identity lives in the `id` / `thread_id` field — so multiple sub-agents under one parent collapsed onto colliding request IDs and were dropped as duplicates. `session_usage_codex.rs` now extracts a unique `thread_id` per file (preferring `id` / `thread_id` over `session_id`) and prefixes request IDs with `codex_session:thread-v1:<thread_id>:<index>`, giving each sub-agent distinct rows. Because fork/sub-agent logs first replay the parent thread's history before the takeover event, the parser detects a history-snapshot boundary (via `forked_from_id` / `subagent` source markers plus a `thread_settings_applied` or `inter_agent_communication` event) and uses the replayed `token_count` events only to restore the cumulative baseline instead of double-counting them as new usage. Archived logs under `archived_sessions/` inherit the sync cursor from their original path by filename suffix, so re-parsing an archived copy imports only appended usage rather than re-importing the whole thread; the fix ships with unit tests covering identity extraction, replay-baseline accounting, distinct request IDs, and archived-cursor inheritance. (#5187)
- **Codex Free-Plan 30-Day Quota Window Now Renders in Footer and Tray**: Codex free accounts are metered on a rolling 30-day secondary window (`limit_window_seconds = 2,592,000`) instead of the weekly window paid plans use, and although the backend already mapped this to the tier name `30_day`, neither the frontend `TIER_I18N_KEYS` whitelist nor the tray's `TIER_LABEL_GROUPS` month group recognized it — so `SubscriptionQuotaView` filtered the tier out and, since it is the only surviving tier for a free account, the quota footer rendered nothing while the tray's `format_subscription_summary` returned `None` and showed no quota at all. A single-sourced `TIER_THIRTY_DAY` ("30_day") constant now ties together the backend `window_seconds_to_tier_name` mapping (which maps `2_592_000` explicitly), the tray's month ("m") group, and the frontend whitelist (`30_day → subscription.thirtyDay`), with the `thirtyDay` label added to all four locales (zh/en/ja/zh-TW). Two regression tests lock in the window-seconds-to-tier mappings and assert that a 30-day-only Codex account still renders in the tray, guarding the invariant that any tier visible in the footer must not leave the tray blank. (#3651, #4886)
- **Usage Dashboard Refresh Interval Persists Across Restarts**: The usage dashboard's auto-refresh cadence was component-local state that reset to 30s on every restart, so a user who chose a different interval (off, 5s, 10s, or 60s) had to re-select it each session. The dashboard now reads and writes the choice through a new `usageDashboardRefreshIntervalMs` app setting: `UsageDashboard` takes `refreshIntervalMs` / `onRefreshIntervalChange` props wired from `SettingsPage` to the persisted settings, initializes from the saved value (normalized to a known option, falling back to the 30s default for unrecognized values), and applies changes optimistically — invalidating the usage queries immediately — while rolling back the selection if the save fails. The field is added to the Rust `AppSettings` struct, the TypeScript `Settings` type, and the zod settings schema, and three component tests cover mount-from-saved, persist-on-change, and rollback-on-failure. (#5057)
- **Exclude Fable Tier Model Env Keys From the Shared Claude Common Config**: Fable is Claude's fourth model-mapping tier (added in v3.16.3), but its `ANTHROPIC_DEFAULT_FABLE_MODEL` / `ANTHROPIC_DEFAULT_FABLE_MODEL_NAME` env keys were missing from the provider-specific exclusion list, so a provider's Fable model pin could leak into the shared common-config snippet and then be injected into other providers on the next switch. Both keys are now stripped in `extract_claude_common_config` alongside the haiku/sonnet/opus pins, guarded by a regression test. The same commit also completes Fable's proxy-takeover support: the two keys are added to `CLAUDE_MODEL_OVERRIDE_ENV_KEYS` (now 12 entries) and the takeover writes a stable `claude-fable-5` role alias (`CLAUDE_TAKEOVER_FABLE_MODEL`), reapplying it when the provider configures Fable and clearing stale values otherwise, mirroring the other three tiers; when Fable is unconfigured no alias is written and the mapper falls back fable→opus. (#5206, fixes #4272)
- **Default Missing Tool Schema Types and Restore the API Key Field for Uncategorized Providers**: Two provider-facing fixes. First, when a client sends a tool whose Anthropic `input_schema` omits the top-level `type` or is an empty `{}`, the proxy passed the schema through the shared `clean_schema` helper without filling in the missing type, producing an OpenAI `parameters` object that strict gateways reject. `clean_schema` (defined once in `transform.rs` and reused by the Responses converter via `super::transform::clean_schema`, so both the Anthropic→OpenAI Chat and Responses paths pick up the change) now defaults a missing root `type` to `"object"` — adding an empty `properties: {}` only when properties is also absent — and applies the defaulting only to the root schema via a new `is_root` flag threaded through a `clean_schema_inner` helper, so nested sub-schemas (an `anyOf` string/null union, an `items`-only array) are preserved unchanged rather than having a spurious `type: "object"` forced onto them. Second, editing a provider with no category (a historically imported or hand-built custom provider) hid the Claude API key input, because `useApiKeyState` only showed and auto-created the field in add mode with a defined non-official category; the gate now keys off category alone, showing and populating the field for any provider that isn't `official` or `cloud_provider` — including the undefined-category edit case — while `official` (OAuth-only, input disabled) and `cloud_provider` (which use dedicated auth fields rather than an Anthropic key) stay conservative. New unit and hook tests cover both converters and the uncategorized/official/cloud-provider key paths. (#5069)
- **Media Fallback for Volcano GLM 5.2 Text-Only Image 400s**: When the local proxy takes over a Volcano Coding Plan provider running GLM 5.2, image blocks in a request no longer produce a dead 400 — the rectifier's media fallback in `src-tauri/src/proxy/media_sanitizer.rs` now covers GLM 5.2 on both its preventive and reactive paths. Preventively, the text-only model registry gains `glm-5.2` as an exact tail match (deliberately not a prefix, so a future multimodal `glm-5.2v` variant following Zhipu's 4v/5v naming keeps its images), stripping image blocks before they reach the text-only endpoint. Reactively, because the gateway's error `Model only support text input` never mentions image/vision/media and drops the third-person `s`, a new self-evident phrase list (`only support text` / `only supports text`) now asserts a modality rejection on its own and bypasses the `mentions_image` gate that previously discarded the error before the fallback hints could run; the old `only supports text` hint is folded into that list. Regression tests exercise the verbatim #5025 error body, the `glm-5.2[1M]` mapped-model form, and a `glm-5.2v` negative assertion. (#5025)
- **Display Renamed Codex Session Titles**: Sessions renamed inside Codex now show their renamed titles in the session manager instead of silently falling back to the original first-message text. The scanner (`src-tauri/src/session_manager/providers/codex.rs`) loads thread titles from Codex's `session_index.jsonl` and `state_5.sqlite` — resolving the DB path from `sqlite_home` / `CODEX_SQLITE_HOME` through a new shared `codex_state_db` module that deduplicates path resolution previously copy-pasted with the history-migration code — and prefers a stored thread title over the first user message when building each session's display title. The read-only title query adds a `busy_timeout` so a lookup during a concurrent Codex write no longer fails immediately with `SQLITE_BUSY` and drops back to the first message, and the `title == first_user_message` check is pushed into a NULL-safe SQL `WHERE` clause (matching Codex's `distinct_thread_metadata_title` semantics) so the potentially large `first_user_message` column no longer crosses into Rust. (#4927)
- **Live Config Edits for OpenCode, OpenClaw, and Hermes Now Sync Into the Database on Startup**: The startup auto-import for the live-config-managed apps previously skipped any provider whose id already existed in the database, so edits made directly to the OpenCode, OpenClaw, or Hermes (`~/.hermes/config.yaml`) live files — a changed base URL, an added or renamed model — were never picked up after the first import and silently diverged from the app's stored copy. The `import_{opencode,openclaw,hermes}_providers_from_live` functions in `services/provider/live.rs` now look up each existing provider and, when its live `settings_config` differs, rewrite the stored provider from the live file (OpenCode additionally refreshes the display name from the live `name`, falling back to the existing name rather than the id); the returned count now covers both new imports and updates, and the startup log line changed from "Imported N" to "Synced N". The sync runs on every launch from `lib.rs::run()`, is a no-op when nothing changed, and is fully non-fatal — a provider that vanishes mid-import or a DB lookup error is warned and skipped, and users with no live file still take the quiet `Ok(0)` path. (#4712, #5098)
- **OpenCode Session Resume Command Updated to Current CLI Syntax**: The Session Manager displayed and copied `opencode session resume <id>` as the resume command for OpenCode sessions, which is not the OpenCode CLI's current resume syntax, so pasting the copied command would not resume the session. Both the SQLite-scanned (`scan_sessions_sqlite`) and JSON-parsed (`parse_session`) session paths in `session_manager/providers/opencode.rs` now emit `opencode -s <id>` to match the CLI's actual resume flag. (#2359)
### Docs
- **Codex + Kimi Local Routing Guides**: New step-by-step guides explain how to run Kimi inside the Codex CLI through CC Switch's local routing, motivated by a protocol mismatch: the newer Codex CLI targets the OpenAI Responses API while both Kimi Open Platform and Kimi For Coding expose the OpenAI Chat Completions shape (`/chat/completions`), so pointing a Kimi endpoint straight at Codex typically 404s on `/responses` or yields streams Codex cannot parse. The guides walk through adding a Codex provider from the built-in `Kimi` (Open Platform, pay-as-you-go, `kimi-k2.7-code`) or `Kimi For Coding` (membership, `kimi-for-coding`) preset, and lay out the four-step conversion chain: Codex is written to talk to `http://127.0.0.1:15721/v1` with `wire_api = "responses"` kept in place, the provider's `meta.apiFormat = "openai_chat"` flags the real upstream as Chat, the route rewrites `/responses` to `/chat/completions` and converts the body, then converts the upstream Chat JSON/SSE back into the Responses shape Codex expects. Added under `docs/guides/` in English, Japanese, and Chinese with accompanying UI screenshots.
- **new-api Sponsor Row in READMEs**: The open-source AI infrastructure project `new-api` is appended as the newest entry in the sponsor table across all four localized READMEs (`README.md`, `README_ZH.md`, `README_JA.md`, `README_DE.md`), along with its banner asset.
### Internal
- **Harden Release Supply Chain**: Added a `.github/CODEOWNERS` that, together with branch protection's Code Owners rule, requires owner review before any PR merges to `main` (with `/.github/` and `/src-tauri/` pinned explicitly alongside the global `*` fallback, all to `@farion1231`), gated the release job behind a `release` environment in `release.yml` so signing secrets unlock only after manual approval, and removed `.github` from `.gitignore` so the previously untracked `labeler.yml` and `workflows/labeler.yml` are now versioned.
- **Settle pnpm Build-Script Approvals**: Approved `esbuild` (via `onlyBuiltDependencies`) and ignored `msw` (via `ignoredBuiltDependencies`) in `pnpm-workspace.yaml` so pnpm 10.13+ stops appending `allowBuilds` placeholders to the file on every install.
- **Platform-Gate the Desktop-Scope Assertion in the Profile Roundtrip Test**: The `profile_roundtrip` integration test's Claude Desktop assertion is now `cfg`-gated to macOS/Windows to match the already cfg-gated desktop switch, so on Linux CI (where desktop live writes error) it expects the seeded `d1` instead of `d2` and no longer panics, poisons the shared test mutex, and cascades into two more failures.
## [3.16.5] - 2026-07-01
Development since v3.16.4 reworks the Codex native-Responses path — restoring a generated model catalog for proxy-less direct-connect, decoupling model mapping from the local-routing toggle, and adding a host/model-prefix blacklist that disables Codex's built-in web_search on gateways that reject it — alongside a broad wave of new provider presets (Qiniu and Code0.ai across all seven apps, the FennoAI/ZetaAPI/TeamoRouter/NekoCode partners, and the non-partner Amux), Claude Sonnet 5 pricing plus a default-tier bump to it, a categorized two-level session view with group-level batch selection, live auto-sync of the shared Claude common config on switch, and a run of credential-safety, tool-detection, Doubao model-id, branding, and icon-size fixes.
+90 -80
View File
@@ -2,7 +2,7 @@
# CC Switch
### The All-in-One Manager for Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw & Hermes Agent
### The All-in-One Manager for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok Build, OpenCode, OpenClaw & Hermes Agent
[![Version](https://img.shields.io/github/v/release/farion1231/cc-switch?color=blue&label=version)](https://github.com/farion1231/cc-switch/releases)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](https://github.com/farion1231/cc-switch/releases)
@@ -25,11 +25,11 @@ English | [中文](README_ZH.md) | [日本語](README_JA.md) | [Deutsch](README_
<details open>
<summary>Click to collapse</summary>
[![Kimi K2.7 Code](assets/partners/banners/kimi-banner-en.png)](https://platform.kimi.ai?aff=cc-switch)
[![Kimi K2.7 Code](https://gcdn.moonshot.cn/growth-cdn/sponsor/kimi-en.png)](https://platform.kimi.ai?aff=cc-switch)
Kimi K2.7 Code is an open-source, coding-focused agentic model developed by Moonshot AI. It delivers stronger coding and agent performance, with substantial improvements in real-world long-horizon coding tasks. These gains translate into higher end-to-end task success rates across complex software engineering workflows. K2.7 Code also improves reasoning efficiency, reducing thinking-token usage by approximately 30% compared with K2.6. **[Click here to start using Kimi](https://platform.kimi.ai?aff=cc-switch)**
Kimi K3 is Moonshot AI's most capable model and the world's first open 3T-class model. With 2.8 trillion parameters, native vision, and a 1-million-token context window, K3 delivers frontier performance across long-horizon coding, knowledge work, and reasoning. CC Switch makes it easy to configure and switch to Kimi across agentic tools. **[Click here to start using Kimi](https://platform.kimi.ai?aff=cc-switch)**
Doing mostly coding work? Try the **[Kimi For Coding plan](https://www.kimi.com/code/?aff=cc-switch)** — a subscription service built for coding!
Doing mostly coding work? Try the **[Kimi Code subscription](https://www.kimi.com/code/?aff=cc-switch)**.
---
@@ -40,8 +40,13 @@ Doing mostly coding work? Try the **[Kimi For Coding plan](https://www.kimi.com/
</tr>
<tr>
<td width="180"><a href="https://aigocode.com/invite/CC-SWITCH"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>Thanks to AIGoCode for sponsoring this project! AIGoCode is an all-in-one platform that integrates Claude Code, Codex, and the latest Gemini models, providing you with stable, efficient, and highly cost-effective AI coding services. The platform offers flexible subscription plans, zero risk of account suspension, direct access with no VPN required, and lightning-fast responses. AIGoCode has prepared a special benefit for CC Switch users: if you register via <a href="https://aigocode.com/invite/CC-SWITCH">this link</a>, you'll receive an extra 10% bonus credit on your first top-up!</td>
<td width="180"><a href="https://zetaapi.ai/go/u117"><img src="assets/partners/logos/zetaapi-banner.png" alt="ZetaAPI" width="150"></a></td>
<td>Thanks to ZetaAPI for sponsoring this project! ZetaAPI focuses on real model fidelity, no watered-down responses, no quality degradation, and pricing as low as 35% of official rates. The platform does not mix traffic, secretly replace models with lower-quality alternatives, or use fake model routing. It supports Claude Code, Codex, Gemini, ChatGPT, and other mainstream AI models, helping users significantly reduce API costs while maintaining reliable model quality. At the same time, ZetaAPI provides enterprise-grade SLA-backed stability, standard API compatibility, one API key for multiple models, fast integration, and pay-as-you-go billing, making it suitable for AI products, coding agents, internal business tools, customer service systems, content generation, and automation workflows. If any model is verified to be inconsistent with its stated quality, ZetaAPI backs it with a 10x compensation guarantee, giving users a more stable, transparent, and trustworthy experience. Register via <a href="https://zetaapi.ai/go/u117">this link</a> and use the promo code CC-SWITCH during your first recharge to enjoy an exclusive 10% discount on your first top-up, just for CC Switch users!</td>
</tr>
<tr>
<td width="180"><a href="https://apinebula.com/VjM74M"><img src="assets/partners/logos/apinebula_banner.png" alt="APINebula" width="150"></a></td>
<td>Thanks to APINEBULA for sponsoring this project! APINEBULA, an enterprise-grade AI aggregation platform under Galaxy Video Bureau, leverages extensive platform resources to provide developers, teams, and enterprises with stable, cost-effective access to large language model APIs. The platform integrates leading, full-powered models like Claude, GPT, and Gemini, allowing you to connect to the world's top AI models through a single API, with prices starting as low as 10% of the original cost. Designed for AI programming, Agent development, and business system integration, APINEBULA supports enterprise-grade high concurrency, formal contracts, corporate bank transfers, and invoicing services. APINEBULA provides special discounts for our software users: register using <a href="https://apinebula.com/VjM74M">this link</a> and enter the <strong>"ccswitch"</strong> promo code during your first recharge to get <strong>10% off</strong>.</td>
</tr>
<tr>
@@ -50,11 +55,6 @@ Doing mostly coding work? Try the **[Kimi For Coding plan](https://www.kimi.com/
Claude Code / Codex / Gemini official channels at 38% / 2% / 9% of original price, with extra discounts on top-ups! AICodeMirror offers special benefits for CC Switch users: register via <a href="https://www.aicodemirror.com/register?invitecode=9915W3">this link</a> to enjoy 20% off your first top-up, and enterprise customers can get up to 25% off!</td>
</tr>
<tr>
<td width="180"><a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF"><img src="assets/partners/logos/shengsuanyun.png" alt="Shengsuanyun" width="150"></a></td>
<td>Thanks to Shengsuanyun for sponsoring this project! Shengsuanyun is a super factory serving AI Native Teams — an industrial-grade AI task parallel execution platform. Its model marketplace aggregates Claude, ChatGPT, Gemini, and other domestic and international LLM and multimedia model capabilities with direct supply. Absolutely no reverse engineering or dilution — platform-wide model SLA availability reaches 99.7%, with <a href="https://watch.shengsuanyun.com/status/shengsuanyun">monitoring dashboards</a> showing green across the board. It also offers enterprise-grade custom gateways for fine-grained team cost and permission management, smart routing, security protection, and BYOK (Bring Your Own Key) hosting. The platform charges on a pay-per-use and tokens plan (coming soon) basis, with invoicing available. Register via <a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF">this link</a> as a new user to receive ¥10 in credits plus a 10% bonus on your first top-up.</td>
</tr>
<tr>
<td width="180"><a href="https://pateway.ai/?ch=etzpm8&aff=WB6M6F67#/"><img src="assets/partners/logos/pateway.png" alt="PatewayAI" width="150"></a></td>
<td>Thanks to PatewayAI for sponsoring this project! PatewayAI is an API relay service provider built for heavy AI developers, focused on directly relaying official high-quality model APIs. It offers the full Claude lineup and the Codex series, 100% sourced from official channels — no dilution, no fakes, verification welcome. Billing is transparent and every token-level invoice can be audited line by line.
@@ -62,6 +62,51 @@ It also supports enterprise-grade concurrency and provides a dedicated managemen
Register now via <a href="https://pateway.ai/?ch=etzpm8&aff=WB6M6F67#/">this link</a> to receive $3 in trial credit. Top-ups go as low as 60% of the original price, with a two-way referral bonus of up to $150!</td>
</tr>
<tr>
<td width="180"><a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL"><img src="assets/partners/logos/fenno-banner.png" alt="Fenno.ai" width="150"></a></td>
<td>Thanks to Fenno.ai for sponsoring this project! Fenno.ai is a stable and efficient API relay service provider, currently focused on Codex relay. It is compatible with the OpenAI and Anthropic protocols and can be flexibly used from Codex, Claude Code, OpenCode, and other mainstream coding tools. It reliably supports enterprise-grade workloads of hundreds of billions of tokens per day, with corporate (B2B) settlement and invoicing for both domestic and overseas entities. Fenno.ai offers an exclusive benefit for CC Switch users: subscribe via <a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL">this link</a> to the incredible ¥9.9 Coding Plan worth $150 in credits, and earn up to 20% in referral rewards — invite more, earn more!</td>
</tr>
<tr>
<td width="180"><a href="https://runapi.co/register?aff=iOKB"><img src="assets/partners/logos/runapi.jpg" alt="RunAPI" width="150"></a></td>
<td>Thanks to RunAPI for sponsoring this project! RunAPI is a high-performance and reliable AI model API gateway — one API key gives you access to 150+ mainstream models including OpenAI, Claude, Gemini, DeepSeek, and Grok, with prices as low as 10% of the official rate and excellent stability. It works seamlessly with Claude Code, OpenClaw, and other tools. Exclusive benefit for CC Switch users: register via <a href="https://runapi.co/register?aff=iOKB">this link</a> and enjoy a 10% discount on your first top-up!</td>
</tr>
<tr>
<td width="180"><a href="https://unity2.ai/register?source=ccs"><img src="assets/partners/logos/unity2.jpg" alt="Unity2.ai" width="150"></a></td>
<td>Thanks to Unity2.ai for sponsoring this project! Unity2.ai is a high-performance AI model API relay platform for individual developers, teams, and enterprises. Long trusted by leading companies in China, it serves over 30 billion tokens per day and supports high concurrency at the 5,000 RPM level. It offers balance-based billing, first top-up bonuses, bundle subscriptions, corporate invoicing, and dedicated support. Register via <a href="https://unity2.ai/register?source=ccs">this link</a> to get $2 in credits, plus another $10 for joining the official group — up to $12 in free credits!</td>
</tr>
<tr>
<td width="180"><a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF"><img src="assets/partners/logos/shengsuanyun.png" alt="Shengsuanyun" width="150"></a></td>
<td>Thanks to Shengsuanyun for sponsoring this project! Shengsuanyun is a super factory serving AI Native Teams — an industrial-grade AI task parallel execution platform. Its model marketplace aggregates Claude, ChatGPT, Gemini, and other domestic and international LLM and multimedia model capabilities with direct supply. Absolutely no reverse engineering or dilution — platform-wide model SLA availability reaches 99.7%, with <a href="https://watch.shengsuanyun.com/status/shengsuanyun">monitoring dashboards</a> showing green across the board. It also offers enterprise-grade custom gateways for fine-grained team cost and permission management, smart routing, security protection, and BYOK (Bring Your Own Key) hosting. The platform charges on a pay-per-use and tokens plan (coming soon) basis, with invoicing available. Register via <a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF">this link</a> as a new user to receive ¥10 in credits plus a 10% bonus on your first top-up.</td>
</tr>
<tr>
<td width="180"><a href="https://aigocode.com/invite/CC-SWITCH"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>Thanks to AIGoCode for sponsoring this project! AIGoCode is an all-in-one platform that integrates Claude Code, Codex, and the latest Gemini models, providing you with stable, efficient, and highly cost-effective AI coding services. The platform offers flexible subscription plans, zero risk of account suspension, direct access with no VPN required, and lightning-fast responses. AIGoCode has prepared a special benefit for CC Switch users: if you register via <a href="https://aigocode.com/invite/CC-SWITCH">this link</a>, you'll receive an extra 10% bonus credit on your first top-up!</td>
</tr>
<tr>
<td width="180"><a href="https://subrouter.ai/register?aff=l3ri"><img src="assets/partners/logos/subrouter-banner.png" alt="SubRouter" width="150"></a></td>
<td>Thanks to SubRouter for sponsoring this project! SubRouter is a marketplace and smart routing platform for AI service operators. Merchants can launch operating sites, publish packages, manage users, models, and pricing, while users discover services and access reliable AI models through one unified API. Register via <a href="https://subrouter.ai/register?aff=l3ri">this link</a>!</td>
</tr>
<tr>
<td width="180"><a href="https://apikey.fun/register?aff=CCSwitch"><img src="assets/partners/logos/apikey_banner.png" alt="APIKEY.FUN" width="150"></a></td>
<td>Thanks to APIKEY.FUN for sponsoring this project! APIKEY.FUN is a professional enterprise-grade AI relay platform dedicated to providing stable, efficient, and low-cost AI model API access for enterprises and individual developers. The platform supports popular mainstream models such as Claude, OpenAI, and Gemini, with prices as low as 7% of official rates. Register through this project's <a href="https://apikey.fun/register?aff=CCSwitch">exclusive link</a> to enjoy an exclusive offer of up to <strong>permanent 5% off top-ups</strong>.</td>
</tr>
<tr>
<td width="180"><a href="https://console.claudeapi.com/register?aff=pCLD"><img src="assets/partners/logos/claudeapi.png" alt="ClaudeAPI" width="150"></a></td>
<td>This project is sponsored by <a href="https://console.claudeapi.com/register?aff=pCLD">Claude API</a>. Direct Claude API access — connect Claude Code and Agent apps in 3 minutes. New users can claim a free trial credit.Powered by official Anthropic API keys + AWS Bedrock official channels. No reverse engineering, no model degradation. Full support for Opus / Sonnet / Haiku model lineup, with official capabilities preserved including Tool Use, 1M context window, and more. Built for Claude Code power users, Agent engineers, and enterprise engineering teams. Invoicing and dedicated team support available. Click <a href="https://console.claudeapi.com/register?aff=pCLD">here</a> to register!</td>
</tr>
<tr>
<td width="180"><a href="https://code0.ai/agent/register/B2XHxGjGmRvqgznY"><img src="assets/partners/logos/code0.png" alt="code0.ai" width="150"></a></td>
<td>Thanks to <a href="https://code0.ai/agent/register/B2XHxGjGmRvqgznY">code0.ai</a> for sponsoring this project! code0.ai is an AI coding service platform built for developers, supporting Claude Code, Codex, Gemini, and other mainstream AI coding capabilities. It helps individual developers and teams use AI Agents more stably and efficiently for coding, debugging, refactoring, and automation workflows. ccswitch users can contact customer support via the <a href="https://code0.ai/agent/register/B2XHxGjGmRvqgznY">code0.ai website</a> to claim test credits and experience a reliable AI coding service.</td>
</tr>
<tr>
<td width="180"><a href="https://teamorouter.com/?utm_source=cc_switch&utm_medium=referral&utm_campaign=ai_directory"><img src="assets/partners/logos/TeamoRouter-banner.png" alt="TeamoRouter" width="150"></a></td>
<td>Thanks to TeamoRouter for sponsoring this project! TeamoRouter is an enterprise-grade Agentic LLM gateway built for developers, AI teams, and businesses. Without requiring any subscriptions, it lets you access Claude Code, Codex, Gemini CLI, OpenAI Codex, and other popular AI agents through a single unified API, while offering API pricing at discounts of up to 90%.
@@ -69,6 +114,16 @@ Unlike typical API relay services, TeamoRouter aggregates hundreds of official m
TeamoRouter also offers enterprise features including centralized billing, team management, BYOK, smart routing, usage analytics, dynamic provider optimization, and dedicated support. For an even simpler experience, Teamo Desktop lets you use Claude Code, Codex, Gemini CLI, and other popular AI agents with one-click setup—no API key management or manual gateway configuration required. Register via <a href="https://teamorouter.com/?utm_source=cc_switch&utm_medium=referral&utm_campaign=ai_directory">this link</a> as a new user to receive 10% off your first top-up.</td>
</tr>
<tr>
<td width="180"><a href="https://www.newapi.ai/"><img src="assets/partners/logos/newapi-banner.png" alt="new-api" width="150"></a></td>
<td>Thanks to the open-source AI infrastructure project <a href="https://www.newapi.ai/">new-api</a> for its strong support of this project! new-api is an open-source AI infrastructure project from QuantumNous and one of the leading unified LLM access-and-distribution projects by activity and adoption, focused on helping developers, teams, and enterprises build manageable, scalable AI service platforms at lower cost. As a fellow project rooted in the open-source ecosystem, new-api hopes to sponsor and support the continued growth of more outstanding open-source projects. 🌟 Star new-api to show your support: <a href="https://github.com/QuantumNous/new-api">https://github.com/QuantumNous/new-api</a>. Website: <a href="https://www.newapi.ai/">https://www.newapi.ai/</a>.</td>
</tr>
<tr>
<td width="180"><a href="https://claudecn.ai/register?aff=HEL9"><img src="assets/partners/logos/claudecn.jpg" alt="ClaudeCN" width="150"></a></td>
<td>Thanks to ClaudeCN for sponsoring this project! ClaudeCN is an enterprise-grade AI gateway platform operated by a registered company. It delivers high-availability commercial API access to popular models including Claude, GPT, and DeepSeek, and is built around formal enterprise procurement workflows — corporate bank transfers, signed contracts, and full compliance. Register via <a href="https://claudecn.ai/register?aff=HEL9">this link</a>!</td>
</tr>
<tr>
<td width="180"><a href="https://www.byteplus.com/en/product/modelark?utm_campaign=hw&utm_content=ccswitch&utm_medium=devrel_tool_web&utm_source=OWO&utm_term=ccswitch"><img src="assets/partners/logos/byteplus.png" alt="BytePlus" width="150"></a></td>
<td>Thanks to Dola seed for sponsoring this project! Dola Seed 2.0 is a fullmodal general large model independently developed by ByteDance for the global market. Built on a unified multimodal architecture, it supports joint understanding and generation of text, images, audio, and video. It natively enables agent collaboration, with strong reasoning, longtask execution, tool integration, and coding capabilities. It is widely applicable to smart cockpits, personal assistants, education, customer support, marketing, retail, and other scenarios. It excels in multimodal perception, endtoend complex task delivery, stable interaction, and data security, and is readily accessible and deployable via the ModelArk platform.Register via <a href="https://www.byteplus.com/en/product/modelark?utm_campaign=hw&utm_content=ccswitch&utm_medium=devrel_tool_web&utm_source=OWO&utm_term=ccswitch">this link</a> to get 500,000 tokens of free inference quota per model.<a href="https://www.volcengine.com/activity/ai618?utm_campaign=hw&utm_content=hw&utm_medium=devrel_tool_web&utm_source=OWO&utm_term=ccswitch"> >>中国大陆地区的开发者请点击这里</a></td>
@@ -80,13 +135,13 @@ TeamoRouter also offers enterprise features including centralized billing, team
</tr>
<tr>
<td width="180"><a href="https://cubence.com/signup?code=CCSWITCH&source=ccs"><img src="assets/partners/logos/cubence.png" alt="Cubence" width="150"></a></td>
<td>Thanks to Cubence for sponsoring this project! Cubence is a reliable and efficient API relay service provider, offering relay services for Claude Code, Codex, Gemini, and more with flexible billing options including pay-as-you-go and monthly plans. Cubence provides special discounts for CC Switch users: register using <a href="https://cubence.com/signup?code=CCSWITCH&source=ccs">this link</a> and enter the "CCSWITCH" promo code during recharge to get 10% off every top-up!</td>
<td width="180"><a href="https://nekocode.ai?aff=CCSWITCH"><img src="assets/partners/logos/nekocode-banner.png" alt="NekoCode" width="150"></a></td>
<td>Thanks to <a href="https://nekocode.ai?aff=CCSWITCH">NekoCode</a> for sponsoring this project! NekoCode provides developers with a stable, efficient, and reliable API relay service for Claude, Codex, and other AI models. With transparent pricing and flexible pay-as-you-go billing, it offers a simple and cost-effective way to access AI models. CC Switch users can enjoy an exclusive 10% discount: register via <a href="https://nekocode.ai?aff=CCSWITCH">this link</a> and enter promo code <code>cc-switch</code> during recharge to receive 10% off your top-up!</td>
</tr>
<tr>
<td width="180"><a href="https://www.dmxapi.cn/register?aff=bUHu"><img src="assets/partners/logos/dmx-en.jpg" alt="DMXAPI" width="150"></a></td>
<td>Thanks to DMXAPI for sponsoring this project! DMXAPI provides global large model API services to 200+ enterprise users. One API key for all global models. Features include: instant invoicing, unlimited concurrency, starting from $0.15, 24/7 technical support. GPT/Claude/Gemini all at 32% off, domestic models 20-50% off, Claude Code exclusive models at 66% off! <a href="https://www.dmxapi.cn/register?aff=bUHu">Register here</a></td>
<td width="180"><a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch"><img src="assets/partners/logos/atlascloud_banner.png" alt="Atlas Cloud" width="150"></a></td>
<td>Atlas Cloud is a full-modal AI inference platform that gives developers a single AI API to access video generation, image generation, and LLM APIs. Instead of managing multiple vendor integrations, you connect once and get unified access to 300+ curated models across all modalities. Check out Atlas Cloud's new <a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch">coding plan</a> promotion for more budget-friendly API access!</td>
</tr>
<tr>
@@ -95,13 +150,8 @@ TeamoRouter also offers enterprise features including centralized billing, team
</tr>
<tr>
<td width="180"><a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch"><img src="assets/partners/logos/crazyrouter.png" alt="Crazyrouter" width="150"></a></td>
<td>Thanks to Crazyrouter for sponsoring this project! Crazyrouter is a high-performance AI API aggregation platform — one API key for 300+ models including Claude Code, Codex, Gemini CLI, and more. All models at 55% of official pricing with auto-failover, smart routing, and unlimited concurrency. Crazyrouter offers an exclusive deal for CC Switch users: register via <a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch">this link</a> and contact customer support to claim <strong>$2 free credit</strong>, plus enter promo code `CCSWITCH` on your first top-up for an extra <strong>30% bonus credit</strong>! </td>
</tr>
<tr>
<td width="180"><a href="https://www.right.codes/register?aff=CCSWITCH"><img src="assets/partners/logos/rightcode.jpg" alt="RightCode" width="150"></a></td>
<td>Thank you to Right Code for sponsoring this project! Right Code reliably provides routing services for models such as Claude Code, Codex, and Gemini, with both pay-as-you-go and monthly subscription billing options available. Invoices are available upon top-up, and enterprise and team users can receive dedicated one-on-one support. Right Code also offers an exclusive discount for CC Switch users: register via <a href="https://www.right.codes/register?aff=CCSWITCH">this link</a>, and with every top-up you will receive pay-as-you-go credit equivalent to 5% of the amount paid.</td>
<td width="180"><a href="https://www.ccsub.net/register?ref=Y6Z8DXEA"><img src="assets/partners/logos/ccsub.svg" alt="CCSub" width="150"></a></td>
<td>Thanks to CCSub for sponsoring this project! CCSub is a stable, affordable AI API relay platform — your drop-in replacement for a Claude.ai subscription. One API key gives you access to Claude Opus 4.8, Sonnet, Haiku, GPT-5, Gemini, and DeepSeek at roughly 30% of direct API cost, with no VPN required from anywhere in the world. Compatible with Claude Code, Codex, Cursor, Cline, Continue, Windsurf, and all major AI coding tools. Register via <a href="https://www.ccsub.net/register?ref=Y6Z8DXEA">this link</a> and get $5 free credit on sign-up.</td>
</tr>
<tr>
@@ -114,69 +164,29 @@ TeamoRouter also offers enterprise features including centralized billing, team
<td>Thanks to Micu API for sponsoring this project! Micu API is a global LLM relay service provider dedicated to delivering the best cost-performance ratio with high stability. Backed by a registered enterprise for core assurance, eliminating any risk of service discontinuation, with fast official invoicing support! We champion "zero cost to try": top up from as low as ¥1 with no minimum, and get fee-free refunds anytime! Micu API offers an exclusive deal for CC Switch users: register via <a href="https://www.micuapi.ai/register?aff=aOYQ">this link</a> and enter promo code "ccswitch" when topping up to enjoy a <strong>10% discount</strong>!</td>
</tr>
<tr>
<td width="180"><a href="https://www.right.codes/register?aff=CCSWITCH"><img src="assets/partners/logos/rightcode.jpg" alt="RightCode" width="150"></a></td>
<td>Thank you to Right Code for sponsoring this project! Right Code reliably provides routing services for models such as Claude Code, Codex, and Gemini, with both pay-as-you-go and monthly subscription billing options available. Invoices are available upon top-up, and enterprise and team users can receive dedicated one-on-one support. Right Code also offers an exclusive discount for CC Switch users: register via <a href="https://www.right.codes/register?aff=CCSWITCH">this link</a>, and with every top-up you will receive pay-as-you-go credit equivalent to 5% of the amount paid.</td>
</tr>
<tr>
<td width="180"><a href="https://etok.ai"><img src="assets/partners/logos/etok.png" alt="ETok" width="150"></a></td>
<td>Thanks to ETok.ai for sponsoring this project! ETok.ai is dedicated to building a one-stop AI programming tool service platform. We offer professional Claude Code packages and technical community services, with support for Google Gemini and OpenAI Codex. Through carefully designed plans and a professional tech community, we provide developers with reliable service guarantees and continuous technical support, making AI-assisted programming a true productivity tool. Click <a href="https://etok.ai">here</a> to register!</td>
</tr>
<tr>
<td width="180"><a href="https://console.claudeapi.com/register?aff=pCLD"><img src="assets/partners/logos/claudeapi.png" alt="ClaudeAPI" width="150"></a></td>
<td>This project is sponsored by <a href="https://console.claudeapi.com/register?aff=pCLD">Claude API</a>. Direct Claude API access — connect Claude Code and Agent apps in 3 minutes. New users can claim a free trial credit.Powered by official Anthropic API keys + AWS Bedrock official channels. No reverse engineering, no model degradation. Full support for Opus / Sonnet / Haiku model lineup, with official capabilities preserved including Tool Use, 1M context window, and more. Built for Claude Code power users, Agent engineers, and enterprise engineering teams. Invoicing and dedicated team support available. Click <a href="https://console.claudeapi.com/register?aff=pCLD">here</a> to register!</td>
<td width="180"><a href="https://cubence.com/signup?code=CCSWITCH&source=ccs"><img src="assets/partners/logos/cubence.png" alt="Cubence" width="150"></a></td>
<td>Thanks to Cubence for sponsoring this project! Cubence is a reliable and efficient API relay service provider, offering relay services for Claude Code, Codex, Gemini, and more with flexible billing options including pay-as-you-go and monthly plans. Cubence provides special discounts for CC Switch users: register using <a href="https://cubence.com/signup?code=CCSWITCH&source=ccs">this link</a> and enter the "CCSWITCH" promo code during recharge to get 10% off every top-up!</td>
</tr>
<tr>
<td width="180"><a href="https://code0.ai?source=ccswitch"><img src="assets/partners/logos/code0.png" alt="code0.ai" width="150"></a></td>
<td>Thanks to <a href="https://code0.ai?source=ccswitch">code0.ai</a> for sponsoring this project! code0.ai is an AI coding service platform built for developers, supporting Claude Code, Codex, Gemini, and other mainstream AI coding capabilities. It helps individual developers and teams use AI Agents more stably and efficiently for coding, debugging, refactoring, and automation workflows. ccswitch users can contact customer support via the <a href="https://code0.ai?source=ccswitch">code0.ai website</a> to claim test credits and experience a reliable AI coding service.</td>
<td width="180"><a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch"><img src="assets/partners/logos/crazyrouter.png" alt="Crazyrouter" width="150"></a></td>
<td>Thanks to Crazyrouter for sponsoring this project! Crazyrouter is a high-performance AI API aggregation platform — one API key for 300+ models including Claude Code, Codex, Gemini CLI, and more. All models at 55% of official pricing with auto-failover, smart routing, and unlimited concurrency. Crazyrouter offers an exclusive deal for CC Switch users: register via <a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch">this link</a> and contact customer support to claim <strong>$2 free credit</strong>, plus enter promo code `CCSWITCH` on your first top-up for an extra <strong>30% bonus credit</strong>! </td>
</tr>
<tr>
<td width="180"><a href="https://claudecn.top"><img src="assets/partners/logos/claudecn.jpg" alt="ClaudeCN" width="150"></a></td>
<td>Thanks to ClaudeCN for sponsoring this project! ClaudeCN is an enterprise-grade AI gateway platform operated by a registered company. It delivers high-availability commercial API access to popular models including Claude, GPT, and DeepSeek, and is built around formal enterprise procurement workflows — corporate bank transfers, signed contracts, and full compliance. Register via <a href="https://claudecn.top">this link</a>!</td>
</tr>
<tr>
<td width="180"><a href="https://runapi.co"><img src="assets/partners/logos/runapi.jpg" alt="RunAPI" width="150"></a></td>
<td>Thanks to RunAPI for sponsoring this project! RunAPI is a high-performance and reliable AI model API gateway — one API key gives you access to 150+ mainstream models including OpenAI, Claude, Gemini, DeepSeek, and Grok, with prices as low as 10% of the official rate and excellent stability. It works seamlessly with Claude Code, OpenClaw, and other tools. Exclusive benefit for CC Switch users: register and contact customer support to claim a free ¥14 credit. Register via <a href="https://runapi.co">this link</a>!</td>
</tr>
<tr>
<td width="180"><a href="https://apikey.fun/register?aff=CCSwitch"><img src="assets/partners/logos/apikey_banner.png" alt="APIKEY.FUN" width="150"></a></td>
<td>Thanks to APIKEY.FUN for sponsoring this project! APIKEY.FUN is a professional enterprise-grade AI relay platform dedicated to providing stable, efficient, and low-cost AI model API access for enterprises and individual developers. The platform supports popular mainstream models such as Claude, OpenAI, and Gemini, with prices as low as 7% of official rates. Register through this project's <a href="https://apikey.fun/register?aff=CCSwitch">exclusive link</a> to enjoy an exclusive offer of up to <strong>permanent 5% off top-ups</strong>.</td>
</tr>
<tr>
<td width="180"><a href="https://apinebula.com/02rw5X"><img src="assets/partners/logos/apinebula_banner.png" alt="APINebula" width="150"></a></td>
<td>Thanks to APINEBULA for sponsoring this project! APINEBULA, an enterprise-grade AI aggregation platform under Galaxy Video Bureau, leverages extensive platform resources to provide developers, teams, and enterprises with stable, cost-effective access to large language model APIs. The platform integrates leading, full-powered models like Claude, GPT, and Gemini, allowing you to connect to the world's top AI models through a single API, with prices starting as low as 10% of the original cost. Designed for AI programming, Agent development, and business system integration, APINEBULA supports enterprise-grade high concurrency, formal contracts, corporate bank transfers, and invoicing services. APINEBULA provides special discounts for our software users: register using <a href="https://apinebula.com/02rw5X">this link</a> and enter the <strong>"ccswitch"</strong> promo code during your first recharge to get <strong>10% off</strong>.</td>
</tr>
<tr>
<td width="180"><a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch"><img src="assets/partners/logos/atlascloud_banner.png" alt="Atlas Cloud" width="150"></a></td>
<td>Atlas Cloud is a full-modal AI inference platform that gives developers a single AI API to access video generation, image generation, and LLM APIs. Instead of managing multiple vendor integrations, you connect once and get unified access to 300+ curated models across all modalities. Check out Atlas Cloud's new <a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch">coding plan</a> promotion for more budget-friendly API access!</td>
</tr>
<tr>
<td width="180"><a href="https://www.ccsub.net/register?ref=Y6Z8DXEA"><img src="assets/partners/logos/ccsub.svg" alt="CCSub" width="150"></a></td>
<td>Thanks to CCSub for sponsoring this project! CCSub is a stable, affordable AI API relay platform — your drop-in replacement for a Claude.ai subscription. One API key gives you access to Claude Opus 4.8, Sonnet, Haiku, GPT-5, Gemini, and DeepSeek at roughly 30% of direct API cost, with no VPN required from anywhere in the world. Compatible with Claude Code, Codex, Cursor, Cline, Continue, Windsurf, and all major AI coding tools. Register via <a href="https://www.ccsub.net/register?ref=Y6Z8DXEA">this link</a> and get $5 free credit on sign-up.</td>
</tr>
<tr>
<td width="180"><a href="https://unity2.ai/register?source=ccs"><img src="assets/partners/logos/unity2.jpg" alt="Unity2.ai" width="150"></a></td>
<td>Thanks to Unity2.ai for sponsoring this project! Unity2.ai is a high-performance AI model API relay platform for individual developers, teams, and enterprises. Long trusted by leading companies in China, it serves over 30 billion tokens per day and supports high concurrency at the 5,000 RPM level. It offers balance-based billing, first top-up bonuses, bundle subscriptions, corporate invoicing, and dedicated support. Register via <a href="https://unity2.ai/register?source=ccs">this link</a> to get $2 in credits, plus another $10 for joining the official group — up to $12 in free credits!</td>
</tr>
<tr>
<td width="180"><a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL"><img src="assets/partners/logos/fenno-banner.png" alt="Fenno.ai" width="150"></a></td>
<td>Thanks to Fenno.ai for sponsoring this project! Fenno.ai is a stable and efficient API relay service provider, currently focused on Codex relay. It is compatible with the OpenAI and Anthropic protocols and can be flexibly used from Codex, Claude Code, OpenCode, and other mainstream coding tools. It reliably supports enterprise-grade workloads of hundreds of billions of tokens per day, with corporate (B2B) settlement and invoicing for both domestic and overseas entities. Fenno.ai offers an exclusive benefit for CC Switch users: subscribe via <a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL">this link</a> to the incredible ¥9.9 Coding Plan worth $150 in credits, and earn up to 20% in referral rewards — invite more, earn more!</td>
</tr>
<tr>
<td width="180"><a href="https://zetaapi.ai/go/ccs"><img src="assets/partners/logos/zetaapi-banner.png" alt="ZetaAPI" width="150"></a></td>
<td>Thanks to ZetaAPI for sponsoring this project! ZetaAPI focuses on real model fidelity, no watered-down responses, no quality degradation, and pricing as low as 35% of official rates. The platform does not mix traffic, secretly replace models with lower-quality alternatives, or use fake model routing. It supports Claude Code, Codex, Gemini, ChatGPT, and other mainstream AI models, helping users significantly reduce API costs while maintaining reliable model quality. At the same time, ZetaAPI provides enterprise-grade SLA-backed stability, standard API compatibility, one API key for multiple models, fast integration, and pay-as-you-go billing, making it suitable for AI products, coding agents, internal business tools, customer service systems, content generation, and automation workflows. If any model is verified to be inconsistent with its stated quality, ZetaAPI backs it with a 10x compensation guarantee, giving users a more stable, transparent, and trustworthy experience. Register via <a href="https://zetaapi.ai/go/ccs">this link</a> and use the promo code CC-SWITCH during your first recharge to enjoy an exclusive 10% discount on your first top-up, just for CC Switch users!</td>
</tr>
<tr>
<td width="180"><a href="https://nekocode.ai?aff=CCSWITCH"><img src="assets/partners/logos/nekocode-banner.png" alt="NekoCode" width="150"></a></td>
<td>Thanks to <a href="https://nekocode.ai?aff=CCSWITCH">NekoCode</a> for sponsoring this project! NekoCode provides developers with a stable, efficient, and reliable API relay service for Claude, Codex, and other AI models. With transparent pricing and flexible pay-as-you-go billing, it offers a simple and cost-effective way to access AI models. CC Switch users can enjoy an exclusive 10% discount: register via <a href="https://nekocode.ai?aff=CCSWITCH">this link</a> and enter promo code <code>cc-switch</code> during recharge to receive 10% off your top-up!</td>
<td width="180"><a href="https://www.dmxapi.cn/register?aff=bUHu"><img src="assets/partners/logos/dmx-en.jpg" alt="DMXAPI" width="150"></a></td>
<td>Thanks to DMXAPI for sponsoring this project! DMXAPI provides global large model API services to 200+ enterprise users. One API key for all global models. Features include: instant invoicing, unlimited concurrency, starting from $0.15, 24/7 technical support. GPT/Claude/Gemini all at 32% off, domestic models 20-50% off, Claude Code exclusive models at 66% off! <a href="https://www.dmxapi.cn/register?aff=bUHu">Register here</a></td>
</tr>
</table>
@@ -185,13 +195,13 @@ TeamoRouter also offers enterprise features including centralized billing, team
## Why CC Switch?
Modern AI-powered coding relies on tools like Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw, and Hermes — but each has its own configuration format. Switching API providers means manually editing JSON, TOML, or `.env` files, and there is no unified way to manage MCP and Skills across multiple tools.
Modern AI-powered coding relies on tools like Claude Code, Claude Desktop, Codex, Gemini CLI, Grok Build, OpenCode, OpenClaw, and Hermes — but each has its own configuration format. Switching API providers means manually editing JSON, TOML, or `.env` files, and there is no unified way to manage MCP and Skills across multiple tools.
**CC Switch** gives you a single desktop app to manage all supported AI tools. Instead of editing config files by hand, you get a visual interface to import providers with one click, switch between them instantly, with 50+ built-in provider presets, unified MCP and Skills management, and system tray quick switching — all backed by a reliable SQLite database with atomic writes that protect your configs from corruption.
- **One App, Seven Tools** — Manage Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw, and Hermes from a single interface
- **One App, Eight Tools** — Manage Claude Code, Claude Desktop, Codex, Gemini CLI, Grok Build, OpenCode, OpenClaw, and Hermes from a single interface
- **No More Manual Editing** — 50+ provider presets including AWS Bedrock, NVIDIA NIM, and community relays; just pick and switch
- **Unified MCP & Skills Management** — One panel to manage MCP servers and Skills across Claude, Codex, Gemini, OpenCode, and Hermes with bidirectional sync
- **Unified MCP & Skills Management** — One panel to manage MCP servers and Skills across Claude, Codex, Gemini, Grok Build, OpenCode, and Hermes with bidirectional sync
- **System Tray Quick Switch** — Switch providers instantly from the tray menu, no need to open the full app
- **Cloud Sync** — Sync provider data across devices via Dropbox, OneDrive, iCloud, or WebDAV servers
- **Cross-Platform** — Native desktop app for Windows, macOS, and Linux, built with Tauri 2
@@ -209,18 +219,18 @@ Modern AI-powered coding relies on tools like Claude Code, Claude Desktop, Codex
### Provider Management
- **7 supported tools, 50+ presets** — Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw, Hermes; copy your key and import with one click
- **8 supported tools, 50+ presets** — Claude Code, Claude Desktop, Codex, Gemini CLI, Grok Build, OpenCode, OpenClaw, Hermes; copy your key and import with one click
- **Universal providers** — One config syncs to Claude Code, Codex, and Gemini CLI
- One-click switching, system tray quick access, drag-and-drop sorting, import/export
### Proxy & Failover
- **Local proxy with hot-switching** — Format conversion, auto-failover, circuit breaker, provider health monitoring, and request rectifier
- **App-level takeover** — Independently proxy Claude, Codex, or Gemini, down to individual providers
- **App-level takeover** — Independently proxy Claude, Codex, Gemini, or Grok Build, down to individual providers
### MCP, Prompts & Skills
- **Unified MCP panel** — Manage MCP servers across Claude, Codex, Gemini, OpenCode, and Hermes with bidirectional sync and Deep Link import
- **Unified MCP panel** — Manage MCP servers across Claude, Codex, Gemini, Grok Build, OpenCode, and Hermes with bidirectional sync and Deep Link import
- **Prompts** — Markdown editor with cross-app sync (CLAUDE.md / AGENTS.md / GEMINI.md) and backfill protection
- **Skills** — One-click install from GitHub repos or ZIP files, custom repository management, with symlink and file copy support
@@ -244,7 +254,7 @@ Modern AI-powered coding relies on tools like Claude Code, Claude Desktop, Codex
<details>
<summary><strong>Which AI tools does CC Switch support?</strong></summary>
CC Switch supports seven tools: **Claude Code**, **Claude Desktop**, **Codex**, **Gemini CLI**, **OpenCode**, **OpenClaw**, and **Hermes**. Each tool has dedicated provider presets and configuration management.
CC Switch supports eight tools: **Claude Code**, **Claude Desktop**, **Codex**, **Gemini CLI**, **Grok Build**, **OpenCode**, **OpenClaw**, and **Hermes**. Each tool has dedicated provider presets and configuration management.
</details>
+90 -80
View File
@@ -2,7 +2,7 @@
# CC Switch
### Der All-in-One-Manager für Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw & Hermes Agent
### Der All-in-One-Manager für Claude Code, Claude Desktop, Codex, Gemini CLI, Grok Build, OpenCode, OpenClaw & Hermes Agent
[![Version](https://img.shields.io/github/v/release/farion1231/cc-switch?color=blue&label=version)](https://github.com/farion1231/cc-switch/releases)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](https://github.com/farion1231/cc-switch/releases)
@@ -25,11 +25,11 @@
<details open>
<summary>Zum Einklappen klicken</summary>
[![Kimi K2.7 Code](assets/partners/banners/kimi-banner-en.png)](https://platform.kimi.ai?aff=cc-switch)
[![Kimi K2.7 Code](https://gcdn.moonshot.cn/growth-cdn/sponsor/kimi-en.png)](https://platform.kimi.ai?aff=cc-switch)
Kimi K2.7 Code ist ein quelloffenes, auf Programmierung spezialisiertes Agenten-Modell von Moonshot AI. Es bietet eine stärkere Programmier- und Agentenleistung mit erheblichen Verbesserungen bei realen, langfristigen Programmieraufgaben. Diese Fortschritte führen zu höheren End-to-End-Erfolgsraten in komplexen Software-Engineering-Workflows. Zudem verbessert K2.7 Code die Reasoning-Effizienz und reduziert den Verbrauch an Thinking-Tokens um rund 30 % gegenüber K2.6. **[Hier klicken, um Kimi auszuprobieren](https://platform.kimi.ai?aff=cc-switch)**
Kimi K3 ist das bislang leistungsstärkste Modell von Moonshot AI und das weltweit erste offene Modell der 3T-Klasse. Mit 2,8 Billionen Parametern, nativen visuellen Fähigkeiten und einem Kontextfenster von 1 Million Token liefert K3 Spitzenleistung bei langfristigen Programmieraufgaben, Wissensarbeit und Reasoning. Mit CC Switch lässt sich Kimi in den verschiedensten Agenten-Tools bequem konfigurieren und umschalten. **[Hier klicken, um Kimi zu nutzen](https://platform.kimi.ai?aff=cc-switch)**
Hauptsächlich mit Programmierung beschäftigt? Probieren Sie den **[Kimi-For-Coding-Plan](https://www.kimi.com/code/?aff=cc-switch)** ein Abo, das speziell fürs Programmieren entwickelt wurde!
Hauptsächlich mit Programmierung beschäftigt? Probieren Sie das **[Kimi-Code-Abo](https://www.kimi.com/code/?aff=cc-switch)** aus!
---
@@ -40,8 +40,13 @@ Hauptsächlich mit Programmierung beschäftigt? Probieren Sie den **[Kimi-For-Co
</tr>
<tr>
<td width="180"><a href="https://aigocode.com/invite/CC-SWITCH"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>Danke an AIGoCode für die Unterstützung dieses Projekts! AIGoCode ist eine All-in-One-Plattform, die Claude Code, Codex und die neuesten Gemini-Modelle integriert und Ihnen stabile, effiziente und äußerst kostengünstige KI-Coding-Dienste bietet. Die Plattform stellt flexible Abonnementpläne bereit, birgt kein Risiko einer Kontosperrung, ermöglicht Direktzugriff ohne VPN und reagiert blitzschnell. AIGoCode hat ein besonderes Angebot für CC-Switch-Nutzer vorbereitet: Wenn Sie sich über <a href="https://aigocode.com/invite/CC-SWITCH">diesen Link</a> registrieren, erhalten Sie bei Ihrer ersten Aufladung zusätzliche 10 % Bonusguthaben!</td>
<td width="180"><a href="https://zetaapi.ai/go/u117"><img src="assets/partners/logos/zetaapi-banner.png" alt="ZetaAPI" width="150"></a></td>
<td>Danke an ZetaAPI für die Unterstützung dieses Projekts! ZetaAPI legt den Fokus auf echte Modelltreue — keine verwässerten Antworten, keine Qualitätsminderung — und Preise von nur 35 % der offiziellen Tarife. Die Plattform mischt keinen Traffic, ersetzt Modelle nicht heimlich durch minderwertige Alternativen und nutzt kein gefälschtes Modell-Routing. Sie unterstützt Claude Code, Codex, Gemini, ChatGPT und weitere gängige KI-Modelle und hilft Nutzern, die API-Kosten deutlich zu senken und gleichzeitig eine zuverlässige Modellqualität zu gewährleisten. Gleichzeitig bietet ZetaAPI eine SLA-gestützte Stabilität auf Unternehmensniveau, Standard-API-Kompatibilität, einen API-Key für mehrere Modelle, schnelle Integration und nutzungsbasierte Abrechnung — geeignet für KI-Produkte, Coding-Agents, interne Unternehmenstools, Kundenservice-Systeme, Content-Erstellung und Automatisierungs-Workflows. Falls bei einem Modell nachgewiesen wird, dass es nicht der angegebenen Qualität entspricht, sichert ZetaAPI dies mit einer 10-fachen Entschädigungsgarantie ab und bietet Nutzern ein stabileres, transparenteres und vertrauenswürdigeres Erlebnis. Registrieren Sie sich über <a href="https://zetaapi.ai/go/u117">diesen Link</a> und verwenden Sie bei Ihrer ersten Aufladung den Promo-Code CC-SWITCH, um als CC-Switch-Nutzer einen exklusiven Rabatt von 10 % auf Ihre erste Aufladung zu erhalten!</td>
</tr>
<tr>
<td width="180"><a href="https://apinebula.com/VjM74M"><img src="assets/partners/logos/apinebula_banner.png" alt="APINebula" width="150"></a></td>
<td>Danke an APINEBULA für die Unterstützung dieses Projekts! APINEBULA ist eine Enterprise-KI-Aggregationsplattform unter Galaxy Video Bureau und nutzt umfangreiche Plattformressourcen, um Entwicklern, Teams und Unternehmen stabilen und kosteneffizienten Zugang zu APIs großer Sprachmodelle zu bieten. Die Plattform bündelt führende vollwertige Modelle wie Claude, GPT und Gemini. Über eine einzige API erhalten Sie Zugriff auf weltweit führende KI-Modelle, mit Preisen ab 10 % des Originalpreises. APINEBULA ist für KI-Programmierung, Agent-Entwicklung und Integration in Geschäftssysteme ausgelegt und unterstützt enterprise-grade hohe Parallelität, formale Verträge, Firmenüberweisungen und Rechnungsstellung. APINEBULA bietet Nutzern dieser Software besondere Rabatte: Registrieren Sie sich über <a href="https://apinebula.com/VjM74M">diesen Link</a> und geben Sie beim ersten Aufladen den Aktionscode <strong>"ccswitch"</strong> ein, um <strong>10 % Rabatt</strong> zu erhalten.</td>
</tr>
<tr>
@@ -50,11 +55,6 @@ Hauptsächlich mit Programmierung beschäftigt? Probieren Sie den **[Kimi-For-Co
Offizielle Kanäle von Claude Code / Codex / Gemini zu 38 % / 2 % / 9 % des Originalpreises, mit zusätzlichen Rabatten beim Aufladen! AICodeMirror bietet besondere Vorteile für CC-Switch-Nutzer: Registrieren Sie sich über <a href="https://www.aicodemirror.com/register?invitecode=9915W3">diesen Link</a> und erhalten Sie 20 % Rabatt auf Ihre erste Aufladung; Unternehmenskunden erhalten bis zu 25 % Rabatt!</td>
</tr>
<tr>
<td width="180"><a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF"><img src="assets/partners/logos/shengsuanyun.png" alt="Shengsuanyun" width="150"></a></td>
<td>Danke an Shengsuanyun für die Unterstützung dieses Projekts! Shengsuanyun ist eine Superfabrik für KI-native Teams — eine Plattform zur parallelen Ausführung von KI-Aufgaben in industrieller Qualität. Ihr Modellmarktplatz bündelt die Fähigkeiten von Claude, ChatGPT, Gemini und weiteren in- und ausländischen LLM- und Multimedia-Modellen mit Direktbezug. Absolut kein Reverse Engineering und keine Verwässerung — die plattformweite Modell-SLA-Verfügbarkeit erreicht 99,7 %, und die <a href="https://watch.shengsuanyun.com/status/shengsuanyun">Monitoring-Dashboards</a> zeigen durchgehend grün an. Es bietet außerdem unternehmensgerechte, anpassbare Gateways für fein abgestufte Kosten- und Berechtigungsverwaltung im Team, intelligentes Routing, Sicherheitsschutz und BYOK-Hosting (Bring Your Own Key). Die Plattform rechnet nach Nutzung sowie über einen Token-Plan (in Kürze verfügbar) ab, und Rechnungsstellung ist möglich. Registrieren Sie sich über <a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF">diesen Link</a> als Neukunde und erhalten Sie ein Guthaben von ¥10 sowie 10 % Bonus auf Ihre erste Aufladung.</td>
</tr>
<tr>
<td width="180"><a href="https://pateway.ai/?ch=etzpm8&aff=WB6M6F67#/"><img src="assets/partners/logos/pateway.png" alt="PatewayAI" width="150"></a></td>
<td>Danke an PatewayAI für die Unterstützung dieses Projekts! PatewayAI ist ein API-Relay-Anbieter für anspruchsvolle KI-Entwickler, der sich auf das direkte Relayen offizieller hochwertiger Modell-APIs konzentriert. Er bietet die komplette Claude-Reihe und die Codex-Serie, zu 100 % aus offiziellen Kanälen bezogen — keine Verwässerung, keine Fälschungen, Überprüfung ausdrücklich erwünscht. Die Abrechnung ist transparent, und jede Rechnung auf Token-Ebene lässt sich Zeile für Zeile prüfen.
@@ -62,6 +62,51 @@ Er unterstützt zudem unternehmensgerechte Nebenläufigkeit und stellt Unternehm
Registrieren Sie sich jetzt über <a href="https://pateway.ai/?ch=etzpm8&aff=WB6M6F67#/">diesen Link</a> und erhalten Sie ein Testguthaben von 3 $. Aufladungen sind ab 60 % des Originalpreises möglich, mit einem beidseitigen Empfehlungsbonus von bis zu 150 $!</td>
</tr>
<tr>
<td width="180"><a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL"><img src="assets/partners/logos/fenno-banner.png" alt="Fenno.ai" width="150"></a></td>
<td>Danke an Fenno.ai für die Unterstützung dieses Projekts! Fenno.ai ist ein stabiler und effizienter API-Relay-Dienstleister, der sich derzeit hauptsächlich auf Codex-Relay konzentriert. Er ist mit den OpenAI- und Anthropic-Protokollen kompatibel und lässt sich flexibel mit Codex, Claude Code, OpenCode und anderen gängigen Coding-Tools nutzen. Er unterstützt zuverlässig Workloads auf Unternehmensniveau von Hunderten Milliarden Tokens pro Tag und bietet B2B-Abrechnung sowie Rechnungsstellung für Unternehmen im In- und Ausland. Fenno.ai bietet einen exklusiven Vorteil für CC-Switch-Nutzer: Abonnieren Sie über <a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL">diesen Link</a> den unschlagbaren ¥9,9-Coding-Plan im Wert von $150 Guthaben und erhalten Sie bis zu 20% Empfehlungsprämien — je mehr Einladungen, desto mehr Belohnung!</td>
</tr>
<tr>
<td width="180"><a href="https://runapi.co/register?aff=iOKB"><img src="assets/partners/logos/runapi.jpg" alt="RunAPI" width="150"></a></td>
<td>Danke an RunAPI für die Unterstützung dieses Projekts! RunAPI ist ein leistungsstarkes und zuverlässiges KI-Modell-API-Gateway — ein API-Schlüssel gibt Ihnen Zugriff auf mehr als 150 gängige Modelle, darunter OpenAI, Claude, Gemini, DeepSeek und Grok, zu Preisen ab 10 % des offiziellen Tarifs und mit ausgezeichneter Stabilität. Es arbeitet nahtlos mit Claude Code, OpenClaw und weiteren Werkzeugen zusammen. Exklusiver Vorteil für CC-Switch-Nutzer: Registrieren Sie sich über <a href="https://runapi.co/register?aff=iOKB">diesen Link</a> und erhalten Sie 10 % Rabatt auf Ihre erste Aufladung!</td>
</tr>
<tr>
<td width="180"><a href="https://unity2.ai/register?source=ccs"><img src="assets/partners/logos/unity2.jpg" alt="Unity2.ai" width="150"></a></td>
<td>Danke an Unity2.ai für die Unterstützung dieses Projekts! Unity2.ai ist eine leistungsstarke AI-Modell-API-Relay-Plattform für Einzelentwickler, Teams und Unternehmen. Sie wird seit Langem von führenden Unternehmen in China genutzt, verarbeitet täglich über 30 Milliarden Tokens und unterstützt hohe Parallelität auf 5.000-RPM-Niveau. Geboten werden Guthaben-Abrechnung, Ersteinzahlungsbonus, Kombi-Abonnements, Firmenrechnungen und persönliche Betreuung. Registrieren Sie sich über <a href="https://unity2.ai/register?source=ccs">diesen Link</a> und erhalten Sie $2 Guthaben, plus weitere $10 für den Beitritt zur offiziellen Gruppe — bis zu $12 Gratis-Guthaben!</td>
</tr>
<tr>
<td width="180"><a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF"><img src="assets/partners/logos/shengsuanyun.png" alt="Shengsuanyun" width="150"></a></td>
<td>Danke an Shengsuanyun für die Unterstützung dieses Projekts! Shengsuanyun ist eine Superfabrik für KI-native Teams — eine Plattform zur parallelen Ausführung von KI-Aufgaben in industrieller Qualität. Ihr Modellmarktplatz bündelt die Fähigkeiten von Claude, ChatGPT, Gemini und weiteren in- und ausländischen LLM- und Multimedia-Modellen mit Direktbezug. Absolut kein Reverse Engineering und keine Verwässerung — die plattformweite Modell-SLA-Verfügbarkeit erreicht 99,7 %, und die <a href="https://watch.shengsuanyun.com/status/shengsuanyun">Monitoring-Dashboards</a> zeigen durchgehend grün an. Es bietet außerdem unternehmensgerechte, anpassbare Gateways für fein abgestufte Kosten- und Berechtigungsverwaltung im Team, intelligentes Routing, Sicherheitsschutz und BYOK-Hosting (Bring Your Own Key). Die Plattform rechnet nach Nutzung sowie über einen Token-Plan (in Kürze verfügbar) ab, und Rechnungsstellung ist möglich. Registrieren Sie sich über <a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF">diesen Link</a> als Neukunde und erhalten Sie ein Guthaben von ¥10 sowie 10 % Bonus auf Ihre erste Aufladung.</td>
</tr>
<tr>
<td width="180"><a href="https://aigocode.com/invite/CC-SWITCH"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>Danke an AIGoCode für die Unterstützung dieses Projekts! AIGoCode ist eine All-in-One-Plattform, die Claude Code, Codex und die neuesten Gemini-Modelle integriert und Ihnen stabile, effiziente und äußerst kostengünstige KI-Coding-Dienste bietet. Die Plattform stellt flexible Abonnementpläne bereit, birgt kein Risiko einer Kontosperrung, ermöglicht Direktzugriff ohne VPN und reagiert blitzschnell. AIGoCode hat ein besonderes Angebot für CC-Switch-Nutzer vorbereitet: Wenn Sie sich über <a href="https://aigocode.com/invite/CC-SWITCH">diesen Link</a> registrieren, erhalten Sie bei Ihrer ersten Aufladung zusätzliche 10 % Bonusguthaben!</td>
</tr>
<tr>
<td width="180"><a href="https://subrouter.ai/register?aff=l3ri"><img src="assets/partners/logos/subrouter-banner.png" alt="SubRouter" width="150"></a></td>
<td>Danke an SubRouter für die Unterstützung dieses Projekts! SubRouter ist ein Marktplatz und eine intelligente Routing-Plattform für Betreiber von KI-Diensten. Händler können eigene Betriebsseiten starten, Pakete veröffentlichen sowie Nutzer, Modelle und Preise verwalten, während Nutzer im Marktplatz Dienste entdecken und über eine einzige einheitliche API zuverlässige und effiziente Modellaufrufe nutzen. Registrieren Sie sich über <a href="https://subrouter.ai/register?aff=l3ri">diesen Link</a>!</td>
</tr>
<tr>
<td width="180"><a href="https://apikey.fun/register?aff=CCSwitch"><img src="assets/partners/logos/apikey_banner.png" alt="APIKEY.FUN" width="150"></a></td>
<td>Danke an APIKEY.FUN für die Unterstützung dieses Projekts! APIKEY.FUN ist eine professionelle KI-Relay-Plattform auf Enterprise-Niveau, die Unternehmen und einzelnen Entwicklern stabilen, effizienten und kostengünstigen Zugriff auf KI-Modell-APIs bietet. Die Plattform unterstützt beliebte Mainstream-Modelle wie Claude, OpenAI und Gemini, mit Preisen ab 7 % der offiziellen Tarife. Wer sich über den <a href="https://apikey.fun/register?aff=CCSwitch">exklusiven Link</a> dieses Projekts registriert, kann ein exklusives Angebot von bis zu <strong>dauerhaft 5 % Rabatt auf Aufladungen</strong> erhalten.</td>
</tr>
<tr>
<td width="180"><a href="https://console.claudeapi.com/register?aff=pCLD"><img src="assets/partners/logos/claudeapi.png" alt="ClaudeAPI" width="150"></a></td>
<td>Dieses Projekt wird von <a href="https://console.claudeapi.com/register?aff=pCLD">Claude API</a> gesponsert. Direkter Claude-API-Zugriff — verbinden Sie Claude Code und Agent-Apps in 3 Minuten. Neukunden können ein kostenloses Testguthaben einlösen. Betrieben mit offiziellen Anthropic-API-Schlüsseln + offiziellen AWS-Bedrock-Kanälen. Kein Reverse Engineering, keine Modellverschlechterung. Volle Unterstützung der Modellreihe Opus / Sonnet / Haiku, mit erhaltenen offiziellen Fähigkeiten einschließlich Tool Use, 1M-Kontextfenster und mehr. Entwickelt für Claude-Code-Power-User, Agent-Ingenieure und technische Unternehmensteams. Rechnungsstellung und dedizierter Team-Support verfügbar. Klicken Sie <a href="https://console.claudeapi.com/register?aff=pCLD">hier</a>, um sich zu registrieren!</td>
</tr>
<tr>
<td width="180"><a href="https://code0.ai/agent/register/B2XHxGjGmRvqgznY"><img src="assets/partners/logos/code0.png" alt="code0.ai" width="150"></a></td>
<td>Vielen Dank an <a href="https://code0.ai/agent/register/B2XHxGjGmRvqgznY">code0.ai</a> für die Unterstützung dieses Projekts! code0.ai ist eine für Entwickler entwickelte AI-Coding-Service-Plattform, die Claude Code, Codex, Gemini und weitere gängige AI-Coding-Funktionen unterstützt. Sie hilft einzelnen Entwicklern und Teams, AI-Agents stabiler und effizienter für Programmierung, Debugging, Refactoring und Automatisierungs-Workflows zu nutzen. ccswitch-Nutzer können über die <a href="https://code0.ai/agent/register/B2XHxGjGmRvqgznY">code0.ai-Website</a> den Kundensupport kontaktieren, um Testguthaben zu erhalten und einen zuverlässigen AI-Coding-Service zu erleben.</td>
</tr>
<tr>
<td width="180"><a href="https://teamorouter.com/?utm_source=cc_switch&utm_medium=referral&utm_campaign=ai_directory"><img src="assets/partners/logos/TeamoRouter-banner.png" alt="TeamoRouter" width="150"></a></td>
<td>Danke an TeamoRouter für die Unterstützung dieses Projekts! TeamoRouter ist ein Agentic-LLM-Gateway in Enterprise-Qualität, das für Entwickler, KI-Teams und Unternehmen entwickelt wurde. Ganz ohne Abonnement können Sie über eine einzige einheitliche API auf Claude Code, Codex, Gemini CLI, OpenAI Codex und weitere beliebte KI-Agenten zugreifen — bei API-Preisen mit Rabatten von bis zu 90 %.
@@ -69,6 +114,16 @@ Anders als typische API-Relay-Dienste bündelt TeamoRouter Hunderte offizieller
TeamoRouter bietet außerdem Enterprise-Funktionen wie zentrale Abrechnung, Team-Verwaltung, BYOK, intelligentes Routing, Nutzungsanalysen, dynamische Anbieter-Optimierung und dedizierten Support. Für ein noch einfacheres Erlebnis können Sie mit Teamo Desktop Claude Code, Codex, Gemini CLI und weitere beliebte KI-Agenten per Ein-Klick-Einrichtung nutzen — ohne Verwaltung von API-Schlüsseln oder manuelle Gateway-Konfiguration. Registrieren Sie sich als neuer Nutzer über <a href="https://teamorouter.com/?utm_source=cc_switch&utm_medium=referral&utm_campaign=ai_directory">diesen Link</a> und erhalten Sie 10 % Rabatt auf Ihre erste Aufladung.</td>
</tr>
<tr>
<td width="180"><a href="https://www.newapi.ai/"><img src="assets/partners/logos/newapi-banner.png" alt="new-api" width="150"></a></td>
<td>Vielen Dank an das Open-Source-KI-Infrastrukturprojekt <a href="https://www.newapi.ai/">new-api</a> für die tatkräftige Unterstützung dieses Projekts! new-api ist ein Open-Source-KI-Infrastrukturprojekt von QuantumNous und eines der nach Aktivität und Verbreitung führenden Projekte für den einheitlichen Zugang zu und die Verteilung von LLMs, das sich darauf konzentriert, Entwicklern, Teams und Unternehmen beim Aufbau verwaltbarer und skalierbarer KI-Serviceplattformen zu geringeren Kosten zu helfen. Als ein ebenfalls im Open-Source-Ökosystem verwurzeltes Projekt möchte new-api durch Sponsoring die kontinuierliche Weiterentwicklung weiterer herausragender Open-Source-Projekte unterstützen. 🌟 Unterstützen Sie new-api mit einem Star: <a href="https://github.com/QuantumNous/new-api">https://github.com/QuantumNous/new-api</a>. Website: <a href="https://www.newapi.ai/">https://www.newapi.ai/</a>.</td>
</tr>
<tr>
<td width="180"><a href="https://claudecn.ai/register?aff=HEL9"><img src="assets/partners/logos/claudecn.jpg" alt="ClaudeCN" width="150"></a></td>
<td>Danke an ClaudeCN für die Unterstützung dieses Projekts! ClaudeCN ist eine unternehmensgerechte KI-Gateway-Plattform, die von einem eingetragenen Unternehmen betrieben wird. Sie bietet hochverfügbaren kommerziellen API-Zugriff auf beliebte Modelle wie Claude, GPT und DeepSeek und ist auf formelle Unternehmensbeschaffungsprozesse ausgerichtet — Banküberweisungen von Firmen, unterzeichnete Verträge und volle Compliance. Registrieren Sie sich über <a href="https://claudecn.ai/register?aff=HEL9">diesen Link</a>!</td>
</tr>
<tr>
<td width="180"><a href="https://www.byteplus.com/en/product/modelark?utm_campaign=hw&utm_content=ccswitch&utm_medium=devrel_tool_web&utm_source=OWO&utm_term=ccswitch"><img src="assets/partners/logos/byteplus.png" alt="BytePlus" width="150"></a></td>
<td>Danke an Dola seed für die Unterstützung dieses Projekts! Dola Seed 2.0 ist ein voll-modales Allzweck-Großmodell, das von ByteDance eigenständig für den globalen Markt entwickelt wurde. Aufbauend auf einer einheitlichen multimodalen Architektur unterstützt es das gemeinsame Verstehen und Generieren von Text, Bildern, Audio und Video. Es ermöglicht von Haus aus die Zusammenarbeit von Agenten und verfügt über starke Fähigkeiten in den Bereichen Schlussfolgern, Ausführung langer Aufgaben, Werkzeugintegration und Programmierung. Es ist breit einsetzbar — etwa für intelligente Cockpits, persönliche Assistenten, Bildung, Kundensupport, Marketing, Einzelhandel und weitere Szenarien. Es überzeugt bei multimodaler Wahrnehmung, der Ende-zu-Ende-Bewältigung komplexer Aufgaben, stabiler Interaktion und Datensicherheit und ist über die ModelArk-Plattform einfach zugänglich und bereitstellbar. Registrieren Sie sich über <a href="https://www.byteplus.com/en/product/modelark?utm_campaign=hw&utm_content=ccswitch&utm_medium=devrel_tool_web&utm_source=OWO&utm_term=ccswitch">diesen Link</a> und erhalten Sie pro Modell ein kostenloses Inferenzkontingent von 500.000 Token.<a href="https://www.volcengine.com/activity/ai618?utm_campaign=hw&utm_content=hw&utm_medium=devrel_tool_web&utm_source=OWO&utm_term=ccswitch"> >>中国大陆地区的开发者请点击这里</a></td>
@@ -80,13 +135,13 @@ TeamoRouter bietet außerdem Enterprise-Funktionen wie zentrale Abrechnung, Team
</tr>
<tr>
<td width="180"><a href="https://cubence.com/signup?code=CCSWITCH&source=ccs"><img src="assets/partners/logos/cubence.png" alt="Cubence" width="150"></a></td>
<td>Danke an Cubence für die Unterstützung dieses Projekts! Cubence ist ein zuverlässiger und effizienter API-Relay-Anbieter, der Relay-Dienste für Claude Code, Codex, Gemini und mehr mit flexiblen Abrechnungsoptionen einschließlich nutzungsbasierter und monatlicher Pläne bereitstellt. Cubence bietet Sonderrabatte für CC-Switch-Nutzer: Registrieren Sie sich über <a href="https://cubence.com/signup?code=CCSWITCH&source=ccs">diesen Link</a> und geben Sie beim Aufladen den Gutscheincode „CCSWITCH" ein, um bei jeder Aufladung 10 % Rabatt zu erhalten!</td>
<td width="180"><a href="https://nekocode.ai?aff=CCSWITCH"><img src="assets/partners/logos/nekocode-banner.png" alt="NekoCode" width="150"></a></td>
<td>Vielen Dank an <a href="https://nekocode.ai?aff=CCSWITCH">NekoCode</a> für die Unterstützung dieses Projekts! NekoCode bietet Entwicklern einen stabilen, effizienten und zuverlässigen API-Relay-Dienst für Claude, Codex und weitere KI-Modelle. Mit transparenter Preisgestaltung und flexibler nutzungsbasierter Abrechnung bietet es einen einfachen und kostengünstigen Zugang zu KI-Modellen. CC-Switch-Nutzer erhalten einen exklusiven Rabatt von 10 %: Registrieren Sie sich über <a href="https://nekocode.ai?aff=CCSWITCH">diesen Link</a> und geben Sie beim Aufladen den Gutscheincode <code>cc-switch</code> ein, um 10 % Rabatt auf Ihre Aufladung zu erhalten!</td>
</tr>
<tr>
<td width="180"><a href="https://www.dmxapi.cn/register?aff=bUHu"><img src="assets/partners/logos/dmx-en.jpg" alt="DMXAPI" width="150"></a></td>
<td>Danke an DMXAPI für die Unterstützung dieses Projekts! DMXAPI stellt mehr als 200 Unternehmenskunden globale Großmodell-API-Dienste bereit. Ein API-Schlüssel für alle Modelle weltweit. Zu den Funktionen gehören: sofortige Rechnungsstellung, unbegrenzte Nebenläufigkeit, ab 0,15 $, technischer Support rund um die Uhr. GPT/Claude/Gemini durchgehend zu 32 % Rabatt, inländische Modelle 2050 % Rabatt, exklusive Claude-Code-Modelle zu 66 % Rabatt! <a href="https://www.dmxapi.cn/register?aff=bUHu">Hier registrieren</a></td>
<td width="180"><a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch"><img src="assets/partners/logos/atlascloud_banner.png" alt="Atlas Cloud" width="150"></a></td>
<td>Atlas Cloud ist eine vollmodale KI-Inferenzplattform, die Entwicklern über eine einzige KI-API Zugriff auf Videogenerierung, Bildgenerierung und LLM-APIs bietet. Statt mehrere Anbieterintegrationen zu verwalten, verbinden Sie sich einmal und erhalten einheitlichen Zugriff auf mehr als 300 kuratierte Modelle über alle Modalitäten hinweg. Sehen Sie sich die neue <a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch">Coding-Plan</a>-Aktion von Atlas Cloud für kostengünstigeren API-Zugang an!</td>
</tr>
<tr>
@@ -95,13 +150,8 @@ TeamoRouter bietet außerdem Enterprise-Funktionen wie zentrale Abrechnung, Team
</tr>
<tr>
<td width="180"><a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch"><img src="assets/partners/logos/crazyrouter.png" alt="Crazyrouter" width="150"></a></td>
<td>Danke an Crazyrouter für die Unterstützung dieses Projekts! Crazyrouter ist eine leistungsstarke KI-API-Aggregationsplattform — ein API-Schlüssel für mehr als 300 Modelle, darunter Claude Code, Codex, Gemini CLI und weitere. Alle Modelle zu 55 % des offiziellen Preises, mit automatischem Failover, intelligentem Routing und unbegrenzter Nebenläufigkeit. Crazyrouter bietet ein exklusives Angebot für CC-Switch-Nutzer: Registrieren Sie sich über <a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch">diesen Link</a> und kontaktieren Sie den Kundensupport, um <strong>2 $ Gratisguthaben</strong> zu erhalten; geben Sie zusätzlich bei Ihrer ersten Aufladung den Gutscheincode `CCSWITCH` ein, um <strong>30 % Bonusguthaben</strong> zu bekommen! </td>
</tr>
<tr>
<td width="180"><a href="https://www.right.codes/register?aff=CCSWITCH"><img src="assets/partners/logos/rightcode.jpg" alt="RightCode" width="150"></a></td>
<td>Danke an Right Code für die Unterstützung dieses Projekts! Right Code stellt zuverlässig Routing-Dienste für Modelle wie Claude Code, Codex und Gemini bereit, wahlweise mit nutzungsbasierter Abrechnung oder monatlichem Abonnement. Rechnungen sind beim Aufladen verfügbar, und Unternehmens- sowie Teamkunden erhalten dedizierten Einzelsupport. Right Code bietet außerdem einen exklusiven Rabatt für CC-Switch-Nutzer: Registrieren Sie sich über <a href="https://www.right.codes/register?aff=CCSWITCH">diesen Link</a> und erhalten Sie bei jeder Aufladung nutzungsbasiertes Guthaben in Höhe von 5 % des gezahlten Betrags.</td>
<td width="180"><a href="https://www.ccsub.net/register?ref=Y6Z8DXEA"><img src="assets/partners/logos/ccsub.svg" alt="CCSub" width="150"></a></td>
<td>Danke an CCSub für die Unterstützung dieses Projekts! CCSub ist eine zuverlässige und kostengünstige AI-API-Relay-Plattform — Ihr direkter Ersatz für ein Claude.ai-Abonnement. Mit einem einzigen API-Schlüssel erhalten Sie Zugriff auf Claude Opus 4.8, Sonnet, Haiku, GPT-5, Gemini und DeepSeek zu etwa 30 % der Kosten der direkten API-Nutzung — ohne VPN, weltweit nutzbar. Kompatibel mit Claude Code, Codex, Cursor, Cline, Continue, Windsurf und allen gängigen AI-Coding-Tools. Registrieren Sie sich über <a href="https://www.ccsub.net/register?ref=Y6Z8DXEA">diesen Link</a> und erhalten Sie $5 Startguthaben bei der Anmeldung.</td>
</tr>
<tr>
@@ -114,69 +164,29 @@ TeamoRouter bietet außerdem Enterprise-Funktionen wie zentrale Abrechnung, Team
<td>Danke an Micu API für die Unterstützung dieses Projekts! Micu API ist ein globaler LLM-Relay-Anbieter, der sich der Bereitstellung des besten Preis-Leistungs-Verhältnisses bei hoher Stabilität widmet. Gestützt auf ein eingetragenes Unternehmen als Kernabsicherung wird jedes Risiko einer Diensteinstellung ausgeschlossen, mit schneller offizieller Rechnungsstellung! Wir stehen für „kostenloses Ausprobieren": Aufladungen sind schon ab ¥1 ohne Mindestbetrag möglich, und gebührenfreie Rückerstattungen sind jederzeit möglich! Micu API bietet ein exklusives Angebot für CC-Switch-Nutzer: Registrieren Sie sich über <a href="https://www.micuapi.ai/register?aff=aOYQ">diesen Link</a> und geben Sie beim Aufladen den Gutscheincode „ccswitch" ein, um <strong>10 % Rabatt</strong> zu erhalten!</td>
</tr>
<tr>
<td width="180"><a href="https://www.right.codes/register?aff=CCSWITCH"><img src="assets/partners/logos/rightcode.jpg" alt="RightCode" width="150"></a></td>
<td>Danke an Right Code für die Unterstützung dieses Projekts! Right Code stellt zuverlässig Routing-Dienste für Modelle wie Claude Code, Codex und Gemini bereit, wahlweise mit nutzungsbasierter Abrechnung oder monatlichem Abonnement. Rechnungen sind beim Aufladen verfügbar, und Unternehmens- sowie Teamkunden erhalten dedizierten Einzelsupport. Right Code bietet außerdem einen exklusiven Rabatt für CC-Switch-Nutzer: Registrieren Sie sich über <a href="https://www.right.codes/register?aff=CCSWITCH">diesen Link</a> und erhalten Sie bei jeder Aufladung nutzungsbasiertes Guthaben in Höhe von 5 % des gezahlten Betrags.</td>
</tr>
<tr>
<td width="180"><a href="https://etok.ai"><img src="assets/partners/logos/etok.png" alt="ETok" width="150"></a></td>
<td>Danke an ETok.ai für die Unterstützung dieses Projekts! ETok.ai widmet sich dem Aufbau einer Komplettlösung für KI-Programmierwerkzeuge. Wir bieten professionelle Claude-Code-Pakete und Dienste einer technischen Community, mit Unterstützung für Google Gemini und OpenAI Codex. Durch sorgfältig gestaltete Pläne und eine professionelle Tech-Community geben wir Entwicklern verlässliche Servicegarantien und kontinuierlichen technischen Support an die Hand und machen KI-gestützte Programmierung zu einem echten Produktivitätswerkzeug. Klicken Sie <a href="https://etok.ai">hier</a>, um sich zu registrieren!</td>
</tr>
<tr>
<td width="180"><a href="https://console.claudeapi.com/register?aff=pCLD"><img src="assets/partners/logos/claudeapi.png" alt="ClaudeAPI" width="150"></a></td>
<td>Dieses Projekt wird von <a href="https://console.claudeapi.com/register?aff=pCLD">Claude API</a> gesponsert. Direkter Claude-API-Zugriff — verbinden Sie Claude Code und Agent-Apps in 3 Minuten. Neukunden können ein kostenloses Testguthaben einlösen. Betrieben mit offiziellen Anthropic-API-Schlüsseln + offiziellen AWS-Bedrock-Kanälen. Kein Reverse Engineering, keine Modellverschlechterung. Volle Unterstützung der Modellreihe Opus / Sonnet / Haiku, mit erhaltenen offiziellen Fähigkeiten einschließlich Tool Use, 1M-Kontextfenster und mehr. Entwickelt für Claude-Code-Power-User, Agent-Ingenieure und technische Unternehmensteams. Rechnungsstellung und dedizierter Team-Support verfügbar. Klicken Sie <a href="https://console.claudeapi.com/register?aff=pCLD">hier</a>, um sich zu registrieren!</td>
<td width="180"><a href="https://cubence.com/signup?code=CCSWITCH&source=ccs"><img src="assets/partners/logos/cubence.png" alt="Cubence" width="150"></a></td>
<td>Danke an Cubence für die Unterstützung dieses Projekts! Cubence ist ein zuverlässiger und effizienter API-Relay-Anbieter, der Relay-Dienste für Claude Code, Codex, Gemini und mehr mit flexiblen Abrechnungsoptionen einschließlich nutzungsbasierter und monatlicher Pläne bereitstellt. Cubence bietet Sonderrabatte für CC-Switch-Nutzer: Registrieren Sie sich über <a href="https://cubence.com/signup?code=CCSWITCH&source=ccs">diesen Link</a> und geben Sie beim Aufladen den Gutscheincode „CCSWITCH" ein, um bei jeder Aufladung 10 % Rabatt zu erhalten!</td>
</tr>
<tr>
<td width="180"><a href="https://code0.ai?source=ccswitch"><img src="assets/partners/logos/code0.png" alt="code0.ai" width="150"></a></td>
<td>Vielen Dank an <a href="https://code0.ai?source=ccswitch">code0.ai</a> für die Unterstützung dieses Projekts! code0.ai ist eine für Entwickler entwickelte AI-Coding-Service-Plattform, die Claude Code, Codex, Gemini und weitere gängige AI-Coding-Funktionen unterstützt. Sie hilft einzelnen Entwicklern und Teams, AI-Agents stabiler und effizienter für Programmierung, Debugging, Refactoring und Automatisierungs-Workflows zu nutzen. ccswitch-Nutzer können über die <a href="https://code0.ai?source=ccswitch">code0.ai-Website</a> den Kundensupport kontaktieren, um Testguthaben zu erhalten und einen zuverlässigen AI-Coding-Service zu erleben.</td>
<td width="180"><a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch"><img src="assets/partners/logos/crazyrouter.png" alt="Crazyrouter" width="150"></a></td>
<td>Danke an Crazyrouter für die Unterstützung dieses Projekts! Crazyrouter ist eine leistungsstarke KI-API-Aggregationsplattform — ein API-Schlüssel für mehr als 300 Modelle, darunter Claude Code, Codex, Gemini CLI und weitere. Alle Modelle zu 55 % des offiziellen Preises, mit automatischem Failover, intelligentem Routing und unbegrenzter Nebenläufigkeit. Crazyrouter bietet ein exklusives Angebot für CC-Switch-Nutzer: Registrieren Sie sich über <a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch">diesen Link</a> und kontaktieren Sie den Kundensupport, um <strong>2 $ Gratisguthaben</strong> zu erhalten; geben Sie zusätzlich bei Ihrer ersten Aufladung den Gutscheincode `CCSWITCH` ein, um <strong>30 % Bonusguthaben</strong> zu bekommen! </td>
</tr>
<tr>
<td width="180"><a href="https://claudecn.top"><img src="assets/partners/logos/claudecn.jpg" alt="ClaudeCN" width="150"></a></td>
<td>Danke an ClaudeCN für die Unterstützung dieses Projekts! ClaudeCN ist eine unternehmensgerechte KI-Gateway-Plattform, die von einem eingetragenen Unternehmen betrieben wird. Sie bietet hochverfügbaren kommerziellen API-Zugriff auf beliebte Modelle wie Claude, GPT und DeepSeek und ist auf formelle Unternehmensbeschaffungsprozesse ausgerichtet — Banküberweisungen von Firmen, unterzeichnete Verträge und volle Compliance. Registrieren Sie sich über <a href="https://claudecn.top">diesen Link</a>!</td>
</tr>
<tr>
<td width="180"><a href="https://runapi.co"><img src="assets/partners/logos/runapi.jpg" alt="RunAPI" width="150"></a></td>
<td>Danke an RunAPI für die Unterstützung dieses Projekts! RunAPI ist ein leistungsstarkes und zuverlässiges KI-Modell-API-Gateway — ein API-Schlüssel gibt Ihnen Zugriff auf mehr als 150 gängige Modelle, darunter OpenAI, Claude, Gemini, DeepSeek und Grok, zu Preisen ab 10 % des offiziellen Tarifs und mit ausgezeichneter Stabilität. Es arbeitet nahtlos mit Claude Code, OpenClaw und weiteren Werkzeugen zusammen. Exklusiver Vorteil für CC-Switch-Nutzer: Registrieren Sie sich und kontaktieren Sie den Kundensupport, um ein kostenloses Guthaben von ¥14 einzulösen. Registrieren Sie sich über <a href="https://runapi.co">diesen Link</a>!</td>
</tr>
<tr>
<td width="180"><a href="https://apikey.fun/register?aff=CCSwitch"><img src="assets/partners/logos/apikey_banner.png" alt="APIKEY.FUN" width="150"></a></td>
<td>Danke an APIKEY.FUN für die Unterstützung dieses Projekts! APIKEY.FUN ist eine professionelle KI-Relay-Plattform auf Enterprise-Niveau, die Unternehmen und einzelnen Entwicklern stabilen, effizienten und kostengünstigen Zugriff auf KI-Modell-APIs bietet. Die Plattform unterstützt beliebte Mainstream-Modelle wie Claude, OpenAI und Gemini, mit Preisen ab 7 % der offiziellen Tarife. Wer sich über den <a href="https://apikey.fun/register?aff=CCSwitch">exklusiven Link</a> dieses Projekts registriert, kann ein exklusives Angebot von bis zu <strong>dauerhaft 5 % Rabatt auf Aufladungen</strong> erhalten.</td>
</tr>
<tr>
<td width="180"><a href="https://apinebula.com/02rw5X"><img src="assets/partners/logos/apinebula_banner.png" alt="APINebula" width="150"></a></td>
<td>Danke an APINEBULA für die Unterstützung dieses Projekts! APINEBULA ist eine Enterprise-KI-Aggregationsplattform unter Galaxy Video Bureau und nutzt umfangreiche Plattformressourcen, um Entwicklern, Teams und Unternehmen stabilen und kosteneffizienten Zugang zu APIs großer Sprachmodelle zu bieten. Die Plattform bündelt führende vollwertige Modelle wie Claude, GPT und Gemini. Über eine einzige API erhalten Sie Zugriff auf weltweit führende KI-Modelle, mit Preisen ab 10 % des Originalpreises. APINEBULA ist für KI-Programmierung, Agent-Entwicklung und Integration in Geschäftssysteme ausgelegt und unterstützt enterprise-grade hohe Parallelität, formale Verträge, Firmenüberweisungen und Rechnungsstellung. APINEBULA bietet Nutzern dieser Software besondere Rabatte: Registrieren Sie sich über <a href="https://apinebula.com/02rw5X">diesen Link</a> und geben Sie beim ersten Aufladen den Aktionscode <strong>"ccswitch"</strong> ein, um <strong>10 % Rabatt</strong> zu erhalten.</td>
</tr>
<tr>
<td width="180"><a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch"><img src="assets/partners/logos/atlascloud_banner.png" alt="Atlas Cloud" width="150"></a></td>
<td>Atlas Cloud ist eine vollmodale KI-Inferenzplattform, die Entwicklern über eine einzige KI-API Zugriff auf Videogenerierung, Bildgenerierung und LLM-APIs bietet. Statt mehrere Anbieterintegrationen zu verwalten, verbinden Sie sich einmal und erhalten einheitlichen Zugriff auf mehr als 300 kuratierte Modelle über alle Modalitäten hinweg. Sehen Sie sich die neue <a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch">Coding-Plan</a>-Aktion von Atlas Cloud für kostengünstigeren API-Zugang an!</td>
</tr>
<tr>
<td width="180"><a href="https://www.ccsub.net/register?ref=Y6Z8DXEA"><img src="assets/partners/logos/ccsub.svg" alt="CCSub" width="150"></a></td>
<td>Danke an CCSub für die Unterstützung dieses Projekts! CCSub ist eine zuverlässige und kostengünstige AI-API-Relay-Plattform — Ihr direkter Ersatz für ein Claude.ai-Abonnement. Mit einem einzigen API-Schlüssel erhalten Sie Zugriff auf Claude Opus 4.8, Sonnet, Haiku, GPT-5, Gemini und DeepSeek zu etwa 30 % der Kosten der direkten API-Nutzung — ohne VPN, weltweit nutzbar. Kompatibel mit Claude Code, Codex, Cursor, Cline, Continue, Windsurf und allen gängigen AI-Coding-Tools. Registrieren Sie sich über <a href="https://www.ccsub.net/register?ref=Y6Z8DXEA">diesen Link</a> und erhalten Sie $5 Startguthaben bei der Anmeldung.</td>
</tr>
<tr>
<td width="180"><a href="https://unity2.ai/register?source=ccs"><img src="assets/partners/logos/unity2.jpg" alt="Unity2.ai" width="150"></a></td>
<td>Danke an Unity2.ai für die Unterstützung dieses Projekts! Unity2.ai ist eine leistungsstarke AI-Modell-API-Relay-Plattform für Einzelentwickler, Teams und Unternehmen. Sie wird seit Langem von führenden Unternehmen in China genutzt, verarbeitet täglich über 30 Milliarden Tokens und unterstützt hohe Parallelität auf 5.000-RPM-Niveau. Geboten werden Guthaben-Abrechnung, Ersteinzahlungsbonus, Kombi-Abonnements, Firmenrechnungen und persönliche Betreuung. Registrieren Sie sich über <a href="https://unity2.ai/register?source=ccs">diesen Link</a> und erhalten Sie $2 Guthaben, plus weitere $10 für den Beitritt zur offiziellen Gruppe — bis zu $12 Gratis-Guthaben!</td>
</tr>
<tr>
<td width="180"><a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL"><img src="assets/partners/logos/fenno-banner.png" alt="Fenno.ai" width="150"></a></td>
<td>Danke an Fenno.ai für die Unterstützung dieses Projekts! Fenno.ai ist ein stabiler und effizienter API-Relay-Dienstleister, der sich derzeit hauptsächlich auf Codex-Relay konzentriert. Er ist mit den OpenAI- und Anthropic-Protokollen kompatibel und lässt sich flexibel mit Codex, Claude Code, OpenCode und anderen gängigen Coding-Tools nutzen. Er unterstützt zuverlässig Workloads auf Unternehmensniveau von Hunderten Milliarden Tokens pro Tag und bietet B2B-Abrechnung sowie Rechnungsstellung für Unternehmen im In- und Ausland. Fenno.ai bietet einen exklusiven Vorteil für CC-Switch-Nutzer: Abonnieren Sie über <a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL">diesen Link</a> den unschlagbaren ¥9,9-Coding-Plan im Wert von $150 Guthaben und erhalten Sie bis zu 20% Empfehlungsprämien — je mehr Einladungen, desto mehr Belohnung!</td>
</tr>
<tr>
<td width="180"><a href="https://zetaapi.ai/go/ccs"><img src="assets/partners/logos/zetaapi-banner.png" alt="ZetaAPI" width="150"></a></td>
<td>Danke an ZetaAPI für die Unterstützung dieses Projekts! ZetaAPI legt den Fokus auf echte Modelltreue — keine verwässerten Antworten, keine Qualitätsminderung — und Preise von nur 35 % der offiziellen Tarife. Die Plattform mischt keinen Traffic, ersetzt Modelle nicht heimlich durch minderwertige Alternativen und nutzt kein gefälschtes Modell-Routing. Sie unterstützt Claude Code, Codex, Gemini, ChatGPT und weitere gängige KI-Modelle und hilft Nutzern, die API-Kosten deutlich zu senken und gleichzeitig eine zuverlässige Modellqualität zu gewährleisten. Gleichzeitig bietet ZetaAPI eine SLA-gestützte Stabilität auf Unternehmensniveau, Standard-API-Kompatibilität, einen API-Key für mehrere Modelle, schnelle Integration und nutzungsbasierte Abrechnung — geeignet für KI-Produkte, Coding-Agents, interne Unternehmenstools, Kundenservice-Systeme, Content-Erstellung und Automatisierungs-Workflows. Falls bei einem Modell nachgewiesen wird, dass es nicht der angegebenen Qualität entspricht, sichert ZetaAPI dies mit einer 10-fachen Entschädigungsgarantie ab und bietet Nutzern ein stabileres, transparenteres und vertrauenswürdigeres Erlebnis. Registrieren Sie sich über <a href="https://zetaapi.ai/go/ccs">diesen Link</a> und verwenden Sie bei Ihrer ersten Aufladung den Promo-Code CC-SWITCH, um als CC-Switch-Nutzer einen exklusiven Rabatt von 10 % auf Ihre erste Aufladung zu erhalten!</td>
</tr>
<tr>
<td width="180"><a href="https://nekocode.ai?aff=CCSWITCH"><img src="assets/partners/logos/nekocode-banner.png" alt="NekoCode" width="150"></a></td>
<td>Vielen Dank an <a href="https://nekocode.ai?aff=CCSWITCH">NekoCode</a> für die Unterstützung dieses Projekts! NekoCode bietet Entwicklern einen stabilen, effizienten und zuverlässigen API-Relay-Dienst für Claude, Codex und weitere KI-Modelle. Mit transparenter Preisgestaltung und flexibler nutzungsbasierter Abrechnung bietet es einen einfachen und kostengünstigen Zugang zu KI-Modellen. CC-Switch-Nutzer erhalten einen exklusiven Rabatt von 10 %: Registrieren Sie sich über <a href="https://nekocode.ai?aff=CCSWITCH">diesen Link</a> und geben Sie beim Aufladen den Gutscheincode <code>cc-switch</code> ein, um 10 % Rabatt auf Ihre Aufladung zu erhalten!</td>
<td width="180"><a href="https://www.dmxapi.cn/register?aff=bUHu"><img src="assets/partners/logos/dmx-en.jpg" alt="DMXAPI" width="150"></a></td>
<td>Danke an DMXAPI für die Unterstützung dieses Projekts! DMXAPI stellt mehr als 200 Unternehmenskunden globale Großmodell-API-Dienste bereit. Ein API-Schlüssel für alle Modelle weltweit. Zu den Funktionen gehören: sofortige Rechnungsstellung, unbegrenzte Nebenläufigkeit, ab 0,15 $, technischer Support rund um die Uhr. GPT/Claude/Gemini durchgehend zu 32 % Rabatt, inländische Modelle 2050 % Rabatt, exklusive Claude-Code-Modelle zu 66 % Rabatt! <a href="https://www.dmxapi.cn/register?aff=bUHu">Hier registrieren</a></td>
</tr>
</table>
@@ -185,13 +195,13 @@ TeamoRouter bietet außerdem Enterprise-Funktionen wie zentrale Abrechnung, Team
## Warum CC Switch?
Modernes KI-gestütztes Programmieren stützt sich auf Werkzeuge wie Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw und Hermes — doch jedes hat sein eigenes Konfigurationsformat. Der Wechsel des API-Anbieters bedeutet, JSON-, TOML- oder `.env`-Dateien von Hand zu bearbeiten, und es gibt keine einheitliche Möglichkeit, MCP und Skills über mehrere Werkzeuge hinweg zu verwalten.
Modernes KI-gestütztes Programmieren stützt sich auf Werkzeuge wie Claude Code, Claude Desktop, Codex, Gemini CLI, Grok Build, OpenCode, OpenClaw und Hermes — doch jedes hat sein eigenes Konfigurationsformat. Der Wechsel des API-Anbieters bedeutet, JSON-, TOML- oder `.env`-Dateien von Hand zu bearbeiten, und es gibt keine einheitliche Möglichkeit, MCP und Skills über mehrere Werkzeuge hinweg zu verwalten.
**CC Switch** gibt Ihnen eine einzige Desktop-App, um alle unterstützten KI-Werkzeuge zu verwalten. Statt Konfigurationsdateien von Hand zu bearbeiten, erhalten Sie eine visuelle Oberfläche, um Anbieter mit einem Klick zu importieren und sofort zwischen ihnen zu wechseln — mit 50+ integrierten Anbieter-Presets, einheitlicher MCP- und Skills-Verwaltung und schnellem Umschalten über das System-Tray. Das Ganze gestützt auf eine zuverlässige SQLite-Datenbank mit atomaren Schreibvorgängen, die Ihre Konfigurationen vor Beschädigung schützen.
- **Eine App, sieben Werkzeuge** — Verwalten Sie Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw und Hermes über eine einzige Oberfläche
- **Eine App, acht Werkzeuge** — Verwalten Sie Claude Code, Claude Desktop, Codex, Gemini CLI, Grok Build, OpenCode, OpenClaw und Hermes über eine einzige Oberfläche
- **Kein manuelles Bearbeiten mehr** — 50+ Anbieter-Presets einschließlich AWS Bedrock, NVIDIA NIM und Community-Relays; einfach auswählen und umschalten
- **Einheitliche MCP- & Skills-Verwaltung** — Ein Panel zur Verwaltung von MCP-Servern und Skills für Claude, Codex, Gemini, OpenCode und Hermes mit bidirektionaler Synchronisierung
- **Einheitliche MCP- & Skills-Verwaltung** — Ein Panel zur Verwaltung von MCP-Servern und Skills für Claude, Codex, Gemini, Grok Build, OpenCode und Hermes mit bidirektionaler Synchronisierung
- **Schnellumschaltung über System-Tray** — Wechseln Sie Anbieter sofort über das Tray-Menü, ohne die vollständige App öffnen zu müssen
- **Cloud-Synchronisierung** — Synchronisieren Sie Anbieterdaten geräteübergreifend über Dropbox, OneDrive, iCloud oder WebDAV-Server
- **Plattformübergreifend** — Native Desktop-App für Windows, macOS und Linux, gebaut mit Tauri 2
@@ -209,18 +219,18 @@ Modernes KI-gestütztes Programmieren stützt sich auf Werkzeuge wie Claude Code
### Anbieterverwaltung
- **7 unterstützte Werkzeuge, 50+ Presets** — Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw, Hermes; Schlüssel kopieren und mit einem Klick importieren
- **8 unterstützte Werkzeuge, 50+ Presets** — Claude Code, Claude Desktop, Codex, Gemini CLI, Grok Build, OpenCode, OpenClaw, Hermes; Schlüssel kopieren und mit einem Klick importieren
- **Universelle Anbieter** — Eine Konfiguration synchronisiert sich mit Claude Code, Codex und Gemini CLI
- Umschaltung mit einem Klick, Schnellzugriff über System-Tray, Sortierung per Drag-and-drop, Import/Export
### Proxy & Failover
- **Lokaler Proxy mit Hot-Switching** — Formatkonvertierung, automatisches Failover, Circuit Breaker, Anbieter-Health-Monitoring und Request-Rectifier
- **Übernahme auf App-Ebene** — Claude, Codex oder Gemini unabhängig über den Proxy leiten, bis hinunter auf einzelne Anbieter
- **Übernahme auf App-Ebene** — Claude, Codex, Gemini oder Grok Build unabhängig über den Proxy leiten, bis hinunter auf einzelne Anbieter
### MCP, Prompts & Skills
- **Einheitliches MCP-Panel** — Verwalten Sie MCP-Server für Claude, Codex, Gemini, OpenCode und Hermes mit bidirektionaler Synchronisierung und Deep-Link-Import
- **Einheitliches MCP-Panel** — Verwalten Sie MCP-Server für Claude, Codex, Gemini, Grok Build, OpenCode und Hermes mit bidirektionaler Synchronisierung und Deep-Link-Import
- **Prompts** — Markdown-Editor mit App-übergreifender Synchronisierung (CLAUDE.md / AGENTS.md / GEMINI.md) und Backfill-Schutz
- **Skills** — Installation mit einem Klick aus GitHub-Repositorys oder ZIP-Dateien, Verwaltung eigener Repositorys, mit Unterstützung für Symlinks und Dateikopien
@@ -244,7 +254,7 @@ Modernes KI-gestütztes Programmieren stützt sich auf Werkzeuge wie Claude Code
<details>
<summary><strong>Welche KI-Werkzeuge unterstützt CC Switch?</strong></summary>
CC Switch unterstützt sieben Werkzeuge: **Claude Code**, **Claude Desktop**, **Codex**, **Gemini CLI**, **OpenCode**, **OpenClaw** und **Hermes**. Jedes Werkzeug verfügt über dedizierte Anbieter-Presets und Konfigurationsverwaltung.
CC Switch unterstützt acht Werkzeuge: **Claude Code**, **Claude Desktop**, **Codex**, **Gemini CLI**, **Grok Build**, **OpenCode**, **OpenClaw** und **Hermes**. Jedes Werkzeug verfügt über dedizierte Anbieter-Presets und Konfigurationsverwaltung.
</details>
+90 -80
View File
@@ -2,7 +2,7 @@
# CC Switch
### Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw、Hermes Agent のオールインワン管理ツール
### Claude Code、Claude Desktop、Codex、Gemini CLI、Grok Build、OpenCode、OpenClaw、Hermes Agent のオールインワン管理ツール
[![Version](https://img.shields.io/github/v/release/farion1231/cc-switch?color=blue&label=version)](https://github.com/farion1231/cc-switch/releases)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](https://github.com/farion1231/cc-switch/releases)
@@ -25,11 +25,11 @@
<details open>
<summary>クリックで折りたたむ</summary>
[![Kimi K2.7 Code](assets/partners/banners/kimi-banner-en.png)](https://platform.kimi.ai?aff=cc-switch)
[![Kimi K2.7 Code](https://gcdn.moonshot.cn/growth-cdn/sponsor/kimi-en.png)](https://platform.kimi.ai?aff=cc-switch)
Kimi K2.7 Code は Moonshot AI が開発した、コーディングに特化したオープンソースのエージェントモデルです。コーディング能力とエージェント性能が全面的に強化され、実世界の長程コーディングタスクで大幅な向上を実現し、複雑なソフトウェアエンジニアリングのワークフロー全体でエンドツーエンドのタスク成功率を高めます。さらに K2.7 Code は推論効率を改善し、K2.6 と比べて推論トークンの消費を約 30% 削減します。**[ここをクリックして Kimi を体験す](https://platform.kimi.ai?aff=cc-switch)**
Kimi K3 は Moonshot AI がこれまでに開発した中で最も高性能なモデルであり、世界初のオープンソース 3T クラスモデルです。2.8 兆パラメータ、ネイティブな視覚能力、100 万トークンのコンテキストウィンドウを備え、長期にわたるコーディング、ナレッジワーク、推論タスクにおいてフロンティア級の性能を発揮します。CC Switch を使えば、さまざまなエージェントツールで Kimi を手軽に設定・切り替えできます。**[ここをクリックして Kimi を使い始め](https://platform.kimi.ai?aff=cc-switch)**
コーディング作業がメインですか?コーディングのために作られたサブスクリプション、**[Kimi For Coding プラ](https://www.kimi.com/code/?aff=cc-switch)** をぜひお試しください!
コーディング作業がメインですか?**[Kimi Code サブスクリプショ](https://www.kimi.com/code/?aff=cc-switch)** をぜひお試しください!
---
@@ -40,8 +40,13 @@ Kimi K2.7 Code は Moonshot AI が開発した、コーディングに特化し
</tr>
<tr>
<td width="180"><a href="https://aigocode.com/invite/CC-SWITCH"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>本プロジェクトは AIGoCode のスポンサー提供でお届けしています。AIGoCode は、Claude CodeCodex・最新の Gemini モデルを統合したオールインワンのAIコーディングプラットフォームで、安定性・高速性・コストパフォーマンスに優れた開発サービスを提供します。柔軟なサブスクリプションプランを備え、レスポンスも非常に高速です。さらに、CC Switch ユーザー向けの特典として、<a href="https://aigocode.com/invite/CC-SWITCH">このリンク</a>から登録すると、初回チャージ時に10%分のボーナスクレジットが付与されます!</td>
<td width="180"><a href="https://zetaapi.ai/go/u117"><img src="assets/partners/logos/zetaapi-banner.png" alt="ZetaAPI" width="150"></a></td>
<td>本プロジェクトをご支援いただいている ZetaAPI に感謝します!ZetaAPI は、モデル品質の忠実性、水増しなし、性能劣化なし、公式価格の 35% から利用できる低価格を主な特徴としています。プラットフォームはトラフィックの混在、低品質モデルへの密かな切り替え、虚偽のモデルルーティングを行わず、Claude CodeCodexGemini、ChatGPT などの主要 AI モデルに対応しており、モデル品質を維持しながら API 利用コストを大幅に削減できます。同時に、ZetaAPI はエンタープライズ級の SLA 安定性保証、標準 API 互換、1つの Key による複数モデル接続、迅速な導入、従量課金などの機能を提供し、AI プロダクト、コード生成、企業内ツール、カスタマーサポート、コンテンツ生成、自動化ワークフローなどの用途に適しています。万が一、モデル品質が表記内容と一致しないことが確認された場合、ZetaAPI は 10 倍補償保証を提供し、ユーザーがより安定して、透明性高く、安心して利用できる環境を実現します。<a href="https://zetaapi.ai/go/u117">こちらのリンク</a>から登録し、初回チャージ時にプロモコード CC-SWITCH を使用すると、CC Switch ユーザー限定の初回チャージ 10% オフ特典をご利用いただけます!</td>
</tr>
<tr>
<td width="180"><a href="https://apinebula.com/VjM74M"><img src="assets/partners/logos/apinebula_banner.png" alt="APINebula" width="150"></a></td>
<td>本プロジェクトは「APINEBULA」のスポンサーシップにより運営されています!APINEBULA は、「銀河録像局」傘下のエンタープライズ向け AI 統合プラットフォームです。大手の豊富なリソースを背景に、開発者、チーム、そして企業ユーザーの皆様へ、安定性とコストパフォーマンスに優れた大規模言語モデル(LLM)の API 連携サービスを提供しています。Claude、GPT、Gemini をはじめとする世界中の主要なフルスペック(満血)モデルを 1 つの API に集約。世界トップクラスの AI モデルを、<strong>最大 90% OFF(元の価格の 1 割〜)</strong>という圧倒的な低価格でご利用いただけます。また、企業向けの高度な並行処理(高コンカレンシー)、正式な契約締結、法人口座振り込み、請求書・領収書発行など、ビジネス利用に必要なサポートも万全です。AI プログラミング、AI エージェント開発、業務システムへの統合など、様々なシーンに最適です。<a href="https://apinebula.com/VjM74M">こちらのリンク</a>から登録し、チャージ時にプロモコード <strong>「ccswitch」を入力すると、さらに 10% OFF</strong> の割引特典が適用されます!</td>
</tr>
<tr>
@@ -50,11 +55,6 @@ Kimi K2.7 Code は Moonshot AI が開発した、コーディングに特化し
Claude Code / Codex / Gemini 公式チャンネルが最安で元価格の 38% / 2% / 9%、チャージ時にはさらに割引!AICodeMirror は CC Switch ユーザー向けに特別特典を用意:<a href="https://www.aicodemirror.com/register?invitecode=9915W3">このリンク</a>から登録すると初回チャージ 20% オフ、法人のお客様は最大 25% オフ!</td>
</tr>
<tr>
<td width="180"><a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF"><img src="assets/partners/logos/shengsuanyun.png" alt="Shengsuanyun" width="150"></a></td>
<td>胜算雲(Shengsuanyun)のご支援に感謝します!胜算雲は AI ネイティブチーム向けのスーパーファクトリーであり、産業グレードの AI タスク並列実行プラットフォームです。モデルマーケットプレイスでは Claude、ChatGPT、Gemini をはじめとする国内外の LLM およびマルチメディアモデルの計算リソースを集約・直接提供。リバースエンジニアリングや品質低下は一切なく、プラットフォーム全体のモデル SLA 可用性は 99.7% に達し、<a href="https://watch.shengsuanyun.com/status/shengsuanyun">監視ダッシュボード</a>は常時グリーン表示です。さらにエンタープライズ向けカスタムゲートウェイを提供し、チームのきめ細かなコスト・権限管理、スマートルーティング、セキュリティ保護、BYOK(自社キー持ち込み)ホスティングを実現します。従量課金およびトークンプラン(近日公開)対応で、請求書発行にも対応。<a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF">このリンク</a>から新規登録すると 10 元分のクレジットと初回チャージ 10% ボーナスが付与されます。</td>
</tr>
<tr>
<td width="180"><a href="https://pateway.ai/?ch=etzpm8&aff=WB6M6F67#/"><img src="assets/partners/logos/pateway.png" alt="PatewayAI" width="150"></a></td>
<td>PatewayAI のご支援に感謝します!PatewayAI はヘビーな AI 開発者向けに、公式直結の高品質モデル API 中継サービスを専門に提供するプロバイダーです。Claude シリーズ全モデルおよび Codex シリーズに対応し、100% 公式ソースから直接提供。混ぜ物・水増しは一切なく、検証も歓迎します。課金は透明で、トークン単位の請求書を 1 件ずつ照合可能です。
@@ -62,6 +62,51 @@ Claude Code / Codex / Gemini 公式チャンネルが最安で元価格の 38% /
現在、<a href="https://pateway.ai/?ch=etzpm8&aff=WB6M6F67#/">このリンク</a>からご登録いただくと $3 のトライアルクレジットを進呈。チャージは最安で元価格の 60%、友達紹介は双方にボーナスが付与され、紹介報酬は最大 $150!</td>
</tr>
<tr>
<td width="180"><a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL"><img src="assets/partners/logos/fenno-banner.png" alt="Fenno.ai" width="150"></a></td>
<td>Fenno.ai のご支援に感謝します!Fenno.ai は安定かつ高効率な API 中継サービスプロバイダーで、現在は主に Codex の中継を提供しています。OpenAI および Anthropic プロトコルに対応し、Codex・Claude Code・OpenCode などの主要なコーディングツールから柔軟に利用できます。1 日あたり数千億トークンというエンタープライズ級の呼び出し需要を安定して支え、国内外の法人による B2B 決済・請求書発行に対応しています。Fenno.ai は CC Switch 利用者限定の特典を用意しています:<a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL">こちらのリンク</a>から 9.9 元(150 ドル相当)のお得な Coding Plan を購入でき、友達紹介で最大 20% の特典がもらえます。紹介すればするほどお得です!</td>
</tr>
<tr>
<td width="180"><a href="https://runapi.co/register?aff=iOKB"><img src="assets/partners/logos/runapi.jpg" alt="RunAPI" width="150"></a></td>
<td>本プロジェクトのスポンサーである RunAPI に感謝いたします!RunAPI は高効率で安定した AI モデル API ゲートウェイです。一つの API Key で、OpenAI、Claude、Gemini、DeepSeek、Grok など 150 種類以上の主要モデルにアクセス可能。料金は公式価格の最大 10%、安定性にも優れ、Claude Code や OpenClaw などのツールとシームレスに連携できます。CC Switch ユーザー限定特典:<a href="https://runapi.co/register?aff=iOKB">こちらのリンク</a>から登録し、初回チャージで 10% オフの割引をお楽しみいただけます!</td>
</tr>
<tr>
<td width="180"><a href="https://unity2.ai/register?source=ccs"><img src="assets/partners/logos/unity2.jpg" alt="Unity2.ai" width="150"></a></td>
<td>Unity2.ai のご支援に感謝します!Unity2.ai は個人開発者・チーム・企業向けの高性能 AI モデル API リレープラットフォームです。中国の大手企業に長年利用されており、1 日 300 億トークン以上を処理し、5000 RPM クラスの高並列に対応しています。残高課金、初回チャージボーナス、組み合わせサブスクリプション、企業向け請求書発行、専任サポートを提供。<a href="https://unity2.ai/register?source=ccs">こちらのリンク</a>から登録すると $2 のクレジット、公式グループへの参加でさらに $10、最大 $12 の無料クレジットがもらえます!</td>
</tr>
<tr>
<td width="180"><a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF"><img src="assets/partners/logos/shengsuanyun.png" alt="Shengsuanyun" width="150"></a></td>
<td>胜算雲(Shengsuanyun)のご支援に感謝します!胜算雲は AI ネイティブチーム向けのスーパーファクトリーであり、産業グレードの AI タスク並列実行プラットフォームです。モデルマーケットプレイスでは Claude、ChatGPT、Gemini をはじめとする国内外の LLM およびマルチメディアモデルの計算リソースを集約・直接提供。リバースエンジニアリングや品質低下は一切なく、プラットフォーム全体のモデル SLA 可用性は 99.7% に達し、<a href="https://watch.shengsuanyun.com/status/shengsuanyun">監視ダッシュボード</a>は常時グリーン表示です。さらにエンタープライズ向けカスタムゲートウェイを提供し、チームのきめ細かなコスト・権限管理、スマートルーティング、セキュリティ保護、BYOK(自社キー持ち込み)ホスティングを実現します。従量課金およびトークンプラン(近日公開)対応で、請求書発行にも対応。<a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF">このリンク</a>から新規登録すると 10 元分のクレジットと初回チャージ 10% ボーナスが付与されます。</td>
</tr>
<tr>
<td width="180"><a href="https://aigocode.com/invite/CC-SWITCH"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>本プロジェクトは AIGoCode のスポンサー提供でお届けしています。AIGoCode は、Claude Code・Codex・最新の Gemini モデルを統合したオールインワンのAIコーディングプラットフォームで、安定性・高速性・コストパフォーマンスに優れた開発サービスを提供します。柔軟なサブスクリプションプランを備え、レスポンスも非常に高速です。さらに、CC Switch ユーザー向けの特典として、<a href="https://aigocode.com/invite/CC-SWITCH">このリンク</a>から登録すると、初回チャージ時に10%分のボーナスクレジットが付与されます!</td>
</tr>
<tr>
<td width="180"><a href="https://subrouter.ai/register?aff=l3ri"><img src="assets/partners/logos/subrouter-banner.png" alt="SubRouter" width="150"></a></td>
<td>本プロジェクトをご支援いただいている SubRouter に感謝します!SubRouter は、AI サービス事業者向けのマーケットプレイス兼スマートルーティングプラットフォームです。事業者は独立した運営サイトを立ち上げ、プランを公開し、ユーザー・モデル・価格を管理でき、ユーザーはマーケットでサービスを見つけ、統一された API を通じて安定かつ高効率なモデル呼び出しを利用できます。<a href="https://subrouter.ai/register?aff=l3ri">こちらのリンク</a>から登録してください!</td>
</tr>
<tr>
<td width="180"><a href="https://apikey.fun/register?aff=CCSwitch"><img src="assets/partners/logos/apikey_banner.png" alt="APIKEY.FUN" width="150"></a></td>
<td>APIKEY.FUN のご支援に感謝します!APIKEY.FUN は、企業および個人開発者向けに安定・高効率・低コストな AI モデル API 接続サービスを提供する、プロフェッショナルなエンタープライズ級 AI リレープラットフォームです。Claude、OpenAI、Gemini などの主要人気モデルに対応し、料金は公式価格の 7% から利用できます。本プロジェクトの<a href="https://apikey.fun/register?aff=CCSwitch">専用リンク</a>から登録すると、最大で<strong>チャージ永久 5% オフ</strong>の特別優待も受けられます。</td>
</tr>
<tr>
<td width="180"><a href="https://console.claudeapi.com/register?aff=pCLD"><img src="assets/partners/logos/claudeapi.png" alt="ClaudeAPI" width="150"></a></td>
<td>本プロジェクトは <a href="https://console.claudeapi.com/register?aff=pCLD">Claude API</a> がスポンサーです。Claude API 直結 — わずか 3 分で Claude Code や Agent アプリに接続可能。新規ユーザーにはテストクレジットを提供しています。Anthropic 公式キーおよび AWS Bedrock 公式チャネルに基づいており、リバースエンジニアリングや性能劣化はありません。Opus / Sonnet / Haiku の全モデルラインナップをサポートし、Tool Use や 1M コンテキストなどの公式機能をすべて保持しています。Claude Code ヘビーユーザー、Agent エンジニア、企業技術チームに最適です。請求書発行およびチーム対応が可能です。<a href="https://console.claudeapi.com/register?aff=pCLD">こちら</a>から登録してください!</td>
</tr>
<tr>
<td width="180"><a href="https://code0.ai/agent/register/B2XHxGjGmRvqgznY"><img src="assets/partners/logos/code0.png" alt="code0.ai" width="150"></a></td>
<td>本プロジェクトをご支援いただいている <a href="https://code0.ai/agent/register/B2XHxGjGmRvqgznY">code0.ai</a> に感謝します!code0.ai は開発者向けの AI コーディングサービスプラットフォームで、Claude Code、Codex、Gemini などの主要な AI コーディング機能に対応しています。個人開発者やチームが、コーディング、デバッグ、リファクタリング、自動化ワークフローで AI Agent をより安定かつ効率的に活用できるよう支援します。ccswitch ユーザーは <a href="https://code0.ai/agent/register/B2XHxGjGmRvqgznY">code0.ai 公式サイト</a> からカスタマーサポートに連絡することで、テストクレジットを受け取り、信頼性の高い AI コーディングサービスを体験できます。</td>
</tr>
<tr>
<td width="180"><a href="https://teamorouter.com/?utm_source=cc_switch&utm_medium=referral&utm_campaign=ai_directory"><img src="assets/partners/logos/TeamoRouter-banner.png" alt="TeamoRouter" width="150"></a></td>
<td>このプロジェクトをご支援いただいている TeamoRouter に感謝します!TeamoRouter は、開発者、AI チーム、企業向けに構築されたエンタープライズグレードの Agentic LLM ゲートウェイです。サブスクリプション不要で、単一の統合 API を通じて Claude Code、Codex、Gemini CLI、OpenAI Codex、その他の人気 AI エージェントにアクセスでき、API 料金は最大 90% オフで利用できます。
@@ -69,6 +114,16 @@ Claude Code / Codex / Gemini 公式チャンネルが最安で元価格の 38% /
TeamoRouter は、集中請求、チーム管理、BYOK、スマートルーティング、利用状況分析、動的なプロバイダー最適化、専任サポートなどのエンタープライズ機能も提供しています。さらにシンプルな体験を求める場合は、Teamo Desktop を使うことで、Claude Code、Codex、Gemini CLI、その他の人気 AI エージェントをワンクリックで利用できます。API キー管理や手動のゲートウェイ設定は不要です。新規ユーザーとして<a href="https://teamorouter.com/?utm_source=cc_switch&utm_medium=referral&utm_campaign=ai_directory">こちらのリンク</a>から登録すると、初回チャージが 10% オフになります。</td>
</tr>
<tr>
<td width="180"><a href="https://www.newapi.ai/"><img src="assets/partners/logos/newapi-banner.png" alt="new-api" width="150"></a></td>
<td>オープンソースの AI インフラプロジェクト <a href="https://www.newapi.ai/">new-api</a> による本プロジェクトへの多大なご支援に感謝します!new-api は QuantumNous(锟腾科技)が開発したオープンソースの AI インフラプロジェクトであり、活発さと利用規模の面でリードする LLM 統合アクセス・配信プロジェクトの一つで、開発者・チーム・企業がより低コストで管理・拡張可能な AI サービスプラットフォームを構築できるよう支援することに注力しています。同じくオープンソースエコシステムに根ざすプロジェクトとして、new-api はスポンサーシップを通じて、より多くの優れたオープンソースプロジェクトの継続的な発展を支援したいと考えています。🌟 new-api への Star で応援をお願いします:<a href="https://github.com/QuantumNous/new-api">https://github.com/QuantumNous/new-api</a>。公式サイト:<a href="https://www.newapi.ai/">https://www.newapi.ai/</a>。</td>
</tr>
<tr>
<td width="180"><a href="https://claudecn.ai/register?aff=HEL9"><img src="assets/partners/logos/claudecn.jpg" alt="ClaudeCN" width="150"></a></td>
<td>本プロジェクトのスポンサーである ClaudeCN に感謝いたします!ClaudeCN は、実体のある企業によって運営されるエンタープライズ向け AI ゲートウェイプラットフォームです。Claude、GPT、DeepSeek など主要モデルへの高可用な商用 API アクセスを提供し、企業の調達プロセスにも対応 — 法人振込や正式契約に対応し、コンプライアンス面でも安心してご利用いただけます。<a href="https://claudecn.ai/register?aff=HEL9">こちら</a>からご登録ください!</td>
</tr>
<tr>
<td width="180"><a href="https://www.byteplus.com/en/product/modelark?utm_campaign=hw&utm_content=ccswitch&utm_medium=devrel_tool_web&utm_source=OWO&utm_term=ccswitch"><img src="assets/partners/logos/byteplus.png" alt="BytePlus" width="150"></a></td>
<td>Dola seed のご支援に感謝します!Dola Seed 2.0 は ByteDance がグローバル市場向けに独自開発したフルモーダル汎用大規模モデルです。統一されたマルチモーダルアーキテクチャを基盤に、テキスト・画像・音声・動画の統合的な理解と生成をサポートします。エージェント連携をネイティブに実現し、強力な推論、長時間タスクの実行、ツール統合、コーディング能力を備えています。スマートコックピット、パーソナルアシスタント、教育、カスタマーサポート、マーケティング、リテールなど幅広いシナリオに適用可能で、マルチモーダル認識、エンドツーエンドの複雑なタスク遂行、安定したインタラクション、データセキュリティに優れ、ModelArk プラットフォームを通じて手軽に利用・デプロイできます。<a href="https://www.byteplus.com/en/product/modelark?utm_campaign=hw&utm_content=ccswitch&utm_medium=devrel_tool_web&utm_source=OWO&utm_term=ccswitch">このリンク</a>からご登録いただくと、モデルごとに 500,000 トークンの無料推論クォータを進呈します。<a href="https://www.volcengine.com/activity/ai618?utm_campaign=hw&utm_content=hw&utm_medium=devrel_tool_web&utm_source=OWO&utm_term=ccswitch"> >>中国大陆地区的开发者请点击这里</a></td>
@@ -80,13 +135,13 @@ TeamoRouter は、集中請求、チーム管理、BYOK、スマートルーテ
</tr>
<tr>
<td width="180"><a href="https://cubence.com/signup?code=CCSWITCH&source=ccs"><img src="assets/partners/logos/cubence.png" alt="Cubence" width="150"></a></td>
<td>Cubence のご支援に感謝します!Cubence は Claude Code、Codex、Gemini などのリレーサービスを提供する信頼性の高い API 中継プラットフォームで、従量課金や月額プランなど柔軟な料金体系を提供しています。CC Switch ユーザー向けの特別割引<a href="https://cubence.com/signup?code=CCSWITCH&source=ccs">このリンク</a>登録し、チャージ時に「CCSWITCH」クーポンを入力すると、毎回 10% オフになります!</td>
<td width="180"><a href="https://nekocode.ai?aff=CCSWITCH"><img src="assets/partners/logos/nekocode-banner.png" alt="NekoCode" width="150"></a></td>
<td>本プロジェクトをご支援いただいている <a href="https://nekocode.ai?aff=CCSWITCH">NekoCode</a> に感謝します!NekoCode はClaude や Codex などの AI モデルに対応した、安定性・効率性・信頼性に優れた API 中継サービスを提供しています。料金体系は明瞭で、柔軟な従量課金にも対応しています。CC Switch ユーザー限定の 10%オフ特典<a href="https://nekocode.ai?aff=CCSWITCH">こちらのリンク</a> から登録し、チャージ時にクーポンコード <code>cc-switch</code> を入力すると、チャージが 10%オフになります!</td>
</tr>
<tr>
<td width="180"><a href="https://www.dmxapi.cn/register?aff=bUHu"><img src="assets/partners/logos/dmx-en.jpg" alt="DMXAPI" width="150"></a></td>
<td>DMXAPI のご支援に感謝します!DMXAPI は 200 社以上の企業ユーザーにグローバル大規模モデル API サービスを提供しています。1 つの API キーで全世界のモデルにアクセス可能。即時請求書発行、同時接続数無制限、最低 $0.15 から、24 時間年中無休のテクニカルサポート。GPT/Claude/Gemini が全て 32% オフ、国内モデルは 20〜50% オフ、Claude Code 専用モデルは 66% オフ実施中!<a href="https://www.dmxapi.cn/register?aff=bUHu">登録はこちら</a></td>
<td width="180"><a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch"><img src="assets/partners/logos/atlascloud_banner.png" alt="Atlas Cloud" width="150"></a></td>
<td>Atlas Cloud は、1 つの API で動画・画像生成や LLM(大規模言語モデル)を利用できる全モーダル対応の AI 推論プラットフォームです。複数のベンダーを個別に管理する手間を省き、一度の接続で 300 以上の厳選されたマルチモーダルモデルにアクセスできます。より低コストで API を利用できる、開発者向けの新しい<a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch">「コーディングプラン」</a>プロモーションをぜひチェックしてください!</td>
</tr>
<tr>
@@ -95,13 +150,8 @@ TeamoRouter は、集中請求、チーム管理、BYOK、スマートルーテ
</tr>
<tr>
<td width="180"><a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch"><img src="assets/partners/logos/crazyrouter.png" alt="Crazyrouter" width="150"></a></td>
<td>Crazyrouter のご支援に感謝します!Crazyrouter は高性能 AI API アグリゲーションプラットフォームです。1 つの API キーで Claude Code、Codex、Gemini CLI など 300 以上のモデルにアクセス可能。全モデル公式価格の 55% で利用でき、自動フェイルオーバー、スマートルーティング、無制限同時接続に対応。CC Switch ユーザー向けの限定特典:<a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch">こちらのリンク</a>から登録後、カスタマーサポートまでご連絡いただくと <strong>$2 の無料クレジット</strong> を受け取れます。さらに初回チャージ時にプロモコード `CCSWITCH` を入力すると <strong>30% のボーナスクレジット</strong> が追加されます</td>
</tr>
<tr>
<td width="180"><a href="https://www.right.codes/register?aff=CCSWITCH"><img src="assets/partners/logos/rightcode.jpg" alt="RightCode" width="150"></a></td>
<td>本プロジェクトへのご支援として、Right Code にご協賛いただき誠にありがとうございます。Right Code は、Claude Code、Codex、Gemini などのモデル向け中継サービスを安定して提供しており、従量課金と月額プランの 2 つの料金体系から選択できます。チャージ後に請求書の発行が可能で、法人・チームのお客様には専任担当による個別対応も行っています。さらに、CC Switch ユーザー向けの特別優待として、<a href="https://www.right.codes/register?aff=CCSWITCH">こちらのリンク</a>から登録すると、チャージのたびに実際の支払額の 5% 相当の従量課金クレジットが付与されます。</td>
<td width="180"><a href="https://www.ccsub.net/register?ref=Y6Z8DXEA"><img src="assets/partners/logos/ccsub.svg" alt="CCSub" width="150"></a></td>
<td>CCSub のご支援に感謝します!CCSub は安定した低価格の AI API リレープラットフォームで、Claude Code 公式サブスクリプションの強力な代替です。1 つの API キーで Claude Opus 4.8、Sonnet 4.6、Haiku 4.5、GPT-5、Gemini、DeepSeek の全モデル公式直接利用の約 1/3 のコストでご利用いただけます。VPN 不要で世界中から直接接続可能。Claude Code、Codex、Cursor、Cline、Continue、Windsurf など主要な AI コーディングツールすべてに対応しています。<a href="https://www.ccsub.net/register?ref=Y6Z8DXEA">こちらのリンク</a>から登録すると $5 の無料クレジットがもらえます</td>
</tr>
<tr>
@@ -114,69 +164,29 @@ TeamoRouter は、集中請求、チーム管理、BYOK、スマートルーテ
<td>Micu API のご支援に感謝します!Micu API は、最高のコストパフォーマンスと高い安定性を追求するグローバル大規模言語モデル中継サービスプロバイダーです。法人企業がバックアップしており、サービス停止のリスクを排除、迅速な正規請求書発行に対応!「試行コストゼロ」をモットーに、最低 1 元からチャージ可能で手数料無料、いつでも返金可能!CC Switch ユーザー向けの限定特典:<a href="https://www.micuapi.ai/register?aff=aOYQ">こちらのリンク</a>から登録し、チャージ時にプロモコード「ccswitch」を入力すると <strong>10% 割引</strong> が適用されます!</td>
</tr>
<tr>
<td width="180"><a href="https://www.right.codes/register?aff=CCSWITCH"><img src="assets/partners/logos/rightcode.jpg" alt="RightCode" width="150"></a></td>
<td>本プロジェクトへのご支援として、Right Code にご協賛いただき誠にありがとうございます。Right Code は、Claude Code、Codex、Gemini などのモデル向け中継サービスを安定して提供しており、従量課金と月額プランの 2 つの料金体系から選択できます。チャージ後に請求書の発行が可能で、法人・チームのお客様には専任担当による個別対応も行っています。さらに、CC Switch ユーザー向けの特別優待として、<a href="https://www.right.codes/register?aff=CCSWITCH">こちらのリンク</a>から登録すると、チャージのたびに実際の支払額の 5% 相当の従量課金クレジットが付与されます。</td>
</tr>
<tr>
<td width="180"><a href="https://etok.ai"><img src="assets/partners/logos/etok.png" alt="ETok" width="150"></a></td>
<td>ETok.ai のご支援に感謝します!ETok.ai はワンストップ AI プログラミングツールサービスプラットフォームの構築に取り組んでいます。Claude Code のプロフェッショナルプランと技術コミュニティサービスを提供し、Google Gemini や OpenAI Codex にも対応しています。丁寧に設計されたプランと専門的な技術コミュニティを通じて、開発者に安定したサービス保証と継続的な技術サポートを提供し、AI アシストプログラミングを真の生産性ツールにします。<a href="https://etok.ai">こちら</a>から登録してください!</td>
</tr>
<tr>
<td width="180"><a href="https://console.claudeapi.com/register?aff=pCLD"><img src="assets/partners/logos/claudeapi.png" alt="ClaudeAPI" width="150"></a></td>
<td>本プロジェクトは <a href="https://console.claudeapi.com/register?aff=pCLD">Claude API</a> がスポンサーです。Claude API 直結 — わずか 3 分で Claude Code や Agent アプリに接続可能。新規ユーザーにはテストクレジットを提供しています。Anthropic 公式キーおよび AWS Bedrock 公式チャネルに基づいており、リバースエンジニアリングや性能劣化はありません。Opus / Sonnet / Haiku の全モデルラインナップをサポートし、Tool Use や 1M コンテキストなどの公式機能をすべて保持しています。Claude Code ヘビーユーザー、Agent エンジニア、企業技術チームに最適です。請求書発行およびチーム対応が可能です。<a href="https://console.claudeapi.com/register?aff=pCLD">こちら</a>から登録してください</td>
<td width="180"><a href="https://cubence.com/signup?code=CCSWITCH&source=ccs"><img src="assets/partners/logos/cubence.png" alt="Cubence" width="150"></a></td>
<td>Cubence のご支援に感謝します!Cubence は Claude CodeCodex、Gemini などのリレーサービスを提供する信頼性の高い API 中継プラットフォームで、従量課金や月額プランなど柔軟な料金体系を提供しています。CC Switch ユーザー向けの特別割引:<a href="https://cubence.com/signup?code=CCSWITCH&source=ccs">このリンク</a>登録し、チャージ時に「CCSWITCH」クーポンを入力すると、毎回 10% オフになります</td>
</tr>
<tr>
<td width="180"><a href="https://code0.ai?source=ccswitch"><img src="assets/partners/logos/code0.png" alt="code0.ai" width="150"></a></td>
<td>本プロジェクトをご支援いただいている <a href="https://code0.ai?source=ccswitch">code0.ai</a> に感謝します!code0.ai は開発者向けの AI コーディングサービスプラットフォームで、Claude Code、Codex、Gemini などの主要な AI コーディング機能に対応しています。個人開発者やチームが、コーディング、デバッグ、リファクタリング、自動化ワークフローで AI Agent をより安定かつ効率的に活用できるよう支援します。ccswitch ユーザー<a href="https://code0.ai?source=ccswitch">code0.ai 公式サイト</a> からカスタマーサポートに連絡することで、テストクレジットを受け取り、信頼性の高い AI コーディングサービスを体験できます</td>
<td width="180"><a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch"><img src="assets/partners/logos/crazyrouter.png" alt="Crazyrouter" width="150"></a></td>
<td>Crazyrouter のご支援に感謝します!Crazyrouter は高性能 AI API アグリゲーションプラットフォームです。1 つの API キーで Claude Code、Codex、Gemini CLI など 300 以上のモデルにアクセス可能。全モデルが公式価格の 55% で利用でき、自動フェイルオーバー、スマートルーティング、無制限同時接続に対応。CC Switch ユーザー向けの限定特典:<a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch">こちらのリンク</a>から登録後、カスタマーサポートまでご連絡いただくと <strong>$2 の無料クレジット</strong> を受け取れます。さらに初回チャージ時にプロモコード `CCSWITCH` を入力すると <strong>30% のボーナスクレジット</strong> が追加されます</td>
</tr>
<tr>
<td width="180"><a href="https://claudecn.top"><img src="assets/partners/logos/claudecn.jpg" alt="ClaudeCN" width="150"></a></td>
<td>本プロジェクトのスポンサーである ClaudeCN に感謝いたします!ClaudeCN は、実体のある企業によって運営されるエンタープライズ向け AI ゲートウェイプラットフォームです。Claude、GPT、DeepSeek など主要モデルへの高可用な商用 API アクセスを提供し、企業の調達プロセスにも対応 — 法人振込や正式契約に対応し、コンプライアンス面でも安心してご利用いただけます。<a href="https://claudecn.top">こちら</a>からご登録ください!</td>
</tr>
<tr>
<td width="180"><a href="https://runapi.co"><img src="assets/partners/logos/runapi.jpg" alt="RunAPI" width="150"></a></td>
<td>本プロジェクトのスポンサーである RunAPI に感謝いたします!RunAPI は高効率で安定した AI モデル API ゲートウェイです。一つの API Key で、OpenAI、Claude、Gemini、DeepSeek、Grok など 150 種類以上の主要モデルにアクセス可能。料金は公式価格の最大 10%、安定性にも優れ、Claude Code や OpenClaw などのツールとシームレスに連携できます。CC Switch ユーザー限定特典:ご登録後にカスタマーサポートへご連絡いただくと、14 元の無料クレジットを進呈いたします。<a href="https://runapi.co">こちら</a>からご登録ください!</td>
</tr>
<tr>
<td width="180"><a href="https://apikey.fun/register?aff=CCSwitch"><img src="assets/partners/logos/apikey_banner.png" alt="APIKEY.FUN" width="150"></a></td>
<td>APIKEY.FUN のご支援に感謝します!APIKEY.FUN は、企業および個人開発者向けに安定・高効率・低コストな AI モデル API 接続サービスを提供する、プロフェッショナルなエンタープライズ級 AI リレープラットフォームです。Claude、OpenAI、Gemini などの主要人気モデルに対応し、料金は公式価格の 7% から利用できます。本プロジェクトの<a href="https://apikey.fun/register?aff=CCSwitch">専用リンク</a>から登録すると、最大で<strong>チャージ永久 5% オフ</strong>の特別優待も受けられます。</td>
</tr>
<tr>
<td width="180"><a href="https://apinebula.com/02rw5X"><img src="assets/partners/logos/apinebula_banner.png" alt="APINebula" width="150"></a></td>
<td>本プロジェクトは「APINEBULA」のスポンサーシップにより運営されています!APINEBULA は、「銀河録像局」傘下のエンタープライズ向け AI 統合プラットフォームです。大手の豊富なリソースを背景に、開発者、チーム、そして企業ユーザーの皆様へ、安定性とコストパフォーマンスに優れた大規模言語モデル(LLM)の API 連携サービスを提供しています。Claude、GPT、Gemini をはじめとする世界中の主要なフルスペック(満血)モデルを 1 つの API に集約。世界トップクラスの AI モデルを、<strong>最大 90% OFF(元の価格の 1 割〜)</strong>という圧倒的な低価格でご利用いただけます。また、企業向けの高度な並行処理(高コンカレンシー)、正式な契約締結、法人口座振り込み、請求書・領収書発行など、ビジネス利用に必要なサポートも万全です。AI プログラミング、AI エージェント開発、業務システムへの統合など、様々なシーンに最適です。<a href="https://apinebula.com/02rw5X">こちらのリンク</a>から登録し、チャージ時にプロモコード <strong>「ccswitch」を入力すると、さらに 10% OFF</strong> の割引特典が適用されます!</td>
</tr>
<tr>
<td width="180"><a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch"><img src="assets/partners/logos/atlascloud_banner.png" alt="Atlas Cloud" width="150"></a></td>
<td>Atlas Cloud は、1 つの API で動画・画像生成や LLM(大規模言語モデル)を利用できる全モーダル対応の AI 推論プラットフォームです。複数のベンダーを個別に管理する手間を省き、一度の接続で 300 以上の厳選されたマルチモーダルモデルにアクセスできます。より低コストで API を利用できる、開発者向けの新しい<a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch">「コーディングプラン」</a>プロモーションをぜひチェックしてください!</td>
</tr>
<tr>
<td width="180"><a href="https://www.ccsub.net/register?ref=Y6Z8DXEA"><img src="assets/partners/logos/ccsub.svg" alt="CCSub" width="150"></a></td>
<td>CCSub のご支援に感謝します!CCSub は安定した低価格の AI API リレープラットフォームで、Claude Code 公式サブスクリプションの強力な代替です。1 つの API キーで Claude Opus 4.8、Sonnet 4.6、Haiku 4.5、GPT-5、Gemini、DeepSeek の全モデルを公式直接利用の約 1/3 のコストでご利用いただけます。VPN 不要で世界中から直接接続可能。Claude Code、Codex、Cursor、Cline、Continue、Windsurf など主要な AI コーディングツールすべてに対応しています。<a href="https://www.ccsub.net/register?ref=Y6Z8DXEA">こちらのリンク</a>から登録すると $5 の無料クレジットがもらえます。</td>
</tr>
<tr>
<td width="180"><a href="https://unity2.ai/register?source=ccs"><img src="assets/partners/logos/unity2.jpg" alt="Unity2.ai" width="150"></a></td>
<td>Unity2.ai のご支援に感謝します!Unity2.ai は個人開発者・チーム・企業向けの高性能 AI モデル API リレープラットフォームです。中国の大手企業に長年利用されており、1 日 300 億トークン以上を処理し、5000 RPM クラスの高並列に対応しています。残高課金、初回チャージボーナス、組み合わせサブスクリプション、企業向け請求書発行、専任サポートを提供。<a href="https://unity2.ai/register?source=ccs">こちらのリンク</a>から登録すると $2 のクレジット、公式グループへの参加でさらに $10、最大 $12 の無料クレジットがもらえます!</td>
</tr>
<tr>
<td width="180"><a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL"><img src="assets/partners/logos/fenno-banner.png" alt="Fenno.ai" width="150"></a></td>
<td>Fenno.ai のご支援に感謝します!Fenno.ai は安定かつ高効率な API 中継サービスプロバイダーで、現在は主に Codex の中継を提供しています。OpenAI および Anthropic プロトコルに対応し、Codex・Claude Code・OpenCode などの主要なコーディングツールから柔軟に利用できます。1 日あたり数千億トークンというエンタープライズ級の呼び出し需要を安定して支え、国内外の法人による B2B 決済・請求書発行に対応しています。Fenno.ai は CC Switch 利用者限定の特典を用意しています:<a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL">こちらのリンク</a>から 9.9 元(150 ドル相当)のお得な Coding Plan を購入でき、友達紹介で最大 20% の特典がもらえます。紹介すればするほどお得です!</td>
</tr>
<tr>
<td width="180"><a href="https://zetaapi.ai/go/ccs"><img src="assets/partners/logos/zetaapi-banner.png" alt="ZetaAPI" width="150"></a></td>
<td>本プロジェクトをご支援いただいている ZetaAPI に感謝します!ZetaAPI は、モデル品質の忠実性、水増しなし、性能劣化なし、公式価格の 35% から利用できる低価格を主な特徴としています。プラットフォームはトラフィックの混在、低品質モデルへの密かな切り替え、虚偽のモデルルーティングを行わず、Claude Code、Codex、Gemini、ChatGPT などの主要 AI モデルに対応しており、モデル品質を維持しながら API 利用コストを大幅に削減できます。同時に、ZetaAPI はエンタープライズ級の SLA 安定性保証、標準 API 互換、1つの Key による複数モデル接続、迅速な導入、従量課金などの機能を提供し、AI プロダクト、コード生成、企業内ツール、カスタマーサポート、コンテンツ生成、自動化ワークフローなどの用途に適しています。万が一、モデル品質が表記内容と一致しないことが確認された場合、ZetaAPI は 10 倍補償保証を提供し、ユーザーがより安定して、透明性高く、安心して利用できる環境を実現します。<a href="https://zetaapi.ai/go/ccs">こちらのリンク</a>から登録し、初回チャージ時にプロモコード CC-SWITCH を使用すると、CC Switch ユーザー限定の初回チャージ 10% オフ特典をご利用いただけます!</td>
</tr>
<tr>
<td width="180"><a href="https://nekocode.ai?aff=CCSWITCH"><img src="assets/partners/logos/nekocode-banner.png" alt="NekoCode" width="150"></a></td>
<td>本プロジェクトをご支援いただいている <a href="https://nekocode.ai?aff=CCSWITCH">NekoCode</a> に感謝します!NekoCode は、Claude や Codex などの AI モデルに対応した、安定性・効率性・信頼性に優れた API 中継サービスを提供しています。料金体系は明瞭で、柔軟な従量課金にも対応しています。CC Switch ユーザー限定の 10%オフ特典:<a href="https://nekocode.ai?aff=CCSWITCH">こちらのリンク</a> から登録し、チャージ時にクーポンコード <code>cc-switch</code> を入力すると、チャージが 10%オフになります!</td>
<td width="180"><a href="https://www.dmxapi.cn/register?aff=bUHu"><img src="assets/partners/logos/dmx-en.jpg" alt="DMXAPI" width="150"></a></td>
<td>DMXAPI のご支援に感謝します!DMXAPI は 200 社以上の企業ユーザーにグローバル大規模モデル API サービスを提供しています。1 つの API キーで全世界のモデルにアクセス可能。即時請求書発行、同時接続数無制限、最低 $0.15 から、24 時間年中無休のテクニカルサポート。GPT/Claude/Gemini が全て 32% オフ、国内モデルは 20〜50% オフ、Claude Code 専用モデルは 66% オフ実施中!<a href="https://www.dmxapi.cn/register?aff=bUHu">登録はこちら</a></td>
</tr>
</table>
@@ -185,13 +195,13 @@ TeamoRouter は、集中請求、チーム管理、BYOK、スマートルーテ
## CC Switch を選ぶ理由
最新の AI コーディングは Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw、Hermes などのツールに依存していますが、各ツールの設定形式はバラバラです。API プロバイダを切り替えるたびに JSON、TOML、`.env` ファイルを手動で編集する必要があり、複数ツール間で MCP や Skills を統一的に管理する手段もありません。
最新の AI コーディングは Claude Code、Claude Desktop、Codex、Gemini CLI、Grok Build、OpenCode、OpenClaw、Hermes などのツールに依存していますが、各ツールの設定形式はバラバラです。API プロバイダを切り替えるたびに JSON、TOML、`.env` ファイルを手動で編集する必要があり、複数ツール間で MCP や Skills を統一的に管理する手段もありません。
**CC Switch** は、対応する AI ツールを 1 つのデスクトップアプリで一元管理できます。設定ファイルを手作業で編集する代わりに、ワンクリックでプロバイダをインポートし、瞬時に切り替えられるビジュアルインターフェースを提供します。50 以上の組み込みプリセット、統一 MCP・Skills 管理、システムトレイからの即時切り替え機能を搭載。すべてはアトミック書き込みによる信頼性の高い SQLite データベースに支えられており、設定の破損を防ぎます。
- **1 つのアプリで 7 つのツール** -- Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw、Hermes を単一インターフェースで管理
- **1 つのアプリで 8 つのツール** -- Claude Code、Claude Desktop、Codex、Gemini CLI、Grok Build、OpenCode、OpenClaw、Hermes を単一インターフェースで管理
- **手動編集は不要** -- AWS Bedrock、NVIDIA NIM、コミュニティリレーなど 50 以上のプロバイダプリセットを内蔵。選んで切り替えるだけ
- **統一 MCP・Skills 管理** -- 1 つのパネルで Claude、Codex、Gemini、OpenCode、Hermes の MCP サーバーと Skills を双方向同期で管理
- **統一 MCP・Skills 管理** -- 1 つのパネルで Claude、Codex、Gemini、Grok Build、OpenCode、Hermes の MCP サーバーと Skills を双方向同期で管理
- **システムトレイでクイック切り替え** -- トレイメニューから即座にプロバイダを切り替え。アプリを開く必要なし
- **クラウド同期** -- Dropbox、OneDrive、iCloud、または WebDAV サーバー経由でデバイス間のプロバイダデータを同期
- **クロスプラットフォーム** -- Tauri 2 で構築された Windows、macOS、Linux 対応のネイティブデスクトップアプリ
@@ -209,18 +219,18 @@ TeamoRouter は、集中請求、チーム管理、BYOK、スマートルーテ
### プロバイダ管理
- **7 つの対応ツール、50 以上のプリセット** -- Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw、Hermes。キーをコピーしてワンクリックでインポート
- **8 つの対応ツール、50 以上のプリセット** -- Claude Code、Claude Desktop、Codex、Gemini CLI、Grok Build、OpenCode、OpenClaw、Hermes。キーをコピーしてワンクリックでインポート
- **ユニバーサルプロバイダ** -- 1 つの設定を Claude Code、Codex、Gemini CLI に同期
- ワンクリック切り替え、システムトレイクイックアクセス、ドラッグ&ドロップ並び替え、インポート/エクスポート
### プロキシ & フェイルオーバー
- **ローカルプロキシのホットスイッチ** -- フォーマット変換、自動フェイルオーバー、サーキットブレーカー、プロバイダヘルスモニタリング、リクエストレクティファイア
- **アプリレベルのテイクオーバー** -- Claude、Codex、Gemini を個別にプロキシ経由でルーティング、プロバイダ単位で設定可能
- **アプリレベルのテイクオーバー** -- Claude、Codex、Gemini、Grok Build を個別にプロキシ経由でルーティング、プロバイダ単位で設定可能
### MCP、Prompts & Skills
- **統一 MCP パネル** -- Claude、Codex、Gemini、OpenCode、Hermes の MCP サーバーを管理、双方向同期、Deep Link インポート対応
- **統一 MCP パネル** -- Claude、Codex、Gemini、Grok Build、OpenCode、Hermes の MCP サーバーを管理、双方向同期、Deep Link インポート対応
- **Prompts** -- Markdown エディタ、クロスアプリ同期(CLAUDE.md / AGENTS.md / GEMINI.md)、バックフィル保護
- **Skills** -- GitHub リポジトリまたは ZIP ファイルからワンクリックインストール、カスタムリポジトリ管理、シンボリックリンクとファイルコピーに対応
@@ -244,7 +254,7 @@ TeamoRouter は、集中請求、チーム管理、BYOK、スマートルーテ
<details>
<summary><strong>CC Switch はどの AI ツールに対応していますか?</strong></summary>
CC Switch は **Claude Code**、**Claude Desktop**、**Codex**、**Gemini CLI**、**OpenCode**、**OpenClaw**、**Hermes** の 7 つのツールに対応しています。各ツールに専用のプロバイダプリセットと設定管理が用意されています。
CC Switch は **Claude Code**、**Claude Desktop**、**Codex**、**Gemini CLI**、**Grok Build**、**OpenCode**、**OpenClaw**、**Hermes** の 8 つのツールに対応しています。各ツールに専用のプロバイダプリセットと設定管理が用意されています。
</details>
+91 -81
View File
@@ -2,7 +2,7 @@
# CC Switch
### Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw 和 Hermes Agent 的全方位管理工具
### Claude Code、Claude Desktop、Codex、Gemini CLI、Grok Build、OpenCode、OpenClaw 和 Hermes Agent 的全方位管理工具
[![Version](https://img.shields.io/github/v/release/farion1231/cc-switch?color=blue&label=version)](https://github.com/farion1231/cc-switch/releases)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](https://github.com/farion1231/cc-switch/releases)
@@ -25,11 +25,11 @@
<details open>
<summary>点击折叠</summary>
[![Kimi K2.7 Code](assets/partners/banners/kimi-banner-zh.png)](https://platform.kimi.com?aff=cc-switch)
[![Kimi K2.7 Code](https://gcdn.moonshot.cn/growth-cdn/sponsor/kimi-zh.png)](https://platform.kimi.com?aff=cc-switch)
Kimi K2.7 Code 是 Moonshot AI 开发的编程专用开源智能体模型。它在编程与智能体执行能力上全面增强,在真实长程编程任务中实现显著提升,带来复杂软件工程工作流中更高的端到端任务成功率。同时,K2.7 Code 优化了推理效率,相较 K2.6 平均减少约 30% 的推理 token 消耗。**[点击此处开 Kimi 使用体验](https://platform.kimi.com?aff=cc-switch)**
Kimi K3 是 Moonshot AI 迄今能力最强的模型,也是全球首个开源 3T 级模型。K3 拥有 2.8T 参数、原生视觉能力与 100 万 Token 上下文,在长程编码、知识工作和推理任务中展现前沿性能。使用 CC Switch,可以在各类 Agent 工具中便捷配置和切换 Kimi。**[点击此处开始使用 Kimi](https://platform.kimi.com?aff=cc-switch)**
主要编程工作?可以试试 **[Kimi For Coding 编程套餐](https://www.kimi.com/code/?aff=cc-switch)**,专为编程打造的订阅服务!
主要进行编程工作?可以试试 **[Kimi Code 订阅](https://www.kimi.com/code/?aff=cc-switch)**
---
@@ -40,8 +40,13 @@ Kimi K2.7 Code 是 Moonshot AI 开发的编程专用开源智能体模型。它
</tr>
<tr>
<td width="180"><a href="https://aigocode.com/invite/CC-SWITCH"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>感谢 AIGoCode 赞助本项目!AIGoCode 是一个集成了 Claude Code、Codex 以及 Gemini 最新模型的一站式平台,为你提供稳定、高效且高性价比的AI编程服务。本站提供灵活的订阅计划,零封号风险,国内直连,无需魔法,极速响应。AIGoCode 为 CC Switch 的用户提供了特别福利,通过<a href="https://aigocode.com/invite/CC-SWITCH">此链接</a>注册的用户首次充值可以获得额外10%奖励额度</td>
<td width="180"><a href="https://zetaapi.ai/go/u117"><img src="assets/partners/logos/zetaapi-banner.png" alt="ZetaAPI" width="150"></a></td>
<td>感谢 ZetaAPI 赞助本项目!ZetaAPI 主打模型不掺水、保真不降智、价格低至官方价 35 折,平台不混量、不暗中替换低质量模型、不做虚假路由,支持 Claude Code、CodexGemini、ChatGPT 等主流模型接入,帮助用户在保证模型质量的同时大幅降低 API 使用成本。同时,ZetaAPI 提供企业级 SLA 稳定性保障、标准接口兼容、一个 Key 接入多模型、快速集成、按量计费等能力,适用于 AI 产品、代码生成、企业内部工具、客服系统、内容生产和自动化流程等场景。若经验证发现模型质量与标称不符,ZetaAPI 承诺假一赔十,让用户用得更稳定、更透明、更放心。通过<a href="https://zetaapi.ai/go/u117">此链接</a>注册,并在首次充值时使用优惠码 CC-SWITCH,即可享受 CC Switch 用户专属的首次充值九折优惠</td>
</tr>
<tr>
<td width="180"><a href="https://apinebula.com/VjM74M"><img src="assets/partners/logos/apinebula_banner.png" alt="APINebula" width="150"></a></td>
<td>感谢 APINEBULA 赞助本项目!APINEBULA 是银河录像局旗下的企业级 AI 聚合平台,背靠大平台资源,面向开发者、团队与企业用户提供稳定、高性价比的大模型 API 接入服务。平台聚合 Claude、GPT、Gemini 等主流满血模型,一个接口,接入全球顶尖 AI 大模型,各大模型价格低至 1 折起,支持企业级高并发、正式合同、对公打款与开票服务,适合 AI 编程、Agent 开发、业务系统集成等多种场景!使用<a href="https://apinebula.com/VjM74M">此链接</a>注册并在充值时填写 <strong>"ccswitch"</strong> 优惠码可享<strong>九折优惠</strong></td>
</tr>
<tr>
@@ -50,11 +55,6 @@ Kimi K2.7 Code 是 Moonshot AI 开发的编程专用开源智能体模型。它
Claude Code / Codex / Gemini 官方渠道低至 3.8 / 0.2 / 0.9 折,充值更有折上折!AICodeMirror 为 CCSwitch 的用户提供了特别福利,通过<a href="https://www.aicodemirror.com/register?invitecode=9915W3">此链接</a>注册的用户,可享受首充8折,企业客户最高可享 7.5 折!</td>
</tr>
<tr>
<td width="180"><a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF"><img src="assets/partners/logos/shengsuanyun.png" alt="Shengsuanyun" width="150"></a></td>
<td>感谢胜算云赞助了本项目!胜算云是专为AI Native Teams服务的超级工厂,工业级AI任务并行执行平台,模型商城集采直供聚合接入了Claude、Chatgpt、Gemini等海内外LLM及图片视频多媒体模型算力,绝无逆向掺水、全站模型SLA可用性高达99.7%、<a href="https://watch.shengsuanyun.com/status/shengsuanyun">监测接口</a>日常全绿。更有企业级专属定制网关,实现团队精细化成本与权限管控,智能路由+安全防护+BYOK企业自带密钥托管。平台按量及tokens plan(即将上线)计费,可开票,使用<a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF">此链接</a>注册新用户可获10元模力及首充10%赠送。</td>
</tr>
<tr>
<td width="180"><a href="https://pateway.ai/?ch=etzpm8&aff=WB6M6F67#/"><img src="assets/partners/logos/pateway.png" alt="PatewayAI" width="150"></a></td>
<td>感谢 PatewayAI 赞助了本项目!PatewayAI 是一家面向重度 AI 开发者、专注官方直连高品质模型 API 中转服务商。提供 Claude 全系列与 Codex 系列模型,100% 官方源直供,不掺假不注水,欢迎检验。计费透明,Token 级账单可逐笔核验。
@@ -62,6 +62,51 @@ Claude Code / Codex / Gemini 官方渠道低至 3.8 / 0.2 / 0.9 折,充值更
现在通过<a href="https://pateway.ai/?ch=etzpm8&aff=WB6M6F67#/">此链接</a>注册即送 $3 试用额度,用户充值低至 6 折,邀请好友双向赠送,邀请奖励可达 $150!</td>
</tr>
<tr>
<td width="180"><a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL"><img src="assets/partners/logos/fenno-banner.png" alt="Fenno.ai" width="150"></a></td>
<td>感谢 Fenno.ai 赞助了本项目!Fenno.ai 是一家稳定、高效的 API 中转服务商,目前主要提供 Codex 中转服务,兼容 OpenAI 及 Anthropic 协议,可灵活接入 Codex、Claude Code、OpenCode 等主流编程工具,可稳定支撑千亿 Token/日的企业级调用需求,支持国内及海外主体公对公结算、开票。Fenno.ai 为 CC Switch 的用户提供了专属福利:通过<a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL">此链接</a>即可订阅 9.9 元/150 刀额度的超值 Coding Plan,邀请好友最高可享 20% 奖励,多邀多得!</td>
</tr>
<tr>
<td width="180"><a href="https://runapi.co/register?aff=iOKB"><img src="assets/partners/logos/runapi.jpg" alt="RunAPI" width="150"></a></td>
<td>感谢 RunAPI 赞助本项目!RunAPI 是高效稳定的 AI 模型 API 中转平台,一个 API Key 即可访问 OpenAI、Claude、Gemini、DeepSeek、Grok 等 150+ 主流模型,低至 1 折,极其稳定,可以无缝兼容 Claude Code、OpenClaw 等工具。RunAPI为CC switch的用户提供了特别福利,使用<a href="https://runapi.co/register?aff=iOKB">此链接</a>注册并首次充值即可享受10%的优惠折扣!</td>
</tr>
<tr>
<td width="180"><a href="https://unity2.ai/register?source=ccs"><img src="assets/partners/logos/unity2.jpg" alt="Unity2.ai" width="150"></a></td>
<td>感谢 Unity2.ai 赞助了本项目!Unity2.ai 是面向个人开发者、团队和企业的高性能 AI 模型 API 中转平台,长期服务国内头部企业,日均承载超 300 亿 token 调用,支持 5000 RPM 级高并发。支持余额计费、首充赠额、组合订阅、企业开票和专属对接。通过<a href="https://unity2.ai/register?source=ccs">此链接</a>注册可领取 $2 余额,加入官方群再送 $10 余额,最高可领 $12 免费额度!</td>
</tr>
<tr>
<td width="180"><a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF"><img src="assets/partners/logos/shengsuanyun.png" alt="Shengsuanyun" width="150"></a></td>
<td>感谢胜算云赞助了本项目!胜算云是专为AI Native Teams服务的超级工厂,工业级AI任务并行执行平台,模型商城集采直供聚合接入了Claude、Chatgpt、Gemini等海内外LLM及图片视频多媒体模型算力,绝无逆向掺水、全站模型SLA可用性高达99.7%、<a href="https://watch.shengsuanyun.com/status/shengsuanyun">监测接口</a>日常全绿。更有企业级专属定制网关,实现团队精细化成本与权限管控,智能路由+安全防护+BYOK企业自带密钥托管。平台按量及tokens plan(即将上线)计费,可开票,使用<a href="https://www.shengsuanyun.com/?from=CH_4HHXMRYF">此链接</a>注册新用户可获10元模力及首充10%赠送。</td>
</tr>
<tr>
<td width="180"><a href="https://aigocode.com/invite/CC-SWITCH"><img src="assets/partners/logos/aigocode.png" alt="AIGoCode" width="150"></a></td>
<td>感谢 AIGoCode 赞助了本项目!AIGoCode 是一个集成了 Claude Code、Codex 以及 Gemini 最新模型的一站式平台,为你提供稳定、高效且高性价比的AI编程服务。本站提供灵活的订阅计划,零封号风险,国内直连,无需魔法,极速响应。AIGoCode 为 CC Switch 的用户提供了特别福利,通过<a href="https://aigocode.com/invite/CC-SWITCH">此链接</a>注册的用户首次充值可以获得额外10%奖励额度!</td>
</tr>
<tr>
<td width="180"><a href="https://subrouter.ai/register?aff=l3ri"><img src="assets/partners/logos/subrouter-banner.png" alt="SubRouter" width="150"></a></td>
<td>感谢 SubRouter 赞助本项目!SubRouter 是面向 AI 服务经营者的公开市场与智能路由平台。商家可快速开通独立经营站,发布套餐、管理用户与模型价格;用户可在市场发现服务,并通过统一 API 获得稳定高效的模型调用。通过<a href="https://subrouter.ai/register?aff=l3ri">此链接</a>注册!</td>
</tr>
<tr>
<td width="180"><a href="https://apikey.fun/register?aff=CCSwitch"><img src="assets/partners/logos/apikey_banner.png" alt="APIKEY.FUN" width="150"></a></td>
<td>感谢 APIKEY.FUN 赞助本项目!APIKEY.FUN 是一家专业的企业级 AI 中转站,致力于为企业和个人开发者提供稳定、高效、低成本的 AI 模型 API 接入服务。平台支持 Claude、OpenAI、Gemini 等主流热门模型,价格低至官方原价的 7%。通过本项目<a href="https://apikey.fun/register?aff=CCSwitch">专属链接</a>注册,还可享受最高 <strong>充值永久 95 折</strong> 专属优惠。</td>
</tr>
<tr>
<td width="180"><a href="https://console.claudeapi.com/register?aff=pCLD"><img src="assets/partners/logos/claudeapi.png" alt="ClaudeAPI" width="150"></a></td>
<td>本项目由 <a href="https://console.claudeapi.com/register?aff=pCLD">Claude API</a> 赞助。Claude API 直连,三分钟接入 Claude Code 与 Agent 应用 新用户可领取测试额度。基于 Anthropic 官方 Key + AWS Bedrock 官方渠道,非逆向、非降智,支持 Opus / Sonnet / Haiku 全系列模型,保留 Tool Use、1M 上下文等官方能力。适合 Claude Code 深度用户、Agent 工程师与企业技术团队,支持开票和团队对接。点击<a href="https://console.claudeapi.com/register?aff=pCLD">这里</a>注册!</td>
</tr>
<tr>
<td width="180"><a href="https://code0.ai/agent/register/B2XHxGjGmRvqgznY"><img src="assets/partners/logos/code0.png" alt="code0.ai" width="150"></a></td>
<td>感谢 <a href="https://code0.ai/agent/register/B2XHxGjGmRvqgznY">code0.ai</a> 赞助本项目!code0.ai 是专为开发者打造的 AI 编程服务平台,支持 Claude Code、Codex、Gemini 等主流 AI 编程能力,帮助个人开发者和团队更稳定、更高效地使用 AI Agent 完成代码开发、调试与自动化任务。ccswitch 用户可通过 <a href="https://code0.ai/agent/register/B2XHxGjGmRvqgznY">code0.ai 官网</a> 联系客服领取测试额度,体验高效稳定的 AI 编程服务!</td>
</tr>
<tr>
<td width="180"><a href="https://teamorouter.com/zh?utm_source=cc_switch&utm_medium=referral&utm_campaign=ai_directory"><img src="assets/partners/logos/TeamoRouter-banner.png" alt="TeamoRouter" width="150"></a></td>
<td>感谢 TeamoRouter 赞助本项目!TeamoRouter 是一款面向开发者、AI 团队和企业的企业级 Agentic LLM 网关。无需任何订阅,你就可以通过一个统一 API 访问 Claude Code、Codex、Gemini CLI、OpenAI Codex 以及其他热门 AI Agent,同时享受最高可达 90% 折扣的 API 价格。
@@ -69,6 +114,16 @@ Claude Code / Codex / Gemini 官方渠道低至 3.8 / 0.2 / 0.9 折,充值更
TeamoRouter 还提供企业级功能,包括集中账单、团队管理、BYOK、智能路由、用量分析、动态提供商优化和专属支持。为了获得更简单的使用体验,Teamo Desktop 支持你一键使用 Claude Code、Codex、Gemini CLI 和其他热门 AI Agent,无需管理 API Key,也无需手动配置网关。新用户通过<a href="https://teamorouter.com/zh?utm_source=cc_switch&utm_medium=referral&utm_campaign=ai_directory">此链接</a>注册,首次充值可享受 10% 折扣。</td>
</tr>
<tr>
<td width="180"><a href="https://www.newapi.ai/"><img src="assets/partners/logos/newapi-banner.png" alt="new-api" width="150"></a></td>
<td>感谢开源 AI 基础设施项目 <a href="https://www.newapi.ai/">new-api</a> 对本项目的鼎力支持!new-api 是由 QuantumNous(锟腾科技)推出的开源 AI 基础设施项目,也是活跃度与使用规模领先的大模型统一接入与分发项目之一,专注于帮助开发者、团队和企业以更低成本构建可管理、可扩展的 AI 服务平台。作为同样扎根开源生态的项目,new-api 希望通过赞助支持更多优秀开源项目持续发展。🌟 欢迎 Star 支持 new-api<a href="https://github.com/QuantumNous/new-api">https://github.com/QuantumNous/new-api</a>,官网:<a href="https://www.newapi.ai/">https://www.newapi.ai/</a>。</td>
</tr>
<tr>
<td width="180"><a href="https://claudecn.ai/register?aff=HEL9"><img src="assets/partners/logos/claudecn.jpg" alt="ClaudeCN" width="150"></a></td>
<td>感谢 ClaudeCN 赞助本项目!ClaudeCN 由是一家实体企业运营的企业级AI中转平台。平台可提供高可用性的商用API服务,提供Claude、GPT、Deepseek等热门模型,支持企业采购流程,可对公打款、签约,服务合规有保障。点击<a href="https://claudecn.ai/register?aff=HEL9">此链接</a>注册!</td>
</tr>
<tr>
<td width="180"><a href="https://www.volcengine.com/activity/ai618?utm_campaign=hw&utm_content=hw&utm_medium=devrel_tool_web&utm_source=OWO&utm_term=ccswitch"><img src="assets/partners/logos/huoshan.png" alt="HuoShan" width="150"></a></td>
<td>感谢火山方舟 Agent Plan 模型赞助了本项目!方舟 Agent Plan 模型订阅套餐集成了包含 Doubao-Seed、Doubao-Seedance、Doubao-Seedream 等在内的字节跳动自研 SOTA 级模型,覆盖文本、代码、图像、视频等多模态任务。最新支持 MiniMax-M3、DeepSeek-V4 系列、GLM-5.1、Doubao-Seed-2.0 系列、Kimi-K2.6 等模型,工具不限。超全模态模型与 Harness 升级一步到位,深度支持 Agent 框架与 AI 编程工具。一次订阅,可以为不同任务切换合适的 AI 引擎。方舟 Coding Plan 为 CC Switch 的用户提供了专属福利:通过<a href="https://www.volcengine.com/activity/ai618?utm_campaign=hw&utm_content=hw&utm_medium=devrel_tool_web&utm_source=OWO&utm_term=ccswitch">此链接</a>订阅方舟 Coding Plan,新客户首两个月享 2.5 折优惠,再用专属邀请码 6J6FV5N2 领取奖励叠加 9.5 折,低至 9.4 元/月!<a href="https://www.byteplus.com/en/product/modelark?utm_campaign=hw&utm_content=ccswitch&utm_medium=devrel_tool_web&utm_source=OWO&utm_term=ccswitch">>>For developers outside Mainland China, please click here</a></td>
@@ -80,14 +135,13 @@ TeamoRouter 还提供企业级功能,包括集中账单、团队管理、BYOK
</tr>
<tr>
<td width="180"><a href="https://cubence.com/signup?code=CCSWITCH&source=ccs"><img src="assets/partners/logos/cubence.png" alt="Cubence" width="150"></a></td>
<td>感谢 Cubence 赞助本项目!Cubence 是一家可靠高效的 API 中继服务提供商,提供对 Claude Code、Codex、Gemini 等模型的中继服务,并提供按量、包月等灵活的计费方式。Cubence 为 CC Switch 用户提供了特别优惠:使用 <a href="https://cubence.com/signup?code=CCSWITCH&source=ccs">此链接</a> 注册,并在充值时输入 "CCSWITCH" 优惠码,每次充值均可享受九折优惠!</td>
<td width="180"><a href="https://nekocode.ai?aff=CCSWITCH"><img src="assets/partners/logos/nekocode-banner.png" alt="NekoCode" width="150"></a></td>
<td>感谢 <a href="https://nekocode.ai?aff=CCSWITCH">NekoCode</a> 赞助本项目!NekoCode 为开发者提供稳定、高效、可靠的 Claude、Codex 等 AI 模型 API 中转服务,价格透明,接入便捷,支持灵活的按量计费。CC Switch 用户专享 9 折福利:通过 <a href="https://nekocode.ai?aff=CCSWITCH">此链接</a> 注册,并在充值时输入优惠码 <code>cc-switch</code>,即可享受充值 9 折优惠!</td>
</tr>
<tr>
<td width="180"><a href="https://www.dmxapi.cn/register?aff=bUHu"><img src="assets/partners/logos/dmx-zh.jpeg" alt="DMXAPI" width="150"></a></td>
<td>感谢 DMXAPI(大模型API)赞助了本项目! DMXAPI,一个Key用全球大模型。
为200多家企业用户提供全球大模型API服务。· 充值即开票 ·当天开票 ·并发不限制 ·1元起充 · 7x24 在线技术辅导,GPT/Claude/Gemini全部6.8折,国内模型5~8折,Claude Code 专属模型3.4折进行中!<a href="https://www.dmxapi.cn/register?aff=bUHu">点击这里注册</a></td>
<td width="180"><a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch"><img src="assets/partners/logos/atlascloud_banner.png" alt="Atlas Cloud" width="150"></a></td>
<td>Atlas Cloud 是一个全模态 AI 推理平台,通过单一 API 为开发者提供视频生成、图像生成及 LLM 接入。免去繁琐的多供应商对接,一次连接即可调用 300+ 款全模态精选模型。立即查看 Atlas Cloud 全新<a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch">“编程计划”</a>优惠,获取更具性价比的 API 接入!</td>
</tr>
<tr>
@@ -96,13 +150,8 @@ TeamoRouter 还提供企业级功能,包括集中账单、团队管理、BYOK
</tr>
<tr>
<td width="180"><a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch"><img src="assets/partners/logos/crazyrouter.png" alt="Crazyrouter" width="150"></a></td>
<td>感谢 Crazyrouter 赞助本项目!Crazyrouter 是一个高性能 AI API 聚合平台——一个 API Key 即可访问 300+ 模型,包括 Claude Code、Codex、Gemini CLI 等。全部模型低至官方定价的 55%,支持自动故障转移、智能路由和无限并发。Crazyrouter 为 CC Switch 用户提供了专属优惠:通过<a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch">此链接</a>注册后联系客服即可领取 <strong>$2 免费额度</strong>,首次充值时输入优惠码 `CCSWITCH` 还可获得额外 <strong>30% 奖励额度</strong></td>
</tr>
<tr>
<td width="180"><a href="https://www.right.codes/register?aff=CCSWITCH"><img src="assets/partners/logos/rightcode.jpg" alt="RightCode" width="150"></a></td>
<td>感谢 Right Code 赞助了本项目!Right Code 稳定提供 Claude Code、Codex、Gemini 等模型的中转服务,并可选按量、包月两种计费模式。充值即可开票,企业、团队用户一对一对接。同时为 CC Switch 的用户提供了特别优惠:通过<a href="https://www.right.codes/register?aff=CCSWITCH">此链接</a>注册,每次充值均可获得实付金额5%的按量额度!</td>
<td width="180"><a href="https://www.ccsub.net/register?ref=Y6Z8DXEA"><img src="assets/partners/logos/ccsub.svg" alt="CCSub" width="150"></a></td>
<td>感谢 CCSub 赞助本项目!CCSub 是稳定、实惠的 AI API 中转平台,是 Claude Code 官方订阅的超强平替。一个 API Key 即可调用 Claude Opus 4.8、Sonnet 4.6、Haiku 4.5、GPT-5、Gemini、DeepSeek 全系列模型,价格约为官方直连的 1/3,全球直连无需梯子。兼容 Claude Code、Codex、Cursor、Cline、Continue、Windsurf 等所有主流 AI 编程工具。通过<a href="https://www.ccsub.net/register?ref=Y6Z8DXEA">此链接</a>注册即送 $5 体验额度</td>
</tr>
<tr>
@@ -115,69 +164,30 @@ TeamoRouter 还提供企业级功能,包括集中账单、团队管理、BYOK
<td>感谢 米醋API 赞助了本项目!米醋API 是一家致力于提供极致性价比与高稳定性的全球大模型中转服务商。米醋API 背后有实体企业做核心保障,杜绝跑路风险,支持极速正规开票!我们主打“试错零成本”:1 元起充低门槛,0 手续费随时退款!米醋API 为本软件的用户提供了特别优惠,使用<a href="https://www.micuapi.ai/register?aff=aOYQ">此链接</a>注册并在充值时填写"ccswitch"优惠码可享九折优惠!</td>
</tr>
<tr>
<td width="180"><a href="https://www.right.codes/register?aff=CCSWITCH"><img src="assets/partners/logos/rightcode.jpg" alt="RightCode" width="150"></a></td>
<td>感谢 Right Code 赞助了本项目!Right Code 稳定提供 Claude Code、Codex、Gemini 等模型的中转服务,并可选按量、包月两种计费模式。充值即可开票,企业、团队用户一对一对接。同时为 CC Switch 的用户提供了特别优惠:通过<a href="https://www.right.codes/register?aff=CCSWITCH">此链接</a>注册,每次充值均可获得实付金额5%的按量额度!</td>
</tr>
<tr>
<td width="180"><a href="https://etok.ai"><img src="assets/partners/logos/etok.png" alt="ETok" width="150"></a></td>
<td>感谢 ETok.ai 赞助了本项目!ETok.ai 致力于打造一站式 AI 编程工具服务平台。我们提供 Claude Code 专业套餐及技术社群服务,同时支持 Google Gemini 和 OpenAI Codex。通过精心设计的套餐方案和专业的技术社群,为开发者提供稳定的服务保障和持续的技术支持,让 AI 辅助编程真正成为开发者的生产力工具。点击<a href="https://etok.ai">这里</a>注册!</td>
</tr>
<tr>
<td width="180"><a href="https://console.claudeapi.com/register?aff=pCLD"><img src="assets/partners/logos/claudeapi.png" alt="ClaudeAPI" width="150"></a></td>
<td>本项目由 <a href="https://console.claudeapi.com/register?aff=pCLD">Claude API</a> 赞助。Claude API 直连,三分钟接入 Claude Code 与 Agent 应用 新用户可领取测试额度。基于 Anthropic 官方 Key + AWS Bedrock 官方渠道,非逆向、非降智,支持 Opus / Sonnet / Haiku 全系列模型,保留 Tool Use、1M 上下文等官方能力。适合 Claude Code 深度用户、Agent 工程师与企业技术团队,支持开票和团队对接。点击<a href="https://console.claudeapi.com/register?aff=pCLD">这里</a>注册!</td>
<td width="180"><a href="https://cubence.com/signup?code=CCSWITCH&source=ccs"><img src="assets/partners/logos/cubence.png" alt="Cubence" width="150"></a></td>
<td>感谢 Cubence 赞助本项目!Cubence 是一家可靠高效的 API 中继服务提供商,提供对 Claude Code、Codex、Gemini 等模型的中继服务,并提供按量、包月等灵活的计费方式。Cubence 为 CC Switch 的用户提供了特别优惠:使用 <a href="https://cubence.com/signup?code=CCSWITCH&source=ccs">此链接</a> 注册,并在充值时输入 "CCSWITCH" 优惠码,每次充值均可享受九折优惠</td>
</tr>
<tr>
<td width="180"><a href="https://code0.ai?source=ccswitch"><img src="assets/partners/logos/code0.png" alt="code0.ai" width="150"></a></td>
<td>感谢 <a href="https://code0.ai?source=ccswitch">code0.ai</a> 赞助本项目!code0.ai 是专为开发者打造的 AI 编程服务平台,支持 Claude Code、Codex、Gemini 等主流 AI 编程能力,帮助个人开发者和团队更稳定、更高效地使用 AI Agent 完成代码开发、调试与自动化任务。ccswitch 用户可通过 <a href="https://code0.ai?source=ccswitch">code0.ai 官网</a> 联系客服领取测试额度,体验高效稳定的 AI 编程服务</td>
<td width="180"><a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch"><img src="assets/partners/logos/crazyrouter.png" alt="Crazyrouter" width="150"></a></td>
<td>感谢 Crazyrouter 赞助本项目!Crazyrouter 是一个高性能 AI API 聚合平台——一个 API Key 即可访问 300+ 模型,包括 Claude Code、Codex、Gemini CLI 等。全部模型低至官方定价的 55%,支持自动故障转移、智能路由和无限并发。Crazyrouter 为 CC Switch 用户提供了专属优惠:通过<a href="https://crazyrouter.com/register?aff=OZcm&ref=cc-switch">此链接</a>注册后联系客服即可领取 <strong>$2 免费额度</strong>,首次充值时输入优惠码 `CCSWITCH` 还可获得额外 <strong>30% 奖励额度</strong></td>
</tr>
<tr>
<td width="180"><a href="https://claudecn.top"><img src="assets/partners/logos/claudecn.jpg" alt="ClaudeCN" width="150"></a></td>
<td>感谢 ClaudeCN 赞助本项目!ClaudeCN 由是一家实体企业运营的企业级AI中转平台。平台可提供高可用性的商用API服务,提供Claude、GPT、Deepseek等热门模型,支持企业采购流程,可对公打款、签约,服务合规有保障。点击<a href="https://claudecn.top">此链接</a>注册!</td>
</tr>
<tr>
<td width="180"><a href="https://runapi.co"><img src="assets/partners/logos/runapi.jpg" alt="RunAPI" width="150"></a></td>
<td>感谢 RunAPI 赞助本项目!RunAPI 是高效稳定的 AI 模型 API 中转平台,一个 API Key 即可访问 OpenAI、Claude、Gemini、DeepSeek、Grok 等 150+ 主流模型,低至 1 折,极其稳定,可以无缝兼容 Claude Code、OpenClaw 等工具。RunAPI为CC switch的用户提供了特别福利,注册后联系客服可以领取14元额度,点击<a href="https://runapi.co">此链接</a>注册!</td>
</tr>
<tr>
<td width="180"><a href="https://apikey.fun/register?aff=CCSwitch"><img src="assets/partners/logos/apikey_banner.png" alt="APIKEY.FUN" width="150"></a></td>
<td>感谢 APIKEY.FUN 赞助本项目!APIKEY.FUN 是一家专业的企业级 AI 中转站,致力于为企业和个人开发者提供稳定、高效、低成本的 AI 模型 API 接入服务。平台支持 Claude、OpenAI、Gemini 等主流热门模型,价格低至官方原价的 7%。通过本项目<a href="https://apikey.fun/register?aff=CCSwitch">专属链接</a>注册,还可享受最高 <strong>充值永久 95 折</strong> 专属优惠。</td>
</tr>
<tr>
<td width="180"><a href="https://apinebula.com/02rw5X"><img src="assets/partners/logos/apinebula_banner.png" alt="APINebula" width="150"></a></td>
<td>感谢 APINEBULA 赞助本项目!APINEBULA 是银河录像局旗下的企业级 AI 聚合平台,背靠大平台资源,面向开发者、团队与企业用户提供稳定、高性价比的大模型 API 接入服务。平台聚合 Claude、GPT、Gemini 等主流满血模型,一个接口,接入全球顶尖 AI 大模型,各大模型价格低至 1 折起,支持企业级高并发、正式合同、对公打款与开票服务,适合 AI 编程、Agent 开发、业务系统集成等多种场景!使用<a href="https://apinebula.com/02rw5X">此链接</a>注册并在充值时填写 <strong>"ccswitch"</strong> 优惠码可享<strong>九折优惠</strong></td>
</tr>
<tr>
<td width="180"><a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch"><img src="assets/partners/logos/atlascloud_banner.png" alt="Atlas Cloud" width="150"></a></td>
<td>Atlas Cloud 是一个全模态 AI 推理平台,通过单一 API 为开发者提供视频生成、图像生成及 LLM 接入。免去繁琐的多供应商对接,一次连接即可调用 300+ 款全模态精选模型。立即查看 Atlas Cloud 全新<a href="https://www.atlascloud.ai/coding-plan?utm_source=github&utm_campaign=cc-switch">“编程计划”</a>优惠,获取更具性价比的 API 接入!</td>
</tr>
<tr>
<td width="180"><a href="https://www.ccsub.net/register?ref=Y6Z8DXEA"><img src="assets/partners/logos/ccsub.svg" alt="CCSub" width="150"></a></td>
<td>感谢 CCSub 赞助本项目!CCSub 是稳定、实惠的 AI API 中转平台,是 Claude Code 官方订阅的超强平替。一个 API Key 即可调用 Claude Opus 4.8、Sonnet 4.6、Haiku 4.5、GPT-5、Gemini、DeepSeek 全系列模型,价格约为官方直连的 1/3,全球直连无需梯子。兼容 Claude Code、Codex、Cursor、Cline、Continue、Windsurf 等所有主流 AI 编程工具。通过<a href="https://www.ccsub.net/register?ref=Y6Z8DXEA">此链接</a>注册即送 $5 体验额度!</td>
</tr>
<tr>
<td width="180"><a href="https://unity2.ai/register?source=ccs"><img src="assets/partners/logos/unity2.jpg" alt="Unity2.ai" width="150"></a></td>
<td>感谢 Unity2.ai 赞助了本项目!Unity2.ai 是面向个人开发者、团队和企业的高性能 AI 模型 API 中转平台,长期服务国内头部企业,日均承载超 300 亿 token 调用,支持 5000 RPM 级高并发。支持余额计费、首充赠额、组合订阅、企业开票和专属对接。通过<a href="https://unity2.ai/register?source=ccs">此链接</a>注册可领取 $2 余额,加入官方群再送 $10 余额,最高可领 $12 免费额度!</td>
</tr>
<tr>
<td width="180"><a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL"><img src="assets/partners/logos/fenno-banner.png" alt="Fenno.ai" width="150"></a></td>
<td>感谢 Fenno.ai 赞助了本项目!Fenno.ai 是一家稳定、高效的 API 中转服务商,目前主要提供 Codex 中转服务,兼容 OpenAI 及 Anthropic 协议,可灵活接入 Codex、Claude Code、OpenCode 等主流编程工具,可稳定支撑千亿 Token/日的企业级调用需求,支持国内及海外主体公对公结算、开票。Fenno.ai 为 CC Switch 的用户提供了专属福利:通过<a href="https://api.fenno.ai/register?redirect=/purchase?tab=subscription%26group=16&aff=P9MR3D3PLCNL">此链接</a>即可订阅 9.9 元/150 刀额度的超值 Coding Plan,邀请好友最高可享 20% 奖励,多邀多得!</td>
</tr>
<tr>
<td width="180"><a href="https://zetaapi.ai/go/ccs"><img src="assets/partners/logos/zetaapi-banner.png" alt="ZetaAPI" width="150"></a></td>
<td>感谢 ZetaAPI 赞助本项目!ZetaAPI 主打模型不掺水、保真不降智、价格低至官方价 35 折,平台不混量、不暗中替换低质量模型、不做虚假路由,支持 Claude Code、Codex、Gemini、ChatGPT 等主流模型接入,帮助用户在保证模型质量的同时大幅降低 API 使用成本。同时,ZetaAPI 提供企业级 SLA 稳定性保障、标准接口兼容、一个 Key 接入多模型、快速集成、按量计费等能力,适用于 AI 产品、代码生成、企业内部工具、客服系统、内容生产和自动化流程等场景。若经验证发现模型质量与标称不符,ZetaAPI 承诺假一赔十,让用户用得更稳定、更透明、更放心。通过<a href="https://zetaapi.ai/go/ccs">此链接</a>注册,并在首次充值时使用优惠码 CC-SWITCH,即可享受 CC Switch 用户专属的首次充值九折优惠!</td>
</tr>
<tr>
<td width="180"><a href="https://nekocode.ai?aff=CCSWITCH"><img src="assets/partners/logos/nekocode-banner.png" alt="NekoCode" width="150"></a></td>
<td>感谢 <a href="https://nekocode.ai?aff=CCSWITCH">NekoCode</a> 赞助本项目!NekoCode 为开发者提供稳定、高效、可靠的 Claude、Codex 等 AI 模型 API 中转服务,价格透明,接入便捷,支持灵活的按量计费。CC Switch 用户专享 9 折福利:通过 <a href="https://nekocode.ai?aff=CCSWITCH">此链接</a> 注册,并在充值时输入优惠码 <code>cc-switch</code>,即可享受充值 9 折优惠!</td>
<td width="180"><a href="https://www.dmxapi.cn/register?aff=bUHu"><img src="assets/partners/logos/dmx-zh.jpeg" alt="DMXAPI" width="150"></a></td>
<td>感谢 DMXAPI(大模型API赞助本项目! DMXAPI,一个Key用全球大模型。
为200多家企业用户提供全球大模型API服务。· 充值即开票 ·当天开票 ·并发不限制 ·1元起充 · 7x24 在线技术辅导,GPT/Claude/Gemini全部6.8折,国内模型5~8折,Claude Code 专属模型3.4折进行中!<a href="https://www.dmxapi.cn/register?aff=bUHu">点击这里注册</a></td>
</tr>
</table>
@@ -186,13 +196,13 @@ TeamoRouter 还提供企业级功能,包括集中账单、团队管理、BYOK
## 为什么选择 CC Switch
现代 AI 编程依赖于 Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw 和 Hermes 等工具——但每个工具都有自己的配置格式。切换 API 供应商意味着手动编辑 JSON、TOML 或 `.env` 文件,而在多个工具之间缺乏一个统一管理 MCP, SKILLS 的方式。
现代 AI 编程依赖于 Claude Code、Claude Desktop、Codex、Gemini CLI、Grok Build、OpenCode、OpenClaw 和 Hermes 等工具——但每个工具都有自己的配置格式。切换 API 供应商意味着手动编辑 JSON、TOML 或 `.env` 文件,而在多个工具之间缺乏一个统一管理 MCP, SKILLS 的方式。
**CC Switch** 为你提供一个桌面应用来管理所有支持的 AI 工具。无需手动编辑配置文件,你将获得一个可视化界面,一键将供应商导入应用,一键在不同的供应商之间进行切换,内置 50+ 供应商预设、统一的 MCP, SKILLS 管理以及系统托盘即时切换功能——所有操作都基于可靠的 SQLite 数据库和原子写入机制,保护你的配置不被损坏。
- **一个应用,个工具** — 在单一界面中管理 Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw 和 Hermes
- **一个应用,个工具** — 在单一界面中管理 Claude Code、Claude Desktop、Codex、Gemini CLI、Grok Build、OpenCode、OpenClaw 和 Hermes
- **告别手动编辑** — 50+ 供应商预设,包括 AWS Bedrock、NVIDIA NIM 和社区中转服务;一键即可切换
- **统一 MCP, SKILLS 管理** — 一个面板管理 Claude、Codex、Gemini、OpenCode 和 Hermes 的 MCP, SKILLS, 支持双向同步
- **统一 MCP, SKILLS 管理** — 一个面板管理 Claude、Codex、Gemini、Grok Build、OpenCode 和 Hermes 的 MCP, SKILLS, 支持双向同步
- **系统托盘快速切换** — 从托盘菜单即时切换供应商,无需打开完整应用
- **云同步** — 通过 Dropbox、OneDrive、iCloud 或 WebDAV 服务器在不同设备之间同步供应商数据
- **跨平台** — 基于 Tauri 2 构建的原生桌面应用,支持 Windows、macOS 和 Linux
@@ -210,18 +220,18 @@ TeamoRouter 还提供企业级功能,包括集中账单、团队管理、BYOK
### 供应商管理
- **7 个支持工具,50+ 预设** — Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw、Hermes;复制 key 即可一键导入
- **8 个支持工具,50+ 预设** — Claude Code、Claude Desktop、Codex、Gemini CLI、Grok Build、OpenCode、OpenClaw、Hermes;复制 key 即可一键导入
- **通用供应商** — 一份配置同步到 Claude Code、Codex 和 Gemini CLI
- 一键切换、系统托盘快速访问、拖拽排序、导入导出
### 代理与故障转移
- **本地代理热切换** — 格式转换、自动故障转移、熔断器、供应商健康监控和整流器
- **应用级代理接管** — 独立为 Claude、CodexGemini 配置代理,具体到单个供应商
- **应用级代理接管** — 独立为 Claude、CodexGemini 或 Grok Build 配置代理,具体到单个供应商
### MCP、Prompts 与 Skills
- **统一 MCP 面板** — 管理 Claude、Codex、Gemini、OpenCode 和 Hermes 的 MCP 服务器,双向同步,支持 Deep Link 导入
- **统一 MCP 面板** — 管理 Claude、Codex、Gemini、Grok Build、OpenCode 和 Hermes 的 MCP 服务器,双向同步,支持 Deep Link 导入
- **Prompts** — Markdown 编辑器,跨应用同步(CLAUDE.md / AGENTS.md / GEMINI.md),回填保护
- **Skills** — 从 GitHub 仓库或 ZIP 文件一键安装,自定义仓库管理,支持软连接和文件复制
@@ -245,7 +255,7 @@ TeamoRouter 还提供企业级功能,包括集中账单、团队管理、BYOK
<details>
<summary><strong>CC Switch 支持哪些 AI 工具?</strong></summary>
CC Switch 支持个工具:**Claude Code**、**Claude Desktop**、**Codex**、**Gemini CLI**、**OpenCode**、**OpenClaw** 和 **Hermes**。每个工具都有专属的供应商预设和配置管理。
CC Switch 支持个工具:**Claude Code**、**Claude Desktop**、**Codex**、**Gemini CLI**、**Grok Build**、**OpenCode**、**OpenClaw** 和 **Hermes**。每个工具都有专属的供应商预设和配置管理。
</details>
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 KiB

@@ -0,0 +1,141 @@
# Using GPT Models in Claude Code with CC Switch
> Applies to CC Switch 3.17.0 and later. (Both integration methods in this guide existed in earlier versions, but the gpt-5.6 preset and the client-identity fix landed in 3.17.0; on older versions, requesting new models like `gpt-5.6-luna` falsely returns 404.) This guide is compiled from the repository's documentation and code, and all sample data has been de-identified.
## Why local routing is needed
Claude Code targets the Anthropic Messages protocol — that is, `/v1/messages` — whereas the upstreams for Codex-family models, whether the OpenAI Responses API exposed by a third-party gateway or the Codex service behind a ChatGPT subscription, all speak the Responses protocol. The two protocols use completely different request bodies, streaming events, and response structures, so putting such an endpoint directly into Claude Code's config leaves the upstream receiving a `/v1/messages` request it doesn't recognize — which can only fail.
CC Switch's approach is to keep Claude Code always connected to the local route and still sending requests as Anthropic Messages; once the route detects that the active provider is Responses-format, it converts the request into Responses for the upstream, then converts the response back into the Messages shape it returns to Claude Code — tool calls, images, PDFs, and thinking configuration are all within the conversion scope.
This guide covers both integration methods:
- **Method 1 (API Key)**: you have a gateway endpoint and key compatible with the OpenAI Responses API, and you want to run the GPT-family models behind it inside Claude Code.
- **Method 2 (ChatGPT subscription)**: you have a ChatGPT Plus/Pro subscription and use its quota directly by signing in through Codex OAuth — no API key needed at any point.
The chain has four main steps:
1. When Claude Code is taken over, `ANTHROPIC_BASE_URL` in `~/.claude/settings.json` is written as the local route address (default `http://127.0.0.1:15721`), the auth entry keeps only a placeholder, and real credentials never enter the live config.
2. The provider's `API Format` is set to OpenAI Responses, telling the route that the real upstream speaks the Responses protocol.
3. The route converts the `/v1/messages` request into a Responses request body for the upstream; Method 2 additionally carries the OAuth token and the official client identity to reach ChatGPT's Codex service.
4. After the upstream responds, the route converts the Responses JSON/SSE back into the Messages shape Claude Code understands.
![The Needs Routing marker on a GPT provider in the Claude provider list](../images/claude-codex-routing/01-claude-providers-require-routing.png)
## Prerequisites
- CC Switch installed and able to start (3.17.0 or later; see the version note at the top for why).
- Claude Code installed and run at least once.
- For Method 1: a service endpoint compatible with the OpenAI Responses API and its API Key; follow the gateway's documentation for the endpoint and model names. Note it's the **Responses API**, not Chat Completions; a gateway that only offers the Chat format still works — see the `API Format` note in Step 1.
- For Method 2: a ChatGPT Plus/Pro subscription account.
## Step 1: Add a provider
### Method 1: Third-party Responses gateway (API Key)
Open CC Switch, switch to the top-level `Claude Code` tab, click the plus button in the upper-right corner to add a provider, keep the default `Custom Configuration`, then fill in:
- **Provider Name**: anything you like, e.g. `GPT Gateway`.
- **API Key**: your gateway key. The real key is stored only in CC Switch and injected by the local route when forwarding.
- **API Endpoint**: just the gateway's service root, e.g. `https://gpt-gateway.example.com`, without a trailing slash — the route sends requests to the gateway's Responses endpoint (`/v1/responses`) automatically. When the gateway path is unusual, turn on the `Full URL` toggle next to it and paste the complete endpoint verbatim.
Then expand `Advanced Options`:
- **API Format**: change from the default `Anthropic Messages (Native)` to **`OpenAI Responses API (Requires routing)`**. If the gateway only offers the Chat Completions protocol, choose `OpenAI Chat Completions (Requires routing)` here instead; every other step is identical.
- **Auth Field**: keep the default `ANTHROPIC_AUTH_TOKEN (Default)`; the route sends `Authorization: Bearer <key>` to the upstream — exactly the auth header an OpenAI-compatible gateway expects. Unless the gateway's documentation explicitly requires `x-api-key`, don't switch to `ANTHROPIC_API_KEY`; the wrong choice typically shows up as 401/403.
- **Model Mapping**: map Claude Code's model roles to the real models the gateway recognizes. **At minimum, fill in the `Default fallback model`** (e.g. `gpt-5.6`, per the gateway's documentation) — if left empty, unmatched requests pass through to the upstream under the original Claude model name and error out, while roles you haven't configured individually fall back to it. For finer control, specify per row: put your main model in `Sonnet`/`Opus`, and a cheap, fast model in `Haiku` (Claude Code's background sub-tasks use this tier). The `Display name` only affects what shows in the `/model` menu; leave it empty to show the real model name directly.
- **Declare 1M**: the `1M` checkbox on each model-mapping row declares to Claude Code that the tier supports a 1M context. Check it only when the gateway truly serves that model with a window of one million tokens or more (e.g. a gateway offering gpt-5.6 at its API spec); otherwise long conversations will error out at the upstream's real ceiling.
![Method 1: in Advanced Options, set API Format to OpenAI Responses, keep the default auth field, and map to the upstream's real models](../images/claude-codex-routing/02-responses-provider-form.png)
After saving, a `Needs Routing` marker appears on the card — providers like this only work while local routing is running.
### Method 2: ChatGPT subscription (Codex OAuth)
Again on the `Claude Code` tab, click the plus button and pick the **`Codex`** preset with the OpenAI icon from the preset list — it appearing under the Claude Code tab is not a mistake; this preset is built precisely for "using a ChatGPT subscription inside Claude Code":
- **No API Key and no address needed** — requests always go to ChatGPT's Codex service, so the address field in the form needs no changes.
- Click **`Sign in with ChatGPT`**. This is a device-code flow: CC Switch opens the browser automatically and copies the verification code to the clipboard; paste the code on the browser page to complete authorization, while the app shows `Waiting for authorization...`.
- After a successful sign-in, `Auth status` shows the signed-in account (email). Multiple accounts are supported: you can `Add another account`, `Set as default`, or pin a specific account to this provider; day-to-day management can also go through `Settings``OAuth Authentication Center`.
- **FAST mode**: an optional toggle; when on, requests carry `service_tier="priority"` for lower latency but consume ChatGPT quota at a higher rate. Keep it off by default.
- The model tiers are pre-filled: `Sonnet`/`Opus` map to `gpt-5.6`, and `Haiku` maps to `gpt-5.6-luna` (used for background sub-tasks — faster and lighter on quota).
![Method 2: ChatGPT login status and account management in the Codex preset](../images/claude-codex-routing/03-codex-oauth-form.png)
The login credentials are stored in `~/.cc-switch/codex_oauth_auth.json` (not `~/.codex/`), independent of the Codex CLI's own login; the token refreshes automatically before it expires.
## Step 2: Enable local routing and take over Claude Code
Go to the `Routing` page in Settings, expand `Local Routing`, and complete two toggles:
1. Turn on the `Routing Master Switch` to start the local service (the first time you enable it, an explanatory confirmation dialog appears). The default address is `127.0.0.1:15721`.
2. Turn on `Claude Code` under `Routing Enabled`. If you only want Claude Code to use routing, leave the other apps off.
After takeover, CC Switch points Claude Code's live config at the local route, with only a placeholder in the auth entry; both Method 1's gateway key and Method 2's OAuth token are injected by the local route on forward.
> **Note**: the live config is read when the Claude Code process starts. After you first enable takeover (or disable it to restore a direct connection), if Claude Code is already running, open a new terminal session. Afterward, switching providers in routing mode is a hot switch and needs no further restart.
## Step 3: Switch providers and verify
Return to the Claude Code provider list and click `Enable` on the target provider. If routing isn't running, CC Switch shows "This provider uses OpenAI Responses API format, requires the routing service to work properly. Start routing first." — this notice doesn't block the switch, but with routing off the request is bound to fail, so go back to Step 2 and turn it on.
Inside Claude Code you can verify step by step:
- Open a new session and use `/model` to view the model menu: each tier shows the display name from the model mapping (by default, Method 2 shows `gpt-5.6` and `gpt-5.6-luna`). A few spots in the UI may still show Claude-family model names — those are the internal role aliases the routing takeover uses; this is normal, and the `/model` menu and usage dashboard are authoritative.
- Send a small question and watch the `Current Provider` on the Settings → Routing page change to your provider and `Total Requests` start to climb.
- In the usage dashboard, these requests show under the upstream's real models: a tier mapped to `gpt-5.6` resolves to the Sol tier and displays as `GPT-5.6 Sol`, while `gpt-5.6-luna` displays as `GPT-5.6 Luna`; you can filter by provider to reconcile token usage.
- The Method 2 provider card also shows subscription quota: utilization and reset countdowns for the 5-hour and 7-day windows, drawn from the ChatGPT account itself and shared with the official Codex client.
## Capabilities and known limitations
- **Prompt caching is automatic**: the route injects a stable `prompt_cache_key` per session, and together with OpenAI's automatic prefix caching, long conversations don't resend everything at full price each turn — no configuration needed.
- **Thinking is mapped to reasoning effort**: Claude Code's thinking toggle and thinking level are mapped to GPT's `reasoning.effort` (low/medium/high); GPT's reasoning content round-trips across turns intact in encrypted form, so multi-turn reasoning coherence is unaffected by the conversion. Method 2 also accesses in stateless mode (`store:false`), leaving no conversation stored on OpenAI's servers.
- **Tools and multimodal are fully converted**: multi-turn tool calls, image inputs, and PDF inputs are all fully converted.
- **Context is managed against a 200K window**: Claude Code auto-compacts routed providers against a default 200K window. When the upstream's real window is larger (e.g. gpt-5.6 on ChatGPT's Codex service is 372K), anything beyond 200K currently goes unused — compaction triggers early, which is conservative but safe. The only switch to push past 200K today is the `1M` checkbox in the model mapping (a strict 1M declaration), for use only with Method 1 and only when the upstream truly serves the model at 1M or more; Method 2's upstream ceiling is 372K, short of 1M, so checking it would instead make long conversations error out at the upstream's real ceiling — keep it at the default.
- **Output ceiling**: for Method 2, the output ceiling is controlled by the ChatGPT server (the `max_tokens` in Claude Code's request is not sent downstream); for Method 1, Claude Code's `max_tokens` is passed through as-is — no configuration needed.
- **Web search is unavailable**: Claude Code's WebSearch relies on Anthropic's servers to run, which the GPT upstream can't take on, so for tasks involving web search, switch back to a Claude-family provider. Locally executed WebFetch is unaffected.
- **Dashboard dollar amounts are for reference**: token counts are accurate, but the dollar figures are estimates converted at public API prices — Method 2's subscription traffic is estimated at GPT-5.6's public price, and Method 1's third-party gateways bill at their own rates, so both may differ from what you're actually charged and serve only for comparison. For Method 2, treat the window utilization on the provider card as authoritative for quota consumption.
## FAQ
**The upstream returns 401 or 403 (Method 1)**
First confirm the `Auth Field` in Advanced Options is the default `ANTHROPIC_AUTH_TOKEN (Default)` — switching to `ANTHROPIC_API_KEY` sends `x-api-key`, which the vast majority of OpenAI-compatible gateways don't accept. Then confirm the key itself is valid and has balance.
**Requesting new models like `gpt-5.6-luna` returns 404 Model not found (Method 2)**
Upgrade to CC Switch 3.17.0 or later. On older versions the client identity wasn't aligned with the official Codex client, so the ChatGPT server resolves new models to a nonexistent engine.
**The switch didn't take effect, or the `/model` menu still shows old names**
Both the model menu and the route address are read when Claude Code starts: after first enabling takeover you must open a new terminal session; switching between providers is a hot switch, but the display names in the menu only refresh in a new session.
**Claude Code reports "Codex OAuth authentication failed", or the card shows "Session expired" (Method 2)**
The login credentials have expired. Go back to the provider form or `Settings``OAuth Authentication Center` and run through `Sign in with ChatGPT` again; no command-line steps are needed.
**The conversation auto-compacts partway through**
See "Capabilities and known limitations": routed providers are managed against a 200K window, so the compaction threshold comes early — this is expected behavior.
**Restoring the official Claude setup**
Switch back to an official provider, or turn off the `Claude Code` routing toggle on the Routing page — CC Switch restores the pre-takeover live config, and the official login credentials are unaffected throughout. After restoring, you'll again need to open a new terminal session.
**Should you enable FAST mode? (Method 2)**
Leaving it off is fine. Turn it on only if you're especially latency-sensitive and willing to accept faster quota consumption; if the ChatGPT server rejects the parameter, turning the toggle off restores things.
## Compliance note
Method 2 uses ChatGPT subscription quota outside the official Codex client, and this isn't a gray-area hack: Thibault Sottiaux (@thsottiaux), the OpenAI Codex lead, has publicly demonstrated and encouraged pointing Claude Code (the "orange crab", as he jokingly calls it) at GPT-5.6 Sol — using exactly a "local proxy + model alias" approach, the same category as Method 2 here. As the lead of the Codex product line, his active encouragement to use their own model inside a competitor's client shows that running GPT-family models in Claude Code on a subscription is a use the vendor welcomes and encourages people to try.
Two practical reminders are still worth noting: first, this traffic is counted against the same subscription quota as the official Codex client, so heavy use hits the cap sooner; second, CC Switch's authentication center keeps a compliance notice out of caution ("Use your other subscriptions in Claude Code — please be mindful of compliance risks."), and whether this fits the terms that apply to your account is for you to check. When using a third-party gateway in Method 1, separately read the target gateway's terms on billing, compliance, and data retention.
## References
- [CC Switch User Manual: Add a Provider (incl. Codex OAuth reverse proxy and API formats)](../user-manual/en/2-providers/2.1-add.md)
- [CC Switch User Manual: Proxy Service](../user-manual/en/4-proxy/4.1-service.md)
- [CC Switch User Manual: App Routing](../user-manual/en/4-proxy/4.2-routing.md)
- [CC Switch v3.17.0 Release Notes](../release-notes/v3.17.0-en.md)
- Reverse guide: [Using Claude Models in Codex](./codex-claude-routing-guide-en.md)
@@ -0,0 +1,141 @@
# CC Switch を使って Claude Code で GPT モデルを利用する
> 対象バージョン: CC Switch 3.17.0 以降(本記事の 2 つの接続方法自体はより古いバージョンにもありますが、gpt-5.6 プリセットとクライアントアイデンティティの修正は 3.17.0 から導入されたため、古いバージョンで `gpt-5.6-luna` のような新しいモデルをリクエストすると 404 が誤って返ることがあります)。本記事はリポジトリ内のドキュメントとコードをもとに整理し、サンプルデータはすべて匿名化しています。
## ローカルルーティングが必要な理由
Claude Code が前提としているのは Anthropic Messages プロトコル、つまり `/v1/messages` です。一方で Codex 系モデルの上流——サードパーティゲートウェイが公開している OpenAI Responses API であれ、ChatGPT サブスクリプションの背後にある Codex サービスであれ——が話すのはすべて Responses プロトコルです。この 2 つのプロトコルは、リクエストボディ、ストリーミングイベント、レスポンス構造がまったく異なります。この種のアドレスをそのまま Claude Code の設定に入れても、上流が受け取るのは自分の知らない `/v1/messages` リクエストであり、結果は失敗にしかなりません。
CC Switch では、Claude Code が常にローカルルートへ接続し、Anthropic Messages のままリクエストを送るようにします。ルートは現在のプロバイダーが Responses 形式だと判定すると、リクエストを Responses に変換して上流へ送り、最後にレスポンスを Messages 形式へ戻して Claude Code に返します——ツール呼び出し、画像、PDF、思考設定もすべて変換の対象です。
対応する 2 つの接続方法を、本記事はどちらもカバーします:
- **方式 1API Key**: OpenAI Responses API 互換のゲートウェイエンドポイントと Key を持っていて、その背後の GPT 系モデルを Claude Code で動かしたい場合。
- **方式 2(ChatGPT サブスクリプション)**: ChatGPT Plus/Pro のサブスクリプションを持っていて、Codex OAuth ログインでサブスクリプション枠を直接使いたい場合。全工程で API Key は不要です。
この経路は主に 4 つのステップに分かれます:
1. Claude Code を引き継ぐと、`~/.claude/settings.json``ANTHROPIC_BASE_URL` がローカルルートのアドレス(デフォルトは `http://127.0.0.1:15721`)に書き換えられ、認証項目にはプレースホルダーだけが残り、実際の認証情報は live 設定には入りません。
2. プロバイダーの `API フォーマット` を OpenAI Responses に設定し、実際の上流は Responses プロトコルだとルートに伝えます。
3. ルートは `/v1/messages` リクエストを Responses のリクエストボディへ変換して上流へ送ります。方式 2 ではさらに OAuth token と公式クライアントのアイデンティティを付けて ChatGPT の Codex サービスにアクセスします。
4. 上流から返ってきた後、ルートは Responses の JSON/SSE を Claude Code が理解できる Messages 形式へ変換して返します。
![Claude プロバイダー一覧の GPT プロバイダーに付く「ルーティングが必要」マーク](../images/claude-codex-routing/01-claude-providers-require-routing.png)
## 事前準備
- インストール済みで起動できる CC Switch(3.17.0 以降、理由は冒頭のバージョン説明を参照)。
- インストール済みで、少なくとも 1 回は実行したことのある Claude Code。
- 方式 1 に必要なもの: OpenAI Responses API 互換のサービスエンドポイントと対応する API Key。エンドポイントアドレスとモデル名はゲートウェイのドキュメントに従ってください。注意すべきは **Responses API** であって Chat Completions ではないという点です。ゲートウェイが Chat 形式しか提供していない場合でも動作します。Step 1 の「API フォーマット」の説明を参照してください。
- 方式 2 に必要なもの: ChatGPT Plus/Pro のサブスクリプションアカウント。
## Step 1: プロバイダーを追加する
### 方式 1: サードパーティ Responses ゲートウェイ(API Key
CC Switch を開き、上部の `Claude Code` タブへ切り替え、右上のプラスボタンからプロバイダーを追加します。デフォルトの `カスタム設定` のまま、次の項目を入力します:
- **プロバイダー名**: 任意です。たとえば `GPT Gateway`
- **API Key**: あなたのゲートウェイの Key。実際の Key は CC Switch 内にのみ保存され、ローカルルートが転送時に注入します。
- **API エンドポイント**: ゲートウェイのサービスルートアドレスを入力すれば十分です。たとえば `https://gpt-gateway.example.com`。末尾にスラッシュを付けないでください。ルートが自動的にそのゲートウェイの Responses エンドポイント(`/v1/responses`)へリクエストを送ります。ゲートウェイのパスが特殊な場合は、隣の `フル URL` スイッチをオンにして、完全なエンドポイントをそのまま貼り付けてください。
続いて `高級オプション` を展開します:
- **API フォーマット**: デフォルトの `Anthropic Messages(ネイティブ)` から **`OpenAI Responses API(ルーティングが必要)`** に変更します。ゲートウェイが Chat Completions プロトコルしか提供していない場合は、ここで `OpenAI Chat Completions(ルーティングが必要)` を選びます。その他のステップはまったく同じです。
- **認証フィールド**: デフォルトの `ANTHROPIC_AUTH_TOKEN(デフォルト)` のままにします。ルートは `Authorization: Bearer <key>` を上流へ送ります——これはまさに OpenAI 互換ゲートウェイが期待する認証ヘッダーです。ゲートウェイのドキュメントが明確に `x-api-key` を要求している場合を除き、`ANTHROPIC_API_KEY` に変更しないでください。誤って変更した場合の典型的な症状は 401/403 です。
- **モデルマッピング**: Claude Code のモデル役割を、ゲートウェイが認識する実際のモデルへマッピングします。**少なくとも `既定フォールバックモデル` は入力してください**(たとえば `gpt-5.6`。ゲートウェイのドキュメントに従ってください)——空欄のままだと、一致しなかったリクエストは元の Claude モデル名のまま上流へ転送されてエラーになります。個別に設定していない役割はこのフォールバックへ回帰します。より細かく設定するには、行ごとに指定します:`Sonnet`/`Opus` に主力モデル、`Haiku` に安価で高速なモデル(Claude Code のバックグラウンド小タスクがこの区分を使います)。`表示名``/model` メニューでの表示にのみ影響し、空欄なら実際のモデル名がそのまま表示されます。
- **1M 対応を宣言**: モデルマッピングの各行にある `1M` チェックボックスは、その区分が 1M コンテキストに対応していることを Claude Code に宣言します。ゲートウェイが実際に 100 万トークン以上のウィンドウでそのモデルを提供している場合(たとえば API 仕様どおりに gpt-5.6 を提供するゲートウェイ)にのみチェックしてください。そうでないと、長い対話が上流の実際の上限で直接エラーになります。
![方式 1: 高級オプションで API フォーマットを OpenAI Responses に設定し、認証フィールドはデフォルトのまま、上流の実際のモデルへマッピングする](../images/claude-codex-routing/02-responses-provider-form.png)
保存すると、カードに `ルーティングが必要` のマークが表示されます——この種のプロバイダーは、ローカルルーティングが実行中でなければ正しく動作しません。
### 方式 2: ChatGPT サブスクリプション(Codex OAuth
同じく `Claude Code` タブでプラスボタンを押し、プリセット一覧で OpenAI アイコンの付いた **`Codex`** プリセットを選びます——これが Claude Code タブの下に出ていても選択ミスではありません。このプリセットはまさに「Claude Code で ChatGPT サブスクリプションを使う」ために用意されたものです:
- **API Key もアドレスの入力も不要です**——リクエストは常に ChatGPT の Codex サービスへ送られるため、フォームのアドレス項目は変更する必要がありません。
- **`ChatGPT でログイン`** をクリックします。これはデバイスコードフローです:CC Switch が自動的にブラウザを開き、認証コードをクリップボードにコピーします。ブラウザのページで認証コードを貼り付けて認可を完了させてください。その間、アプリ内には `認証を待機中...` と表示されます。
- ログインに成功すると、`認証状態` にログイン済みアカウント(メールアドレス)が表示されます。複数アカウントに対応しています:`別のアカウントを追加``デフォルトに設定`、またはこのプロバイダーで特定のアカウントを指定できます。日常的な管理は `設定``OAuth 認証センター` からも行えます。
- **FAST モード**: 任意のスイッチです。オンにするとリクエストが `service_tier="priority"` を伴い、より低い遅延と引き換えに、より高いレートで ChatGPT のクォータを消費します。デフォルトはオフのままにしてください。
- モデル区分はあらかじめ入力済みです:`Sonnet`/`Opus``gpt-5.6` に、`Haiku``gpt-5.6-luna` に対応します(バックグラウンドの小タスクがこれを使い、より速く、より枠を節約します)。
![方式 2: Codex プリセットの ChatGPT ログイン状態とアカウント管理](../images/claude-codex-routing/03-codex-oauth-form.png)
ログイン認証情報は `~/.cc-switch/codex_oauth_auth.json``~/.codex/` ではありません)に保存され、Codex CLI 自身のログインとは互いに影響しません。token は有効期限前に自動的に更新されます。
## Step 2: ローカルルーティングを有効にして Claude Code をルーティングする
設定の `ルーティング` ページに入り、`ローカルルーティング` を展開して、2 つのスイッチを設定します:
1. `ルーティング総スイッチ` をオンにしてローカルサービスを起動します(初回起動時は説明の確認ダイアログが表示されます)。デフォルトアドレスは `127.0.0.1:15721` です。
2. `ルーティング有効``Claude Code` をオンにします。Claude Code だけをルーティングしたい場合は、その他のアプリはオフのままで構いません。
引き継ぎ後、CC Switch は Claude Code の live 設定をローカルルートへ向け、認証項目にはプレースホルダーだけが入ります。方式 1 のゲートウェイ Key も方式 2 の OAuth token も、いずれもローカルルートが転送時に注入します。
> **注意**: live 設定は Claude Code プロセスの起動時に読み込まれます。初回の引き継ぎを有効にした後(または引き継ぎをオフにして直結に戻した後)、Claude Code が実行中の場合は、ターミナルセッションを開き直してください。その後、ルーティングモードでのプロバイダー切り替えはホットスワップになり、再起動は不要です。
## Step 3: プロバイダーを切り替えて確認する
Claude Code のプロバイダー一覧に戻り、目的のプロバイダーの `有効化` をクリックします。ルーティングが実行されていない場合、CC Switch は「このプロバイダーは OpenAI Responses API フォーマットを使用しており、ルーティングサービスが必要です。先にルーティングを起動してください」と表示します——この表示は切り替えを妨げませんが、ルーティングが未起動のままではリクエストは必ず失敗します。Step 2 に戻ってオンにすれば解決します。
Claude Code に入ったら、段階的に確認できます:
- 新しいセッションを開き、`/model` でモデルメニューを確認します:各区分にはモデルマッピングの表示名が表示されます(方式 2 のデフォルトは `gpt-5.6``gpt-5.6-luna`)。画面の一部の箇所には Claude 系のモデル名が現れることがあります——それはルーティング引き継ぎが使う内部の役割エイリアスであり、正常な現象です。`/model` メニューと使用量ダッシュボードを基準にしてください。
- 小さな質問を 1 つ送り、設定 → ルーティングページの「現在のプロバイダー」があなたのプロバイダーに変わり、「総リクエスト数」が増え始めるのを確認します。
- 使用量ダッシュボードでは、これらのリクエストが上流の実際のモデルで表示されます:`gpt-5.6` にマッピングされた区分は Sol 区分として解析され `GPT-5.6 Sol` と表示され、`gpt-5.6-luna``GPT-5.6 Luna` と表示されます。プロバイダーで絞り込んで token 使用量を照合できます。
- 方式 2 のプロバイダーカードには、さらにサブスクリプション枠が表示されます:5 時間ウィンドウと 7 日ウィンドウの利用率とリセットまでのカウントダウンです。データは ChatGPT アカウント自体から取得され、公式 Codex クライアントと同じ枠を共有します。
## 機能の範囲と既知の制限
- **プロンプトキャッシュが自動で有効**: ルートは各セッションに安定した `prompt_cache_key` を注入し、OpenAI 側の自動プレフィックスキャッシュと組み合わせます。長い対話でも毎回全額で再送されることはなく、設定は不要です。
- **思考を reasoning effort に換算**: Claude Code の思考スイッチと思考レベルは GPT の `reasoning.effort`low/medium/high)へ換算されます。GPT の推論内容は暗号化された形態でターンをまたいで完全に再生され、複数ターンの推論の一貫性は変換の影響を受けません。方式 2 は同時にステルスモード(`store:false`)でアクセスし、OpenAI サーバー側にはセッションを残しません。
- **ツールとマルチモーダルを完全変換**: 複数ターンのツール呼び出し、画像、PDF 入力はすべて完全に変換されます。
- **コンテキストは 200K ウィンドウで管理**: Claude Code はルーティングプロバイダーに対して、デフォルトの 200K ウィンドウで自動圧縮を行います。上流の実際のウィンドウがより大きい場合(たとえば ChatGPT Codex サービスの gpt-5.6 は 372K)でも、200K を超える部分は現状では使われません——圧縮が早めにトリガーされ、保守的ですが安全です。200K を突破したい場合、現在唯一のスイッチはモデルマッピングの `1M` チェックボックス(厳密に 1M を宣言)で、方式 1 に限り、かつ上流が実際に 1M 以上でそのモデルを提供している場合にのみ使用します。方式 2 の上流上限は 372K で 1M には届かないため、チェックするとかえって長い対話が上流の実際の上限でエラーになります。デフォルトのままにしてください。
- **出力上限**: 方式 2 の出力上限は ChatGPT サーバー側が制御します(Claude Code のリクエスト内の `max_tokens` は下流に送られません)。方式 1 は Claude Code の `max_tokens` をそのまま透過するため、設定は不要です。
- **Web 検索は利用不可**: Claude Code の WebSearch は Anthropic サーバー側での実行に依存しており、GPT 上流では引き受けられません。Web 検索を伴うタスクは Claude 系プロバイダーへ切り替えることをおすすめします。ローカルで実行される WebFetch は影響を受けません。
- **使用量ダッシュボードの金額は参考値**: token カウントは正確ですが、ドル金額は公開 API 価格に基づく概算です——方式 2 のサブスクリプショントラフィックは GPT-5.6 の公開価格で換算され、方式 1 のサードパーティゲートウェイはそれぞれのレートで課金されます。どちらも実際の請求額と一致しない可能性があり、比較用途に留めてください。方式 2 の枠の消費は、プロバイダーカード上のウィンドウ利用率を基準にしてください。
## よくある質問
**上流が 401 または 403 を返す(方式 1)**
まず高級オプションの `認証フィールド` がデフォルトの `ANTHROPIC_AUTH_TOKEN(デフォルト)` になっているか確認してください——`ANTHROPIC_API_KEY` に変更すると `x-api-key` で送信され、大多数の OpenAI 互換ゲートウェイはこれを受け付けません。あわせて、Key 自体が有効で残高があることも確認してください。
**`gpt-5.6-luna` などの新しいモデルをリクエストすると 404 Model not found が出る(方式 2**
CC Switch 3.17.0 以降にアップグレードしてください。古いバージョンではクライアントアイデンティティが公式 Codex クライアントに揃っておらず、ChatGPT サーバー側が新しいモデルを存在しないエンジンへ解決してしまいます。
**切り替えても反映されない、または `/model` メニューが古い名前のまま**
モデルメニューもルーティングアドレスも、Claude Code の起動時に読み込まれます:初回の引き継ぎを有効にした後は必ずターミナルセッションを開き直してください。プロバイダー間の切り替えはホットスワップですが、メニューの表示名は新しいセッションでなければ更新されません。
**Claude Code に「Codex OAuth 認証に失敗しました」と出る、またはカードに「セッションが期限切れです」と表示される(方式 2)**
ログイン認証情報が失効しています。プロバイダーフォーム、または `設定``OAuth 認証センター` に戻り、`ChatGPT でログイン` をもう一度実行してください。コマンドライン操作は一切不要です。
**対話の途中で自動的に圧縮されてしまう**
「機能の範囲」を参照してください:ルーティングプロバイダーは 200K ウィンドウで管理され、圧縮のしきい値もそれに合わせて早まります。想定どおりの動作です。
**公式の Claude の使い方に戻したい**
公式プロバイダーに切り替えるか、ルーティングページで `Claude Code` のルーティングスイッチをオフにしてください——CC Switch は引き継ぎ前の live 設定に戻し、公式ログインの認証情報は全工程を通じて影響を受けません。戻した後も同様にターミナルセッションを開き直す必要があります。
**FAST モードをオンにすべきか(方式 2)**
デフォルトのオフのままで構いません。遅延に特に敏感で、かつクォータのより速い消費を受け入れられる場合にのみオンにしてください。ChatGPT サーバー側がこのパラメータを拒否する場合は、スイッチをオフにすれば復旧します。
## コンプライアンスに関する注意
方式 2 は ChatGPT サブスクリプション枠を公式 Codex クライアント以外で使うものですが、これはグレーな手法ではありません:OpenAI Codex の責任者である Thibault Sottiaux@thsottiaux)自身が、Claude Code(彼が「orange crab」と呼ぶもの)を GPT-5.6 Sol に向けて使うことを公開でデモし、推奨しています——用いているのはまさに「ローカルプロキシ + モデルエイリアス」であり、本記事の方式 2 と同じ類の手法です。Codex という製品ラインの責任者が、競合クライアントで自社モデルを使うことを自ら奨励しているのですから、サブスクリプションで Claude Code に GPT 系モデルを動かすことは、公式が歓迎し、試すことを推奨している使い方だと分かります。
とはいえ、実務上の 2 点は依然として留意する価値があります:1 つは、この部分のトラフィックが公式 Codex クライアントと同じサブスクリプション枠に合算されるため、ヘビーユースではより速く上限に達すること。もう 1 つは、CC Switch の認証センターが念のためコンプライアンス上の注意(「Claude Code で他のサブスクリプションをご利用いただけます。コンプライアンスリスクにご注意ください。」)を残していることです。あなたのアカウントに適用される規約に沿うかどうかは、各自でご確認ください。方式 1 でサードパーティゲートウェイを使う場合は、対象ゲートウェイの課金・コンプライアンス・データ保持に関する規約を別途お読みください。
## 参考リンク
- [CC Switch ユーザーマニュアル: プロバイダーを追加(Codex OAuth リバースプロキシと API フォーマットを含む)](../user-manual/ja/2-providers/2.1-add.md)
- [CC Switch ユーザーマニュアル: プロキシサービス](../user-manual/ja/4-proxy/4.1-service.md)
- [CC Switch ユーザーマニュアル: アプリケーションルーティング](../user-manual/ja/4-proxy/4.2-routing.md)
- [CC Switch v3.17.0 リリースノート](../release-notes/v3.17.0-ja.md)
- 逆方向のガイド: [Codex で Claude モデルを使う](./codex-claude-routing-guide-ja.md)
@@ -0,0 +1,141 @@
# 通过 CC Switch 在 Claude Code 中使用 GPT 模型
> 适用版本:CC Switch 3.17.0 及以上(更早版本已具备本文两种接入方式,但 gpt-5.6 预设与客户端身份修复自 3.17.0 落地,低版本请求 `gpt-5.6-luna` 这类新模型会误报 404)。本文根据仓库内文档与代码整理,示例数据均已去敏。
## 为什么需要本地路由
Claude Code 面向的是 Anthropic Messages 协议,也就是 `/v1/messages`;而 Codex 系模型的上游——无论是第三方网关暴露的 OpenAI Responses API,还是 ChatGPT 订阅背后的 Codex 服务——说的都是 Responses 协议。两种协议的请求体、流式事件和返回结构完全不同,把这类地址直接填进 Claude Code 的配置里,上游收到的是它不认识的 `/v1/messages` 请求,结果只能是失败。
CC Switch 的做法是让 Claude Code 始终连本机路由,仍以 Anthropic Messages 发送请求;路由识别当前供应商是 Responses 格式后,把请求转换成 Responses 发给上游,再把响应转换回 Messages 形态返回给 Claude Code——工具调用、图片、PDF、思考配置都在转换范围内。
对应两种接入方式,本文都会覆盖:
- **方式一(API Key**:你手里有某个兼容 OpenAI Responses API 的网关端点和 Key,想在 Claude Code 里跑它背后的 GPT 系模型。
- **方式二(ChatGPT 订阅)**:你有 ChatGPT Plus/Pro 订阅,通过 Codex OAuth 登录直接使用订阅额度,全程不需要 API Key。
这条链路主要分成四步:
1. 接管 Claude Code 后,`~/.claude/settings.json` 里的 `ANTHROPIC_BASE_URL` 会被写成本机路由地址(默认 `http://127.0.0.1:15721`),认证项只留占位符,真实凭据不进 live 配置。
2. 供应商的 `API 格式` 设为 OpenAI Responses,告诉路由:真实上游说的是 Responses 协议。
3. 路由把 `/v1/messages` 请求转换成 Responses 请求体发给上游;方式二还会带上 OAuth token 与官方客户端身份访问 ChatGPT 的 Codex 服务。
4. 上游返回后,路由再把 Responses 的 JSON/SSE 转回 Claude Code 能理解的 Messages 形态。
![Claude 供应商列表里 GPT 供应商的「需要路由」标记](../images/claude-codex-routing/01-claude-providers-require-routing.png)
## 准备工作
- 已安装并能启动的 CC Switch(3.17.0 及以上,原因见开头的版本说明)。
- 已安装 Claude Code,并至少运行过一次。
- 方式一需要:一个兼容 OpenAI Responses API 的服务端点和对应的 API Key,端点地址与模型名以网关文档为准。注意是 **Responses API**,不是 Chat Completions;网关只提供 Chat 格式时也能走通,见第一步「API 格式」处的说明。
- 方式二需要:一个 ChatGPT Plus/Pro 订阅账号。
## 第一步:添加供应商
### 方式一:第三方 Responses 网关(API Key
打开 CC Switch,切到顶部的 `Claude Code` 标签,点击右上角加号添加供应商,保持默认的 `自定义配置`,然后填写:
- **供应商名称**:随意,例如 `GPT Gateway`
- **API Key**:你的网关 Key。真实 Key 只保存在 CC Switch 里,由本地路由转发时注入。
- **请求地址**:填网关服务根地址即可,例如 `https://gpt-gateway.example.com`,不要以斜杠结尾,路由会自动把请求打到该网关的 Responses 端点(`/v1/responses`)。网关路径特殊时,打开旁边的 `完整 URL` 开关原样粘贴完整端点。
然后展开 `高级选项`
- **API 格式**:从默认的 `Anthropic Messages (原生)` 改成 **`OpenAI Responses API (需开启路由)`**。如果网关只提供 Chat Completions 协议,这里改选 `OpenAI Chat Completions (需开启路由)`,其余步骤完全相同。
- **认证字段**:保持默认的 `ANTHROPIC_AUTH_TOKEN(默认)`,路由会以 `Authorization: Bearer <key>` 发给上游——这正是 OpenAI 兼容网关期望的认证头。除非网关文档明确要求 `x-api-key`,否则不要改成 `ANTHROPIC_API_KEY`,改错的典型表现是 401/403。
- **模型映射**:把 Claude Code 的模型角色映射到网关认识的真实模型。**至少要填 `默认兜底模型`**(例如 `gpt-5.6`,以网关文档为准)——留空时未匹配的请求会以原始 Claude 模型名透传给上游而报错,未单独配置的角色则会回落到它。更精细的做法是按行指定:`Sonnet`/`Opus` 填主力模型,`Haiku` 填一个便宜快速的模型(Claude Code 的后台小任务走这一档)。`显示名称` 只影响 `/model` 菜单里的展示,留空则直接显示真实模型名。
- **声明支持 1M**:模型映射每行的 `1M` 复选框会向 Claude Code 声明该档支持 1M 上下文。仅当网关确实以 100 万 token 及以上的窗口服务该模型时才勾选(例如按 API 规格提供 gpt-5.6 的网关),否则长对话会在上游真实上限处直接报错。
![方式一:高级选项里把 API 格式设为 OpenAI Responses,认证字段保持默认,并映射到上游真实模型](../images/claude-codex-routing/02-responses-provider-form.png)
保存后,卡片上会出现 `需要路由` 标记——这类供应商必须在本地路由运行时才能正常工作。
### 方式二:ChatGPT 订阅(Codex OAuth
同样在 `Claude Code` 标签点加号,在预设列表里选择带 OpenAI 图标的 **`Codex`** 预设——它出现在 Claude Code 标签下没有选错,这个预设就是为「在 Claude Code 里用 ChatGPT 订阅」准备的:
- **不需要 API Key,也不需要填地址**——请求固定发往 ChatGPT 的 Codex 服务,表单里的地址项无需改动。
- 点击 **`使用 ChatGPT 登录`**。这是设备码流程:CC Switch 会自动打开浏览器并把验证码复制到剪贴板,在浏览器页面粘贴验证码完成授权即可,期间应用内显示 `等待授权中...`
- 登录成功后,`认证状态` 显示已登录账号(邮箱)。支持多账号:可 `添加其他账号``设为默认`,或在这个供应商上指定使用某个账号;日常管理也可以走 `设置``OAuth 认证中心`
- **FAST 模式**:可选开关,开启后请求携带 `service_tier="priority"` 换取更低延迟,但会按更高速率消耗 ChatGPT 配额,默认保持关闭。
- 模型档位已预填好:`Sonnet`/`Opus` 对应 `gpt-5.6``Haiku` 对应 `gpt-5.6-luna`(后台小任务走它,更快更省额度)。
![方式二:Codex 预设的 ChatGPT 登录状态与账号管理](../images/claude-codex-routing/03-codex-oauth-form.png)
登录凭据保存在 `~/.cc-switch/codex_oauth_auth.json`(不是 `~/.codex/`),与 Codex CLI 自己的登录互不影响;token 会在到期前自动刷新。
## 第二步:开启本地路由并接管 Claude Code
进入设置里的 `路由` 页面,展开 `本地路由`,完成两个开关:
1. 打开 `路由总开关`,启动本地服务(首次开启会弹出说明确认框)。默认地址是 `127.0.0.1:15721`
2.`路由启用` 中打开 `Claude Code`。只想让 Claude Code 走路由的话,其余应用保持关闭。
接管后,CC Switch 会把 Claude Code 的 live 配置指向本机路由,认证项只有占位符;方式一的网关 Key 和方式二的 OAuth token 都由本地路由在转发时注入。
> **注意**live 配置是 Claude Code 进程启动时读取的。首次开启接管(或关闭接管恢复直连)后,如果 Claude Code 正在运行,请重开一个终端会话。之后在路由模式下切换供应商就是热切换,无需再重启。
## 第三步:切换供应商并验证
回到 Claude Code 供应商列表,点击目标供应商的 `启用`。如果路由没有在运行,CC Switch 会提示「此供应商使用 OpenAI Responses 接口格式,需要路由服务才能正常使用,请先启动路由」——这个提示不会拦截切换,但路由未开时请求必然失败,回到第二步打开即可。
进入 Claude Code 后可以逐级验证:
- 新开会话,用 `/model` 查看模型菜单:各档显示的是模型映射里的显示名称(方式二默认即 `gpt-5.6``gpt-5.6-luna`)。界面个别位置仍可能出现 Claude 系的模型字样——那是路由接管使用的内部角色别名,属正常现象,以 `/model` 菜单和用量看板为准。
- 发一个小问题,观察设置 → 路由页面的「当前 Provider」变成你的供应商、「总请求数」开始增长。
- 用量看板里,这些请求会按上游真实模型显示:映射到 `gpt-5.6` 的档位解析为 Sol 档、显示为 `GPT-5.6 Sol``gpt-5.6-luna` 显示为 `GPT-5.6 Luna`,可按供应商筛选核对 token 用量。
- 方式二的供应商卡片还会显示订阅额度:5 小时与 7 天窗口的利用率和重置倒计时,数据来自 ChatGPT 账号本身,与官方 Codex 客户端共用同一份额度。
## 能力边界与已知限制
- **提示缓存自动生效**:路由会为每个会话注入稳定的 `prompt_cache_key`,配合 OpenAI 侧的自动前缀缓存,长对话不会每轮全价重发,无需任何配置。
- **思考折算为 reasoning effort**Claude Code 的思考开关与思考等级会被折算成 GPT 的 `reasoning.effort`low/medium/high);GPT 的推理内容以加密形态跨轮完整回放,多轮推理连贯性不受转换影响。方式二同时以无痕模式(`store:false`)访问,不在 OpenAI 服务端留存会话。
- **工具与多模态完整转换**:多轮工具调用、图片与 PDF 输入都被完整转换。
- **上下文按 200K 窗口管理**Claude Code 对路由供应商按默认 200K 窗口做自动压缩。上游实际窗口更大时(如 ChatGPT Codex 服务的 gpt-5.6 为 372K),超出 200K 的部分当前不会被用到——压缩会提前触发,保守但安全。想突破 200K,目前唯一的开关是模型映射里的 `1M` 复选框(严格声明 1M),仅限方式一且上游真按 1M 及以上服务该模型时使用;方式二的上游上限是 372K、够不到 1M,勾选反而会让长对话在上游真实上限处报错,请维持默认。
- **输出上限**:方式二的输出上限由 ChatGPT 服务端控制(Claude Code 请求里的 `max_tokens` 不会下发);方式一则原样透传 Claude Code 的 `max_tokens`,无需配置。
- **联网搜索不可用**Claude Code 的 WebSearch 依赖 Anthropic 服务端执行,GPT 上游无法承接,涉及联网搜索的任务建议切回 Claude 系供应商。本地执行的 WebFetch 不受影响。
- **用量看板金额是参考值**:token 计数准确,但美元金额是按公开 API 价折算的估算——方式二订阅流量按 GPT-5.6 公开价折算,方式一的第三方网关按各自费率计费,两者都可能与真实扣费不符,仅供对比。方式二的额度消耗以供应商卡片上的窗口利用率为准。
## 常见问题
**上游返回 401 或 403(方式一)**
先确认高级选项里的 `认证字段` 是默认的 `ANTHROPIC_AUTH_TOKEN(默认)`——改成 `ANTHROPIC_API_KEY` 会以 `x-api-key` 发送,绝大多数 OpenAI 兼容网关不接受。再确认 Key 本身有效、有余额。
**请求 `gpt-5.6-luna` 等新模型报 404 Model not found(方式二)**
升级到 CC Switch 3.17.0 及以上。旧版本的客户端身份未对齐官方 Codex 客户端,ChatGPT 服务端会把新模型解析到不存在的引擎。
**切换后没生效,或 `/model` 菜单还是旧名字**
模型菜单和路由地址都是 Claude Code 启动时读取的:首次开启接管后必须重开终端会话;供应商之间切换虽然是热切换,但菜单里的显示名称要新会话才刷新。
**Claude Code 报「Codex OAuth 认证失败」或卡片显示「会话已过期」(方式二)**
登录凭据已失效。回到供应商表单或 `设置``OAuth 认证中心`,重新走一遍 `使用 ChatGPT 登录` 即可;无需运行任何命令行操作。
**对话进行到一半就自动压缩了**
见「能力边界」:路由供应商按 200K 窗口管理,压缩阈值随之提前,属预期行为。
**想恢复官方 Claude 用法**
切回官方供应商,或在路由页面关闭 `Claude Code` 的路由开关——CC Switch 会恢复接管前的 live 配置,官方登录凭据全程不受影响。恢复后同样需要重开终端会话。
**FAST 模式要不要开(方式二)**
默认关闭即可。只有当你对延迟特别敏感、且愿意接受配额更快消耗时再打开;如果 ChatGPT 服务端拒绝该参数,关掉开关即可恢复。
## 合规提示
方式二把 ChatGPT 订阅额度用在官方 Codex 客户端之外,这并不是灰色玩法:OpenAI Codex 负责人 Thibault Sottiaux@thsottiaux)就公开演示并鼓励过把 Claude Code(他戏称的「orange crab」)指向 GPT-5.6 Sol 使用——用的正是「本地代理 + 模型别名」,和本文方式二属于同一类做法。作为 Codex 这条产品线的负责人,他主动鼓励大家在竞品客户端里用自家模型,可见用订阅在 Claude Code 里跑 GPT 系模型,是官方乐见并鼓励尝试的用法。
两点实务提醒仍值得留意:一是这部分流量与官方 Codex 客户端合并计入同一份订阅额度,重度使用会更快触顶;二是 CC Switch 认证中心出于稳妥保留了合规提示(「在 Claude Code 中使用您的其他订阅,请注意合规风险。」),是否符合你账号所适用的条款可自行留意。方式一使用第三方网关时,请另行阅读目标网关关于计费、合规与数据留存的条款。
## 参考链接
- [CC Switch 用户手册:添加供应商(含 Codex OAuth 反向代理与 API 格式)](../user-manual/zh/2-providers/2.1-add.md)
- [CC Switch 用户手册:代理服务](../user-manual/zh/4-proxy/4.1-service.md)
- [CC Switch 用户手册:应用路由](../user-manual/zh/4-proxy/4.2-routing.md)
- [CC Switch v3.17.0 发布说明](../release-notes/v3.17.0-zh.md)
- 反方向攻略:[在 Codex 中使用 Claude 模型](./codex-claude-routing-guide-zh.md)
@@ -0,0 +1,129 @@
# Using Claude Models in Codex with CC Switch
> Applies to CC Switch 3.17.0 and later (the Anthropic Messages upstream was introduced in 3.17.0). This guide is based on the repository documentation and code, and uses a Claude-family relay gateway as the example. Screenshots are generated from the current frontend UI with de-identified sample data to avoid exposing a real API key.
## Why local routing is needed
The newer Codex CLI targets the OpenAI Responses API, while the various Claude-family relay gateways and internal enterprise gateways expose the Anthropic Messages protocol — that is, `/v1/messages`. These two protocols use completely different request bodies, streaming events, and response structures, so putting such a gateway's endpoint directly into Codex configuration can only result in a request to `/responses` coming back 404.
This feature targets the scenario where all you have is a `/v1/messages` endpoint: you have a key for some Claude-family relay gateway and want to run Claude-family models with Codex's interaction style; or your company has banned the Claude Code client for compliance reasons and kept only an approved Claude-family gateway — the model itself is available, and all that's missing is a permitted client, which Codex can now fill.
CC Switch's approach is to keep Codex always talking to the local route and still sending Responses API requests; once the route detects that the active provider is Anthropic-format, it converts the request into Anthropic Messages for the upstream, then converts the response back into the Responses shape it returns to Codex.
![Needs routing marker in the Codex provider list](../images/codex-claude-routing/01-codex-providers-require-routing.png)
The chain has four main steps:
1. When Codex is taken over, the local configuration is written as `http://127.0.0.1:15721/v1`, and `wire_api = "responses"` is forcibly kept in place.
2. The provider's `anthropic` upstream format tells the route that the real upstream speaks the Anthropic Messages protocol.
3. The route rewrites `/responses` to `/v1/messages` and converts the Responses request body into an Anthropic request body.
4. After the upstream responds, the route converts the Anthropic JSON or SSE back into the Responses JSON/SSE that Codex understands — reasoning content, tool calls, and images are all within the conversion scope.
## Prerequisites
Prepare these three things first:
- CC Switch installed and able to start (3.17.0 or later).
- Codex CLI installed and run at least once, so the `~/.codex/` directory structure exists.
- An API key that can reach an Anthropic Messages protocol endpoint (`/v1/messages`) — from some Claude-family relay gateway, or an internal enterprise Claude gateway; follow the gateway's documentation for its endpoint and auth method. Note: some providers restrict their Claude API to Claude Code only, so such a key may error out when used through Codex — if you're unsure, check with your provider first.
The Codex tab currently has no built-in Anthropic preset, so the steps below use the `Custom Configuration` path — just four or five fields from start to finish.
## Step 1: Add a Codex provider
Open CC Switch, switch to the top-level `Codex` tab, click the plus button in the upper-right corner to add a provider, keep the default `Custom Configuration`, then fill in:
- **Provider Name**: anything you like, e.g. `Claude Gateway`.
- **API Key**: your gateway key. The real key is stored only in CC Switch and injected by the local route when forwarding, so it never enters Codex's live config.
- **API Request URL**: just the gateway's service root, e.g. `https://claude-gateway.example.com`. It works with or without a trailing `/v1` — the route sends requests to `/v1/messages` automatically; don't assemble `/v1/messages` yourself (if your gateway documentation gives you a complete messages URL, you can turn on the `Full URL` toggle next to it and paste it verbatim). The yellow hint below the address bar, "compatible with OpenAI Response format", is generic copy written for the direct Responses scenario; when you choose the Anthropic format, just fill it in as this guide describes.
- **Default Model**: enter a Claude model id the gateway recognizes, e.g. `claude-sonnet-5`; follow the model names in the gateway's documentation.
Then expand `Advanced Options` and change `Upstream Format` from the default `Responses (native)` to **`Anthropic Messages (routing required)`**.
![Codex provider form for a Claude gateway](../images/codex-claude-routing/02-claude-codex-provider-form.png)
After selecting Anthropic Messages, three supporting fields appear below:
![Advanced options for the Anthropic upstream](../images/codex-claude-routing/03-anthropic-advanced-options.png)
- **Auth field**: determines which header carries the API key to the upstream; only one of the two is sent — choose per your gateway's documentation.
- `ANTHROPIC_AUTH_TOKEN (Authorization)`: sends `Authorization: Bearer <key>`. This is the default, and most Claude-family relay gateways use it.
- `ANTHROPIC_API_KEY (x-api-key)`: sends `x-api-key: <key>`. Some gateways that follow Anthropic's native header convention require this. Picking the wrong one usually shows up as 401 / 403.
- **Emulate Claude Code client**: off by default. Turn it on only when the gateway or its upstream restricts usage to "Claude Code only"; when enabled, it spoofs the User-Agent, `anthropic-beta`, and `x-app` headers and injects the Claude Code identity as the first line of the system prompt. Ordinary gateways don't need it; if you're still rejected after enabling it, see "FAQ".
- **Max output tokens**: the Anthropic protocol's `max_tokens` is required, and when a Codex request carries no output ceiling the route falls back to a conservative 8192, which may truncate long answers or deep reasoning (showing up as an incomplete reply, `stop_reason=max_tokens`). If you hit truncation, raise this to the model's real ceiling here — but don't exceed it, or the upstream will 400 outright.
The `Model Mapping` in the same area is optional: add model ids like `claude-opus-4-8`, `claude-sonnet-5`, and `claude-haiku-4-5-20251001` (use the names your upstream recognizes) one per row, and CC Switch generates a model catalog so Codex's `/model` menu can list them; you can also leave it empty, in which case Codex just requests the default model.
After you save the provider, a `Needs Routing` marker appears on the card — providers like this only work while local routing is running.
## Step 2: Enable local routing and take over Codex
Go to the `Routing` page in Settings, expand `Local Routing`, and complete two toggles:
1. Turn on the `Routing Master Switch` to start the local service (the first time you enable it, an explanatory confirmation dialog appears). The default address is `127.0.0.1:15721`.
2. Turn on `Codex` under `Routing Enabled`. If you only want Codex to use routing, you can leave Claude and Gemini off.
![Enabling Codex takeover on the local routing page](../images/codex-claude-routing/04-local-route-codex-takeover.png)
After takeover, CC Switch points Codex's live config at the local route (`base_url = http://127.0.0.1:15721/v1`), with only a placeholder in `auth.json`. The real Claude key stays in the CC Switch provider config and is injected by the local route on forward, using the auth field you selected.
## Step 3: Switch providers and restart Codex
Return to the Codex provider list and click `Enable` on the Claude provider. If routing isn't running, CC Switch shows "This provider uses Anthropic Messages API format, requires the routing service to work properly. Start routing first." — just go back to Step 2 and turn it on.
After switching, restart the current Codex terminal session: `config.toml` and the model catalog are read when the Codex process starts, and a running process isn't guaranteed to hot-load them.
Inside Codex you can verify step by step:
- If you configured model mapping, use `/model` to check whether the Claude models now appear in the menu; without a mapping, Codex just uses the default model.
- Send a small question and watch the "Current Provider" on the Settings → Routing page change from "Waiting for first request..." to your Claude provider, with "Total Requests" starting to climb.
- In the usage dashboard, these requests show their model names faithfully as `claude-*`, and you can filter by provider to reconcile token usage.
## Capabilities and known limitations
- **Prompt caching is automatic**: the conversion bridge injects standard 5-minute prompt-cache markers (system prompt, tool definitions, and conversation history) per Anthropic's convention, so long conversations don't resend everything at full price each turn — no configuration needed.
- **Reasoning and tools are lossless**: extended thinking content round-trips across the bridge intact, and multi-turn tool calls, image inputs, and PDF inputs are all fully converted.
- **Supports the `[1m]` long-context marker**: when the default model or a model id in the mapping ends with `[1m]` (e.g. `claude-sonnet-5[1m]`), the route strips the marker and automatically adds the corresponding 1M-context beta header, provided the gateway supports that capability.
- **Web search is unavailable**: in Anthropic upstream mode, Codex's built-in `web_search` is deliberately disabled — the conversion layer can't translate it for the Anthropic endpoint, and disabling it avoids presenting the model with a tool that's guaranteed to fail.
- **Truncation is reported faithfully**: when the upstream stops at the output ceiling or the stream is cut off, Codex sees "incomplete" rather than a disguised success, making it easy to notice and raise the max output tokens.
## FAQ
**The upstream returns 401 or 403**
Nine times out of ten the auth field doesn't match what the gateway wants: switch between `ANTHROPIC_AUTH_TOKEN (Authorization)` and `ANTHROPIC_API_KEY (x-api-key)` per your gateway's documentation and try again (most gateways use the default Bearer). Also confirm the key itself is valid and has balance.
**Codex reports 404 or cannot find `/responses`**
Usually Codex routing takeover isn't enabled, or you manually wrote the gateway's address directly into Codex — an Anthropic-protocol upstream has no `/responses` endpoint, so that always 404s. Check whether the current provider's `base_url` in `~/.codex/config.toml` points to `http://127.0.0.1:15721/v1`.
**The upstream returns 404 (routing already enabled)**
Check the API Request URL: it should be the gateway's service root, not an address carrying another protocol's path such as `/chat/completions`. When the gateway path is unusual, use the `Full URL` toggle to paste the complete messages endpoint directly.
**Replies often get cut off mid-way**
This is the default 8192 output ceiling showing up. Raise it in `Max output tokens` under the provider form's Advanced Options (don't exceed the model's/gateway's real ceiling), save, and retry.
**`/model` doesn't show the Claude models**
Confirm you've added entries to the model mapping, then restart Codex after saving the provider — the model catalog isn't hot-loaded by a running process. When the default model isn't in the mapping, the menu won't list it, but a direct request still works.
**Web search doesn't work**
By design; see "Capabilities and known limitations". For tasks that need web search, switch back to a Responses/Chat-format provider.
**An error says usage is restricted to Claude Code**
Some providers restrict their Claude API to the Claude Code client, so it gets rejected when going through this guide's chain via Codex. Try turning on the `Emulate Claude Code client` toggle in Advanced Options; if it still errors after that, the restriction is enforced on the provider's side — check with your provider whether your key can be used outside Claude Code. Keep this toggle off for ordinary gateways.
## Compliance note
Before using this in the "company bans the client but keeps only the gateway" scenario, it's worth confirming that doing so complies with your organization's specific policy — whether what's banned is a specific client or a manner of use differs from one place to the next. When using a third-party relay gateway, read the target gateway's terms on billing, compliance, and data retention.
## References
- [CC Switch User Manual: Proxy Service](../user-manual/en/4-proxy/4.1-service.md)
- [CC Switch User Manual: App Routing](../user-manual/en/4-proxy/4.2-routing.md)
- [CC Switch v3.17.0 Release Notes](../release-notes/v3.17.0-en.md)
- This feature comes from community contribution [#5071](https://github.com/farion1231/cc-switch/pull/5071); thanks @yeeyzy.
@@ -0,0 +1,129 @@
# CC Switch を使って Codex で Claude モデルを利用する
> 対象バージョン: CC Switch 3.17.0 以降(「Anthropic Messages 上流」は 3.17.0 から導入)。本記事はリポジトリ内のドキュメントとコードをもとに整理し、Claude 系中継ゲートウェイを例に説明します。スクリーンショットは現在のフロントエンド UI から、実際の API Key が漏れないよう匿名化したサンプルデータで生成しています。
## ローカルルーティングが必要な理由
新しい Codex CLI は OpenAI Responses API を前提にしています。一方で各種 Claude 系中継ゲートウェイや企業内部ゲートウェイが公開しているのは Anthropic Messages プロトコル、つまり `/v1/messages` です。この 2 つのプロトコルは、リクエストボディ、ストリーミングイベント、レスポンス構造がまったく異なります。この種のゲートウェイのエンドポイントをそのまま Codex 設定に入れても、`/responses` へのリクエストが 404 になるだけです。
この機能は「手元にあるのが `/v1/messages` エンドポイントだけ」という場面のためのものです。ある Claude 系中継ゲートウェイの Key を持っていて、Codex の操作感で Claude 系モデルを使いたい。あるいは会社がコンプライアンス方針で Claude Code クライアントを禁止し、承認済みの Claude 系ゲートウェイだけを残している——モデル自体は利用できるのに、許可されたクライアントがないだけです。その空白を Codex で埋められます。
CC Switch では、Codex が常にローカルルートへ接続し、Responses API のままリクエストを送るようにします。ルートは現在のプロバイダーが Anthropic 形式だと判定すると、リクエストを Anthropic Messages に変換して上流へ送り、最後にレスポンスを Responses 形式へ戻して Codex に返します。
![Codex プロバイダー一覧の「ルーティングが必要」マーク](../images/codex-claude-routing/01-codex-providers-require-routing.png)
この経路は主に 4 つのステップに分かれます:
1. Codex を引き継ぐと、ローカル設定は `http://127.0.0.1:15721/v1` に書き換えられ、`wire_api = "responses"` が強制的に維持されます。
2. プロバイダーの上流フォーマット `anthropic` が、実際の上流は Anthropic Messages プロトコルだとルートに伝えます。
3. ルートは `/responses``/v1/messages` に書き換え、Responses のリクエストボディを Anthropic のリクエストボディへ変換します。
4. 上流から返ってきた後、ルートは Anthropic の JSON または SSE を Codex が理解できる Responses JSON/SSE へ変換して返します——推論内容、ツール呼び出し、画像もすべて変換対象です。
## 事前準備
先に次の 3 つを用意してください:
- インストール済みで起動できる CC Switch(3.17.0 以降)。
- インストール済みの Codex CLI。少なくとも 1 回は実行し、`~/.codex/` のディレクトリ構造が存在していること。
- Anthropic Messages プロトコルのエンドポイント(`/v1/messages`)へアクセスできる API Key——ある Claude 系中継ゲートウェイ、または企業内部の Claude ゲートウェイのもの。エンドポイントと認証方式はゲートウェイのドキュメントに従ってください。注意:一部のプロバイダーは Claude API を Claude Code 内でのみ利用できるよう制限しており、この種の Key を Codex で使うとエラーになることがあります。判断がつかない場合は、先にプロバイダーへ問い合わせてください。
Codex タブには現時点で Anthropic の内蔵プリセットがないため、以下では「カスタム設定」の手順で進めます。入力する項目は全体でも 4〜5 個です。
## Step 1: Codex プロバイダーを追加する
CC Switch を開き、上部の `Codex` タブへ切り替え、右上のプラスボタンからプロバイダーを追加します。デフォルトの `カスタム設定` のまま、次の項目を入力します:
- **プロバイダー名**: 任意です。たとえば `Claude Gateway`
- **API Key**: あなたのゲートウェイの Key。実際の Key は CC Switch 内にのみ保存され、ローカルルートが転送時に注入するため、Codex の live 設定には入りません。
- **API エンドポイント**: ゲートウェイのルートアドレスを入力すれば十分です。たとえば `https://claude-gateway.example.com``/v1` は付けても付けなくても正しく処理され、ルートが自動的に `/v1/messages` へリクエストを送ります。自分で `/v1/messages` を組み立てないでください(ゲートウェイのドキュメントが完全な messages URL を指定している場合は、隣の `フル URL` スイッチをオンにしてそのまま貼り付けても構いません)。アドレス欄の下に表示される「OpenAI Response 互換」という黄色のヒントは Responses 直結向けの汎用文言です。Anthropic フォーマットを選ぶ場合は本記事のとおりに入力してください。
- **デフォルトモデル**: ゲートウェイが認識する Claude モデル ID を入力します。たとえば `claude-sonnet-5`。ゲートウェイのドキュメントにあるモデル名に従ってください。
続いて `高級オプション` を展開し、`上流フォーマット` をデフォルトの `Responses(ネイティブ)` から **`Anthropic Messages(ルーティング必須)`** に変更します。
![Claude ゲートウェイの Codex プロバイダーフォーム](../images/codex-claude-routing/02-claude-codex-provider-form.png)
Anthropic Messages を選ぶと、下に 3 つの関連フィールドが追加で表示されます:
![Anthropic 上流の高級オプション](../images/codex-claude-routing/03-anthropic-advanced-options.png)
- **認証フィールド**: API Key をどのヘッダーで上流へ送るかを決めます。送信されるのはどちらか一方のみで、ゲートウェイのドキュメントに従って選びます。
- `ANTHROPIC_AUTH_TOKENAuthorization`: `Authorization: Bearer <key>` を送信します。デフォルト値で、多くの Claude 系中継ゲートウェイがこの方式を使います。
- `ANTHROPIC_API_KEYx-api-key`: `x-api-key: <key>` を送信します。Anthropic ネイティブのヘッダー規約を踏襲する一部のゲートウェイはこちらを要求します。選択を誤ると、通常 401 / 403 という形で現れます。
- **Claude Code クライアントを模倣**: デフォルトはオフです。ゲートウェイ(またはその上流)が「Claude Code からのみ利用可能」と制限している場合にのみオンにします。オンにすると User-Agent・`anthropic-beta``x-app` ヘッダーを偽装し、システムプロンプトの先頭行に Claude Code のアイデンティティを注入します。通常のゲートウェイでは不要です。オンにしても拒否される場合の対処は「よくある質問」を参照してください。
- **最大出力トークン**: Anthropic プロトコルの `max_tokens` は必須項目です。Codex のリクエストが出力上限を含まない場合、ルートは保守的に 8192 で補います。長い回答や深い思考では切り詰められることがあります(回答が不完全になる、`stop_reason=max_tokens` になる、といった形で現れます)。切り詰められたら、ここでモデルの実際の上限に合わせて引き上げてください。ただし超えないように——超えると上流が直接 400 を返します。
同じエリアの `モデルマッピング` は任意です。`claude-opus-4-8``claude-sonnet-5``claude-haiku-4-5-20251001` のようなモデル ID(上流が認識する名前に従ってください)を 1 行ずつ追加すると、CC Switch がモデルカタログを生成し、Codex の `/model` メニューに一覧表示できるようになります。入力しなくても利用でき、その場合 Codex はデフォルトモデルを直接リクエストします。
プロバイダーを保存すると、カードに `ルーティングが必要` のマークが表示されます——この種のプロバイダーは、ローカルルーティングが実行中でなければ正しく動作しません。
## Step 2: ローカルルーティングを有効にして Codex をルーティングする
設定の `ルーティング` ページに入り、`ローカルルーティング` を展開して、2 つのスイッチを設定します:
1. `ルーティング総スイッチ` をオンにしてローカルサービスを起動します(初回起動時は説明の確認ダイアログが表示されます)。デフォルトアドレスは `127.0.0.1:15721` です。
2. `ルーティング有効``Codex` をオンにします。Codex だけをルーティングしたい場合は、Claude と Gemini はオフのままで構いません。
![ローカルルーティング画面で Codex のルーティングを有効化](../images/codex-claude-routing/04-local-route-codex-takeover.png)
引き継ぎ後、CC Switch は Codex の live 設定をローカルルートへ向け(`base_url = http://127.0.0.1:15721/v1`)、`auth.json` にはプレースホルダーだけが入ります。実際の Claude Key は CC Switch のプロバイダー設定内に残り、ローカルルートが転送時に、あなたが選んだ認証フィールドに従って注入します。
## Step 3: プロバイダーを切り替えて Codex を再起動する
Codex プロバイダー一覧に戻り、Claude プロバイダーの `有効化` をクリックします。ルーティングが実行されていない場合、CC Switch は「このプロバイダーは Anthropic Messages API フォーマットを使用しており、ルーティングサービスが必要です。先にルーティングを起動してください」と表示します——Step 2 に戻ってオンにすれば解決します。
切り替え後は、現在の Codex ターミナルセッションを再起動することをおすすめします。`config.toml` とモデルカタログは Codex プロセスの起動時に読み込まれるため、実行中のプロセスがホットロードするとは限りません。
Codex に入ったら、段階的に確認できます:
- モデルマッピングを設定している場合は、`/model` で Claude モデルがメニューに表示されているか確認します。マッピングを設定していない場合、Codex はデフォルトモデルを直接使います。
- 小さな質問を 1 つ送り、設定 → ルーティングページの「現在のプロバイダー」が「最初のリクエスト待ち」からあなたの Claude プロバイダーに変わり、「総リクエスト数」が増え始めるのを確認します。
- 使用量ダッシュボードでは、これらのリクエストのモデル名が `claude-*` としてそのまま表示され、プロバイダーで絞り込んで token 使用量を照合できます。
## 機能の範囲と既知の制限
- **プロンプトキャッシュが自動で有効**: 変換ブリッジは Anthropic 標準に従って 5 分のプロンプトキャッシュマーカー(システムプロンプト、ツール定義、対話履歴)を注入します。長い対話でも毎回全額で再送されることはなく、設定は不要です。
- **推論とツールを無損失で往復**: extended thinking の内容はブリッジをまたいで往復しても保持され、複数ターンのツール呼び出し、画像、PDF 入力も完全に変換されます。
- **`[1m]` 長コンテキストマーカーに対応**: デフォルトモデルやモデルマッピングのモデル ID が `[1m]` で終わる場合(例:`claude-sonnet-5[1m]`)、ルートはマーカーを取り除き、対応する 1M コンテキストの beta ヘッダーを自動で補います。ただし、ゲートウェイがその機能に対応していることが前提です。
- **Web 検索は利用不可**: Anthropic 上流モードでは Codex の内蔵 `web_search` が意図的に無効化されます——変換層が Anthropic エンドポイントへ翻訳できないためで、必ず失敗するツールをモデルに提示しないための措置です。
- **切り詰めをそのまま報告**: 上流が出力上限で止まったりストリームが切断されたりすると、Codex は偽装された成功ではなく「未完了」を受け取ります。これにより気づきやすくなり、最大出力トークンを引き上げる判断ができます。
## よくある質問
**上流が 401 または 403 を返す**
ほとんどの場合、認証フィールドがゲートウェイの要求と一致していません。`ANTHROPIC_AUTH_TOKENAuthorization``ANTHROPIC_API_KEYx-api-key` を、ゲートウェイのドキュメントに従って切り替えて再試行してください(多くのゲートウェイはデフォルトの Bearer です)。あわせて、Key 自体が有効で残高があることも確認してください。
**Codex が 404 を返す、または `/responses` が見つからない**
多くの場合、Codex のルーティング引き継ぎが有効になっていないか、ゲートウェイのエンドポイントを手動で Codex に直接書いています——Anthropic プロトコルの上流には `/responses` エンドポイントが存在しないため、必ず 404 になります。`~/.codex/config.toml` の現在の provider の `base_url``http://127.0.0.1:15721/v1` を指しているか確認してください。
**上流が 404 を返す(ルーティングは有効)**
API エンドポイントを確認してください。ゲートウェイのルートアドレスであるべきで、`/chat/completions` のような別プロトコルのパスが付いたアドレスではいけません。ゲートウェイのパスが特殊な場合は、`フル URL` スイッチを使って完全な messages エンドポイントをそのまま貼り付けてください。
**回答が途中で切り詰められることが多い**
これはデフォルトの 8192 出力上限の現れです。プロバイダーフォームの高級オプションにある `最大出力トークン` で引き上げ(モデル / ゲートウェイの実際の上限を超えないように)、保存してから再試行してください。
**`/model` に Claude モデルが表示されない**
モデルマッピングにエントリが追加されていることを確認し、プロバイダーを保存してから Codex を再起動してください——モデルカタログは実行中のプロセスにはホットロードされません。デフォルトモデルがマッピングに含まれていない場合、メニューには表示されませんが、直接リクエストは有効です。
**Web 検索が使えない**
仕様どおりです。「機能の範囲と既知の制限」を参照してください。Web 検索が必要なタスクは、Responses / Chat フォーマットのプロバイダーへ切り替えることをおすすめします。
**Claude Code でしか使えないというエラーが出る**
一部のプロバイダーは、その Claude API を Claude Code クライアント内でのみ利用できるよう制限しており、本ガイドの経路で Codex から使うと拒否されます。高級オプションの `Claude Code クライアントを模倣` スイッチをオンにして試すことはできますが、それでもエラーになる場合、制限はプロバイダーのサーバー側にあります。その Key を Claude Code 以外で使えるかどうか、プロバイダーへ問い合わせて確認してください。通常のゲートウェイでは、このスイッチはオフのままにしてください。
## コンプライアンスに関する注意
「会社がクライアントを禁止し、ゲートウェイだけを残している」という場面で使う前に、この使い方が所属組織の具体的な方針に沿っているかを確認することをおすすめします——禁止されているのが特定のクライアントなのか、それともある種の利用方法なのかは、組織によって解釈が異なります。サードパーティ中継ゲートウェイを使う場合は、対象ゲートウェイの課金・コンプライアンス・データ保持に関する規約を必ずお読みください。
## 参考リンク
- [CC Switch ユーザーマニュアル: プロキシサービス](../user-manual/ja/4-proxy/4.1-service.md)
- [CC Switch ユーザーマニュアル: アプリケーションルーティング](../user-manual/ja/4-proxy/4.2-routing.md)
- [CC Switch v3.17.0 リリースノート](../release-notes/v3.17.0-ja.md)
- この機能はコミュニティからの貢献 [#5071](https://github.com/farion1231/cc-switch/pull/5071) によるものです。@yeeyzy に感謝します。
@@ -0,0 +1,129 @@
# 通过 CC Switch 在 Codex 中使用 Claude 模型
> 适用版本:CC Switch 3.17.0 及以上(「Anthropic Messages 上游」自 3.17.0 引入)。本文根据仓库内文档与代码整理,以 Claude 系中转网关为例演示。截图来自当前前端界面,使用去敏示例数据生成,避免泄露真实 API Key。
## 为什么需要本地路由
新版 Codex CLI 面向的是 OpenAI Responses API,而各类 Claude 系中转网关、企业内部网关暴露的是 Anthropic Messages 协议,也就是 `/v1/messages`。这两种协议的请求体、流式事件和返回结构完全不同,把这类网关的接口地址直接填进 Codex 配置里,结果只能是请求 `/responses` 返回 404。
这个功能面向的就是「手里只有 `/v1/messages` 端点」的场景:你有某个 Claude 系中转网关的 Key,想用 Codex 的交互习惯跑 Claude 系列模型;或者公司出于合规策略禁用了 Claude Code 客户端、只保留了经批准的 Claude 系网关——模型本身可用,缺的只是一个被允许的客户端,现在 Codex 可以补上这个位置。
CC Switch 的做法是让 Codex 始终连本机路由,仍以 Responses API 发送请求;路由识别当前供应商是 Anthropic 格式后,把请求转换成 Anthropic Messages 发给上游,再把响应转换回 Responses 形态返回给 Codex。
![Codex 供应商列表里的需要路由标记](../images/codex-claude-routing/01-codex-providers-require-routing.png)
这条链路主要分成四步:
1. Codex 接管时,本地配置会被写成 `http://127.0.0.1:15721/v1`,并强制保持 `wire_api = "responses"`
2. 供应商的上游格式 `anthropic` 会告诉路由:真实上游说的是 Anthropic Messages 协议。
3. 路由把 `/responses` 改写到 `/v1/messages`,并把 Responses 请求体转换成 Anthropic 请求体。
4. 上游返回后,路由再把 Anthropic 的 JSON 或 SSE 转回 Codex 能理解的 Responses JSON/SSE——推理内容、工具调用、图片都在转换范围内。
## 准备工作
你需要先准备好三样东西:
- 已安装并能启动的 CC Switch3.17.0 及以上)。
- 已安装 Codex CLI,并至少运行过一次,让 `~/.codex/` 目录结构存在。
- 一个能访问 Anthropic Messages 协议端点(`/v1/messages`)的 API Key——来自某个 Claude 系中转网关,或企业内部的 Claude 网关;端点地址和认证方式以网关文档为准。注意:部分供应商会限制其 Claude API 只能在 Claude Code 中使用,这类 Key 走 Codex 可能会报错,拿不准就先咨询供应商。
Codex 页签目前没有 Anthropic 内置预设,下面走「自定义配置」路径,全程也就四五个字段。
## 第一步:添加 Codex 供应商
打开 CC Switch,切到顶部的 `Codex` 标签,点击右上角的加号添加供应商,保持默认的 `自定义配置`,然后填写:
- **供应商名称**:随意,例如 `Claude Gateway`
- **API Key**:你的网关 Key。真实 Key 只保存在 CC Switch 里,由本地路由转发时注入,不会进入 Codex 的 live 配置。
- **API 请求地址**:填网关服务根地址即可,例如 `https://claude-gateway.example.com`。带不带 `/v1` 都能被正确处理,路由会自动把请求打到 `/v1/messages`;不要自己拼 `/v1/messages`(如果网关文档给的就是完整 messages URL,打开旁边的 `完整 URL` 开关原样粘贴也可以)。地址栏下方那句「兼容 OpenAI Response 格式」的黄色提示是为 Responses 直连场景写的通用文案,选 Anthropic 格式时按本文填写即可。
- **默认模型**:填网关认识的 Claude 模型 id,例如 `claude-sonnet-5`,以网关文档给出的模型名为准。
然后展开 `高级选项`,把 `上游格式` 从默认的 `Responses(原生)` 改成 **`Anthropic Messages(需开启路由)`**。
![Claude 网关的 Codex 供应商表单](../images/codex-claude-routing/02-claude-codex-provider-form.png)
选中 Anthropic Messages 后,下方会多出三个配套字段:
![Anthropic 上游的高级选项](../images/codex-claude-routing/03-anthropic-advanced-options.png)
- **认证字段**:决定 API Key 以哪个请求头发给上游,两者只发其一,按网关文档选择。
- `ANTHROPIC_AUTH_TOKENAuthorization`:发 `Authorization: Bearer <key>`,是默认值,多数 Claude 系中转网关用这种。
- `ANTHROPIC_API_KEYx-api-key`:发 `x-api-key: <key>`,部分沿用 Anthropic 原生请求头约定的网关要求这种。选错通常表现为 401 / 403。
- **模拟 Claude Code 客户端**:默认关闭。仅当网关或其上游限制「只能通过 Claude Code 使用」时才打开,开启后会伪装 User-Agent、`anthropic-beta``x-app` 请求头,并在系统提示首行注入 Claude Code 身份。普通网关不需要开;开启后仍被拒的处理见「常见问题」。
- **最大输出 tokens**Anthropic 协议的 `max_tokens` 是必填项,而 Codex 请求未携带输出上限时,路由按保守的 8192 兜底,长回答或深度思考可能被截断(表现为回复不完整、`stop_reason=max_tokens`)。遇到截断就在这里按模型真实上限调高,但不要超过——超了上游会直接 400。
同区的 `模型映射` 是可选项:把 `claude-opus-4-8``claude-sonnet-5``claude-haiku-4-5-20251001` 这类模型 id(以你上游认识的名字为准)逐行加进去,CC Switch 会生成模型目录让 Codex 的 `/model` 菜单能列出它们;不填也能用,Codex 会直接请求默认模型。
保存供应商后,卡片上会出现 `需要路由` 标记——这类供应商必须在本地路由运行时才能正常工作。
## 第二步:开启本地路由并接管 Codex
进入设置里的 `路由` 页面,展开 `本地路由`,完成两个开关:
1. 打开 `路由总开关`,启动本地服务(首次开启会弹出一个说明确认框)。默认地址是 `127.0.0.1:15721`
2.`路由启用` 中打开 `Codex`。如果只想让 Codex 走路由,可以保持 Claude、Gemini 关闭。
![本地路由页面中启用 Codex 接管](../images/codex-claude-routing/04-local-route-codex-takeover.png)
接管后,CC Switch 会把 Codex 的 live 配置指向本机路由(`base_url = http://127.0.0.1:15721/v1`),`auth.json` 里只有占位符。真实 Claude Key 仍保存在 CC Switch 的供应商配置里,由本地路由在转发时按你选的认证字段注入。
## 第三步:切换供应商并重启 Codex
回到 Codex 供应商列表,点击 Claude 供应商的 `启用`。如果路由没有在运行,CC Switch 会提示「此供应商使用 Anthropic Messages 接口格式,需要路由服务才能正常使用,请先启动路由」——回到第二步打开即可。
切换后建议重启当前 Codex 终端会话:`config.toml` 和模型目录是 Codex 进程启动时读取的,运行中的进程不保证热加载。
进入 Codex 后可以逐级验证:
- 配置了模型映射的话,用 `/model` 查看 Claude 模型是否已出现在菜单里;没配映射时 Codex 直接用默认模型。
- 发一个小问题,观察设置 → 路由页面的「当前 Provider」从「等待首次请求」变成你的 Claude 供应商、「总请求数」开始增长。
- 用量看板里,这些请求的模型名会如实显示为 `claude-*`,可按供应商筛选核对 token 用量。
## 能力边界与已知限制
- **提示缓存自动生效**:转换桥会按 Anthropic 标准注入 5 分钟提示缓存标记(系统提示、工具定义与对话历史),长对话不会每轮全价重发,无需任何配置。
- **推理与工具无损**extended thinking 内容跨桥往返保留,多轮工具调用、图片与 PDF 输入都被完整转换。
- **支持 `[1m]` 长上下文标记**:默认模型或模型映射里的模型 id 以 `[1m]` 结尾(如 `claude-sonnet-5[1m]`)时,路由会剥掉标记并自动补发对应的 1M 上下文 beta 头,前提是网关支持该能力。
- **联网搜索不可用**Anthropic 上游模式下 Codex 的内置 `web_search` 会被主动禁用——转换层无法把它翻译给 Anthropic 端点,禁用是为了不给模型呈现一个必然失败的工具。
- **截断如实上报**:上游停在输出上限或流被掐断时,Codex 会看到「未完成」而不是被伪装的成功,方便你察觉并调高最大输出 tokens。
## 常见问题
**上游返回 401 或 403**
十有八九是认证字段与网关要求不符:在 `ANTHROPIC_AUTH_TOKENAuthorization``ANTHROPIC_API_KEYx-api-key` 之间按网关文档换一个再试(多数网关用默认的 Bearer)。另外确认 Key 本身有效、有余额。
**Codex 报 404 或找不到 `/responses`**
通常是没有开启 Codex 路由接管,或者你手动把网关的地址直接写给了 Codex——Anthropic 协议的上游没有 `/responses` 端点,这样一定 404。检查 `~/.codex/config.toml` 里当前 provider 的 `base_url` 是否指向 `http://127.0.0.1:15721/v1`
**上游返回 404(路由已开启)**
检查 API 请求地址:应该是网关的服务根地址,而不是带 `/chat/completions` 之类其它协议路径的地址。网关路径特殊时,用 `完整 URL` 开关直接粘贴完整的 messages 端点。
**回复经常中途截断**
这是默认 8192 输出上限的表现。在供应商表单高级选项的 `最大输出 tokens` 里调高(不要超过模型/网关真实上限),保存后重试。
**`/model` 看不到 Claude 模型**
确认模型映射里已添加条目,保存供应商后重启 Codex——模型目录不会被运行中的进程热加载。默认模型不在映射里时菜单不会列出它,但直接请求仍然有效。
**联网搜索用不了**
设计如此,见「能力边界」。需要联网搜索的任务建议切回 Responses/Chat 格式的供应商。
**报错提示只能在 Claude Code 中使用**
部分供应商会限制其 Claude API 只能在 Claude Code 客户端中使用,经 Codex 走本攻略的链路时会被拒绝。可以尝试打开高级选项里的 `模拟 Claude Code 客户端` 开关;若开启后仍然报错,说明限制在供应商服务端,请咨询供应商确认你的 Key 能否在 Claude Code 之外使用。普通网关请保持该开关关闭。
## 合规提示
在「公司禁客户端、只留网关」的场景下使用前,建议确认这样做符合你所在组织的具体政策——被禁的是特定客户端还是某种使用方式,各家口径不同。使用第三方中转网关时,请阅读目标网关关于计费、合规与数据留存的条款。
## 参考链接
- [CC Switch 用户手册:代理服务](../user-manual/zh/4-proxy/4.1-service.md)
- [CC Switch 用户手册:应用路由](../user-manual/zh/4-proxy/4.2-routing.md)
- [CC Switch v3.17.0 发布说明](../release-notes/v3.17.0-zh.md)
- 功能来自社区贡献 [#5071](https://github.com/farion1231/cc-switch/pull/5071),感谢 @yeeyzy
+112
View File
@@ -0,0 +1,112 @@
# Using Kimi in Codex: CC Switch Local Routing Guide
> Applies to CC Switch 3.16.5 and nearby versions. This guide is based on the repository documentation and code, and uses Kimi as an example of an OpenAI Chat Completions-compatible API. Screenshots are generated from the current frontend UI with de-identified sample data to avoid exposing a real API key or account balance.
## Why local routing is needed
The newer Codex CLI targets the OpenAI Responses API, while both the Kimi Open Platform and Kimi For Coding expose the OpenAI Chat Completions shape, `/chat/completions`. These two protocols use different request bodies, streaming events, and response structures. If you put a Kimi endpoint directly into Codex configuration, the usual result is a 404 on `/responses`, or streaming responses that Codex cannot parse correctly.
The third-party tools officially supported by Kimi For Coding are Anthropic-compatible coding agents such as Claude Code and Roo Code — Codex is not on the list. To use Kimi inside Codex, you need a protocol conversion layer, and that is exactly what CC Switch Local Routing does.
CC Switch solves this by making Codex always talk to a local route and continue sending Responses API requests. The route detects whether the active provider is Chat-format, rewrites the request into Chat Completions for the upstream provider, and finally converts the Chat response back into the Responses shape that Codex understands.
![Needs routing marker in the Codex provider list](../images/codex-kimi-routing/01-codex-providers-require-routing.png)
The chain has four main steps:
1. When Codex routing is enabled, the local configuration is written as `http://127.0.0.1:15721/v1`, while `wire_api = "responses"` is kept in place.
2. The provider's `meta.apiFormat = "openai_chat"` tells the route that the real upstream is Chat Completions.
3. The route rewrites `/responses` or `/v1/responses` to `/chat/completions`, and converts the Responses request body into a Chat request body.
4. After the upstream responds, the route converts the Chat JSON or SSE stream back into Responses JSON/SSE.
## Prerequisites
Prepare these three things first:
- CC Switch installed and able to start.
- Codex CLI installed and run at least once, so the `~/.codex/config.toml` directory structure exists.
- A Kimi API key.
Kimi API keys come from two different places, matching two different built-in presets in CC Switch:
- **Kimi Open Platform** (platform.kimi.com): pay-as-you-go API keys billed by token usage, matching the `Kimi` preset. The OpenAI-compatible base URL is `https://api.moonshot.cn/v1` and the default model is `kimi-k2.7-code`.
- **Kimi For Coding** (kimi.com/code): a dedicated key generated from the Kimi Code membership benefits, matching the `Kimi For Coding` preset. The base URL is `https://api.kimi.com/coding/v1` and the unified model is `kimi-for-coding`.
Both presets already contain the correct endpoint and model details, so prefer the presets and do not manually assemble the endpoint path.
## Step 1: Add a Codex provider
Open CC Switch, switch to the top-level `Codex` tab, and click the plus button in the upper-right corner to add a provider.
Depending on which kind of key you have, choose the built-in `Kimi` preset (Open Platform, pay-as-you-go) or `Kimi For Coding` preset (membership subscription). You only need to do two things:
- Enter the matching Kimi API key.
- Save the provider.
![Upstream format in the Kimi Codex provider form](../images/codex-kimi-routing/02-kimi-codex-routing-form.png)
The preset already includes Kimi's request base URL, default model, model menu, thinking/reasoning parameters, and presets `Upstream Format` under `Advanced Options` to `Chat Completions (routing required)`. You can adjust the default model or model display names if needed — for example, the Open Platform preset defaults to `kimi-k2.7-code`, and you can switch to `kimi-k2.7-code-highspeed` following the official documentation. The protocol conversion is handled by the routing layer.
## Step 2: Enable local routing and route Codex
Go to the `Routing` page in Settings, expand `Local Routing`, and complete two toggles:
1. Turn on the main routing switch to start the local service. The default address is `127.0.0.1:15721`.
2. Turn on `Codex` under `Routing Enabled`. If you only want Codex to use local routing, you can leave Claude and Gemini off.
![Enabling Codex routing on the local routing page](../images/codex-kimi-routing/03-local-route-codex-takeover.png)
After routing is enabled, CC Switch points Codex's live configuration to the local route and manages authentication with a placeholder. The real Kimi key stays in the CC Switch provider configuration and is injected by the local route while forwarding requests, so you do not need to expose the key in Codex's live configuration.
## Step 3: Switch providers and restart Codex
Return to the Codex provider list and click `Enable` on the Kimi provider. If you see the `Needs Routing` marker, that provider must be used while routing is running; when the route is not started, CC Switch shows a prompt saying the routing service is required.
After switching, restart the current Codex terminal session. This is recommended because:
- The Codex process may already have read the old `config.toml`.
- After `model_catalog_json` is generated, the `/model` menu usually needs a fresh process before it refreshes.
Inside Codex, use `/model` to check whether the current model comes from the Kimi preset, such as `Kimi K2.7 Code` or `Kimi For Coding`. The Codex app currently does not support multi-model selection, so it defaults to the first configured model. Then send a small test prompt and confirm that the request count increases in the routing panel, or that a Codex request appears in usage/request logs.
## How to handle other Chat providers
Kimi, DeepSeek, MiniMax, SiliconFlow, and other common Chat-format providers already have presets in CC Switch, so use presets first. Only choose custom configuration for providers that are not covered by presets; in that case, fill in the API key, base URL, and models according to the provider's documentation, and set `Upstream Format` under `Advanced Options` to `Chat Completions (routing required)`.
If the upstream provider directly supports the OpenAI Responses API, set `Upstream Format` to `Responses`; CC Switch then connects through Responses directly without Chat conversion.
## FAQ
**Codex reports 404 or cannot find `/responses`**
Usually Codex routing is not enabled, or the Kimi Chat base URL was written directly into Codex manually — the Kimi upstream has no `/responses` endpoint, so that always 404s. Check whether `~/.codex/config.toml` points to `http://127.0.0.1:15721/v1`.
**Kimi upstream reports 401 or 403**
First confirm the key matches the preset: Open Platform keys only work with the `Kimi` preset, and Kimi Code membership keys only work with the `Kimi For Coding` preset. The two key families are not interchangeable.
**Kimi upstream reports 404**
If you are using a built-in Kimi preset, first confirm that the active provider really comes from the preset and that Codex routing is enabled. Only custom providers require extra base URL checks: the base URL should be the service root, not the full endpoint path with `/chat/completions`.
**`/model` does not show Kimi models**
Restart Codex after saving the provider. CC Switch generates `cc-switch-model-catalog.json` and writes its path to `model_catalog_json`, but a running Codex process may not hot-load the model catalog.
The Codex app currently does not support multi-model selection, so it uses the first configured model by default.
**Routing is enabled, but requests still go to the wrong provider**
Confirm that all three states match: the current provider under the Codex tab is Kimi; the local routing service is running; and the Codex toggle is enabled under `Routing Enabled`.
**Can I use an official OpenAI Codex account through local routing?**
Not recommended. CC Switch blocks switching to official providers while local routing takeover is enabled, because accessing official APIs through a proxy may create account risk. Routing is mainly intended for third-party, aggregator, or protocol-conversion scenarios.
## References
- [CC Switch User Manual: Add Provider](../user-manual/en/2-providers/2.1-add.md)
- [CC Switch User Manual: Proxy Service](../user-manual/en/4-proxy/4.1-service.md)
- [CC Switch User Manual: App Routing](../user-manual/en/4-proxy/4.2-routing.md)
- [Kimi Open Platform: Using Kimi K2.7 Code in coding tools](https://platform.kimi.com/docs/guide/agent-support)
- [Kimi Code Docs: Overview](https://www.kimi.com/code/docs/)
- [Kimi Code Docs: Using with third-party coding agents](https://www.kimi.com/code/docs/third-party-tools/other-coding-agents.html)
+112
View File
@@ -0,0 +1,112 @@
# Codex で Kimi を使う: CC Switch ローカルルーティングガイド
> 対象バージョン: CC Switch 3.16.5 およびその前後のバージョン。本記事はリポジトリ内のドキュメントとコードをもとに整理し、OpenAI Chat Completions 互換 API の例として Kimi を使用します。スクリーンショットは現在のフロントエンド UI から、実際の API Key やアカウント残高が漏れないよう匿名化したサンプルデータで生成しています。
## ローカルルーティングが必要な理由
新しい Codex CLI は OpenAI Responses API を前提にしています。一方で Kimi オープンプラットフォームと Kimi For Coding が実際に公開しているのは、いずれも OpenAI Chat Completions 形式、つまり `/chat/completions` です。この 2 つのプロトコルは、リクエストボディ、ストリーミングイベント、レスポンス構造が異なります。Kimi のエンドポイントをそのまま Codex 設定に入れると、`/responses` へのリクエストが 404 になる、ストリーミングレスポンスを Codex が正しく解析できない、といった問題が起きがちです。
Kimi For Coding が公式にサポートするサードパーティツールは、Claude Code や Roo Code など Anthropic 互換のコーディング Agent であり、Codex はリストに含まれていません。Codex で Kimi を使うにはプロトコル変換レイヤーが必要で、それこそが CC Switch のローカルルーティングの役割です。
CC Switch では、Codex が常にローカルルートへ接続し、Responses API のままリクエストを送るようにします。ルート内部で現在のプロバイダーが Chat 形式かどうかを判定し、必要ならリクエストを Chat Completions に書き換えて上流へ送り、最後に Chat レスポンスを Codex が理解できる Responses 形式へ戻します。
![Codex プロバイダー一覧のローカルルーティング必須マーク](../images/codex-kimi-routing/01-codex-providers-require-routing.png)
この経路は主に 4 つのステップに分かれます:
1. Codex ルーティングを有効にすると、ローカル設定は `http://127.0.0.1:15721/v1` に書き換えられ、`wire_api = "responses"` は維持されます。
2. Provider の `meta.apiFormat = "openai_chat"` が、実際の上流は Chat Completions だとルートに伝えます。
3. ルートは `/responses` または `/v1/responses``/chat/completions` に書き換え、Responses のリクエストボディを Chat のリクエストボディへ変換します。
4. 上流から返ってきた後、ルートは Chat の JSON または SSE ストリームを Responses JSON/SSE へ変換して返します。
## 事前準備
先に次の 3 つを用意してください:
- インストール済みで起動できる CC Switch。
- インストール済みの Codex CLI。少なくとも 1 回は実行し、`~/.codex/config.toml` のディレクトリ構造が存在していること。
- Kimi の API Key。
Kimi の API Key には 2 つの取得元があり、CC Switch の 2 つの内蔵プリセットに対応します:
- **Kimi オープンプラットフォーム**platform.kimi.com: トークン使用量に応じた従量課金の API Key。プリセット `Kimi` に対応し、OpenAI 互換 base URL は `https://api.moonshot.cn/v1`、デフォルトモデルは `kimi-k2.7-code` です。
- **Kimi For Coding**kimi.com/code: Kimi メンバーシップの Kimi Code 特典から生成する専用 Key。プリセット `Kimi For Coding` に対応し、base URL は `https://api.kimi.com/coding/v1`、モデルは `kimi-for-coding` に統一されています。
どちらのプリセットにも公式情報に基づくエンドポイントとモデルがすでに設定されているため、まずはプリセットを使い、エンドポイントパスを手で組み立てる必要はありません。
## Step 1: Codex プロバイダーを追加する
CC Switch を開き、上部の `Codex` タブへ切り替え、右上のプラスボタンからプロバイダーを追加します。
手元の Key の種類に応じて、内蔵プリセットの `Kimi`(オープンプラットフォーム・従量課金)または `Kimi For Coding`(メンバーシップ)を選びます。必要なのは次の 2 つだけです:
- 対応する Kimi API Key を入力する。
- プロバイダーを保存する。
![Kimi Codex プロバイダーフォームの上流フォーマット設定](../images/codex-kimi-routing/02-kimi-codex-routing-form.png)
プリセットには Kimi のリクエスト先、デフォルトモデル、モデルメニュー、thinking/reasoning パラメータがすでに含まれており、`高級オプション``上流フォーマット``Chat Completions(ルーティング必須)` にプリセットされています。必要に応じてデフォルトモデルやモデル表示名を調整できます。たとえばオープンプラットフォームのプリセットはデフォルトが `kimi-k2.7-code` で、公式ドキュメントに従って `kimi-k2.7-code-highspeed` に変更することもできます。プロトコル変換はルーティング層に任せれば十分です。
## Step 2: ローカルルーティングを有効にして Codex をルーティングする
設定の `ルーティング` ページに入り、`ローカルルーティング` を展開して、次の 2 つのスイッチを設定します:
1. `ルーティング総スイッチ` をオンにしてローカルサービスを起動します。デフォルトアドレスは `127.0.0.1:15721` です。
2. `ルーティング有効``Codex` をオンにします。Codex だけをルーティングしたい場合は、Claude と Gemini はオフのままで構いません。
![ローカルルーティング画面で Codex ルーティングを有効化](../images/codex-kimi-routing/03-local-route-codex-takeover.png)
ルーティングを有効にすると、CC Switch は Codex の live 設定をローカルルートへ向け、認証はプレースホルダーで管理します。実際の Kimi Key は CC Switch の Provider 設定内に残り、ローカルルートが転送時に注入します。そのため、Codex の live 設定に Key を露出させる必要はありません。
## Step 3: プロバイダーを切り替えて Codex を再起動する
Codex プロバイダー一覧に戻り、Kimi プロバイダーの `有効化` をクリックします。`ルーティングが必要` の表示が見える場合、そのプロバイダーはルーティング実行中に使う必要があります。ルーティングが起動していない場合、CC Switch は「ルーティングサービスが必要」という趣旨のメッセージを表示します。
切り替え後は、現在の Codex ターミナルセッションを再起動することをおすすめします。理由は次のとおりです:
- Codex プロセスがすでに古い `config.toml` を読み込んでいる可能性があります。
- `model_catalog_json` の生成後、`/model` メニューの更新には通常、新しいプロセスが必要です。
Codex に入ったら、`/model` で現在のモデルが Kimi プリセット由来かどうかを確認します。たとえば `Kimi K2.7 Code``Kimi For Coding` などです。現在の Codex app は複数モデル選択に対応していないため、設定内の最初のモデルをデフォルトで使用します。その後、小さな質問を 1 つ送って、ルーティングパネルのリクエスト数が増えるか、usage / リクエストログに Codex リクエストが出るかを確認します。
## 他の Chat プロバイダーの場合
Kimi、DeepSeek、MiniMax、SiliconFlow など一般的な Chat 形式プロバイダーは CC Switch にプリセットがあるため、まずはプリセットを使ってください。プリセットにないプロバイダーだけ、カスタム設定を選びます。その場合は相手側のドキュメントに従って API Key、base URL、モデルを入力し、`高級オプション``上流フォーマット``Chat Completions(ルーティング必須)` に設定します。
上流が OpenAI Responses API を直接サポートしている場合は、`上流フォーマット``Responses` にすれば、CC Switch は Responses のまま直結でき、Chat 変換は行いません。
## よくある質問
**Codex が 404 を返す、または `/responses` が見つからない**
多くの場合、Codex ルーティングが有効になっていないか、Kimi の Chat base URL を手動で Codex に直接書いています。Kimi の上流には `/responses` エンドポイントが存在しないため、必ず 404 になります。`~/.codex/config.toml``http://127.0.0.1:15721/v1` を指しているか確認してください。
**Kimi 上流が 401 または 403 を返す**
まず Key とプリセットの組み合わせを確認してください。オープンプラットフォームの Key はプリセット `Kimi` 専用、Kimi Code 特典の Key はプリセット `Kimi For Coding` 専用で、2 種類の Key は相互に使えません。
**Kimi 上流が 404 を返す**
内蔵 Kimi プリセットを使っている場合は、まず現在のプロバイダーが本当にプリセット由来であること、そして Codex ルーティングが有効であることを確認してください。カスタムプロバイダーを使っている場合だけ、base URL を追加で確認します。base URL はサービスのルートであり、`/chat/completions` 付きの完全なエンドポイントパスではありません。
**`/model` に Kimi モデルが表示されない**
プロバイダーを保存した後、Codex を再起動してください。CC Switch は `cc-switch-model-catalog.json` を生成し、そのパスを `model_catalog_json` に書き込みますが、実行中の Codex プロセスがモデルカタログをホットロードするとは限りません。
現在の Codex app は複数モデル選択に対応していないため、設定内の最初のモデルをデフォルトで使用します。
**ルーティングを有効にしたのに、リクエストが別のプロバイダーへ行く**
次の 3 つの状態が一致しているか確認してください:Codex タブの現在のプロバイダーが Kimi であること、ローカルルーティングサービスが実行中であること、`ルーティング有効` で Codex スイッチがオンであること。
**公式 OpenAI Codex アカウントをローカルルーティング経由で使えますか**
おすすめしません。CC Switch はローカルルーティング有効中、公式プロバイダーへの切り替えをブロックします。プロキシ経由で公式 API にアクセスすると、アカウントリスクが発生する可能性があるためです。ルーティングは主にサードパーティ、集約サービス、またはプロトコル変換のための機能です。
## 参考リンク
- [CC Switch ユーザーマニュアル: プロバイダーの追加](../user-manual/ja/2-providers/2.1-add.md)
- [CC Switch ユーザーマニュアル: プロキシサービス](../user-manual/ja/4-proxy/4.1-service.md)
- [CC Switch ユーザーマニュアル: アプリケーションルーティング](../user-manual/ja/4-proxy/4.2-routing.md)
- [Kimi オープンプラットフォーム: コーディングツールで Kimi K2.7 Code を使う](https://platform.kimi.com/docs/guide/agent-support)
- [Kimi Code ドキュメント: 概要](https://www.kimi.com/code/docs/)
- [Kimi Code ドキュメント: サードパーティ Coding Agent での利用](https://www.kimi.com/code/docs/third-party-tools/other-coding-agents.html)
+112
View File
@@ -0,0 +1,112 @@
# 在 Codex 中用 KimiCC Switch 本地路由攻略
> 适用版本:CC Switch 3.16.5 及附近版本。本文根据仓库内文档与代码整理,并用 Kimi 作为 OpenAI Chat Completions 兼容接口的示例。截图来自当前前端界面,使用去敏示例数据生成,避免泄露真实 API Key 或账户余额。
## 为什么需要本地路由
新版 Codex CLI 面向的是 OpenAI Responses API,而 Kimi 开放平台和 Kimi For Coding 实际暴露的都是 OpenAI Chat Completions 形态,也就是 `/chat/completions`。这两种协议的请求体、流式事件和返回结构不同,直接把 Kimi 的接口地址填进 Codex 配置里,常见结果就是请求 `/responses` 返回 404,或者流式响应无法被 Codex 正确解析。
Kimi For Coding 官方目前支持的第三方工具是 Claude Code、Roo Code 这类兼容 Anthropic 协议的编程 Agent,并没有覆盖 Codex。所以想在 Codex 里用 Kimi,需要一层协议转换——这正是 CC Switch 本地路由做的事。
CC Switch 的做法是让 Codex 始终连本机路由,仍以 Responses API 发送请求;路由在内部识别当前供应商是否是 Chat 格式,再把请求改写成 Chat Completions 发给上游,最后把 Chat 响应转换回 Responses 形态返回给 Codex。
![Codex 供应商列表里的需要路由标记](../images/codex-kimi-routing/01-codex-providers-require-routing.png)
这条链路主要分成四步:
1. Codex 接管时,本地配置会被写成 `http://127.0.0.1:15721/v1`,并强制保持 `wire_api = "responses"`
2. Provider 的 `meta.apiFormat = "openai_chat"` 会告诉路由:真实上游是 Chat Completions。
3. 路由把 `/responses``/v1/responses` 改写到 `/chat/completions`,并把 Responses 请求体转换成 Chat 请求体。
4. 上游返回后,路由再把 Chat 的 JSON 或 SSE 转回 Codex 能理解的 Responses JSON/SSE。
## 准备工作
你需要先准备好三样东西:
- 已安装并能启动的 CC Switch。
- 已安装 Codex CLI,并至少运行过一次,让 `~/.codex/config.toml` 目录结构存在。
- 一个 Kimi API Key。
Kimi 的 API Key 有两个来源,对应 CC Switch 里两个不同的内置预设:
- **Kimi 开放平台**platform.kimi.com):按 token 用量计费的 API Key,对应预设 `Kimi`OpenAI 兼容 base URL 是 `https://api.moonshot.cn/v1`,默认模型 `kimi-k2.7-code`
- **Kimi For Coding**kimi.com/code):Kimi 会员 Kimi Code 权益生成的专用 Key,对应预设 `Kimi For Coding`base URL 是 `https://api.kimi.com/coding/v1`,模型统一为 `kimi-for-coding`
两个预设都已经按官方信息配好接口地址和模型,请优先使用预设,不需要手动拼接口路径。
## 第一步:添加 Codex 供应商
打开 CC Switch,切到顶部的 `Codex` 标签,点击右上角的加号添加供应商。
按你手里 Key 的类型,在内置预设里选择 `Kimi`(开放平台按量计费)或 `Kimi For Coding`(会员订阅),然后只需要做两件事:
- 填入对应的 Kimi API Key。
- 保存供应商。
![Kimi Codex 供应商表单中的上游格式设置](../images/codex-kimi-routing/02-kimi-codex-routing-form.png)
预设已经内置 Kimi 的请求地址、默认模型、模型菜单、thinking/reasoning 参数,并把 `高级选项` 里的 `上游格式` 预设为 `Chat Completions(需开启路由)`。你可以按需调整默认模型或模型显示名——例如开放平台预设默认是 `kimi-k2.7-code`,也可以按官方文档换成 `kimi-k2.7-code-highspeed`;协议转换交给路由层完成即可。
## 第二步:开启本地路由并接管 Codex
进入设置里的 `路由` 页面,展开 `本地路由`,完成两个开关:
1. 打开 `路由总开关`,启动本地服务。默认地址是 `127.0.0.1:15721`
2.`路由启用` 中打开 `Codex`。如果只想让 Codex 走路由,可以保持 Claude、Gemini 关闭。
![本地路由页面中启用 Codex 接管](../images/codex-kimi-routing/03-local-route-codex-takeover.png)
接管后,CC Switch 会把 Codex 的 live 配置指向本机路由,并用占位符管理认证。真实 Kimi Key 仍保存在 CC Switch 的 Provider 配置里,由本地路由在转发时注入,不需要你把 Key 暴露给 Codex live 配置。
## 第三步:切换供应商并重启 Codex
回到 Codex 供应商列表,点击 Kimi 供应商的 `启用`。如果看到 `需要路由` 标记,说明这个供应商必须在路由运行时使用;没有启动路由时,CC Switch 会弹出“需要路由服务才能正常使用”的提示。
切换后建议重启当前 Codex 终端会话。原因是:
- Codex 进程可能已经读取过旧的 `config.toml`
- `model_catalog_json` 生成后,`/model` 菜单通常需要新进程才能刷新。
进入 Codex 后,可以用 `/model` 查看当前模型是否来自 Kimi 预设,例如 `Kimi K2.7 Code``Kimi For Coding`。目前 Codex app 不支持多模型选择时,会默认使用配置里的第一个模型。随后发一个小问题,确认路由面板的请求数增长,或者在用量/请求日志里看到 Codex 请求即可。
## 其它 Chat 供应商怎么处理
Kimi、DeepSeek、MiniMax、SiliconFlow 等常见 Chat 格式供应商在 CC Switch 里已有预设,优先用预设即可。只有预设里没有的供应商,才需要选择自定义配置;这时按对方文档填 API Key、base URL 和模型,并把 `高级选项` 里的 `上游格式` 选为 `Chat Completions(需开启路由)`
如果上游直接支持 OpenAI Responses API,把 `上游格式` 选为 `Responses` 即可;这时 CC Switch 按 Responses 直连,不做 Chat 转换。
## 常见问题
**Codex 报 404 或找不到 `/responses`**
通常是没有开启 Codex 接管,或者你手动把 Kimi 的 Chat base URL 直接写给了 Codex——Kimi 上游没有 `/responses` 端点,这样一定会 404。检查 `~/.codex/config.toml` 是否指向 `http://127.0.0.1:15721/v1`
**Kimi 上游报 401 或 403**
先确认 Key 和预设是否匹配:开放平台的 Key 只能配 `Kimi` 预设,Kimi Code 会员权益的 Key 只能配 `Kimi For Coding` 预设,两套 Key 不能混用。
**Kimi 上游报 404**
如果用的是内置 Kimi 预设,先确认当前供应商确实来自预设,并且 Codex 路由已启用。只有在使用自定义供应商时,才需要额外检查 base URL:它应该是服务根地址,而不是带 `/chat/completions` 的完整接口路径。
**`/model` 看不到 Kimi 模型**
保存供应商后重启 Codex。CC Switch 会生成 `cc-switch-model-catalog.json` 并把路径写入 `model_catalog_json`,但正在运行的 Codex 进程不一定会热加载模型目录。
目前 Codex app 不支持多模型选择,默认使用配置的第一个模型。
**开了路由但请求仍走错供应商**
确认三处状态一致:Codex 标签下当前供应商是 Kimi;本地路由服务正在运行;`路由启用` 里 Codex 开关已打开。
**可以用官方 OpenAI Codex 账号走本地路由吗**
不建议。CC Switch 会在本地路由接管模式下阻止切到官方供应商,因为用代理访问官方 API 可能带来账号风险。路由主要用于第三方、聚合或协议转换场景。
## 参考链接
- [CC Switch 用户手册:添加供应商](../user-manual/zh/2-providers/2.1-add.md)
- [CC Switch 用户手册:代理服务](../user-manual/zh/4-proxy/4.1-service.md)
- [CC Switch 用户手册:应用路由](../user-manual/zh/4-proxy/4.2-routing.md)
- [Kimi 开放平台:在编程工具中使用 Kimi K2.7 Code 模型](https://platform.kimi.com/docs/guide/agent-support)
- [Kimi Code 文档:概览](https://www.kimi.com/code/docs/)
- [Kimi Code 文档:在第三方 Coding Agent 中使用](https://www.kimi.com/code/docs/third-party-tools/other-coding-agents.html)
Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

+366
View File
@@ -0,0 +1,366 @@
# CC Switch v3.17.0
> This release brings a long-awaited capability: **one-click "Projects" switching** — save your current provider, MCP, Skills, and memory files as a single named snapshot, swap the whole set for another one from the title bar or tray with a single click, and have the state of the project you're leaving automatically saved back on the way out. The Codex side gets plenty too: **your official ChatGPT subscription account can now route through the local proxy as well**, getting the same routing and usage statistics as third-party providers; the GPT-5.6 family's context window and Sol / Terra / Luna three-tier pricing land in one step; and a native Anthropic Messages upstream format is added — is Claude Code banned at your company but the Claude API isn't? You can now **use Claude-family models directly inside Codex**. On top of that comes a big wave of correctness fixes: upstream failures no longer turn into "empty replies", cache-write tokens are no longer double-billed, deleted MCP servers no longer come back from the dead, and Kimi For Coding's 256K window finally takes effect for real.
**[中文版 →](v3.17.0-zh.md) | [日本語版 →](v3.17.0-ja.md)**
---
## Usage Guides
The new capabilities in this release land mainly in the project switcher at the top of the home page, the Codex provider form, and the usage dashboard. The following docs are worth reading alongside it:
- **[Using Claude in Codex (local routing guide)](../guides/codex-claude-routing-guide-en.md)**: a new step-by-step guide for this release's native Anthropic Messages upstream. It walks through setting a Codex provider's upstream format to `anthropic` to connect to any Claude-family gateway that only offers `/v1/messages`, and use Claude-family models inside Codex.
- **[Using Kimi inside Codex (local routing guide)](../guides/codex-kimi-routing-guide-en.md)**: a new step-by-step guide added in this release. Newer Codex CLI speaks the OpenAI Responses protocol, while the Kimi Open Platform and Kimi For Coding expose Chat Completions endpoints, so a direct connection usually 404s; the guide walks through using the built-in `Kimi` / `Kimi For Coding` presets together with local routing to handle the protocol conversion.
- **[Codex Official Login Preservation](../guides/codex-official-auth-preservation-guide-en.md)**: understand how CC Switch preserves your official ChatGPT login when you switch to a third-party provider. This release goes a step further — the official account itself can now route through the proxy too (see "Added" below).
- **[Usage Statistics](../user-manual/en/4-proxy/4.4-usage.md)**: understand the Usage Dashboard's data sources and how the statistics are counted. This release fixes cache-write billing, fills in Codex subagent session accounting, and adds GPT-5.6 and Hunyuan Hy3 pricing.
---
> [!WARNING]
>
> ## Only Official Channels (Please Read)
>
> CC Switch is a **fully free and open-source** desktop app, and we **do not charge users any fees**. Please only obtain the software through the official channels listed below:
>
> | Channel | Only Official |
> | ------------------ | ------------------------------------------------------------------------------ |
> | Website | **[ccswitch.io](https://ccswitch.io)** |
> | Source | **[github.com/farion1231/cc-switch](https://github.com/farion1231/cc-switch)** |
> | Downloads | **[GitHub Releases](https://github.com/farion1231/cc-switch/releases)** |
> | Author | **[@farion1231](https://github.com/farion1231)** |
> | Report an Imposter | **[GitHub Issues](https://github.com/farion1231/cc-switch/issues)** |
>
> **Any "CC Switch" website or client that asks you for payment, top-ups, or login credentials is fake.** If you have been tricked into paying, stop the transaction immediately and file a report through GitHub Issues.
---
## Overview
CC Switch v3.17.0 is a major feature release following v3.16.5, centered on **Projects**: you can save the current provider, MCP, Skills, and memory-file state of Claude Code / Claude Desktop / Codex as a named snapshot — say a "Development" set for a coding directory and a "Drawing" set for a writing-and-drawing directory — and swap the whole set with one click from the switcher at the top of the home page or the "Projects" tray submenu. Before you switch, the state of the project you're about to leave is automatically saved back, so a project always holds exactly what you last left it as. The second main thread is Codex: **your official ChatGPT subscription account can now be taken over by the local proxy route** (no API key needed — Codex's own login credentials are passed through verbatim and your official login is never overwritten); paired with a corrected client identity, the latest subscription models like `gpt-5.6-luna` no longer falsely 404; GPT-5.6's 372K context-window injection, Sol / Terra / Luna three-tier pricing (including the 1.25× cache-write rate), and preset default models all land together; and the Codex upstream format gains a native Anthropic Messages protocol — which targets a very real scenario: plenty of companies ban the Claude Code client but do **not** ban the Claude API, and those users can now point Codex directly at the Claude API (or any gateway that only offers `/v1/messages`) and keep using Claude-family models inside Codex.
For everyday-use correctness, this release makes three concentrated waves of fixes. **Proxy bridge**: semantic failures returned inside a 2xx are no longer turned into an empty reply but trigger failover instead; reasoning content, tool results, and the system role round-trip losslessly across the Responses↔Anthropic bridge; prompt-cache breakpoint injection is more thorough, so long conversations no longer resend everything at full price each turn. **Usage billing**: cache-write tokens were previously billed twice — once at the input rate and once at the cache-creation rate — and are now corrected (the database is upgraded to schema v13 so historical data stays consistent); usage and quota queries automatically retry on transient network failures and no longer cache a failure body as real data. **Codex `config.toml`**: MCP servers you deleted in the app no longer come back on provider switch; when parsing a live file fails, sync errors out rather than blanking the whole file; and the "Apply Common Config" merge is moved to the backend so comments and key order are no longer scrambled. Also included: Kimi For Coding's 256K window finally taking effect, Codex subagent and free-tier quota accounting filled in, Zhipu team-plan quota queries, OpenCode form enhancements, and a batch of preset updates.
**Release date**: 2026-07-13
**Stats**: 69 commits | 172 files changed | +21,067 / -2,464 lines
---
## Highlights
- **One-click "Projects" switching**: save your provider, MCP, Skills, and memory files as a single named snapshot (say one set for coding, another for writing and drawing) and swap the whole thing with one click from the top of the home page or the tray; the state of the project you leave is saved back automatically on switch. Covers three scopes — Claude Code, Claude Desktop, and Codex — that don't interfere with one another.
- **Official Codex account can route through the proxy too**: a Codex session logged in with a ChatGPT subscription can now route through the local proxy, getting routing and usage statistics identical to third-party providers; the official login credentials are never overwritten or stored.
- **GPT-5.6 fully in place**: a 372K context window is auto-injected when Claude Code routes through Codex takeover; Sol / Terra / Luna three-tier pricing is seeded (cache writes billed at 1.25× the input rate); the relevant presets' default models are bumped to the gpt-5.6 family; and, with the client identity corrected, `gpt-5.6-luna` no longer falsely 404s.
- **Use Claude-family models inside Codex (native Anthropic Messages upstream)**: plenty of companies ban the Claude Code client but not the Claude API — now, by setting a Codex provider's upstream format to `anthropic`, you can connect directly to the Claude API or any gateway that only offers `/v1/messages`, with the local proxy handling the two-way Responses↔Anthropic conversion and standard 5-minute prompt-cache injection built in.
- **Proxy bridge correctness fixes**: upstream failures fail closed and trigger failover instead of an empty reply; reasoning / tool results / the system role survive the bridge losslessly; cache writes are no longer double-billed; breakpoint injection is more thorough.
- **Codex config.toml hardening**: deleted MCP servers no longer come back; MCP sync errors out rather than blanking the file on a parse failure; common-config merges preserve comments and key order.
- **Kimi For Coding 256K finally works**: the previous 262144 compaction window never actually took effect (clamped back to Claude Code's 200K default); this release fills in the model-alias routing and window injection; existing providers need to re-apply the preset (see "Upgrade Notes").
---
## Added
### Projects: Named Snapshots of a Whole Configuration, Switched in One Click
This is the headline feature of the release. You can save your current provider, MCP, Skills, and memory-file state as a named "project", then swap the whole set with one click from the project switcher at the top of the home page or the "Projects" tray submenu, instead of toggling each piece by hand.
Here's a typical scenario: you have one directory for coding and another for writing or drawing. Coding wants one provider, plus MCP like filesystem / GitHub, code-review Skills, and a memory file spelling out your engineering conventions; writing or drawing often wants a different provider, a different set of MCP, and completely different prompts. Bouncing between the two used to mean switching providers, toggling MCP and Skills one by one, and then editing the memory file; now you save the two states as two projects — "Development" and "Drawing" — and when you switch directories to work, one click in CC Switch puts the whole configuration in place.
Projects span three scopes — Claude Code, Claude Desktop, and Codex (the only dimension CC Switch manages for Claude Desktop is the provider, so its snapshots contain only the provider and applying one touches no other dimension).
A few design points worth knowing:
- **Projects are global entities, switched per scope**: the same project records its own current project and snapshot slot separately on the Claude Code / Claude Desktop / Codex sides, so switching projects on the Codex tab never touches Claude's configuration.
- **Switching auto-saves**: before you switch projects, the state of the project you're about to leave in the current scope is first saved back automatically — so a project always holds exactly what you last left it as, with no (and no need for a) manual "update snapshot" button.
- **Applying is best-effort**: applying a snapshot reuses the existing switch primitives (switch the provider first, then do the minimal MCP / Skills diff toggles, then enable memory files); if something a snapshot references has been deleted, it's only warned and skipped, never rolled back wholesale.
- **Auto-disables proxy takeover**: before applying a project, proxy takeover for each app in that scope is disabled first, to avoid the snapshot state fighting with the routing state.
If you don't use Projects, you can turn off "Show project switcher" under Settings → Home Display, which only hides the home-page entry — the tray submenu and project data are unaffected. It's backed by a new `profiles` table (the database migrates automatically, no manual steps), with UI copy in sync across all four locales.
### Proxy Route Takeover for the Official Codex ChatGPT Account
A Codex session using a ChatGPT subscription (OAuth or API-key login) can now route through CC Switch's local proxy too — official-account traffic gets routing, format conversion, and usage statistics identical to third-party providers. Just pick the built-in "OpenAI Official" entry in the provider panel or tray to take it over (if you deleted it before, it's restored automatically when you add a provider); the card badge shows "Official account routing" while routing.
The implementation deliberately achieves **zero credential storage**: instead of writing any placeholder key to `auth.json`, it projects a dedicated `model_provider` pointing at the local proxy into `config.toml`, and Codex sends its own ChatGPT authorization header to the proxy, which passes it through verbatim to the official endpoint — the credentials on the `codex-official` line are always empty. The official login itself is never overwritten: on takeover, OAuth / API-key material is preserved into the backup, and a 401 / 403 from the official side is treated as a non-retryable error, so failover never quietly moves your conversation to another account. Accordingly, the copy for the "Preserve Codex official login on switch" setting has been updated — under route takeover the official login is always preserved, so that toggle now only governs third-party direct switches that don't route through the proxy.
### GPT-5.6: Context Window, Preset Defaults, and Three-Tier Pricing
Three things were done around the GPT-5.6 family:
- **372K context-window injection**: when Claude Code routes to the ChatGPT Codex (Codex OAuth) backend through proxy takeover, `CLAUDE_CODE_MAX_CONTEXT_TOKENS` and `CLAUDE_CODE_AUTO_COMPACT_WINDOW` (both 372000) are auto-injected into the live `settings.json`, so Claude Code no longer auto-compacts prematurely at its default 200K window nor overflows the upstream. The injection is strictly gated: it only injects when every configured model key points at the gpt-5.6 family (gpt-5.5's catalog window swings between 272K and 372K, so it's deliberately not inherited); your manually set values always win; and it's stripped on switch-away under mirrored conditions, so a program default is never baked into your provider config.
- **Preset default-model bump**: the Codex OAuth presets for Claude Code and Claude Desktop bump their default routes to the gpt-5.6 family (haiku → `gpt-5.6-luna`, main model / sonnet / opus → `gpt-5.6`), and the custom Codex `config.toml` template's default model follows suit.
- **Sol / Terra / Luna three-tier pricing**: the usage dashboard seeds all three tiers at official rates — Sol 5 / 30 / 0.50, Terra 2.50 / 15 / 0.25, Luna 1 / 6 / 0.10 (USD per million tokens, input / output / cache read). Unlike 5.5 and earlier, the 5.6 family bills **prompt-cache writes at 1.25× the input rate** (Sol 6.25 / Terra 3.125 / Luna 1.25), seeded accordingly, with existing rows previously billed at 0 auto-repaired; bare `gpt-5.6` and its per-effort suffix variants align to the Sol rate.
### Use Claude-Family Models inside Codex: Native Anthropic Messages Upstream
This feature comes from a very real need: **plenty of companies ban the Claude Code client for compliance reasons, but do not ban the Claude API.** For those users the model itself is available; what's missing is a permitted client — and Codex can now fill that slot. Pick the new `anthropic` option in a Codex provider's upstream-format selector, and you can connect directly to the Claude API or any gateway offering the native Anthropic Messages protocol (`/v1/messages`); the local proxy handles the two-way conversion of requests, responses, and streaming between Responses and Anthropic, and you chat and use tools inside Codex as usual while Claude-family models run behind the scenes. The form provides the supporting pieces: an auth-field selector (`ANTHROPIC_AUTH_TOKEN` sends `Authorization: Bearer`, the default; or `ANTHROPIC_API_KEY` sends `x-api-key`), an optional Claude Code client-impersonation toggle (off by default), and a per-provider max-output-token override (Codex doesn't send `model_max_output_tokens`, and when unset it falls back to a conservative 8192, which may truncate long or heavy-reasoning replies). The conversion bridge auto-injects standard 5-minute prompt-cache markers (system prompt, tools, and history go through the cache instead of being resent at full price each turn), supports the `[1m]` long-context marker with the corresponding beta header, and reports a truncated stream faithfully as incomplete rather than disguising it as success. ([#5071](https://github.com/farion1231/cc-switch/pull/5071))
### "Default Model" Field Added to the Codex Provider Form
The top-level `model` key in `config.toml` is now an editable field in the form: when a new model (e.g. `gpt-5.6`) ships, you can point an existing provider at it directly without waiting for a preset update (presets only affect newly added providers). The field is two-way synced with the TOML editor, its candidate list is the union of the model-mapping catalog and the provider's `/models` endpoint, and when a value isn't in the catalog it offers a one-click "Add to mapping". An explicitly entered value always wins over the implicit backfill from the mapping's first row; model names and `base_url` are TOML-escaped on write, eliminating any chance that remote data from `/models` injects a forged config line.
### Common-Config Switch Auto-Sync Extended to Codex
The "on switch-away, write shared preferences from the live config back to the common config" behavior v3.16.5 added for Claude now covers Codex: when you switch away from a Codex provider that has the common config enabled, the shareable portion is first re-extracted from its live `config.toml` to update the common config and then carried to the next provider — the preferences you edited directly in the running Codex config are no longer lost on switch, and deleted keys aren't quietly re-injected. The extractor strictly strips provider-specific and injected content (`model` / `model_provider` / `base_url` / `wire_api`, the entire `[model_providers]` table, the MCP projection, the API-key fallback field, the model-catalog pointer, and the injected `web_search` sentinel), so secrets never enter the shared snippet. All failures only warn and never block the switch.
### Claude Subagent Model Configuration
The Claude provider form gains a "subagent" model row that writes `CLAUDE_CODE_SUBAGENT_MODEL`, letting subagents spawned by Claude Code run on a model you specify (usually a cheaper or faster one). It supports the `[1M]` marker; since the subagent model never appears in the `/model` menu, the row shows a "not shown in /model" placeholder with no display-name field. The proxy takeover path and the model mapper support it too: when the request model matches the configured subagent model it's passed through as-is instead of being folded to the default model; the key is also excluded from the shared common config so it doesn't leak across providers. ([#4830](https://github.com/farion1231/cc-switch/pull/4830))
### 1M Context Checkbox for the Fallback Model Field
The Claude form's fallback model field (`ANTHROPIC_MODEL`) now carries the same 1M checkbox that the Sonnet / Opus / Fable tiers have long had: when the fallback model is backed by a 1M window you can declare it faithfully, instead of it being silently treated as 200K. Checking it appends the `[1M]` marker to the model id, unchecking strips it. ([#5124](https://github.com/farion1231/cc-switch/pull/5124), fixes #3679)
### Zhipu Team-Plan Quota Query
Zhipu's team plan (team-edition Coding Plan) uses the same quota endpoint but requires `?type=2` and two extra request headers (`bigmodel-organization` / `bigmodel-project`), which the personal-edition query can't reach. The usage-script dialog gains a "Zhipu GLM Team" template; fill in the API key + organization ID + project ID to query team quota, and if any of the three is missing you get a clear prompt to complete it. Copy is in sync across all four locales. ([#5128](https://github.com/farion1231/cc-switch/pull/5128))
### OpenCode Form: Headers and Per-Model Token-Limit Editors
The OpenCode provider form fills in two pieces of config that previously required hand-editing JSON: a **Headers editor** (provider-level `options.headers`, such as the `HTTP-Referer` / `X-Title` that OpenRouter's leaderboard wants, with add/remove rows and case-insensitive deduplication) and **per-model token limits** (`model.limit.context` / `model.limit.output` numeric inputs, cleared to remove). The "extra options" block becomes a collapsible section that auto-expands when it has content; along the way, a fix stops the old placeholder filter from wrongly deleting genuine option keys that start with `option-`. ([#2907](https://github.com/farion1231/cc-switch/pull/2907))
### New Model Pricing: Tencent Hunyuan Hy3
Seeded pricing for Tencent Hunyuan Hy3 (256K context), released 2026-07-06 (converted from the launch-day list price of CNY 1 / 4 / 0.25 per million tokens); both the `hunyuan-hy3` and `hy3` ids now hit, so their usage no longer shows $0. Note that Hy3 is actually tiered by input length, and the current price table is seeded at the lowest tier, so long-context requests will underestimate cost — to be corrected once the official billing page is clear.
---
## Changed
### Codex Chat Routing Injects prompt_cache_key to Improve Cache Hits
When Codex routes through the local proxy and converts to a Chat Completions upstream, it now injects `prompt_cache_key` in a provider-aware way: auto-enabled for Kimi Coding and the OpenAI official endpoint, explicitly on for the Kimi preset, and left off for unknown OpenAI-compatible gateways to avoid a 400 from strict-schema gateways. The key value only ever takes an explicit client value or a real client session ID, and never generates a random UUID (which would drop every request into a different cache bucket, defeating the purpose). Advanced options offer an auto / enabled / disabled tri-state override.
### Codex Image Capability Auto-Inferred, Manual Toggle Removed
The generated Codex model catalog now only declares `input_modalities = ["text"]` for models on CC Switch's confirmed **exact text-only roster**; GPT, aliases, new suffix variants, and any unknown model all fail open to `["text", "image"]` — fixing GPT-family models being falsely reported as "images not supported" in the Codex IDE extension. The rectifier's "text-only model precheck" toggle continues to govern only the proxy-side active request rewriting, not the catalog declaration; catalog reverse-import also collapses the inferable capability away, so a future roster correction or a model upgrading to multimodal takes effect automatically.
### Context-Window Parameters Pinned into Presets, No Longer Form Fields
The `Codex` (ChatGPT / GPT-5.6) and `Kimi For Coding` presets no longer show the "Max Context Tokens" and "Auto-Compact Window" inputs in the form; the values are pinned directly in the preset env (Codex 372000 / 372000, Kimi For Coding 262144 / 262144) — the vast majority of users never need to touch these two numbers. The two keys are kept in env on purpose: pinning them explicitly makes the local compaction trigger point immune to a remote experimental config dialing it down. The rare users who do want to change the numbers can still edit both keys directly in the provider's JSON editor.
### Provider Connectivity Configuration Simplified
Removed the obsolete per-provider `testConfig` overrides (timeout, retry count, degradation delay threshold): the lightweight `base_url` probe now always uses the global connectivity-check config, while automatic failover remains fully driven by the proxy's separate timeout and circuit-breaker settings. The settings UI and interface naming also migrate from "model test" terminology to "connectivity check".
### Universal (Multi-App) Provider Auto-Synced After Adding
After adding a universal (multi-app) provider through the "Add Provider" dialog, it's now immediately pushed to each live target config, no longer requiring a manual sync afterward. A sync failure doesn't block the add — the provider is saved, with a non-blocking warning shown if the sync fails. ([#2811](https://github.com/farion1231/cc-switch/pull/2811))
### Preset Updates
- **LongCat-2.0**: the Meituan LongCat presets across the board (Claude Code / Claude Desktop / Codex / Hermes / OpenClaw / OpenCode) upgrade from the retired `LongCat-Flash-Chat` / `LongCat-2.0-Preview` to `LongCat-2.0`, declaring the real 1M (1048576) context window. LongCat-2.0 is a text-only model, and the proxy's media-scrub whitelist is updated in sync — images pasted into a conversation are replaced with an unsupported marker rather than hard-rejected upstream. ([#4838](https://github.com/farion1231/cc-switch/pull/4838))
- **SudoCode**: the old `sudocode.us` preset is replaced in place by the new sponsor SudoCode at `sudocode.chat`, covering six clients (the Claude family connects directly to Anthropic passthrough; Codex / OpenCode / OpenClaw / Hermes default to `gpt-5.6-sol`).
- **Volcengine / Doubao / BytePlus website links**: reverted v3.16.5's change of these three presets' `websiteUrl` to product homepages, restoring the attribution-parameter campaign / invite links (this is by design).
- **Code0.ai**: the invite link is updated to the new agent signup link; the API endpoint is unchanged.
- **Removed duplicate OpenAI Compatible presets**: the `OpenAI Compatible` custom-template entry was removed from the OpenCode and OpenClaw preset lists — the built-in `custom` provider flow already offers the same starting point, so the selector no longer shows two entries pointing at the same place. Existing providers are unaffected.
---
## Fixed
### Codex OAuth Client Identity Aligned: Fixes 404 on Latest ChatGPT Models
When routing through local proxy takeover with an official Codex OAuth account, the latest subscription models (e.g. `gpt-5.6-luna`) previously returned a misleading `404 Model not found` — even though the account had access. The root cause is that ChatGPT's Codex backend does model-group routing by the `originator` + `version` headers, and cc-switch previously self-reported `originator: cc-switch` with no version, which routed it to a group where luna wasn't yet deployed. Takeover requests now send the same `originator: codex_cli_rs` + `version: 0.144.1` as the real Codex CLI, satisfying luna's minimum client-version requirement — confirmed fixed by A/B testing against the real backend.
### Responses Upstream Failures No Longer Turn into Empty Replies
When the proxy bridges an Anthropic-format client (Claude Code / Claude Desktop) to an OpenAI Responses upstream, a semantic failure hidden inside an HTTP 2xx body (a `status:"failed"` object, an `error` envelope, or a `response.failed` SSE event before the first output) was previously converted into a silent empty turn. These failures are now recognized as real errors inside the retry loop, so failover can retry with a different provider; a stream that ends cleanly but with incomplete content is faithfully marked truncated rather than complete; a gateway that ignores `stream:true` and returns the whole JSON document is recognized and expanded into a full streaming lifecycle; and when the client history itself is malformed, it errors immediately instead of retrying a guaranteed-to-fail request against every provider.
### Reasoning, Tool Results, and the System Role Preserved Across the Responses/Anthropic Bridge
Content crossing the Responses↔Anthropic bridge in multi-turn tool loops is no longer lost or corrupted: encrypted reasoning entries round-trip losslessly (also eliminating the problem where a failed round-trip got the next request rejected upstream); the streaming converter supports the official reasoning event vocabulary and recovers tool arguments from the terminal event when a gateway skips the deltas; a structured tool result's `is_error` flag, images, and PDF documents are fully preserved in both directions instead of being flattened into a single JSON string; and `system` / `developer` messages in history are correctly promoted to Anthropic `system` instead of being silently demoted to user turns. On billing, when the upstream request succeeds but the subsequent conversion fails, usage is still recorded rather than dropped.
### Cache-Write Tokens No Longer Double-Billed
The `input_tokens` reported by Codex / Gemini-style providers include both cache reads and cache writes, but the cost calculation previously only subtracted cache reads — so cache-write tokens were **billed twice**, once at the input rate and once at the cache-creation rate. Both are now deducted first, and the cache-write number is no longer lost across format conversions (Chat↔Responses↔Anthropic). To keep historical data consistent, the database adds a column recording the storage semantics of each row's `input_tokens` (schema v12→v13 auto-migration): old rows are recomputed under the old semantics, new rows under the new, and Claude-style rows are unaffected.
### Stronger Prompt-Cache Breakpoint Injection
On the proxy paths that inject Anthropic `cache_control` breakpoints (the Codex takeover bridge and the Bedrock native optimizer), the injector now uses the four-breakpoint budget more thoroughly: beyond the tail of tools, the tail of the system prompt, and the latest cacheable message, when budget remains it also anchors an earlier user message, keeping the stable prefix within Anthropic's 20-block lookback window — so long, tool-heavy conversations keep hitting the prompt cache instead of resending the system prompt, tools, and history at full price each turn. Caller-supplied breakpoints are preserved as-is (never removed, reordered, or rewritten); injected markers all use the standard 5-minute TTL.
### Kimi For Coding's 256K Context Window Actually Takes Effect
The `CLAUDE_CODE_AUTO_COMPACT_WINDOW=262144` added to the Kimi For Coding preset in 3.16.4 in fact **never took effect**: Claude Code caps unrecognized model ids at a 200K window and takes the compaction window as `min(model window, configured value)`, so 262144 got clamped back to 200K. This release fills in the two missing pieces — the preset now also pins `CLAUDE_CODE_MAX_CONTEXT_TOKENS`, and it explicitly routes each tier's model to the endpoint's `kimi-for-coding` alias (a `claude-` prefixed id makes Claude Code ignore both window parameters; a non-Claude alias is the key to unlocking the large window). Saved providers also get these two window defaults auto-injected on switch, but **the alias routing only lives in the preset** — a provider saved from the old preset is still effectively 200K and needs a one-time re-apply of the preset (see "Upgrade Notes").
### Deleted Codex MCP Servers No Longer Come Back
The authoritative MCP-server data lives in the database, and the `[mcp_servers]` in the Codex live `config.toml` is only a projection re-synced after every write — but on switch-away this projection was baked into the provider snapshot, so a server you deleted in the app came back to life the next time you activated that provider, and per-entry reconciliation could never clear the orphan. On switch-away, `[mcp_servers]` (including the legacy `[mcp.servers]`) is now stripped from the stored snapshot, and an already-polluted snapshot self-heals on its next switch-away. One visible side effect: a hand-written `[mcp_servers.*]` section in a Codex provider config is stripped out of the snapshot on its first switch-away — from now on, define Codex's MCP servers through the MCP manager (see "Upgrade Notes").
### More Robust MCP Sync: No File-Blanking on Parse Failure, Per-App Errors
Two fixes. First, when writing a single MCP server to Codex, if the existing `config.toml` fails to parse, the old logic fell back to an empty document and wrote the whole thing back — **the entire file was blanked** down to that one MCP entry; it now returns a validation error and leaves the file untouched. Second, "Import from App" previously swallowed each importer's error as 0, so a broken Codex config would only show "imported 0 servers"; it now imports best-effort per app and, on failure, reports exactly which app failed. The projection on switch and save is also scoped to the target app only, so one app's live-file parse failure no longer blocks the others by association, nor falsely reports an already-successful switch as a failure.
### Codex Common-Config Merge Preserves Comments and Key Order
Checking / unchecking "Apply Common Config" in the Codex provider form previously went through a front-end TOML implementation that reformatted the whole file (parse → merge → serialize): comments were dropped, keys were reordered, and empty table headers like `[model_providers]` appeared out of nowhere — the culprit behind "config.toml keeps getting reordered". The merge now goes through a backend command sharing the same merge semantics as writing the live config, so hand-written formatting fully survives the mid-edit merge; a double guard (operation sequence number + config baseline check) was also added for the fast-switch race introduced by going async, so an earlier-issued-but-later-arriving stale result doesn't overwrite newer state.
### Managed Claude Takeover Injects Only a Single Auth Placeholder
When switching from a third-party endpoint to a Codex-managed provider, `~/.claude/settings.json` had both `ANTHROPIC_API_KEY` and `ANTHROPIC_AUTH_TOKEN` placeholders written, causing Claude Code to warn "Both ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY set" on every launch. It now injects only one: Codex-managed uses `ANTHROPIC_AUTH_TOKEN`, Copilot uses `ANTHROPIC_API_KEY`, and any other token key is cleared. Note: after upgrading, if the live config already carries both keys, the "skip rewrite if config unchanged" short-circuit means the warning may persist — toggle the Claude route off and on once (or switch providers once) to trigger a rewrite (see "Upgrade Notes"). ([#5095](https://github.com/farion1231/cc-switch/pull/5095), fixes #4919)
### Usage and Quota Queries: Auto-Retry on Transient Failures, No Longer Poison the Cache
Usage and quota queries frequently showed a "query failed" that a manual refresh couldn't clear, because all transport-layer failures (including a mid-read timeout while reading the response body) were folded into "succeeded, but the result is a failure" — so the front-end's auto-retry never fired, and the failure body was cached as real data. Transport failures now return an error faithfully: react-query's auto-retry kicks in, HTTP 429 is treated as transient like 5xx, retained last-good data expires normally on a 10-minute window, and the footer keeps a retry entry and the real error message in the failure state. (fixes [#3820](https://github.com/farion1231/cc-switch/issues/3820))
### Codex Subagent Session Usage Counted in Local Statistics
The token usage of Codex subagent (spawned agent) sessions previously never made it into local statistics: subagent logs carry the parent thread's `session_id`, so multiple subagents' records collided and were discarded as duplicates. The parser now builds a unique identity from each file's own `thread_id`, and recognizes the replay of the parent thread's history at the start of a subagent log — using it only to recover the cumulative baseline, not to double-bill; archived logs also inherit the sync cursor by filename, so re-parsing only imports the new portion. ([#5187](https://github.com/farion1231/cc-switch/pull/5187))
### Codex Free-Tier 30-Day Quota Window Displays Correctly
Codex free accounts are metered on a rolling 30-day window (rather than the paid tier's weekly window), but the front-end whitelist and tray grouping didn't recognize the `30_day` tier — with a free account's only tier filtered out, the quota footer went entirely blank and the tray showed no quota at all. The 30-day tier now renders correctly in both the footer and the tray, with labels in sync across all four locales. ([#4886](https://github.com/farion1231/cc-switch/pull/4886), fixes #3651)
### Usage Dashboard Refresh Interval Persisted
The usage dashboard's auto-refresh interval was previously component-local state that reset to 30 seconds on every restart. It's now persisted via a new app setting, with changes applied optimistically and rolled back automatically on save failure. ([#5057](https://github.com/farion1231/cc-switch/pull/5057))
### Fable-Tier Model Keys No Longer Leak into the Common Config
Fable is the fourth Claude model-mapping tier added in v3.16.3, but its two keys `ANTHROPIC_DEFAULT_FABLE_MODEL(_NAME)` were left off the provider-specific exclusion list — so one provider's Fable model pin could leak into the shared common config and then be injected into other providers. It's now stripped like the haiku / sonnet / opus tiers, and Fable-tier proxy takeover support was filled in along the way (writing a stable role alias on takeover, cleaning up stale values on switch-away). ([#5206](https://github.com/farion1231/cc-switch/pull/5206), fixes #4272)
### Tool-Schema Default-Type Fallback and API Key Input for Uncategorized Providers
Two provider-side fixes: when a client-sent tool's `input_schema` lacks a top-level `type` (or is an empty `{}`), the proxy conversion would be rejected by a strict gateway; the root schema now auto-fills `type: "object"` (only the root, leaving nested subschemas untouched); and the issue where an uncategorized provider imported from history or hand-built showed no Claude API Key input on edit is fixed — the field now shows for any provider that isn't an official / cloud-vendor category. ([#5069](https://github.com/farion1231/cc-switch/pull/5069))
### Image-Request Fallback for the Text-Only GLM 5.2 Model
When the local proxy takes over Volcengine Coding Plan running GLM 5.2, image blocks in a request no longer produce an unrecoverable 400: the text-only roster exactly includes `glm-5.2` (deliberately not prefix-matching, so a future multimodal `glm-5.2v` isn't caught), and the preventive path strips images before the request goes out; the gateway's error message that doesn't contain the word image (`Model only support text input`) is also recognized by the reactive path's self-identifying-phrase roster, triggering the media fallback. (fixes [#5025](https://github.com/farion1231/cc-switch/issues/5025))
### A Batch of Small Session and Live-Config Sync Fixes
- **Show renamed Codex session titles**: a session you renamed inside Codex now shows the new title in the session manager instead of falling back to the first message text; reads during concurrent writes no longer fail immediately. ([#4927](https://github.com/farion1231/cc-switch/pull/4927))
- **OpenCode / OpenClaw / Hermes live edits synced into the store on startup**: editing a live config file directly (changing the base URL, adding a model) was previously never picked up after the first import; it's now diffed against the store on every startup and updated on difference, all non-fatal. ([#4712](https://github.com/farion1231/cc-switch/pull/4712), [#5098](https://github.com/farion1231/cc-switch/pull/5098))
- **OpenCode session-resume command updated**: the resume command the session manager shows and copies is corrected from the outdated `opencode session resume <id>` to the current CLI's `opencode -s <id>`. ([#2359](https://github.com/farion1231/cc-switch/pull/2359))
- **Official providers skip the connectivity probe**: the connectivity check no longer derives a guaranteed-to-fail credential-less first-party endpoint probe for official-category providers (e.g. hitting `chatgpt.com/backend-api/codex` bare); batch checks skip it, and an individual resolve reports a clear error.
---
## Documentation
### Codex + Kimi Local Routing Guide
A new step-by-step guide (in Chinese / English / Japanese, with UI screenshots) explaining how to use Kimi inside Codex CLI via CC Switch's local routing: newer Codex CLI speaks the OpenAI Responses protocol, while the Kimi Open Platform (pay-as-you-go, `kimi-k2.7-code`) and Kimi For Coding (membership, `kimi-for-coding`) both expose Chat Completions endpoints, so a direct connection usually 404s on `/responses`. The guide covers the whole flow from adding a provider from the built-in preset to the four-step protocol-conversion chain.
### README Sponsor Update
The open-source AI infrastructure project `new-api` joins the sponsor table in the four-language READMEs.
---
## Upgrade Notes
### Kimi For Coding Providers Need a Preset Re-Apply
If you're using a provider created from the Kimi For Coding preset, please **re-pick it from the preset and save once**: the key to the 256K window — routing each tier's model to the `kimi-for-coding` alias — only lives in the new preset, so a provider saved from the old preset still compacts prematurely at 200K even after upgrading.
### Hand-Written Codex `[mcp_servers.*]` Will Be Stripped from the Snapshot
To root out "deleted MCP servers coming back", switching away from a Codex provider strips the `[mcp_servers]` section from the stored snapshot. If you have an MCP server hand-written directly in a Codex provider config, it will disappear from the snapshot on the first switch-away from that provider — please instead define Codex's MCP servers through the MCP manager (MCP tab), where the entries are authoritative and get projected into the live config automatically.
### The Dual-Auth-Key Warning May Need a One-Time Manual Rewrite
If Claude Code still warns "Both ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY set" after upgrading, it's because the takeover logic short-circuits and skips the rewrite when the live config is unchanged. Toggle Claude's route off and on once (or switch providers once) to write the corrected single-placeholder config, and the warning goes away.
### Automatic Database Migration
On the first launch of v3.17.0, the database automatically migrates from schema v11 to v13 (adding the projects table and the usage-semantics column), with no manual steps. If you're in the habit of rolling back to an older version, back up `~/.cc-switch/cc-switch.db` first.
---
## Risk Notice
This release continues the risk notices from previous versions for reverse-proxy-style features.
**Codex OAuth reverse proxy**: using a ChatGPT subscription's Codex OAuth through a reverse proxy may violate OpenAI's terms of service. See the [v3.13.0 release notes](v3.13.0-en.md#-risk-notice) for details. The "official ChatGPT account proxy route takeover" added in this release is the same class of usage, so please be aware of the same risk.
**Codex third-party provider Chat routing**: when CC Switch local proxy converts and forwards Codex requests to third-party providers, each provider may have different requirements for billing, compliance, and data retention. Read the target provider's terms before use.
**Claude Desktop third-party provider proxy switching**: when CC Switch's built-in proxy gateway forwards Claude Desktop requests to third-party providers, you must also follow the target provider's billing, compliance, and data-retention terms.
By enabling these features, users accept the related risks. CC Switch is not responsible for account restrictions, warnings, or service suspensions caused by using these features.
---
## Thanks
Thanks to the following contributors for the features and fixes in v3.17.0:
- [#5071](https://github.com/farion1231/cc-switch/pull/5071): add the native Anthropic Messages protocol as a Codex upstream, thanks @yeeyzy.
- [#4830](https://github.com/farion1231/cc-switch/pull/4830): add Claude subagent model configuration, thanks @AkimioJR.
- [#5124](https://github.com/farion1231/cc-switch/pull/5124): add the 1M checkbox to the fallback model field, thanks @salarkhannn.
- [#5128](https://github.com/farion1231/cc-switch/pull/5128): add Zhipu team-plan quota queries, thanks @zhanxin-xu.
- [#2907](https://github.com/farion1231/cc-switch/pull/2907): add the Headers and token-limit editors to the OpenCode form, thanks @git1677967754.
- [#2811](https://github.com/farion1231/cc-switch/pull/2811): auto-sync universal providers after adding, thanks @hubutui.
- [#4838](https://github.com/farion1231/cc-switch/pull/4838): upgrade the LongCat presets to LongCat-2.0, thanks @solthx.
- [#5095](https://github.com/farion1231/cc-switch/pull/5095): inject only a single auth placeholder on managed Claude takeover, thanks @fengshao1227.
- [#5187](https://github.com/farion1231/cc-switch/pull/5187): count Codex subagent session usage in statistics, thanks @starmiaoa.
- [#4886](https://github.com/farion1231/cc-switch/pull/4886): fix the Codex free-tier 30-day quota window not showing, thanks @SaladDay.
- [#5057](https://github.com/farion1231/cc-switch/pull/5057), [#4927](https://github.com/farion1231/cc-switch/pull/4927), [#2359](https://github.com/farion1231/cc-switch/pull/2359): persist the refresh interval, show renamed session titles, and correct the OpenCode resume command, thanks @makoMakoGo.
- [#5206](https://github.com/farion1231/cc-switch/pull/5206): exclude Fable model keys from the common config, thanks @fzh365.
- [#5069](https://github.com/farion1231/cc-switch/pull/5069): tool-schema default-type fallback and API Key input restoration, thanks @Komikawayi.
- [#4712](https://github.com/farion1231/cc-switch/pull/4712), [#5098](https://github.com/farion1231/cc-switch/pull/5098): sync OpenCode / OpenClaw / Hermes live config on startup, thanks @allenxu09.
Thanks also to everyone who reported Codex official routing, cache billing, MCP sync, and quota query issues — a good portion of this release's fixes came directly from reproduction clues in these real-world scenarios.
---
## Download & Install
Visit [Releases](https://github.com/farion1231/cc-switch/releases/latest) and download the build for your system.
### System Requirements
| System | Minimum Version | Architecture |
| ------- | -------------------- | ----------------------------------- |
| Windows | Windows 10 and later | x64 / ARM64 |
| macOS | macOS 12 (Monterey)+ | Intel (x64) / Apple Silicon (arm64) |
| Linux | See table below | x64 / ARM64 |
### Windows
| File | Description |
| ---------------------------------------- | ------------------------------------------------ |
| `CC-Switch-v3.17.0-Windows.msi` | **Recommended** - MSI installer with auto-update |
| `CC-Switch-v3.17.0-Windows-Portable.zip` | Portable build, unzip and run |
Windows ARM64 devices should pick the artifact whose file name carries the `arm64` tag.
### macOS
| File | Description |
| -------------------------------- | ----------------------------------------------------- |
| `CC-Switch-v3.17.0-macOS.dmg` | **Recommended** - DMG installer, drag to Applications |
| `CC-Switch-v3.17.0-macOS.zip` | Unzip and drag to Applications, Universal Binary |
| `CC-Switch-v3.17.0-macOS.tar.gz` | For Homebrew install and auto-update |
Homebrew install:
```bash
brew install --cask cc-switch
```
Upgrade:
```bash
brew upgrade --cask cc-switch
```
### Linux
Linux assets are available for both **x86_64** and **ARM64** (`aarch64`). Choose the file whose architecture tag matches your machine's `uname -m` output:
- `CC-Switch-v3.17.0-Linux-x86_64.AppImage` / `.deb` / `.rpm`
- `CC-Switch-v3.17.0-Linux-arm64.AppImage` / `.deb` / `.rpm`
| Distribution | Recommended Format | Install Command |
| --------------------------------------- | ------------------ | ---------------------------------------------------------------------- |
| Ubuntu / Debian / Linux Mint / Pop!\_OS | `.deb` | `sudo dpkg -i CC-Switch-*.deb` or `sudo apt install ./CC-Switch-*.deb` |
| Fedora / RHEL / CentOS / Rocky Linux | `.rpm` | `sudo rpm -i CC-Switch-*.rpm` or `sudo dnf install ./CC-Switch-*.rpm` |
| openSUSE | `.rpm` | `sudo zypper install ./CC-Switch-*.rpm` |
| Arch Linux / Manjaro | `.AppImage` | Make executable and run directly, or use AUR |
| Other distributions / unsure | `.AppImage` | `chmod +x CC-Switch-*.AppImage && ./CC-Switch-*.AppImage` |
</content>
</invoke>
+363
View File
@@ -0,0 +1,363 @@
# CC Switch v3.17.0
> 本リリースでは、長らく待ち望まれていた**「プロジェクト」のワンクリック切り替え**を追加しました。現在のプロバイダー、MCP、Skills、メモリファイルを名前付きスナップショットとしてまとめて保存し、タイトルバーやトレイから別の構成へ一括で切り替えられます。切り替え時には、離れる側のプロジェクトの現在状態も自動保存されます。Codex 側も大きく進化しました。**公式 ChatGPT サブスクリプションアカウントもローカルプロキシ経由でルーティング**できるようになり、サードパーティプロバイダーと同じルーティング・使用量統計を利用できます。GPT-5.6 ファミリーのコンテキストウィンドウと Sol / Terra / Luna の 3 段階価格にも対応しました。さらに、ネイティブ Anthropic Messages 上流形式を追加——勤務先で Claude Code クライアントは禁止されていても Claude API は禁止されていませんか?これで **Codex の中から Claude 系モデルを直接利用できます**。このほか、上流エラーが「空の応答」になる問題、キャッシュ書き込み token の二重課金、削除した MCP サーバーの復活、Kimi For Coding の 256K ウィンドウが実際には効いていなかった問題など、多数の正確性の問題を修正しました。
**[English →](v3.17.0-en.md) | [中文版 →](v3.17.0-zh.md)**
---
## 利用ガイド
本リリースの新機能は、主にホーム画面上部のプロジェクト切り替え、Codex プロバイダーフォーム、使用量ダッシュボードにあります。以下のドキュメントもあわせてご覧ください:
- **[Codex で Claude を使う(ローカルルーティングガイド)](../guides/codex-claude-routing-guide-ja.md)**:本リリースの「ネイティブ Anthropic Messages 上流」に対応した新しいステップバイステップガイドです。Codex プロバイダーの上流形式を `anthropic` にして、`/v1/messages` のみを提供する Claude 系ゲートウェイへ接続し、Codex の中で Claude 系モデルを使う手順を説明します。
- **[Codex で Kimi を使う(ローカルルーティングガイド)](../guides/codex-kimi-routing-guide-ja.md)**:新しい Codex CLI は OpenAI Responses 形式を使いますが、Kimi Open Platform と Kimi For Coding は Chat Completions endpoint を提供するため、直接接続すると通常は 404 になります。このガイドでは、内蔵の `Kimi` / `Kimi For Coding` プリセットとローカルルーティングを使ったプロトコル変換を説明します。
- **[Codex の公式ログインを保持する](../guides/codex-official-auth-preservation-guide-ja.md)**:サードパーティプロバイダーへ切り替える際に、CC Switch が公式 ChatGPT ログインをどう保持するかを説明します。本リリースではさらに、公式アカウント自体をプロキシ経由でルーティングできるようになりました。
- **[使用量統計](../user-manual/ja/4-proxy/4.4-usage.md)**:使用量ダッシュボードのデータソースと集計方法を確認できます。本リリースではキャッシュ書き込み課金を修正し、Codex サブエージェントの使用量を補完し、GPT-5.6 と Hunyuan Hy3 の価格を追加しました。
---
> [!WARNING]
>
> ## 唯一の公式チャネル(必ずお読みください)
>
> CC Switch は**完全に無料・オープンソース**のデスクトップアプリで、**ユーザーから料金を徴収することはありません**。本ソフトウェアは下記の公式チャネルからのみ入手してください:
>
> | チャネル | 唯一の公式 |
> | ------------ | ------------------------------------------------------------------------------ |
> | 公式サイト | **[ccswitch.io](https://ccswitch.io)** |
> | ソースコード | **[github.com/farion1231/cc-switch](https://github.com/farion1231/cc-switch)** |
> | ダウンロード | **[GitHub Releases](https://github.com/farion1231/cc-switch/releases)** |
> | 作者 | **[@farion1231](https://github.com/farion1231)** |
> | 偽サイト通報 | **[GitHub Issues](https://github.com/farion1231/cc-switch/issues)** |
>
> **料金請求・チャージ・認証情報の提供を求める「CC Switch」サイトやクライアントはすべて偽物です。** 支払いを誘導された場合は直ちに操作を中止し、GitHub Issues からご報告ください。
---
## 概要
CC Switch v3.17.0 は v3.16.5 に続く大型機能リリースです。中心となるのは**「プロジェクト」**です。Claude Code / Claude Desktop / Codex の現在のプロバイダー、MCP、Skills、メモリファイルを名前付きスナップショットとして保存できます。たとえば、プログラミング用ディレクトリには「開発」、文章作成やイラスト用ディレクトリには「イラスト」という構成を用意し、ホーム上部またはトレイの「プロジェクト」サブメニューからワンクリックで一括切り替えできます。切り替える前には、離れるプロジェクトの現在状態が自動で書き戻されるため、各プロジェクトには最後に離れた時点の構成が常に保持されます。
もう一つの柱は Codex です。**ChatGPT サブスクリプションでログインした公式アカウントもローカルプロキシでルーティング**できるようになりました。API key は不要で、Codex 自身のログイン情報をそのまま転送し、公式ログインを上書きしません。クライアント識別情報も修正され、`gpt-5.6-luna` など最新のサブスクリプションモデルが誤って 404 になる問題を解消しました。GPT-5.6 の 372K コンテキスト注入、Sol / Terra / Luna の 3 段階価格(キャッシュ書き込みは 1.25 倍)、プリセットの既定モデルも同時に対応しています。さらに Codex の上流形式にネイティブ Anthropic Messages を追加しました。これは、**Claude Code クライアントは禁止していても Claude API は禁止していない企業が多い**という現実的な利用場面を想定したものです。Codex を Claude API、または `/v1/messages` のみを提供するゲートウェイへ接続し、Codex の中で Claude 系モデルを使えます。
正確性についても、3 つの領域を集中的に改善しました。**プロキシ変換**では、HTTP 2xx 内の上流エラーを空の応答に変換せず failover へ渡し、Responses↔Anthropic 間で reasoning、ツール結果、system role を保持し、プロンプトキャッシュのブレークポイントを強化しました。**使用量と課金**では、キャッシュ書き込み token が入力料金とキャッシュ作成料金の両方で計上されていた問題を修正し、ネットワークの一時障害で使用量キャッシュが壊れないようにしました。**Codex `config.toml`** では、削除した MCP サーバーの復活を防ぎ、解析不能なファイルを同期時に消去しないよう fail-closed にし、共通設定のマージでコメントやキー順序を維持するようにしました。Kimi For Coding の 256K ウィンドウ、Codex サブエージェントと無料プランの使用量表示、Zhipu チームプランのクォータ照会、OpenCode フォームの強化も含まれます。
**リリース日**: 2026-07-13
**Stats**: 69 commits | 172 files changed | +21,067 / -2,464 lines
---
## ハイライト
- **「プロジェクト」をワンクリック切り替え**:プロバイダー、MCP、Skills、メモリファイルをまとめて名前付きスナップショットとして保存できます(たとえばプログラミング用と、文章作成・イラスト用)。ホーム上部またはトレイから一括切り替えでき、離れるプロジェクトの現在状態は自動保存されます。Claude Code、Claude Desktop、Codex の各スコープは互いに干渉しません。
- **Codex の公式アカウントもプロキシルーティング可能に**:ChatGPT サブスクリプションでログインした Codex セッションをローカルプロキシ経由で扱い、サードパーティプロバイダーと同じルーティング・使用量統計を利用できます。公式ログイン情報は保存も上書きもしません。
- **GPT-5.6 に全面対応**Claude Code の Codex テイクオーバー時に 372K コンテキストを注入し、Sol / Terra / Luna の価格(キャッシュ書き込みは入力価格の 1.25 倍)を追加。プリセットも GPT-5.6 ファミリーへ更新し、クライアント識別修正で `gpt-5.6-luna` の誤った 404 を解消しました。
- **Codex で Claude 系モデルを使用(ネイティブ Anthropic Messages 上流)**Claude Code クライアントは禁止されていても Claude API は禁止されていない企業環境で、Codex の上流形式を `anthropic` に設定すれば Claude API または `/v1/messages` ゲートウェイへ接続できます。ローカルプロキシが Responses↔Anthropic を双方向変換します。
- **プロキシ変換の正確性を改善**:上流エラーを空の応答にせず failover へ渡し、reasoning / ツール結果 / system role を保持。キャッシュ書き込みの二重課金も修正しました。
- **Codex `config.toml` を堅牢化**:削除済み MCP サーバーの復活を防ぎ、解析エラー時にファイルを消去せず、共通設定マージでコメントとキー順序を保持します。
- **Kimi For Coding の 256K が実際に有効に**:以前の 262144 設定は Claude Code の 200K 上限に抑えられていました。本リリースでモデル別名とウィンドウ設定を補完します。既存プロバイダーはプリセットを適用し直してください。
---
## 追加機能
### 「プロジェクト」:構成一式を保存してワンクリック切り替え
本リリースの中心機能です。現在のプロバイダー、MCP、Skills、メモリファイルを名前付き「プロジェクト」として保存し、ホーム上部のプロジェクト切り替えまたはトレイの「プロジェクト」サブメニューから一括で切り替えられます。項目ごとに手作業で切り替える必要はありません。
典型的な例を挙げます。あるディレクトリではプログラミングを行い、別のディレクトリでは文章を書いたりイラストを作ったりするとします。プログラミングには専用のプロバイダー、ファイルシステム / GitHub MCP、コードレビュー Skills、開発ルールを書いたメモリファイルが必要です。一方、文章作成やイラストでは別のプロバイダー、別の MCP、まったく異なるプロンプトを使います。以前は作業を移るたびに、プロバイダーを変え、MCP と Skills を一つずつ切り替え、メモリファイルも変更する必要がありました。今後は 2 つの構成を「開発」と「イラスト」というプロジェクトに保存し、作業ディレクトリを変えるときに **CC Switch で一度クリックするだけ**で構成一式を切り替えられます。ディレクトリに応じて自動切り替えする機能ではなく、ユーザーが明示的に選ぶ仕組みです。
設計上のポイント:
- **プロジェクトは共有、切り替えはスコープ別**:同じプロジェクトでも Claude Code / Claude Desktop / Codex はそれぞれ独立した現在位置とスナップショットを持ちます。Codex タブで切り替えても Claude の設定には触れません。
- **切り替え時に自動保存**:新しいプロジェクトへ移る前に、離れるプロジェクトの現在状態をそのスコープへ自動で書き戻します。このため手動の「現在状態で更新」ボタンは不要です。
- **適用はベストエフォート**:既存の切り替え処理を再利用し、プロバイダー、MCP / Skills の差分、メモリファイルの順に適用します。削除済み項目への参照は警告してスキップし、全体をロールバックしません。
- **プロキシテイクオーバーを自動解除**:スナップショットを適用する前に対象スコープのテイクオーバーを無効化し、ルーティング状態との競合を避けます。
プロジェクトを使わない場合は、「設定 → ホームページ表示」の「プロジェクト切り替えを表示」をオフにできます。ホームの入口だけが非表示になり、トレイのサブメニューやプロジェクトデータはそのまま利用できます。新しい `profiles` テーブルへのデータベース移行は自動です。
### Codex 公式 ChatGPT アカウントのプロキシルーティング
ChatGPT サブスクリプション(OAuth または API-key ログイン)で使う Codex セッションを、CC Switch のローカルプロキシ経由でルーティングできるようになりました。サードパーティプロバイダーと同じルーティング、形式変換、使用量統計を利用できます。プロバイダーパネルまたはトレイで内蔵の「OpenAI Official」を選択してください。削除済みの場合は、プロバイダー追加時に自動復元されます。
実装では**認証情報を保存しない**ことを重視しています。`auth.json` にダミーの API key を書かず、ローカルプロキシを指す専用 `model_provider``config.toml` へ投影します。Codex 自身の ChatGPT Authorization header をプロキシが公式 endpoint へそのまま転送し、`codex-official` 行の認証情報は常に空です。テイクオーバー時も OAuth / API-key 情報をバックアップへ保持します。公式 endpoint の 401 / 403 は再試行不能として扱い、failover が別アカウントへ会話を移したり circuit breaker を汚したりしません。「切り替え時に Codex 公式ログインを保持」設定は、今後、ルーティングを使わないサードパーティへの直接切り替えだけを制御します。
### GPT-5.6:コンテキスト、プリセット、3 段階価格
- **372K コンテキスト注入**Claude Code を ChatGPT CodexCodex OAuth)へルーティングすると、`CLAUDE_CODE_MAX_CONTEXT_TOKENS``CLAUDE_CODE_AUTO_COMPACT_WINDOW` を 372000 で注入します。全設定モデルが GPT-5.6 ファミリーの場合だけ適用し、GPT-5.5 や混在マッピングには適用しません。ユーザーの明示設定が常に優先され、切り替え後に既定値が保存設定へ固着しないよう逆変換で除去します。
- **プリセット更新**Claude Code / Claude Desktop の Codex OAuth プリセットを GPT-5.6 ファミリーへ更新(haiku → `gpt-5.6-luna`、main / sonnet / opus → `gpt-5.6`)。Codex `config.toml` テンプレートも更新しました。
- **Sol / Terra / Luna 価格**100 万 token あたりの入力 / 出力 / キャッシュ読み取りを、Sol 5 / 30 / 0.50、Terra 2.50 / 15 / 0.25、Luna 1 / 6 / 0.10 USD で登録。GPT-5.6 のキャッシュ書き込みは入力の 1.25 倍(6.25 / 3.125 / 1.25)で、既存データベースの誤ったゼロ値も安全に修復します。
### Codex で Claude 系モデルを使う:ネイティブ Anthropic Messages 上流
この機能は、**Claude Code クライアントを禁止していても Claude API は禁止していない企業が多い**という現実的な要望から生まれました。モデルは利用できるのに、許可されたクライアントがない——その空白を Codex で埋められます。Codex プロバイダーの上流形式で `anthropic` を選ぶと、Claude API またはネイティブ Anthropic Messages`/v1/messages`)のみを提供するゲートウェイへ接続できます。ローカルプロキシが Responses↔Anthropic のリクエスト、応答、ストリームを双方向変換するため、Codex の操作感のまま Claude 系モデルとの対話やツール利用が可能です。
フォームには、認証フィールド選択(既定の `ANTHROPIC_AUTH_TOKEN``Authorization: Bearer``ANTHROPIC_API_KEY``x-api-key`)、任意の Claude Code クライアント偽装(既定オフ)、プロバイダー別 `maxOutputTokens` を追加しました。変換ブリッジは system、tools、history に標準 5 分の `cache_control` を注入し、`[1m]` 長コンテキストマーカーと beta header に対応します。途中で切れたストリームは成功として偽装せず、未完了 / 失敗として報告します。([#5071](https://github.com/farion1231/cc-switch/pull/5071)
### Codex プロバイダーフォームに「既定モデル」欄を追加
`config.toml` トップレベルの `model` をフォームから編集できます。新モデル(例:`gpt-5.6`)が公開されたとき、プリセット更新を待たずに既存プロバイダーを切り替えられます。TOML エディターと双方向同期し、モデルマッピングと `/models` endpoint の候補を表示します。明示値はマッピング先頭行より常に優先され、remote の model id を書き込む際は TOML エスケープと制御文字除去を行います。
### Codex でも切り替え時に共通設定を自動同期
v3.16.5 で Claude に追加した live 設定から共通設定への自動書き戻しを Codex にも拡張しました。共通設定を有効にした Codex プロバイダーから離れるとき、live `config.toml` の共有可能な部分を再抽出し、次のプロバイダーへ渡します。`model` / `model_provider` / `base_url` / `wire_api``[model_providers]`、MCP 投影、API key、モデルカタログ、CC Switch が注入した `web_search` は除外されるため、プロバイダー固有情報や秘密情報は共有されません。失敗は警告のみで切り替えを妨げません。
### Claude サブエージェントモデル設定
Claude プロバイダーフォームに「サブエージェント」モデル行を追加し、`CLAUDE_CODE_SUBAGENT_MODEL` を設定できるようにしました。サブエージェントを安価または高速なモデルへ固定でき、`[1M]` マーカーにも対応します。プロキシテイクオーバーとモデルマッパーもこの設定を認識し、共通設定からは除外されるため他プロバイダーへ漏れません。([#4830](https://github.com/farion1231/cc-switch/pull/4830)
### フォールバックモデルにも 1M チェックボックス
Claude フォームのフォールバックモデル(`ANTHROPIC_MODEL`)に、Sonnet / Opus / Fable と同じ 1M チェックボックスを追加しました。チェックすると model id に `[1M]` が付き、解除すると除去されます。([#5124](https://github.com/farion1231/cc-switch/pull/5124)、#3679 を修正)
### Zhipu チームプランのクォータ照会
使用量スクリプトに「Zhipu GLM Team(智谱团队)」テンプレートを追加しました。API Key、組織 ID、プロジェクト ID を入力すると、`?type=2` と必要な request headers を使ってチームプランのクォータを照会します。3 項目のいずれかが欠けている場合は補完を案内します。([#5128](https://github.com/farion1231/cc-switch/pull/5128)
### OpenCode フォーム:Headers とモデル別 Token 上限
OpenCode プロバイダーフォームに、`options.headers``HTTP-Referer` / `X-Title` など)と、モデル別の Context / Output token 上限(`model.limit.context` / `model.limit.output`)の編集 UI を追加しました。「Extra Options」は折りたたみ式になり、既存値があれば自動展開します。旧 draft prefix のため正当な `option-` で始まるキーが消える問題も修正しました。([#2907](https://github.com/farion1231/cc-switch/pull/2907)
### Tencent Hunyuan Hy3 の価格
2026-07-06 公開の Hunyuan Hy3256K context)に価格を追加し、使用量が $0 と表示されないようにしました。`hunyuan-hy3``hy3` の両方を登録しています。実際の Hy3 は入力長で段階課金されますが、現在の単一価格テーブルでは最安段階を使用するため、長コンテキストでは費用を過小評価する可能性があります。
---
## 変更
### Codex Chat に `prompt_cache_key` を供給
Responses→Chat Completions 変換で、プロバイダーを考慮した `prompt_cache_key` を注入します。Kimi Coding と OpenAI 公式 endpoint は自動有効、Kimi プリセットは明示的に有効、未知の互換ゲートウェイは schema 400 を避けるため無効のままです。値はクライアントの明示値または実際の session ID だけを使い、リクエストごとのランダム UUID は生成しません。高度な設定で Auto / Enabled / Disabled を選べます。
### Codex の画像対応を自動推定
生成した Codex モデルカタログは、CC Switch が確認済みの**完全一致 text-only リスト**にあるモデルだけを `["text"]` として宣言します。GPT、alias、新しい suffix、未知のモデルは `["text", "image"]` へ fail-open します。これにより GPT 系モデルが Codex IDE で「画像非対応」と誤表示される問題を解消しました。整流器の text-only preflight は代理側の本文処理だけを制御し、カタログ宣言には影響しません。
### コンテキスト値をフォームではなくプリセットへ固定
`Codex`ChatGPT / GPT-5.6)と `Kimi For Coding` の「Max Context Tokens」「Auto Compact Window」をフォームから外し、プリセット env に固定しました(Codex は 372000 / 372000、Kimi For Coding は 262144 / 262144)。遠隔実験でローカルの圧縮点が引き下げられないよう、2 つの env key 自体は意図的に残しています。必要な場合はプロバイダーの JSON エディターから直接編集できます。
### プロバイダー接続確認を簡素化
古いプロバイダー別 `testConfig`timeout、retry、degraded latency)を削除し、軽量 `base_url` probe はグローバル設定だけを使うようにしました。自動 failover は引き続き独立した proxy timeout と circuit breaker 設定で動作します。「model test」関連の UI / API 名も「connectivity check」へ統一しました。
### Universal Provider を追加後すぐ live 設定へ同期
複数アプリ向け Universal Provider を追加すると、DB 保存後に Claude / Codex / Gemini 等の live 設定へすぐ同期します。同期に失敗しても保存は取り消さず、非ブロッキング警告を表示します。([#2811](https://github.com/farion1231/cc-switch/pull/2811)
### プリセット更新
- **LongCat-2.0**Claude Code / Claude Desktop / Codex / Hermes / OpenClaw / OpenCode の既定モデルを廃止済みの名称から `LongCat-2.0` へ更新し、実際の 1M context を宣言しました。text-only モデルとして media sanitizer にも追加し、画像は上流で 400 になる前に非対応マーカーへ置換します。([#4838](https://github.com/farion1231/cc-switch/pull/4838)
- **SudoCode**:旧 `sudocode.us` プリセットを新しいスポンサー SudoCode(`sudocode.chat`)へ置き換え、6 クライアントをカバーします。Codex / OpenCode / OpenClaw / Hermes の既定は `gpt-5.6-sol` です。
- **火山 / Doubao / BytePlus**v3.16.5 で製品ページへ変更した `websiteUrl` を、帰属パラメータ付きのキャンペーン / 招待リンクへ戻しました。これは意図された設定です。
- **Code0.ai**:招待リンクを新しい agent 登録 URL へ更新。API endpoint は変更していません。
- **重複した OpenAI Compatible プリセットを削除**OpenCode / OpenClaw では内蔵 `custom` フローが同じ初期設定を提供するため、重複エントリだけを削除しました。既存プロバイダーには影響しません。
---
## 修正
### Codex OAuth のクライアント識別を修正し最新モデルの 404 を解消
公式 Codex OAuth をローカルプロキシで使うと、権限がある `gpt-5.6-luna``404 Model not found` になることがありました。ChatGPT Codex backend が `originator` + `version` の組み合わせで model cohort を選ぶ一方、CC Switch は `originator: cc-switch` かつ version なしで送っていたことが原因です。現在は実際の Codex CLI と同じ `originator: codex_cli_rs` + `version: 0.144.1` を送り、luna の最低バージョン要件を満たします。実 endpoint への A/B テストでも修正を確認しました。
### Responses 上流エラーを空の応答にしない
Anthropic クライアントを Responses 上流へつなぐ際、HTTP 2xx body 内の `{status:"failed"}`、error envelope、出力前の `response.failed` SSE event が空の `end_turn` に変換されていました。現在は retry loop 内で実エラーとして検出し、failover が別プロバイダーを選べます。途中終了した stream は不完全または切断エラーとして報告し、`stream:true` を無視して JSON 全体を返す gateway にも対応します。不正なクライアント履歴は再試行不能エラーとして即座に返します。
### Responses / Anthropic 間で reasoning・ツール結果・system role を保持
暗号化 reasoning を署名付き thinking block として往復し、公式 reasoning stream events、欠落 delta の tool arguments、`is_error`、画像、PDF / `input_file` を保持します。履歴中の `system` / `developer` message は user 発言へ降格せず Anthropic `system` へ移します。上流リクエストが成功した後に変換エラーとなった場合も使用量を記録します。
### キャッシュ書き込み token の二重課金を修正
Codex / Gemini の `input_tokens` は cache read と cache write を含みますが、以前は read だけを差し引いたため、write token が入力料金と cache creation 料金の両方で計上されていました。現在は両方を差し引き、Chat↔Responses↔Anthropic の変換でも cache write 値を保持します。schema v13 の `input_token_semantics` により、既存行は旧方式、新規行は新方式で安全に計算します。
### プロンプトキャッシュのブレークポイント注入を強化
ツール末尾、system 末尾、最新の cacheable message に加え、予算があれば古い user message にも anchor を追加します。長い tool-heavy 会話でも安定 prefix が Anthropic の 20-block lookback 内に残ります。呼び出し側が付けた breakpoint は削除・並べ替え・書き換えせず、4 個を超えている場合は警告して自動注入しません。注入 TTL は標準の 5 分です。
### Kimi For Coding の 256K context を実際に有効化
`CLAUDE_CODE_AUTO_COMPACT_WINDOW=262144` だけでは、未知の非 Claude model を Claude Code が 200K とみなすため 200K に抑えられていました。本リリースは `CLAUDE_CODE_MAX_CONTEXT_TOKENS` を追加し、すべての model tier を `kimi-for-coding` alias へ明示的にルーティングします。既存プロバイダーには window 値を live 設定へ注入しますが、alias は新プリセットにのみ含まれるため、旧プリセット利用者は適用し直す必要があります。
### 削除した Codex MCP サーバーが復活しないように
Codex live `config.toml``[mcp_servers]` は DB の投影ですが、切り替え時にプロバイダースナップショットへ保存されていたため、アプリで削除したサーバーが次回有効化時に復活していました。切り替え時に `[mcp_servers]` と旧 `[mcp.servers]` を保存設定から除去し、汚染済みスナップショットも次回切り替えで自己修復します。手書きの `[mcp_servers.*]` も除去対象になるため、今後は MCP 管理画面を使用してください。
### MCP 同期:解析エラー時にファイルを消去せず、アプリ別に失敗を報告
Codex `config.toml` が解析不能な状態で MCP を 1 件同期すると、以前は空の TOML へフォールバックしてファイル全体を上書きし、その MCP だけを残していました。現在は検証エラーを返し、元ファイルを変更しません。全アプリからのインポートも失敗を 0 件として隠さず、成功分を取り込んだうえで失敗したアプリ名をまとめて報告します。切り替え・保存時の投影は対象アプリだけに限定し、他アプリの破損に巻き込まれません。
### Codex 共通設定マージでコメントとキー順序を保持
フロントエンドの TOML parse → merge → stringify は、コメントを削除し、キーを並べ替え、空の table header を作っていました。マージを backend の `toml_edit` ベース処理へ移し、フォームプレビューと live 書き込みで同じ意味論を使うようにしました。非同期化に伴う race も operation sequence と config baseline の 2 段階で防ぎます。
### 管理下 Claude テイクオーバーでは auth placeholder を 1 つだけ注入
Codex 管理プロバイダーへ切り替えると `ANTHROPIC_API_KEY``ANTHROPIC_AUTH_TOKEN` の両方が `PROXY_MANAGED` になり、Claude Code が毎回警告していました。現在は Codex で `ANTHROPIC_AUTH_TOKEN`、Copilot で `ANTHROPIC_API_KEY` の 1 つだけを使います。既存の二重設定は短絡処理で自動書き換えされない場合があるため、Claude ルーティングを一度オフ / オンするか、プロバイダーを切り替えてください。([#5095](https://github.com/farion1231/cc-switch/pull/5095)、#4919 を修正)
### 使用量・クォータ照会の一時障害を再試行し、キャッシュを汚さない
通信失敗や body 読み取り timeout が `success:false` の正常結果として扱われ、再試行されず、失敗データが cache に保存されていました。現在は一時的な transport error、HTTP 429、5xx をエラーとして返し、react-query の retry を有効にします。最後の正常値は 10 分の保持期間に従い、失敗時も footer に retry 操作と実エラーを表示します。([#3820](https://github.com/farion1231/cc-switch/issues/3820)
### Codex サブエージェントの使用量をローカル統計へ追加
サブエージェントログは親 thread の `session_id` を持つため、複数サブエージェントの request ID が衝突し重複として破棄されていました。各ファイルの `thread_id` を優先して一意な ID を作り、ログ冒頭の親履歴 replay は累積 baseline の復元だけに使って二重計上を防ぎます。archive 後も元ファイルの cursor を継承します。([#5187](https://github.com/farion1231/cc-switch/pull/5187)
### Codex 無料プランの 30 日クォータを footer とトレイに表示
無料アカウントの `30_day` window が UI whitelist と tray grouping に含まれず、唯一の tier が消えてクォータ表示全体が空になっていました。30 日 tier を単一の定数で backend / tray / frontend に追加し、4 言語のラベルを用意しました。([#4886](https://github.com/farion1231/cc-switch/pull/4886)、#3651 を修正)
### 使用量ダッシュボードの更新間隔を永続化
自動更新の off / 5s / 10s / 30s / 60s 選択をアプリ設定へ保存し、再起動後も維持します。変更は即時反映し、保存失敗時は元の値へ戻します。([#5057](https://github.com/farion1231/cc-switch/pull/5057)
### Fable model key を Claude 共通設定から除外
`ANTHROPIC_DEFAULT_FABLE_MODEL` / `_NAME` が除外リストから漏れ、あるプロバイダーの Fable mapping が共通設定経由で他へ流れる可能性がありました。Haiku / Sonnet / Opus と同様に除外し、Fable の proxy takeover role alias も補完しました。([#5206](https://github.com/farion1231/cc-switch/pull/5206)、#4272 を修正)
### tool schema の既定 `type` と未分類プロバイダーの API Key 欄
Anthropic tool の root `input_schema``type` がない場合、厳格な OpenAI gateway が変換後の schema を拒否していました。root にだけ `type: "object"` を補い、nested schema は変更しません。また、過去にインポートした category なしのカスタムプロバイダーを編集すると API Key 欄が隠れる問題を修正しました。([#5069](https://github.com/farion1231/cc-switch/pull/5069)
### Volcano GLM 5.2 の text-only 画像 400 を回避
GLM 5.2 を完全一致の text-only model として登録し、画像 block を上流送信前に置換します。将来の multimodal `glm-5.2v` を誤判定しないよう prefix match は使いません。`Model only support text input` という image の語を含まないエラーも reactive fallback で認識します。([#5025](https://github.com/farion1231/cc-switch/issues/5025)
### セッションと live 設定の小修正
- **Codex で変更したセッションタイトルを表示**:`session_index.jsonl` / `state_5.sqlite` の保存タイトルを優先し、同時書き込み時は busy timeout を使います。([#4927](https://github.com/farion1231/cc-switch/pull/4927)
- **OpenCode / OpenClaw / Hermes の live 編集を起動時に DB へ同期**base URL や model を直接変更した場合も、毎回の起動で差分を取り込みます。([#4712](https://github.com/farion1231/cc-switch/pull/4712)、[#5098](https://github.com/farion1231/cc-switch/pull/5098)
- **OpenCode の再開コマンドを更新**:`opencode session resume <id>` を現在の `opencode -s <id>` へ修正しました。([#2359](https://github.com/farion1231/cc-switch/pull/2359)
- **公式プロバイダーの接続 probe をスキップ**:認証情報なしでは必ず失敗する公式 endpoint を batch connectivity check で probe しません。
---
## ドキュメント
### Codex + Kimi ローカルルーティングガイド
Kimi を Codex CLI で使うための手順を、中国語 / 英語 / 日本語とスクリーンショット付きで追加しました。Codex は Responses、Kimi Open Platform`kimi-k2.7-code`)と Kimi For Coding`kimi-for-coding`)は Chat Completions を使うため、内蔵プリセットから追加し、CC Switch のローカルルーティングで双方向変換する流れを説明します。
### README のスポンサー欄を更新
オープンソース AI インフラプロジェクト `new-api` を 4 言語 README のスポンサー表へ追加しました。
---
## アップグレード時の注意
### Kimi For Coding プロバイダーはプリセットを適用し直してください
Kimi For Coding を使用している場合は、**新しいプリセットを選び直して保存**してください。256K を有効にする鍵である `kimi-for-coding` alias への各 model tier のルーティングは新プリセットにのみ含まれます。旧プリセットのままでは、アップグレード後も実際のウィンドウは 200K です。
### 手書きの Codex `[mcp_servers.*]` はスナップショットから除去されます
削除済み MCP サーバーの復活を防ぐため、Codex プロバイダーから切り替えるとき `[mcp_servers]` を保存スナップショットから除去します。手書きした MCP 定義も初回切り替え時に消えるため、今後は MCP 管理画面で定義してください。DB の `mcp_servers` が正本となり、live config へ自動投影されます。
### auth 二重キー警告は一度手動で書き換えを発火させる必要があります
アップグレード後も Claude Code が「Both ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY set」と警告する場合は、live config が一致しているとテイクオーバー処理が書き換えを省略するためです。Claude ルーティングを一度オフにしてからオンに戻すか、プロバイダーを一度切り替えると、単一 placeholder に修正されます。
### データベースは自動移行されます
v3.17.0 の初回起動時に schema v11 から v13(プロジェクトテーブルと input token semantics)へ自動移行します。手動操作は不要です。古いバージョンへ戻す可能性がある場合は、先に `~/.cc-switch/cc-switch.db` をバックアップしてください。
---
## リスク通知
本リリースは、リバースプロキシ系機能に関する以前のリスク通知を引き続き適用します。
**Codex OAuth リバースプロキシ**:ChatGPT サブスクリプションの Codex OAuth をリバースプロキシ経由で使用すると、OpenAI の利用規約に違反する可能性があります。詳細は [v3.13.0 release notes](v3.13.0-ja.md#-リスクに関する注意事項) を参照してください。本リリースで追加した「公式 ChatGPT アカウントのプロキシルーティング」も同じ種類の利用方法であり、同様のリスクがあります。
**Codex サードパーティプロバイダー Chat ルーティング**:CC Switch ローカルプロキシで Codex リクエストを変換し、サードパーティプロバイダーへ転送する場合、課金・コンプライアンス・データ保持に関する制約はプロバイダーごとに異なります。利用前に対象プロバイダーの利用規約を確認してください。
**Claude Desktop サードパーティプロバイダープロキシ切り替え**:CC Switch 内蔵のプロキシゲートウェイで Claude Desktop のリクエストをサードパーティプロバイダーへ転送する場合も、対象プロバイダーの課金・コンプライアンス・データ保持に関する規約に従う必要があります。
上記機能を有効化したユーザーは、関連するリスクを自ら負うものとします。CC Switch は、これらの機能の利用によって発生したアカウント制限、警告、サービス停止について責任を負いません。
---
## 謝辞
v3.17.0 で機能と修正を届けてくださった以下のコントリビューターに感謝します:
- [#5071](https://github.com/farion1231/cc-switch/pull/5071):Codex の上流としてネイティブ Anthropic Messages protocol を追加、@yeeyzy に感謝。
- [#4830](https://github.com/farion1231/cc-switch/pull/4830):Claude サブエージェントモデル設定を追加、@AkimioJR に感謝。
- [#5124](https://github.com/farion1231/cc-switch/pull/5124):フォールバックモデルに 1M チェックボックスを追加、@salarkhannn に感謝。
- [#5128](https://github.com/farion1231/cc-switch/pull/5128):Zhipu チームプランのクォータ照会を追加、@zhanxin-xu に感謝。
- [#2907](https://github.com/farion1231/cc-switch/pull/2907)OpenCode フォームに headers と token 上限編集を追加、@git1677967754 に感謝。
- [#2811](https://github.com/farion1231/cc-switch/pull/2811)Universal Provider の追加後自動同期、@hubutui に感謝。
- [#4838](https://github.com/farion1231/cc-switch/pull/4838)LongCat プリセットを LongCat-2.0 へ更新、@solthx に感謝。
- [#5095](https://github.com/farion1231/cc-switch/pull/5095):管理下 Claude テイクオーバーの auth placeholder を 1 つに修正、@fengshao1227 に感謝。
- [#5187](https://github.com/farion1231/cc-switch/pull/5187):Codex サブエージェントの使用量統計を修正、@starmiaoa に感謝。
- [#4886](https://github.com/farion1231/cc-switch/pull/4886):Codex 無料プランの 30 日クォータ表示を修正、@SaladDay に感謝。
- [#5057](https://github.com/farion1231/cc-switch/pull/5057)、[#4927](https://github.com/farion1231/cc-switch/pull/4927)、[#2359](https://github.com/farion1231/cc-switch/pull/2359):更新間隔の永続化、変更済みセッションタイトル表示、OpenCode 再開コマンド修正、@makoMakoGo に感謝。
- [#5206](https://github.com/farion1231/cc-switch/pull/5206)Fable model key を共通設定から除外、@fzh365 に感謝。
- [#5069](https://github.com/farion1231/cc-switch/pull/5069)tool schema の既定 type と API Key 欄を修正、@Komikawayi に感謝。
- [#4712](https://github.com/farion1231/cc-switch/pull/4712)、[#5098](https://github.com/farion1231/cc-switch/pull/5098)OpenCode / OpenClaw / Hermes の live 設定同期、@allenxu09 に感謝。
Codex 公式ルーティング、キャッシュ課金、MCP 同期、クォータ照会について報告してくださったすべてのユーザーにも感謝します。本リリースの多くの修正は、実際の利用場面から得られた再現情報をもとにしています。
---
## ダウンロードとインストール
[Releases](https://github.com/farion1231/cc-switch/releases/latest) から、お使いのシステムに対応するビルドをダウンロードしてください。
### システム要件
| システム | 最低バージョン | アーキテクチャ |
| -------- | ------------------------ | ----------------------------------- |
| Windows | Windows 10 以降 | x64 / ARM64 |
| macOS | macOS 12 (Monterey) 以降 | Intel (x64) / Apple Silicon (arm64) |
| Linux | 下表を参照 | x64 / ARM64 |
### Windows
| ファイル | 説明 |
| ---------------------------------------- | -------------------------------------------- |
| `CC-Switch-v3.17.0-Windows.msi` | **推奨** - 自動更新対応の MSI インストーラー |
| `CC-Switch-v3.17.0-Windows-Portable.zip` | ポータブル版、展開してそのまま実行できます |
Windows ARM64 デバイスでは、ファイル名に `arm64` が含まれる対応する成果物を選択してください。
### macOS
| ファイル | 説明 |
| -------------------------------- | ------------------------------------------------------ |
| `CC-Switch-v3.17.0-macOS.dmg` | **推奨** - DMG インストーラー、Applications へドラッグ |
| `CC-Switch-v3.17.0-macOS.zip` | 展開して Applications へドラッグ、Universal Binary |
| `CC-Switch-v3.17.0-macOS.tar.gz` | Homebrew インストールと自動更新用 |
Homebrew インストール:
```bash
brew install --cask cc-switch
```
更新:
```bash
brew upgrade --cask cc-switch
```
### Linux
Linux アセットは **x86_64****ARM64**`aarch64`)の両方を提供します。ファイル名のアーキテクチャ識別子を、マシンの `uname -m` 出力に合わせて選択してください:
- `CC-Switch-v3.17.0-Linux-x86_64.AppImage` / `.deb` / `.rpm`
- `CC-Switch-v3.17.0-Linux-arm64.AppImage` / `.deb` / `.rpm`
| ディストリビューション | 推奨形式 | インストール方法 |
| --------------------------------------- | ----------- | -------------------------------------------------------------------------- |
| Ubuntu / Debian / Linux Mint / Pop!\_OS | `.deb` | `sudo dpkg -i CC-Switch-*.deb` または `sudo apt install ./CC-Switch-*.deb` |
| Fedora / RHEL / CentOS / Rocky Linux | `.rpm` | `sudo rpm -i CC-Switch-*.rpm` または `sudo dnf install ./CC-Switch-*.rpm` |
| openSUSE | `.rpm` | `sudo zypper install ./CC-Switch-*.rpm` |
| Arch Linux / Manjaro | `.AppImage` | 実行権限を付与して直接起動、または AUR を使用 |
| その他 / 不明 | `.AppImage` | `chmod +x CC-Switch-*.AppImage && ./CC-Switch-*.AppImage` |
+363
View File
@@ -0,0 +1,363 @@
# CC Switch v3.17.0
> 这一版带来一个盼了很久的能力:**「项目」一键切换**——把当前的供应商、MCP、Skills、记忆文件整套保存为命名快照,在标题栏或托盘里一键换成另一套,切换时还会自动把你离开的项目当前状态存回去。Codex 侧同样收获颇丰:**官方 ChatGPT 订阅账号现在也能走本地代理路由**,享受与第三方供应商相同的路由与用量统计;GPT-5.6 全家的上下文窗口与 Sol / Terra / Luna 三档定价一步到位;还新增了原生 Anthropic Messages 上游格式——所在企业禁用了 Claude Code、但没有禁用 Claude API?现在可以**在 Codex 里直接用上 Claude 系列模型**。此外是一大波正确性修复:上游失败不再变成「空回复」、缓存写入不再被双重计费、删掉的 MCP 服务器不再复活、Kimi For Coding 的 256K 窗口终于真正生效。
**[English →](v3.17.0-en.md) | [日本語版 →](v3.17.0-ja.md)**
---
## 使用攻略
本版的新能力主要落在主页顶部的项目切换器、Codex 供应商表单与用量看板里,建议结合以下文档了解:
- **[在 Codex 中用 Claude(本地路由攻略)](../guides/codex-claude-routing-guide-zh.md)**:本版新增的分步攻略,配合「原生 Anthropic Messages 上游」功能使用。攻略讲解如何把 Codex 供应商的上游格式选为 `anthropic`,接入任何只提供 `/v1/messages` 的 Claude 系网关,在 Codex 里用上 Claude 系列模型。
- **[在 Codex 里使用 Kimi(本地路由攻略)](../guides/codex-kimi-routing-guide-zh.md)**:本版新增的分步攻略。较新的 Codex CLI 走 OpenAI Responses 协议,而 Kimi 开放平台与 Kimi For Coding 暴露的是 Chat Completions 端点,直连通常 404;攻略讲解如何用内置的 `Kimi` / `Kimi For Coding` 预设配合本地路由完成协议转换。
- **[Codex 官方登录保留](../guides/codex-official-auth-preservation-guide-zh.md)**:了解 CC Switch 如何在切换第三方供应商时保留你的官方 ChatGPT 登录。本版在此基础上更进一步——官方账号本身也可以走代理路由(见下方「新功能」)。
- **[用量统计](../user-manual/zh/4-proxy/4.4-usage.md)**:了解用量看板的数据来源与统计口径。本版修正了缓存写入计费、补齐了 Codex 子代理会话统计,并新增 GPT-5.6 与混元 Hy3 定价。
---
> [!WARNING]
>
> ## 唯一官方渠道声明(请务必阅读)
>
> CC Switch 是**完全免费、开源**的桌面应用,**不会向用户收取任何费用**。请仅通过下列官方渠道获取本软件:
>
> | 类别 | 唯一官方 |
> | -------- | ------------------------------------------------------------------------------ |
> | 官网 | **[ccswitch.io](https://ccswitch.io)** |
> | 源码 | **[github.com/farion1231/cc-switch](https://github.com/farion1231/cc-switch)** |
> | 下载 | **[GitHub Releases](https://github.com/farion1231/cc-switch/releases)** |
> | 作者 | **[@farion1231](https://github.com/farion1231)** |
> | 举报山寨 | **[GitHub Issues](https://github.com/farion1231/cc-switch/issues)** |
>
> **任何向你收费、要求充值、或索取登录凭据的"CC Switch"网站或客户端均为假冒**。如果你被诱导支付了费用,请立即停止操作并通过 GitHub Issues 反馈。
---
## 概览
CC Switch v3.17.0 是 v3.16.5 之后的一个功能大版本,核心是**「项目」**:你可以把 Claude Code / Claude Desktop / Codex 当前的供应商、MCP、Skills、记忆文件状态保存为命名快照——比如编程目录一套「开发」、写作绘图目录一套「创作」——在主页顶部的切换器或托盘的「项目」子菜单里一键整套切换——切换前会自动把你正要离开的项目状态存回去,所以项目里保存的永远是你上次离开时的样子。第二条主线是 Codex:**官方 ChatGPT 订阅账号现在也能走本地代理路由接管**(不需要 API Key,Codex 自己的登录凭据原样透传,绝不覆盖你的官方登录);配合修正后的客户端身份,`gpt-5.6-luna` 这类最新订阅模型不再误报 404;GPT-5.6 的 372K 上下文窗口注入、Sol / Terra / Luna 三档定价(含 1.25 倍缓存写入费率)与预设默认模型同步就位;Codex 上游格式还新增了原生 Anthropic Messages 协议——它瞄准一个很现实的场景:不少企业禁用了 Claude Code 客户端、但并没有禁用 Claude API,这些用户现在可以让 Codex 直连 Claude API(或任何只提供 `/v1/messages` 的网关),在 Codex 里照常使用 Claude 系列模型。
围绕日常使用的正确性,本版做了三波集中修复。**代理桥**:上游在 2xx 里返回的语义失败不再被转成空回复,而是触发 failover;推理内容、工具结果、system 角色跨 Responses↔Anthropic 桥无损往返;提示缓存断点注入更充分,长对话不再每轮全价重发。**用量计费**:缓存写入 token 此前被同时按输入价和缓存创建价双重计费,现已修正(数据库升级到 schema v13 以保证历史数据口径不乱);用量与配额查询遇到网络瞬时失败会自动重试、不再把失败体当真实数据缓存。**Codex `config.toml`**:在应用里删掉的 MCP 服务器不再随供应商切换复活;live 文件解析失败时同步宁可报错也不再清空整个文件;「使用通用配置」的合并挪到后端执行,注释与键序不再被打乱。另有 Kimi For Coding 256K 窗口真正生效、Codex 子代理与免费版配额统计补齐、智谱团队套餐配额查询、OpenCode 表单增强与一批预设更新。
**发布日期**2026-07-13
**更新规模**69 commits | 172 files changed | +21,067 / -2,464 lines
---
## 重点内容
- **「项目」一键切换**:把供应商、MCP、Skills、记忆文件整套保存为命名快照(比如编程一套、写作绘图一套),从主页顶部或托盘一键切换;切换时自动保存离开项目的当前状态。覆盖 Claude Code、Claude Desktop、Codex 三个作用域,互不干扰。
- **Codex 官方账号也能走代理路由**ChatGPT 订阅登录的 Codex 会话可通过本地代理路由,获得与第三方供应商一致的路由与用量统计;官方登录凭据绝不被覆盖或存储。
- **GPT-5.6 全面就位**Claude Code 走 Codex 接管时自动注入 372K 上下文窗口;Sol / Terra / Luna 三档定价入库(缓存写入按 1.25 倍输入价计费);相关预设默认模型升级到 gpt-5.6 家族;修正客户端身份后 `gpt-5.6-luna` 不再误报 404。
- **在 Codex 里使用 Claude 系列模型(原生 Anthropic Messages 上游)**:不少企业禁用了 Claude Code 客户端、但没有禁用 Claude API——现在把 Codex 供应商的上游格式选为 `anthropic`,即可直连 Claude API 或任何只提供 `/v1/messages` 的网关,本地代理完成 Responses↔Anthropic 双向转换,自带标准 5 分钟提示缓存注入。
- **代理桥正确性修复**:上游失败 fail-closed 触发 failover 而非空回复;推理 / 工具结果 / system 角色跨桥无损;缓存写入不再双重计费;断点注入更充分。
- **Codex config.toml 加固**:删掉的 MCP 服务器不再复活;解析失败时 MCP 同步宁可报错也不清空文件;通用配置合并保留注释与键序。
- **Kimi For Coding 256K 真正生效**:此前的 262144 压缩窗口从未实际生效(被 Claude Code 的 200K 默认钳回),本版补齐模型别名路由与窗口注入;存量供应商需重新套用预设(见「升级提醒」)。
---
## 新功能
### 「项目」:整套配置的命名快照与一键切换
这是本版的头号功能。你可以把当前的供应商、MCP、Skills、记忆文件状态保存为一个命名「项目」,之后在主页顶部的项目切换器或托盘的「项目」子菜单里一键整套切换,不必再逐项手动勾选。
举个典型场景:你有一个目录用来编程、另一个目录用来写作或绘图。编程时要的是一套供应商,配上文件系统 / GitHub 这类 MCP、代码审查 Skills 和写着工程约定的记忆文件;写作或绘图时往往换另一家供应商、另一组 MCP 和完全不同的提示词。以前在两件事之间来回,意味着切供应商、逐个开关 MCP 和 Skills、再改记忆文件;现在把两套状态分别存成「开发」和「绘图」两个项目,换目录干活时在 CC Switch 里点一下,整套配置随之就位。
项目功能覆盖 Claude Code、Claude Desktop 与 Codex 三个作用域(Claude Desktop 由 CC Switch 管理的维度只有供应商,因此其快照只含供应商、应用时不动其它维度)。
几个值得了解的设计:
- **项目是全局实体、按作用域切换**:同一个项目在 Claude Code / Claude Desktop / Codex 三侧各自记录自己的当前项目与快照槽位,在 Codex 页签切换项目绝不会动到 Claude 的配置。
- **切换即自动保存**:切换项目前,会先把你正要离开的项目在当前作用域下的状态自动存回去——所以项目里保存的永远是你上次离开它时的样子,不需要(也没有)手动「更新快照」按钮。
- **应用是尽力而为的**:套用快照复用现有的切换原语(先切供应商,再做 MCP / Skills 的最小差异开关,最后启用记忆文件);快照里引用的某项如果已被删除,只会告警跳过,不会整体回滚。
- **自动关闭代理接管**:套用项目前会先关闭该作用域内各应用的代理接管,避免快照状态和路由状态打架。
不用项目功能的用户可以在「设置 → 主页显示」里关闭「显示项目切换」,只隐藏主页入口,托盘子菜单与项目数据不受影响。底层由新的 `profiles` 表支撑(数据库自动迁移,无需手动操作),四语界面文案同步就位。
### Codex 官方 ChatGPT 账号的代理路由接管
用 ChatGPT 订阅(OAuth 或 API-key 登录)的 Codex 会话,现在也可以走 CC Switch 的本地代理路由了——官方账号流量获得与第三方供应商一致的路由、格式转换与用量统计。在供应商面板或托盘里选择内置的「OpenAI Official」条目进行接管即可(如果你此前删掉过它,添加供应商时会自动恢复);路由中的卡片徽标显示「官方账号路由中」。
实现上刻意做到**零凭据存储**:不向 `auth.json` 写任何占位密钥,而是往 `config.toml` 投影一个指向本地代理的专用 `model_provider`Codex 把自己的 ChatGPT 授权头原样发给代理、代理原样透传给官方端点——`codex-official` 这一行的凭据永远是空的。官方登录本身绝不被覆盖:接管时 OAuth / API-key 材料会保留进备份;官方端返回的 401 / 403 被视为不可重试错误,failover 绝不会把你的对话悄悄挪到另一个账号上。相应地,「切换时保留 Codex 官方登录」这个设置项的文案已更新——路由接管场景下官方登录总是被保留,该开关现在只管不走路由的第三方直切。
### GPT-5.6:上下文窗口、预设默认与三档定价
围绕 GPT-5.6 家族做了三件事:
- **372K 上下文窗口注入**Claude Code 经代理接管路由到 ChatGPT CodexCodex OAuth)后端时,自动往生效的 `settings.json` 注入 `CLAUDE_CODE_MAX_CONTEXT_TOKENS``CLAUDE_CODE_AUTO_COMPACT_WINDOW`(均为 372000),让 Claude Code 不再按默认 200K 窗口过早自动压缩、也不再撑爆上游。注入门控严格:只有当所有已配置的模型键都指向 gpt-5.6 家族时才注入(gpt-5.5 的目录窗口在 272K / 372K 间摇摆,故意不继承);你手动设置的值永远优先;切走时按镜像条件剥离,程序默认永远不会固化进你的供应商配置。
- **预设默认模型升级**Claude Code 与 Claude Desktop 的 Codex OAuth 预设默认路由升级到 gpt-5.6 家族(haiku → `gpt-5.6-luna`,主模型 / sonnet / opus → `gpt-5.6`),自定义 Codex `config.toml` 模板的默认模型同步跟进。
- **Sol / Terra / Luna 三档定价**:用量看板按官方价目为三档入库——Sol 5 / 30 / 0.50、Terra 2.50 / 15 / 0.25、Luna 1 / 6 / 0.10(美元每百万 token,输入 / 输出 / 缓存读)。与 5.5 及更早版本不同,5.6 家族的**提示缓存写入按 1.25 倍输入价计费**Sol 6.25 / Terra 3.125 / Luna 1.25),已按此入库并自动修复此前按 0 计的存量行;裸 `gpt-5.6` 及各 effort 后缀变体按 Sol 价对齐。
### 在 Codex 里使用 Claude 系列模型:原生 Anthropic Messages 上游
这个功能来自一个很现实的诉求:**不少企业出于合规策略禁用了 Claude Code 客户端,但并没有禁用 Claude API**。对这些用户来说,模型本身是可用的,缺的只是一个被允许的客户端——现在 Codex 可以补上这个位置。在 Codex 供应商的上游格式选择器里选新增的 `anthropic`,即可直连 Claude API 或任何只提供原生 Anthropic Messages 协议(`/v1/messages`)的网关,本地代理完成 Responses↔Anthropic 的请求、响应与流式双向转换,你在 Codex 里照常对话、照常用工具,背后跑的是 Claude 系列模型。表单配套提供:认证字段选择器(`ANTHROPIC_AUTH_TOKEN``Authorization: Bearer`,默认;或 `ANTHROPIC_API_KEY``x-api-key`)、可选的 Claude Code 客户端伪装开关(默认关闭)、以及按供应商的最大输出 token 覆盖(Codex 不发 `model_max_output_tokens`,不设置时回退到保守的 8192,可能截断长回复或重思考回复)。转换桥自动注入标准 5 分钟提示缓存标记(系统提示、工具与历史走缓存而非每轮全价重发),支持 `[1m]` 长上下文标记并补发对应 beta 头,截断的流会如实上报为未完成而不是伪装成功。([#5071](https://github.com/farion1231/cc-switch/pull/5071)
### Codex 供应商表单新增「默认模型」输入框
`config.toml` 顶层的 `model` 键现在是表单里的一个可编辑字段:新模型(如 `gpt-5.6`)发布后,你可以直接把现有供应商指过去,不必等预设更新(预设只影响新添加的供应商)。字段与 TOML 编辑器双向同步,候选列表来自模型映射目录与供应商 `/models` 端点的并集,值不在目录里时提供一键「加入映射」。显式填写的值永远优先于映射第一行的隐式回填;模型名与 `base_url` 写入时做了 TOML 转义,杜绝 `/models` 返回的远端数据注入伪造配置行的可能。
### 通用配置切换自动同步扩展到 Codex
v3.16.5 给 Claude 加的「切走时自动把 live 配置里的共享偏好回写到通用配置」现在覆盖 Codex 了:切走一个启用了通用配置的 Codex 供应商时,会先从它的 live `config.toml` 重新提取可共享部分更新到通用配置,再带给下一个供应商——你直接在运行中的 Codex 配置里改的偏好不再在切换时丢失,删掉的键也不会被悄悄注回。提取器会严格剥离供应商专属与注入内容(`model` / `model_provider` / `base_url` / `wire_api`、整个 `[model_providers]` 表、MCP 投影、API key 兜底字段、模型目录指针与注入的 `web_search` 哨兵),密钥永远不会进入共享片段。所有失败仅告警、绝不阻断切换。
### Claude 子代理模型配置
Claude 供应商表单新增「子代理」模型行,写入 `CLAUDE_CODE_SUBAGENT_MODEL`,让 Claude Code 派生的子代理跑在你指定的(通常更便宜或更快的)模型上。支持 `[1M]` 标记;由于子代理模型不会出现在 `/model` 菜单里,该行显示「不在 /model 中展示」占位而没有显示名字段。代理接管路径与模型映射器已同步支持:请求模型与配置的子代理模型一致时原样放行,不再被折叠到默认模型;该键也被排除在共享通用配置之外,不会跨供应商泄漏。([#4830](https://github.com/farion1231/cc-switch/pull/4830)
### 回退模型字段的 1M 上下文复选框
Claude 表单的回退模型字段(`ANTHROPIC_MODEL`)现在带上了 Sonnet / Opus / Fable 各档早已有的 1M 复选框:回退模型背后是 1M 窗口时可以如实声明,不再被静默当作 200K。勾选即在模型 id 后追加 `[1M]` 标记,取消即剥离。([#5124](https://github.com/farion1231/cc-switch/pull/5124),修复 #3679
### 智谱团队套餐配额查询
智谱的团队套餐(团队版 Coding Plan)走同一个配额端点但需要 `?type=2` 与两个额外请求头(`bigmodel-organization` / `bigmodel-project`),个人版查询够不到。用量脚本弹窗新增「Zhipu GLM Team(智谱团队)」模板,填入 API Key + 组织 ID + 项目 ID 即可查询团队配额;三项缺一会明确提示补全。四语文案同步。([#5128](https://github.com/farion1231/cc-switch/pull/5128)
### OpenCode 表单:请求头与模型 Token 上限编辑器
OpenCode 供应商表单补上了两块此前只能手改 JSON 的配置:**Headers 编辑器**(供应商级 `options.headers`,如 OpenRouter 排行榜要求的 `HTTP-Referer` / `X-Title`,支持增删行、大小写不敏感去重)与**按模型 Token 上限**(`model.limit.context` / `model.limit.output` 数字输入,清空即移除)。「额外选项」块改为可折叠区,已有内容时自动展开;顺带修复了旧占位符过滤会误删真实以 `option-` 开头的选项键的问题。([#2907](https://github.com/farion1231/cc-switch/pull/2907)
### 新增模型定价:腾讯混元 Hy3
为 2026-07-06 发布的腾讯混元 Hy3(256K 上下文)入库定价(按发布日牌价 CNY 1 / 4 / 0.25 每百万 token 折算),`hunyuan-hy3``hy3` 两个 id 都能命中,其用量不再显示 $0。注意 Hy3 实际是按输入长度分档计费,当前单价表按最低档入库,长上下文请求会低估成本,待官方计费页明确后再修正。
---
## 变更
### Codex Chat 路由注入 prompt_cache_key,提升缓存命中
Codex 经本地路由转换到 Chat Completions 上游时,现在会按供应商感知地注入 `prompt_cache_key`Kimi Coding 与 OpenAI 官方端点自动启用、Kimi 预设显式开启,未知的 OpenAI 兼容网关保持关闭以避免严格 schema 网关报 400。键值只取显式客户端值或真实的客户端会话 ID,绝不生成随机 UUID(那会让每个请求落到不同缓存桶、适得其反)。高级选项里提供自动 / 启用 / 禁用三态覆盖。
### Codex 图片能力自动推断,去掉手动开关
生成的 Codex 模型目录现在只把 CC Switch 确认过的**精确文本-only 名录**内的模型声明为 `input_modalities = ["text"]`;GPT、别名、新后缀变体和一切未知模型一律 fail-open 到 `["text", "image"]`——修复了 GPT 系模型在 Codex IDE 扩展里被误报「不支持图片」的问题。整流器的「纯文本模型预检」开关继续只管代理侧的主动请求改写,不影响目录声明;目录反向导入也会把可推断的能力坍缩掉,未来名录修正或模型升级多模态时自动生效。
### 上下文窗口参数钉进预设,不再作为表单字段
`Codex`ChatGPT / GPT-5.6)与 `Kimi For Coding` 预设不再在表单里展示「最大上下文 Tokens」「自动压缩窗口」两个输入框,数值直接钉死在预设 env 里(Codex 372000 / 372000Kimi For Coding 262144 / 262144)——绝大多数用户从不需要碰这两个数字。两个键刻意保留在 env 里:显式钉住能让本地压缩触发点免疫远端实验性配置的下调。极少数想改数字的用户仍可在供应商的 JSON 编辑器里直接编辑这两个键。
### 供应商连通性配置简化
移除了过时的按供应商 `testConfig` 覆盖(超时、重试次数、降级延迟阈值):轻量的 `base_url` 探测现在始终使用全局连通性检查配置,自动 failover 仍完全由代理超时与熔断器的独立设置驱动。设置界面与接口命名也从「模型测试」术语统一迁移到「连通性检查」。
### 通用(多应用)供应商添加后自动同步
通过「添加供应商」弹窗添加通用(多应用)供应商后,现在会立即推送到各 live 目标配置,不再需要手动再点一次同步。同步失败不阻塞添加——供应商已保存但同步失败时给出非阻断的警告提示。([#2811](https://github.com/farion1231/cc-switch/pull/2811)
### 预设更新
- **LongCat-2.0**:美团 LongCat 预设全线(Claude Code / Claude Desktop / Codex / Hermes / OpenClaw / OpenCode)从已退役的 `LongCat-Flash-Chat` / `LongCat-2.0-Preview` 升级到 `LongCat-2.0`,声明真实的 1M(1048576)上下文窗口。LongCat-2.0 是纯文本模型,代理的媒体清洗白名单已同步收录——粘贴进会话的图片会被替换为不支持标记而不是被上游硬拒。([#4838](https://github.com/farion1231/cc-switch/pull/4838)
- **SudoCode**:原 `sudocode.us` 预设原位替换为 `sudocode.chat` 的新赞助商 SudoCode,覆盖六个客户端(Claude 系直连 Anthropic 透传,Codex / OpenCode / OpenClaw / Hermes 默认 `gpt-5.6-sol`)。
- **火山 / 豆包 / BytePlus 官网链接**:撤销了 v3.16.5 把这三个预设 `websiteUrl` 改为产品主页的改动,恢复为带归因参数的活动 / 邀请链接(这是有意为之的设计)。
- **Code0.ai**:邀请链接更新为新的 agent 注册链接;API 端点不变。
- **删除重复的 OpenAI Compatible 预设**OpenCode 与 OpenClaw 预设列表里的 `OpenAI Compatible` 自定义模板条目被移除——内置的 `custom` 供应商流程本就提供相同的起点,选择器里不再出现两个指向同一处的入口。存量供应商不受影响。
---
## 修复
### Codex OAuth 客户端身份对齐:修复最新 ChatGPT 模型 404
用官方 Codex OAuth 账号经本地代理接管路由时,最新的订阅模型(如 `gpt-5.6-luna`)此前会返回误导性的 `404 Model not found`——明明账号有权限。根因是 ChatGPT 的 Codex 后端按 `originator` + `version` 头做模型分组路由,而 cc-switch 此前自报 `originator: cc-switch` 且不带版本号,被路由到一个 luna 尚未部署的分组。现在接管请求发送与真实 Codex CLI 一致的 `originator: codex_cli_rs` + `version: 0.144.1`,满足 luna 的最低客户端版本要求,经真实后端 A/B 实测确认修复。
### Responses 上游失败不再变成空回复
代理把 Anthropic 格式客户端(Claude Code / Claude Desktop)桥接到 OpenAI Responses 上游时,上游藏在 HTTP 2xx 体里的语义失败(`status:"failed"` 对象、`error` 信封、首个输出前的 `response.failed` SSE 事件)此前会被转换成一个悄无声息的空回合。现在这些失败在重试循环内就被识别为真实错误,failover 能够换一个供应商重试;干净结束但内容不完整的流会如实标记为截断而非完成;无视 `stream:true` 直接返回整个 JSON 文档的网关也能被识别并展开为完整的流式生命周期;客户端历史本身格式错误时立即报错,不再拿着必败的请求把每个供应商都重试一遍。
### 跨 Responses/Anthropic 桥保留推理、工具结果与 system 角色
多轮工具循环里跨 Responses↔Anthropic 桥的内容不再丢失或损坏:加密的推理(reasoning)条目无损往返(往返失败会导致下一轮请求被上游拒绝的问题同步消除);流式转换器支持官方的推理事件词汇表并在网关跳过增量时从终结事件恢复工具参数;结构化工具结果的 `is_error` 标志、图片与 PDF 文档在两个方向都完整保留,不再被压平成一个 JSON 字符串;历史里的 `system` / `developer` 消息被正确提升为 Anthropic `system`,不再被静默降级成用户发言。计费上,上游请求成功但后续转换失败时用量照记,不再漏账。
### 缓存写入 token 不再双重计费
Codex / Gemini 类供应商上报的 `input_tokens` 同时包含缓存读与缓存写,而成本计算此前只减掉了缓存读——缓存写入 token 被按输入价和缓存创建价**计了两次费**。现在两者都会先行扣除,并且缓存写入数字在跨格式转换(Chat↔Responses↔Anthropic)时不再丢失。为了让历史数据口径不乱,数据库新增一列记录每行 `input_tokens` 的存储语义(schema v12→v13 自动迁移):旧行按旧口径回算、新行按新口径,Claude 类行不受影响。
### 更强的提示缓存断点注入
在注入 Anthropic `cache_control` 断点的代理路径上(Codex 接管桥与 Bedrock 原生优化器),注入器现在会更充分地使用四个断点预算:除了工具尾、系统尾与最新可缓存消息外,预算有余时再给较早的用户消息加一个锚点,让稳定前缀保持在 Anthropic 20 块回看窗口内——长的、工具密集的对话能持续命中提示缓存,而不是每轮把系统提示、工具与历史全价重发。调用方自带的断点被原样保留(绝不删除、重排或改写);注入的标记一律使用标准 5 分钟 TTL。
### Kimi For Coding 的 256K 上下文窗口真正生效
Kimi For Coding 预设在 3.16.4 加的 `CLAUDE_CODE_AUTO_COMPACT_WINDOW=262144` 其实**从未生效**:Claude Code 对不认识的模型 id 按 200K 窗口封顶,且压缩窗口取 `min(模型窗口, 设定值)`262144 被钳回 200K。本版补齐了缺失的两环——预设同时钉上 `CLAUDE_CODE_MAX_CONTEXT_TOKENS`,并把各档模型显式路由到端点的 `kimi-for-coding` 别名(`claude-` 前缀 id 会让 Claude Code 无视这两个窗口参数,非 Claude 别名才是解锁大窗口的关键)。已保存的供应商在切换时也会自动注入这两个窗口默认值,但**别名路由只存在于预设里**——旧预设存下来的供应商实际仍是 200K,需要重新套用一次预设(见「升级提醒」)。
### 删除的 Codex MCP 服务器不再复活
MCP 服务器的权威数据在数据库里,Codex live `config.toml` 中的 `[mcp_servers]` 只是每次写入后重新同步的投影——但切走供应商时这份投影会被固化进供应商快照,导致你在应用里删掉的服务器在下次激活该供应商时死而复生,且逐条对账永远清不掉这个孤儿。现在切走时会把 `[mcp_servers]`(含旧式 `[mcp.servers]`)从存储快照中剥离,已被污染的快照在下次切走时自愈。一个可见的副作用:手写在 Codex 供应商配置里的 `[mcp_servers.*]` 段会在首次切走时被剥出快照——今后请通过 MCP 管理器定义 Codex 的 MCP 服务器(见「升级提醒」)。
### MCP 同步更健壮:解析失败不清空文件、按应用报错
两处修复。其一,向 Codex 写入单个 MCP 服务器时,如果现有 `config.toml` 解析失败,旧逻辑会退到空文档再整体写回——**整个文件被清空**、只剩那一个 MCP 条目;现在直接返回校验错误并保持文件原样。其二,「从应用导入」此前把每个导入器的错误吞成 0,坏掉的 Codex 配置只会显示「导入了 0 个服务器」;现在逐应用尽力导入、失败时报出具体是哪个应用出了问题。切换与保存时的投影也改为只针对目标应用,一个应用的 live 文件解析失败不再连坐阻塞其它应用、也不再把已经成功的切换误报为失败。
### Codex 通用配置合并保留注释与键序
Codex 供应商表单里勾选 / 取消「应用通用配置」此前走前端 TOML 实现整篇重排(解析 → 合并 → 序列化):注释被丢弃、键被重排、还会凭空多出 `[model_providers]` 这类空表头——就是「config.toml 老被重排」的元凶。现在合并走后端命令、与写 live 配置共用同一套合并语义,手写格式在编辑期合并中完整幸存;针对异步化引入的快速切换竞态也加了双重守卫(操作序号 + 配置基线核对),先发后至的旧结果不会覆盖新状态。
### 受管 Claude 接管只注入单个 auth 占位符
从第三方端点切到 Codex 受管供应商时,`~/.claude/settings.json` 里会同时写入 `ANTHROPIC_API_KEY``ANTHROPIC_AUTH_TOKEN` 两个占位符,导致 Claude Code 每次启动都警告「Both ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY set」。现在只注入一个:Codex 受管走 `ANTHROPIC_AUTH_TOKEN`、Copilot 走 `ANTHROPIC_API_KEY`,其余 token 键一律清除。注意:升级后如果 live 配置已带着双键,由于「配置未变则跳过重写」的短路逻辑,警告可能仍在——把 Claude 路由开关关再开一次(或切换一次供应商)即可触发重写(见「升级提醒」)。([#5095](https://github.com/farion1231/cc-switch/pull/5095),修复 #4919
### 用量与配额查询:瞬时失败可自动重试、不再毒化缓存
用量与配额查询频繁出现手动刷新也清不掉的「查询失败」,根因是所有传输层失败(包括读响应体中途超时)都被折叠成了「成功但结果为失败」——前端的自动重试从不触发,失败体还被当作真实数据缓存。现在传输失败如实返回错误:react-query 自动重试生效,HTTP 429 与 5xx 一样按瞬时失败处理,保留的上次成功数据按 10 分钟窗口正常过期,失败状态下页脚保留重试入口与真实错误信息。(修复 [#3820](https://github.com/farion1231/cc-switch/issues/3820)
### Codex 子代理会话用量计入本地统计
Codex 子代理(spawned agent)会话的 token 用量此前完全没进本地统计:子代理日志里携带的是父线程的 `session_id`,多个子代理的记录互相碰撞、被当作重复丢弃。现在解析器按每个文件自己的 `thread_id` 建立唯一身份,并识别子代理日志开头对父线程历史的重放、只用它恢复累计基线而不重复计费;归档日志也按文件名继承同步游标,重新解析只导入新增部分。([#5187](https://github.com/farion1231/cc-switch/pull/5187)
### Codex 免费版 30 天配额窗口正常显示
Codex 免费账号按 30 天滚动窗口计量(而非付费版的周窗口),但前端白名单和托盘分组都不认识 `30_day` 这个档位——免费账号唯一的档位被过滤掉后,配额页脚整个空白、托盘也不显示任何配额。现在 30 天档位在页脚和托盘都正常渲染,四语标签同步。([#4886](https://github.com/farion1231/cc-switch/pull/4886),修复 #3651
### 用量看板刷新间隔持久化
用量看板的自动刷新间隔此前是组件内状态,每次重启都重置回 30 秒。现在通过新的应用设置持久化,改动乐观生效、保存失败自动回滚。([#5057](https://github.com/farion1231/cc-switch/pull/5057)
### Fable 档模型键不再泄漏进通用配置
Fable 是 v3.16.3 加入的第四个 Claude 模型映射档,但它的 `ANTHROPIC_DEFAULT_FABLE_MODEL(_NAME)` 两个键漏在了供应商专属排除名单之外——某个供应商的 Fable 模型钉选可能泄漏进共享通用配置、再被注入到其它供应商。现已与 haiku / sonnet / opus 三档一样剥离,并顺带补全了 Fable 档的代理接管支持(接管时写入稳定的角色别名、切走时清理陈旧值)。([#5206](https://github.com/farion1231/cc-switch/pull/5206),修复 #4272
### 工具 schema 缺省 type 兜底与无分类供应商的 API Key 输入框
两个供应商侧修复:客户端发来的工具如果 `input_schema` 缺顶层 `type`(或干脆是空 `{}`),代理转换后会被严格网关拒绝,现在根 schema 自动补 `type: "object"`(只补根、不动嵌套子 schema);历史导入或手工构建的无分类供应商在编辑时看不到 Claude API Key 输入框的问题也已修复——现在只要不是官方 / 云厂商类供应商就显示该字段。([#5069](https://github.com/farion1231/cc-switch/pull/5069)
### GLM 5.2 纯文本模型的图片请求兜底
本地代理接管火山 Coding Plan 跑 GLM 5.2 时,请求里的图片块不再产生一个无法恢复的 400:文本-only 名录精确收录 `glm-5.2`(刻意不用前缀匹配,未来的多模态 `glm-5.2v` 不受牵连),预防路径在请求到达前剥离图片;网关那句不含 image 字样的报错(`Model only support text input`)也被反应路径的自证短语名录识别,触发媒体兜底。(修复 [#5025](https://github.com/farion1231/cc-switch/issues/5025)
### 会话与 live 配置同步小修一组
- **显示重命名的 Codex 会话标题**:在 Codex 里重命名过的会话,会话管理器现在显示新标题而不是回退到首条消息文本;并发写入时的读取也不再立即失败。([#4927](https://github.com/farion1231/cc-switch/pull/4927)
- **OpenCode / OpenClaw / Hermes 的 live 编辑在启动时同步入库**:直接改 live 配置文件(换 base URL、加模型)此前在首次导入后就再也不会被拾取;现在每次启动时对比 live 与库存,差异即更新,全程非致命。([#4712](https://github.com/farion1231/cc-switch/pull/4712)、[#5098](https://github.com/farion1231/cc-switch/pull/5098)
- **OpenCode 会话恢复命令更新**:会话管理器展示与复制的恢复命令从过时的 `opencode session resume <id>` 更正为当前 CLI 的 `opencode -s <id>`。([#2359](https://github.com/farion1231/cc-switch/pull/2359)
- **官方供应商跳过连通性探测**:连通性检查不再对官方类供应商推导出一个无凭据必失败的第一方端点探测(例如裸打 `chatgpt.com/backend-api/codex`),批量检查直接跳过、单独解析明确报错。
---
## 文档
### Codex + Kimi 本地路由攻略
新增分步攻略(中 / 英 / 日三语,含界面截图),讲解如何借助 CC Switch 的本地路由在 Codex CLI 里使用 Kimi:较新的 Codex CLI 走 OpenAI Responses 协议,而 Kimi 开放平台(按量付费,`kimi-k2.7-code`)与 Kimi For Coding(会员制,`kimi-for-coding`)暴露的都是 Chat Completions 端点,直连通常在 `/responses` 上 404。攻略覆盖从内置预设添加供应商到四步协议转换链的完整流程。
### README 赞助商更新
开源 AI 基建项目 `new-api` 加入四语 README 的赞助商表。
---
## 升级提醒
### Kimi For Coding 供应商需重新套用预设
如果你在用 Kimi For Coding 预设创建的供应商,请**重新从预设选择一次并保存**:256K 窗口的关键——把各档模型路由到 `kimi-for-coding` 别名——只存在于新版预设里,旧预设存下来的供应商即使升级后实际仍按 200K 窗口过早压缩。
### 手写的 Codex `[mcp_servers.*]` 会被剥出快照
为了根治「删掉的 MCP 服务器复活」,切走 Codex 供应商时会把 `[mcp_servers]` 段从存储快照中剥离。如果你有直接手写在某个 Codex 供应商配置里的 MCP 服务器,它会在首次切走该供应商时从快照消失——请改用 MCP 管理器(MCP 页签)定义 Codex 的 MCP 服务器,那里的条目才是权威数据、会被自动投影到 live 配置。
### 双 auth 键警告可能需要手动触发一次重写
如果升级后 Claude Code 仍提示「Both ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY set」,这是因为 live 配置未变时接管逻辑会短路跳过重写。把 Claude 的路由开关关掉再打开一次(或切换一次供应商)即可写入修正后的单占位符配置,警告随之消失。
### 数据库自动迁移
首次启动 v3.17.0 时数据库会自动从 schema v11 迁移到 v13(新增项目表与用量语义列),无需任何手动操作。如果你有回退到旧版本的习惯,建议先备份 `~/.cc-switch/cc-switch.db`
---
## 风险提示
本版本继续沿用此前版本对反向代理类功能的风险提示。
**Codex OAuth 反向代理**:使用 ChatGPT 订阅的 Codex OAuth 反代可能违反 OpenAI 服务条款,详情见 [v3.13.0 release notes](v3.13.0-zh.md#-风险提示)。本版新增的「官方 ChatGPT 账号代理路由接管」同样属于此类用法,请知悉相同的风险。
**Codex 第三方供应商 Chat 路由**:通过 CC Switch 本地代理把 Codex 请求转换并转发到第三方供应商时,各供应商对计费、合规与数据留存的约束不同,请在使用前阅读目标供应商的服务条款。
**Claude Desktop 第三方供应商代理切换**:通过 CC Switch 内置代理网关把 Claude Desktop 的请求转到第三方供应商时,同样需要遵守目标供应商的计费、合规与数据留存约束。
用户启用上述功能即表示自行承担相关风险。CC Switch 不对因使用这些功能而导致的任何账号限制、警告或服务暂停承担责任。
---
## 致谢
感谢以下贡献者在 v3.17.0 中提交的功能与修复:
- [#5071](https://github.com/farion1231/cc-switch/pull/5071):新增原生 Anthropic Messages 协议作为 Codex 上游,感谢 @yeeyzy
- [#4830](https://github.com/farion1231/cc-switch/pull/4830):新增 Claude 子代理模型配置,感谢 @AkimioJR
- [#5124](https://github.com/farion1231/cc-switch/pull/5124):给回退模型字段加上 1M 复选框,感谢 @salarkhannn
- [#5128](https://github.com/farion1231/cc-switch/pull/5128):新增智谱团队套餐配额查询,感谢 @zhanxin-xu。
- [#2907](https://github.com/farion1231/cc-switch/pull/2907)OpenCode 表单新增请求头与 Token 上限编辑器,感谢 @git1677967754
- [#2811](https://github.com/farion1231/cc-switch/pull/2811):通用供应商添加后自动同步,感谢 @hubutui
- [#4838](https://github.com/farion1231/cc-switch/pull/4838)LongCat 预设升级到 LongCat-2.0,感谢 @solthx
- [#5095](https://github.com/farion1231/cc-switch/pull/5095):受管 Claude 接管只注入单个 auth 占位符,感谢 @fengshao1227
- [#5187](https://github.com/farion1231/cc-switch/pull/5187):Codex 子代理会话用量计入统计,感谢 @starmiaoa
- [#4886](https://github.com/farion1231/cc-switch/pull/4886):修复 Codex 免费版 30 天配额窗口不显示,感谢 @SaladDay
- [#5057](https://github.com/farion1231/cc-switch/pull/5057)、[#4927](https://github.com/farion1231/cc-switch/pull/4927)、[#2359](https://github.com/farion1231/cc-switch/pull/2359):刷新间隔持久化、重命名会话标题显示与 OpenCode 恢复命令修正,感谢 @makoMakoGo
- [#5206](https://github.com/farion1231/cc-switch/pull/5206):Fable 模型键排除出通用配置,感谢 @fzh365
- [#5069](https://github.com/farion1231/cc-switch/pull/5069):工具 schema 缺省 type 兜底与 API Key 输入框恢复,感谢 @Komikawayi
- [#4712](https://github.com/farion1231/cc-switch/pull/4712)、[#5098](https://github.com/farion1231/cc-switch/pull/5098)OpenCode / OpenClaw / Hermes live 配置启动同步,感谢 @allenxu09
也感谢所有反馈 Codex 官方路由、缓存计费、MCP 同步与配额查询问题的用户——本版相当一部分修复来自这些真实使用场景里的复现线索。
---
## 下载与安装
访问 [Releases](https://github.com/farion1231/cc-switch/releases/latest) 下载对应版本。
### 系统要求
| 系统 | 最低版本 | 架构 |
| ------- | -------------------------- | ----------------------------------- |
| Windows | Windows 10 及以上 | x64 / ARM64 |
| macOS | macOS 12 (Monterey) 及以上 | Intel (x64) / Apple Silicon (arm64) |
| Linux | 见下表 | x64 / ARM64 |
### Windows
| 文件 | 说明 |
| ---------------------------------------- | ----------------------------------- |
| `CC-Switch-v3.17.0-Windows.msi` | **推荐** - MSI 安装包,支持自动更新 |
| `CC-Switch-v3.17.0-Windows-Portable.zip` | 便携版,解压即用,不写入注册表 |
Windows ARM64 设备请选择文件名中带 `arm64` 标识的对应制品。
### macOS
| 文件 | 说明 |
| -------------------------------- | --------------------------------------------- |
| `CC-Switch-v3.17.0-macOS.dmg` | **推荐** - DMG 安装包,拖入 Applications 即可 |
| `CC-Switch-v3.17.0-macOS.zip` | 解压后拖入 ApplicationsUniversal Binary |
| `CC-Switch-v3.17.0-macOS.tar.gz` | 用于 Homebrew 安装和自动更新 |
Homebrew 安装:
```bash
brew install --cask cc-switch
```
更新:
```bash
brew upgrade --cask cc-switch
```
### Linux
Linux 资产同时提供 **x86_64****ARM64**`aarch64`)两种架构。资产文件名中包含架构标识,请按你机器的 `uname -m` 输出选择对应版本:
- `CC-Switch-v3.17.0-Linux-x86_64.AppImage` / `.deb` / `.rpm`
- `CC-Switch-v3.17.0-Linux-arm64.AppImage` / `.deb` / `.rpm`
| 发行版 | 推荐格式 | 安装方式 |
| --------------------------------------- | ----------- | ---------------------------------------------------------------------- |
| Ubuntu / Debian / Linux Mint / Pop!\_OS | `.deb` | `sudo dpkg -i CC-Switch-*.deb``sudo apt install ./CC-Switch-*.deb` |
| Fedora / RHEL / CentOS / Rocky Linux | `.rpm` | `sudo rpm -i CC-Switch-*.rpm``sudo dnf install ./CC-Switch-*.rpm` |
| openSUSE | `.rpm` | `sudo zypper install ./CC-Switch-*.rpm` |
| Arch Linux / Manjaro | `.AppImage` | 添加执行权限后直接运行,或使用 AUR |
| 其他发行版 / 不确定 | `.AppImage` | `chmod +x CC-Switch-*.AppImage && ./CC-Switch-*.AppImage` |
+331
View File
@@ -0,0 +1,331 @@
# CC Switch v3.18.0
> This release lets you do two brand-new things: **hand xAI's Grok CLI (Grok Build) over to CC Switch** — it becomes the eighth managed app, with one-click provider switching, MCP / Skills sync, proxy takeover, and usage statistics all included; and **connect Grok to Claude Code, Claude Desktop, and Codex** — either by signing in with your xAI Grok account directly (device-code authorization, no API key, running on your Grok subscription, with a strict-gateway compatibility layer on the Codex side so codex 0.142+ works too), or with an xAI API key (Codex gets a native Responses direct-connection preset; Claude Code can go through local routing). Just as important is a wave of fixes: the **Codex usage double count introduced in v3.17.0 is fixed**, with an automatic data rebuild after upgrading so the dashboard numbers become real again; **codex 0.144.5+ failing to start because of the model catalog is fixed**; and switching providers on Windows no longer flashes a console window or freezes the UI. Diagnostic logs also move from "wiped on every startup" to persistent across restarts, size-rotated, and fully redacted — and a UI crash now leaves an on-disk report instead of just a blank white window.
**[中文版 →](v3.18.0-zh.md) | [日本語版 →](v3.18.0-ja.md)**
---
## Highlights: What You Can Do Now
- **Manage Grok Build (xAI's Grok CLI)**: add, import, and one-click switch Grok Build providers just like Claude Code / Codex; MCP servers and Skills sync bidirectionally, prompts auto-import on first launch, and session management plus the usage dashboard cover it fully; you can also route it through the local proxy for independent routing, failover, and billing.
- **Connect Grok with your account or an API key — both paths work**: subscription users complete an xAI device-code sign-in under Settings → OAuth Auth Center (multiple accounts supported), and all three clients — Claude Code, Claude Desktop, Codex — run straight on your Grok subscription with no API key anywhere; pay-as-you-go users connect with an xAI API key instead — Codex has a ready-made "xAI (Grok)" preset connecting natively to `api.x.ai`, and Claude Code can connect through local routing following this release's new guide. The default model is `grok-4.5` throughout.
- **Get your Codex usage numbers back to reality**: the v3.17.0 fork / sub-agent double count is rooted out at the parser level; the first launch after upgrading automatically backs up the database and rebuilds Codex usage, and the usage page gains a manual "Rebuild Codex Usage" button. Note that on first launch the history is repaired **progressively** — the dashboard numbers first shrink, then fill back in as the background re-import proceeds; this is expected (see "Upgrade Notes").
- **Upgrade the codex CLI without fear**: the "fails to start" problem caused by codex 0.144.5's strict model-catalog parsing is fixed — generated catalogs now backfill the parser-required fields automatically.
- **Switch smoothly on Windows**: switching providers or toggling takeover no longer flashes a black console window, and no longer freezes the UI for ~2 seconds (the freeze fix benefits all platforms).
- **Troubleshoot and share logs with more confidence**: diagnostic logs persist across restarts (20 MB × 4 rotation) and every egress is uniformly redacted — URL credentials, request/response bodies, and sensitive headers never reach disk anymore; a UI crash shows an error card with a Reload button, with the details written to disk.
- **Multi-turn reasoning and parallel tool calls no longer fall over**: the Responses↔Chat bridge fixes three classes of problems — reasoning content attached to the wrong message, parallel tool calls losing their IDs or their order, and tools with a null schema getting the whole request rejected by strict upstreams.
- **Use Kimi K3**: the Kimi open-platform presets for Codex / Hermes / OpenClaw / OpenCode gain K3 (1M context), with built-in pricing seeded so its usage no longer shows $0.
---
## Usage Guides
The new capabilities in this release land mainly in the provider presets, Settings → OAuth Auth Center, and the usage dashboard. The following docs are worth reading alongside it:
- **[xAI Grok Account Sign-In (Settings → OAuth Auth Center)](../user-manual/en/1-getting-started/1.5-settings.md)**: the device-code login flow, multi-account management, and the integration's boundaries; please read the client-identity disclosure under "Risk Notice" below before use.
- **[Using GPT Models in Claude Code (local routing guide)](../guides/claude-codex-routing-guide-en.md)** (now in Chinese / English / Japanese): a new step-by-step guide added in this release. Claude Code always speaks Anthropic Messages to the local `/v1/messages` route, and the local proxy converts each request to the upstream's Responses protocol — a gateway API key, a native Responses endpoint like xAI, or a ChatGPT subscription's Codex service all fit.
- **[Using Claude Models in Codex (local routing guide)](../guides/codex-claude-routing-guide-en.md)**: a new step-by-step guide in three languages, pairing with v3.17.0's native Anthropic Messages upstream to connect Codex to any Claude-family gateway that only offers `/v1/messages`.
- **[Usage Statistics](../user-manual/en/4-proxy/4.4-usage.md)**: understand the Usage Dashboard's data sources and how the statistics are counted. This release fixes the usage double count and adds the "Rebuild Codex Usage" maintenance action.
---
> [!WARNING]
>
> ## Only Official Channels (Please Read)
>
> CC Switch is a **fully free and open-source** desktop app, and we **do not charge users any fees**. Please only obtain the software through the official channels listed below:
>
> | Channel | Only Official |
> | ------------------ | ------------------------------------------------------------------------------ |
> | Website | **[ccswitch.io](https://ccswitch.io)** |
> | Source | **[github.com/farion1231/cc-switch](https://github.com/farion1231/cc-switch)** |
> | Downloads | **[GitHub Releases](https://github.com/farion1231/cc-switch/releases)** |
> | Author | **[@farion1231](https://github.com/farion1231)** |
> | Report an Imposter | **[GitHub Issues](https://github.com/farion1231/cc-switch/issues)** |
>
> **Any "CC Switch" website or client that asks you for payment, top-ups, or login credentials is fake.** If you have been tricked into paying, stop the transaction immediately and file a report through GitHub Issues.
---
## Overview
Both main threads of CC Switch v3.18.0 revolve around xAI Grok. The first is **Grok Build joining the managed apps**: xAI's Grok CLI (live config `~/.grok/config.toml`) becomes the eighth managed app alongside Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw, and Hermes — provider add / import / one-click switch, bidirectional MCP and Skills sync, deep-link import, a standalone preset list, and proxy takeover with its own route namespace; the companion "Grok Official" entry supports official-login-state detection and import, and CC Switch never touches the official credentials. The second is **xAI Grok account OAuth sign-in**: device-code authorization replaces the API key, the local proxy injects the access token per request, and on the Claude Code / Claude Desktop side it performs the Anthropic Messages → xAI Responses conversion; the Codex side gets a managed OAuth preset with its own compatibility layer — the ChatGPT-backend-private shapes emitted by codex 0.142+ (namespace tool declarations, private fields) are deterministically flattened and stripped, so the strictly parsing xAI gateway no longer returns 422; API-key users get a separate "xAI (Grok)" native Responses direct-connection preset that goes through no conversion at all.
On correctness, this release concentrates on **the Codex usage double count from v3.17.0**: the replay of the parent thread's history at the start of fork / sub-agent logs is no longer imported as new usage (the parser now only trusts explicit parent identity plus token-signature alignment), a one-time automatic usage rebuild runs after upgrading (schema v16), and the usage page gains a manual rebuild button; proxy-side usage logging becomes idempotent (replays of the same response no longer stack duplicate rows), and the usage page no longer freezes during large session imports. The **Codex conversion layer** gets four more fixes: tool schemas normalized to object type, reasoning attached forward across turns, streamed parallel tool calls keeping their IDs and order, and generated model catalogs backfilling the fields codex 0.144.5+ requires. The diagnostics story matures too: logs persist across restarts, rotate by size, and redact at every egress, and UI crashes are captured by an error boundary and written to disk. Rounding things out: Kimi K3 presets and pricing, corrected OpenClaw preset costs, SudoCode.us restored, and the first-run tray language following the system locale.
**Release date**: 2026-07-21
**Stats**: 52 commits | 217 files changed | +21,452 / -6,285 lines
---
## Added
### Grok Build: The Eighth Managed App
xAI's Grok CLI ("Grok Build", live config `~/.grok/config.toml`) is now a first-class citizen in CC Switch: provider add / import / one-click switch (with a "restart Grok Build to apply" toast), app visibility and config-directory override settings, session manager and usage dashboard coverage, prompts with first-launch auto-import, `ccswitch://` deep-link provider import, and local proxy takeover — with its own `/grokbuild/v1/responses` route namespace, an independent failover queue, and per-app proxy settings; forwarding reuses Codex's Responses path but never shares Codex's provider namespace or circuit-breaker state.
MCP servers sync bidirectionally with Grok's `[mcp_servers]` table, with the dialect differences smoothed over: Grok infers the transport from `command` / `url` and uses a `headers` field, so exports strip the explicit `type` and rename `http_headers` to `headers`, while imports infer the transport back. Skills gain a Grok Build enable flag too.
On presets, the Codex list was deliberately **not** borrowed (an early cut leaked China-direct providers and Codex default models into the Grok form); instead Grok Build ships its own curated list: only aggregators and relays that actually carry Grok models, with the default model normalized to `grok-4.5` (`x-ai/grok-4.5` on namespaced routers). The tools panel installs Grok via the official xAI installer first (`x.ai/cli/install.sh` / `install.ps1`), with the npm package `@xai-official/grok` as fallback; installs confirmed as native self-update via `grok update`, while npm installs keep npm-anchored updates — the self-update is gated on positive native detection, so it can never touch a different kind of install. UI copy is in sync across all four locales. ([#5453](https://github.com/farion1231/cc-switch/pull/5453))
### Grok Official Login: Detection, Import, and Protection
A new "Grok Official" provider entry maps to the Grok CLI's own built-in xAI OAuth login: selecting it hides the connection fields and writes an empty `~/.grok/config.toml`, and CC Switch never stores or touches the official credentials. Live-config reads, backups, and official-state writes now use syntax-only TOML validation, so an official-login (empty) config round-trips normally; importing the live config while Grok is in official login state yields "Grok Official set as current" instead of an error, matching Codex behavior. Official-state detection is deliberately wired only into the manual import command — the startup auto-importer still rejects official-state configs, so a "Grok Official" entry you deleted never comes back on the next launch. Proxy takeover of an official-login Grok config is skipped automatically, with a clear rejection on the manual path, consistent with the existing "never proxy official providers" policy.
### Sign In with Your xAI Grok Account: Claude Code and Claude Desktop
Claude Code and Claude Desktop gain an "xAI (Grok)" preset that replaces the API key with an OAuth device-code login: requests go through the local proxy, which performs the Anthropic Messages → xAI Responses API conversion and injects the access token per request, with `grok-4.5` as the default model on every tier (the Claude Desktop preset maps the `claude-*`-style role IDs to `grok-4.5` upstream so it passes Desktop's third-party model validation).
Settings → OAuth Auth Center gains an xAI section: device-code login (user code with a copy button, verification link, waiting / cancel / retry), multiple accounts with a default-account picker, per-account removal, and re-auth badges — an account whose refresh token was revoked stays visible as "expired" instead of disappearing, and auth status refetches every 15 seconds so a server-side revocation surfaces on its own.
The integration's boundaries are pinned shut: no matter what the endpoint / format fields in the form say, the upstream is always `https://api.x.ai/v1/responses` (Responses format); OAuth endpoints are resolved via OIDC discovery but strictly validated to `auth.x.ai` over https; refresh tokens live in `~/.cc-switch/xai_oauth_auth.json` (`0600` on Unix; access tokens are memory-only); and OAuth error bodies never enter error messages or logs. Pricing for `grok-4.5` ($2 input / $6 output / $0.50 cache read per million tokens) is seeded in sync so its usage no longer records $0, with existing databases picking the row up automatically on next launch. Four-locale copy included. Please read the client-identity disclosure under "Risk Notice" before use.
Not using OAuth and only have a pay-as-you-go xAI API key? You can still connect it to Claude Code: xAI's API endpoint is standard Responses protocol, so add it as an ordinary Responses provider — a custom provider with `https://api.x.ai/v1` and your API key, upstream format set to Responses, converted between Anthropic Messages ↔ Responses by local routing; it's the same recipe as the [Using GPT Models in Claude Code](../guides/claude-codex-routing-guide-en.md) guide. On the Codex side there's a ready-made API-key preset — see the next section.
### Codex Straight to xAI: Managed OAuth and Native API-Key Presets
Codex gets two ways to talk to xAI directly — OAuth managed if you have a Grok subscription, native direct connection if you have an API key:
- **The "xAI (Grok) OAuth" managed preset** runs Codex on a Grok subscription. The form hides the key / endpoint / format fields and shows the account picker, "fetch models" uses the signed-in account, and the provider is pinned to native Responses with the base URL and per-request token enforced by the proxy — edits are ignored, so the managed route cannot be redirected. Because codex 0.142+ emits ChatGPT-backend-private request shapes (`type:"namespace"` tool declarations make xAI's strict parser return a straight 422, plus private fields like `prompt_cache_retention`, `safety_identifier`, `external_web_access`, the `additional_tools` carrier, and sampling knobs grok-4.5 doesn't support), the OAuth route adds a compatibility layer on top of the native passthrough: namespace tools are flattened to top-level function tools (with the same sha256-truncated naming as the Chat path) and restored to namespaced form on the response side for both streaming and non-streaming, and unsupported fields are stripped — all deterministic field removals / structural lifts, never semantic rewrites, so prompt-cache prefixes stay stable. The layer is gated exclusively on the xAI OAuth provider type; no other provider's traffic is touched.
- **The "xAI (Grok)" API-key preset** connects natively to Responses at `api.x.ai/v1`, with a 500K-context `grok-4.5` catalog entry built in. This preset does **not** receive the xAI-specific compatibility transforms above — API-key users on codex 0.142+ can still hit xAI's strict parser, and the OAuth preset is the fully compatible path.
xAI OAuth token failures are classified as non-retryable, so failover never quietly moves your conversation onto a different Grok account.
### UI Crash Capture: On-Disk Reports and a Reload Screen
A React error boundary now wraps the entire UI (including the database-recovery screen): a renderer crash shows a "Something went wrong in the interface" card with a Reload button instead of a blank white window, and global `error` / `unhandledrejection` handlers persist renderer errors to disk — previously a JS crash left zero on-disk evidence. Everything the frontend logs passes two redaction layers: a structured serializer that redacts by sensitive property name (`tokens` / `apiKeys` / `credentials` variants all normalized to match, hiding the entire value including nested objects) and by value shape (token prefixes, PEM headers, high-entropy opaque strings), then a single text egress whose ordered regex chain covers URL query values and credentials, auth headers and schemes, and named secret containers — even in double-encoded JSON. JSON arriving as a string is re-parsed and redacted structurally, and oversized structured input is dropped entirely rather than truncated — a truncated JSON string would fall back to the weaker text regexes and could leak. The settings toggles were also relabeled to say what they actually control: "Application Diagnostic Logs" (cc-switch.log) versus the proxy's "Record Request Usage" (the stats database, which never was a text log). Four locales in sync.
### "Rebuild Codex Usage" Maintenance Button
The usage dashboard's maintenance section gains "Rebuild Codex Usage": after backing up the database, it wipes only `codex_session`-sourced detail rows, their `_codex_session` daily rollups, and the Codex sync cursors, then re-imports every rollout file from scratch with the corrected parser — the recovery path for databases already inflated by the double-count bug below, and the retry path for deferred fork files whose parent log has since been restored. The manual rebuild fails hard when the pre-rebuild backup cannot be written (the automatic migration variant only warns, since blocking startup on an unwritable backup directory would be the worse outcome after an upgrade); the whole backup → reset → re-import sequence holds the session-sync lock, so the 60-second background sync cannot interleave with the wipe; and completion always sends exactly one frontend refresh notification — including when the re-import returns zero rows or fails — so the dashboard never keeps showing pre-reset numbers. Cursor cleanup matches rollout paths purely by shape (a `rollout-{uuid}` filename under a `sessions` / `archived_sessions` segment), so cursors recorded under an old `CODEX_HOME` are cleaned too. Four locales in sync.
### Session Import Observability: Deferred Files and Suspected Duplicates
Session sync results now report `filesScanned`, `deferredFiles` — fork rollouts whose parent log is missing or whose parent markers conflict are held back without writing a cursor, so a later sync or a manual rebuild retries them instead of importing on a guess — and `suspectedDuplicates`: a post-insert probe checks each imported row for a pre-existing same-fingerprint row (via the `idx_request_logs_dedup_lookup_expr` expression index) and logs a warning per hit. If the double-count bug ever regresses, it will announce itself in the logs instead of silently inflating the totals.
### Kimi K3 in Presets and Built-In Pricing
The Kimi open-platform presets for Codex / Hermes / OpenClaw / OpenCode gain Kimi K3 (1M context window), appended after K2.7 Code so existing default-model behavior is unchanged. The built-in pricing table gains `kimi-k3` (official list price: $3 input / $15 output / $0.30 cache read per million tokens) plus a bare `k3` alias — the Kimi For Coding subscription reports its model under the short id `k3`, which would otherwise match no pricing row (same precedent as the existing `hunyuan-hy3` / `hy3` pair). Existing databases pick both rows up automatically on next launch, without touching user-edited pricing.
### SudoCode.us Returns, Coexisting with SudoCode.chat
The two unrelated companies that happen to share the "SudoCode" name are now two separate presets: the sponsor is renamed "SudoCode.chat", and the previously replaced-in-place "SudoCode.us" returns with its original endpoints, models, and icon, plus a distinct Hermes slug so both can coexist in the additive `~/.hermes/config.yaml`. Counting the new Grok Build preset list, SudoCode.chat ships in seven apps and SudoCode.us in all eight.
---
## Changed
### Diagnostic Logs: Persistent Across Restarts, Size-Rotated, and Never Recording Secrets
`cc-switch.log` is no longer wiped on every startup — the log that could have explained a crash used to be gone by the time the app reopened — and instead rotates at 20 MB with 4 archives kept (~100 MB cap, versus a single file that could balloon to 1 GB before); the previously unbounded `crash.log` rotates at 5 MB with 2 archives, with the check / rotate / append sequence under one lock so concurrent panics cannot lose an archive.
Persistent logs make verbatim secrets a real exposure (users attach these files to public issues), so the same change scrubs every backend log egress: upstream URLs are logged with userinfo / query / fragment stripped (origin-only when no known secret is available to substitute, since a credential could be embedded in the path); request and response bodies are never logged — replaced by byte counts, a short hash, or a safe classification (`sse` / `html` / `json-like` / `binary-or-encoded`, …) that keeps the transform-debugging signal without the content; response headers go through an allowlist (unlisted headers log name-only); the secret values currently in use (API key, access token) are substituted out of any logged URL that carries them; and MCP custom-field values are omitted. The log plugin also registers earlier (updater / startup failures become diagnosable), the persisted log level applies as soon as the database opens and fails closed to Info, and the "Enable Diagnostic Logs" switch now gates frontend-originated log writes too. **Log files written by earlier versions are not retroactively scrubbed** — see "Upgrade Notes".
### Preset Picker: Sponsors Grouped, Everything Else Alphabetized
The preset picker's default order becomes four tiers: official first, then the prime partner, then sponsor presets (in the same order as the README sponsor table, which the preset files were physically reordered to match), then all remaining presets sorted alphabetically by display name instead of file order. An entry matching multiple tiers lands only in the earliest one, so nothing shows up twice.
### Preset "Get API Key" Links Updated
The key-application links on the RunAPI, ClaudeCN, ZetaAPI, and APINebula presets now open each provider's current registration / referral page (ClaudeCN also moved domains: claudecn.top → claudecn.ai). Referral tags remain confined to these links and the README — website links and API endpoints stay untouched.
---
## Fixed
### Codex Fork / Sub-Agent Replayed Parent History No Longer Counted as New Usage (the v3.17.0 Double Count)
Fixes the v3.17.0 usage inflation: forking a Codex task or spawning a sub-agent in copy mode re-counted the parent conversation's token history as new usage — users reported single days jumping by billions of tokens, byte-identical parent/child rows, and empty forks carrying usage they never consumed. Fork / sub-agent rollout files begin by replaying the parent thread's history, and the old parser located the takeover boundary heuristically (the first `thread_settings_applied` event, object-shaped `subagent` source markers): the boundary landed too early when the parent's own settings changes appeared in the replay, and the current string-shaped source markers weren't recognized at all, importing the entire replayed parent history verbatim. The new parser trusts only explicit parent identity — `forked_from_id` on the child's `session_meta`, or `source.subagent.thread_spawn.parent_thread_id`, with a conflict between the two deferring the file — anchors each thread's identity to the rollout filename UUID, loads the parent rollout's own pre-fork token-count sequence, and strips the child's replayed prefix by aligning token signatures against it: replayed events only restore the cumulative baseline and are never inserted as rows. Sub-agent logs that carry no replayed history are now counted as live usage, also fixing the opposite-direction undercount where real sub-agent consumption was skipped as suspected replay. ([#5335](https://github.com/farion1231/cc-switch/issues/5335), [#5433](https://github.com/farion1231/cc-switch/issues/5433), [#5381](https://github.com/farion1231/cc-switch/issues/5381))
### Proxy Usage Logging Is Now Idempotent: Response-Scoped Stable Keys
When a terminal usage event carried no message id — the norm for Codex `/responses` traffic through the local proxy — the dedup key fell back to a random UUID, so every retry / replay of the same upstream response minted a fresh key and `INSERT OR REPLACE` stacked a new row each time; one reporter's database held the same usage combination 2,078 times. The parser now takes the key from the response envelope itself — the Codex `response.completed` event's `response.id` (ids from `response.created` are discarded), Chat Completions `chatcmpl` ids, and the Gemini `responseId` — scoped as `session:{app_type}:{provider_id}:{id}`: the same response replayed against a different provider during failover still bills once per provider without cross-provider collisions (Claude keeps the bare `session:{id}` shape so proxy rows continue to converge with session-log imports). When no envelope id exists at all, the fallback is a deterministic SHA-256 over the response's usage semantics rather than a random UUID — an identical replay must collide into the same key for dedup to work — and the final database write becomes a guarded insert-if-absent within the dedup window instead of an unconditional REPLACE. ([#5496](https://github.com/farion1231/cc-switch/issues/5496))
### Usage Page No Longer Freezes During Large Session Imports
Opening the usage page while a big import ran could lock the whole UI up: every inserted row fired a refresh notification, each notification made the frontend re-run all ~10 usage queries, and those queries contended with the importer for the single database connection while it parsed rollout files tens of megabytes large, line by line — on duplicate-inflated databases the three compounded each other. Session sync now notifies the frontend once per completed pass; all session importers are serialized behind a single-flight lock (a manual "sync now" queues behind the running pass instead of racing it); the blocking parse work runs on a dedicated blocking thread so it no longer starves the async runtime driving the UI's commands; and the 60-second background tick skips missed runs instead of bursting to catch up.
### codex 0.144.5+ No Longer Fails to Start on CC Switch-Generated Model Catalogs
codex ≥ 0.144.5 parses external model catalogs strictly and rejects the whole file when an entry is missing `supports_reasoning_summaries` — both the Codex CLI and the desktop app failed to launch, and deleting the generated catalog didn't help because any provider save regenerated it the same way. The root cause: CC Switch clones its catalog template from the machine-shared `models_cache.json`, whose field set is whatever the last-writing codex process produced — a coexisting older codex build kept rewriting the cache without the field the newer parser requires. Generated catalogs now backfill parser-required fields from the bundled static template, and only when absent (dynamic values always win); optional capability fields whose "missing = parser default" semantics must survive are deliberately not backfilled.
### Windows: No More Console Flash or UI Freeze When Switching Providers
Switching providers or toggling takeover on Windows flashed a transient console window and froze the UI for up to ~2 seconds. Three causes, three fixes: the `codex debug models --bundled` probe launches `codex.cmd` through `cmd.exe`, which in a GUI-subsystem app spawns its own console — the child process is now created with `CREATE_NO_WINDOW`; the model-catalog template was regenerated on every switch — it's now cached process-wide after the first successful load (failures stay retryable, so a bad first probe cannot poison the cache), and the Codex CLI starts at most once per app run; and `switch_provider` was a synchronous command on the main thread — it's now async with the real work on a blocking thread, still serialized by the per-app switch lock. The freeze fix benefits all platforms; the console-flash fix is Windows-specific.
### Tool Schemas with Null, Missing, or Union Parameter Types Accepted by Strict Upstreams
Built-in Codex tools such as `codex_app__automation_update` declare `parameters: null` (or `type: null`), which strict OpenAI-compatible upstreams like DeepSeek reject with a 400 for the entire request — killing tool-using sessions routed through the proxy. The Responses→Chat bridge now normalizes every tool's parameters to a `type:"object"` schema: null or missing parameters (including the nested-form missing case) become `{"type":"object","properties":{}}`, a non-object `type` (including `type: null`) is corrected to `"object"` in place, and top-level `oneOf` union schemas get a root `type:"object"` added with their branches preserved untouched. The same object-type guarantee was extended to the Codex→Anthropic tool path's `input_schema`. Existing `properties` / `required` are never dropped. ([#4706](https://github.com/farion1231/cc-switch/pull/4706), [#5315](https://github.com/farion1231/cc-switch/pull/5315), fixes #4705, #4783)
### Reasoning Models Keep Their Thinking Across Multi-Turn Codex Chat Conversations
With a reasoning model (e.g. kimi-k2-thinking) behind the proxy's Responses→Chat bridge, multi-turn history mangled the thinking: each turn's `reasoning` item got glued onto the tail of the **previous** assistant message, leaving the following assistant turn with no `reasoning_content` — models would visibly break off mid-conversation. Responses semantics place reasoning **before** the message it belongs to, so the bridge now attaches reasoning forward to the assistant message or tool call that follows it; genuine trailing reasoning back-attaches only at a confirmed tail (end of input, or a turn boundary such as an incoming user message — where it was previously silently discarded), appending to any embedded reasoning already present; and pending reasoning is always consumed at boundaries, so it can never leak across a user turn into a later assistant message. ([#5508](https://github.com/farion1231/cc-switch/pull/5508))
### Streamed Parallel Tool Calls Keep Their IDs and Their Order
Two bugs in the Chat→Responses streaming bridge corrupted parallel tool calls from upstreams that split identity across chunks: a continuation delta carrying an empty `id` overwrote the real `call_id` (the Codex client then saw `call_id:""` and couldn't match tool results to calls), and tool calls were emitted the moment they individually became ready, so a later index whose name arrived early could jump ahead of an earlier one — reordering parallel calls. Empty ids are now ignored, and emission goes through a consecutive-index gate that releases tool calls strictly in Chat `index` order, waiting on any not-yet-identified earlier index; no fake call id is ever synthesized mid-stream (only as a last resort at stream finalization, which also skips nameless calls defensively and still emits sparse indexes). ([#5310](https://github.com/farion1231/cc-switch/pull/5310))
### Managed-OAuth Providers Reliably Flagged as "Needs Local Routing"
The "needs routing" badge and switch-time warning were derived from the provider's API format — the wrong signal for managed-OAuth providers (Copilot, Codex OAuth, xAI), whose credential is injected by the proxy regardless of upstream format: a managed provider on a native format got no warning and failed silently without takeover. Routing need is now decided by a single shared predicate: official providers never need routing, managed-OAuth providers always do, and format-based rules apply only to the remaining cases. The switch-time gate also checks the right readiness signal per app: per-app takeover status for most apps (the old gate looked only at a global proxy-running flag, missing "the proxy runs but this app isn't taken over"), while Claude Desktop keeps watching the proxy process itself — the backend's takeover status has no Claude Desktop field, so a uniform per-app gate would have left Desktop warning forever. Claude Desktop provider forms now force proxy mode and lock the model-mapping toggle for every managed-OAuth type, not just xAI. Four locales in sync.
### Tool Updates Work When Node Lives in nvm / fnm / mise
Anchored npm update and repair commands invoked npm by absolute path, but npm's launcher resolves `node` via its `#!/usr/bin/env node` shebang against PATH — and a GUI-launched app inherits only the system PATH, without the version-manager directories, so updates for tools installed via nvm / fnm / mise silently failed. Every anchored npm invocation now prefixes PATH with npm's own sibling `bin` directory, so npm and its shebang resolve to the same Node install; the Codex self-repair (uninstall + reinstall) path is covered too.
### Deleted Default Skill Repositories Stay Deleted
Default Skill repositories were re-seeded on every startup by a "supplement missing defaults" pass, so a default repo you deleted silently returned on the next launch. Seeding is now one-time per database, tracked by a settings flag; databases that already contain repositories at upgrade time get the flag set without any re-seeding, so existing selections are untouched. ([#5356](https://github.com/farion1231/cc-switch/pull/5356))
### First-Run Tray Language Follows the System Locale
Before any language was chosen in settings, the tray menu was hardcoded to Simplified Chinese — on English / Japanese / Traditional-Chinese systems the main UI correctly followed the OS locale while the tray disagreed, until the user switched language once. The tray now derives its first-run language from the OS locale with the same precedence rules as the frontend (including `zh-TW` / `zh-HK` / `zh-Hant` → Traditional Chinese); an explicitly chosen language always wins, and unreadable locales fall back to Chinese as before. ([#4355](https://github.com/farion1231/cc-switch/pull/4355))
### Failed Imports Show the Real Error and Refresh the List
Every failed "import from live config" produced an empty error toast, because Tauri's `invoke` rejects with the backend's error **string** while the handler read `.message` off it. The backend's actual message is now shown (with a localized generic fallback), and the provider list refreshes even on failure, so side effects committed before the error are visible immediately.
### OpenClaw Preset Model Costs Corrected to Official List Prices
Fifteen OpenClaw preset entries carried cost values in the wrong unit or unconverted currency — the `cost` field is USD per million tokens, but e.g. `glm-5.1` was listed at `0.001/0.001` (≈1000× undervalued, so its usage showed near-zero cost) while `deepseek-v4-pro` carried unconverted CNY values (overvalued). All entries now carry official list prices in $/M; subscription-plan and free-tier endpoints deliberately show list prices too, so plan users see the standard value of their usage. Providers created from these presets going forward get the corrected values; previously added providers keep the config they were created with.
### A Batch of Small UI Fixes
- **AiHubMix icon**: the Codex app's AiHubMix preset was the only one missing its brand-icon fields and rendered a generic icon; it now matches the other apps.
- **Two missing locale keys backfilled**: the reason fragment in Codex's "needs routing because it uses Anthropic Messages format" toast rendered in Chinese inside an otherwise-localized sentence (`proxyReasonAnthropicMessages` existed in no locale file), and the provider form's key-status loading label had shipped only as a hardcoded default since April; both now exist in zh / en / ja / zh-TW.
---
## Documentation
### Codex ↔ Claude Routing Guides, Both Directions
Two new guides complete the pair — "Claude models inside the Codex client" and "Responses providers inside the Claude Code client":
- **[Using Claude Models in Codex](../guides/codex-claude-routing-guide-en.md)** (Chinese / English / Japanese, with screenshots): pairs with v3.17.0's native Anthropic Messages upstream to connect Codex to a Claude-family `/v1/messages` gateway; the v3.17.0 release notes now link to it.
- **[Using GPT Models in Claude Code](../guides/claude-codex-routing-guide-en.md)** (Chinese / English / Japanese, with screenshots): drive Claude Code with Responses-speaking providers (a gateway API key, or a ChatGPT subscription's Codex service) — Claude Code always speaks Anthropic Messages to the local `/v1/messages` route, and the proxy converts each request to the upstream's Responses protocol.
### README Sponsor Updates
SubRouter joins the sponsor table across the four README languages; the pinned Kimi sponsor copy is refreshed to K3 with banners served from the Moonshot CDN; RunAPI benefit copy is refreshed, and sponsor rows are reordered to match the in-app preset order.
---
## Upgrade Notes
### Automatic Database Migration and the One-Time Codex Usage Rebuild
Upgrading from v3.17.0 runs three schema migrations in sequence (v13 → v16): v14 rebuilds the `proxy_config` table to admit Grok Build (existing per-app proxy settings are all carried over, and a `grokbuild` row is added); v15 adds Grok Build enablement columns to the MCP-server and Skills tables; and v16 triggers the one-time automatic Codex usage rebuild — the database is first backed up under `backups/`, `codex_session` data and cursors are reset, and the normal startup sync re-imports everything with the corrected parser. Typical datasets take seconds; the heaviest dataset measured (1,801 rollout files / 1.5 GB) took about 65 seconds. Later launches are incremental as before. If you're in the habit of rolling back to older versions, back up `~/.cc-switch/cc-switch.db` yourself first.
**Please note on first launch**: the history repair completes **progressively** — the rebuild proceeds in the background with the startup sync, and during that window the Codex history numbers on the usage dashboard first drop to zero and then fill back in step by step. This is expected behavior, not data loss. The post-rebuild total will usually be **smaller** than before the upgrade: the part inflated by double counting is squeezed out, and what remains is your real usage.
### Boundaries of the Rebuild
- The rebuild recomputes usage from the rollout JSONL files, so **history whose source log was already deleted cannot be reconstructed**.
- Fork files whose parent rollout is missing are deferred and reported instead of imported on a guess; restore the parent log and run "Rebuild Codex Usage" to import them later.
- Historical proxy-source duplicate rows are permanently retained — the migration rebuilds only session-sourced data, and no cleanup pass for past proxy inflation exists; the idempotent logger only guarantees no new duplicates from this point on.
### Old Log Files Are Not Retroactively Scrubbed
From this release on, diagnostic logs are no longer cleared at startup and persist across restarts (up to ~100 MB of rotated runtime logs plus ~15 MB of crash logs). **Log files written by earlier versions are not retroactively scrubbed** and may contain API keys, tokens, or URLs with credentials — review pre-upgrade logs before sharing them publicly.
### Grok Build Installs via the Official Installer Script
Installing or reinstalling Grok Build now prefers the official xAI installer, fetching `x.ai/cli/install.sh` (or `install.ps1` on Windows) at install time, with npm as fallback; existing npm installs keep updating via npm.
### Built-In Pricing Rows Auto-Appended
New pricing rows (`grok-4.5`, `kimi-k3`, `k3`) are appended automatically on next launch via insert-if-absent; user-edited pricing rows are never overwritten.
---
## Risk Notice
### xAI Grok OAuth Sign-In (New in This Release — Please Read)
This release's xAI Grok OAuth integration **reuses the public OAuth client identity and scopes registered for the official Grok CLI** (`client_id b1a00492-073a-47ea-816f-4c329264a828`, scope including `grok-cli:access`), rather than an application identity registered by CC Switch. xAI may not support this use, and **it could lead to account restriction or suspension — use at your own risk**. The feature is entirely opt-in: if you don't add an xAI provider, nothing changes. On first login it creates `~/.cc-switch/xai_oauth_auth.json` (refresh tokens only, `0600` on Unix; access tokens are held in memory) and contacts `auth.x.ai` and `api.x.ai` through your configured outbound proxy, with no local callback port.
### Carried-Over Reverse-Proxy Notices
**Codex OAuth reverse proxy**: using a ChatGPT subscription's Codex OAuth through a reverse proxy may violate OpenAI's terms of service. See the [v3.13.0 release notes](v3.13.0-en.md#-risk-notice) for details.
**Third-party provider routing**: when CC Switch's local proxy converts and forwards Codex, Claude Desktop, or Grok Build requests to third-party providers, each provider may have different requirements for billing, compliance, and data retention. Read the target provider's terms before use.
By enabling these features, users accept the related risks. CC Switch is not responsible for account restrictions, warnings, or service suspensions caused by using these features.
---
## Thanks
Thanks to the following contributors for the features and fixes in v3.18.0:
- [#5453](https://github.com/farion1231/cc-switch/pull/5453): first-class Grok Build support (the core of the eighth managed app), thanks @YUZHEthefool.
- [#5508](https://github.com/farion1231/cc-switch/pull/5508): attach reasoning forward across the Responses→Chat bridge, thanks @ka79376046.
- [#5310](https://github.com/farion1231/cc-switch/pull/5310): preserve streamed parallel tool-call identity and order, thanks @SaladDay.
- [#5315](https://github.com/farion1231/cc-switch/pull/5315): default Codex tool parameters to an object schema, thanks @Komikawayi.
- [#4706](https://github.com/farion1231/cc-switch/pull/4706): normalize tool parameter types for strict OpenAI-compatible providers, thanks @Ryan2128.
- [#5356](https://github.com/farion1231/cc-switch/pull/5356): keep deleted default Skill repositories deleted, thanks @allenxu09.
- [#4355](https://github.com/farion1231/cc-switch/pull/4355): first-run tray language follows the system locale, thanks @LaiYueTing.
- [#5138](https://github.com/farion1231/cc-switch/pull/5138): extend backend CI to Linux / Windows / macOS, thanks @zayokami.
Thanks also to everyone who reported Codex usage anomalies, new-codex startup failures, and tool-call problems — this release's most important fixes came directly from reproduction clues in those real-world reports.
---
## Download & Install
Visit [Releases](https://github.com/farion1231/cc-switch/releases/latest) and download the build for your system.
### System Requirements
| System | Minimum Version | Architecture |
| ------- | -------------------- | ----------------------------------- |
| Windows | Windows 10 and later | x64 / ARM64 |
| macOS | macOS 12 (Monterey)+ | Intel (x64) / Apple Silicon (arm64) |
| Linux | See table below | x64 / ARM64 |
### Windows
| File | Description |
| ---------------------------------------- | ------------------------------------------------ |
| `CC-Switch-v3.18.0-Windows.msi` | **Recommended** - MSI installer with auto-update |
| `CC-Switch-v3.18.0-Windows-Portable.zip` | Portable build, unzip and run |
Windows ARM64 devices should pick the artifact whose file name carries the `arm64` tag.
### macOS
| File | Description |
| -------------------------------- | ----------------------------------------------------- |
| `CC-Switch-v3.18.0-macOS.dmg` | **Recommended** - DMG installer, drag to Applications |
| `CC-Switch-v3.18.0-macOS.zip` | Unzip and drag to Applications, Universal Binary |
| `CC-Switch-v3.18.0-macOS.tar.gz` | For Homebrew install and auto-update |
Homebrew install:
```bash
brew install --cask cc-switch
```
Upgrade:
```bash
brew upgrade --cask cc-switch
```
### Linux
Linux assets are available for both **x86_64** and **ARM64** (`aarch64`). Choose the file whose architecture tag matches your machine's `uname -m` output:
- `CC-Switch-v3.18.0-Linux-x86_64.AppImage` / `.deb` / `.rpm`
- `CC-Switch-v3.18.0-Linux-arm64.AppImage` / `.deb` / `.rpm`
| Distribution | Recommended Format | Install Command |
| --------------------------------------- | ------------------ | ---------------------------------------------------------------------- |
| Ubuntu / Debian / Linux Mint / Pop!\_OS | `.deb` | `sudo dpkg -i CC-Switch-*.deb` or `sudo apt install ./CC-Switch-*.deb` |
| Fedora / RHEL / CentOS / Rocky Linux | `.rpm` | `sudo rpm -i CC-Switch-*.rpm` or `sudo dnf install ./CC-Switch-*.rpm` |
| openSUSE | `.rpm` | `sudo zypper install ./CC-Switch-*.rpm` |
| Arch Linux / Manjaro | `.AppImage` | Make executable and run directly, or use AUR |
| Other distributions / unsure | `.AppImage` | `chmod +x CC-Switch-*.AppImage && ./CC-Switch-*.AppImage` |
+331
View File
@@ -0,0 +1,331 @@
# CC Switch v3.18.0
> 本リリースでは、まったく新しい 2 つのことができるようになります。**xAI の Grok CLIGrok Build)を CC Switch で管理する**——8 番目の管理対象アプリとして、プロバイダーのワンクリック切り替え、MCP / Skills 同期、プロキシテイクオーバー、使用量統計をすべて利用できます。そして **Grok を Claude Code、Claude Desktop、Codex へ接続する**——xAI Grok アカウントで直接サインインする方法(デバイスコード認証、API key 不要、Grok サブスクリプションで動作。Codex 側には厳格ゲートウェイ互換レイヤーがあり codex 0.142+ でも動きます)と、xAI API key で接続する方法(Codex にはネイティブ Responses 直結プリセット、Claude Code はローカルルーティング経由)の両方に対応します。修正も重要です。**v3.17.0 で混入した Codex 使用量の二重計上を修正**し、アップグレード後にデータを自動再構築してダッシュボードの数字を実態に戻します。**codex 0.144.5+ がモデルカタログ起因で起動できない問題も修正**。Windows でのプロバイダー切り替え時にコンソールウィンドウが一瞬表示されたり UI が固まったりする問題も解消しました。診断ログは「起動のたびに消去」から、再起動をまたいで保持・サイズローテーション・全面的な秘密情報除去へ移行し、UI クラッシュも真っ白な画面ではなくディスクに記録が残ります。
**[English →](v3.18.0-en.md) | [中文版 →](v3.18.0-zh.md)**
---
## ハイライト:本リリースでできること
- **Grok BuildxAI の Grok CLI)を管理**Claude Code / Codex と同じように、Grok Build のプロバイダーを追加・インポート・ワンクリック切り替えできます。MCP サーバーと Skills は双方向同期、プロンプトは初回起動時に自動インポート、セッション管理と使用量ダッシュボードも完全対応。ローカルプロキシによるテイクオーバーで、独立したルーティング・failover・課金も利用できます。
- **Grok の接続はアカウントでも API Key でも**:サブスクリプションユーザーは「設定 → OAuth 認証センター」でデバイスコードによる xAI サインインを行い(複数アカウント対応)、Claude Code・Claude Desktop・Codex の 3 クライアントが API key なしで Grok サブスクリプションで動きます。従量課金ユーザーは xAI API key で接続——Codex には `api.x.ai` へネイティブ直結する「xAI (Grok)」プリセットがあり、Claude Code は本リリースの新ガイドに沿ってローカルルーティングで接続できます。既定モデルはいずれも `grok-4.5` です。
- **Codex の使用量を実態に戻す**v3.17.0 の fork / サブエージェント二重計上をパーサーレベルで根治しました。アップグレード後の初回起動でデータベースをバックアップして Codex 使用量を自動再構築し、使用量ページには手動の「Codex 使用量を再構築」ボタンも追加。なお初回起動時の履歴修復は**段階的に**進みます——数字がいったん減り、バックグラウンドの再インポートに伴って徐々に埋め戻されるのは想定どおりの動作です(「アップグレード時の注意」参照)。
- **codex CLI を安心してアップグレード**:codex 0.144.5 の厳格なモデルカタログ解析による「起動できない」問題を修正。生成カタログはパーサー必須フィールドを自動補完します。
- **Windows でスムーズに切り替え**:プロバイダー切り替えやテイクオーバーの切り替えで黒いコンソールウィンドウが一瞬表示されることがなくなり、UI が約 2 秒固まる問題も解消しました(フリーズ修正は全プラットフォームに効きます)。
- **ログの調査と共有をより安心に**:診断ログは再起動をまたいで保持(20 MB × 4 ローテーション)され、すべての出口で一律に秘密情報を除去——URL 内の認証情報、リクエスト / レスポンスボディ、機微なヘッダーはディスクに残りません。UI クラッシュはエラーカードと再読み込みボタンで受け止め、詳細をディスクへ記録します。
- **マルチターンの reasoning と並列ツール呼び出しが壊れない**:Responses↔Chat ブリッジで、reasoning の誤った付け先、並列ツール呼び出しの ID 消失・順序入れ替わり、tool schema が null の場合にリクエスト全体が拒否される問題の 3 種類を修正しました。
- **Kimi K3 を使う**Codex / Hermes / OpenClaw / OpenCode の Kimi オープンプラットフォームプリセットに K3(1M コンテキスト)を追加し、内蔵価格も登録。使用量が $0 と表示されなくなります。
---
## 利用ガイド
本リリースの新機能は、主にプロバイダープリセット、「設定 → OAuth 認証センター」、使用量ダッシュボードにあります。以下のドキュメントもあわせてご覧ください:
- **[xAI Grok アカウントサインイン(設定 → OAuth 認証センター)](../user-manual/ja/1-getting-started/1.5-settings.md)**:デバイスコードログインの流れ、複数アカウント管理、連携の境界について説明します。利用前に下記「リスク通知」のクライアント識別情報の開示を必ずお読みください。
- **[Claude Code で GPT モデルを使う(ローカルルーティングガイド)](../guides/claude-codex-routing-guide-ja.md)**(中国語 / 英語 / 日本語):本リリース追加のステップバイステップガイドです。Claude Code は常にローカルの `/v1/messages` ルートに対して Anthropic Messages を話し、ローカルプロキシが各リクエストを上流の Responses プロトコルへ変換します——ゲートウェイの API key、xAI のようなネイティブ Responses endpoint、ChatGPT サブスクリプションの Codex サービスのいずれにも使えます。
- **[Codex で Claude モデルを使う(ローカルルーティングガイド)](../guides/codex-claude-routing-guide-ja.md)**:本リリース追加の 3 言語ガイド。v3.17.0 の「ネイティブ Anthropic Messages 上流」と組み合わせ、`/v1/messages` のみを提供する Claude 系ゲートウェイへ Codex を接続します。
- **[使用量統計](../user-manual/ja/4-proxy/4.4-usage.md)**:使用量ダッシュボードのデータソースと集計方法を確認できます。本リリースでは二重計上を修正し、「Codex 使用量を再構築」メンテナンス操作を追加しました。
---
> [!WARNING]
>
> ## 唯一の公式チャネル(必ずお読みください)
>
> CC Switch は**完全に無料・オープンソース**のデスクトップアプリで、**ユーザーから料金を徴収することはありません**。本ソフトウェアは下記の公式チャネルからのみ入手してください:
>
> | チャネル | 唯一の公式 |
> | ------------ | ------------------------------------------------------------------------------ |
> | 公式サイト | **[ccswitch.io](https://ccswitch.io)** |
> | ソースコード | **[github.com/farion1231/cc-switch](https://github.com/farion1231/cc-switch)** |
> | ダウンロード | **[GitHub Releases](https://github.com/farion1231/cc-switch/releases)** |
> | 作者 | **[@farion1231](https://github.com/farion1231)** |
> | 偽サイト通報 | **[GitHub Issues](https://github.com/farion1231/cc-switch/issues)** |
>
> **料金請求・チャージ・認証情報の提供を求める「CC Switch」サイトやクライアントはすべて偽物です。** 支払いを誘導された場合は直ちに操作を中止し、GitHub Issues からご報告ください。
---
## 概要
CC Switch v3.18.0 の 2 本柱はどちらも xAI Grok を中心に据えています。1 つ目は **Grok Build の管理対象アプリ入り**です。xAI の Grok CLIlive 設定 `~/.grok/config.toml`)が、Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw、Hermes に並ぶ 8 番目の管理対象アプリになりました——プロバイダーの追加 / インポート / ワンクリック切り替え、MCP・Skills の双方向同期、ディープリンクインポート、独立したプリセットリスト、専用ルート名前空間つきのプロキシテイクオーバー。併設の「Grok 公式」エントリは公式ログイン状態の検出とインポートに対応し、CC Switch が公式認証情報に触れることはありません。2 つ目は **xAI Grok アカウントの OAuth サインイン**です。デバイスコード認証が API key を置き換え、ローカルプロキシがリクエストごとにアクセストークンを注入します。Claude Code / Claude Desktop 側では Anthropic Messages → xAI Responses の変換を行い、Codex 側は管理下 OAuth プリセットと専用互換レイヤーを備えます——codex 0.142+ が発する ChatGPT バックエンド固有の形(namespace ツール宣言や非公開フィールド)を決定的に平坦化・除去し、厳格に解析する xAI ゲートウェイが 422 を返さなくなります。API key ユーザーには、変換を一切通さない「xAI (Grok)」ネイティブ Responses 直結プリセットも用意しました。
正確性の面では、**v3.17.0 の Codex 使用量二重計上**を集中的に修正しました。fork / サブエージェントログ冒頭にある親スレッド履歴のリプレイを新規使用量として取り込まなくなり(パーサーは明示的な親識別子とトークン署名の照合のみを信頼)、アップグレード後に一度だけ使用量を自動再構築(schema v16)、使用量ページには手動再構築ボタンを追加。プロキシ側の使用量記録は冪等になり(同じレスポンスのリプレイが重複行を積み上げない)、大量セッションのインポート中に使用量ページが固まる問題も解消しました。**Codex 変換レイヤー**にはさらに 4 つの修正——tool schema の object 型への正規化、reasoning のターンをまたぐ前方アタッチ、ストリーミング並列ツール呼び出しの ID と順序の保持、codex 0.144.5+ が要求するカタログフィールドの補完。診断まわりも成熟し、ログは再起動をまたいで保持・サイズローテーション・全出口で秘密情報除去、UI クラッシュはエラーバウンダリで捕捉してディスクへ記録します。ほかにも Kimi K3 のプリセットと価格、OpenClaw プリセット価格の修正、SudoCode.us の復活、トレイの初回言語がシステムロケールに従う改善などを含みます。
**リリース日**: 2026-07-21
**Stats**: 52 commits | 217 files changed | +21,452 / -6,285 lines
---
## 追加機能
### Grok Build8 番目の管理対象アプリ
xAI の Grok CLIGrok Build、live 設定 `~/.grok/config.toml`)が CC Switch の第一級市民になりました。プロバイダーの追加 / インポート / ワンクリック切り替え(切り替え後は「Grok Build を再起動して適用」のトースト表示)、アプリ表示と設定ディレクトリ上書きの設定、セッション管理と使用量ダッシュボード対応、初回起動時のプロンプト自動インポート、`ccswitch://` ディープリンクによるプロバイダーインポート、そしてローカルプロキシテイクオーバー——専用の `/grokbuild/v1/responses` ルート名前空間、独立した failover キュー、アプリ別プロキシ設定を持ちます。転送は Codex の Responses 経路を再利用しますが、Codex のプロバイダー名前空間や circuit breaker の状態を共有することはありません。
MCP サーバーは Grok の `[mcp_servers]` テーブルと双方向同期し、方言の差異も吸収します。Grok は `command` / `url` からトランスポートを推定し `headers` フィールドを使うため、エクスポート時は明示的な `type` を取り除き `http_headers``headers` にリネームし、インポート時は逆方向に推定して戻します。Skills にも Grok Build の有効化フラグが付きました。
プリセットはあえて Codex のリストを流用**していません**(初期の実装では中国向け直結プロバイダーや Codex の既定モデルが Grok のフォームに混入していました)。Grok Build 専用に整理したリストとして、実際に Grok モデルを扱うアグリゲーターと中継サービスのみを収録し、既定モデルは `grok-4.5`(名前空間型ルーターでは `x-ai/grok-4.5`)に正規化しています。ツールパネルでの Grok インストールは xAI 公式インストーラー(`x.ai/cli/install.sh` / `install.ps1`)を優先し、npm パッケージ `@xai-official/grok` をフォールバックにします。ネイティブと確認されたインストールは `grok update` で自己更新し、npm インストールは npm 経由の更新を維持——自己更新はネイティブ検出が確定した場合にのみ動くため、別種のインストールを壊すことはありません。4 言語の UI 文言も同期済みです。([#5453](https://github.com/farion1231/cc-switch/pull/5453)
### Grok 公式ログイン:検出・インポート・保護
新しい「Grok 公式」プロバイダーエントリは、Grok CLI 内蔵の xAI OAuth ログインに対応します。選択すると接続フィールドが隠れ、空の `~/.grok/config.toml` を書き込みます。CC Switch が公式認証情報を保存したり触れたりすることはありません。live 設定の読み取り・バックアップ・公式状態の書き込みは構文レベルのみの TOML 検証に変更され、公式ログイン状態(空の設定)が正常に往復します。Grok が公式ログイン状態のときに「live 設定からインポート」すると、エラーではなく「Grok 公式を現在のプロバイダーに設定しました」となり、Codex と同じ挙動になります。公式状態の検出は意図的に手動インポートコマンドだけに配線されています——起動時の自動インポートは公式状態の設定を引き続き拒否するため、削除した「Grok 公式」エントリが次回起動時に復活することはありません。公式ログイン設定へのプロキシテイクオーバーは自動的にスキップされ、手動操作でも明確に拒否されます。既存の「公式プロバイダーはプロキシしない」方針と一貫しています。
### xAI Grok アカウントでサインイン:Claude Code と Claude Desktop
Claude Code と Claude Desktop に「xAI (Grok)」プリセットが加わり、API key の代わりに OAuth デバイスコードログインを使います。リクエストはローカルプロキシを通り、Anthropic Messages → xAI Responses API の変換とリクエストごとのアクセストークン注入が行われます。既定モデルは全 tier で `grok-4.5` です(Claude Desktop プリセットは `claude-*` 形式のロール ID を上流の `grok-4.5` にマッピングし、Desktop のサードパーティモデル検証を通過させます)。
「設定 → OAuth 認証センター」に xAI セクションが追加されました。デバイスコードログイン(コピー可能なユーザーコード、確認リンク、待機 / キャンセル / 再試行)、複数アカウントと既定アカウントの選択、アカウント別の削除、再認証バッジ——refresh token が失効したアカウントは消えるのではなく「期限切れ」として表示され続け、認証状態は 15 秒ごとに再取得されるため、サーバー側の失効も自然に表面化します。
連携の境界は固定されています。フォームの endpoint / 形式フィールドをどう変えても上流は常に `https://api.x.ai/v1/responses`Responses 形式)。OAuth endpoint は OIDC discovery で解決しますが https の `auth.x.ai` のみに厳格検証。refresh token は `~/.cc-switch/xai_oauth_auth.json`Unix ではパーミッション `0600`、access token はメモリのみ)。OAuth のエラーボディはエラーメッセージにもログにも決して入りません。`grok-4.5` の価格(100 万 token あたり入力 $2 / 出力 $6 / キャッシュ読み取り $0.50)も登録され、使用量が $0 と記録されなくなります。既存データベースは次回起動時に自動で行を取り込みます。4 言語対応。利用前に「リスク通知」のクライアント識別情報の開示をお読みください。
OAuth を使わず、従量課金の xAI API key しかない場合でも、Claude Code へ接続できます。xAI の API endpoint は標準の Responses プロトコルなので、通常の Responses プロバイダーとして追加してください——カスタムプロバイダーに `https://api.x.ai/v1` と API key を設定し、上流形式に Responses を選ぶと、ローカルルーティングが Anthropic Messages ↔ Responses を変換します。[Claude Code で GPT モデルを使う](../guides/claude-codex-routing-guide-ja.md)ガイドと同じ手順です。Codex 側には既製の API key プリセットがあります(次節参照)。
### Codex から xAI へ直結:管理下 OAuth と API Key ネイティブの 2 プリセット
Codex には xAI へ直結する 2 つの道があります——Grok サブスクリプションなら管理下 OAuth、API key ならネイティブ直結です:
- **「xAI (Grok) OAuth」管理下プリセット**Codex を Grok サブスクリプションで動かします。フォームは key / endpoint / 形式フィールドを隠してアカウント選択を表示し、「モデルを取得」はサインイン済みアカウントで実行。プロバイダーはネイティブ Responses に固定され、base URL とリクエストごとのトークンはプロキシが強制します——編集しても無視されるため、管理下ルートを別の宛先へ向けることはできません。codex 0.142+ は ChatGPT バックエンド固有のリクエスト形を発します(`type:"namespace"` ツール宣言は xAI の厳格パーサーで即 422。ほかに `prompt_cache_retention``safety_identifier``external_web_access``additional_tools` キャリア、grok-4.5 が対応しないサンプリングパラメータ)。そのため OAuth ルートはネイティブパススルーの上に互換レイヤーを重ねます。namespace ツールはトップレベルの function ツールへ平坦化(Chat 経路と同じ sha256 切り詰め命名)し、レスポンス側ではストリーミング・非ストリーミングの両方で namespace 形へ復元、未対応フィールドは除去——すべて決定的なフィールド削除 / 構造変換であり、意味的な書き換えは一切ないため、プロンプトキャッシュの前方一致は安定を保ちます。このレイヤーは xAI OAuth プロバイダー型だけにゲートされ、他のプロバイダーのトラフィックには触れません。
- **「xAI (Grok)」API Key プリセット**`api.x.ai/v1` のネイティブ Responses に直結し、500K コンテキストの `grok-4.5` カタログエントリを内蔵します。このプリセットには上記の xAI 専用互換変換は**適用されません**——codex 0.142+ の API key ユーザーは依然 xAI の厳格パーサーに当たる可能性があり、完全互換の道は OAuth プリセットです。
xAI OAuth のトークンエラーは再試行不能に分類されるため、failover が会話を別の Grok アカウントへ静かに移すことはありません。
### UI クラッシュの捕捉:エラーのディスク保存と再読み込み画面
React のエラーバウンダリが UI 全体(データベース復旧画面を含む)を包むようになりました。レンダラーがクラッシュすると、真っ白なウィンドウの代わりに「インターフェースでエラーが発生しました」カードと再読み込みボタンが表示され、グローバルの `error` / `unhandledrejection` ハンドラーがレンダラーのエラーをディスクへ永続化します——以前は JS クラッシュの証拠がディスクに一切残りませんでした。フロントエンドが書き出すログはすべて二層の秘密情報除去を通ります。構造化シリアライザーが機微なプロパティ名(`tokens` / `apiKeys` / `credentials` の変種を正規化して照合し、ネストされたオブジェクトを含む値全体を隠す)と値の形(トークン接頭辞、PEM ヘッダー、高エントロピーな不透明文字列)で除去し、次に唯一のテキスト出口の順序付き正規表現チェーンが URL クエリ値と認証情報、認証ヘッダーとスキーム、名前付きシークレットコンテナ(二重エンコードされた JSON も対象)をカバーします。文字列として届いた JSON は再パースして構造的に除去し、過大な構造化入力は切り詰めではなく全体を破棄します——切り詰められた JSON 文字列は弱いテキスト正規表現にフォールバックして漏えいの恐れがあるためです。設定のトグル名も実態に合わせて変更しました。「アプリ診断ログ」(cc-switch.log)と、プロキシの「リクエスト使用量を記録」(統計データベースであり、もともとテキストログではありません)です。4 言語同期。
### 「Codex 使用量を再構築」メンテナンス操作
使用量ダッシュボードのメンテナンスに「Codex 使用量を再構築」が加わりました。データベースをバックアップした後、`codex_session` ソースの明細行、対応する `_codex_session` 日次集計、Codex 同期カーソルだけを消去し、修正済みパーサーで全 rollout ファイルを最初から再インポートします——下記の二重計上バグで膨張したデータベースの復旧経路であり、親ログが復元された保留 fork ファイルの再試行経路でもあります。手動再構築は事前バックアップを書き込めない場合にハードフェイルします(自動移行版は警告のみ。アップグレード後にバックアップディレクトリ書き込み不能で起動がブロックされる方が悪い結果だからです)。バックアップ → リセット → 再インポートの全工程はセッション同期ロックを保持し、60 秒間隔のバックグラウンド同期が消去と交錯できません。完了時にはちょうど 1 回のフロントエンド更新通知を必ず送ります——再インポートが 0 行やエラーで終わった場合も含めて——のでダッシュボードがリセット前の数字のまま止まることはありません。カーソルの掃除はパスの形(`sessions` / `archived_sessions` セグメント配下の `rollout-{uuid}` ファイル名)だけで照合するため、旧 `CODEX_HOME` 配下に記録されたカーソルも掃除できます。4 言語同期。
### セッションインポートの可観測性:保留ファイルと重複疑い
セッション同期の結果に `filesScanned``deferredFiles``suspectedDuplicates` が加わりました。`deferredFiles` は、親ログの欠落や親マーカーの矛盾がある fork rollout をカーソルを書かずに保留し、後の同期や手動再構築で再試行するものです——推測でインポートしません。`suspectedDuplicates` は、挿入後に各行を同一フィンガープリントの既存行と照合(`idx_request_logs_dedup_lookup_expr` 式インデックス経由)し、ヒットごとに警告を記録します。二重計上バグが将来再発しても、総量を静かに膨らませるのではなく、ログの中で自ら名乗り出ます。
### Kimi K3 のプリセットと内蔵価格
Codex / Hermes / OpenClaw / OpenCode の Kimi オープンプラットフォームプリセットに Kimi K3(1M コンテキストウィンドウ)を追加しました。K2.7 Code の後ろに追加したため、既存の既定モデルの挙動は変わりません。内蔵価格テーブルには `kimi-k3`(公式表示価格:100 万 token あたり入力 $3 / 出力 $15 / キャッシュ読み取り $0.30)と裸の `k3` エイリアスを追加——Kimi For Coding サブスクリプションはモデルを短縮 id `k3` で報告するため、これがないとどの価格行にも一致しません(既存の `hunyuan-hy3` / `hy3` ペアと同じ前例です)。既存データベースは次回起動時に両行を自動で取り込み、ユーザーが編集した価格には触れません。
### SudoCode.us が SudoCode.chat と並んで復活
たまたま「SudoCode」という名前を共有する無関係な 2 社が、別々のプリセットになりました。スポンサーは「SudoCode.chat」に改名し、以前その場で置き換えられていた「SudoCode.us」が元の endpoint・モデル・アイコンとともに復帰。Hermes の slug も区別され、追記型の `~/.hermes/config.yaml` の中で両者が共存できます。新しい Grok Build プリセットリストを含めると、SudoCode.chat は 7 アプリ、SudoCode.us は全 8 アプリに収録されています。
---
## 変更
### 診断ログ:再起動をまたいで保持、サイズローテーション、秘密情報は記録しない
`cc-switch.log` は起動のたびに消去されなくなりました——クラッシュを説明できたはずのログが、アプリを開き直したときには消えていたのです。代わりに 20 MB でローテーションし、アーカイブを 4 つ保持します(上限約 100 MB。以前は単一ファイルが 1 GB まで膨張し得ました)。これまで無制限だった `crash.log` は 5 MB ローテーション・アーカイブ 2 つになり、チェック / ローテーション / 追記の一連の処理を 1 つのロックで包んで、並行 panic がアーカイブを失わないようにしました。
ログの永続化は、秘密情報の平文がそのまま露出リスクになることを意味します(ユーザーはこれらのファイルを公開 issue に添付します)。そこで同じ変更で、バックエンドのすべてのログ出口を洗浄しました。上流 URL は userinfo / query / fragment を取り除いた形でのみ記録(置換できる既知のシークレットがない場合は origin のみ。パスに認証情報が埋まっている可能性があるため)。リクエストとレスポンスのボディは一切記録せず、バイト数、短いハッシュ、または安全な分類(`sse` / `html` / `json-like` / `binary-or-encoded` など)に置き換え——変換デバッグに必要なシグナルは残り、中身は残りません。レスポンスヘッダーは許可リスト方式(リスト外は名前のみ記録)。現在使用中のシークレット値(API key、access token)は、それを含むあらゆる記録 URL から置換されます。MCP カスタムフィールドの値は省略。ログプラグインの登録は早められ(アップデーターや起動時の障害が診断可能に)、永続化されたログレベルはデータベースが開き次第適用され、障害時は Info へ安全側に倒れます。「診断ログを有効化」スイッチはフロントエンド発のログ書き込みも制御するようになりました。**アップグレード前の古いログファイルは遡って洗浄されません**——「アップグレード時の注意」を参照してください。
### プリセット選択:スポンサーをグループ化し、残りは名前順
プリセット選択の既定順序が 4 層になりました。公式が最初、次に主要パートナー、その後にスポンサープリセット(README のスポンサー表と同じ順序。プリセットファイルも物理的に並べ替えて揃えました)、最後に残りすべてをファイル順ではなく表示名のアルファベット順で並べます。複数の層に該当するエントリは最も早い層にだけ現れ、重複表示はありません。
### プリセットの「API Key を取得」リンクを更新
RunAPI、ClaudeCN、ZetaAPI、APINebula プリセットの key 申請リンクを、各社の現在の登録 / 紹介ページに更新しました(ClaudeCN はドメインも移転:claudecn.top → claudecn.ai)。紹介タグはこれらのリンクと README に限定され、公式サイトリンクと API endpoint には手を付けていません。
---
## 修正
### Codex fork / サブエージェントのリプレイ済み親履歴を新規使用量として計上しない(v3.17.0 の二重計上を根治)
v3.17.0 の使用量膨張を修正します。Codex タスクの fork やコピーモードでのサブエージェント生成で、親会話の token 履歴が新規使用量として再計上されていました——1 日の使用量が数十億 token 跳ね上がる、親子でバイト単位に同一の行が並ぶ、空の fork が消費していない使用量を背負う、といった報告がありました。fork / サブエージェントの rollout ファイルは冒頭で親スレッドの履歴をリプレイします。旧パーサーは引き継ぎ境界をヒューリスティックに探していました(最初の `thread_settings_applied` イベント、オブジェクト形の `subagent` ソースマーカー)。親自身の設定変更がリプレイに現れると境界が早すぎる位置に落ち、現行の文字列形ソースマーカーはまったく認識されず、リプレイされた親履歴が丸ごとインポートされていました。新パーサーは明示的な親識別子だけを信頼します——子の `session_meta``forked_from_id` または `source.subagent.thread_spawn.parent_thread_id`。両者が矛盾する場合はファイルを保留します。スレッドの識別は rollout ファイル名の UUID に固定し、親 rollout 自身の fork 前 token カウント系列を読み込み、トークン署名の照合で子のリプレイ済みプレフィックスを取り除きます。リプレイイベントは累積ベースラインの復元にのみ使われ、行として挿入されることはありません。リプレイ履歴を持たないサブエージェントログは実使用量として計上されるようになり、実際のサブエージェント消費がリプレイ疑いとしてスキップされる逆方向の計上漏れも同時に修正しました。([#5335](https://github.com/farion1231/cc-switch/issues/5335)、[#5433](https://github.com/farion1231/cc-switch/issues/5433)、[#5381](https://github.com/farion1231/cc-switch/issues/5381)
### プロキシ使用量記録を冪等に:レスポンス由来の安定キー
終端の使用量イベントにメッセージ id がない場合(ローカルプロキシ経由の Codex `/responses` トラフィックでは通常)、重複排除キーはランダム UUID にフォールバックしていました——同じ上流レスポンスの再試行 / リプレイのたびに新しいキーが作られ、`INSERT OR REPLACE` が毎回新しい行を積み上げます。ある報告者のデータベースには同じ使用量の組み合わせが 2,078 回入っていました。パーサーはキーをレスポンスのエンベロープ自体から取るようになりました——Codex `response.completed` イベントの `response.id``response.created` の id は破棄)、Chat Completions の `chatcmpl` id、Gemini の `responseId`——スコープは `session:{app_type}:{provider_id}:{id}`。failover で同じレスポンスが別プロバイダーに再生された場合もプロバイダーごとに 1 回ずつ課金され、プロバイダー間で衝突しません(Claude は裸の `session:{id}` 形を維持し、プロキシ行はセッションログのインポートと収束し続けます)。エンベロープ id がまったくない場合のフォールバックは、ランダム UUID ではなくレスポンスの使用量セマンティクスに対する決定的 SHA-256 です——同一のリプレイが同じキーに衝突しなければ重複排除は成立しません。最終的なデータベース書き込みも、無条件の REPLACE から重複排除ウィンドウ内の「存在しなければ挿入」に変わりました。([#5496](https://github.com/farion1231/cc-switch/issues/5496)
### 大量セッションインポート中に使用量ページが固まらない
大きなインポートの実行中に使用量ページを開くと、UI 全体が固まることがありました。行を挿入するたびに更新通知が飛び、通知のたびにフロントエンドが約 10 個の使用量クエリをすべて再実行し、それらのクエリが、数十 MB の rollout ファイルを 1 行ずつ解析中のインポーターと唯一のデータベース接続を奪い合う——重複で膨張したデータベースでは三者が互いに増幅し合っていました。セッション同期は 1 パス完了ごとに 1 回だけ通知するようになり、すべてのセッションインポーターはシングルフライトロックの後ろで直列化され(手動の「今すぐ同期」は実行中のパスと競争せず、後ろに並びます)、ブロッキングな解析処理は専用のブロッキングスレッドへ移されて UI コマンドを駆動する非同期ランタイムを飢えさせなくなり、60 秒間隔のバックグラウンド実行は取りこぼし分をまとめて実行せずスキップします。
### codex 0.144.5+ が CC Switch 生成のモデルカタログで起動不能にならない
codex ≥ 0.144.5 は外部モデルカタログを厳格に解析し、エントリに `supports_reasoning_summaries` がないとファイル全体を拒否します——Codex CLI もデスクトップアプリも起動できず、生成カタログを削除しても、プロバイダーを保存するたびに同じ形で再生成されるため解決しませんでした。根本原因は、CC Switch がカタログのテンプレートをマシン共有の `models_cache.json` から複製していることです。そのフィールド構成は最後に書き込んだ codex プロセス次第で、共存する旧版 codex が新パーサー必須のフィールドを持たない形でキャッシュを書き換え続けていました。生成カタログは、同梱の静的テンプレートからパーサー必須フィールドを補完するようになりました。補完は欠けている場合のみで(動的な値が常に優先)、「欠落 = パーサー既定値」のセマンティクスを保つべきオプションの能力フィールドは意図的に補完しません。
### Windows:プロバイダー切り替えでコンソールが一瞬表示されず、固まらない
Windows でプロバイダーを切り替えたりテイクオーバーを切り替えたりすると、コンソールウィンドウが一瞬表示され、UI が最大約 2 秒固まっていました。原因は 3 つ、修正も 3 つです。`codex debug models --bundled` プローブは `cmd.exe` 経由で `codex.cmd` を起動しますが、GUI サブシステムのアプリではこれが独自のコンソールを生成します——子プロセスは `CREATE_NO_WINDOW` で作成するようにしました。モデルカタログのテンプレートは切り替えのたびに再生成されていました——初回の成功ロード後にプロセス全体でキャッシュし(失敗は再試行可能のままなので、初回プローブの失敗がキャッシュを汚しません)、Codex CLI の起動はアプリ実行ごとに最大 1 回になりました。`switch_provider` はメインスレッド上の同期コマンドでした——非同期化して実処理をブロッキングスレッドに移し、アプリ別の切り替えロックによる直列化は維持しています。フリーズ修正は全プラットフォームに効き、コンソール表示の修正は Windows 固有です。
### tool schema が null / 欠落 / union 型でも厳格上流に拒否されない
Codex の組み込みツール(`codex_app__automation_update` など)は `parameters: null`(または `type: null`)を宣言しますが、DeepSeek のような厳格な OpenAI 互換上流はリクエスト全体を 400 で拒否し、プロキシ経由のツール利用セッションが死んでいました。Responses→Chat ブリッジはすべてのツールの parameters を `type:"object"` schema に正規化します。null または欠落(ネスト形の欠落を含む)は `{"type":"object","properties":{}}` に、object 以外の `type``type: null` を含む)はその場で `"object"` に修正、トップレベルの `oneOf` union schema にはルートの `type:"object"` を追加して分岐はそのまま保持します。同じ object 型の保証を Codex→Anthropic ツール経路の `input_schema` にも拡張しました。既存の `properties` / `required` を捨てることはありません。([#4706](https://github.com/farion1231/cc-switch/pull/4706)、[#5315](https://github.com/farion1231/cc-switch/pull/5315)、#4705#4783 を修正)
### 推論モデルがマルチターンの Codex Chat 会話で思考を失わない
推論モデル(kimi-k2-thinking など)をプロキシの Responses→Chat ブリッジで使うと、マルチターン履歴が思考を壊していました。各ターンの `reasoning` 項目が**前の**アシスタントメッセージの末尾に貼り付けられ、直後のアシスタントターンに `reasoning_content` がなくなる——モデルが会話の途中で目に見えて壊れていました。Responses のセマンティクスでは reasoning は属するメッセージの**前**に置かれます。ブリッジは reasoning を、その後に続くアシスタントメッセージまたはツール呼び出しへ前方アタッチするようになりました。本物の末尾 reasoning だけが、確定した末尾(入力の終端、またはユーザーメッセージのようなターン境界——以前はここで静かに破棄されていました)で後方アタッチされ、既に埋め込まれた reasoning があれば追記します。保留中の reasoning は境界で必ず消費されるため、ユーザーターンを越えて後のアシスタントメッセージへ漏れることはありません。([#5508](https://github.com/farion1231/cc-switch/pull/5508)
### ストリーミング並列ツール呼び出しの ID と順序を保持
Chat→Responses ストリーミングブリッジの 2 つのバグが、チャンクをまたいで識別子が分割される上流からの並列ツール呼び出しを壊していました。空の `id` を持つ継続デルタが本物の `call_id` を上書きし(Codex クライアントは `call_id:""` を見てツール結果を呼び出しに対応付けられません)、ツール呼び出しは個別に準備でき次第送出されていたため、名前が先に届いた後方インデックスが前方インデックスを追い越し、並列呼び出しの順序が入れ替わっていました。空の id は無視されるようになり、送出は連続インデックスゲートを通って Chat の `index` 順で厳密に解放され、まだ識別されていない前方インデックスを待ちます。ストリーム途中で偽の call id を合成することはありません(ストリーム終端の最終手段としてのみ。そこでも名前のない呼び出しは防御的にスキップし、疎なインデックスはそのまま送出します)。([#5310](https://github.com/farion1231/cc-switch/pull/5310)
### 管理下 OAuth プロバイダーを確実に「ローカルルーティング必要」と表示
「ルーティング必要」バッジと切り替え時の警告は、プロバイダーの API 形式から導出されていました。管理下 OAuth プロバイダー(Copilot、Codex OAuth、xAI)にとってこれは誤ったシグナルです——認証情報は上流形式に関係なくプロキシが注入するため、ネイティブ形式の管理下プロバイダーは警告を受けず、テイクオーバーなしで静かに失敗していました。ルーティングの要否は単一の共有述語で決まるようになりました。公式プロバイダーは決してルーティングを必要とせず、管理下 OAuth プロバイダーは常に必要とし、形式ベースのルールは残りのケースにのみ適用されます。切り替え時のゲートもアプリごとに正しい準備状態を確認します。ほとんどのアプリはアプリ別テイクオーバー状態(旧ゲートはグローバルなプロキシ実行フラグだけを見ており、「プロキシは動いているが当該アプリはテイクオーバーされていない」を見逃していました)、Claude Desktop は引き続きプロキシプロセス自体を監視します——バックエンドのテイクオーバー状態に Claude Desktop のフィールドがないため、一律のアプリ別ゲートでは Desktop が永遠に警告を出し続けます。Claude Desktop のプロバイダーフォームは、xAI だけでなくすべての管理下 OAuth 型でプロキシモードを強制し、モデルマッピングトグルをロックします。4 言語同期。
### Node が nvm / fnm / mise にある場合でもツール更新が動く
アンカー付き npm の更新・修復コマンドは npm を絶対パスで呼び出しますが、npm のランチャーは `#!/usr/bin/env node` shebang で PATH から `node` を解決します——GUI 起動のアプリはシステム PATH しか継承せず、バージョンマネージャーのディレクトリを含まないため、nvm / fnm / mise でインストールしたツールの更新は静かに失敗していました。すべてのアンカー付き npm 呼び出しは、npm 自身の隣にある `bin` ディレクトリを PATH の先頭に追加するようになり、npm とその shebang が同じ Node インストールを解決します。Codex の自己修復(アンインストール + 再インストール)経路もカバーしています。
### 削除した既定 Skill リポジトリが復活しない
既定の Skill リポジトリは、起動のたびに「欠けている既定を補充」する処理で再シードされ、削除した既定リポジトリが次回起動時に静かに戻っていました。シードはデータベースごとに 1 回だけになり、設定フラグで記録されます。アップグレード時に既にリポジトリを持つデータベースは再シードなしでフラグが立つため、既存の選択には触れません。([#5356](https://github.com/farion1231/cc-switch/pull/5356)
### トレイの初回言語がシステムロケールに従う
設定でまだ言語を選んでいない場合、トレイメニューは簡体字中国語に固定されていました——英語 / 日本語 / 繁体字中国語のシステムでは、メイン UI は正しく OS ロケールに従うのにトレイだけが食い違い、ユーザーが一度言語を切り替えるまで続いていました。トレイはフロントエンドと同じ優先順位で OS ロケールから初回言語を導出するようになりました(`zh-TW` / `zh-HK` / `zh-Hant` → 繁体字中国語を含む)。明示的に選んだ言語が常に優先され、ロケールを読めない場合は従来どおり中国語へフォールバックします。([#4355](https://github.com/farion1231/cc-switch/pull/4355)
### インポート失敗時に実際のエラーを表示しリストを更新
「live 設定からインポート」が失敗するたびに空のエラートーストが出ていました。Tauri の `invoke` はバックエンドのエラー**文字列**で reject するのに、ハンドラーがその上の `.message` を読んでいたためです。バックエンドの実際のメッセージを表示するようになり(ローカライズされた汎用フォールバック付き)、失敗時にもプロバイダーリストを更新するため、エラー前にコミットされた副作用がすぐ見えます。
### OpenClaw プリセットのモデルコストを公式表示価格に修正
OpenClaw プリセットの 15 エントリが、誤った単位や未換算の通貨でコスト値を持っていました——`cost` フィールドは 100 万 token あたりの USD ですが、たとえば `glm-5.1``0.001/0.001`(約 1000 分の 1 に過小評価され、使用量のコストがほぼ 0 と表示)、`deepseek-v4-pro` は未換算の人民元値(過大評価)でした。全エントリを $/M の公式表示価格に修正。サブスクリプションプランや無料 tier の endpoint も意図的に表示価格を示し、プランユーザーが自分の使用量の標準価値を見られるようにしています。今後プリセットから作成するプロバイダーには修正値が入り、作成済みのプロバイダーは作成時の設定を保持します。
### UI の小さな修正
- **AiHubMix アイコン**Codex アプリの AiHubMix プリセットだけがブランドアイコンのフィールドを欠き、汎用アイコンで表示されていました。他のアプリと揃いました。
- **欠落していた 2 つのロケールキーを補完**:Codex の「Anthropic Messages 形式のためルーティングが必要」トーストの理由部分が、ローカライズ済みの文の中で中国語のまま表示されていました(`proxyReasonAnthropicMessages` がどの言語ファイルにも存在しなかったため)。プロバイダーフォームの key 状態読み込みラベルも 4 月からハードコードの既定値のみでした。両方とも zh / en / ja / zh-TW に揃いました。
---
## ドキュメント
### Codex ↔ Claude 双方向ルーティングガイド
「Codex クライアントで Claude モデルを使う」「Claude Code クライアントで Responses プロバイダーを使う」の両方向が、2 つの新ガイドで揃いました:
- **[Codex で Claude モデルを使う](../guides/codex-claude-routing-guide-ja.md)**(中 / 英 / 日 3 言語、スクリーンショット付き):v3.17.0 のネイティブ Anthropic Messages 上流と組み合わせ、Claude 系の `/v1/messages` ゲートウェイへ Codex を接続します。v3.17.0 のリリースノートからもリンクされています。
- **[Claude Code で GPT モデルを使う](../guides/claude-codex-routing-guide-ja.md)**(中 / 英 / 日 3 言語、スクリーンショット付き):Responses を話すプロバイダー(ゲートウェイの API key、または ChatGPT サブスクリプションの Codex サービス)で Claude Code を動かします——Claude Code は常にローカルの `/v1/messages` ルートへ Anthropic Messages を話し、プロキシが各リクエストを上流の Responses プロトコルへ変換します。
### README のスポンサー欄を更新
SubRouter が 4 言語 README のスポンサー表に加わりました。ピン留めの Kimi スポンサー文言は K3 に更新され、バナーは Moonshot CDN から配信されます。RunAPI の特典文言も更新し、スポンサー行の順序をアプリ内プリセット順に揃えました。
---
## アップグレード時の注意
### データベース自動移行と Codex 使用量の一回限りの再構築
v3.17.0 からのアップグレードでは、3 つの schema 移行(v13 → v16)が連続実行されます。v14 は Grok Build を受け入れるために `proxy_config` テーブルを再構築(既存のアプリ別プロキシ設定はすべて引き継ぎ、`grokbuild` 行を追加)。v15 は MCP サーバーテーブルと Skills テーブルに Grok Build 有効化列を追加。v16 は一回限りの Codex 使用量自動再構築をトリガーします——データベースをまず `backups/` 配下へバックアップし、`codex_session` データとカーソルをリセットし、その後通常の起動時同期が修正済みパーサーですべてを再インポートします。一般的なデータ量なら数秒。実測で最も重いデータセット(rollout ファイル 1,801 個 / 1.5 GB)は約 65 秒でした。以後の起動は従来どおり増分です。旧バージョンへ戻す可能性がある場合は、先に `~/.cc-switch/cc-switch.db` を自分でバックアップしてください。
**初回起動時の注意**:履歴の修復は**段階的に**完了します——再構築は起動時同期と共にバックグラウンドで進み、その間、使用量ダッシュボードの Codex 履歴の数字はいったんゼロになり、その後少しずつ埋め戻されます。これは想定どおりの動作で、データ損失ではありません。再構築後の合計は通常、アップグレード前より**小さく**なります。二重計上で膨らんでいた分が絞り出され、残ったものが実際の使用量です。
### 再構築の境界
- 再構築は rollout JSONL ファイルから使用量を再計算するため、**ソースログが既に削除された履歴は復元できません**。
- 親 rollout が欠けている fork ファイルは、推測でインポートせず保留として報告されます。親ログを復元してから「Codex 使用量を再構築」を実行すると取り込めます。
- 過去のプロキシソースの重複行は恒久的に残ります——移行が再構築するのはセッションソースのデータだけで、過去のプロキシ膨張を掃除する処理は存在しません。冪等ロガーが保証するのは、今後新しい重複が生まれないことだけです。
### 古いログファイルは遡って洗浄されません
本リリースから診断ログは起動時に消去されず、再起動をまたいで保持されます(ローテーション済みランタイムログ最大約 100 MB、クラッシュログ約 15 MB)。**旧バージョンが書き込んだログファイルは遡って洗浄されず**、API key、トークン、認証情報付き URL を含む可能性があります——公開の場で共有する前に、アップグレード前のログを確認してください。
### Grok Build のインストールは公式インストーラースクリプトを使用
Grok Build のインストール・再インストールは xAI 公式インストーラーを優先し、インストール時に `x.ai/cli/install.sh`Windows では `install.ps1`)を外部取得します。npm はフォールバックです。既存の npm インストールは引き続き npm 経由で更新されます。
### 内蔵価格の行を自動追加
新しい価格行(`grok-4.5``kimi-k3``k3`)は次回起動時に「存在しなければ挿入」で自動追加されます。ユーザーが編集した価格行が上書きされることはありません。
---
## リスク通知
### xAI Grok OAuth サインイン(本リリース新規——必ずお読みください)
本リリースの xAI Grok OAuth 連携は、**公式 Grok CLI 用に登録された公開 OAuth クライアント識別情報とスコープを再利用します**(`client_id b1a00492-073a-47ea-816f-4c329264a828`、scope に `grok-cli:access` を含む)。CC Switch 自身が登録したアプリケーション識別情報ではありません。xAI がこの利用方法を認めない可能性があり、**アカウントの制限や停止につながる恐れがあります——自己責任でご利用ください**。この機能は完全にオプトインです。xAI プロバイダーを追加しなければ、何も変わりません。初回ログイン時に `~/.cc-switch/xai_oauth_auth.json` を作成し(refresh token のみ保存、Unix ではパーミッション `0600`、access token はメモリのみ)、設定済みのアウトバウンドプロキシ経由で `auth.x.ai``api.x.ai` へ接続します。ローカルのコールバックポートはありません。
### 引き続き適用されるリバースプロキシ系の注意
**Codex OAuth リバースプロキシ**:ChatGPT サブスクリプションの Codex OAuth をリバースプロキシ経由で使用すると、OpenAI の利用規約に違反する可能性があります。詳細は [v3.13.0 release notes](v3.13.0-ja.md#-リスクに関する注意事項) を参照してください。
**サードパーティプロバイダーのルーティング**:CC Switch ローカルプロキシで Codex、Claude Desktop、Grok Build のリクエストを変換してサードパーティプロバイダーへ転送する場合、課金・コンプライアンス・データ保持に関する制約はプロバイダーごとに異なります。利用前に対象プロバイダーの利用規約を確認してください。
上記機能を有効化したユーザーは、関連するリスクを自ら負うものとします。CC Switch は、これらの機能の利用によって発生したアカウント制限、警告、サービス停止について責任を負いません。
---
## 謝辞
v3.18.0 で機能と修正を届けてくださった以下のコントリビューターに感謝します:
- [#5453](https://github.com/farion1231/cc-switch/pull/5453)Grok Build の第一級サポート(8 番目の管理対象アプリの中核実装)、@YUZHEthefool に感謝。
- [#5508](https://github.com/farion1231/cc-switch/pull/5508)Responses→Chat ブリッジの reasoning 前方アタッチ、@ka79376046 に感謝。
- [#5310](https://github.com/farion1231/cc-switch/pull/5310):ストリーミング並列ツール呼び出しの ID と順序の保持、@SaladDay に感謝。
- [#5315](https://github.com/farion1231/cc-switch/pull/5315)Codex ツール parameters の object schema への正規化、@Komikawayi に感謝。
- [#4706](https://github.com/farion1231/cc-switch/pull/4706):厳格な OpenAI 互換プロバイダー向けのツール型正規化、@Ryan2128 に感謝。
- [#5356](https://github.com/farion1231/cc-switch/pull/5356):削除した既定 Skill リポジトリの復活防止、@allenxu09 に感謝。
- [#4355](https://github.com/farion1231/cc-switch/pull/4355):トレイ初回言語のシステムロケール追従、@LaiYueTing に感謝。
- [#5138](https://github.com/farion1231/cc-switch/pull/5138):バックエンド CI の Linux / Windows / macOS 3 プラットフォーム展開、@zayokami に感謝。
Codex 使用量の異常、新しい codex の起動失敗、ツール呼び出しの問題を報告してくださったすべてのユーザーにも感謝します。本リリースの最も重要な修正は、こうした実際の利用場面からの再現情報をもとにしています。
---
## ダウンロードとインストール
[Releases](https://github.com/farion1231/cc-switch/releases/latest) から、お使いのシステムに対応するビルドをダウンロードしてください。
### システム要件
| システム | 最低バージョン | アーキテクチャ |
| -------- | ------------------------ | ----------------------------------- |
| Windows | Windows 10 以降 | x64 / ARM64 |
| macOS | macOS 12 (Monterey) 以降 | Intel (x64) / Apple Silicon (arm64) |
| Linux | 下表を参照 | x64 / ARM64 |
### Windows
| ファイル | 説明 |
| ---------------------------------------- | -------------------------------------------- |
| `CC-Switch-v3.18.0-Windows.msi` | **推奨** - 自動更新対応の MSI インストーラー |
| `CC-Switch-v3.18.0-Windows-Portable.zip` | ポータブル版、展開してそのまま実行できます |
Windows ARM64 デバイスでは、ファイル名に `arm64` が含まれる対応する成果物を選択してください。
### macOS
| ファイル | 説明 |
| -------------------------------- | ------------------------------------------------------ |
| `CC-Switch-v3.18.0-macOS.dmg` | **推奨** - DMG インストーラー、Applications へドラッグ |
| `CC-Switch-v3.18.0-macOS.zip` | 展開して Applications へドラッグ、Universal Binary |
| `CC-Switch-v3.18.0-macOS.tar.gz` | Homebrew インストールと自動更新用 |
Homebrew インストール:
```bash
brew install --cask cc-switch
```
更新:
```bash
brew upgrade --cask cc-switch
```
### Linux
Linux アセットは **x86_64****ARM64**`aarch64`)の両方を提供します。ファイル名のアーキテクチャ識別子を、マシンの `uname -m` 出力に合わせて選択してください:
- `CC-Switch-v3.18.0-Linux-x86_64.AppImage` / `.deb` / `.rpm`
- `CC-Switch-v3.18.0-Linux-arm64.AppImage` / `.deb` / `.rpm`
| ディストリビューション | 推奨形式 | インストール方法 |
| --------------------------------------- | ----------- | -------------------------------------------------------------------------- |
| Ubuntu / Debian / Linux Mint / Pop!\_OS | `.deb` | `sudo dpkg -i CC-Switch-*.deb` または `sudo apt install ./CC-Switch-*.deb` |
| Fedora / RHEL / CentOS / Rocky Linux | `.rpm` | `sudo rpm -i CC-Switch-*.rpm` または `sudo dnf install ./CC-Switch-*.rpm` |
| openSUSE | `.rpm` | `sudo zypper install ./CC-Switch-*.rpm` |
| Arch Linux / Manjaro | `.AppImage` | 実行権限を付与して直接実行、または AUR を利用 |
| その他 / 不明な場合 | `.AppImage` | `chmod +x CC-Switch-*.AppImage && ./CC-Switch-*.AppImage` |
+331
View File
@@ -0,0 +1,331 @@
# CC Switch v3.18.0
> 这一版你可以做两件全新的事:**把 xAI 的 Grok CLIGrok Build)交给 CC Switch 管理**——它成为第八个受管应用,供应商一键切换、MCP / Skills 同步、代理接管与用量统计一应俱全;以及**把 Grok 接进 Claude Code、Claude Desktop 和 Codex**——既可以直接用 xAI Grok 账号登录(设备码授权、无需 API Key,跑你的 Grok 订阅,Codex 侧自带严格网关兼容层,codex 0.142+ 也能跑通),也可以用 xAI API Key 接入(Codex 有原生 Responses 直连预设,Claude Code 可走本地路由)。同样重要的是一波修复:v3.17.0 引入的 **Codex 用量双计已修**,升级后自动重建数据,看板数字恢复真实;**codex 0.144.5+ 因模型目录无法启动的问题已修**;Windows 上切换供应商不再闪黑窗、不再卡住界面。诊断日志也从「每次启动清空」变为跨重启持久保留、按大小轮转、全面脱敏,界面崩溃会落盘留证而不再只剩一片白屏。
**[English →](v3.18.0-en.md) | [日本語版 →](v3.18.0-ja.md)**
---
## 重点内容:你现在可以
- **管理 Grok BuildxAI 的 Grok CLI**:像管理 Claude Code / Codex 一样添加、导入、一键切换 Grok Build 的供应商;MCP 服务器与 Skills 双向同步、提示词首启自动导入、会话管理与用量看板全覆盖;还可以走本地代理接管,获得独立的路由、failover 与计费。
- **把 Grok 接进 Claude Code / Claude Desktop / Codex——账号登录与 API Key 双路径**:订阅用户在「设置 → OAuth 授权中心」用设备码完成 xAI 账号登录(支持多账号),三个客户端直接跑你的 Grok 订阅、全程无需 API Key;按量付费用户则用 xAI API Key 接入——Codex 有现成的「xAI (Grok)」预设原生直连 `api.x.ai`Claude Code 可按本版新攻略走本地路由接入。默认模型均为 `grok-4.5`
- **把 Codex 的用量数字修回真实值**v3.17.0 的 fork / 子代理双计问题已在解析器层根治;升级后首次启动自动备份并重建 Codex 用量,用量页里也新增了手动「重建 Codex 用量」按钮。注意首次启动时历史记录是**逐渐**修复的——看板数字先变少、再随后台重导逐步回填,属预期行为(见「升级提醒」)。
- **放心升级 codex CLI**codex 0.144.5 起严格解析模型目录导致的「无法启动」已修复,生成目录会自动补齐解析器必需字段。
- **在 Windows 上顺滑切换**:切换供应商 / 开关接管不再闪过黑色控制台窗口,也不再卡住界面约 2 秒(卡顿修复对全平台生效)。
- **更放心地排查与分享日志**:诊断日志跨重启保留(20 MB × 4 轮转)、所有出口统一脱敏——URL 凭据、请求响应体、敏感请求头都不会再落盘;界面崩溃有错误卡片和重载按钮,错误详情写入磁盘。
- **多轮重推理、并行工具调用不再翻车**:Responses↔Chat 桥修复了推理内容错挂、并行工具调用 ID 丢失 / 乱序、工具 schema 为 null 被严格上游整单拒绝三类问题。
- **用上 Kimi K3**Codex / Hermes / OpenClaw / OpenCode 的 Kimi 开放平台预设加入 K3(1M 上下文),内置定价同步入库,用量不再显示 $0。
---
## 使用攻略
本版新能力主要落在供应商预设、「设置 → OAuth 授权中心」与用量看板里,建议结合以下文档了解:
- **[xAI Grok 账号登录(设置 → OAuth 授权中心)](../user-manual/zh/1-getting-started/1.5-settings.md)**:设备码登录流程、多账号管理与集成边界说明;使用前请先阅读下方「风险提示」中的客户端身份披露。
- **[在 Claude Code 中使用 GPT 模型(本地路由攻略)](../guides/claude-codex-routing-guide-zh.md)**:本版新增的分步攻略,现已中 / 英 / 日三语齐全。Claude Code 始终对本地 `/v1/messages` 路由说 Anthropic Messages 协议,由本地代理把每个请求转换成上游的 Responses 协议——网关 API Key、xAI 这类原生 Responses 端点,或 ChatGPT 订阅的 Codex 服务都适用。
- **[在 Codex 中使用 Claude 模型(本地路由攻略)](../guides/codex-claude-routing-guide-zh.md)**:本版新增的三语分步攻略,配合 v3.17.0 的「原生 Anthropic Messages 上游」功能,把 Codex 接到任何只提供 `/v1/messages` 的 Claude 系网关。
- **[用量统计](../user-manual/zh/4-proxy/4.4-usage.md)**:了解用量看板的数据来源与统计口径。本版修复用量双计并新增「重建 Codex 用量」维护操作。
---
> [!WARNING]
>
> ## 唯一官方渠道声明(请务必阅读)
>
> CC Switch 是**完全免费、开源**的桌面应用,**不会向用户收取任何费用**。请仅通过下列官方渠道获取本软件:
>
> | 类别 | 唯一官方 |
> | -------- | ------------------------------------------------------------------------------ |
> | 官网 | **[ccswitch.io](https://ccswitch.io)** |
> | 源码 | **[github.com/farion1231/cc-switch](https://github.com/farion1231/cc-switch)** |
> | 下载 | **[GitHub Releases](https://github.com/farion1231/cc-switch/releases)** |
> | 作者 | **[@farion1231](https://github.com/farion1231)** |
> | 举报山寨 | **[GitHub Issues](https://github.com/farion1231/cc-switch/issues)** |
>
> **任何向你收费、要求充值、或索取登录凭据的"CC Switch"网站或客户端均为假冒**。如果你被诱导支付了费用,请立即停止操作并通过 GitHub Issues 反馈。
---
## 概览
CC Switch v3.18.0 的两条主线都围绕 xAI Grok。第一条是 **Grok Build 加入受管应用**xAI 的 Grok CLIlive 配置 `~/.grok/config.toml`)成为与 Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw、Hermes 并列的第八个受管应用——供应商添加 / 导入 / 一键切换、MCP 与 Skills 双向同步、深链导入、独立预设列表,以及带专属路由命名空间的代理接管;配套的「Grok 官方」条目支持官方登录态识别与导入,CC Switch 绝不触碰官方凭据。第二条是 **xAI Grok 账号 OAuth 登录**:设备码授权替代 API Key,本地代理逐请求注入访问令牌,Claude Code / Claude Desktop 侧完成 Anthropic Messages → xAI Responses 转换;Codex 侧则提供受管 OAuth 预设并自带兼容层——codex 0.142+ 发出的 ChatGPT 后端私有形态(namespace 工具声明、私有字段)会被确定性地展平与剥离,严格解析的 xAI 网关不再返回 422;API Key 用户则另有一条「xAI (Grok)」原生 Responses 直连预设,不经任何转换。
围绕正确性,本版集中修复了 **v3.17.0 的 Codex 用量双计**:fork / 子代理日志开头对父线程历史的重放不再被当作新用量导入(解析器改为只认显式父身份 + 令牌签名对齐),升级后自动执行一次性用量重建(schema v16),用量页新增手动重建按钮;代理侧用量记录改为幂等(同一响应重放不再堆叠重复行),大量会话导入时用量页不再卡死。**Codex 转换层**另有四处修复:工具 schema 归一为 object 类型、推理内容跨轮前向附挂、流式并行工具调用保 ID 保序、生成的模型目录补齐 codex 0.144.5+ 必需字段。诊断体系也走向成熟:日志跨重启持久、按大小轮转、所有出口脱敏,界面崩溃被错误边界捕获并落盘。此外还有 Kimi K3 预设与定价、OpenClaw 预设成本修正、SudoCode.us 回归、托盘首启语言跟随系统等一批改进。
**发布日期**2026-07-21
**更新规模**52 commits | 217 files changed | +21,452 / -6,285 lines
---
## 新功能
### Grok Build:第八个受管应用
xAI 的 Grok CLIGrok Buildlive 配置 `~/.grok/config.toml`)现在是 CC Switch 的一等公民:供应商添加 / 导入 / 一键切换(切换后提示重启 Grok Build 生效)、应用显隐与配置目录覆盖设置、会话管理与用量看板覆盖、提示词首启自动导入、`ccswitch://` 深链导入供应商,以及本地代理接管——拥有专属的 `/grokbuild/v1/responses` 路由命名空间、独立的 failover 队列与按应用代理设置;转发复用 Codex 的 Responses 通路,但绝不与 Codex 共享供应商命名空间或熔断状态。
MCP 服务器与 Grok 的 `[mcp_servers]` 表双向同步,方言差异已被抹平:Grok 靠 `command` / `url` 推断传输类型且用 `headers` 字段,导出时会剥掉显式 `type` 并把 `http_headers` 重命名为 `headers`,导入时反向推断回来。Skills 也获得 Grok Build 启用开关。
预设方面刻意**没有**借用 Codex 列表(早期版本曾把国产直连供应商和 Codex 默认模型漏进 Grok 表单),而是独立整理了一份:只收录真正承载 Grok 模型的聚合与中转站,默认模型归一为 `grok-4.5`(命名空间路由站为 `x-ai/grok-4.5`)。工具面板安装 Grok 优先走 xAI 官方安装器(`x.ai/cli/install.sh` / `install.ps1`),npm 包 `@xai-official/grok` 作为兜底;被确认是原生安装的走 `grok update` 自更新,npm 安装保持 npm 锚定更新——自更新门控在「确定检测为原生」上,绝不会误伤另一种安装。四语界面文案同步就位。([#5453](https://github.com/farion1231/cc-switch/pull/5453)
### Grok 官方登录:识别、导入与保护
新增「Grok 官方」供应商条目,对应 Grok CLI 自带的 xAI OAuth 登录:选中它会隐藏连接字段并写入一个空的 `~/.grok/config.toml`,CC Switch 从不存储、也从不触碰官方凭据。live 配置的读取、备份与官方态写入改用仅语法级的 TOML 校验,官方登录态(空配置)可以正常往返;Grok 处于官方登录态时「从 live 导入」会得到「已设 Grok 官方为当前」而不是报错,与 Codex 行为一致。官方态识别刻意只接线到手动导入命令——启动时的自动导入器仍会拒绝官方态配置,所以你删掉的「Grok 官方」条目绝不会在下次启动时复活。对官方登录配置的代理接管会被自动跳过,手动路径给出明确拒绝,与现有「不代理官方供应商」的策略一致。
### 用 xAI Grok 账号登录:Claude Code 与 Claude Desktop
Claude Code 与 Claude Desktop 新增「xAI (Grok)」预设,用 OAuth 设备码登录代替 API Key:请求经本地代理完成 Anthropic Messages → xAI Responses API 转换并逐请求注入访问令牌,各档默认模型都是 `grok-4.5`Claude Desktop 预设把 `claude-*` 形式的角色 ID 映射到上游 `grok-4.5`,以通过 Desktop 的第三方模型校验)。
「设置 → OAuth 授权中心」新增 xAI 区块:设备码登录(用户码带复制按钮、验证链接、等待 / 取消 / 重试)、多账号与默认账号选择、按账号移除、重授权徽标——刷新令牌被吊销的账号会以「已过期」状态保留可见而不是消失,授权状态每 15 秒自动刷新,服务端吊销会自己浮现出来。
集成边界是钉死的:无论表单里的端点 / 格式字段怎么改,上游始终是 `https://api.x.ai/v1/responses`Responses 格式);OAuth 端点经 OIDC 发现解析,但强制校验为 https 的 `auth.x.ai`;刷新令牌存于 `~/.cc-switch/xai_oauth_auth.json`Unix 上 `0600`;访问令牌只存内存);OAuth 错误响应体绝不进入错误信息或日志。`grok-4.5` 定价($2 输入 / $6 输出 / $0.50 缓存读,每百万 token)同步入库,用量不再记 $0,存量数据库下次启动自动补行。四语文案同步。使用前请阅读「风险提示」中的客户端身份披露。
不用 OAuth、只有按量付费的 xAI API Key?同样能接进 Claude CodexAI 的 API 端点就是标准 Responses 协议,把它当作一个普通的 Responses 供应商添加——自定义供应商填 `https://api.x.ai/v1` 与 API Key、上游格式选 Responses,经本地路由完成 Anthropic Messages ↔ Responses 转换,与〈[在 Claude Code 中使用 GPT 模型](../guides/claude-codex-routing-guide-zh.md)〉攻略是同一套玩法。Codex 侧则有现成的 API Key 预设,见下一节。
### Codex 直连 xAIOAuth 受管与 API Key 原生双预设
Codex 获得两条直连 xAI 的路——有 Grok 订阅走 OAuth 受管,有 API Key 走原生直连:
- **「xAI (Grok) OAuth」受管预设**:让 Codex 跑在 Grok 订阅上。表单隐藏密钥 / 端点 / 格式字段、显示账号选择器,「获取模型」用已登录账号发起;供应商被钉死为原生 Responses,base URL 与逐请求令牌由代理强制执行——改了也会被忽略,受管路由无法被重定向。由于 codex 0.142+ 会发出 ChatGPT 后端私有的请求形态(`type:"namespace"` 工具声明会让 xAI 严格解析器直接 422,另有 `prompt_cache_retention``safety_identifier``external_web_access``additional_tools` 载体字段和 grok-4.5 不支持的采样参数),OAuth 路由在原生透传上加了一层兼容层:namespace 工具被展平为顶层 function 工具(与 Chat 路径同款 sha256 截断命名)、响应侧流式与非流式都还原回 namespace 形态,不支持的字段被剥除——全部是确定性的字段删除 / 结构提升,绝无语义改写,prompt 缓存前缀保持稳定。兼容层只门控在 xAI OAuth 供应商类型上,任何其它供应商的流量都不受影响。
- **「xAI (Grok)」API Key 预设**:直连 `api.x.ai/v1` 的原生 Responses,自带 500K 上下文的 `grok-4.5` 目录条目。该预设**不会**应用上述 xAI 专属兼容转换——codex 0.142+ 的 API Key 用户仍可能撞上 xAI 的严格解析器,OAuth 预设才是完全兼容的路径。
xAI OAuth 的令牌失败被归为不可重试错误,failover 绝不会把你的对话悄悄挪到另一个 Grok 账号上。
### 界面崩溃捕获:错误落盘与重载页
React 错误边界现在包住整个界面(包括数据库恢复界面):渲染进程崩溃时显示「界面出错了」卡片和重载按钮,而不是一片白屏;全局 `error` / `unhandledrejection` 处理器把渲染端错误持久化到磁盘——此前一次 JS 崩溃在盘上零证据。前端写出的所有日志经过两层脱敏:结构化序列化器按敏感属性名(`tokens` / `apiKeys` / `credentials` 等变体归一匹配,整值含嵌套对象一起隐藏)与值形态(令牌前缀、PEM 头、高熵不透明串)脱敏,再经唯一文本出口的有序正则链覆盖 URL 查询值与凭据、认证头与 scheme、命名密钥容器(双重编码的 JSON 也覆盖)。字符串形态到达的 JSON 会被重新解析后做结构化脱敏;超大结构化输入整体丢弃而非截断——截断的 JSON 串会退化到较弱的文本正则,可能泄漏。设置里的开关文案也改为名副其实:「应用诊断日志」(cc-switch.log)与代理的「记录请求用量」(统计数据库,本来就不是文本日志)。四语同步。
### 「重建 Codex 用量」维护按钮
用量看板的维护区新增「重建 Codex 用量」:备份数据库后,只清除 `codex_session` 来源的明细行、对应的 `_codex_session` 日汇总与 Codex 同步游标,然后用修正后的解析器从头重导所有 rollout 文件——这是被下述双计 bug 污染的数据库的恢复路径,也是父日志恢复后延迟 fork 文件的重试路径。手动重建在备份写不出时会硬失败(自动迁移版只告警,因为在升级后因备份目录不可写而卡死启动是更糟的结局);整个「备份 → 重置 → 重导」序列持有会话同步锁,60 秒后台同步无法与清除交错;完成时保证恰好发出一次前端刷新通知——包括重导为零行或失败的路径——看板绝不会停留在重置前的数字上。游标清理按路径形态匹配(`sessions` / `archived_sessions` 段下的 `rollout-{uuid}` 文件名),旧 `CODEX_HOME` 下记录的游标也能清到。四语同步。
### 会话导入可观测性:延迟文件与疑似重复
会话同步结果现在报告 `filesScanned``deferredFiles`——父日志缺失或父标记冲突的 fork rollout 会被搁置且不写游标,等后续同步或手动重建重试,而不是靠猜导入——以及 `suspectedDuplicates`:插入后逐行探测是否已存在同指纹行(走 `idx_request_logs_dedup_lookup_expr` 表达式索引),每次命中记一条警告。双计 bug 未来若复发,会在日志里自己喊出来,而不是无声地吹大总数。
### Kimi K3 预设与定价
Codex / Hermes / OpenClaw / OpenCode 的 Kimi 开放平台预设加入 Kimi K3(1M 上下文窗口),追加在 K2.7 Code 之后,现有默认模型行为不变。内置定价表新增 `kimi-k3`(官方牌价 $3 输入 / $15 输出 / $0.30 缓存读,每百万 token)与裸 `k3` 别名——Kimi For Coding 订阅上报的模型短 id 是 `k3`,否则匹配不到任何定价行(与现有 `hunyuan-hy3` / `hy3` 同款先例)。存量数据库下次启动自动补齐两行,不碰用户改过的定价。
### SudoCode.us 回归,与 SudoCode.chat 并存
两家恰好同名「SudoCode」的无关公司现在是两个独立预设:赞助商更名为「SudoCode.chat」,此前被原位替换掉的「SudoCode.us」带着原有端点、模型与图标回归,Hermes slug 也做了区分,两者可在累加式的 `~/.hermes/config.yaml` 中共存。算上新的 Grok Build 预设列表,SudoCode.chat 覆盖七个应用、SudoCode.us 覆盖全部八个。
---
## 变更
### 诊断日志:跨重启持久、按大小轮转、绝不记录密钥
`cc-switch.log` 不再在每次启动时被清空——过去能解释崩溃的日志,等应用重开时已经没了——改为 20 MB 轮转、保留 4 个归档(上限约 100 MB,对比过去单文件可膨胀到 1 GB);此前无上限的 `crash.log` 改为 5 MB 轮转、保留 2 个归档,检查 / 轮转 / 追加序列在同一把锁下,并发 panic 不会丢归档。
日志持久化让明文密钥成为真实的暴露面(用户会把日志附到公开 issue 里),所以同一批改动里把后端所有日志出口都做了清洗:上游 URL 只记剥掉 userinfo / query / fragment 的形式(没有已知密钥可替换时只记 origin,因为凭据可能嵌在路径里);请求与响应体一律不记——换成字节数、短哈希或安全分类(`sse` / `html` / `json-like` / `binary-or-encoded` 等),排查转换问题的信号还在、内容没了;响应头走白名单(名单外只记名字);正在使用的密钥值(API Key、访问令牌)会从任何携带它的 URL 里被替换掉;MCP 自定义字段值一律省略。日志插件注册提前(更新器 / 启动期故障可诊断),持久化的日志级别在数据库打开后立即生效、故障时收敛到 Info,「启用诊断日志」开关现在也管前端发起的日志写入。**升级前的旧日志文件不会被追溯清洗**——见「升级提醒」。
### 预设选择器:赞助商分组,其余按名称排序
预设选择器的默认顺序改为四层:官方最前,其次首要合作伙伴,然后是赞助商预设(与 README 赞助商表同序,预设文件已物理重排对齐),最后所有其余预设按显示名字母序排列,不再按文件序。命中多层的条目只落在最早一层,不会重复出现。
### 预设「获取 API Key」链接更新
RunAPI、ClaudeCN、ZetaAPI、APINebula 预设的密钥申请链接更新为各家当前的注册 / 推荐页(ClaudeCN 同时迁移了域名:claudecn.top → claudecn.ai)。推荐标签仅限这些链接与 README——官网链接和 API 端点保持不动。
---
## 修复
### Codex fork / 子代理不再把重放的父历史当新用量(v3.17.0 双计根治)
修复 v3.17.0 的用量膨胀:fork 一个 Codex 任务或以复制模式派生子代理时,父对话的 token 历史被当作新用量重复计入——有用户报告单日用量跳涨数十亿 token、父子行字节级相同、空 fork 背着从未消耗过的用量。fork / 子代理的 rollout 文件开头会重放父线程历史,旧解析器靠启发式找接管边界(第一个 `thread_settings_applied` 事件、对象形态的 `subagent` 来源标记):父线程自己的设置变更出现在重放里时边界落得太早,而当前字符串形态的来源标记则完全识别不到,整段父历史被原样导入。新解析器只认显式父身份——子方 `session_meta` 上的 `forked_from_id``source.subagent.thread_spawn.parent_thread_id`,两者冲突时搁置该文件——线程身份锚定到 rollout 文件名 UUID,加载父 rollout 自己的 fork 前 token 计数序列,用令牌签名对齐剥掉子方的重放前缀:重放事件只用于恢复累计基线,绝不插行。不带重放历史的子代理日志现在按真实用量计入,反方向的漏计(真实子代理消耗被当作疑似重放跳过)同步修复。([#5335](https://github.com/farion1231/cc-switch/issues/5335)、[#5433](https://github.com/farion1231/cc-switch/issues/5433)、[#5381](https://github.com/farion1231/cc-switch/issues/5381)
### 代理用量记录改为幂等:响应级稳定键
终态用量事件不带消息 id 时(经本地代理的 Codex `/responses` 流量是常态),去重键此前回退到随机 UUID——同一上游响应的每次重试 / 重放都造一个新键,`INSERT OR REPLACE` 每次都堆一行新的;有用户的数据库里同一用量组合出现了 2,078 次。解析器现在从响应信封本身取键——Codex `response.completed` 事件的 `response.id`(丢弃 `response.created` 的 id)、Chat Completions 的 `chatcmpl` id、Gemini 的 `responseId`——并按 `session:{app_type}:{provider_id}:{id}` 作用域化:failover 时同一响应打到不同供应商仍按供应商各记一次、互不碰撞(Claude 保持裸 `session:{id}` 形态,代理行继续与会话日志导入合流)。完全没有信封 id 时,兜底从响应的用量语义做确定性 SHA-256——相同重放必须撞进同一个键,去重才成立——最终写库也从无条件 REPLACE 改为去重窗口内的「不存在才插入」。([#5496](https://github.com/farion1231/cc-switch/issues/5496)
### 大量会话导入时用量页不再卡死
导入大批会话时打开用量页可能整个卡住:每插入一行就发一次刷新通知,每次通知让前端重跑全部约 10 个用量查询,这些查询又与正在逐行解析几十 MB rollout 文件的导入器争抢唯一数据库连接——在被重复行吹大的数据库上三者互相放大。现在会话同步改为每轮完成只通知一次;所有会话导入器串行在单飞锁后(手动「立即同步」排队等待运行中的一轮,而不是与之竞争);阻塞式解析挪到专用阻塞线程,不再饿死驱动界面命令的异步运行时;60 秒后台节拍错过就跳过,不再突发补跑。
### codex 0.144.5+ 不再因 CC Switch 生成的模型目录无法启动
codex ≥ 0.144.5 严格解析外部模型目录,条目缺 `supports_reasoning_summaries` 时整个文件被拒——Codex CLI 和桌面端都起不来,删掉生成目录也没用,因为任何一次供应商保存都会按同样方式重新生成。根因是 CC Switch 从机器共享的 `models_cache.json` 克隆目录模板,而它的字段集取决于最后写它的那个 codex 进程——共存的旧版 codex 一直在用缺字段的形态重写缓存。生成目录现在会从内置静态模板回填解析器必需字段,且只在缺失时回填(动态值永远优先);「缺失即解析器默认值」的可选能力字段刻意不回填,语义必须保留。
### Windows:切换供应商不再闪黑窗、不再卡死
Windows 上切换供应商或开关接管会闪过一个控制台窗口、界面卡住约 2 秒。三个原因、三处修复:`codex debug models --bundled` 探测经 `cmd.exe` 启动 `codex.cmd`,GUI 子系统应用里这会弹出自己的控制台——子进程现在带 `CREATE_NO_WINDOW` 创建;模型目录模板此前每次切换都重新生成——现在首次成功加载后进程级缓存(失败保持可重试,坏的首次探测不会毒化缓存),Codex CLI 每次应用运行至多启动一次;`switch_provider` 此前是跑在主线程上的同步命令——现在异步化、真实工作在阻塞线程上,仍由按应用切换锁串行。卡顿修复对全平台生效,闪窗修复是 Windows 专属。
### 工具 schema 为 null / 缺失 / 联合类型不再被严格上游整单拒绝
Codex 内置工具(如 `codex_app__automation_update`)声明 `parameters: null`(或 `type: null`),DeepSeek 这类严格的 OpenAI 兼容上游会对整个请求返回 400,经代理路由的工具会话直接被杀。Responses→Chat 桥现在把每个工具的 parameters 归一为 `type:"object"` schema:null 或缺失(含嵌套形态的缺失)变为 `{"type":"object","properties":{}}`,非 object 的 `type`(含 `type: null`)原位纠正为 `"object"`,顶层 `oneOf` 联合 schema 补根 `type:"object"`、分支原样保留。同样的 object 类型保证扩展到了 Codex→Anthropic 工具路径的 `input_schema`。已有的 `properties` / `required` 绝不丢弃。([#4706](https://github.com/farion1231/cc-switch/pull/4706)、[#5315](https://github.com/farion1231/cc-switch/pull/5315),修复 #4705#4783
### 推理模型在多轮 Codex Chat 对话中保住思考
推理模型(如 kimi-k2-thinking)走代理的 Responses→Chat 桥时,多轮历史会弄坏思考内容:每轮的 `reasoning` 条目被粘到**上一条**助手消息的尾巴上,紧随的助手轮反而没有 `reasoning_content`——模型会肉眼可见地中途断片。Responses 语义里推理位于它所属消息**之前**,桥现在把推理前向附挂到其后的助手消息或工具调用上;真正的尾部推理只在确凿的尾部(输入结束,或用户消息这样的轮次边界——此前在这里会被静默丢弃)向后附挂,并追加到已内嵌的推理之后;悬挂中的推理在边界处必被消费,绝不会跨过用户轮泄漏进后面的助手消息。([#5508](https://github.com/farion1231/cc-switch/pull/5508)
### 流式并行工具调用保住 ID 与顺序
Chat→Responses 流式桥的两个 bug 会弄坏「身份分散在多个 chunk」的上游发来的并行工具调用:携带空 `id` 的续传增量会覆盖真实 `call_id`Codex 客户端看到 `call_id:""`,工具结果对不上调用);工具调用各自就绪就立即发出,名字先到的靠后索引能插到靠前索引前面——并行调用被重排。现在空 id 一律忽略;发射经过连续索引闸门,严格按 Chat `index` 顺序放行,未识别的靠前索引没就绪就等待;流中途绝不合成假 call id(只在流终结时作为最后手段,且防御性跳过无名调用、稀疏索引照常发出)。([#5310](https://github.com/farion1231/cc-switch/pull/5310)
### 受管 OAuth 供应商可靠地标记为「需本地路由」
「需路由」徽标与切换时警告此前由供应商的 API 格式推导,对受管 OAuth 供应商(Copilot、Codex OAuth、xAI)这是错误信号——它们的凭据由代理注入、与上游格式无关,原生格式的受管供应商拿不到警告、不开接管就静默失败。路由需求现在由唯一共享谓词决定:官方供应商永不需要路由,受管 OAuth 供应商恒需要,格式规则只适用于其余情况。切换时的门槛也按应用查对了就绪信号:多数应用查按应用接管状态(旧门槛只看全局代理运行标志,漏掉「代理在跑但当前应用没被接管」),Claude Desktop 继续看代理进程本身——后端接管状态没有 Claude Desktop 字段,统一按应用查会让 Desktop 永远弹警告。Claude Desktop 供应商表单对所有受管 OAuth 类型强制代理模式并锁定模型映射开关,不再只对 xAI。四语同步。
### Node 装在 nvm / fnm / mise 里时工具更新可用
锚定的 npm 更新与修复命令按绝对路径调 npm,但 npm 启动器靠 `#!/usr/bin/env node` shebang 从 PATH 找 `node`——GUI 启动的应用只继承系统 PATH,不含版本管理器目录,nvm / fnm / mise 安装的工具更新静默失败。现在每个锚定 npm 调用都把 npm 自己的同级 `bin` 目录前置到 PATHnpm 与它的 shebang 解析到同一个 Node;Codex 自修复(卸载 + 重装)路径同样覆盖。
### 删除的默认 Skill 仓库不再复活
默认 Skill 仓库此前每次启动被「补齐缺失默认项」逻辑重新播种,删掉的默认仓库下次启动又静默回来。播种改为按数据库一次性,用设置标志记录;升级时已有仓库的数据库直接置标志、不再补种,现有选择不受影响。([#5356](https://github.com/farion1231/cc-switch/pull/5356)
### 托盘首启语言跟随系统
设置里还没选过语言时,托盘菜单被硬编码为简体中文——英文 / 日文 / 繁中系统上主界面正确跟随系统语言、托盘却不一致,直到用户手动切一次语言。托盘现在按与前端相同的优先级从系统 locale 推导首启语言(含 `zh-TW` / `zh-HK` / `zh-Hant` → 繁体中文);显式选择的语言永远优先,locale 读不到时照旧回落中文。([#4355](https://github.com/farion1231/cc-switch/pull/4355)
### 导入失败显示真实错误并刷新列表
每次「从 live 配置导入」失败都弹一个空错误提示,因为 Tauri 的 `invoke` 以后端错误**字符串**拒绝,而处理器从它上面读 `.message`。现在显示后端真实报错(带本地化的通用兜底),失败时也会刷新供应商列表——报错前已提交的副作用立即可见。
### OpenClaw 预设模型成本修正为官方牌价
15 个 OpenClaw 预设条目的成本值单位错误或未换汇——`cost` 字段是美元每百万 token,例如 `glm-5.1` 记成 `0.001/0.001`(低估约 1000 倍,用量成本近乎 0),`deepseek-v4-pro` 则带着未换算的人民币值(高估)。所有条目改为官方牌价 $/M;订阅套餐与免费档端点也刻意展示牌价,套餐用户能看到自己用量的标准价值。今后从预设新建的供应商拿到修正值;已创建的供应商保持创建时的配置。
### 界面小修一组
- **AiHubMix 图标**Codex 应用的 AiHubMix 预设此前缺品牌图标字段、渲染成通用图标,现与其它应用一致。
- **两个缺失文案键补齐**Codex「因使用 Anthropic Messages 格式需要路由」提示里的原因片段此前在非中文界面显示中文(`proxyReasonAnthropicMessages` 不存在于任何语言文件);供应商表单的密钥状态加载标签自 4 月起只有硬编码默认值。两者已在 zh / en / ja / zh-TW 全部补齐。
---
## 文档
### Codex ↔ Claude 双向路由攻略
两篇新攻略把「Codex 客户端用 Claude 模型」「Claude Code 客户端用 Responses 供应商」补成了双向:
- **[在 Codex 中使用 Claude 模型](../guides/codex-claude-routing-guide-zh.md)**(中 / 英 / 日三语,含截图):配合 v3.17.0 的原生 Anthropic Messages 上游,把 Codex 接到 Claude 系 `/v1/messages` 网关;v3.17.0 的 release notes 已回链本攻略。
- **[在 Claude Code 中使用 GPT 模型](../guides/claude-codex-routing-guide-zh.md)**(中 / 英 / 日三语,含截图):用 Responses 协议的供应商(网关 API Key,或 ChatGPT 订阅的 Codex 服务)驱动 Claude Code——Claude Code 始终对本地 `/v1/messages` 路由说 Anthropic Messages,由代理把每个请求转换成上游的 Responses 协议。
### README 赞助商更新
SubRouter 加入四语 README 赞助商表;置顶的 Kimi 赞助文案更新到 K3、横幅改由 Moonshot CDN 提供;RunAPI 权益文案刷新,赞助商行序与应用内预设顺序对齐。
---
## 升级提醒
### 数据库自动迁移与 Codex 用量一次性重建
从 v3.17.0 升级会连续执行三次 schema 迁移(v13 → v16):v14 重建 `proxy_config` 表以纳入 Grok Build(现有按应用代理设置全部保留,并新增 `grokbuild` 行);v15 给 MCP 服务器表与 Skills 表加 Grok Build 启用列;v16 触发一次性的 Codex 用量自动重建——数据库先备份到 `backups/` 下,`codex_session` 数据与游标被重置,随后正常的启动同步用修正后的解析器重导全部数据。典型数据量只需数秒;实测最重的数据集(1,801 个 rollout 文件 / 1.5 GB)约 65 秒。之后的启动照旧增量。若有回退旧版本的习惯,建议先自行备份 `~/.cc-switch/cc-switch.db`
**首次启动时请留意**:历史记录的修复是**逐渐完成**的——重建随启动同步在后台进行,这段时间里用量看板的 Codex 历史数字会先清零、再逐步回填,属预期行为,不是数据丢失。重建完成后的总数通常会比升级前**更小**:被双计吹大的那部分被挤掉了,剩下的才是真实用量。
### 重建的边界
- 重建从 rollout JSONL 文件重新计算用量,**源日志已被删除的历史无法重建**。
- 父 rollout 缺失的 fork 文件会被搁置并报告,而不是靠猜导入;恢复父日志后运行「重建 Codex 用量」可补导。
- 历史上代理来源的重复行会永久保留——迁移只重建会话来源的数据,不存在针对过往代理膨胀的清理逻辑;幂等记录只保证从此不再产生新重复。
### 旧日志文件不会被追溯脱敏
诊断日志从本版起不再在启动时清空、跨重启持久保留(运行日志轮转上限约 100 MB,另有约 15 MB 崩溃日志)。**早期版本写下的日志文件不会被追溯清洗**,可能含有 API Key、令牌或带凭据的 URL——公开分享前请先检查升级前的旧日志。
### Grok Build 安装走官方安装脚本
安装或重装 Grok Build 现在优先使用 xAI 官方安装器,安装时会外联获取 `x.ai/cli/install.sh`Windows 为 `install.ps1`),npm 作为兜底;已有的 npm 安装继续经 npm 更新。
### 内置定价自动补行
新定价行(`grok-4.5``kimi-k3``k3`)在下次启动时按「不存在才插入」自动追加;用户编辑过的定价行绝不被覆盖。
---
## 风险提示
### xAI Grok OAuth 登录(本版新增,请阅读)
本版的 xAI Grok OAuth 集成**复用官方 Grok CLI 注册的公开 OAuth 客户端身份与权限范围**(`client_id b1a00492-073a-47ea-816f-4c329264a828`scope 含 `grok-cli:access`),而不是 CC Switch 自己注册的应用身份。xAI 可能不支持这种用法,**使用可能导致账号被限制或封禁——风险自担**。该功能完全可选:不添加 xAI 供应商,一切照旧。首次登录会创建 `~/.cc-switch/xai_oauth_auth.json`(仅存刷新令牌,Unix 上权限 `0600`;访问令牌只存内存),并经你配置的出站代理访问 `auth.x.ai``api.x.ai`,无本地回调端口。
### 沿用的反向代理类提示
**Codex OAuth 反向代理**:使用 ChatGPT 订阅的 Codex OAuth 反代可能违反 OpenAI 服务条款,详情见 [v3.13.0 release notes](v3.13.0-zh.md#-风险提示)。
**第三方供应商路由**:通过 CC Switch 本地代理把 Codex、Claude Desktop 或 Grok Build 的请求转换并转发到第三方供应商时,各供应商对计费、合规与数据留存的约束不同,请在使用前阅读目标供应商的服务条款。
用户启用上述功能即表示自行承担相关风险。CC Switch 不对因使用这些功能而导致的任何账号限制、警告或服务暂停承担责任。
---
## 致谢
感谢以下贡献者在 v3.18.0 中提交的功能与修复:
- [#5453](https://github.com/farion1231/cc-switch/pull/5453)Grok Build 一等公民支持(第八个受管应用的主体实现),感谢 @YUZHEthefool
- [#5508](https://github.com/farion1231/cc-switch/pull/5508)Responses→Chat 桥推理内容前向附挂,感谢 @ka79376046
- [#5310](https://github.com/farion1231/cc-switch/pull/5310):流式并行工具调用保 ID 保序,感谢 @SaladDay
- [#5315](https://github.com/farion1231/cc-switch/pull/5315)Codex 工具 parameters 归一为 object schema,感谢 @Komikawayi
- [#4706](https://github.com/farion1231/cc-switch/pull/4706):严格 OpenAI 兼容上游的工具类型归一,感谢 @Ryan2128
- [#5356](https://github.com/farion1231/cc-switch/pull/5356):删除的默认 Skill 仓库不再复活,感谢 @allenxu09
- [#4355](https://github.com/farion1231/cc-switch/pull/4355):托盘首启语言跟随系统 locale,感谢 @LaiYueTing
- [#5138](https://github.com/farion1231/cc-switch/pull/5138):后端 CI 扩展到 Linux / Windows / macOS 三平台,感谢 @zayokami
也感谢所有反馈 Codex 用量异常、codex 新版启动失败与工具调用问题的用户——本版最重要的几个修复都来自这些真实场景里的复现线索。
---
## 下载与安装
访问 [Releases](https://github.com/farion1231/cc-switch/releases/latest) 下载对应版本。
### 系统要求
| 系统 | 最低版本 | 架构 |
| ------- | -------------------------- | ----------------------------------- |
| Windows | Windows 10 及以上 | x64 / ARM64 |
| macOS | macOS 12 (Monterey) 及以上 | Intel (x64) / Apple Silicon (arm64) |
| Linux | 见下表 | x64 / ARM64 |
### Windows
| 文件 | 说明 |
| ---------------------------------------- | ----------------------------------- |
| `CC-Switch-v3.18.0-Windows.msi` | **推荐** - MSI 安装包,支持自动更新 |
| `CC-Switch-v3.18.0-Windows-Portable.zip` | 便携版,解压即用,不写入注册表 |
Windows ARM64 设备请选择文件名中带 `arm64` 标识的对应制品。
### macOS
| 文件 | 说明 |
| -------------------------------- | --------------------------------------------- |
| `CC-Switch-v3.18.0-macOS.dmg` | **推荐** - DMG 安装包,拖入 Applications 即可 |
| `CC-Switch-v3.18.0-macOS.zip` | 解压后拖入 ApplicationsUniversal Binary |
| `CC-Switch-v3.18.0-macOS.tar.gz` | 用于 Homebrew 安装和自动更新 |
Homebrew 安装:
```bash
brew install --cask cc-switch
```
更新:
```bash
brew upgrade --cask cc-switch
```
### Linux
Linux 资产同时提供 **x86_64****ARM64**`aarch64`)两种架构。资产文件名中包含架构标识,请按你机器的 `uname -m` 输出选择对应版本:
- `CC-Switch-v3.18.0-Linux-x86_64.AppImage` / `.deb` / `.rpm`
- `CC-Switch-v3.18.0-Linux-arm64.AppImage` / `.deb` / `.rpm`
| 发行版 | 推荐格式 | 安装方式 |
| --------------------------------------- | ----------- | ---------------------------------------------------------------------- |
| Ubuntu / Debian / Linux Mint / Pop!\_OS | `.deb` | `sudo dpkg -i CC-Switch-*.deb``sudo apt install ./CC-Switch-*.deb` |
| Fedora / RHEL / CentOS / Rocky Linux | `.rpm` | `sudo rpm -i CC-Switch-*.rpm``sudo dnf install ./CC-Switch-*.rpm` |
| openSUSE | `.rpm` | `sudo zypper install ./CC-Switch-*.rpm` |
| Arch Linux / Manjaro | `.AppImage` | 添加执行权限后直接运行,或使用 AUR |
| 其他发行版 / 不确定 | `.AppImage` | `chmod +x CC-Switch-*.AppImage && ./CC-Switch-*.AppImage` |
@@ -278,21 +278,36 @@ Log level descriptions:
Settings > **OAuth Auth Center** Tab
Added in v3.13.0, the **OAuth Auth Center** (Beta) provides unified management for third-party OAuth credentials. It currently supports two account types:
Added in v3.13.0, the **OAuth Auth Center** (Beta) provides unified management for third-party OAuth credentials. It currently supports three account types:
| Account Type | Purpose |
| ------------------------- | ---------------------------------------------------------- |
| **GitHub Copilot** | Used with the Copilot reverse proxy |
| **ChatGPT (Codex OAuth)** | Used with the Codex OAuth reverse proxy; manage ChatGPT accounts |
| **xAI (Grok OAuth)** | Used with the xAI Responses API reverse proxy |
**What you can do here**:
- Log in to ChatGPT / GitHub accounts via the Device Code flow
- Log in to ChatGPT / GitHub / xAI accounts via the Device Code flow
- View the list of logged-in accounts and authentication status
- Set a default account when managing multiple accounts
- Remove individual accounts or log out all accounts at once
> **Note**: Both features use reverse-engineered OAuth flows and carry account risk and Terms of Service risk. Before using, please read the full risk notice in [2.1 Add Provider → Codex OAuth Reverse Proxy](../2-providers/2.1-add.md#codex-oauth-reverse-proxy-claude-provider).
The xAI integration uses the OAuth 2.0 Device Authorization Grant, not a loopback callback. Its network contract is explicit:
| Dependency | Endpoint |
| ---------- | -------- |
| OpenID discovery | `https://auth.x.ai/.well-known/openid-configuration` |
| Device authorization | `device_authorization_endpoint` from discovery (currently `https://auth.x.ai/oauth2/device/code`) |
| Token exchange / refresh | `token_endpoint` from discovery (currently `https://auth.x.ai/oauth2/token`) |
| Inference | `https://api.x.ai/v1/responses` |
| Model discovery | `https://api.x.ai/v1/models` |
There is no local callback port. Authentication and model requests use CC Switch's global outbound proxy configuration.
The xAI integration does **not** use an OAuth application registered to CC Switch. It reuses the public OAuth client identity and scopes registered for the official Grok CLI (`client_id: b1a00492-073a-47ea-816f-4c329264a828`, including `grok-cli:access`) to obtain account-backed API access.
> **Warning**: These account-backed reverse proxies use reverse-engineered OAuth integrations and carry account and Terms of Service risk. Reusing an upstream product's OAuth client may be unsupported and may cause the provider to restrict or suspend the account. Review the applicable provider terms and use these features at your own risk. For the existing Codex flow, also read [2.1 Add Provider → Codex OAuth Reverse Proxy](../2-providers/2.1-add.md#codex-oauth-reverse-proxy-claude-provider).
## About Page
@@ -8,7 +8,7 @@ Once enabled, you can:
- Use third-party Anthropic-compatible providers in Claude Desktop
- Configure model mapping for models outside the three role IDs: legacy Claude IDs (e.g. `claude-3-5-sonnet`) and non-Claude models like DeepSeek / Kimi / DouBao / OpenAI / Gemini all need it
- Reuse Copilot / Codex OAuth account-based providers
- Reuse Copilot / Codex OAuth / xAI OAuth account-based providers
- Switch between Claude Desktop official mode and third-party providers
Claude Desktop and Claude Code are separate app entry points. Claude Code uses `~/.claude/settings.json`, while Claude Desktop uses its own 3P profile configuration. In CC Switch they appear as separate apps: "Claude" and "Claude Desktop"; the icon badge in the bottom-right corner helps distinguish them.
+8 -3
View File
@@ -275,7 +275,12 @@ If none of the above solutions work:
### Log Files
Attach log files when submitting an Issue:
CC Switch stores logs under its application configuration directory. By default this is `.cc-switch` in your home directory; if you changed the configuration directory in Advanced Settings, use that custom location instead.
- macOS/Linux: `~/.cc-switch/logs/`
- Windows: `%APPDATA%\cc-switch\logs\`
When submitting an Issue, attach the files relevant to the problem:
- General, network, or proxy errors: `~/.cc-switch/logs/cc-switch.log` and its rotated files
- Application crashes: `~/.cc-switch/crash.log`, `crash.log.1`, and `crash.log.2`
- The default Windows location is `C:\Users\<username>\.cc-switch\...`
The runtime log rotates at 20 MB and retains the four most recent archives. Restarting the application does not clear existing logs. Logs may contain runtime environment details, so review them before posting publicly.
@@ -278,21 +278,36 @@ WebDAV プロトコルを使用して複数のデバイス間で設定を同期
設定 → **OAuth 認証センター** タブ
v3.13.0 で追加された **OAuth 認証センター**(Beta)は、サードパーティの OAuth 認証情報を一元管理します。現在、以下の 2 種類のアカウントタイプをサポートしています:
v3.13.0 で追加された **OAuth 認証センター**(Beta)は、サードパーティの OAuth 認証情報を一元管理します。現在、以下の 3 種類のアカウントタイプをサポートしています:
| アカウントタイプ | 用途 |
| ----------------------------- | --------------------------------------------------------------- |
| **GitHub Copilot** | Copilot リバースプロキシと組み合わせて使用 |
| **ChatGPT (Codex OAuth)** | Codex OAuth リバースプロキシと組み合わせて使用、ChatGPT アカウントを管理 |
| **xAI (Grok OAuth)** | xAI Responses API リバースプロキシと組み合わせて使用 |
**ここでできること**
- Device Code フローで ChatGPT / GitHub アカウントにログイン
- Device Code フローで ChatGPT / GitHub / xAI アカウントにログイン
- ログイン済みアカウント一覧と認証状態の確認
- マルチアカウント時のデフォルトアカウント設定
- 個別アカウントの削除や全アカウントの一括ログアウト
> **注意**:これら 2 つの機能はリバースエンジニアリングされた OAuth フローを使用するため、アカウントリスクおよび利用規約リスクが存在します。ご利用前に [2.1 プロバイダーの追加 → Codex OAuth リバースプロキシ](../2-providers/2.1-add.md#codex-oauth-リバースプロキシclaude-プロバイダー) の完全なリスク通知をお読みください。
xAI 連携はローカルのループバックコールバックではなく、OAuth 2.0 Device Authorization Grant を使用します。ネットワーク依存関係は次のとおりです:
| 依存関係 | エンドポイント |
| -------- | -------------- |
| OpenID discovery | `https://auth.x.ai/.well-known/openid-configuration` |
| デバイス認証 | discovery の `device_authorization_endpoint`(現在は `https://auth.x.ai/oauth2/device/code` |
| Token 交換・更新 | discovery の `token_endpoint`(現在は `https://auth.x.ai/oauth2/token` |
| 推論 | `https://api.x.ai/v1/responses` |
| モデル取得 | `https://api.x.ai/v1/models` |
ローカルのコールバックポートは使用しません。認証およびモデルリクエストは CC Switch のグローバル送信プロキシ設定を共有します。
xAI 連携は CC Switch 用に登録された OAuth アプリケーションを**使用しません**。公式 Grok CLI 用に登録された公開 OAuth クライアントの ID とスコープ(`client_id: b1a00492-073a-47ea-816f-4c329264a828``grok-cli:access` を含む)を再利用して、アカウントベースの API アクセスを取得します。
> **警告**:これらのアカウント型リバースプロキシは、リバースエンジニアリングされた OAuth 連携を使用しており、アカウントおよび利用規約上のリスクがあります。上流製品の OAuth クライアント再利用は公式にサポートされない可能性があり、サービス提供者によるアカウントの制限・停止につながるおそれがあります。各プロバイダーの規約を確認し、自己責任で使用してください。既存の Codex フローについては [2.1 プロバイダーの追加 → Codex OAuth リバースプロキシ](../2-providers/2.1-add.md#codex-oauth-リバースプロキシclaude-プロバイダー) も参照してください。
## バージョン情報ページ
@@ -8,7 +8,7 @@ Claude Desktop パネルでは、CC Switch 上で Claude Desktop のプロバイ
- Claude Desktop でサードパーティの Anthropic 互換プロバイダーを使う
- 三つの役割 ID 以外のモデルにマッピングを設定する:旧式 Claude ID(例:`claude-3-5-sonnet`)や DeepSeek / Kimi / DouBao / OpenAI / Gemini などの非 Claude モデルはいずれも必要
- Copilot / Codex OAuth のアカウント型プロバイダーを再利用する
- Copilot / Codex OAuth / xAI OAuth のアカウント型プロバイダーを再利用する
- Claude Desktop 公式モードとサードパーティプロバイダーを切り替える
Claude Desktop と Claude Code は別々のアプリ入口です。Claude Code は `~/.claude/settings.json` を使い、Claude Desktop は専用の 3P profile 設定を使います。CC Switch 上でも「Claude」と「Claude Desktop」として別々に表示され、アイコン右下の小さなバッジで区別できます。
+8 -3
View File
@@ -275,7 +275,12 @@ Codex OAuth リバースプロキシは **リバースエンジニアリング
### ログファイル
Issue を提出する際にログファイルを添付できます:
CC Switch のログはアプリケーション設定ディレクトリに保存されます。既定ではホームディレクトリ内の `.cc-switch` です。詳細設定で設定ディレクトリを変更した場合は、その場所を使用してください。
- macOS/Linux`~/.cc-switch/logs/`
- Windows`%APPDATA%\cc-switch\logs\`
Issue を提出する際は、問題に応じて次のファイルを添付してください:
- 一般的なエラー、ネットワーク、プロキシの問題:`~/.cc-switch/logs/cc-switch.log` とローテーション済みファイル
- アプリケーションのクラッシュ:`~/.cc-switch/crash.log`、`crash.log.1`、`crash.log.2`
- Windows の既定パス:`C:\Users\<ユーザー名>\.cc-switch\...`
実行ログは 20 MB ごとにローテーションされ、直近 4 個のアーカイブを保持します。アプリを再起動しても既存ログは削除されません。公開する前に、ログの内容を確認してください。
@@ -278,21 +278,36 @@ CC Switch 自身数据的存储位置,默认为 `~/.cc-switch/`。
设置 → **OAuth 认证中心** Tab
v3.13.0 新增的 **OAuth 认证中心**(Beta)统一管理第三方 OAuth 凭据,目前支持类账号:
v3.13.0 新增的 **OAuth 认证中心**(Beta)统一管理第三方 OAuth 凭据,目前支持类账号:
| 账号类型 | 用途 |
| ---------------------------- | ----------------------------------------------- |
| **GitHub Copilot** | 配合 Copilot 反向代理使用 |
| **ChatGPT (Codex OAuth)** | 配合 Codex OAuth 反向代理使用,管理 ChatGPT 账号 |
| **xAI (Grok OAuth)** | 配合 xAI Responses API 反向代理使用 |
**你可以在这里**
- 通过 Device Code 流程登录 ChatGPT / GitHub 账号
- 通过 Device Code 流程登录 ChatGPT / GitHub / xAI 账号
- 查看已登录账号列表和认证状态
- 为多账号设置默认账号
- 移除单个账号或一键注销所有账号
> ⚠️ **注意**:这两项功能使用逆向 OAuth 流程,存在账号风险和服务条款风险。使用前请阅读 [2.1 添加供应商 → Codex OAuth 反向代理](../2-providers/2.1-add.md#codex-oauth-反向代理claude-供应商) 的完整风险提示。
xAI 集成使用 OAuth 2.0 Device Authorization Grant,不使用本机回环回调。其网络依赖契约如下:
| 依赖 | 端点 |
| ---- | ---- |
| OpenID discovery | `https://auth.x.ai/.well-known/openid-configuration` |
| 设备授权 | discovery 返回的 `device_authorization_endpoint`(当前为 `https://auth.x.ai/oauth2/device/code` |
| Token 交换/刷新 | discovery 返回的 `token_endpoint`(当前为 `https://auth.x.ai/oauth2/token` |
| 推理 | `https://api.x.ai/v1/responses` |
| 模型发现 | `https://api.x.ai/v1/models` |
流程不占用任何本地回调端口;认证和模型请求均复用 CC Switch 的全局出站代理设置。
xAI 集成**不使用**注册给 CC Switch 的 OAuth 应用,而是复用 xAI 官方 Grok CLI 注册的公共 OAuth 客户端身份与 scope`client_id: b1a00492-073a-47ea-816f-4c329264a828`,包含 `grok-cli:access`)获取账号型 API 访问权限。
> ⚠️ **警告**:这些账号型反向代理使用逆向接入的 OAuth 流程,存在账号与服务条款风险。复用上游产品的 OAuth 客户端可能不受官方支持,并可能导致服务商限制或封禁账号。请在使用前审阅对应服务商条款并自行承担风险;现有 Codex 流程另见 [2.1 添加供应商 → Codex OAuth 反向代理](../2-providers/2.1-add.md#codex-oauth-反向代理claude-供应商) 的完整风险提示。
## 关于页面
@@ -8,7 +8,7 @@ Claude Desktop 面板用于在 CC Switch 中管理 Claude Desktop 的供应商
- 在 Claude Desktop 中使用第三方 Anthropic 兼容供应商
- 为三档角色 ID 之外的模型配置映射:旧式 Claude ID(如 `claude-3-5-sonnet`)、以及 DeepSeek / Kimi / DouBao / OpenAI / Gemini 等非 Claude 模型都需要
- 复用 Copilot / Codex OAuth 账号类供应商
- 复用 Copilot / Codex OAuth / xAI OAuth 账号类供应商
- 在 Claude Desktop 官方模式和第三方供应商之间切换
Claude Desktop 与 Claude Code 是两个不同的应用入口。Claude Code 使用 `~/.claude/settings.json`Claude Desktop 使用自己的 3P profile 配置;在 CC Switch 中也分别显示为「Claude」和「Claude Desktop」,图标右下角会有一个小图标用来区分。
+8 -3
View File
@@ -275,7 +275,12 @@ Codex OAuth 反向代理通过**逆向工程的 OAuth 流程**访问 ChatGPT 账
### 日志文件
提交 Issue 时可附上日志文件:
CC Switch 的日志位于应用配置目录中。默认配置目录是用户主目录下的 `.cc-switch`;如果你在高级设置中修改过配置目录,请以自定义目录为准。
- macOS/Linux`~/.cc-switch/logs/`
- Windows`%APPDATA%\cc-switch\logs\`
提交 Issue 时,请按问题类型附上:
- 普通错误、网络或代理问题:`~/.cc-switch/logs/` 中的 `cc-switch.log` 及其轮转文件
- 应用崩溃:`~/.cc-switch/crash.log` 及 `crash.log.1`、`crash.log.2`
- Windows 默认路径为 `C:\Users\<用户名>\.cc-switch\...`
运行日志按 20 MB 轮转,并保留最近 4 个归档;重启应用不会再清空已有日志。日志可能包含运行环境信息,公开提交前建议先检查内容。
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "cc-switch",
"version": "3.16.5",
"version": "3.18.0",
"description": "All-in-One Assistant for Claude Code, Codex & Gemini CLI",
"type": "module",
"scripts": {
@@ -70,6 +70,7 @@
"@tanstack/react-virtual": "^3.13.23",
"@tauri-apps/api": "^2.8.0",
"@tauri-apps/plugin-dialog": "^2.4.0",
"@tauri-apps/plugin-log": "2.8.0",
"@tauri-apps/plugin-process": "^2.0.0",
"@tauri-apps/plugin-store": "^2.0.0",
"@tauri-apps/plugin-updater": "^2.0.0",
+10
View File
@@ -98,6 +98,9 @@ importers:
'@tauri-apps/plugin-dialog':
specifier: ^2.4.0
version: 2.4.0
'@tauri-apps/plugin-log':
specifier: 2.8.0
version: 2.8.0
'@tauri-apps/plugin-process':
specifier: ^2.0.0
version: 2.3.0
@@ -1562,6 +1565,9 @@ packages:
'@tauri-apps/plugin-dialog@2.4.0':
resolution: {integrity: sha512-OvXkrEBfWwtd8tzVCEXIvRfNEX87qs2jv6SqmVPiHcJjBhSF/GUvjqUNIDmKByb5N8nvDqVUM7+g1sXwdC/S9w==}
'@tauri-apps/plugin-log@2.8.0':
resolution: {integrity: sha512-a+7rOq3MJwpTOLLKbL8d0qGZ85hgHw5pNOWusA9o3cf7cEgtYHiGY/+O8fj8MvywQIGqFv0da2bYQDlrqLE7rw==}
'@tauri-apps/plugin-process@2.3.0':
resolution: {integrity: sha512-0DNj6u+9csODiV4seSxxRbnLpeGYdojlcctCuLOCgpH9X3+ckVZIEj6H7tRQ7zqWr7kSTEWnrxtAdBb0FbtrmQ==}
@@ -4348,6 +4354,10 @@ snapshots:
dependencies:
'@tauri-apps/api': 2.8.0
'@tauri-apps/plugin-log@2.8.0':
dependencies:
'@tauri-apps/api': 2.8.0
'@tauri-apps/plugin-process@2.3.0':
dependencies:
'@tauri-apps/api': 2.8.0
+4
View File
@@ -2,3 +2,7 @@ packages: []
onlyBuiltDependencies:
- '@tailwindcss/oxide'
- esbuild
ignoredBuiltDependencies:
- msw
+11 -1
View File
@@ -758,7 +758,7 @@ dependencies = [
[[package]]
name = "cc-switch"
version = "3.16.5"
version = "3.18.0"
dependencies = [
"anyhow",
"arboard",
@@ -799,6 +799,7 @@ dependencies = [
"serde_yaml",
"serial_test",
"sha2",
"sys-locale",
"tauri",
"tauri-build",
"tauri-plugin-deep-link",
@@ -5438,6 +5439,15 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "sys-locale"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4"
dependencies = [
"libc",
]
[[package]]
name = "system-configuration"
version = "0.7.0"
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "cc-switch"
version = "3.16.5"
version = "3.18.0"
description = "All-in-One Assistant for Claude Code, Codex & Gemini CLI"
authors = ["Jason Young"]
license = "MIT"
@@ -81,6 +81,7 @@ sha2 = "0.10"
hmac = "0.12"
json5 = "0.4"
json-five = "0.3.1"
sys-locale = "0.3"
[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))'.dependencies]
tauri-plugin-single-instance = "2"
+3 -4
View File
@@ -2,13 +2,12 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": [
"main"
],
"windows": ["main"],
"permissions": [
"core:default",
"opener:default",
"updater:default",
"log:default",
"core:window:allow-set-skip-taskbar",
"core:window:allow-start-dragging",
"core:window:allow-minimize",
@@ -19,4 +18,4 @@
"process:allow-restart",
"dialog:default"
]
}
}
+71 -4
View File
@@ -14,6 +14,8 @@ pub struct McpApps {
#[serde(default)]
pub gemini: bool,
#[serde(default)]
pub grokbuild: bool,
#[serde(default)]
pub opencode: bool,
#[serde(default)]
pub hermes: bool,
@@ -26,6 +28,7 @@ impl McpApps {
AppType::Claude => self.claude,
AppType::Codex => self.codex,
AppType::Gemini => self.gemini,
AppType::GrokBuild => self.grokbuild,
AppType::OpenCode => self.opencode,
AppType::OpenClaw => false, // OpenClaw doesn't support MCP
AppType::Hermes => self.hermes,
@@ -39,6 +42,7 @@ impl McpApps {
AppType::Claude => self.claude = enabled,
AppType::Codex => self.codex = enabled,
AppType::Gemini => self.gemini = enabled,
AppType::GrokBuild => self.grokbuild = enabled,
AppType::OpenCode => self.opencode = enabled,
AppType::OpenClaw => {} // OpenClaw doesn't support MCP, ignore
AppType::Hermes => self.hermes = enabled,
@@ -58,6 +62,9 @@ impl McpApps {
if self.gemini {
apps.push(AppType::Gemini);
}
if self.grokbuild {
apps.push(AppType::GrokBuild);
}
if self.opencode {
apps.push(AppType::OpenCode);
}
@@ -69,7 +76,12 @@ impl McpApps {
/// 检查是否所有应用都未启用
pub fn is_empty(&self) -> bool {
!self.claude && !self.codex && !self.gemini && !self.opencode && !self.hermes
!self.claude
&& !self.codex
&& !self.gemini
&& !self.grokbuild
&& !self.opencode
&& !self.hermes
}
}
@@ -83,6 +95,8 @@ pub struct SkillApps {
#[serde(default)]
pub gemini: bool,
#[serde(default)]
pub grokbuild: bool,
#[serde(default)]
pub opencode: bool,
#[serde(default)]
pub hermes: bool,
@@ -95,6 +109,7 @@ impl SkillApps {
AppType::Claude => self.claude,
AppType::Codex => self.codex,
AppType::Gemini => self.gemini,
AppType::GrokBuild => self.grokbuild,
AppType::OpenCode => self.opencode,
AppType::Hermes => self.hermes,
AppType::OpenClaw => false, // OpenClaw doesn't support Skills
@@ -108,6 +123,7 @@ impl SkillApps {
AppType::Claude => self.claude = enabled,
AppType::Codex => self.codex = enabled,
AppType::Gemini => self.gemini = enabled,
AppType::GrokBuild => self.grokbuild = enabled,
AppType::OpenCode => self.opencode = enabled,
AppType::Hermes => self.hermes = enabled,
AppType::OpenClaw => {} // OpenClaw doesn't support Skills, ignore
@@ -127,6 +143,9 @@ impl SkillApps {
if self.gemini {
apps.push(AppType::Gemini);
}
if self.grokbuild {
apps.push(AppType::GrokBuild);
}
if self.opencode {
apps.push(AppType::OpenCode);
}
@@ -138,7 +157,12 @@ impl SkillApps {
/// 检查是否所有应用都未启用
pub fn is_empty(&self) -> bool {
!self.claude && !self.codex && !self.gemini && !self.opencode && !self.hermes
!self.claude
&& !self.codex
&& !self.gemini
&& !self.grokbuild
&& !self.opencode
&& !self.hermes
}
/// 仅启用指定应用(其他应用设为禁用)
@@ -271,6 +295,8 @@ pub struct McpRoot {
pub codex: McpConfig,
#[serde(default, skip_serializing_if = "McpConfig::is_empty")]
pub gemini: McpConfig,
#[serde(default, skip_serializing_if = "McpConfig::is_empty")]
pub grokbuild: McpConfig,
/// OpenCode MCP 配置(v4.0.0+,实际使用 opencode.json
#[serde(default, skip_serializing_if = "McpConfig::is_empty")]
pub opencode: McpConfig,
@@ -292,6 +318,7 @@ impl Default for McpRoot {
claude_desktop: McpConfig::default(),
codex: McpConfig::default(),
gemini: McpConfig::default(),
grokbuild: McpConfig::default(),
opencode: McpConfig::default(),
openclaw: McpConfig::default(),
hermes: McpConfig::default(),
@@ -323,6 +350,8 @@ pub struct PromptRoot {
#[serde(default)]
pub gemini: PromptConfig,
#[serde(default)]
pub grokbuild: PromptConfig,
#[serde(default)]
pub opencode: PromptConfig,
#[serde(default)]
pub openclaw: PromptConfig,
@@ -348,6 +377,7 @@ pub enum AppType {
ClaudeDesktop,
Codex,
Gemini,
GrokBuild,
OpenCode,
OpenClaw,
Hermes,
@@ -360,6 +390,7 @@ impl AppType {
AppType::ClaudeDesktop => "claude-desktop",
AppType::Codex => "codex",
AppType::Gemini => "gemini",
AppType::GrokBuild => "grokbuild",
AppType::OpenCode => "opencode",
AppType::OpenClaw => "openclaw",
AppType::Hermes => "hermes",
@@ -384,6 +415,7 @@ impl AppType {
AppType::ClaudeDesktop,
AppType::Codex,
AppType::Gemini,
AppType::GrokBuild,
AppType::OpenCode,
AppType::OpenClaw,
AppType::Hermes,
@@ -402,13 +434,14 @@ impl FromStr for AppType {
"claude-desktop" | "claude_desktop" | "claudedesktop" => Ok(AppType::ClaudeDesktop),
"codex" => Ok(AppType::Codex),
"gemini" => Ok(AppType::Gemini),
"grokbuild" | "grok-build" | "grok_build" | "grok" => Ok(AppType::GrokBuild),
"opencode" => Ok(AppType::OpenCode),
"openclaw" => Ok(AppType::OpenClaw),
"hermes" => Ok(AppType::Hermes),
other => Err(AppError::localized(
"unsupported_app",
format!("不支持的应用标识: '{other}'。可选值: claude, claude-desktop, codex, gemini, opencode, openclaw, hermes。"),
format!("Unsupported app id: '{other}'. Allowed: claude, claude-desktop, codex, gemini, opencode, openclaw, hermes."),
format!("不支持的应用标识: '{other}'。可选值: claude, claude-desktop, codex, gemini, grokbuild, opencode, openclaw, hermes。"),
format!("Unsupported app id: '{other}'. Allowed: claude, claude-desktop, codex, gemini, grokbuild, opencode, openclaw, hermes."),
)),
}
}
@@ -444,6 +477,7 @@ impl CommonConfigSnippets {
AppType::ClaudeDesktop => None,
AppType::Codex => self.codex.as_ref(),
AppType::Gemini => self.gemini.as_ref(),
AppType::GrokBuild => None,
AppType::OpenCode => self.opencode.as_ref(),
AppType::OpenClaw => self.openclaw.as_ref(),
AppType::Hermes => self.hermes.as_ref(),
@@ -457,6 +491,7 @@ impl CommonConfigSnippets {
AppType::ClaudeDesktop => {}
AppType::Codex => self.codex = snippet,
AppType::Gemini => self.gemini = snippet,
AppType::GrokBuild => {}
AppType::OpenCode => self.opencode = snippet,
AppType::OpenClaw => self.openclaw = snippet,
AppType::Hermes => self.hermes = snippet,
@@ -500,6 +535,7 @@ impl Default for MultiAppConfig {
apps.insert("claude-desktop".to_string(), ProviderManager::default());
apps.insert("codex".to_string(), ProviderManager::default());
apps.insert("gemini".to_string(), ProviderManager::default());
apps.insert("grokbuild".to_string(), ProviderManager::default());
apps.insert("opencode".to_string(), ProviderManager::default());
apps.insert("openclaw".to_string(), ProviderManager::default());
apps.insert("hermes".to_string(), ProviderManager::default());
@@ -662,6 +698,7 @@ impl MultiAppConfig {
AppType::ClaudeDesktop => &self.mcp.claude_desktop,
AppType::Codex => &self.mcp.codex,
AppType::Gemini => &self.mcp.gemini,
AppType::GrokBuild => &self.mcp.grokbuild,
AppType::OpenCode => &self.mcp.opencode,
AppType::OpenClaw => &self.mcp.openclaw,
AppType::Hermes => &self.mcp.hermes,
@@ -675,6 +712,7 @@ impl MultiAppConfig {
AppType::ClaudeDesktop => &mut self.mcp.claude_desktop,
AppType::Codex => &mut self.mcp.codex,
AppType::Gemini => &mut self.mcp.gemini,
AppType::GrokBuild => &mut self.mcp.grokbuild,
AppType::OpenCode => &mut self.mcp.opencode,
AppType::OpenClaw => &mut self.mcp.openclaw,
AppType::Hermes => &mut self.mcp.hermes,
@@ -691,6 +729,7 @@ impl MultiAppConfig {
Self::auto_import_prompt_if_exists(&mut config, AppType::Claude)?;
Self::auto_import_prompt_if_exists(&mut config, AppType::Codex)?;
Self::auto_import_prompt_if_exists(&mut config, AppType::Gemini)?;
Self::auto_import_prompt_if_exists(&mut config, AppType::GrokBuild)?;
Self::auto_import_prompt_if_exists(&mut config, AppType::OpenCode)?;
Self::auto_import_prompt_if_exists(&mut config, AppType::OpenClaw)?;
Self::auto_import_prompt_if_exists(&mut config, AppType::Hermes)?;
@@ -714,6 +753,7 @@ impl MultiAppConfig {
|| !self.prompts.claude_desktop.prompts.is_empty()
|| !self.prompts.codex.prompts.is_empty()
|| !self.prompts.gemini.prompts.is_empty()
|| !self.prompts.grokbuild.prompts.is_empty()
|| !self.prompts.opencode.prompts.is_empty()
|| !self.prompts.openclaw.prompts.is_empty()
|| !self.prompts.hermes.prompts.is_empty()
@@ -728,6 +768,7 @@ impl MultiAppConfig {
AppType::Claude,
AppType::Codex,
AppType::Gemini,
AppType::GrokBuild,
AppType::OpenCode,
AppType::OpenClaw,
AppType::Hermes,
@@ -801,6 +842,7 @@ impl MultiAppConfig {
AppType::ClaudeDesktop => &mut config.prompts.claude_desktop.prompts,
AppType::Codex => &mut config.prompts.codex.prompts,
AppType::Gemini => &mut config.prompts.gemini.prompts,
AppType::GrokBuild => &mut config.prompts.grokbuild.prompts,
AppType::OpenCode => &mut config.prompts.opencode.prompts,
AppType::OpenClaw => &mut config.prompts.openclaw.prompts,
AppType::Hermes => &mut config.prompts.hermes.prompts,
@@ -843,6 +885,7 @@ impl MultiAppConfig {
AppType::ClaudeDesktop => continue, // Claude Desktop 3P profiles don't use MCP here
AppType::Codex => &self.mcp.codex.servers,
AppType::Gemini => &self.mcp.gemini.servers,
AppType::GrokBuild => continue,
AppType::OpenCode => &self.mcp.opencode.servers,
AppType::OpenClaw => continue, // OpenClaw MCP is still in development, skip
AppType::Hermes => continue, // Hermes didn't exist in v3.6.x, skip
@@ -1133,6 +1176,30 @@ mod tests {
);
}
#[test]
#[serial]
fn auto_imports_grokbuild_prompt_on_first_launch() {
let _home = TempHome::new();
write_prompt_file(AppType::GrokBuild, "# Grok Build Prompt\n\nTest content");
let config = MultiAppConfig::load().expect("load config");
assert_eq!(config.prompts.grokbuild.prompts.len(), 1);
let prompt = config
.prompts
.grokbuild
.prompts
.values()
.next()
.expect("grokbuild prompt exists");
assert!(prompt.enabled, "grokbuild prompt should be enabled");
assert_eq!(prompt.content, "# Grok Build Prompt\n\nTest content");
assert_eq!(
prompt.description,
Some("Automatically imported on first launch".to_string())
);
}
#[test]
#[serial]
fn auto_imports_all_three_apps_prompts() {
+8 -2
View File
@@ -371,7 +371,7 @@ pub fn validate_direct_provider(provider: &Provider) -> Result<(), AppError> {
if matches!(
meta.provider_type.as_deref(),
Some("github_copilot") | Some("codex_oauth")
Some("github_copilot") | Some("codex_oauth") | Some("xai_oauth")
) {
return Err(AppError::localized(
"claude_desktop.provider.type_unsupported",
@@ -476,7 +476,12 @@ fn is_managed_oauth_proxy_provider(provider: &Provider) -> bool {
.meta
.as_ref()
.and_then(|meta| meta.provider_type.as_deref())
.is_some_and(|provider_type| matches!(provider_type, "github_copilot" | "codex_oauth"))
.is_some_and(|provider_type| {
matches!(
provider_type,
"github_copilot" | "codex_oauth" | "xai_oauth"
)
})
}
pub fn validate_provider(provider: &Provider) -> Result<(), AppError> {
@@ -1583,6 +1588,7 @@ mod tests {
for (provider_type, api_format) in [
("github_copilot", "openai_chat"),
("codex_oauth", "openai_responses"),
("xai_oauth", "openai_responses"),
] {
let provider = oauth_proxy_provider(provider_type, provider_type, api_format);
validate_proxy_provider(&provider).expect("oauth proxy provider should validate");
File diff suppressed because it is too large Load Diff
+4 -51
View File
@@ -6,6 +6,7 @@
use crate::codex_config::{
get_codex_config_dir, read_codex_config_text, CC_SWITCH_CODEX_MODEL_PROVIDER_ID,
};
use crate::codex_state_db::codex_state_db_paths;
use crate::config::{atomic_write, copy_file, get_app_config_dir};
use crate::database::{is_official_seed_id, Database};
use crate::error::AppError;
@@ -28,7 +29,6 @@ const MIGRATION_NAME: &str = "codex-history-provider-migration-v1";
const OFFICIAL_UNIFY_MIGRATION_NAME: &str = "codex-official-history-unify-v1";
/// 还原操作自身的备份目录(与迁移备份分开,保持迁移账本目录纯净)。
const OFFICIAL_UNIFY_RESTORE_BACKUP_NAME: &str = "codex-official-history-unify-restore-v1";
const CODEX_STATE_DB_FILENAME: &str = "state_5.sqlite";
/// SQLite 变量上限保守值,IN 列表按此分块。
const STATE_DB_ID_CHUNK: usize = 500;
@@ -1116,55 +1116,6 @@ fn migrate_codex_state_dbs(
Ok(migrated)
}
fn codex_state_db_paths(codex_dir: &Path, config_text: &str) -> Vec<PathBuf> {
let mut paths = Vec::new();
push_unique_path(&mut paths, codex_dir.join(CODEX_STATE_DB_FILENAME));
// Codex lets SQLite state move away from CODEX_HOME; config takes precedence.
if let Some(sqlite_home) = sqlite_home_from_codex_config(config_text) {
push_unique_path(&mut paths, sqlite_home.join(CODEX_STATE_DB_FILENAME));
} else if let Some(sqlite_home) = sqlite_home_from_env() {
push_unique_path(&mut paths, sqlite_home.join(CODEX_STATE_DB_FILENAME));
}
paths
}
fn push_unique_path(paths: &mut Vec<PathBuf>, path: PathBuf) {
if !paths.contains(&path) {
paths.push(path);
}
}
fn sqlite_home_from_codex_config(config_text: &str) -> Option<PathBuf> {
let doc = config_text.parse::<DocumentMut>().ok()?;
let raw = doc.get("sqlite_home")?.as_str()?.trim();
if raw.is_empty() {
return None;
}
Some(resolve_user_path(raw))
}
fn sqlite_home_from_env() -> Option<PathBuf> {
let raw = std::env::var("CODEX_SQLITE_HOME").ok()?;
let raw = raw.trim();
if raw.is_empty() {
return None;
}
Some(resolve_user_path(raw))
}
fn resolve_user_path(raw: &str) -> PathBuf {
if raw == "~" {
return crate::config::get_home_dir();
}
if let Some(rest) = raw.strip_prefix("~/") {
return crate::config::get_home_dir().join(rest);
}
if let Some(rest) = raw.strip_prefix("~\\") {
return crate::config::get_home_dir().join(rest);
}
PathBuf::from(raw)
}
fn migrate_codex_state_db_provider_bucket(
db_path: &Path,
codex_dir: &Path,
@@ -1329,6 +1280,7 @@ fn relative_backup_path(path: &Path, root: &Path) -> PathBuf {
#[cfg(test)]
mod tests {
use super::*;
use crate::codex_state_db::CODEX_STATE_DB_FILENAME;
use crate::provider::Provider;
use serial_test::serial;
use std::ffi::OsString;
@@ -2185,7 +2137,8 @@ base_url = "https://proxy.example/v1"
let env_sqlite_home = dir.path().join("env-sqlite-home");
let config_sqlite_home = dir.path().join("config-sqlite-home");
let _guard = EnvVarGuard::set("CODEX_SQLITE_HOME", &env_sqlite_home);
let config_text = format!("sqlite_home = \"{}\"\n", config_sqlite_home.display());
// TOML 字面量字符串(单引号)Windows 路径含反斜杠,basic string 会解析失败。
let config_text = format!("sqlite_home = '{}'\n", config_sqlite_home.display());
let paths = codex_state_db_paths(&codex_dir, &config_text);
+100
View File
@@ -0,0 +1,100 @@
//! Locating Codex's per-thread state SQLite databases.
//!
//! Codex stores thread metadata in `state_5.sqlite`, normally inside the Codex
//! config dir (`CODEX_HOME` / `~/.codex`). The SQLite location can be moved with
//! the `sqlite_home` key in `config.toml` or the `CODEX_SQLITE_HOME` env var;
//! when set, a second DB lives there. Both history migration and the session
//! list's title lookup need the same resolution, so it lives here once.
use std::path::{Path, PathBuf};
use toml_edit::DocumentMut;
use crate::config::get_home_dir;
/// Filename of Codex's per-thread state database. Codex bumps the version
/// number across releases; update this single source of truth when a new state
/// DB version ships.
pub(crate) const CODEX_STATE_DB_FILENAME: &str = "state_5.sqlite";
/// Env var that overrides the Codex SQLite state directory.
const CODEX_SQLITE_HOME_ENV: &str = "CODEX_SQLITE_HOME";
/// Resolve every candidate `state_5.sqlite` path: the config-dir DB plus, when
/// Codex is configured to keep its SQLite state elsewhere, that DB too.
///
/// `config_dir` is the Codex config dir (`~/.codex`); `config_text` is the raw
/// `config.toml` contents, used to detect a `sqlite_home` override.
pub(crate) fn codex_state_db_paths(config_dir: &Path, config_text: &str) -> Vec<PathBuf> {
let mut paths = Vec::new();
push_unique_path(&mut paths, config_dir.join(CODEX_STATE_DB_FILENAME));
// Codex lets SQLite state move away from CODEX_HOME; config takes precedence.
if let Some(sqlite_home) = sqlite_home_from_codex_config(config_text) {
push_unique_path(&mut paths, sqlite_home.join(CODEX_STATE_DB_FILENAME));
} else if let Some(sqlite_home) = sqlite_home_from_env() {
push_unique_path(&mut paths, sqlite_home.join(CODEX_STATE_DB_FILENAME));
}
paths
}
fn push_unique_path(paths: &mut Vec<PathBuf>, path: PathBuf) {
if !paths.contains(&path) {
paths.push(path);
}
}
fn sqlite_home_from_codex_config(config_text: &str) -> Option<PathBuf> {
let doc = config_text.parse::<DocumentMut>().ok()?;
let raw = doc.get("sqlite_home")?.as_str()?.trim();
if raw.is_empty() {
return None;
}
Some(resolve_user_path(raw))
}
fn sqlite_home_from_env() -> Option<PathBuf> {
let raw = std::env::var(CODEX_SQLITE_HOME_ENV).ok()?;
let raw = raw.trim();
if raw.is_empty() {
return None;
}
Some(resolve_user_path(raw))
}
fn resolve_user_path(raw: &str) -> PathBuf {
if raw == "~" {
return get_home_dir();
}
if let Some(rest) = raw.strip_prefix("~/") {
return get_home_dir().join(rest);
}
if let Some(rest) = raw.strip_prefix("~\\") {
return get_home_dir().join(rest);
}
PathBuf::from(raw)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn includes_config_sqlite_home() {
let temp = tempdir().expect("tempdir");
let sqlite_home = temp.path().join("sqlite-home");
// 用 TOML 字面量字符串(单引号)承载路径:Windows 路径含反斜杠,basic string(双引号)
// 会把 `\U`/`\s` 等当作非法转义导致解析失败。
let config_text = format!("sqlite_home = '{}'\n", sqlite_home.display());
let paths = codex_state_db_paths(temp.path(), &config_text);
assert_eq!(
paths,
vec![
temp.path().join(CODEX_STATE_DB_FILENAME),
sqlite_home.join(CODEX_STATE_DB_FILENAME),
]
);
}
}
+93
View File
@@ -2,13 +2,16 @@ use tauri::State;
use crate::commands::codex_oauth::CodexOAuthState;
use crate::commands::copilot::CopilotAuthState;
use crate::commands::xai_oauth::XaiOAuthState;
use crate::proxy::providers::codex_oauth_auth::CodexOAuthError;
use crate::proxy::providers::copilot_auth::{
CopilotAuthError, GitHubAccount, GitHubDeviceCodeResponse,
};
use crate::proxy::providers::xai_oauth_auth::{XaiOAuthAccount, XaiOAuthError};
const AUTH_PROVIDER_GITHUB_COPILOT: &str = "github_copilot";
const AUTH_PROVIDER_CODEX_OAUTH: &str = "codex_oauth";
const AUTH_PROVIDER_XAI_OAUTH: &str = "xai_oauth";
#[derive(Debug, Clone, serde::Serialize)]
pub struct ManagedAuthAccount {
@@ -19,6 +22,7 @@ pub struct ManagedAuthAccount {
pub authenticated_at: i64,
pub is_default: bool,
pub github_domain: String,
pub requires_reauth: bool,
}
#[derive(Debug, Clone, serde::Serialize)]
@@ -44,6 +48,7 @@ fn ensure_auth_provider(auth_provider: &str) -> Result<&'static str, String> {
match auth_provider {
AUTH_PROVIDER_GITHUB_COPILOT => Ok(AUTH_PROVIDER_GITHUB_COPILOT),
AUTH_PROVIDER_CODEX_OAUTH => Ok(AUTH_PROVIDER_CODEX_OAUTH),
AUTH_PROVIDER_XAI_OAUTH => Ok(AUTH_PROVIDER_XAI_OAUTH),
_ => Err(format!("Unsupported auth provider: {auth_provider}")),
}
}
@@ -61,6 +66,23 @@ fn map_account(
avatar_url: account.avatar_url,
authenticated_at: account.authenticated_at,
github_domain: account.github_domain,
requires_reauth: false,
}
}
fn map_xai_account(
account: XaiOAuthAccount,
default_account_id: Option<&str>,
) -> ManagedAuthAccount {
ManagedAuthAccount {
is_default: default_account_id == Some(account.id.as_str()),
id: account.id,
provider: AUTH_PROVIDER_XAI_OAUTH.to_string(),
login: account.login,
avatar_url: account.avatar_url,
authenticated_at: account.authenticated_at,
github_domain: account.github_domain,
requires_reauth: account.requires_reauth,
}
}
@@ -84,6 +106,7 @@ pub async fn auth_start_login(
github_domain: Option<String>,
copilot_state: State<'_, CopilotAuthState>,
codex_state: State<'_, CodexOAuthState>,
xai_state: State<'_, XaiOAuthState>,
) -> Result<ManagedAuthDeviceCodeResponse, String> {
let auth_provider = ensure_auth_provider(&auth_provider)?;
match auth_provider {
@@ -103,6 +126,14 @@ pub async fn auth_start_login(
.map_err(|e| e.to_string())?;
Ok(map_device_code_response(auth_provider, response))
}
AUTH_PROVIDER_XAI_OAUTH => {
let auth_manager = xai_state.0.read().await;
let response = auth_manager
.start_device_flow()
.await
.map_err(|e| e.to_string())?;
Ok(map_device_code_response(auth_provider, response))
}
_ => unreachable!(),
}
}
@@ -114,6 +145,7 @@ pub async fn auth_poll_for_account(
github_domain: Option<String>,
copilot_state: State<'_, CopilotAuthState>,
codex_state: State<'_, CodexOAuthState>,
xai_state: State<'_, XaiOAuthState>,
) -> Result<Option<ManagedAuthAccount>, String> {
let auth_provider = ensure_auth_provider(&auth_provider)?;
match auth_provider {
@@ -146,6 +178,18 @@ pub async fn auth_poll_for_account(
Err(e) => Err(e.to_string()),
}
}
AUTH_PROVIDER_XAI_OAUTH => {
let auth_manager = xai_state.0.write().await;
match auth_manager.poll_for_token(&device_code).await {
Ok(account) => {
let default_account_id = auth_manager.get_status().await.default_account_id;
Ok(account
.map(|account| map_xai_account(account, default_account_id.as_deref())))
}
Err(XaiOAuthError::AuthorizationPending) => Ok(None),
Err(e) => Err(e.to_string()),
}
}
_ => unreachable!(),
}
}
@@ -155,6 +199,7 @@ pub async fn auth_list_accounts(
auth_provider: String,
copilot_state: State<'_, CopilotAuthState>,
codex_state: State<'_, CodexOAuthState>,
xai_state: State<'_, XaiOAuthState>,
) -> Result<Vec<ManagedAuthAccount>, String> {
let auth_provider = ensure_auth_provider(&auth_provider)?;
match auth_provider {
@@ -178,6 +223,16 @@ pub async fn auth_list_accounts(
.map(|account| map_account(auth_provider, account, default_account_id.as_deref()))
.collect())
}
AUTH_PROVIDER_XAI_OAUTH => {
let auth_manager = xai_state.0.read().await;
let status = auth_manager.get_status().await;
let default_account_id = status.default_account_id.clone();
Ok(status
.accounts
.into_iter()
.map(|account| map_xai_account(account, default_account_id.as_deref()))
.collect())
}
_ => unreachable!(),
}
}
@@ -187,6 +242,7 @@ pub async fn auth_get_status(
auth_provider: String,
copilot_state: State<'_, CopilotAuthState>,
codex_state: State<'_, CodexOAuthState>,
xai_state: State<'_, XaiOAuthState>,
) -> Result<ManagedAuthStatus, String> {
let auth_provider = ensure_auth_provider(&auth_provider)?;
match auth_provider {
@@ -226,6 +282,22 @@ pub async fn auth_get_status(
.collect(),
})
}
AUTH_PROVIDER_XAI_OAUTH => {
let auth_manager = xai_state.0.read().await;
let status = auth_manager.get_status().await;
let default_account_id = status.default_account_id.clone();
Ok(ManagedAuthStatus {
provider: auth_provider.to_string(),
authenticated: status.authenticated,
default_account_id: default_account_id.clone(),
migration_error: None,
accounts: status
.accounts
.into_iter()
.map(|account| map_xai_account(account, default_account_id.as_deref()))
.collect(),
})
}
_ => unreachable!(),
}
}
@@ -236,6 +308,7 @@ pub async fn auth_remove_account(
account_id: String,
copilot_state: State<'_, CopilotAuthState>,
codex_state: State<'_, CodexOAuthState>,
xai_state: State<'_, XaiOAuthState>,
) -> Result<(), String> {
let auth_provider = ensure_auth_provider(&auth_provider)?;
match auth_provider {
@@ -253,6 +326,13 @@ pub async fn auth_remove_account(
.await
.map_err(|e| e.to_string())
}
AUTH_PROVIDER_XAI_OAUTH => {
let auth_manager = xai_state.0.write().await;
auth_manager
.remove_account(&account_id)
.await
.map_err(|e| e.to_string())
}
_ => unreachable!(),
}
}
@@ -263,6 +343,7 @@ pub async fn auth_set_default_account(
account_id: String,
copilot_state: State<'_, CopilotAuthState>,
codex_state: State<'_, CodexOAuthState>,
xai_state: State<'_, XaiOAuthState>,
) -> Result<(), String> {
let auth_provider = ensure_auth_provider(&auth_provider)?;
match auth_provider {
@@ -280,6 +361,13 @@ pub async fn auth_set_default_account(
.await
.map_err(|e| e.to_string())
}
AUTH_PROVIDER_XAI_OAUTH => {
let auth_manager = xai_state.0.write().await;
auth_manager
.set_default_account(&account_id)
.await
.map_err(|e| e.to_string())
}
_ => unreachable!(),
}
}
@@ -289,6 +377,7 @@ pub async fn auth_logout(
auth_provider: String,
copilot_state: State<'_, CopilotAuthState>,
codex_state: State<'_, CodexOAuthState>,
xai_state: State<'_, XaiOAuthState>,
) -> Result<(), String> {
let auth_provider = ensure_auth_provider(&auth_provider)?;
match auth_provider {
@@ -300,6 +389,10 @@ pub async fn auth_logout(
let auth_manager = codex_state.0.write().await;
auth_manager.clear_auth().await.map_err(|e| e.to_string())
}
AUTH_PROVIDER_XAI_OAUTH => {
let auth_manager = xai_state.0.write().await;
auth_manager.clear_auth().await.map_err(|e| e.to_string())
}
_ => unreachable!(),
}
}
+3 -2
View File
@@ -49,13 +49,14 @@ pub async fn get_codex_oauth_quota(
}
};
Ok(query_codex_quota(
// 瞬时传输失败以 Err 传播(前端 reject → retry + 保留上次成功值)。
query_codex_quota(
&token,
Some(&id),
"codex_oauth",
"Codex OAuth access token expired or rejected. Please re-login via cc-switch.",
)
.await)
.await
}
/// 获取 Codex OAuth (ChatGPT Plus/Pro) 可用模型列表
+7
View File
@@ -7,12 +7,19 @@ pub async fn get_coding_plan_quota(
// 火山方舟用控制面 AK/SK 签名查询用量;其他供应商不传,沿用 api_key。
access_key_id: Option<String>,
secret_access_key: Option<String>,
// 智谱团队版(zhipu_team)靠显式标识路由(base_url 与个人版相同无法区分)。
coding_plan_provider: Option<String>,
team_organization_id: Option<String>,
team_project_id: Option<String>,
) -> Result<SubscriptionQuota, String> {
crate::services::coding_plan::get_coding_plan_quota(
&base_url,
&api_key,
access_key_id.as_deref(),
secret_access_key.as_deref(),
coding_plan_provider.as_deref(),
team_organization_id.as_deref(),
team_project_id.as_deref(),
)
.await
}
+28
View File
@@ -99,6 +99,15 @@ pub async fn get_config_status(
Ok(ConfigStatus { exists, path })
}
AppType::GrokBuild => {
let config_path = crate::grok_config::get_grok_config_path();
let exists = config_path.exists();
let path = crate::grok_config::get_grok_config_dir()
.to_string_lossy()
.to_string();
Ok(ConfigStatus { exists, path })
}
AppType::OpenCode => {
let config_path = crate::opencode_config::get_opencode_config_path();
let exists = config_path.exists();
@@ -143,6 +152,7 @@ pub async fn get_config_dir(app: String) -> Result<String, String> {
}
AppType::Codex => codex_config::get_codex_config_dir(),
AppType::Gemini => crate::gemini_config::get_gemini_dir(),
AppType::GrokBuild => crate::grok_config::get_grok_config_dir(),
AppType::OpenCode => crate::opencode_config::get_opencode_dir(),
AppType::OpenClaw => crate::openclaw_config::get_openclaw_dir(),
AppType::Hermes => crate::hermes_config::get_hermes_dir(),
@@ -160,6 +170,7 @@ pub async fn open_config_folder(handle: AppHandle, app: String) -> Result<bool,
}
AppType::Codex => codex_config::get_codex_config_dir(),
AppType::Gemini => crate::gemini_config::get_gemini_dir(),
AppType::GrokBuild => crate::grok_config::get_grok_config_dir(),
AppType::OpenCode => crate::opencode_config::get_opencode_dir(),
AppType::OpenClaw => crate::openclaw_config::get_openclaw_dir(),
AppType::Hermes => crate::hermes_config::get_hermes_dir(),
@@ -275,6 +286,23 @@ pub async fn get_common_config_snippet(
.map_err(|e| e.to_string())
}
/// 对前端编辑器里的 config.toml 文本做通用配置片段的合并/剥离。
/// 放后端是为了走 toml_edit(保注释、保键序);前端 smol-toml 的
/// 整文档重序列化会破坏用户手写格式。
#[tauri::command]
pub async fn update_toml_common_config_snippet(
config_toml: String,
snippet_toml: String,
enabled: bool,
) -> Result<String, String> {
crate::services::provider::update_toml_common_config_snippet(
&config_toml,
&snippet_toml,
enabled,
)
.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn set_common_config_snippet(
app_type: String,
+1 -1
View File
@@ -8,7 +8,7 @@ use tauri::State;
/// Parse a deep link URL and return the parsed request for frontend confirmation
#[tauri::command]
pub fn parse_deeplink(url: String) -> Result<DeepLinkImportRequest, String> {
log::info!("Parsing deep link URL: {url}");
log::info!("Parsing deep link URL: {}", crate::url_for_log(&url));
parse_deeplink_url(&url).map_err(|e| e.to_string())
}
+1 -7
View File
@@ -197,11 +197,5 @@ pub async fn toggle_mcp_app(
/// 从所有应用导入 MCP 服务器(复用已有的导入逻辑)
#[tauri::command]
pub async fn import_mcp_from_apps(state: State<'_, AppState>) -> Result<usize, String> {
let mut total = 0;
total += McpService::import_from_claude(&state).unwrap_or(0);
total += McpService::import_from_codex(&state).unwrap_or(0);
total += McpService::import_from_gemini(&state).unwrap_or(0);
total += McpService::import_from_opencode(&state).unwrap_or(0);
total += McpService::import_from_hermes(&state).unwrap_or(0);
Ok(total)
McpService::import_from_all_apps(&state).map_err(|e| e.to_string())
}
+378 -53
View File
@@ -111,8 +111,8 @@ pub struct ToolVersion {
wsl_distro: Option<String>,
}
const VALID_TOOLS: [&str; 6] = [
"claude", "codex", "gemini", "opencode", "openclaw", "hermes",
const VALID_TOOLS: [&str; 7] = [
"claude", "codex", "gemini", "grok", "opencode", "openclaw", "hermes",
];
#[derive(Debug, Clone, serde::Deserialize)]
@@ -424,6 +424,7 @@ fn tool_display_name(tool: &str) -> &'static str {
"claude" => "Claude Code",
"codex" => "Codex",
"gemini" => "Gemini CLI",
"grok" => "Grok Build",
"opencode" => "OpenCode",
"openclaw" => "OpenClaw",
"hermes" => "Hermes",
@@ -440,6 +441,8 @@ const CLAUDE_INSTALL_UNIX: &str =
"bash -c 'tmp=$(mktemp) && curl -fsSL https://claude.ai/install.sh -o $tmp && bash $tmp; status=$?; rm -f $tmp; exit $status'";
const OPENCODE_INSTALL_UNIX: &str =
"bash -c 'tmp=$(mktemp) && curl -fsSL https://opencode.ai/install -o $tmp && bash $tmp; status=$?; rm -f $tmp; exit $status'";
const GROK_INSTALL_UNIX: &str =
"bash -c 'tmp=$(mktemp) && curl -fsSL https://x.ai/cli/install.sh -o $tmp && bash $tmp; status=$?; rm -f $tmp; exit $status'";
/// Hermes 官方安装器会自带/选择合适的 Python 运行时。不要再用
/// `python3 -m pip ... || python -m pip ...`:Hermes PyPI 包要求 Python >=3.11,
@@ -453,6 +456,8 @@ const HERMES_UPDATE_UNIX: &str =
#[cfg(target_os = "windows")]
const HERMES_INSTALL_WINDOWS_SCRIPT: &str =
"irm https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.ps1 | iex";
#[cfg(target_os = "windows")]
const GROK_INSTALL_WINDOWS_SCRIPT: &str = "irm https://x.ai/cli/install.ps1 | iex";
#[cfg(target_os = "windows")]
fn powershell_encoded_command(script: &str) -> String {
@@ -473,6 +478,14 @@ fn hermes_install_windows_command() -> String {
)
}
#[cfg(target_os = "windows")]
fn grok_install_windows_command() -> String {
format!(
"powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand {}",
powershell_encoded_command(GROK_INSTALL_WINDOWS_SCRIPT)
)
}
#[cfg(target_os = "windows")]
fn hermes_update_windows_command() -> String {
// fallback 是 powershell.exe,不是 .cmd/.bat;这里不需要 `call`。PowerShell 的
@@ -492,6 +505,7 @@ fn npm_install_command_for(tool: &str) -> Option<&'static str> {
"claude" => Some("npm i -g @anthropic-ai/claude-code@latest"),
"codex" => Some("npm i -g @openai/codex@latest"),
"gemini" => Some("npm i -g @google/gemini-cli@latest"),
"grok" => Some("npm i -g @xai-official/grok@latest"),
"opencode" => Some("npm i -g opencode-ai@latest"),
"openclaw" => Some("npm i -g openclaw@latest"),
_ => None,
@@ -500,7 +514,7 @@ fn npm_install_command_for(tool: &str) -> Option<&'static str> {
fn official_update_args(tool: &str) -> Option<&'static str> {
match tool {
"claude" | "codex" | "hermes" => Some("update"),
"claude" | "codex" | "grok" | "hermes" => Some("update"),
"openclaw" => Some("update --yes"),
"opencode" => Some("upgrade"),
_ => None,
@@ -532,6 +546,20 @@ fn tool_action_shell_command_for_shell(
action: ToolLifecycleAction,
shell: LifecycleCommandShell,
) -> Option<String> {
// xAI's primary Windows distribution is the native PowerShell installer.
// Keep npm as the network/policy fallback, matching the POSIX installer chain.
#[cfg(target_os = "windows")]
if tool == "grok"
&& matches!(action, ToolLifecycleAction::Install)
&& matches!(shell, LifecycleCommandShell::WindowsBatch)
{
return Some(chain_update_commands(
grok_install_windows_command(),
npm_install_command_for(tool)?.to_string(),
shell,
));
}
if tool == "hermes" {
return Some(
match (action, shell) {
@@ -638,7 +666,7 @@ fn build_tool_action_line(
// (npm/pnpm)或 .exe(volta),静态命令头部是 `npm`(也是 .cmd)、`py` 等——
// 全部加 `call ` 前缀,风格统一且语义正确。含空格的头部已被 `win_quote_path_for_batch`
// 加上双引号,call 对带引号的路径解析正常。
return Ok(format!("call {command}"));
Ok(format!("call {command}"))
}
#[cfg(not(target_os = "windows"))]
@@ -762,6 +790,7 @@ async fn get_single_tool_version_impl(
}
"codex" => fetch_npm_latest_for_tool(&client, "@openai/codex", tool, local).await,
"gemini" => fetch_npm_latest_for_tool(&client, "@google/gemini-cli", tool, local).await,
"grok" => fetch_npm_latest_for_tool(&client, "@xai-official/grok", tool, local).await,
"opencode" => {
if let Some(version) =
fetch_npm_latest_for_tool(&client, "opencode-ai", tool, local).await
@@ -1069,6 +1098,8 @@ fn default_flag_for_shell(shell: &str) -> &'static str {
}
}
// 以下 shell 解析辅助函数仅被 macOS/Linux 的终端启动逻辑使用;Windows 非 test 编译下为死代码。
#[cfg_attr(windows, allow(dead_code))]
fn fallback_user_shell() -> &'static str {
if cfg!(target_os = "macos") {
"/bin/zsh"
@@ -1077,6 +1108,7 @@ fn fallback_user_shell() -> &'static str {
}
}
#[cfg_attr(windows, allow(dead_code))]
fn valid_user_shell_path(shell: &str) -> bool {
if shell.is_empty()
|| !shell.starts_with('/')
@@ -1100,11 +1132,13 @@ fn is_executable_file(path: &std::path::Path) -> bool {
}
#[cfg(not(unix))]
#[cfg_attr(windows, allow(dead_code))]
fn is_executable_file(path: &std::path::Path) -> bool {
path.is_file()
}
/// 获取用户默认 shell 的完整路径;异常或被污染的 SHELL 回退到平台默认值。
#[cfg_attr(windows, allow(dead_code))]
fn get_user_shell() -> String {
std::env::var("SHELL")
.ok()
@@ -1113,6 +1147,7 @@ fn get_user_shell() -> String {
}
/// 构建 exec 行:引号保护 shell 路径,交还用户 shell 让其按默认规则加载 rc 配置。
#[cfg_attr(windows, allow(dead_code))]
fn build_exec_line(shell: &str, cwd: Option<&Path>) -> String {
let quoted_shell = shell_single_quote(shell);
@@ -1132,6 +1167,7 @@ fn build_exec_line(shell: &str, cwd: Option<&Path>) -> String {
}
/// 构建 provider 命令行:通过用户 shell 的交互模式执行,确保 GUI 启动的终端也加载用户 PATH。
#[cfg_attr(windows, allow(dead_code))]
fn build_provider_command_line(shell: &str, config_path: &str, cwd: Option<&Path>) -> String {
let claude_command = format!("claude --settings {}", shell_single_quote(config_path));
let command = cwd
@@ -1152,6 +1188,7 @@ fn build_provider_command_line(shell: &str, config_path: &str, cwd: Option<&Path
)
}
#[cfg_attr(windows, allow(dead_code))]
fn provider_command_flag_for_shell(shell: &str) -> &'static str {
match shell.rsplit('/').next().unwrap_or(shell) {
"dash" | "sh" => "-c",
@@ -1160,6 +1197,7 @@ fn provider_command_flag_for_shell(shell: &str) -> &'static str {
}
}
#[cfg_attr(windows, allow(dead_code))]
fn build_final_shell_cd_command(shell: &str, cwd: Option<&Path>) -> String {
if matches!(shell.rsplit('/').next().unwrap_or(shell), "zsh") {
return String::new();
@@ -1442,6 +1480,22 @@ fn opencode_extra_search_paths(
paths
}
/// Grok's official installer writes the launcher to `$GROK_BIN_DIR` or, by
/// default, `~/.grok/bin`. Keep these ahead of generic npm/Node locations so
/// version probing and anchored updates can see the native distribution even
/// when the GUI process inherited a stale PATH.
fn grok_extra_search_paths(
home: &Path,
grok_bin_dir: Option<std::ffi::OsString>,
) -> Vec<std::path::PathBuf> {
let mut paths = Vec::new();
push_env_single_dir(&mut paths, grok_bin_dir);
if !home.as_os_str().is_empty() {
push_unique_path(&mut paths, home.join(".grok").join("bin"));
}
paths
}
fn tool_executable_candidates(tool: &str, dir: &Path) -> Vec<std::path::PathBuf> {
#[cfg(target_os = "windows")]
{
@@ -1491,6 +1545,12 @@ fn build_tool_search_paths(tool: &str) -> Vec<std::path::PathBuf> {
// 常见的安装路径(原生安装优先)
let mut search_paths: Vec<std::path::PathBuf> = Vec::new();
if tool == "grok" {
let extra_paths = grok_extra_search_paths(&home, std::env::var_os("GROK_BIN_DIR"));
for path in extra_paths {
push_unique_path(&mut search_paths, path);
}
}
if !home.as_os_str().is_empty() {
push_unique_path(&mut search_paths, home.join(".local/bin"));
push_unique_path(&mut search_paths, home.join(".npm-global/bin"));
@@ -1936,6 +1996,7 @@ fn npm_package_for(tool: &str) -> Option<&'static str> {
"claude" => Some("@anthropic-ai/claude-code"),
"codex" => Some("@openai/codex"),
"gemini" => Some("@google/gemini-cli"),
"grok" => Some("@xai-official/grok"),
"opencode" => Some("opencode-ai"),
"openclaw" => Some("openclaw"),
_ => None,
@@ -1974,6 +2035,19 @@ fn brew_formula_from_path(real: &str) -> Option<String> {
None
}
/// xAI's native installer uses `~/.grok/bin` for its launchers and
/// `~/.grok/downloads/grok-<platform>` for the downloaded binary. The launcher
/// directory also covers the default Windows layout, where the executable is
/// copied instead of symlinked. Checking the real target additionally supports
/// a custom `$GROK_BIN_DIR` on POSIX, whose launcher still points into the
/// standard downloads directory.
fn is_grok_native_install(bin_path: &str, real_target: &str) -> bool {
[bin_path, real_target].iter().any(|path| {
let normalized = path.replace('\\', "/").to_ascii_lowercase();
normalized.contains("/.grok/bin/") || normalized.contains("/.grok/downloads/grok-")
})
}
/// 含空格才用 POSIX 单引号包一层,否则保持裸路径——命令展示更干净。
/// claude / brew / volta / bun / npm 五个锚定分支共用,避免"含空格"判定漂移。
///
@@ -2099,6 +2173,28 @@ fn sibling_bin(bin_path: &str, exe: &str) -> Option<String> {
}
}
/// Build an npm command anchored to the same Node installation as `bin_path`.
///
/// npm's POSIX launcher is normally a JavaScript file with an
/// `#!/usr/bin/env node` shebang. Calling npm by absolute path therefore does
/// not make it independent of PATH: `env` still needs to find `node`. GUI apps
/// commonly inherit only the system PATH, while nvm/fnm/mise keep Node in a
/// user directory. Prefixing npm's sibling directory makes both npm and its
/// transitive Node interpreter resolve to the installation we selected.
#[cfg(not(target_os = "windows"))]
fn anchored_npm_command(bin_path: &str, args: &str) -> Option<String> {
let dir = parent_dir(bin_path);
if dir.is_empty() {
return None;
}
let npm = sibling_bin(bin_path, "npm")?;
Some(format!(
"PATH={}:\"$PATH\" {} {args}",
shell_single_quote(&dir),
quote_path_if_spaced(&npm)
))
}
#[cfg(not(target_os = "windows"))]
fn anchored_official_update_command(tool: &str, bin_path: &str) -> Option<String> {
official_update_args(tool).map(|args| format!("{} {args}", quote_path_if_spaced(bin_path)))
@@ -2145,9 +2241,10 @@ fn prefers_official_update(tool: &str, shell: LifecycleCommandShell) -> bool {
/// 二进制。唯一实测可靠的修复是先 `uninstall` 清掉残骸、再 `install` 装回完整的主包 + 平台二进制
/// (实测输出 `added 2 packages`)。
///
/// 锚定到与 codex 入口同目录的 npm(与升级路径一致,不依赖 GUI 非登录进程的 PATH)。`|| true`
/// 让 uninstall 失败(如 nvm 上对半损坏包静默返回非 0)不触发外层 `set -e` 中止,但随后的
/// install 失败仍会被 `set -e` 捕获并上报给前端 toast。
/// 锚定到与 codex 入口同目录的 npm,并把该目录放到每条 npm 命令的 PATH 首位,使 npm
/// 的 `#!/usr/bin/env node` 能找到同一安装下的 node,不依赖 GUI 非登录进程的 PATH。
/// `|| true` 让 uninstall 失败(如 nvm 上对半损坏包静默返回非 0)不触发外层 `set -e`
/// 中止,但随后的 install 若失败仍会被 `set -e` 捕获并上报给前端 toast。
///
/// **仅对会锚定到 sibling npm 的 node 管理器来源(nvm/fnm/mise/homebrew npm)生效**
/// `runnable=false` 是宽信号(权限 / node 版本 / 任意 `--version` 失败皆可触发),非 npm
@@ -2174,12 +2271,10 @@ fn codex_repair_command(bin_path: &str, real: &str) -> Option<String> {
) {
return None;
}
let npm = sibling_bin(bin_path, "npm")?;
let npm = quote_path_if_spaced(&npm);
let pkg = "@openai/codex";
Some(format!(
"{npm} uninstall -g {pkg} || true; {npm} i -g {pkg}@latest"
))
let uninstall = anchored_npm_command(bin_path, &format!("uninstall -g {pkg}"))?;
let install = anchored_npm_command(bin_path, &format!("i -g {pkg}@latest"))?;
Some(format!("{uninstall} || true; {install}"))
}
/// Windows 暂不做平台分发自愈:Windows 上 codex 的破坏模式不同(EPERM 文件锁 / 版本 bump
@@ -2219,8 +2314,7 @@ fn package_manager_anchored_command_from_paths(
// self-update,上层会直接锚到 CLI 自身;否则返回 None 走静态兜底。
_ => return None,
}
let npm = sibling_bin(bin_path, "npm")?;
Some(format!("{} i -g {pkg}@latest", quote_path_if_spaced(&npm)))
anchored_npm_command(bin_path, &format!("i -g {pkg}@latest"))
}
/// 给定工具、原始 bin 路径(命令行命中的入口)、canonicalize 后的真身路径,
@@ -2229,7 +2323,8 @@ fn package_manager_anchored_command_from_paths(
/// 便于单测覆盖各包管理器分支。Windows 版同名函数因 sibling 扩展名歧义必须读 fs,
/// 是刻意保留的平台差异(详见 Windows 版本 doc)。
///
/// **关键不变量:返回的命令必须用绝对路径调用执行体,不依赖 PATH**。
/// **关键不变量:返回的命令必须用绝对路径调用执行体;若执行体通过
/// `#!/usr/bin/env` 查找解释器,还必须把其同级 bin 目录显式放到 PATH 首位**。
/// 这条命令最终在 `run_tool_lifecycle_silently` 的非登录 `bash -c` 里执行——
/// GUI App 启动的进程 PATH 由 launchd / Windows Service / systemd 给,通常**不含**
/// `~/.local/bin` / `/opt/homebrew/bin` / `~/.volta/bin` 等用户级 bin 目录;而探测
@@ -2241,14 +2336,15 @@ fn package_manager_anchored_command_from_paths(
/// 判定顺序(命中即返回):
/// ① Hermes → `<bin_path 绝对> update`;Hermes CLI 自己知道安装环境,避免 cc-switch
/// 猜系统 `python3`/`python` 时撞上 Python 版本或 pyenv shim 问题。
/// ② Claude 原生安装器(`~/.local/share/claude/versions/`→ `<bin_path 绝对> update`
/// ② Claude / Grok 原生安装器 → `<bin_path 绝对> update`
/// bin_path 指向 launcher,launcher 内部 dispatch update 子命令。它不归 npm 管,
/// 且在 PATH 里比 nvm/homebrew 更靠前,用 npm 升级会装到别处且被原生那份遮蔽。
/// ③ Homebrew formula(真身在 `Cellar/<formula>/`)→ `<bin_path 同目录>/brew upgrade <formula>`;
/// formula 由 Homebrew 拥有,避免 self-update 尝试改动包管理器管理的安装。
/// ④ 其余支持官方自升级的工具 → `<bin_path 绝对> update/upgrade || <原锚定包管理器命令>`
/// Codex 的 self-update 只在部分 release 可用,所以保留 npm/brew/bun/volta fallback。
/// ⑤ 不支持官方自升级的 npm 全局包(例如 Gemini CLI) → 锚定到"那处 bin 目录的 npm"。
/// ⑤ 不支持官方自升级的 npm 全局包(例如 Gemini CLI,以及非 native 的 Grok Build) → 锚定到
/// "那处 bin 目录的 npm"。
#[cfg(not(target_os = "windows"))]
fn anchored_command_from_paths(tool: &str, bin_path: &str, real_target: &str) -> Option<String> {
let real_lower = real_target.to_ascii_lowercase();
@@ -2262,6 +2358,9 @@ fn anchored_command_from_paths(tool: &str, bin_path: &str, real_target: &str) ->
{
return anchored_official_update_command(tool, bin_path);
}
if tool == "grok" && is_grok_native_install(bin_path, real_target) {
return anchored_official_update_command(tool, bin_path);
}
let package_command = package_manager_anchored_command_from_paths(tool, bin_path, real_target);
if brew_formula_from_path(real_target).is_some() {
return package_command;
@@ -2309,7 +2408,7 @@ fn package_manager_anchored_command_from_paths(tool: &str, bin_path: &str) -> Op
/// Windows 版锚定命令生成。对平台确认可静默运行的工具优先使用官方 CLI 自升级;
/// 对 npm/Volta/pnpm 这类可确认写回位置的安装,再接一个包管理器 fallback。不存在 brew/bun/claude-native
/// (Windows 没 Homebrew、Bun for Windows 仍 preview、claude.ai/install.sh 是 bash 脚本)。
/// (Windows 没 Homebrew、Bun for Windows 仍 previewGrok native 使用 PowerShell installer)。
/// Scoop/Chocolatey/winget/nvm-windows/MS Store node 都归 npm 类——它们都只是"如何装
/// node"的不同入口,全局包真正的 idiom 仍是 sibling `npm.cmd`。
///
@@ -2318,16 +2417,15 @@ fn package_manager_anchored_command_from_paths(tool: &str, bin_path: &str) -> Op
/// 装 `.cmd`、Volta 装 `.exe`,纯字符串拼接无法消歧。这一平台差异**被刻意保留**:
/// 测试用 tempdir 隔离 fs,生产侧 TOCTOU 是 by design(见 `sibling_bin_with_ext` doc)。
///
/// `_real_target` 占位维持与 POSIX 版的签名对称——Windows 上未观测到需要真身路径
/// 区分的等价类(无 Cellar、无 claude-native installer)。若未来加 Scoop persist 锚定
/// (scoop 装的工具真身在 `<scoop_root>/persist/<app>/...`),从这里启用 `_real_target`。
/// `real_target` 维持与 POSIX 版的签名对称,并辅助识别 Grok native。若未来加 Scoop
/// persist 锚定(scoop 装的工具真身在 `<scoop_root>/persist/<app>/...`),也从这里取真身。
///
/// **关键不变量同 POSIX 版:返回的命令必须用绝对路径,不依赖 PATH**。Windows GUI
/// 进程 PATH 由 Service Control Manager / explorer.exe 给,通常不含用户 `%LOCALAPPDATA%`
/// 下的 Volta/pnpm 路径;`$SHELL -lic` 的探测时 PATH 与执行时 PATH 不对称。
///
/// 判定顺序(命中即返回):
/// ① hermes → `<bin_path> update`;Hermes CLI 自己处理安装环境。
/// ① hermes / Grok native → `<bin_path> update`;CLI 自己处理安装环境。
/// ② 支持官方自升级且 Windows 可安全静默执行的工具 → `<bin_path> update/upgrade || call <包管理器 fallback>`。
/// ③ 其余 npm 工具 → sibling `npm.cmd`/`.exe` i -g <pkg>@latest。
///
@@ -2335,10 +2433,13 @@ fn package_manager_anchored_command_from_paths(tool: &str, bin_path: &str) -> Op
/// 扩展名存在时,支持官方自升级的工具仍返回 `<bin_path> update/upgrade`,其余工具
/// 才返 None 让上游兜回静态命令、`anchored=false`。
#[cfg(target_os = "windows")]
fn anchored_command_from_paths(tool: &str, bin_path: &str, _real_target: &str) -> Option<String> {
fn anchored_command_from_paths(tool: &str, bin_path: &str, real_target: &str) -> Option<String> {
if tool == "hermes" {
return anchored_official_update_command(tool, bin_path);
}
if tool == "grok" && is_grok_native_install(bin_path, real_target) {
return anchored_official_update_command(tool, bin_path);
}
let package_command = package_manager_anchored_command_from_paths(tool, bin_path);
if prefers_official_update(tool, LifecycleCommandShell::WindowsBatch) {
let update = anchored_official_update_command(tool, bin_path)?;
@@ -2409,17 +2510,17 @@ fn static_fallback_command(tool: &str) -> String {
///
/// 设计理由:
/// - install 没有锚点可言(从无到有),但**有"上游推荐方式"这一事实** ——
/// Anthropic 和 SST(OpenCode)都已将自家 native installer 列为首推、把 npm 列为传统方式。
/// Anthropic、xAI 和 SST(OpenCode)都已将自家 native installer 列为首推、把 npm 列为替代方式。
/// 把这层认知补进来,让 install 表与 update 端的锚定决策树共用同一份"上游事实"。
/// - Hermes 使用官方 installer,避免用系统 Python/pip 安装时踩 Python >=3.11 与 pyenv
/// `python` shim 问题;更新路径若能锚定已安装 CLI,则走 `<hermes> update`。
/// **Hermes 没有 npm 包,install 端不享受 `||` 降级**——上游 installer 不可达就只能等。
/// - 对**有 npm 包**的工具(claude/opencode),短路链(POSIX `||`)保证官方脚本不可达/
/// - 对**有 npm 包**的工具(claude/grok/opencode),短路链(POSIX `||`)保证官方脚本不可达/
/// 防火墙拦截时仍能装上,降级到裸 `npm i -g`。官方脚本本身不用 pipe,
/// 所以这条路径在 WSL 的 `sh -c` 子 shell 中也不依赖外层 `pipefail`。
/// - Windows 原生不启用:claude.ai/install.sh、opencode.ai/install 都是 bash 脚本,
/// Windows 原生继续走 `tool_action_shell_command` 的 npm/PowerShell 命令;WSL 作为
/// Linux 环境复用这套 POSIX 安装优先级。
/// - Windows 上 Claude/OpenCode 原生不启用(对应 installer 都是 bash 脚本);Grok
/// 使用官方 PowerShell installer,并同样保留 npm fallback。WSL 作为 Linux 环境
/// 复用这套 POSIX 安装优先级。
fn installer_with_npm_fallback(installer: &str, tool: &str) -> String {
match npm_install_command_for(tool) {
Some(npm) => chain_update_commands(
@@ -2434,6 +2535,7 @@ fn installer_with_npm_fallback(installer: &str, tool: &str) -> String {
fn posix_install_command_for(tool: &str) -> String {
match tool {
"claude" => installer_with_npm_fallback(CLAUDE_INSTALL_UNIX, tool),
"grok" => installer_with_npm_fallback(GROK_INSTALL_UNIX, tool),
"opencode" => installer_with_npm_fallback(OPENCODE_INSTALL_UNIX, tool),
"hermes" => HERMES_INSTALL_UNIX.to_string(),
_ => static_fallback_command_for(tool, ToolLifecycleAction::Install),
@@ -2544,6 +2646,7 @@ fn wsl_distro_for_tool(tool: &str) -> Option<String> {
"claude" => crate::settings::get_claude_override_dir(),
"codex" => crate::settings::get_codex_override_dir(),
"gemini" => crate::settings::get_gemini_override_dir(),
"grok" => crate::settings::get_grok_override_dir(),
"opencode" => crate::settings::get_opencode_override_dir(),
"openclaw" => crate::settings::get_openclaw_override_dir(),
"hermes" => crate::settings::get_hermes_override_dir(),
@@ -2731,7 +2834,7 @@ fn launch_terminal_with_env(
#[cfg(target_os = "windows")]
{
launch_windows_terminal(&temp_dir, &config_file, cwd)?;
return Ok(());
Ok(())
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
@@ -3252,6 +3355,7 @@ del \"%~f0\" >nul 2>&1
result
}
#[cfg_attr(windows, allow(dead_code))]
fn shell_single_quote(value: &str) -> String {
format!("'{}'", value.replace('\'', "'\"'\"'"))
}
@@ -3650,6 +3754,39 @@ mod tests {
assert_eq!(extract_version("no version here"), "no version here");
}
#[test]
fn grok_lifecycle_metadata_is_consistent() {
let requested = vec!["unsupported".to_string(), "grok".to_string()];
assert_eq!(normalize_requested_tools(&requested), vec!["grok"]);
assert_eq!(tool_display_name("grok"), "Grok Build");
assert_eq!(npm_package_for("grok"), Some("@xai-official/grok"));
assert_eq!(
npm_install_command_for("grok"),
Some("npm i -g @xai-official/grok@latest")
);
assert_eq!(official_update_args("grok"), Some("update"));
for action in [ToolLifecycleAction::Install, ToolLifecycleAction::Update] {
assert_eq!(
tool_action_shell_command_for_shell("grok", action, LifecycleCommandShell::Posix)
.as_deref(),
Some("npm i -g @xai-official/grok@latest")
);
}
// Static update remains package-manager based. Only a path positively
// identified as xAI's native install may run `grok update`.
assert_eq!(
tool_action_shell_command_for_shell(
"grok",
ToolLifecycleAction::Update,
LifecycleCommandShell::WindowsBatch,
)
.as_deref(),
Some("npm i -g @xai-official/grok@latest")
);
}
#[test]
fn test_compare_semver() {
use std::cmp::Ordering;
@@ -3838,11 +3975,8 @@ mod tests {
let (_dir, sub, bin_path) = setup_sibling("Volta", "codex.cmd", &["volta.exe"]);
let cmd = anchored_command_from_paths("codex", &bin_path, &bin_path);
let volta_full = format!("{}\\volta.exe", sub.to_string_lossy());
let expected = format!(
"{} update || call {} install @openai/codex",
expect_quoted_path(&bin_path),
expect_quoted_path(&volta_full)
);
// codex 自 5092fe51 起不在 prefers_official_update:直接锚定包管理器,无 `codex update ||` 前缀。
let expected = format!("{} install @openai/codex", expect_quoted_path(&volta_full));
assert_eq!(cmd.as_deref(), Some(expected.as_str()));
}
@@ -3854,9 +3988,9 @@ mod tests {
let (_dir, sub, bin_path) = setup_sibling("pnpm", "codex.cmd", &["pnpm.cmd"]);
let cmd = anchored_command_from_paths("codex", &bin_path, &bin_path);
let pnpm_full = format!("{}\\pnpm.cmd", sub.to_string_lossy());
// codex 自 5092fe51 起不在 prefers_official_update:直接锚定包管理器,无 `codex update ||` 前缀。
let expected = format!(
"{} update || call {} add -g @openai/codex@latest",
expect_quoted_path(&bin_path),
"{} add -g @openai/codex@latest",
expect_quoted_path(&pnpm_full)
);
assert_eq!(cmd.as_deref(), Some(expected.as_str()));
@@ -3888,20 +4022,62 @@ mod tests {
let (_dir, sub, bin_path) = setup_sibling("v22.0.0", "codex.cmd", &["npm.cmd"]);
let cmd = anchored_command_from_paths("codex", &bin_path, &bin_path);
let npm_full = format!("{}\\npm.cmd", sub.to_string_lossy());
// codex 自 5092fe51 起不在 prefers_official_update:直接锚定包管理器,无 `codex update ||` 前缀。
let expected = format!(
"{} update || call {} i -g @openai/codex@latest",
expect_quoted_path(&bin_path),
"{} i -g @openai/codex@latest",
expect_quoted_path(&npm_full)
);
assert_eq!(cmd.as_deref(), Some(expected.as_str()));
}
#[test]
fn grok_windows_anchors_to_sibling_npm() {
let (_dir, sub, bin_path) = setup_sibling("v22.0.0", "grok.cmd", &["npm.cmd"]);
let cmd = anchored_command_from_paths("grok", &bin_path, &bin_path);
let npm_full = format!("{}\\npm.cmd", sub.to_string_lossy());
let expected = format!(
"{} i -g @xai-official/grok@latest",
expect_quoted_path(&npm_full)
);
assert_eq!(cmd.as_deref(), Some(expected.as_str()));
}
#[test]
fn grok_native_windows_uses_self_update() {
let (_dir, _sub, bin_path) = setup_sibling(".grok/bin", "grok.exe", &["npm.cmd"]);
let cmd = anchored_command_from_paths("grok", &bin_path, &bin_path);
let expected = format!("{} update", expect_quoted_path(&bin_path));
assert_eq!(cmd.as_deref(), Some(expected.as_str()));
}
#[test]
fn grok_windows_install_prefers_powershell_with_npm_fallback() {
let install = static_fallback_command_for("grok", ToolLifecycleAction::Install);
let native = grok_install_windows_command();
assert!(
install.starts_with(&native),
"native installer first: {install}"
);
assert!(
install.ends_with("|| call npm i -g @xai-official/grok@latest"),
"npm fallback should remain available: {install}"
);
let expected_encoded = powershell_encoded_command(GROK_INSTALL_WINDOWS_SCRIPT);
assert_eq!(
native
.split_once("-EncodedCommand ")
.map(|(_, encoded)| encoded),
Some(expected_encoded.as_str())
);
}
#[test]
fn windows_no_sibling_uses_cli_update_without_package_fallback() {
// sibling npm.cmd 不存在(纯独立二进制)时,仍可锚定到 CLI 自身跑官方 update。
// 只是没有包管理器 fallback。
let (_dir, _sub, bin_path) = setup_sibling("", "codex.cmd", &[]);
let cmd = anchored_command_from_paths("codex", &bin_path, &bin_path);
// sibling 包管理器不存在(纯独立二进制)时,仍可锚定到 CLI 自身跑官方 update。
// 只是没有包管理器 fallback。用 claude —— codex 自 5092fe51 起一律走 npm 锚定,
// 已不再有官方 self-update 分支,故改用仍在 prefers_official_update 的 claude 覆盖此路径。
let (_dir, _sub, bin_path) = setup_sibling("", "claude.cmd", &[]);
let cmd = anchored_command_from_paths("claude", &bin_path, &bin_path);
let expected = format!("{} update", expect_quoted_path(&bin_path));
assert_eq!(cmd.as_deref(), Some(expected.as_str()));
}
@@ -3977,9 +4153,9 @@ mod tests {
let (_dir, sub, bin_path) = setup_sibling("Program Files", "codex.cmd", &["npm.cmd"]);
let cmd = anchored_command_from_paths("codex", &bin_path, &bin_path);
let npm_full = format!("{}\\npm.cmd", sub.to_string_lossy());
// codex 走 npm 锚定(5092fe51),含空格的 npm 全路径仍必须被双引号包裹。
let expected = format!(
"{} update || call {} i -g @openai/codex@latest",
expect_quoted_path(&bin_path),
"{} i -g @openai/codex@latest",
expect_quoted_path(&npm_full)
);
assert_eq!(cmd.as_deref(), Some(expected.as_str()));
@@ -4001,9 +4177,10 @@ mod tests {
// 含空格的环境**(否则 sub 本身含空格 + 子目录 `path%foo%` 触发 4 倍 `%` 转义
// 会让 expected 漏引号、假失败)。
let npm_full = format!("{}\\npm.cmd", sub.to_string_lossy());
// codex 走 npm 锚定(5092fe51)batch 行 = `call <npm 全路径> i -g ...`
// 含字面 `%` 的 npm 路径仍须 4 倍转义。
let expected = format!(
"call {} update || call {} i -g @openai/codex@latest",
expect_quoted_path(&bin_path),
"call {} i -g @openai/codex@latest",
expect_quoted_path(&npm_full)
);
assert_eq!(batch_line, expected);
@@ -4214,6 +4391,15 @@ mod tests {
let codex =
wsl_tool_action_shell_command("codex", ToolLifecycleAction::Install).unwrap();
assert_eq!(codex, "npm i -g @openai/codex@latest");
let grok = wsl_tool_action_shell_command("grok", ToolLifecycleAction::Install).unwrap();
assert!(
grok.starts_with(
"bash -c 'tmp=$(mktemp) && curl -fsSL https://x.ai/cli/install.sh "
) && grok.contains(" || npm i -g @xai-official/grok@latest"),
"WSL grok install should prefer native POSIX installer with npm fallback: {grok}"
);
assert!(!grok.contains("| bash"));
}
#[test]
@@ -4330,6 +4516,28 @@ mod tests {
assert_eq!(cmd.as_deref(), Some("/Users/me/.local/bin/claude update"));
}
#[test]
fn grok_native_installer_uses_self_update() {
// ~/.grok/bin/grok is a launcher symlink into ~/.grok/downloads.
// Updating it through npm would create or mutate a different install.
let cmd = anchored_command_from_paths(
"grok",
"/Users/me/.grok/bin/grok",
"/Users/me/.grok/downloads/grok-macos-aarch64",
);
assert_eq!(cmd.as_deref(), Some("/Users/me/.grok/bin/grok update"));
}
#[test]
fn grok_custom_bin_dir_is_native_when_target_is_official_download() {
let cmd = anchored_command_from_paths(
"grok",
"/Users/me/bin/grok",
"/Users/me/.grok/downloads/grok-macos-aarch64",
);
assert_eq!(cmd.as_deref(), Some("/Users/me/bin/grok update"));
}
#[test]
fn gemini_homebrew_formula_uses_brew_upgrade() {
// /opt/homebrew/bin/gemini → Cellar/gemini-cli/...:是 brew formula 而非 npm 全局包,
@@ -4369,7 +4577,22 @@ mod tests {
assert_eq!(
cmd.as_deref(),
Some(
"/Users/me/.nvm/versions/node/v22.14.0/bin/npm i -g @google/gemini-cli@latest"
"PATH='/Users/me/.nvm/versions/node/v22.14.0/bin':\"$PATH\" /Users/me/.nvm/versions/node/v22.14.0/bin/npm i -g @google/gemini-cli@latest"
)
);
}
#[test]
fn grok_nvm_anchors_to_npm_without_cli_update() {
let cmd = anchored_command_from_paths(
"grok",
"/Users/me/.nvm/versions/node/v22.14.0/bin/grok",
"/Users/me/.nvm/versions/node/v22.14.0/lib/node_modules/@xai-official/grok/bin/grok",
);
assert_eq!(
cmd.as_deref(),
Some(
"PATH='/Users/me/.nvm/versions/node/v22.14.0/bin':\"$PATH\" /Users/me/.nvm/versions/node/v22.14.0/bin/npm i -g @xai-official/grok@latest"
)
);
}
@@ -4386,7 +4609,7 @@ mod tests {
);
assert_eq!(
cmd.as_deref(),
Some("/Users/me/.nvm/versions/node/v22.14.0/bin/npm i -g @openai/codex@latest")
Some("PATH='/Users/me/.nvm/versions/node/v22.14.0/bin':\"$PATH\" /Users/me/.nvm/versions/node/v22.14.0/bin/npm i -g @openai/codex@latest")
);
}
@@ -4401,7 +4624,7 @@ mod tests {
);
assert_eq!(
cmd.as_deref(),
Some("/opt/homebrew/bin/openclaw update --yes || /opt/homebrew/bin/npm i -g openclaw@latest")
Some("/opt/homebrew/bin/openclaw update --yes || PATH='/opt/homebrew/bin':\"$PATH\" /opt/homebrew/bin/npm i -g openclaw@latest")
);
}
@@ -4528,7 +4751,7 @@ mod tests {
assert_eq!(
cmd.as_deref(),
Some(
"/Users/me/.local/share/fnm_multishells/12345_abc/bin/npm i -g @openai/codex@latest"
"PATH='/Users/me/.local/share/fnm_multishells/12345_abc/bin':\"$PATH\" /Users/me/.local/share/fnm_multishells/12345_abc/bin/npm i -g @openai/codex@latest"
)
);
}
@@ -4542,7 +4765,62 @@ mod tests {
);
assert_eq!(
cmd.as_deref(),
Some("'/Users/my name/.nvm/versions/node/v22/bin/npm' i -g @openai/codex@latest")
Some("PATH='/Users/my name/.nvm/versions/node/v22/bin':\"$PATH\" '/Users/my name/.nvm/versions/node/v22/bin/npm' i -g @openai/codex@latest")
);
}
#[test]
fn npm_anchor_supplies_sibling_node_to_env_shebang() {
use std::os::unix::fs::PermissionsExt;
use std::process::Command;
let temp = tempfile::tempdir().expect("temp dir should be created");
let bin = temp.path().join("home dir/.nvm/versions/node/v22.14.0/bin");
std::fs::create_dir_all(&bin).expect("node bin should be created");
let marker = temp.path().join("sibling-node-used");
let node = bin.join("node");
let npm = bin.join("npm");
std::fs::write(
&node,
format!(
"#!/bin/sh\nprintf sibling-node > {}\n",
shell_single_quote(&marker.to_string_lossy())
),
)
.expect("fake node should be written");
std::fs::write(&npm, "#!/usr/bin/env node\n").expect("fake npm should be written");
for executable in [&node, &npm] {
let mut permissions = std::fs::metadata(executable)
.expect("fake executable metadata should exist")
.permissions();
permissions.set_mode(0o755);
std::fs::set_permissions(executable, permissions)
.expect("fake executable should be executable");
}
let codex = bin.join("codex").to_string_lossy().into_owned();
let real = temp
.path()
.join("home dir/.nvm/versions/node/v22.14.0/lib/node_modules/@openai/codex/bin/codex.js")
.to_string_lossy()
.into_owned();
let command = anchored_command_from_paths("codex", &codex, &real)
.expect("nvm codex should produce an anchored npm command");
let output = Command::new("/bin/bash")
.args(["-c", &command])
.env("PATH", "/usr/bin:/bin")
.output()
.expect("anchored npm command should start");
assert!(
output.status.success(),
"anchored npm command failed: {}",
decode_command_output(&output.stderr)
);
assert_eq!(
std::fs::read_to_string(marker).expect("sibling node should leave a marker"),
"sibling-node"
);
}
@@ -4649,7 +4927,7 @@ mod tests {
broken.runnable = false;
assert_eq!(
installs_anchored_command("codex", &[broken]).as_deref(),
Some("/Users/me/.nvm/versions/node/v22.14.0/bin/npm uninstall -g @openai/codex || true; /Users/me/.nvm/versions/node/v22.14.0/bin/npm i -g @openai/codex@latest")
Some("PATH='/Users/me/.nvm/versions/node/v22.14.0/bin':\"$PATH\" /Users/me/.nvm/versions/node/v22.14.0/bin/npm uninstall -g @openai/codex || true; PATH='/Users/me/.nvm/versions/node/v22.14.0/bin':\"$PATH\" /Users/me/.nvm/versions/node/v22.14.0/bin/npm i -g @openai/codex@latest")
);
}
@@ -4661,7 +4939,7 @@ mod tests {
let cmd = installs_anchored_command("codex", &[healthy]);
assert_eq!(
cmd.as_deref(),
Some("/Users/me/.nvm/versions/node/v22.14.0/bin/npm i -g @openai/codex@latest")
Some("PATH='/Users/me/.nvm/versions/node/v22.14.0/bin':\"$PATH\" /Users/me/.nvm/versions/node/v22.14.0/bin/npm i -g @openai/codex@latest")
);
assert!(!cmd.unwrap().contains("uninstall"));
}
@@ -4824,6 +5102,27 @@ mod tests {
assert_eq!(cmd, "npm i -g @google/gemini-cli@latest");
}
#[test]
fn grok_install_prefers_native_with_npm_fallback() {
let cmd = install_command_for("grok");
assert!(
cmd.contains("https://x.ai/cli/install.sh"),
"should include official installer URL: {cmd}"
);
assert!(
cmd.contains("@xai-official/grok@latest"),
"should keep npm package as fallback: {cmd}"
);
let parts: Vec<&str> = cmd.split("||").collect();
assert_eq!(parts.len(), 2, "should be a two-step short-circuit chain");
assert!(parts[0].contains("install.sh"), "native first: {cmd}");
assert!(
!parts[0].contains('|'),
"native installer should avoid pipe: {cmd}"
);
assert!(parts[1].contains("npm i -g"), "npm second: {cmd}");
}
#[test]
fn openclaw_install_keeps_static_npm() {
let cmd = install_command_for("openclaw");
@@ -4846,6 +5145,11 @@ mod tests {
"npm i -g @google/gemini-cli@latest"
);
assert!(!static_fallback_command("gemini").contains("gemini update"));
assert_eq!(
static_fallback_command("grok"),
"npm i -g @xai-official/grok@latest"
);
assert!(!static_fallback_command("grok").contains("grok update"));
assert_eq!(
static_fallback_command("opencode"),
"opencode upgrade || npm i -g opencode-ai@latest"
@@ -4988,6 +5292,27 @@ mod tests {
assert_eq!(count, 1);
}
#[test]
fn grok_extra_search_paths_prefers_override_then_default_native_dir() {
let home = PathBuf::from("/home/tester");
let paths =
grok_extra_search_paths(&home, Some(std::ffi::OsString::from("/custom/grok/bin")));
assert_eq!(paths[0], PathBuf::from("/custom/grok/bin"));
assert_eq!(paths[1], PathBuf::from("/home/tester/.grok/bin"));
}
#[test]
fn grok_extra_search_paths_deduplicates_default_override() {
let home = PathBuf::from("/home/tester");
let paths = grok_extra_search_paths(
&home,
Some(std::ffi::OsString::from("/home/tester/.grok/bin")),
);
assert_eq!(paths, vec![PathBuf::from("/home/tester/.grok/bin")]);
}
#[test]
fn cli_path_env_search_paths_include_path_entries_and_dedupe() {
let temp = tempfile::tempdir().expect("temp dir should be created");
+4
View File
@@ -18,6 +18,7 @@ mod model_fetch;
mod omo;
mod openclaw;
mod plugin;
mod profile;
mod prompt;
mod provider;
mod proxy;
@@ -27,6 +28,7 @@ pub mod skill;
mod stream_check;
mod subscription;
mod sync_support;
mod xai_oauth;
mod lightweight;
mod s3_sync;
@@ -52,6 +54,7 @@ pub use model_fetch::*;
pub use omo::*;
pub use openclaw::*;
pub use plugin::*;
pub use profile::*;
pub use prompt::*;
pub use provider::*;
pub use proxy::*;
@@ -60,6 +63,7 @@ pub use settings::*;
pub use skill::*;
pub use stream_check::*;
pub use subscription::*;
pub use xai_oauth::*;
pub use lightweight::*;
pub use s3_sync::*;
+195
View File
@@ -0,0 +1,195 @@
//! 项目 Profile 管理命令
use serde::Serialize;
use tauri::{Emitter, Manager, State};
use crate::database::Profile;
use crate::services::profile::{ProfilePayload, ProfileScope, ProfileService};
use crate::store::AppState;
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProfileDto {
pub id: String,
pub name: String,
pub payload: ProfilePayload,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at: Option<i64>,
}
impl From<Profile> for ProfileDto {
fn from(profile: Profile) -> Self {
// 单条 payload 损坏不应拖垮整个列表:降级为默认值并记日志
let payload = serde_json::from_str(&profile.payload).unwrap_or_else(|e| {
log::warn!(
"解析 profile '{}' payload 失败,使用默认值: {e}",
profile.id
);
ProfilePayload::default()
});
Self {
id: profile.id,
name: profile.name,
payload,
created_at: profile.created_at,
updated_at: profile.updated_at,
}
}
}
/// 每个分组当前激活的项目 id(未使用项目时为 null)
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CurrentProfileIds {
pub claude: Option<String>,
pub claude_desktop: Option<String>,
pub codex: Option<String>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProfilesResponse {
pub profiles: Vec<ProfileDto>,
pub current_ids: CurrentProfileIds,
}
/// Profile 应用完成后的统一收尾:发事件 + 重建托盘菜单
///
/// 只对项目所属分组内的应用发 provider-switched。UI 与托盘两个入口必须
/// 共用此函数,保证事件 payload 形状一致(前端 App.tsx 的
/// provider-switched 监听依赖该形状)。
pub fn emit_profile_apply_events(
app: &tauri::AppHandle,
state: &AppState,
profile_id: &str,
scope: ProfileScope,
) {
for app_type in scope.apps().iter() {
let app_str = app_type.as_str();
let (proxy_enabled, auto_failover_enabled) = state.db.get_proxy_flags_sync(app_str);
let provider_id = crate::settings::get_effective_current_provider(&state.db, app_type)
.ok()
.flatten()
.unwrap_or_default();
let event_data = serde_json::json!({
"appType": app_str,
"proxyEnabled": proxy_enabled,
"autoFailoverEnabled": auto_failover_enabled,
"providerId": provider_id,
});
if let Err(e) = app.emit("provider-switched", event_data) {
log::error!("发射 provider-switched 事件失败: {e}");
}
}
if let Err(e) = app.emit(
"profile-applied",
serde_json::json!({ "profileId": profile_id, "scope": scope.as_str() }),
) {
log::error!("发射 profile-applied 事件失败: {e}");
}
crate::tray::refresh_tray_menu(app);
}
#[tauri::command]
pub fn list_profiles(state: State<'_, AppState>) -> Result<ProfilesResponse, String> {
let profiles = ProfileService::list(&state).map_err(|e| e.to_string())?;
let current_ids = CurrentProfileIds {
claude: state
.db
.get_current_profile_id(ProfileScope::Claude.as_str())
.map_err(|e| e.to_string())?,
claude_desktop: state
.db
.get_current_profile_id(ProfileScope::ClaudeDesktop.as_str())
.map_err(|e| e.to_string())?,
codex: state
.db
.get_current_profile_id(ProfileScope::Codex.as_str())
.map_err(|e| e.to_string())?,
};
Ok(ProfilesResponse {
profiles: profiles.into_iter().map(ProfileDto::from).collect(),
current_ids,
})
}
#[tauri::command]
pub fn create_profile(
state: State<'_, AppState>,
name: String,
scope: String,
) -> Result<ProfileDto, String> {
let scope = ProfileScope::parse(&scope).map_err(|e| e.to_string())?;
ProfileService::create(&state, &name, scope)
.map(ProfileDto::from)
.map_err(|e| e.to_string())
}
#[tauri::command]
pub fn update_profile(
state: State<'_, AppState>,
id: String,
name: Option<String>,
resnapshot: Option<bool>,
scope: Option<String>,
) -> Result<ProfileDto, String> {
let scope = scope
.map(|s| ProfileScope::parse(&s))
.transpose()
.map_err(|e| e.to_string())?;
ProfileService::update(&state, &id, name, resnapshot.unwrap_or(false), scope)
.map(ProfileDto::from)
.map_err(|e| e.to_string())
}
#[tauri::command]
pub fn delete_profile(state: State<'_, AppState>, id: String) -> Result<(), String> {
ProfileService::delete(&state, &id).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn clear_current_profile(state: State<'_, AppState>, scope: String) -> Result<(), String> {
let scope = ProfileScope::parse(&scope).map_err(|e| e.to_string())?;
state
.db
.set_current_profile_id(scope.as_str(), None)
.map_err(|e| e.to_string())
}
/// 应用项目快照(只作用于发起页所属分组内的应用)。
///
/// 注意:必须保持同步命令(跑在 Tauri 线程池)——`ProviderService::switch`
/// 内部使用 block_on 获取切换锁,放进 async 命令会在运行时线程上 panic。
#[tauri::command]
pub fn apply_profile(
app: tauri::AppHandle,
state: State<'_, AppState>,
id: String,
scope: String,
) -> Result<Vec<String>, String> {
let scope = ProfileScope::parse(&scope).map_err(|e| e.to_string())?;
let (warnings, should_stop_proxy) =
ProfileService::apply(&state, &id, scope).map_err(|e| e.to_string())?;
if should_stop_proxy {
// sync 命令线程没有 Tokio runtime,无法直接 await stop()
// 把停止服务放到 Tauri async runtime,停止后再补发事件刷新 UI。
let app_handle = app.clone();
let profile_id = id.clone();
let proxy_service = state.proxy_service.clone();
tauri::async_runtime::spawn(async move {
if let Err(e) = proxy_service.stop().await {
log::warn!("切换项目后停止代理服务失败: {e}");
}
if let Some(app_state) = app_handle.try_state::<AppState>() {
emit_profile_apply_events(&app_handle, app_state.inner(), &profile_id, scope);
}
});
} else {
emit_profile_apply_events(&app, &state, &id, scope);
}
Ok(warnings)
}
+126 -33
View File
@@ -1,8 +1,9 @@
use indexmap::IndexMap;
use tauri::{Emitter, State};
use tauri::{Emitter, Manager, State};
use crate::app_config::AppType;
use crate::commands::copilot::CopilotAuthState;
use crate::commands::xai_oauth::XaiOAuthState;
use crate::error::AppError;
use crate::provider::{ClaudeDesktopMode, Provider};
use crate::services::{
@@ -100,16 +101,63 @@ pub fn switch_provider_test_hook(
}
#[tauri::command]
pub fn switch_provider(
state: State<'_, AppState>,
pub async fn switch_provider(
app_handle: tauri::AppHandle,
app: String,
id: String,
) -> Result<SwitchResult, String> {
let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?;
switch_provider_internal(&state, app_type, &id).map_err(|e| e.to_string())
tauri::async_runtime::spawn_blocking(move || {
let state = app_handle
.try_state::<AppState>()
.ok_or_else(|| "应用状态不可用".to_string())?;
switch_provider_internal(state.inner(), app_type, &id).map_err(|e| e.to_string())
})
.await
.map_err(|e| format!("供应商切换任务执行失败: {e}"))?
}
fn import_default_config_internal(state: &AppState, app_type: AppType) -> Result<bool, AppError> {
if matches!(app_type, AppType::GrokBuild) {
// 官方登录态(live 语法合法且无自定义模型表)+ 用户手动导入:
// 导入的正确结果是让 Grok Official 成为当前供应商,而非报错。
// 只挂在命令层 = 只有手动动作可达;启动自动导入走 service 层、
// 官方态照旧报错静默跳过,删掉的官方条目不会被重启复活
//(全项目惯例:启动自动导入只产出 default,从不产出官方条目)。
if let Ok(settings) = crate::grok_config::read_grok_live_settings() {
let config = settings
.get("config")
.and_then(serde_json::Value::as_str)
.unwrap_or_default();
if crate::grok_config::is_official_live_config(config) {
state.db.ensure_official_seed_by_id(
crate::database::GROKBUILD_OFFICIAL_PROVIDER_ID,
AppType::GrokBuild,
)?;
state.db.set_current_provider(
app_type.as_str(),
crate::database::GROKBUILD_OFFICIAL_PROVIDER_ID,
)?;
crate::settings::set_current_provider(
&app_type,
Some(crate::database::GROKBUILD_OFFICIAL_PROVIDER_ID),
)?;
return Ok(true);
}
}
// Safety net: 与 claude-desktop 导入同语义 —— 用户主动点导入是"重新
// 整理该表"的隐式信号,把官方入口补回来。覆盖导入必然失败的场景
//live 文件缺失 / TOML 语法错误 / 残缺的自定义配置),避免
// "报错 + 空列表"死胡同。失败只 warn,不影响导入主流程。
if let Err(e) = state.db.ensure_official_seed_by_id(
crate::database::GROKBUILD_OFFICIAL_PROVIDER_ID,
AppType::GrokBuild,
) {
log::warn!("Failed to ensure grokbuild-official seed during import: {e}");
}
}
let imported = ProviderService::import_default_config(state, app_type.clone())?;
if imported {
@@ -231,6 +279,25 @@ pub fn ensure_claude_desktop_official_provider(state: State<'_, AppState>) -> Re
.map_err(|e| e.to_string())
}
#[tauri::command]
pub fn ensure_codex_official_provider(state: State<'_, AppState>) -> Result<bool, String> {
state
.db
.ensure_official_seed_by_id(crate::database::CODEX_OFFICIAL_PROVIDER_ID, AppType::Codex)
.map_err(|e| e.to_string())
}
#[tauri::command]
pub fn ensure_grokbuild_official_provider(state: State<'_, AppState>) -> Result<bool, String> {
state
.db
.ensure_official_seed_by_id(
crate::database::GROKBUILD_OFFICIAL_PROVIDER_ID,
AppType::GrokBuild,
)
.map_err(|e| e.to_string())
}
fn claude_provider_models_are_claude_safe(provider: &Provider) -> bool {
let Some(env) = provider
.settings_config
@@ -266,7 +333,7 @@ pub(crate) fn suggested_claude_desktop_routes(
.meta
.as_ref()
.and_then(|meta| meta.provider_type.as_deref()),
Some("github_copilot") | Some("codex_oauth")
Some("github_copilot") | Some("codex_oauth") | Some("xai_oauth")
);
fn add_route(
@@ -376,37 +443,42 @@ pub async fn queryProviderUsage(
app_handle: tauri::AppHandle,
state: State<'_, AppState>,
copilot_state: State<'_, CopilotAuthState>,
xai_state: State<'_, XaiOAuthState>,
#[allow(non_snake_case)] providerId: String, // 使用 camelCase 匹配前端
app: String,
) -> Result<crate::provider::UsageResult, String> {
let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?;
// inner 可能以两种形式失败:
// 1) 返回 Ok(UsageResult { success: false, .. }) —— 业务失败(401、脚本报错等)
// 2) 返回 Err(String) —— RPC/DB/Copilot fetch_usage 等 transport 层失败
// 两种都要把"失败"写进 UsageCache 并刷新托盘,让 format_script_summary 的
// success 守卫生效、suffix 自然消失,避免旧 success 快照长期滞留
// 同时保持原始 Err 返回给前端 React Query 的 onError 回调,不吞错误。
let inner =
query_provider_usage_inner(&state, &copilot_state, app_type.clone(), &providerId).await;
let snapshot = match &inner {
Ok(r) => r.clone(),
Err(err_msg) => crate::provider::UsageResult {
success: false,
data: None,
error: Some(err_msg.clone()),
},
};
let payload = serde_json::json!({
"kind": "script",
"appType": app_type.as_str(),
"providerId": &providerId,
"data": &snapshot,
});
if let Err(e) = app_handle.emit("usage-cache-updated", payload) {
log::error!("emit usage-cache-updated (script) 失败: {e}");
// 1) 返回 Ok(UsageResult { success: false, .. }) —— 确定性失败(401、脚本
// 报错、未知供应商等)。写进 UsageCache 并刷新托盘,让
// format_script_summary 的 success 守卫生效、suffix 自然消失。
// 2) 返回 Err(String) —— 瞬时传输失败(网络/超时)及 DB/Copilot fetch 等
// 不写失败快照、不 emit:保留上一份托盘快照,与前端 react-query reject
// 保留上次 data 的语义一致;否则失败快照会经 useUsageCacheBridge 盲写
// 回 query 缓存,抹掉 reject 本该保留的旧值。
let inner = query_provider_usage_inner(
&state,
&copilot_state,
&xai_state,
app_type.clone(),
&providerId,
)
.await;
if let Ok(snapshot) = &inner {
let payload = serde_json::json!({
"kind": "script",
"appType": app_type.as_str(),
"providerId": &providerId,
"data": snapshot,
});
if let Err(e) = app_handle.emit("usage-cache-updated", payload) {
log::error!("emit usage-cache-updated (script) 失败: {e}");
}
state
.usage_cache
.put_script(app_type, providerId, snapshot.clone());
crate::tray::schedule_tray_refresh(&app_handle);
}
state.usage_cache.put_script(app_type, providerId, snapshot);
crate::tray::schedule_tray_refresh(&app_handle);
inner
}
@@ -457,6 +529,7 @@ fn resolve_coding_plan_credentials(
async fn query_provider_usage_inner(
state: &AppState,
copilot_state: &CopilotAuthState,
xai_state: &XaiOAuthState,
app_type: AppType,
provider_id: &str,
) -> Result<crate::provider::UsageResult, String> {
@@ -518,12 +591,20 @@ async fn query_provider_usage_inner(
// 其他供应商为 Noneservice 层沿用 api_key。
let access_key_id = usage_script.and_then(|s| s.access_key_id.clone());
let secret_access_key = usage_script.and_then(|s| s.secret_access_key.clone());
// 智谱团队版:显式 provider 标识 + 组织/项目 ID(与个人版智谱 base_url 相同,
// 靠 coding_plan_provider == "zhipu_team" 在 service 层路由)。
let coding_plan_provider = usage_script.and_then(|s| s.coding_plan_provider.clone());
let team_organization_id = usage_script.and_then(|s| s.team_organization_id.clone());
let team_project_id = usage_script.and_then(|s| s.team_project_id.clone());
let quota = crate::services::coding_plan::get_coding_plan_quota(
&base_url,
&api_key,
access_key_id.as_deref(),
secret_access_key.as_deref(),
coding_plan_provider.as_deref(),
team_organization_id.as_deref(),
team_project_id.as_deref(),
)
.await
.map_err(|e| format!("Failed to query coding plan: {e}"))?;
@@ -617,9 +698,19 @@ async fn query_provider_usage_inner(
});
}
let quota = crate::services::subscription::get_subscription_quota(app_type.as_str())
.await
.map_err(|e| format!("Failed to query subscription quota: {e}"))?;
// xAI OAuth 托管供应商的额度属绑定的 SuperGrok 账号,而非所在 app 的
// CLI 凭据(对 codex/claude 而言 CLI 凭据是 ChatGPT/Claude 订阅,跨了
// 订阅体系,查出来的数字张冠李戴)。
let quota = if provider.map(Provider::is_xai_oauth).unwrap_or(false) {
let account_id = provider
.and_then(|p| p.meta.as_ref())
.and_then(|m| m.managed_account_id_for("xai_oauth"));
crate::commands::xai_oauth::query_xai_oauth_quota_for(xai_state, account_id).await?
} else {
crate::services::subscription::get_subscription_quota(app_type.as_str())
.await
.map_err(|e| format!("Failed to query subscription quota: {e}"))?
};
if !quota.success {
return Ok(crate::provider::UsageResult {
@@ -1081,6 +1172,8 @@ mod native_query_credentials_tests {
coding_plan_provider: coding_plan_provider.map(str::to_string),
access_key_id: None,
secret_access_key: None,
team_organization_id: None,
team_project_id: None,
}
}
+9 -2
View File
@@ -6,6 +6,7 @@ use crate::error::AppError;
use crate::proxy::types::*;
use crate::proxy::{CircuitBreakerConfig, CircuitBreakerStats};
use crate::store::AppState;
use std::str::FromStr;
/// 启动代理服务器(仅启动服务,不接管 Live 配置)
#[tauri::command]
@@ -22,6 +23,7 @@ pub async fn stop_proxy_server(state: tauri::State<'_, AppState>) -> Result<(),
if takeover.claude
|| takeover.codex
|| takeover.gemini
|| takeover.grokbuild
|| takeover.opencode
|| takeover.openclaw
{
@@ -279,13 +281,18 @@ pub async fn switch_proxy_provider(
app_type: String,
provider_id: String,
) -> Result<(), String> {
// Block official providers during proxy takeover
// Codex's built-in official provider can use the client's native OpenAI
// login through takeover. Other official providers remain blocked.
let provider = state
.db
.get_provider_by_id(&provider_id, &app_type)
.map_err(|e| format!("读取供应商失败: {e}"))?
.ok_or_else(|| format!("供应商不存在: {provider_id}"))?;
if provider.category.as_deref() == Some("official") {
let app = crate::app_config::AppType::from_str(&app_type)
.map_err(|e| format!("无效的应用类型: {e}"))?;
if provider.category.as_deref() == Some("official")
&& !crate::services::provider::official_provider_supports_proxy_takeover(&app, &provider)
{
return Err(
"代理接管模式下不能切换到官方供应商 (Cannot switch to official provider during proxy takeover)"
.to_string(),
+1 -10
View File
@@ -247,7 +247,7 @@ pub async fn install_update_and_restart(app: AppHandle) -> Result<bool, String>
"Windows 更新安装失败: {e}。已执行退出前清理,代理或 Live 接管可能已暂停;请重启应用或重新开启代理后再试。"
)
})?;
return Ok(true);
Ok(true)
}
#[cfg(not(target_os = "windows"))]
@@ -661,15 +661,6 @@ pub async fn set_optimizer_config(
state: tauri::State<'_, crate::AppState>,
config: crate::proxy::types::OptimizerConfig,
) -> Result<bool, String> {
// Validate cache_ttl: only allow known values
match config.cache_ttl.as_str() {
"5m" | "1h" => {}
other => {
return Err(format!(
"Invalid cache_ttl value: '{other}'. Allowed values: '5m', '1h'"
))
}
}
state
.db
.set_optimizer_config(&config)
+6
View File
@@ -72,6 +72,12 @@ pub async fn stream_check_all_providers(
let mut results = Vec::new();
for (id, provider) in providers {
// Official OAuth providers intentionally have no user-configured probe
// target. Never turn their runtime adapter defaults into unauthenticated
// network probes against first-party endpoints.
if provider.category.as_deref() == Some("official") {
continue;
}
if let Some(ids) = &allowed_ids {
if !ids.contains(&id) {
continue;
+22 -21
View File
@@ -2,17 +2,19 @@ use std::str::FromStr;
use tauri::{Emitter, State};
use crate::app_config::AppType;
use crate::services::subscription::{CredentialStatus, SubscriptionQuota};
use crate::services::subscription::SubscriptionQuota;
use crate::store::AppState;
/// 查询官方订阅额度
///
/// 读取 CLI 工具已有的 OAuth 凭据并调用官方 API 获取使用额度。
/// 结果(无论业务失败还是 transport 层 Err)都会写入 `UsageCache`、通知托盘
/// 刷新,并 emit `usage-cache-updated`,让前端 React Query 与托盘共享同一份
/// 最新数据。失败快照写入后 `format_subscription_summary` 会通过 `success=false`
/// 守卫返回 `None`,托盘 suffix 自然消失,避免长期滞留旧配额数字。
/// Err 原样向前端返回,React Query 的 onError 不会被吞掉。
/// `Ok`(成功或确定性失败)写入 `UsageCache`、通知托盘刷新并 emit
/// `usage-cache-updated`,让前端 React Query 与托盘共享同一份最新数据;失败
/// 快照写入后 `format_subscription_summary` 会通过 `success=false` 守卫返回
/// `None`,托盘 suffix 自然消失,避免长期滞留旧配额数字。
/// `Err`(瞬时传输失败)不写快照、不 emit:保留上一份托盘快照,与前端
/// react-query reject 保留上次 data 的语义一致(emit 失败快照会经
/// `useUsageCacheBridge` 盲写回 query 缓存,抹掉本该保留的旧值)。
#[tauri::command]
pub async fn get_subscription_quota(
app: tauri::AppHandle,
@@ -20,22 +22,21 @@ pub async fn get_subscription_quota(
tool: String,
) -> Result<SubscriptionQuota, String> {
let inner = crate::services::subscription::get_subscription_quota(&tool).await;
let snapshot = match &inner {
Ok(q) => q.clone(),
// transport 层 Err —— 凭据状态不明,用 Valid 表达"凭据没问题,是通信/parse 出错"。
Err(err_msg) => SubscriptionQuota::error(&tool, CredentialStatus::Valid, err_msg.clone()),
};
if let Ok(app_type) = AppType::from_str(&tool) {
let payload = serde_json::json!({
"kind": "subscription",
"appType": app_type.as_str(),
"data": &snapshot,
});
if let Err(e) = app.emit("usage-cache-updated", payload) {
log::error!("emit usage-cache-updated (subscription) 失败: {e}");
if let Ok(snapshot) = &inner {
if let Ok(app_type) = AppType::from_str(&tool) {
let payload = serde_json::json!({
"kind": "subscription",
"appType": app_type.as_str(),
"data": snapshot,
});
if let Err(e) = app.emit("usage-cache-updated", payload) {
log::error!("emit usage-cache-updated (subscription) 失败: {e}");
}
state
.usage_cache
.put_subscription(app_type, snapshot.clone());
crate::tray::schedule_tray_refresh(&app);
}
state.usage_cache.put_subscription(app_type, snapshot);
crate::tray::schedule_tray_refresh(&app);
}
inner
}
+67 -42
View File
@@ -275,52 +275,47 @@ pub fn delete_model_pricing(state: State<'_, AppState>, model_id: String) -> Res
/// 手动触发会话日志同步
#[tauri::command]
pub fn sync_session_usage(
pub async fn sync_session_usage(
state: State<'_, AppState>,
) -> Result<crate::services::session_usage::SessionSyncResult, AppError> {
// 同步 Claude 会话日志
let mut result = crate::services::session_usage::sync_claude_session_logs(&state.db)?;
let db = state.db.clone();
let _guard = crate::services::session_usage::session_sync_mutex()
.lock()
.await;
tauri::async_runtime::spawn_blocking(move || {
crate::services::session_usage::sync_all_unlocked(&db)
})
.await
.map_err(|error| AppError::Message(format!("会话用量同步任务失败: {error}")))
}
// 同步 Codex 使用数据
match crate::services::session_usage_codex::sync_codex_usage(&state.db) {
Ok(codex_result) => {
result.imported += codex_result.imported;
result.skipped += codex_result.skipped;
result.files_scanned += codex_result.files_scanned;
result.errors.extend(codex_result.errors);
}
Err(e) => {
result.errors.push(format!("Codex 同步失败: {e}"));
}
}
/// Codex reset 成功后,无论重导是否导入新行或返回错误,都必须通知前端刷新。
/// 调用方应只在 reset 成功后调用,避免把未发生的数据变更误报为重建完成。
fn finish_codex_rebuild(
result: Result<crate::services::session_usage::SessionSyncResult, AppError>,
) -> Result<crate::services::session_usage::SessionSyncResult, AppError> {
crate::usage_events::notify_log_recorded();
result
}
// 同步 Gemini 使用数据
match crate::services::session_usage_gemini::sync_gemini_usage(&state.db) {
Ok(gemini_result) => {
result.imported += gemini_result.imported;
result.skipped += gemini_result.skipped;
result.files_scanned += gemini_result.files_scanned;
result.errors.extend(gemini_result.errors);
}
Err(e) => {
result.errors.push(format!("Gemini 同步失败: {e}"));
}
}
// 同步 OpenCode 使用数据
match crate::services::session_usage_opencode::sync_opencode_usage(&state.db) {
Ok(opencode_result) => {
result.imported += opencode_result.imported;
result.skipped += opencode_result.skipped;
result.files_scanned += opencode_result.files_scanned;
result.errors.extend(opencode_result.errors);
}
Err(e) => {
result.errors.push(format!("OpenCode 同步失败: {e}"));
}
}
Ok(result)
/// 备份数据库后,仅重建 Codex session 用量。锁覆盖 backup → reset → import
/// 整个序列,避免后台同步在清理和重导之间插入数据。
#[tauri::command]
pub async fn rebuild_codex_usage(
state: State<'_, AppState>,
) -> Result<crate::services::session_usage::SessionSyncResult, AppError> {
let db = state.db.clone();
let _guard = crate::services::session_usage::session_sync_mutex()
.lock()
.await;
tauri::async_runtime::spawn_blocking(move || {
db.backup_database_file()?;
db.reset_codex_usage()?;
let result = crate::services::session_usage_codex::sync_codex_usage(&db);
finish_codex_rebuild(result)
})
.await
.map_err(|error| AppError::Message(format!("Codex 用量重建任务失败: {error}")))?
}
/// 获取数据来源分布
@@ -342,3 +337,33 @@ pub struct ModelPricingInfo {
pub cache_read_cost_per_million: String,
pub cache_creation_cost_per_million: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn codex_rebuild_notifies_when_reimport_is_empty() {
crate::usage_events::take_test_notify_count();
let result = finish_codex_rebuild(Ok(
crate::services::session_usage::SessionSyncResult::default(),
))
.expect("空重导应成功");
assert_eq!(result.imported, 0);
assert_eq!(crate::usage_events::take_test_notify_count(), 1);
}
#[test]
fn codex_rebuild_notifies_when_reimport_fails_after_reset() {
crate::usage_events::take_test_notify_count();
let result = finish_codex_rebuild(Err(AppError::Message(
"synthetic reimport failure".to_string(),
)));
assert!(result.is_err());
assert_eq!(crate::usage_events::take_test_notify_count(), 1);
}
}
+135
View File
@@ -0,0 +1,135 @@
//! xAI OAuth state and xAI-specific commands.
use crate::proxy::providers::xai_oauth_auth::XaiOAuthManager;
use crate::proxy::providers::XAI_API_BASE_URL;
use crate::services::model_fetch::FetchedModel;
use crate::services::subscription::{CredentialStatus, SubscriptionQuota};
use serde::Deserialize;
use std::sync::Arc;
use std::time::Duration;
use tauri::State;
use tokio::sync::RwLock;
pub struct XaiOAuthState(pub Arc<RwLock<XaiOAuthManager>>);
/// 查询 xAI OAuth (SuperGrok 反代) 订阅额度的共享核心
///
/// 与 `get_codex_oauth_quota` 平行:数据走 cc-switch 自管的 xAI OAuth token
/// 而非 Grok CLI 的 ~/.grok/auth.json。两者是同一个 OAuth client
/// client_id 与 Grok CLI 一致),token 对 grok.com 账单端点等效,因此
/// 复用 `subscription_grok::query_grok_quota`,协议与 Grok CLI 路径完全一致。
///
/// 供两处调用:`get_xai_oauth_quota` 命令(前端 footer)与
/// `commands::provider` 的 official_subscription 分支(用量脚本/托盘路径,
/// xai_oauth 供应商的额度属绑定的 SuperGrok 账号而非所在 app 的 CLI 凭据)。
///
/// - `account_id` 未指定时回退到 `XaiOAuthManager` 的默认账号
/// - 没有任何账号时返回 `not_found`,前端 `SubscriptionQuotaView` 会静默不渲染
/// - 瞬时传输失败以 `Err` 传播(前端 reject → retry + 保留上次成功值)
pub(crate) async fn query_xai_oauth_quota_for(
state: &XaiOAuthState,
account_id: Option<String>,
) -> Result<SubscriptionQuota, String> {
let manager = state.0.read().await;
// 解析最终使用的账号 ID:显式 > 默认账号 > 无账号 (not_found)
let resolved = match account_id
.as_deref()
.map(str::trim)
.filter(|id| !id.is_empty())
{
Some(id) => Some(id.to_string()),
None => manager.default_account_id().await,
};
let Some(id) = resolved else {
return Ok(SubscriptionQuota::not_found("xai_oauth"));
};
// 获取(必要时自动刷新)access_token
let token = match manager.get_valid_token_for_account(&id).await {
Ok(t) => t,
Err(e) => {
return Ok(SubscriptionQuota::error(
"xai_oauth",
CredentialStatus::Expired,
format!("xAI OAuth token unavailable: {e}"),
));
}
};
crate::services::subscription_grok::query_grok_quota(
&token,
"xai_oauth",
"Please re-login via cc-switch.",
)
.await
}
/// 查询 xAI OAuth (SuperGrok 反代) 订阅额度
#[tauri::command(rename_all = "camelCase")]
pub async fn get_xai_oauth_quota(
account_id: Option<String>,
state: State<'_, XaiOAuthState>,
) -> Result<SubscriptionQuota, String> {
query_xai_oauth_quota_for(&state, account_id).await
}
#[derive(Debug, Deserialize)]
struct ModelsResponse {
#[serde(default)]
data: Vec<ModelEntry>,
}
#[derive(Debug, Deserialize)]
struct ModelEntry {
id: String,
#[serde(default)]
owned_by: Option<String>,
}
#[tauri::command(rename_all = "camelCase")]
pub async fn get_xai_oauth_models(
account_id: Option<String>,
state: State<'_, XaiOAuthState>,
) -> Result<Vec<FetchedModel>, String> {
let manager = state.0.read().await;
let resolved = match account_id
.as_deref()
.map(str::trim)
.filter(|id| !id.is_empty())
{
Some(id) => Some(id.to_string()),
None => manager.default_account_id().await,
};
let account_id = resolved.ok_or_else(|| "No usable xAI account available".to_string())?;
let token = manager
.get_valid_token_for_account(&account_id)
.await
.map_err(|error| format!("xAI OAuth token unavailable: {error}"))?;
let response = crate::proxy::http_client::get()
.get(format!("{XAI_API_BASE_URL}/models"))
.bearer_auth(token)
.timeout(Duration::from_secs(15))
.send()
.await
.map_err(|error| format!("xAI models request failed: {error}"))?;
let status = response.status();
if !status.is_success() {
return Err(format!("xAI models request failed: HTTP {status}"));
}
let payload: ModelsResponse = response
.json()
.await
.map_err(|_| "xAI models response was not valid JSON".to_string())?;
let mut models: Vec<FetchedModel> = payload
.data
.into_iter()
.map(|model| FetchedModel {
id: model.id,
owned_by: model.owned_by,
})
.collect();
models.sort_by(|a, b| a.id.cmp(&b.id));
Ok(models)
}
+8 -5
View File
@@ -13,7 +13,7 @@ impl Database {
pub fn get_all_mcp_servers(&self) -> Result<IndexMap<String, McpServer>, AppError> {
let conn = lock_conn!(self.conn);
let mut stmt = conn.prepare(
"SELECT id, name, server_config, description, homepage, docs, tags, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, enabled_hermes
"SELECT id, name, server_config, description, homepage, docs, tags, enabled_claude, enabled_codex, enabled_gemini, enabled_grokbuild, enabled_opencode, enabled_hermes
FROM mcp_servers
ORDER BY name ASC, id ASC"
).map_err(|e| AppError::Database(e.to_string()))?;
@@ -30,8 +30,9 @@ impl Database {
let enabled_claude: bool = row.get(7)?;
let enabled_codex: bool = row.get(8)?;
let enabled_gemini: bool = row.get(9)?;
let enabled_opencode: bool = row.get(10)?;
let enabled_hermes: bool = row.get(11)?;
let enabled_grokbuild: bool = row.get(10)?;
let enabled_opencode: bool = row.get(11)?;
let enabled_hermes: bool = row.get(12)?;
let server = serde_json::from_str(&server_config_str).unwrap_or_default();
let tags = serde_json::from_str(&tags_str).unwrap_or_default();
@@ -46,6 +47,7 @@ impl Database {
claude: enabled_claude,
codex: enabled_codex,
gemini: enabled_gemini,
grokbuild: enabled_grokbuild,
opencode: enabled_opencode,
hermes: enabled_hermes,
},
@@ -72,8 +74,8 @@ impl Database {
conn.execute(
"INSERT OR REPLACE INTO mcp_servers (
id, name, server_config, description, homepage, docs, tags,
enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, enabled_hermes
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
enabled_claude, enabled_codex, enabled_gemini, enabled_grokbuild, enabled_opencode, enabled_hermes
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
params![
server.id,
server.name,
@@ -88,6 +90,7 @@ impl Database {
server.apps.claude,
server.apps.codex,
server.apps.gemini,
server.apps.grokbuild,
server.apps.opencode,
server.apps.hermes,
],
+3 -1
View File
@@ -4,6 +4,7 @@
pub mod failover;
pub mod mcp;
pub mod profiles;
pub mod prompts;
pub mod providers;
pub mod providers_seed;
@@ -15,5 +16,6 @@ pub mod universal_providers;
pub mod usage_rollup;
// 所有 DAO 方法都通过 Database impl 提供,无需单独导出
// 导出 FailoverQueueItem 供外部使用
// 导出 FailoverQueueItem / Profile 供外部使用
pub use failover::FailoverQueueItem;
pub use profiles::Profile;
+207
View File
@@ -0,0 +1,207 @@
//! 项目 Profile 数据访问对象
//!
//! profiles 表存放全应用共享的项目实体(供应商/MCP/Skills/Prompt 快照),
//! payload 为原始 JSON 文本(按 app 分槽),解析在 service 层进行。
//! 各应用分组(scope)独立的 current 标记存放于 settings 表(key-value)。
use crate::database::{lock_conn, Database};
use crate::error::AppError;
use rusqlite::params;
/// 每个 scope 的 current 标记 key = 前缀 + scope(如 current_profile_id_claude
const CURRENT_PROFILE_ID_KEY_PREFIX: &str = "current_profile_id_";
fn current_profile_key(scope: &str) -> String {
format!("{CURRENT_PROFILE_ID_KEY_PREFIX}{scope}")
}
/// 项目 Profile 记录(全应用共享,无所属分组)
#[derive(Debug, Clone)]
pub struct Profile {
pub id: String,
pub name: String,
/// 原始 JSON 快照文本(ProfilePayload),解析在 service 层
pub payload: String,
pub sort_order: Option<i64>,
pub created_at: Option<i64>,
pub updated_at: Option<i64>,
}
impl Database {
/// 获取所有项目(按 sort_order 优先、created_at 兜底排序)
pub fn get_all_profiles(&self) -> Result<Vec<Profile>, AppError> {
let conn = lock_conn!(self.conn);
let mut stmt = conn
.prepare(
"SELECT id, name, payload, sort_order, created_at, updated_at
FROM profiles
ORDER BY sort_order IS NULL, sort_order, created_at, id",
)
.map_err(|e| AppError::Database(e.to_string()))?;
let rows = stmt
.query_map([], |row| {
Ok(Profile {
id: row.get(0)?,
name: row.get(1)?,
payload: row.get(2)?,
sort_order: row.get(3)?,
created_at: row.get(4)?,
updated_at: row.get(5)?,
})
})
.map_err(|e| AppError::Database(e.to_string()))?;
let mut profiles = Vec::new();
for row in rows {
profiles.push(row.map_err(|e| AppError::Database(e.to_string()))?);
}
Ok(profiles)
}
/// 获取单个项目
pub fn get_profile(&self, id: &str) -> Result<Option<Profile>, AppError> {
let conn = lock_conn!(self.conn);
let mut stmt = conn
.prepare(
"SELECT id, name, payload, sort_order, created_at, updated_at
FROM profiles WHERE id = ?1",
)
.map_err(|e| AppError::Database(e.to_string()))?;
match stmt.query_row(params![id], |row| {
Ok(Profile {
id: row.get(0)?,
name: row.get(1)?,
payload: row.get(2)?,
sort_order: row.get(3)?,
created_at: row.get(4)?,
updated_at: row.get(5)?,
})
}) {
Ok(profile) => Ok(Some(profile)),
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
Err(e) => Err(AppError::Database(e.to_string())),
}
}
/// 保存项目(插入或整行替换)
pub fn save_profile(&self, profile: &Profile) -> Result<(), AppError> {
let conn = lock_conn!(self.conn);
conn.execute(
"INSERT OR REPLACE INTO profiles
(id, name, payload, sort_order, created_at, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
params![
profile.id,
profile.name,
profile.payload,
profile.sort_order,
profile.created_at,
profile.updated_at,
],
)
.map_err(|e| AppError::Database(e.to_string()))?;
Ok(())
}
/// 删除项目,返回是否实际删除了记录
pub fn delete_profile(&self, id: &str) -> Result<bool, AppError> {
let conn = lock_conn!(self.conn);
let affected = conn
.execute("DELETE FROM profiles WHERE id = ?1", params![id])
.map_err(|e| AppError::Database(e.to_string()))?;
Ok(affected > 0)
}
/// 读取某分组当前激活的项目 id(未使用项目时为 None)
pub fn get_current_profile_id(&self, scope: &str) -> Result<Option<String>, AppError> {
self.get_setting(&current_profile_key(scope))
}
/// 设置某分组当前激活的项目 id;None 表示"不使用项目"(删除 key
pub fn set_current_profile_id(&self, scope: &str, id: Option<&str>) -> Result<(), AppError> {
let key = current_profile_key(scope);
match id {
Some(id) => self.set_setting(&key, id),
None => {
let conn = lock_conn!(self.conn);
conn.execute("DELETE FROM settings WHERE key = ?1", params![key])
.map_err(|e| AppError::Database(e.to_string()))?;
Ok(())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample(id: &str, name: &str, sort_order: Option<i64>) -> Profile {
Profile {
id: id.to_string(),
name: name.to_string(),
payload: r#"{"providers":{"claude":null,"codex":null}}"#.to_string(),
sort_order,
created_at: Some(1_000),
updated_at: Some(1_000),
}
}
#[test]
fn test_profile_crud_roundtrip() -> Result<(), AppError> {
let db = Database::memory()?;
db.save_profile(&sample("a", "Dev", Some(2)))?;
db.save_profile(&sample("b", "Draw", Some(1)))?;
db.save_profile(&sample("c", "Misc", None))?;
// sort_order 优先,NULL 排最后
let all = db.get_all_profiles()?;
assert_eq!(
all.iter().map(|p| p.id.as_str()).collect::<Vec<_>>(),
vec!["b", "a", "c"]
);
let got = db.get_profile("a")?.expect("profile a exists");
assert_eq!(got.name, "Dev");
assert!(got.payload.contains("providers"));
// 整行替换更新
let mut updated = sample("a", "Dev Renamed", Some(2));
updated.updated_at = Some(2_000);
db.save_profile(&updated)?;
let got = db.get_profile("a")?.expect("profile a exists");
assert_eq!(got.name, "Dev Renamed");
assert_eq!(got.updated_at, Some(2_000));
assert!(db.delete_profile("a")?);
assert!(!db.delete_profile("a")?);
assert!(db.get_profile("a")?.is_none());
Ok(())
}
#[test]
fn test_current_profile_id_is_scoped() -> Result<(), AppError> {
let db = Database::memory()?;
assert_eq!(db.get_current_profile_id("claude")?, None);
assert_eq!(db.get_current_profile_id("codex")?, None);
// 两个分组的标记互不影响
db.set_current_profile_id("claude", Some("a"))?;
db.set_current_profile_id("codex", Some("b"))?;
assert_eq!(db.get_current_profile_id("claude")?, Some("a".to_string()));
assert_eq!(db.get_current_profile_id("codex")?, Some("b".to_string()));
db.set_current_profile_id("claude", None)?;
assert_eq!(db.get_current_profile_id("claude")?, None);
assert_eq!(db.get_current_profile_id("codex")?, Some("b".to_string()));
// 重复清除应幂等
db.set_current_profile_id("claude", None)?;
assert_eq!(db.get_current_profile_id("claude")?, None);
Ok(())
}
}
+43 -1
View File
@@ -710,7 +710,10 @@ impl Database {
#[cfg(test)]
mod ensure_official_seed_tests {
use crate::app_config::AppType;
use crate::database::{Database, CLAUDE_DESKTOP_OFFICIAL_PROVIDER_ID};
use crate::database::{
Database, CLAUDE_DESKTOP_OFFICIAL_PROVIDER_ID, CODEX_OFFICIAL_PROVIDER_ID,
GROKBUILD_OFFICIAL_PROVIDER_ID,
};
#[test]
fn ensure_inserts_when_missing() {
@@ -769,6 +772,45 @@ mod ensure_official_seed_tests {
);
}
#[test]
fn ensure_recreates_codex_official_seed_after_deletion() {
let db = Database::memory().expect("memory db");
db.init_default_official_providers().expect("seed");
db.delete_provider(AppType::Codex.as_str(), CODEX_OFFICIAL_PROVIDER_ID)
.expect("delete Codex official");
let inserted = db
.ensure_official_seed_by_id(CODEX_OFFICIAL_PROVIDER_ID, AppType::Codex)
.expect("ensure Codex official");
assert!(inserted);
let provider = db
.get_provider_by_id(CODEX_OFFICIAL_PROVIDER_ID, AppType::Codex.as_str())
.expect("query")
.expect("Codex official restored");
assert_eq!(provider.category.as_deref(), Some("official"));
assert_eq!(provider.settings_config["auth"], serde_json::json!({}));
}
#[test]
fn ensure_recreates_grokbuild_official_seed_after_deletion() {
let db = Database::memory().expect("memory db");
db.init_default_official_providers().expect("seed");
db.delete_provider(AppType::GrokBuild.as_str(), GROKBUILD_OFFICIAL_PROVIDER_ID)
.expect("delete Grok Build official");
let inserted = db
.ensure_official_seed_by_id(GROKBUILD_OFFICIAL_PROVIDER_ID, AppType::GrokBuild)
.expect("ensure Grok Build official");
assert!(inserted);
let provider = db
.get_provider_by_id(GROKBUILD_OFFICIAL_PROVIDER_ID, AppType::GrokBuild.as_str())
.expect("query")
.expect("Grok Build official restored");
assert_eq!(provider.category.as_deref(), Some("official"));
// 空 config:切换时不注入自定义模型表,Grok CLI 回落到自带 OAuth 登录
assert_eq!(provider.settings_config["config"], serde_json::json!(""));
}
#[test]
fn ensure_rejects_unknown_seed() {
let db = Database::memory().expect("memory db");
+27 -1
View File
@@ -7,10 +7,13 @@
//! - `src/config/claudeProviderPresets.ts`"Claude Official"
//! - `src/config/codexProviderPresets.ts`"OpenAI Official"
//! - `src/config/geminiProviderPresets.ts`"Google Official"
//! - `src/components/providers/forms/GrokBuildProviderForm.tsx`"Grok Official"
use crate::app_config::AppType;
pub(crate) const CLAUDE_DESKTOP_OFFICIAL_PROVIDER_ID: &str = "claude-desktop-official";
pub(crate) const CODEX_OFFICIAL_PROVIDER_ID: &str = "codex-official";
pub(crate) const GROKBUILD_OFFICIAL_PROVIDER_ID: &str = "grokbuild-official";
/// 单条官方供应商种子定义。
pub(crate) struct OfficialProviderSeed {
@@ -49,7 +52,7 @@ pub(crate) const OFFICIAL_SEEDS: &[OfficialProviderSeed] = &[
settings_config_json: r#"{"env":{}}"#,
},
OfficialProviderSeed {
id: "codex-official",
id: CODEX_OFFICIAL_PROVIDER_ID,
app_type: AppType::Codex,
name: "OpenAI Official",
website_url: "https://chatgpt.com/codex",
@@ -68,6 +71,16 @@ pub(crate) const OFFICIAL_SEEDS: &[OfficialProviderSeed] = &[
// 空 env + 空 config 让用户走 Google OAuth
settings_config_json: r#"{"env":{},"config":{}}"#,
},
OfficialProviderSeed {
id: GROKBUILD_OFFICIAL_PROVIDER_ID,
app_type: AppType::GrokBuild,
name: "Grok Official",
website_url: "https://x.ai/grok",
icon: "grok",
icon_color: "currentColor",
// 空 config = 不写自定义模型表,Grok CLI 回落到自带的 xAI OAuth 登录
settings_config_json: r#"{"config":""}"#,
},
];
/// 判断给定的 provider id 是否属于内置官方种子。
@@ -91,4 +104,17 @@ mod tests {
assert_eq!(seed.app_type, AppType::ClaudeDesktop);
assert!(is_official_seed_id(CLAUDE_DESKTOP_OFFICIAL_PROVIDER_ID));
}
#[test]
fn official_seeds_include_grokbuild() {
let seed = OFFICIAL_SEEDS
.iter()
.find(|seed| seed.id == GROKBUILD_OFFICIAL_PROVIDER_ID)
.expect("grok build official seed");
assert_eq!(seed.app_type, AppType::GrokBuild);
assert!(is_official_seed_id(GROKBUILD_OFFICIAL_PROVIDER_ID));
// 空 config = 官方登录态:切换时不注入自定义模型表
assert_eq!(seed.settings_config_json, r#"{"config":""}"#);
}
}
+30
View File
@@ -328,6 +328,7 @@ impl Database {
"claude" => (6, 90, 180, 8, 3, 90, 0.7, 15),
"codex" => (3, 60, 120, 4, 2, 60, 0.6, 10),
"gemini" => (5, 60, 120, 4, 2, 60, 0.6, 10),
"grokbuild" => (3, 60, 120, 4, 2, 60, 0.6, 10),
_ => (3, 60, 120, 4, 2, 60, 0.6, 10), // 默认值
};
@@ -398,6 +399,18 @@ impl Database {
)
.map_err(|e| AppError::Database(e.to_string()))?;
// grokbuild: Responses protocol, same timeout defaults as Codex.
conn.execute(
"INSERT OR IGNORE INTO proxy_config (
app_type, max_retries,
streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
circuit_error_rate_threshold, circuit_min_requests
) VALUES ('grokbuild', 3, 60, 120, 600, 4, 2, 60, 0.6, 10)",
[],
)
.map_err(|e| AppError::Database(e.to_string()))?;
Ok(())
}
@@ -494,6 +507,23 @@ impl Database {
Ok(count > 0)
}
/// 同步版本:检查是否有任一 app 的 enabled = true
///
/// 用于 `ProfileService::apply` 等 sync 路径判断是否需要停止代理服务。
pub fn is_live_takeover_active_sync(&self) -> bool {
let conn = match self.conn.lock() {
Ok(c) => c,
Err(_) => return false,
};
conn.query_row(
"SELECT COUNT(*) FROM proxy_config WHERE enabled = 1",
[],
|row| row.get::<_, i64>(0),
)
.unwrap_or(0)
> 0
}
// ==================== Provider Health ====================
/// 获取Provider健康状态
+37 -36
View File
@@ -22,8 +22,8 @@ impl Database {
let mut stmt = conn
.prepare(
"SELECT id, name, description, directory, repo_owner, repo_name, repo_branch,
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode,
enabled_hermes, installed_at, content_hash, updated_at
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_grokbuild,
enabled_opencode, enabled_hermes, installed_at, content_hash, updated_at
FROM skills ORDER BY name ASC",
)
.map_err(|e| AppError::Database(e.to_string()))?;
@@ -43,12 +43,13 @@ impl Database {
claude: row.get(8)?,
codex: row.get(9)?,
gemini: row.get(10)?,
opencode: row.get(11)?,
hermes: row.get(12)?,
grokbuild: row.get(11)?,
opencode: row.get(12)?,
hermes: row.get(13)?,
},
installed_at: row.get(13)?,
content_hash: row.get(14)?,
updated_at: row.get::<_, i64>(15).unwrap_or(0),
installed_at: row.get(14)?,
content_hash: row.get(15)?,
updated_at: row.get::<_, i64>(16).unwrap_or(0),
})
})
.map_err(|e| AppError::Database(e.to_string()))?;
@@ -67,8 +68,8 @@ impl Database {
let mut stmt = conn
.prepare(
"SELECT id, name, description, directory, repo_owner, repo_name, repo_branch,
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode,
enabled_hermes, installed_at, content_hash, updated_at
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_grokbuild,
enabled_opencode, enabled_hermes, installed_at, content_hash, updated_at
FROM skills WHERE id = ?1",
)
.map_err(|e| AppError::Database(e.to_string()))?;
@@ -87,12 +88,13 @@ impl Database {
claude: row.get(8)?,
codex: row.get(9)?,
gemini: row.get(10)?,
opencode: row.get(11)?,
hermes: row.get(12)?,
grokbuild: row.get(11)?,
opencode: row.get(12)?,
hermes: row.get(13)?,
},
installed_at: row.get(13)?,
content_hash: row.get(14)?,
updated_at: row.get::<_, i64>(15).unwrap_or(0),
installed_at: row.get(14)?,
content_hash: row.get(15)?,
updated_at: row.get::<_, i64>(16).unwrap_or(0),
})
});
@@ -109,9 +111,9 @@ impl Database {
conn.execute(
"INSERT OR REPLACE INTO skills
(id, name, description, directory, repo_owner, repo_name, repo_branch,
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, enabled_hermes,
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_grokbuild, enabled_opencode, enabled_hermes,
installed_at, content_hash, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16)",
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17)",
params![
skill.id,
skill.name,
@@ -124,6 +126,7 @@ impl Database {
skill.apps.claude,
skill.apps.codex,
skill.apps.gemini,
skill.apps.grokbuild,
skill.apps.opencode,
skill.apps.hermes,
skill.installed_at,
@@ -157,8 +160,8 @@ impl Database {
let conn = lock_conn!(self.conn);
let affected = conn
.execute(
"UPDATE skills SET enabled_claude = ?1, enabled_codex = ?2, enabled_gemini = ?3, enabled_opencode = ?4, enabled_hermes = ?5 WHERE id = ?6",
params![apps.claude, apps.codex, apps.gemini, apps.opencode, apps.hermes, id],
"UPDATE skills SET enabled_claude = ?1, enabled_codex = ?2, enabled_gemini = ?3, enabled_grokbuild = ?4, enabled_opencode = ?5, enabled_hermes = ?6 WHERE id = ?7",
params![apps.claude, apps.codex, apps.gemini, apps.grokbuild, apps.opencode, apps.hermes, id],
)
.map_err(|e| AppError::Database(e.to_string()))?;
Ok(affected > 0)
@@ -232,32 +235,30 @@ impl Database {
Ok(())
}
/// 初始化默认的 Skill 仓库(启动时调用,补充缺失的默认仓库
/// 初始化默认的 Skill 仓库(启动时调用,每个数据库仅执行一次
pub fn init_default_skill_repos(&self) -> Result<usize, AppError> {
// 获取已有仓库列表
let existing = self.get_skill_repos()?;
let existing_keys: std::collections::HashSet<(String, String)> = existing
.iter()
.map(|r| (r.owner.clone(), r.name.clone()))
.collect();
const INITIALIZED_KEY: &str = "default_skill_repos_initialized";
if self.get_bool_flag(INITIALIZED_KEY)? {
return Ok(0);
}
// 兼容升级前已经存在的用户选择,并记录初始化状态,避免以后删空后恢复默认值。
if !self.get_skill_repos()?.is_empty() {
self.set_setting(INITIALIZED_KEY, "true")?;
return Ok(0);
}
// 获取默认仓库列表
let default_store = crate::services::skill::SkillStore::default();
let mut count = 0;
// 仅插入缺失的默认仓库
for repo in &default_store.repos {
let key = (repo.owner.clone(), repo.name.clone());
if !existing_keys.contains(&key) {
self.save_skill_repo(repo)?;
count += 1;
log::info!("补充默认 Skill 仓库: {}/{}", repo.owner, repo.name);
}
self.save_skill_repo(repo)?;
count += 1;
log::info!("初始化默认 Skill 仓库: {}/{}", repo.owner, repo.name);
}
if count > 0 {
log::info!("补充默认 Skill 仓库完成,新增 {count} 个");
}
self.set_setting(INITIALIZED_KEY, "true")?;
Ok(count)
}
}
+42 -4
View File
@@ -4,6 +4,7 @@
use crate::database::{lock_conn, Database};
use crate::error::AppError;
use crate::services::sql_helpers::{fresh_input_sql, INPUT_TOKEN_SEMANTICS_FRESH};
use crate::services::usage_stats::effective_usage_log_filter;
use chrono::{Duration, Local, TimeZone};
@@ -115,6 +116,8 @@ impl Database {
fn do_rollup_and_prune(conn: &rusqlite::Connection, cutoff: i64) -> Result<u64, AppError> {
// Aggregate old logs, merging with any pre-existing rollup rows via LEFT JOIN.
let effective_filter = effective_usage_log_filter("l");
let fresh_detail_input = fresh_input_sql("l");
let fresh_old_input = fresh_input_sql("old");
// request_model 维度保留路由接管的「客户端别名 → 真实模型」映射,
// pricing_model 维度保留写入时的计价基准(request 计价模式下与 model 分叉);
// 明细行的这两列可能为 NULL(历史/手工数据),归一为 ''。
@@ -124,15 +127,16 @@ impl Database {
request_count, success_count,
input_tokens, output_tokens,
cache_read_tokens, cache_creation_tokens,
total_cost_usd, avg_latency_ms)
input_token_semantics, total_cost_usd, avg_latency_ms)
SELECT
d, a, p, m, rm, pm,
COALESCE(old.request_count, 0) + new_req,
COALESCE(old.success_count, 0) + new_succ,
COALESCE(old.input_tokens, 0) + new_in,
COALESCE({fresh_old_input}, 0) + new_in,
COALESCE(old.output_tokens, 0) + new_out,
COALESCE(old.cache_read_tokens, 0) + new_cr,
COALESCE(old.cache_creation_tokens, 0) + new_cc,
{INPUT_TOKEN_SEMANTICS_FRESH},
CAST(COALESCE(CAST(old.total_cost_usd AS REAL), 0) + new_cost AS TEXT),
CASE WHEN COALESCE(old.request_count, 0) + new_req > 0
THEN (COALESCE(old.avg_latency_ms, 0) * COALESCE(old.request_count, 0)
@@ -147,7 +151,7 @@ impl Database {
COALESCE(l.pricing_model, '') as pm,
COUNT(*) as new_req,
SUM(CASE WHEN l.status_code >= 200 AND l.status_code < 300 THEN 1 ELSE 0 END) as new_succ,
COALESCE(SUM(l.input_tokens), 0) as new_in,
COALESCE(SUM({fresh_detail_input}), 0) as new_in,
COALESCE(SUM(l.output_tokens), 0) as new_out,
COALESCE(SUM(l.cache_read_tokens), 0) as new_cr,
COALESCE(SUM(l.cache_creation_tokens), 0) as new_cc,
@@ -327,7 +331,7 @@ mod tests {
let (provider_id, request_count, input_tokens, output_tokens, cache_read_tokens) = &rows[0];
assert_eq!(provider_id, "openai");
assert_eq!(*request_count, 1);
assert_eq!(*input_tokens, 100);
assert_eq!(*input_tokens, 90, "rollup stores normalized fresh input");
assert_eq!(*output_tokens, 20);
assert_eq!(*cache_read_tokens, 10);
@@ -340,6 +344,40 @@ mod tests {
Ok(())
}
#[test]
fn test_rollup_normalizes_total_cache_semantics_to_fresh() -> Result<(), AppError> {
let db = Database::memory()?;
let old_ts = chrono::Utc::now().timestamp() - 40 * 86400;
{
let conn = crate::database::lock_conn!(db.conn);
conn.execute(
"INSERT INTO proxy_request_logs (
request_id, provider_id, app_type, model,
input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens,
input_token_semantics, total_cost_usd,
latency_ms, status_code, created_at
) VALUES ('total-semantics-rollup', 'p1', 'codex', 'gpt-5.5',
100, 5, 10, 20, 1, '0.10', 100, 200, ?1)",
[old_ts],
)?;
}
assert_eq!(db.rollup_and_prune(30)?, 1);
let conn = crate::database::lock_conn!(db.conn);
let row: (i64, i64, i64, i64) = conn.query_row(
"SELECT input_tokens, cache_read_tokens, cache_creation_tokens,
input_token_semantics
FROM usage_daily_rollups WHERE model = 'gpt-5.5'",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
)?;
assert_eq!(row, (70, 10, 20, 2));
Ok(())
}
#[test]
fn test_rollup_preserves_request_model_dimension() -> Result<(), AppError> {
let db = Database::memory()?;
+6 -2
View File
@@ -32,12 +32,16 @@ mod schema;
mod tests;
// DAO 类型导出供外部使用
pub(crate) use dao::providers_seed::{is_official_seed_id, CLAUDE_DESKTOP_OFFICIAL_PROVIDER_ID};
pub(crate) use dao::providers_seed::{
is_official_seed_id, CLAUDE_DESKTOP_OFFICIAL_PROVIDER_ID, CODEX_OFFICIAL_PROVIDER_ID,
GROKBUILD_OFFICIAL_PROVIDER_ID,
};
pub(crate) use dao::proxy::{
validate_cost_multiplier, validate_pricing_source, PRICING_SOURCE_REQUEST,
PRICING_SOURCE_RESPONSE,
};
pub use dao::FailoverQueueItem;
pub use dao::Profile;
use crate::config::get_app_config_dir;
use crate::error::AppError;
@@ -49,7 +53,7 @@ use std::sync::Mutex;
/// 当前 Schema 版本号
/// 每次修改表结构时递增,并在 schema.rs 中添加相应的迁移逻辑
pub(crate) const SCHEMA_VERSION: i32 = 11;
pub(crate) const SCHEMA_VERSION: i32 = 16;
/// 安全地序列化 JSON,避免 unwrap panic
pub(crate) fn to_json_string<T: Serialize>(value: &T) -> Result<String, AppError> {
+484 -3
View File
@@ -65,7 +65,8 @@ impl Database {
id TEXT PRIMARY KEY, name TEXT NOT NULL, server_config TEXT NOT NULL,
description TEXT, homepage TEXT, docs TEXT, tags TEXT NOT NULL DEFAULT '[]',
enabled_claude BOOLEAN NOT NULL DEFAULT 0, enabled_codex BOOLEAN NOT NULL DEFAULT 0,
enabled_gemini BOOLEAN NOT NULL DEFAULT 0, enabled_opencode BOOLEAN NOT NULL DEFAULT 0,
enabled_gemini BOOLEAN NOT NULL DEFAULT 0, enabled_grokbuild BOOLEAN NOT NULL DEFAULT 0,
enabled_opencode BOOLEAN NOT NULL DEFAULT 0,
enabled_hermes BOOLEAN NOT NULL DEFAULT 0
)",
[],
@@ -93,6 +94,7 @@ impl Database {
enabled_claude BOOLEAN NOT NULL DEFAULT 0,
enabled_codex BOOLEAN NOT NULL DEFAULT 0,
enabled_gemini BOOLEAN NOT NULL DEFAULT 0,
enabled_grokbuild BOOLEAN NOT NULL DEFAULT 0,
enabled_opencode BOOLEAN NOT NULL DEFAULT 0,
enabled_hermes BOOLEAN NOT NULL DEFAULT 0,
installed_at INTEGER NOT NULL DEFAULT 0,
@@ -122,7 +124,7 @@ impl Database {
// 8. Proxy Config 表(三行结构,app_type 主键)
conn.execute("CREATE TABLE IF NOT EXISTS proxy_config (
app_type TEXT PRIMARY KEY CHECK (app_type IN ('claude','codex','gemini')),
app_type TEXT PRIMARY KEY CHECK (app_type IN ('claude','codex','gemini','grokbuild')),
proxy_enabled INTEGER NOT NULL DEFAULT 0, listen_address TEXT NOT NULL DEFAULT '127.0.0.1',
listen_port INTEGER NOT NULL DEFAULT 15721, enable_logging INTEGER NOT NULL DEFAULT 1,
enabled INTEGER NOT NULL DEFAULT 0, auto_failover_enabled INTEGER NOT NULL DEFAULT 0,
@@ -169,6 +171,15 @@ impl Database {
[],
)
.map_err(|e| AppError::Database(e.to_string()))?;
conn.execute(
"INSERT OR IGNORE INTO proxy_config (app_type, max_retries,
streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
circuit_error_rate_threshold, circuit_min_requests)
VALUES ('grokbuild', 3, 60, 120, 600, 4, 2, 60, 0.6, 10)",
[],
)
.map_err(|e| AppError::Database(e.to_string()))?;
}
// 9. Provider Health 表
@@ -189,6 +200,7 @@ impl Database {
pricing_model TEXT,
input_tokens INTEGER NOT NULL DEFAULT 0, output_tokens INTEGER NOT NULL DEFAULT 0,
cache_read_tokens INTEGER NOT NULL DEFAULT 0, cache_creation_tokens INTEGER NOT NULL DEFAULT 0,
input_token_semantics INTEGER NOT NULL DEFAULT 0,
input_cost_usd TEXT NOT NULL DEFAULT '0', output_cost_usd TEXT NOT NULL DEFAULT '0',
cache_read_cost_usd TEXT NOT NULL DEFAULT '0', cache_creation_cost_usd TEXT NOT NULL DEFAULT '0',
total_cost_usd TEXT NOT NULL DEFAULT '0', latency_ms INTEGER NOT NULL, first_token_ms INTEGER,
@@ -275,6 +287,7 @@ impl Database {
output_tokens INTEGER NOT NULL DEFAULT 0,
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
cache_creation_tokens INTEGER NOT NULL DEFAULT 0,
input_token_semantics INTEGER NOT NULL DEFAULT 0,
total_cost_usd TEXT NOT NULL DEFAULT '0',
avg_latency_ms INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (date, app_type, provider_id, model, request_model, pricing_model)
@@ -295,6 +308,35 @@ impl Database {
)
.map_err(|e| AppError::Database(e.to_string()))?;
// 19. Profiles 表(全应用共享的项目实体,payload 按 app 分槽快照
// 供应商/MCP/Skills/Prompt;各应用分组的 current 标记在 settings 表)
conn.execute(
"CREATE TABLE IF NOT EXISTS profiles (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
payload TEXT NOT NULL,
sort_order INTEGER,
created_at INTEGER,
updated_at INTEGER
)",
[],
)
.map_err(|e| AppError::Database(e.to_string()))?;
// 修复跑过未发布开发版的库:current 标记曾是全局 key,现按应用分组
// (随 v12 定稿为 current_profile_id_<scope>,不单独 bump 版本)
if conn
.execute(
"INSERT OR REPLACE INTO settings (key, value)
SELECT 'current_profile_id_claude', value FROM settings
WHERE key = 'current_profile_id'",
[],
)
.is_ok()
{
let _ = conn.execute("DELETE FROM settings WHERE key = 'current_profile_id'", []);
}
// 尝试添加 live_takeover_active 列到 proxy_config 表
let _ = conn.execute(
"ALTER TABLE proxy_config ADD COLUMN live_takeover_active INTEGER NOT NULL DEFAULT 0",
@@ -444,6 +486,31 @@ impl Database {
Self::migrate_v10_to_v11(conn)?;
Self::set_user_version(conn, 11)?;
}
11 => {
log::info!("迁移数据库从 v11 到 v12(添加项目 Profiles 表)");
Self::migrate_v11_to_v12(conn)?;
Self::set_user_version(conn, 12)?;
}
12 => {
log::info!("迁移数据库从 v12 到 v13(记录输入 token 缓存语义)");
Self::migrate_v12_to_v13(conn)?;
Self::set_user_version(conn, 13)?;
}
13 => {
log::info!("迁移数据库从 v13 到 v14(添加 Grok Build 代理配置)");
Self::migrate_v13_to_v14(conn)?;
Self::set_user_version(conn, 14)?;
}
14 => {
log::info!("迁移数据库从 v14 到 v15Skills/MCP 添加 Grok Build 支持)");
Self::migrate_v14_to_v15(conn)?;
Self::set_user_version(conn, 15)?;
}
15 => {
log::info!("迁移数据库从 v15 到 v16(重建 Codex 会话用量)");
Self::migrate_v15_to_v16(conn)?;
Self::set_user_version(conn, 16)?;
}
_ => {
return Err(AppError::Database(format!(
"未知的数据库版本 {version},无法迁移到 {SCHEMA_VERSION}"
@@ -616,6 +683,7 @@ impl Database {
request_model TEXT,
input_tokens INTEGER NOT NULL DEFAULT 0, output_tokens INTEGER NOT NULL DEFAULT 0,
cache_read_tokens INTEGER NOT NULL DEFAULT 0, cache_creation_tokens INTEGER NOT NULL DEFAULT 0,
input_token_semantics INTEGER NOT NULL DEFAULT 0,
input_cost_usd TEXT NOT NULL DEFAULT '0', output_cost_usd TEXT NOT NULL DEFAULT '0',
cache_read_cost_usd TEXT NOT NULL DEFAULT '0', cache_creation_cost_usd TEXT NOT NULL DEFAULT '0',
total_cost_usd TEXT NOT NULL DEFAULT '0', latency_ms INTEGER NOT NULL, first_token_ms INTEGER,
@@ -756,12 +824,25 @@ impl Database {
old_cb.3,
old_cb.4,
),
(
"grokbuild",
false,
false,
3,
old_config.4,
old_config.5,
old_cb.0,
old_cb.1,
old_cb.2,
old_cb.3,
old_cb.4,
),
];
// 创建新表
conn.execute("DROP TABLE IF EXISTS proxy_config_new", [])?;
conn.execute("CREATE TABLE proxy_config_new (
app_type TEXT PRIMARY KEY CHECK (app_type IN ('claude','codex','gemini')),
app_type TEXT PRIMARY KEY CHECK (app_type IN ('claude','codex','gemini','grokbuild')),
proxy_enabled INTEGER NOT NULL DEFAULT 0, listen_address TEXT NOT NULL DEFAULT '127.0.0.1',
listen_port INTEGER NOT NULL DEFAULT 15721, enable_logging INTEGER NOT NULL DEFAULT 1,
enabled INTEGER NOT NULL DEFAULT 0, auto_failover_enabled INTEGER NOT NULL DEFAULT 0,
@@ -772,6 +853,7 @@ impl Database {
circuit_min_requests INTEGER NOT NULL DEFAULT 10,
default_cost_multiplier TEXT NOT NULL DEFAULT '1',
pricing_model_source TEXT NOT NULL DEFAULT 'response',
live_takeover_active INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now'))
)", [])?;
@@ -1270,6 +1352,177 @@ impl Database {
Ok(())
}
/// v11 -> v12 迁移:添加项目 Profiles 表
/// 与 create_tables_on_conn 中的建表语句保持一致(IF NOT EXISTS 保证幂等)
fn migrate_v11_to_v12(conn: &Connection) -> Result<(), AppError> {
conn.execute(
"CREATE TABLE IF NOT EXISTS profiles (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
payload TEXT NOT NULL,
sort_order INTEGER,
created_at INTEGER,
updated_at INTEGER
)",
[],
)
.map_err(|e| AppError::Database(format!("v11 -> v12 创建 profiles 表失败: {e}")))?;
Ok(())
}
/// v12 -> v13:记录 input_tokens 是否包含缓存写入。
///
/// 默认 0 表示旧版/未知语义;旧 Codex 行只包含 cache read,不包含
/// cache creation。新代理行会显式写入 1(total-inclusive) 或 2(fresh)。
fn migrate_v12_to_v13(conn: &Connection) -> Result<(), AppError> {
if Self::table_exists(conn, "proxy_request_logs")? {
Self::add_column_if_missing(
conn,
"proxy_request_logs",
"input_token_semantics",
"INTEGER NOT NULL DEFAULT 0",
)?;
}
if Self::table_exists(conn, "usage_daily_rollups")? {
Self::add_column_if_missing(
conn,
"usage_daily_rollups",
"input_token_semantics",
"INTEGER NOT NULL DEFAULT 0",
)?;
}
Ok(())
}
/// v13 -> v14: allow Grok Build to own an independent proxy configuration row.
fn migrate_v13_to_v14(conn: &Connection) -> Result<(), AppError> {
if !Self::table_exists(conn, "proxy_config")? {
return Ok(());
}
conn.execute("DROP TABLE IF EXISTS proxy_config_v14", [])
.map_err(|e| AppError::Database(e.to_string()))?;
conn.execute(
"CREATE TABLE proxy_config_v14 (
app_type TEXT PRIMARY KEY CHECK (app_type IN ('claude','codex','gemini','grokbuild')),
proxy_enabled INTEGER NOT NULL DEFAULT 0,
listen_address TEXT NOT NULL DEFAULT '127.0.0.1',
listen_port INTEGER NOT NULL DEFAULT 15721,
enable_logging INTEGER NOT NULL DEFAULT 1,
enabled INTEGER NOT NULL DEFAULT 0,
auto_failover_enabled INTEGER NOT NULL DEFAULT 0,
max_retries INTEGER NOT NULL DEFAULT 3,
streaming_first_byte_timeout INTEGER NOT NULL DEFAULT 60,
streaming_idle_timeout INTEGER NOT NULL DEFAULT 120,
non_streaming_timeout INTEGER NOT NULL DEFAULT 600,
circuit_failure_threshold INTEGER NOT NULL DEFAULT 4,
circuit_success_threshold INTEGER NOT NULL DEFAULT 2,
circuit_timeout_seconds INTEGER NOT NULL DEFAULT 60,
circuit_error_rate_threshold REAL NOT NULL DEFAULT 0.6,
circuit_min_requests INTEGER NOT NULL DEFAULT 10,
default_cost_multiplier TEXT NOT NULL DEFAULT '1',
pricing_model_source TEXT NOT NULL DEFAULT 'response',
live_takeover_active INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
)",
[],
)
.map_err(|e| AppError::Database(e.to_string()))?;
let copied_columns = [
("app_type", "'claude'"),
("proxy_enabled", "0"),
("listen_address", "'127.0.0.1'"),
("listen_port", "15721"),
("enable_logging", "1"),
("enabled", "0"),
("auto_failover_enabled", "0"),
("max_retries", "3"),
("streaming_first_byte_timeout", "60"),
("streaming_idle_timeout", "120"),
("non_streaming_timeout", "600"),
("circuit_failure_threshold", "4"),
("circuit_success_threshold", "2"),
("circuit_timeout_seconds", "60"),
("circuit_error_rate_threshold", "0.6"),
("circuit_min_requests", "10"),
("default_cost_multiplier", "'1'"),
("pricing_model_source", "'response'"),
("live_takeover_active", "0"),
("created_at", "datetime('now')"),
("updated_at", "datetime('now')"),
]
.into_iter()
.map(|(column, fallback)| {
Self::has_column(conn, "proxy_config", column).map(|exists| {
if exists {
format!("\"{column}\"")
} else {
fallback.into()
}
})
})
.collect::<Result<Vec<_>, AppError>>()?
.join(", ");
let copy_sql = format!(
"INSERT INTO proxy_config_v14 (
app_type, proxy_enabled, listen_address, listen_port, enable_logging,
enabled, auto_failover_enabled, max_retries,
streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
circuit_error_rate_threshold, circuit_min_requests,
default_cost_multiplier, pricing_model_source, live_takeover_active,
created_at, updated_at
)
SELECT {copied_columns} FROM proxy_config"
);
conn.execute(&copy_sql, [])
.map_err(|e| AppError::Database(e.to_string()))?;
conn.execute("DROP TABLE proxy_config", [])
.map_err(|e| AppError::Database(e.to_string()))?;
conn.execute("ALTER TABLE proxy_config_v14 RENAME TO proxy_config", [])
.map_err(|e| AppError::Database(e.to_string()))?;
conn.execute(
"INSERT OR IGNORE INTO proxy_config (app_type) VALUES ('grokbuild')",
[],
)
.map_err(|e| AppError::Database(e.to_string()))?;
Ok(())
}
/// v14 -> v15: persist Grok Build enablement for unified Skills and MCP.
fn migrate_v14_to_v15(conn: &Connection) -> Result<(), AppError> {
if Self::table_exists(conn, "mcp_servers")? {
Self::add_column_if_missing(
conn,
"mcp_servers",
"enabled_grokbuild",
"BOOLEAN NOT NULL DEFAULT 0",
)?;
}
if Self::table_exists(conn, "skills")? {
Self::add_column_if_missing(
conn,
"skills",
"enabled_grokbuild",
"BOOLEAN NOT NULL DEFAULT 0",
)?;
}
Ok(())
}
/// v15 -> v16: remove Codex session rows and cursors so startup sync can
/// rebuild them with fork-history alignment. Must stay connection-level:
/// schema migration already owns the Database connection mutex.
fn migrate_v15_to_v16(conn: &Connection) -> Result<(), AppError> {
let codex_dir = crate::codex_config::get_codex_config_dir();
crate::services::session_usage_codex::reset_codex_usage_on_conn(conn, &codex_dir)
}
/// 插入默认模型定价数据
/// 格式: (model_id, display_name, input, output, cache_read, cache_creation)
/// 注意: model_id 使用短横线格式(如 claude-haiku-4-5),与 API 返回的模型名称标准化后一致
@@ -1403,6 +1656,25 @@ impl Database {
"0.30",
"3.75",
),
// GPT-5.6 系列(Sol / Terra / Luna2026-06 发布)
// 5.6 家族起 cache write 收 1.25× 输入价(此前 GPT 模型写缓存免费,勿回填旧系列)
("gpt-5.6-sol", "GPT-5.6 Sol", "5", "30", "0.50", "6.25"),
(
"gpt-5.6-terra",
"GPT-5.6 Terra",
"2.50",
"15",
"0.25",
"3.125",
),
("gpt-5.6-luna", "GPT-5.6 Luna", "1", "6", "0.10", "1.25"),
// 裸名 gpt-5.6 是 sol 的官方别名;effort 后缀对齐 gpt-5.5 系列的记账形态
("gpt-5.6", "GPT-5.6 Sol", "5", "30", "0.50", "6.25"),
("gpt-5.6-low", "GPT-5.6 Sol", "5", "30", "0.50", "6.25"),
("gpt-5.6-medium", "GPT-5.6 Sol", "5", "30", "0.50", "6.25"),
("gpt-5.6-high", "GPT-5.6 Sol", "5", "30", "0.50", "6.25"),
("gpt-5.6-xhigh", "GPT-5.6 Sol", "5", "30", "0.50", "6.25"),
("gpt-5.6-minimal", "GPT-5.6 Sol", "5", "30", "0.50", "6.25"),
// GPT-5.5 系列
("gpt-5.5", "GPT-5.5", "5", "30", "0.50", "0"),
("gpt-5.5-low", "GPT-5.5", "5", "30", "0.50", "0"),
@@ -1840,6 +2112,12 @@ impl Database {
"0.19",
"0",
),
("kimi-k3", "Kimi K3", "3.00", "15.00", "0.30", "0"),
// Kimi For Coding 套餐里 K3 的裸名(无 kimi- 前缀),同标准 list 价
("k3", "Kimi K3", "3.00", "15.00", "0.30", "0"),
// 腾讯混元 (Tencent Hunyuan)(官方 CNY 1/4/0.25 按 1 USD ≈ 7.14 折算;Hy3 阶梯计价取最低档)
("hunyuan-hy3", "Hunyuan Hy3", "0.14", "0.56", "0.035", "0"),
("hy3", "Hunyuan Hy3", "0.14", "0.56", "0.035", "0"),
// MiniMax 系列
("minimax-m2.1", "MiniMax M2.1", "0.27", "0.95", "0.03", "0"),
(
@@ -1967,6 +2245,11 @@ impl Database {
("qwq-32b", "QwQ 32B", "0.20", "0.60", "0", "0"),
("qwen3-32b", "Qwen3 32B", "0.16", "0.64", "0", "0"),
// Grok 系列 (xAI)
("grok-4.5", "Grok 4.5", "2", "6", "0.50", "0"),
// Grok CLI 官方 OAuth 态 modelUsage 上报的内部别名。定价由
// costUsdTicks1 tick = 1e-10 USD)双轮实测反推:input/output 与
// grok-4.5 同为 2/6cache read 实际按 0.30 计(非 API 挂牌的 0.50
("grok-4.5-build", "Grok 4.5 Build", "2", "6", "0.30", "0"),
("grok-4.3", "Grok 4.3", "1.25", "2.50", "0.20", "0"),
(
"grok-4.20-0309-reasoning",
@@ -2135,6 +2418,44 @@ impl Database {
fn repair_current_model_pricing(conn: &Connection) -> Result<(), AppError> {
let pricing_fixes = [
// 2026-07-12 GPT-5.6 家族 cache write=1.25× 输入价(OpenAI 5.6 起的新规),
// 修正早期 seed 的 0 值;只匹配未被用户改过的行
(
"gpt-5.6-sol",
"GPT-5.6 Sol",
"5",
"30",
"0.50",
"6.25",
"5",
"30",
"0.50",
"0",
),
(
"gpt-5.6-terra",
"GPT-5.6 Terra",
"2.50",
"15",
"0.25",
"3.125",
"2.50",
"15",
"0.25",
"0",
),
(
"gpt-5.6-luna",
"GPT-5.6 Luna",
"1",
"6",
"0.10",
"1.25",
"1",
"6",
"0.10",
"0",
),
// 2026-06-10 全量核价(厂商官方 list 价;CNY 按 ~7.14 折算)
// GLM 4.6/4.7:旧值是中转/OpenRouter 折扣价,统一到 Z.ai 官方(与 glm-5/5.1 一致)
(
@@ -2602,3 +2923,163 @@ impl Database {
Ok(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn migrate_v12_to_v13_adds_input_token_semantics_columns() -> Result<(), AppError> {
let conn = Connection::open_in_memory()?;
conn.execute(
"CREATE TABLE proxy_request_logs (request_id TEXT PRIMARY KEY)",
[],
)?;
conn.execute(
"CREATE TABLE usage_daily_rollups (date TEXT PRIMARY KEY)",
[],
)?;
Database::set_user_version(&conn, 12)?;
Database::apply_schema_migrations_on_conn(&conn)?;
assert_eq!(Database::get_user_version(&conn)?, SCHEMA_VERSION);
assert!(Database::has_column(
&conn,
"proxy_request_logs",
"input_token_semantics"
)?);
assert!(Database::has_column(
&conn,
"usage_daily_rollups",
"input_token_semantics"
)?);
let log_default: i64 = conn.query_row(
"SELECT dflt_value = '0' FROM pragma_table_info('proxy_request_logs')
WHERE name = 'input_token_semantics'",
[],
|row| row.get(0),
)?;
assert_eq!(log_default, 1);
Ok(())
}
#[test]
fn migrate_v13_to_v14_adds_grokbuild_proxy_row_and_preserves_values() -> Result<(), AppError> {
let conn = Connection::open_in_memory()?;
Database::create_tables_on_conn(&conn)?;
conn.execute("DELETE FROM proxy_config WHERE app_type = 'grokbuild'", [])?;
conn.execute(
"UPDATE proxy_config SET enabled = 1, max_retries = 9 WHERE app_type = 'codex'",
[],
)?;
Database::set_user_version(&conn, 13)?;
Database::apply_schema_migrations_on_conn(&conn)?;
assert_eq!(Database::get_user_version(&conn)?, SCHEMA_VERSION);
let grok_rows: i64 = conn.query_row(
"SELECT COUNT(*) FROM proxy_config WHERE app_type = 'grokbuild'",
[],
|row| row.get(0),
)?;
assert_eq!(grok_rows, 1);
let codex_values: (i64, i64) = conn.query_row(
"SELECT enabled, max_retries FROM proxy_config WHERE app_type = 'codex'",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)?;
assert_eq!(codex_values, (1, 9));
Ok(())
}
#[test]
fn migrate_v14_to_v15_adds_grokbuild_skill_and_mcp_flags() -> Result<(), AppError> {
let conn = Connection::open_in_memory()?;
conn.execute_batch(
"CREATE TABLE mcp_servers (
id TEXT PRIMARY KEY,
enabled_codex BOOLEAN NOT NULL DEFAULT 0
);
CREATE TABLE skills (
id TEXT PRIMARY KEY,
enabled_codex BOOLEAN NOT NULL DEFAULT 0
);",
)?;
conn.execute(
"INSERT INTO mcp_servers (id, enabled_codex) VALUES ('mcp-1', 1)",
[],
)?;
conn.execute(
"INSERT INTO skills (id, enabled_codex) VALUES ('skill-1', 1)",
[],
)?;
Database::set_user_version(&conn, 14)?;
Database::apply_schema_migrations_on_conn(&conn)?;
assert_eq!(Database::get_user_version(&conn)?, SCHEMA_VERSION);
assert!(Database::has_column(
&conn,
"mcp_servers",
"enabled_grokbuild"
)?);
assert!(Database::has_column(&conn, "skills", "enabled_grokbuild")?);
let mcp_values: (i64, i64) = conn.query_row(
"SELECT enabled_codex, enabled_grokbuild FROM mcp_servers WHERE id = 'mcp-1'",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)?;
let skill_values: (i64, i64) = conn.query_row(
"SELECT enabled_codex, enabled_grokbuild FROM skills WHERE id = 'skill-1'",
[],
|row| Ok((row.get(0)?, row.get(1)?)),
)?;
assert_eq!(mcp_values, (1, 0));
assert_eq!(skill_values, (1, 0));
Ok(())
}
#[test]
fn migrate_v15_to_v16_resets_only_codex_session_usage() -> Result<(), AppError> {
let conn = Connection::open_in_memory()?;
Database::create_tables_on_conn(&conn)?;
conn.execute_batch(
"INSERT INTO proxy_request_logs (
request_id, provider_id, app_type, model, input_tokens,
output_tokens, cache_read_tokens, latency_ms, status_code,
created_at, data_source
) VALUES
('codex-row', '_codex_session', 'codex', 'gpt', 1, 1, 0, 0, 200, 1, 'codex_session'),
('gemini-row', '_gemini_session', 'gemini', 'gemini', 1, 1, 0, 0, 200, 1, 'gemini_session');
INSERT INTO usage_daily_rollups (date, app_type, provider_id, model)
VALUES
('2026-07-10', 'codex', '_codex_session', 'gpt'),
('2026-07-10', 'gemini', '_gemini_session', 'gemini');
INSERT INTO session_log_sync
(file_path, last_modified, last_line_offset, last_synced_at)
VALUES
('/old/sessions/rollout-old-00000000-0000-4000-8000-000000000001.jsonl', 1, 1, 1),
('/gemini/tmp/session-123.json', 1, 1, 1);",
)?;
Database::set_user_version(&conn, 15)?;
Database::apply_schema_migrations_on_conn(&conn)?;
assert_eq!(Database::get_user_version(&conn)?, 16);
let counts: (i64, i64, i64, i64) = conn.query_row(
"SELECT
(SELECT COUNT(*) FROM proxy_request_logs WHERE data_source = 'codex_session'),
(SELECT COUNT(*) FROM proxy_request_logs WHERE data_source = 'gemini_session'),
(SELECT COUNT(*) FROM usage_daily_rollups WHERE provider_id = '_codex_session'),
(SELECT COUNT(*) FROM session_log_sync)",
[],
|row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)),
)?;
assert_eq!(counts, (0, 1, 0, 1));
Ok(())
}
}
+90 -2
View File
@@ -151,6 +151,38 @@ fn normalize_default(default: &Option<String>) -> Option<String> {
.map(|s| s.trim_matches('\'').trim_matches('"').to_string())
}
#[test]
fn deleted_default_skill_repo_is_not_restored() {
let db = Database::memory().expect("create memory db");
assert_eq!(db.init_default_skill_repos().expect("initialize repos"), 4);
for repo in db.get_skill_repos().expect("get initialized repos") {
db.delete_skill_repo(&repo.owner, &repo.name)
.expect("delete repo");
}
assert!(db.get_skill_repos().expect("get deleted repos").is_empty());
assert_eq!(
db.init_default_skill_repos().expect("reinitialize repos"),
0
);
assert!(db.get_skill_repos().expect("get repos").is_empty());
}
#[test]
fn existing_skill_repo_selection_is_not_supplemented() {
let db = Database::memory().expect("create memory db");
let default_store = crate::services::skill::SkillStore::default();
db.save_skill_repo(&default_store.repos[0])
.expect("save existing repo");
assert_eq!(db.init_default_skill_repos().expect("initialize repos"), 0);
assert_eq!(db.get_skill_repos().expect("get repos").len(), 1);
assert!(db
.get_bool_flag("default_skill_repos_initialized")
.expect("get initialized flag"));
}
#[test]
fn schema_migration_sets_user_version_when_missing() {
let conn = Connection::open_in_memory().expect("open memory db");
@@ -426,6 +458,62 @@ fn migration_v10_to_v11_rebuilds_rollups_with_request_model_dimension() {
);
}
#[test]
fn schema_create_tables_repairs_dev_global_profile_marker() {
let conn = Connection::open_in_memory().expect("open memory db");
// 模拟跑过未发布开发版的库:user_version 已是 12(迁移不会再跑),
// 但 current 标记还是全局 key(现按应用分组)
conn.execute_batch(
r#"
CREATE TABLE profiles (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
payload TEXT NOT NULL,
sort_order INTEGER,
created_at INTEGER,
updated_at INTEGER
);
INSERT INTO profiles (id, name, payload) VALUES ('p1', 'Project A', '{}');
CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT);
INSERT INTO settings (key, value) VALUES ('current_profile_id', 'p1');
"#,
)
.expect("seed dev v12 shape");
Database::set_user_version(&conn, 12).expect("set user_version=12");
Database::create_tables_on_conn(&conn).expect("create tables should repair marker");
// 全局 current 标记改名为 claude 组标记,旧 key 删除
let claude_marker: String = conn
.query_row(
"SELECT value FROM settings WHERE key = 'current_profile_id_claude'",
[],
|row| row.get(0),
)
.expect("scoped current marker");
assert_eq!(claude_marker, "p1");
let old_marker: i64 = conn
.query_row(
"SELECT COUNT(*) FROM settings WHERE key = 'current_profile_id'",
[],
|row| row.get(0),
)
.expect("count old marker");
assert_eq!(old_marker, 0);
// 修复必须幂等:再跑一遍不应破坏已迁移的标记
Database::create_tables_on_conn(&conn).expect("repair is idempotent");
let claude_marker: String = conn
.query_row(
"SELECT value FROM settings WHERE key = 'current_profile_id_claude'",
[],
|row| row.get(0),
)
.expect("scoped current marker survives");
assert_eq!(claude_marker, "p1");
}
#[test]
fn schema_create_tables_repairs_legacy_proxy_config_singleton_to_per_app() {
let conn = Connection::open_in_memory().expect("open memory db");
@@ -461,7 +549,7 @@ fn schema_create_tables_repairs_legacy_proxy_config_singleton_to_per_app() {
let count: i32 = conn
.query_row("SELECT COUNT(*) FROM proxy_config", [], |r| r.get(0))
.expect("count rows");
assert_eq!(count, 3, "per-app proxy_config should have 3 rows");
assert_eq!(count, 4, "per-app proxy_config should have 4 rows");
// 新结构下应能按 app_type 查询
let _: i32 = conn
@@ -601,7 +689,7 @@ fn migration_from_v3_8_schema_v1_to_current_schema_v3() {
let proxy_rows: i64 = conn
.query_row("SELECT COUNT(*) FROM proxy_config", [], |r| r.get(0))
.expect("count proxy_config rows");
assert_eq!(proxy_rows, 3);
assert_eq!(proxy_rows, 4);
// model_pricing 应具备默认数据(迁移时会 seed)
let pricing_rows: i64 = conn
+40 -11
View File
@@ -101,17 +101,7 @@ pub fn import_mcp_from_deeplink(
// Server exists - merge apps only, keep other fields unchanged
log::info!("MCP server '{id}' already exists, merging apps only");
let mut merged_apps = existing.apps.clone();
// Merge new apps into existing apps
if target_apps.claude {
merged_apps.claude = true;
}
if target_apps.codex {
merged_apps.codex = true;
}
if target_apps.gemini {
merged_apps.gemini = true;
}
let merged_apps = merge_mcp_apps(&existing.apps, &target_apps);
McpServer {
id: existing.id.clone(),
@@ -166,6 +156,7 @@ pub(crate) fn parse_mcp_apps(apps_str: &str) -> Result<McpApps, AppError> {
claude: false,
codex: false,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
};
@@ -175,6 +166,7 @@ pub(crate) fn parse_mcp_apps(apps_str: &str) -> Result<McpApps, AppError> {
"claude" => apps.claude = true,
"codex" => apps.codex = true,
"gemini" => apps.gemini = true,
"grokbuild" | "grok" => apps.grokbuild = true,
"opencode" => apps.opencode = true,
"openclaw" => {
// OpenClaw doesn't support MCP, ignore silently
@@ -197,3 +189,40 @@ pub(crate) fn parse_mcp_apps(apps_str: &str) -> Result<McpApps, AppError> {
Ok(apps)
}
fn merge_mcp_apps(existing: &McpApps, target: &McpApps) -> McpApps {
let mut merged = existing.clone();
for app in target.enabled_apps() {
merged.set_enabled_for(&app, true);
}
merged
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn enabled_apps_merge_covers_every_supported_mcp_client() {
let existing = McpApps {
claude: true,
..McpApps::default()
};
let target = McpApps {
codex: true,
gemini: true,
grokbuild: true,
opencode: true,
hermes: true,
..McpApps::default()
};
let merged = merge_mcp_apps(&existing, &target);
assert!(merged.claude);
assert!(merged.codex);
assert!(merged.gemini);
assert!(merged.grokbuild);
assert!(merged.opencode);
assert!(merged.hermes);
}
}
+13 -6
View File
@@ -81,10 +81,10 @@ fn parse_provider_deeplink(
// Validate app type
if !matches!(
app.as_str(),
"claude" | "codex" | "gemini" | "opencode" | "openclaw" | "hermes"
"claude" | "codex" | "gemini" | "grokbuild" | "opencode" | "openclaw" | "hermes"
) {
return Err(AppError::InvalidInput(format!(
"Invalid app type: must be 'claude', 'codex', 'gemini', 'opencode', 'openclaw', or 'hermes', got '{app}'"
"Invalid app type: must be 'claude', 'codex', 'gemini', 'grokbuild', 'opencode', 'openclaw', or 'hermes', got '{app}'"
)));
}
@@ -190,10 +190,10 @@ fn parse_prompt_deeplink(
// Validate app type
if !matches!(
app.as_str(),
"claude" | "codex" | "gemini" | "opencode" | "openclaw" | "hermes"
"claude" | "codex" | "gemini" | "grokbuild" | "opencode" | "openclaw" | "hermes"
) {
return Err(AppError::InvalidInput(format!(
"Invalid app type: must be 'claude', 'codex', 'gemini', 'opencode', 'openclaw', or 'hermes', got '{app}'"
"Invalid app type: must be 'claude', 'codex', 'gemini', 'grokbuild', 'opencode', 'openclaw', or 'hermes', got '{app}'"
)));
}
@@ -262,10 +262,17 @@ fn parse_mcp_deeplink(
let trimmed = app.trim();
if !matches!(
trimmed,
"claude" | "codex" | "gemini" | "opencode" | "openclaw" | "hermes"
"claude"
| "codex"
| "gemini"
| "grokbuild"
| "grok"
| "opencode"
| "openclaw"
| "hermes"
) {
return Err(AppError::InvalidInput(format!(
"Invalid app in 'apps': must be 'claude', 'codex', 'gemini', 'opencode', 'openclaw', or 'hermes', got '{trimmed}'"
"Invalid app in 'apps': must be 'claude', 'codex', 'gemini', 'grokbuild', 'opencode', 'openclaw', or 'hermes', got '{trimmed}'"
)));
}
}
+88 -1
View File
@@ -122,7 +122,10 @@ pub fn import_provider_from_deeplink(
&provider_id,
normalized.clone(),
) {
log::warn!("Failed to add custom endpoint '{normalized}': {e}");
log::warn!(
"Failed to add custom endpoint '{}': {e}",
crate::url_for_log(&normalized)
);
}
}
}
@@ -145,6 +148,7 @@ pub(crate) fn build_provider_from_request(
AppType::Claude | AppType::ClaudeDesktop => build_claude_settings(request),
AppType::Codex => build_codex_settings(request),
AppType::Gemini => build_gemini_settings(request),
AppType::GrokBuild => build_grokbuild_settings(request),
AppType::OpenCode => build_opencode_settings(request),
AppType::OpenClaw => build_additive_app_settings(request),
AppType::Hermes => build_hermes_settings(request),
@@ -267,6 +271,8 @@ fn build_provider_meta(request: &DeepLinkImportRequest) -> Result<Option<Provide
coding_plan_provider: None,
access_key_id: None,
secret_access_key: None,
team_organization_id: None,
team_project_id: None,
};
Ok(Some(ProviderMeta {
@@ -442,6 +448,36 @@ fn build_gemini_settings(request: &DeepLinkImportRequest) -> serde_json::Value {
json!({ "env": env })
}
fn build_grokbuild_settings(request: &DeepLinkImportRequest) -> serde_json::Value {
let model = request
.model
.as_deref()
.filter(|value| !value.trim().is_empty())
.unwrap_or(crate::grok_config::DEFAULT_MODEL)
.trim();
let name = request
.name
.as_deref()
.filter(|value| !value.trim().is_empty())
.unwrap_or("custom")
.trim();
let endpoint = get_primary_endpoint(request).trim().to_string();
let api_key = request.api_key.as_deref().unwrap_or("").trim();
let model_value = toml_edit::Value::from(model).to_string();
let name_value = toml_edit::Value::from(name).to_string();
let endpoint_value = toml_edit::Value::from(endpoint.as_str()).to_string();
let api_key_value = toml_edit::Value::from(api_key).to_string();
json!({
"config": format!(
"[models]\ndefault = {model_value}\n\n[model.{model_value}]\nmodel = {model_value}\nbase_url = {endpoint_value}\nname = {name_value}\napi_key = {api_key_value}\napi_backend = \"{}\"\ncontext_window = {}\n",
crate::grok_config::DEFAULT_API_BACKEND,
crate::grok_config::DEFAULT_CONTEXT_WINDOW,
)
})
}
/// Build OpenCode settings configuration
fn build_opencode_settings(request: &DeepLinkImportRequest) -> serde_json::Value {
let endpoint = get_primary_endpoint(request);
@@ -598,6 +634,7 @@ pub fn parse_and_merge_config(
"claude" => merge_claude_config(&mut merged, &config_value)?,
"codex" => merge_codex_config(&mut merged, &config_value)?,
"gemini" => merge_gemini_config(&mut merged, &config_value)?,
"grokbuild" => merge_grokbuild_config(&mut merged, &config_value)?,
// Additive mode apps use JSON config directly; pass through as-is
"openclaw" | "opencode" | "hermes" => {
merge_additive_config(&mut merged, &config_value)?;
@@ -772,6 +809,56 @@ fn merge_gemini_config(
Ok(())
}
fn merge_grokbuild_config(
request: &mut DeepLinkImportRequest,
config: &serde_json::Value,
) -> Result<(), AppError> {
let config_toml = if let Some(config_toml) = config.get("config").and_then(|v| v.as_str()) {
config_toml.to_string()
} else {
let toml_value: toml::Value = serde_json::from_value(config.clone()).map_err(|error| {
AppError::InvalidInput(format!("Invalid Grok Build config: {error}"))
})?;
toml::to_string(&toml_value).map_err(|error| {
AppError::InvalidInput(format!("Invalid Grok Build config: {error}"))
})?
};
let model = crate::grok_config::extract_model_config(&config_toml).ok_or_else(|| {
AppError::InvalidInput("Invalid Grok Build config.toml model profile".to_string())
})?;
if request
.api_key
.as_ref()
.is_none_or(|value| value.is_empty())
{
request.api_key = model.api_key.or_else(|| {
crate::grok_config::extract_credentials(&config_toml).map(|(_, api_key)| api_key)
});
}
if request
.endpoint
.as_ref()
.is_none_or(|value| value.is_empty())
{
request.endpoint = Some(model.base_url);
}
if request.model.is_none() {
request.model = Some(model.model);
}
if request
.homepage
.as_ref()
.is_none_or(|value| value.is_empty())
{
if let Some(endpoint) = request.endpoint.as_deref() {
request.homepage = infer_homepage_from_endpoint(endpoint);
}
}
Ok(())
}
/// Merge configuration for additive mode apps (OpenClaw, OpenCode)
///
/// These apps use JSON config directly, so we only extract common fields
+95
View File
@@ -87,6 +87,39 @@ fn test_parse_deeplink_with_notes() {
assert_eq!(request.notes, Some("Test notes".to_string()));
}
#[test]
fn test_parse_grokbuild_provider() {
use super::provider::build_provider_from_request;
let url = "ccswitch://v1/import?resource=provider&app=grokbuild&name=Grok%20Relay&endpoint=https%3A%2F%2Fapi.example.com%2Fv1&apiKey=secret&model=grok-4.5";
let request = parse_deeplink_url(url).unwrap();
assert_eq!(request.app.as_deref(), Some("grokbuild"));
assert_eq!(request.name.as_deref(), Some("Grok Relay"));
assert_eq!(
request.endpoint.as_deref(),
Some("https://api.example.com/v1")
);
assert_eq!(request.api_key.as_deref(), Some("secret"));
assert_eq!(request.model.as_deref(), Some("grok-4.5"));
let provider = build_provider_from_request(&AppType::GrokBuild, &request).unwrap();
let config = provider.settings_config["config"].as_str().unwrap();
let document = config.parse::<toml::Value>().unwrap();
let model = &document["model"]["grok-4.5"];
assert_eq!(document["models"]["default"].as_str(), Some("grok-4.5"));
assert_eq!(
model["base_url"].as_str(),
Some("https://api.example.com/v1")
);
assert_eq!(model["name"].as_str(), Some("Grok Relay"));
assert_eq!(model["api_key"].as_str(), Some("secret"));
assert_eq!(model["api_backend"].as_str(), Some("responses"));
assert_eq!(model["context_window"].as_integer(), Some(500_000));
}
#[test]
fn test_parse_invalid_scheme() {
let url = "https://v1/import?resource=provider&app=claude&name=Test";
@@ -500,6 +533,38 @@ experimental_bearer_token = "sk-rightcode"
assert_eq!(merged.model, Some("gpt-5-codex".to_string()));
}
#[test]
fn test_parse_and_merge_config_grokbuild() {
let config_toml = r#"[models]
default = "grok-profile"
[model."grok-profile"]
model = "grok-upstream"
base_url = "https://grok.example/v1"
name = "Grok Relay"
api_key = "sk-grok"
api_backend = "responses"
context_window = 500000
"#;
let config_json = serde_json::json!({ "config": config_toml }).to_string();
let request = DeepLinkImportRequest {
version: "v1".to_string(),
resource: "provider".to_string(),
app: Some("grokbuild".to_string()),
name: Some("Grok Relay".to_string()),
config: Some(BASE64_STANDARD.encode(config_json.as_bytes())),
config_format: Some("json".to_string()),
..Default::default()
};
let merged = parse_and_merge_config(&request).expect("merge Grok Build config");
assert_eq!(merged.api_key.as_deref(), Some("sk-grok"));
assert_eq!(merged.endpoint.as_deref(), Some("https://grok.example/v1"));
assert_eq!(merged.model.as_deref(), Some("grok-upstream"));
assert_eq!(merged.homepage.as_deref(), Some("https://grok.example"));
}
#[test]
fn test_parse_and_merge_config_url_override() {
let config_json = r#"{"env":{"ANTHROPIC_AUTH_TOKEN":"sk-old","ANTHROPIC_BASE_URL":"https://api.anthropic.com/v1"}}"#;
@@ -709,6 +774,11 @@ fn test_parse_mcp_apps() {
assert!(!apps.codex);
assert!(apps.gemini);
let apps = parse_mcp_apps("grokbuild,opencode,hermes").unwrap();
assert!(apps.grokbuild);
assert!(apps.opencode);
assert!(apps.hermes);
let err = parse_mcp_apps("invalid").unwrap_err();
assert!(err.to_string().contains("Invalid app"));
}
@@ -731,6 +801,18 @@ fn test_parse_prompt_deeplink() {
assert!(request.enabled.unwrap());
}
#[test]
fn test_parse_grokbuild_prompt_deeplink() {
let content_b64 = BASE64_STANDARD.encode("Grok instructions");
let url = format!(
"ccswitch://v1/import?resource=prompt&app=grokbuild&name=test&content={content_b64}"
);
let request = parse_deeplink_url(&url).expect("parse Grok Build prompt deeplink");
assert_eq!(request.app.as_deref(), Some("grokbuild"));
}
#[test]
fn test_parse_mcp_deeplink() {
let config = r#"{"mcpServers":{"test":{"command":"echo"}}}"#;
@@ -747,6 +829,19 @@ fn test_parse_mcp_deeplink() {
assert!(request.enabled.unwrap());
}
#[test]
fn test_parse_grokbuild_mcp_deeplink() {
let config = r#"{"mcpServers":{"test":{"command":"echo"}}}"#;
let config_b64 = BASE64_STANDARD.encode(config);
let url = format!(
"ccswitch://v1/import?resource=mcp&apps=grokbuild&config={config_b64}&enabled=true"
);
let request = parse_deeplink_url(&url).expect("parse Grok Build MCP deeplink");
assert_eq!(request.apps.as_deref(), Some("grokbuild"));
}
#[test]
fn test_parse_skill_deeplink() {
let url = "ccswitch://v1/import?resource=skill&repo=owner/repo&directory=skills&branch=dev";
+640
View File
@@ -0,0 +1,640 @@
use serde_json::{json, Value};
use std::fs;
use std::path::PathBuf;
use crate::config::{get_home_dir, write_text_file};
use crate::error::AppError;
use crate::provider::Provider;
pub const DEFAULT_MODEL: &str = "grok-4.5";
pub const DEFAULT_API_BACKEND: &str = "responses";
pub const DEFAULT_CONTEXT_WINDOW: i64 = 500_000;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GrokModelConfig {
pub profile: String,
pub model: String,
pub base_url: String,
pub name: String,
pub api_key: Option<String>,
pub env_key: Option<String>,
pub api_backend: String,
pub context_window: i64,
}
/// Grok Build configuration directory (`~/.grok`).
pub fn get_grok_config_dir() -> PathBuf {
crate::settings::get_grok_override_dir().unwrap_or_else(|| get_home_dir().join(".grok"))
}
/// Grok Build live configuration path (`~/.grok/config.toml`).
pub fn get_grok_config_path() -> PathBuf {
get_grok_config_dir().join("config.toml")
}
fn required_non_empty_string<'a>(
table: &'a toml::value::Table,
key: &str,
) -> Result<&'a str, AppError> {
table
.get(key)
.and_then(toml::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| {
AppError::localized(
"provider.grokbuild.field.missing",
format!("Grok Build 配置缺少有效的 {key} 字段"),
format!("Grok Build configuration is missing a valid {key} field"),
)
})
}
fn optional_non_empty_string(table: &toml::value::Table, key: &str) -> Option<String> {
table
.get(key)
.and_then(toml::Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToString::to_string)
}
/// Syntax-only validation for a Grok Build config document (empty allowed).
///
/// 官方条目走 Grok CLI 自带的 xAI OAuth 登录,config.toml 不需要(通常也没有)
/// 自定义模型表:空文档合法,非空只要求 TOML 语法合法。live 层的读写与官方
/// 快照校验都用它;"必须有完整自定义模型表"的强校验见 `validate_config_toml`。
pub fn validate_config_toml_syntax(config_toml: &str) -> Result<(), AppError> {
if config_toml.trim().is_empty() {
return Ok(());
}
config_toml
.parse::<toml::Value>()
.map(|_| ())
.map_err(|error| {
AppError::localized(
"provider.grokbuild.config.invalid_toml",
format!("Grok Build config.toml 格式错误: {error}"),
format!("Invalid Grok Build config.toml: {error}"),
)
})
}
/// Whether a live config document represents the official login state.
///
/// 官方态 = 语法合法且完全没有自定义模型痕迹(无 `[models]` 也无 `[model.*]`
/// 允许 `[mcp_servers]` 等其它内容)。只要出现过任一自定义键就返回 false,
/// 让残缺的自定义配置继续走 `validate_config_toml` 报出真实错误,
/// 而不是被误判成官方态静默吞掉。语法不合法同样返回 false。
pub fn is_official_live_config(config_toml: &str) -> bool {
let Ok(document) = config_toml.parse::<toml::Value>() else {
return false;
};
document
.as_table()
.is_some_and(|root| !root.contains_key("models") && !root.contains_key("model"))
}
/// Validate the provider-owned Grok Build TOML document.
pub fn validate_config_toml(config_toml: &str) -> Result<(), AppError> {
let document = config_toml.parse::<toml::Value>().map_err(|error| {
AppError::localized(
"provider.grokbuild.config.invalid_toml",
format!("Grok Build config.toml 格式错误: {error}"),
format!("Invalid Grok Build config.toml: {error}"),
)
})?;
let root = document.as_table().ok_or_else(|| {
AppError::localized(
"provider.grokbuild.config.not_table",
"Grok Build 配置必须是 TOML 表结构",
"Grok Build configuration must be a TOML table",
)
})?;
let models = root
.get("models")
.and_then(toml::Value::as_table)
.ok_or_else(|| {
AppError::localized(
"provider.grokbuild.models.missing",
"Grok Build 配置缺少 [models]",
"Grok Build configuration is missing [models]",
)
})?;
let default_model = required_non_empty_string(models, "default")?;
let model_entries = root
.get("model")
.and_then(toml::Value::as_table)
.ok_or_else(|| {
AppError::localized(
"provider.grokbuild.model.missing",
"Grok Build 配置缺少 [model.<name>]",
"Grok Build configuration is missing [model.<name>]",
)
})?;
let selected_model = model_entries
.get(default_model)
.and_then(toml::Value::as_table)
.ok_or_else(|| {
AppError::localized(
"provider.grokbuild.default_model.missing",
format!("Grok Build 配置缺少 [model.\"{default_model}\"]"),
format!("Grok Build configuration is missing [model.\"{default_model}\"]"),
)
})?;
required_non_empty_string(selected_model, "model")?;
required_non_empty_string(selected_model, "base_url")?;
required_non_empty_string(selected_model, "name")?;
if optional_non_empty_string(selected_model, "api_key").is_none()
&& optional_non_empty_string(selected_model, "env_key").is_none()
{
return Err(AppError::localized(
"provider.grokbuild.credentials.missing",
"Grok Build 配置缺少有效的 api_key 或 env_key 字段",
"Grok Build configuration is missing a valid api_key or env_key field",
));
}
required_non_empty_string(selected_model, "api_backend")?;
selected_model
.get("context_window")
.and_then(toml::Value::as_integer)
.filter(|value| *value > 0)
.ok_or_else(|| {
AppError::localized(
"provider.grokbuild.context_window.invalid",
"Grok Build context_window 必须是正整数",
"Grok Build context_window must be a positive integer",
)
})?;
Ok(())
}
pub fn extract_model_config(config_toml: &str) -> Option<GrokModelConfig> {
let document = config_toml.parse::<toml::Value>().ok()?;
let root = document.as_table()?;
let default_model = root
.get("models")?
.as_table()?
.get("default")?
.as_str()?
.trim();
let selected_model = root
.get("model")?
.as_table()?
.get(default_model)?
.as_table()?;
Some(GrokModelConfig {
profile: default_model.to_string(),
model: selected_model.get("model")?.as_str()?.trim().to_string(),
base_url: selected_model
.get("base_url")?
.as_str()?
.trim_end_matches('/')
.to_string(),
name: selected_model.get("name")?.as_str()?.trim().to_string(),
api_key: optional_non_empty_string(selected_model, "api_key"),
env_key: optional_non_empty_string(selected_model, "env_key"),
api_backend: selected_model
.get("api_backend")?
.as_str()?
.trim()
.to_string(),
context_window: selected_model.get("context_window")?.as_integer()?,
})
}
pub fn extract_credentials(config_toml: &str) -> Option<(String, String)> {
let config = extract_model_config(config_toml)?;
let api_key = config
.api_key
.or_else(|| {
config
.env_key
.as_deref()
.and_then(|key| std::env::var(key).ok())
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
})
.or_else(|| {
std::env::var("XAI_API_KEY")
.ok()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
})?;
Some((config.base_url, api_key))
}
pub fn extract_inline_api_key(config_toml: &str) -> Option<String> {
extract_model_config(config_toml)?.api_key
}
pub fn extract_base_url(config_toml: &str) -> Option<String> {
Some(extract_model_config(config_toml)?.base_url)
}
fn update_selected_model_string(
config_toml: &str,
field: &str,
value: &str,
) -> Result<String, AppError> {
let mut document = config_toml
.parse::<toml_edit::DocumentMut>()
.map_err(|error| {
AppError::localized(
"provider.grokbuild.config.invalid_toml",
format!("Grok Build config.toml 格式错误: {error}"),
format!("Invalid Grok Build config.toml: {error}"),
)
})?;
let default_model = document
.get("models")
.and_then(|item| item.get("default"))
.and_then(toml_edit::Item::as_str)
.map(str::trim)
.filter(|model| !model.is_empty())
.ok_or_else(|| {
AppError::localized(
"provider.grokbuild.default_model.missing",
"Grok Build 配置缺少 models.default",
"Grok Build configuration is missing models.default",
)
})?
.to_string();
let selected_model = document
.get_mut("model")
.and_then(|item| item.get_mut(&default_model))
.and_then(toml_edit::Item::as_table_like_mut)
.ok_or_else(|| {
AppError::localized(
"provider.grokbuild.default_model.missing",
format!("Grok Build 配置缺少 [model.\"{default_model}\"]"),
format!("Grok Build configuration is missing [model.\"{default_model}\"]"),
)
})?;
selected_model.insert(field, toml_edit::value(value));
Ok(document.to_string())
}
pub fn apply_proxy_takeover(
config_toml: &str,
proxy_base_url: &str,
token_placeholder: &str,
) -> Result<String, AppError> {
let updated = update_selected_model_string(config_toml, "base_url", proxy_base_url)?;
update_selected_model_string(&updated, "api_key", token_placeholder)
}
pub fn update_api_key(config_toml: &str, api_key: &str) -> Result<String, AppError> {
update_selected_model_string(config_toml, "api_key", api_key)
}
pub fn has_proxy_placeholder(config_toml: &str, token_placeholder: &str) -> bool {
extract_model_config(config_toml)
.and_then(|config| config.api_key)
.is_some_and(|api_key| api_key == token_placeholder)
}
pub fn base_url_matches(config_toml: &str, predicate: impl FnOnce(&str) -> bool) -> bool {
extract_model_config(config_toml).is_some_and(|config| predicate(&config.base_url))
}
/// Remove MCP projections from a provider-owned Grok Build settings snapshot.
/// MCP servers are owned by the database and projected into live config.toml.
pub fn strip_grok_mcp_servers_from_settings(settings: &mut Value) -> Result<(), AppError> {
let Some(config_text) = settings
.get("config")
.and_then(Value::as_str)
.map(str::to_string)
else {
return Ok(());
};
if !config_text.contains("mcp") {
return Ok(());
}
let mut document = config_text
.parse::<toml_edit::DocumentMut>()
.map_err(|error| AppError::Message(format!("Invalid Grok Build config.toml: {error}")))?;
let mut changed = document.as_table_mut().remove("mcp_servers").is_some();
if let Some(mcp_table) = document
.get_mut("mcp")
.and_then(toml_edit::Item::as_table_like_mut)
{
if mcp_table.remove("servers").is_some() {
changed = true;
}
if mcp_table.is_empty() {
document.as_table_mut().remove("mcp");
}
}
if changed {
if let Some(object) = settings.as_object_mut() {
object.insert("config".to_string(), Value::String(document.to_string()));
}
}
Ok(())
}
/// Read the live `~/.grok/config.toml` as a provider settings snapshot.
///
/// 只做 TOML 语法校验:live 处于官方态(无自定义模型表)时同样需要能被
/// 读取,供切换回填与界面展示使用。需要"完整自定义模型配置"的导入路径
/// 由调用方自行叠加 `validate_config_toml`。
pub fn read_grok_live_settings() -> Result<Value, AppError> {
let path = get_grok_config_path();
if !path.exists() {
return Err(AppError::localized(
"grokbuild.config.missing",
"Grok Build 配置文件不存在",
"Grok Build configuration file not found",
));
}
let config = fs::read_to_string(&path).map_err(|error| AppError::io(&path, error))?;
validate_config_toml_syntax(&config)?;
Ok(json!({ "config": config }))
}
pub fn write_grok_provider_live(provider: &Provider) -> Result<(), AppError> {
let settings = provider.settings_config.as_object().ok_or_else(|| {
AppError::localized(
"provider.grokbuild.settings.not_object",
"Grok Build 配置必须是 JSON 对象",
"Grok Build configuration must be a JSON object",
)
})?;
let config = settings
.get("config")
.and_then(Value::as_str)
.ok_or_else(|| {
AppError::localized(
"provider.grokbuild.config.missing",
"Grok Build 配置缺少 config 字段",
"Grok Build configuration is missing the config field",
)
})?;
// 官方条目不注入自定义模型表:按快照原样写回(首次为空文件),
// Grok CLI 回落到官方内置模型 + 自带 OAuth 登录;MCP 投影随后由
// 切换流程重新补写。非官方供应商必须携带完整的自定义模型配置。
if provider.category.as_deref() != Some("official") {
validate_config_toml(config)?;
}
write_grok_live_settings(&json!({ "config": config }))
}
/// Raw live-file writer, mirroring `read_grok_live_settings` (syntax-only).
///
/// 代理接管的备份/恢复也走这里:官方态 live(无自定义模型表)必须可以
/// 原样写回。完整形状校验由 `write_grok_provider_live` 的非官方分支负责。
pub fn write_grok_live_settings(settings: &Value) -> Result<(), AppError> {
let config = settings
.get("config")
.and_then(Value::as_str)
.ok_or_else(|| {
AppError::localized(
"provider.grokbuild.config.missing",
"Grok Build 配置缺少 config 字段",
"Grok Build configuration is missing the config field",
)
})?;
validate_config_toml_syntax(config)?;
write_text_file(&get_grok_config_path(), config)
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
use tempfile::TempDir;
fn valid_config() -> &'static str {
r#"[models]
default = "grok-4.5"
[model."grok-4.5"]
model = "grok-4.5"
base_url = "https://example.com/v1"
name = "Example"
api_key = "secret"
api_backend = "responses"
context_window = 500000
"#
}
fn valid_env_key_config() -> &'static str {
r#"[models]
default = "grok-env"
[model."grok-env"]
model = "grok-4.5"
base_url = "https://example.com/v1"
name = "Example Env"
env_key = "GROK_TEST_API_KEY"
api_backend = "responses"
context_window = 500000
"#
}
#[test]
fn validates_expected_config_shape() {
validate_config_toml(valid_config()).expect("valid Grok Build config");
validate_config_toml(valid_env_key_config()).expect("valid env_key configuration");
}
#[test]
fn syntax_validation_accepts_official_snapshots() {
validate_config_toml_syntax("").expect("empty official snapshot");
validate_config_toml_syntax("[mcp_servers.echo]\ncommand = \"echo\"\n")
.expect("official-mode config without model tables");
assert!(validate_config_toml_syntax("not = [valid").is_err());
}
#[test]
fn official_live_config_detection() {
// 官方态:完全没有自定义模型痕迹
assert!(is_official_live_config(""));
assert!(is_official_live_config(" \n# comment only\n"));
assert!(is_official_live_config(
"[mcp_servers.echo]\ncommand = \"echo\"\n"
));
// 出现过任一自定义键(哪怕残缺)都不是官方态,交给强校验报错
assert!(!is_official_live_config(valid_config()));
assert!(!is_official_live_config("[models]\ndefault = \"x\"\n"));
assert!(!is_official_live_config("[model.x]\nmodel = \"x\"\n"));
// 语法不合法不是官方态
assert!(!is_official_live_config("not = [valid"));
}
#[test]
fn rejects_missing_selected_model_table() {
let error = validate_config_toml("[models]\ndefault = \"grok-4.5\"\n")
.expect_err("missing model table should fail");
assert!(error.to_string().contains("model"));
}
#[test]
fn rejects_config_without_api_key_or_env_key() {
let config = valid_config().replace("api_key = \"secret\"\n", "");
let error = validate_config_toml(&config).expect_err("credentials should be required");
assert!(error.to_string().contains("api_key"));
assert!(error.to_string().contains("env_key"));
}
#[test]
fn extracts_selected_model_and_updates_takeover_fields() {
let selected = extract_model_config(valid_config()).expect("selected model");
assert_eq!(selected.profile, "grok-4.5");
assert_eq!(selected.model, "grok-4.5");
assert_eq!(selected.base_url, "https://example.com/v1");
let updated = apply_proxy_takeover(
valid_config(),
"http://127.0.0.1:15721/grokbuild/v1",
"PROXY_MANAGED",
)
.expect("takeover config");
let selected = extract_model_config(&updated).expect("updated selected model");
assert_eq!(selected.base_url, "http://127.0.0.1:15721/grokbuild/v1");
assert_eq!(selected.api_key.as_deref(), Some("PROXY_MANAGED"));
assert!(has_proxy_placeholder(&updated, "PROXY_MANAGED"));
}
#[test]
fn takeover_preserves_env_key_profile_and_injects_inline_placeholder() {
let updated = apply_proxy_takeover(
valid_env_key_config(),
"http://127.0.0.1:15721/grokbuild/v1",
"PROXY_MANAGED",
)
.expect("takeover config");
let selected = extract_model_config(&updated).expect("updated selected model");
assert_eq!(selected.profile, "grok-env");
assert_eq!(selected.env_key.as_deref(), Some("GROK_TEST_API_KEY"));
assert_eq!(selected.api_key.as_deref(), Some("PROXY_MANAGED"));
}
#[test]
#[serial]
fn resolves_api_key_from_configured_environment_variable() {
let original = std::env::var_os("GROK_TEST_API_KEY");
std::env::set_var("GROK_TEST_API_KEY", "env-secret");
let credentials = extract_credentials(valid_env_key_config()).expect("credentials");
assert_eq!(credentials.0, "https://example.com/v1");
assert_eq!(credentials.1, "env-secret");
match original {
Some(value) => std::env::set_var("GROK_TEST_API_KEY", value),
None => std::env::remove_var("GROK_TEST_API_KEY"),
}
}
#[test]
fn strips_projected_mcp_servers_without_touching_model_config() {
let mut settings = json!({
"config": format!(
"{}\n[mcp_servers.echo]\ncommand = \"echo\"\n",
valid_config()
)
});
strip_grok_mcp_servers_from_settings(&mut settings).expect("strip MCP servers");
let config = settings.get("config").and_then(Value::as_str).unwrap();
assert!(!config.contains("mcp_servers"));
assert!(config.contains("model = \"grok-4.5\""));
validate_config_toml(config).expect("stripped config remains valid");
}
#[test]
#[serial]
fn official_provider_roundtrips_without_custom_model_tables() {
let temp = TempDir::new().expect("temp dir");
let original_test_home = std::env::var_os("CC_SWITCH_TEST_HOME");
std::env::set_var("CC_SWITCH_TEST_HOME", temp.path());
// 官方条目:空 config 可写(清掉自定义模型表,交还 Grok CLI 官方登录)
let mut official = Provider::with_id(
"grokbuild-official".to_string(),
"Grok Official".to_string(),
json!({ "config": "" }),
None,
);
official.category = Some("official".to_string());
write_grok_provider_live(&official).expect("official empty config is writable");
assert_eq!(
fs::read_to_string(get_grok_config_path()).expect("read config"),
""
);
// 官方态 live(如 MCP 投影补写后)无自定义模型表,读取与原样写回都必须可用
let official_live = "[mcp_servers.echo]\ncommand = \"echo\"\n";
write_grok_live_settings(&json!({ "config": official_live }))
.expect("official-mode live is writable for backup restore");
let settings = read_grok_live_settings().expect("official-mode live is readable");
assert_eq!(
settings.get("config").and_then(Value::as_str),
Some(official_live)
);
// 非官方供应商仍要求完整的自定义模型配置
let custom = Provider::with_id(
"custom".to_string(),
"Custom".to_string(),
json!({ "config": "" }),
None,
);
assert!(write_grok_provider_live(&custom).is_err());
match original_test_home {
Some(value) => std::env::set_var("CC_SWITCH_TEST_HOME", value),
None => std::env::remove_var("CC_SWITCH_TEST_HOME"),
}
}
#[test]
#[serial]
fn writes_and_reads_live_config() {
let temp = TempDir::new().expect("temp dir");
let original_test_home = std::env::var_os("CC_SWITCH_TEST_HOME");
std::env::set_var("CC_SWITCH_TEST_HOME", temp.path());
let provider = Provider::with_id(
"grok".to_string(),
"Example".to_string(),
json!({ "config": valid_config() }),
None,
);
write_grok_provider_live(&provider).expect("write live config");
let path = get_grok_config_path();
assert_eq!(path, temp.path().join(".grok").join("config.toml"));
assert_eq!(
fs::read_to_string(path).expect("read config"),
valid_config()
);
assert_eq!(
read_grok_live_settings()
.expect("read live settings")
.get("config")
.and_then(Value::as_str),
Some(valid_config())
);
match original_test_home {
Some(value) => std::env::set_var("CC_SWITCH_TEST_HOME", value),
None => std::env::remove_var("CC_SWITCH_TEST_HOME"),
}
}
}
+385 -135
View File
@@ -6,6 +6,7 @@ mod claude_mcp;
mod claude_plugin;
mod codex_config;
mod codex_history_migration;
mod codex_state_db;
mod commands;
mod config;
mod database;
@@ -13,12 +14,14 @@ mod deeplink;
mod error;
mod gemini_config;
mod gemini_mcp;
mod grok_config;
pub mod hermes_config;
mod init_status;
mod lightweight;
#[cfg(target_os = "linux")]
mod linux_fix;
mod mcp;
mod model_capabilities;
mod openclaw_config;
mod opencode_config;
mod panic_hook;
@@ -41,17 +44,22 @@ pub use codex_config::{get_codex_auth_path, get_codex_config_path, write_codex_l
pub use commands::open_provider_terminal;
pub use commands::*;
pub use config::{get_claude_mcp_path, get_claude_settings_path, read_json_file};
pub use database::Database;
pub use database::{Database, Profile};
pub use deeplink::{import_provider_from_deeplink, parse_deeplink_url, DeepLinkImportRequest};
pub use error::AppError;
pub use grok_config::get_grok_config_path;
pub use mcp::{
import_from_claude, import_from_codex, import_from_gemini, remove_server_from_claude,
remove_server_from_codex, remove_server_from_gemini, sync_enabled_to_claude,
sync_enabled_to_codex, sync_enabled_to_gemini, sync_single_server_to_claude,
sync_single_server_to_codex, sync_single_server_to_gemini,
import_from_claude, import_from_codex, import_from_gemini, import_from_grokbuild,
remove_server_from_claude, remove_server_from_codex, remove_server_from_gemini,
remove_server_from_grokbuild, sync_enabled_to_claude, sync_enabled_to_codex,
sync_enabled_to_gemini, sync_single_server_to_claude, sync_single_server_to_codex,
sync_single_server_to_gemini, sync_single_server_to_grokbuild,
};
pub use prompt::Prompt;
pub use provider::{Provider, ProviderMeta};
pub use services::{
profile::{ProfilePayload, ProfileScope, ProfileService},
provider::reapply_current_codex_official_live,
skill::{migrate_skills_to_ssot, ImportSkillSelection},
ConfigService, EndpointLatency, McpService, PromptService, ProviderService, ProxyService,
SkillService, SpeedtestService,
@@ -61,7 +69,7 @@ pub use store::AppState;
use tauri_plugin_deep_link::DeepLinkExt;
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogKind};
use std::sync::Arc;
use std::{fmt, sync::Arc};
#[cfg(target_os = "macos")]
use tauri::image::Image;
use tauri::tray::{TrayIconBuilder, TrayIconEvent};
@@ -85,35 +93,129 @@ fn set_windows_app_user_model_id(app: &tauri::AppHandle) {
}
}
fn redact_url_for_log(url_str: &str) -> String {
match url::Url::parse(url_str) {
Ok(url) => {
let mut output = format!("{}://", url.scheme());
if let Some(host) = url.host_str() {
output.push_str(host);
}
output.push_str(url.path());
pub(crate) struct RedactedUrl<'a> {
url: &'a str,
known_secrets: &'a [String],
}
let mut keys: Vec<String> = url.query_pairs().map(|(k, _)| k.to_string()).collect();
keys.sort();
keys.dedup();
impl fmt::Display for RedactedUrl<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&redact_url_for_log_with_secrets(
self.url,
self.known_secrets,
))
}
}
if !keys.is_empty() {
output.push_str("?[keys:");
output.push_str(&keys.join(","));
output.push(']');
}
/// 为日志提供惰性 URL 脱敏包装;只有日志实际输出时才解析和重建 URL。
pub(crate) fn url_for_log(url: &str) -> RedactedUrl<'_> {
RedactedUrl {
url,
known_secrets: &[],
}
}
output
}
Err(_) => {
let base = url_str.split('#').next().unwrap_or(url_str);
match base.split_once('?') {
Some((prefix, _)) => format!("{prefix}?[redacted]"),
None => base.to_string(),
}
/// 为持有确切认证材料的调用方提供优先精确匹配、再启发式兜底的 URL 脱敏。
pub(crate) fn url_for_log_with_secrets<'a>(
url: &'a str,
known_secrets: &'a [String],
) -> RedactedUrl<'a> {
RedactedUrl { url, known_secrets }
}
/// 已知密钥参与子串脱敏的最短长度:过短的值(如 "api")当作子串会误伤无关文本,
/// 所以只对足够长、几乎不可能是普通词的值做替换。
const MIN_KNOWN_SECRET_LEN: usize = 8;
/// 唯一的密钥脱敏原语:把字符串里出现的、我们确切握有的密钥值替换为 [REDACTED]。
/// 不做任何“看起来像密钥”的形状猜测——只隐藏已知值,天然收敛、不误伤正常路径。
fn redact_known_secrets(text: &str, known_secrets: &[String]) -> String {
let mut output = text.to_string();
for secret in known_secrets {
if secret.chars().count() >= MIN_KNOWN_SECRET_LEN {
output = output.replace(secret.as_str(), "[REDACTED]");
}
}
output
}
/// 无 scheme 的裸 authority 形态(如 `user:pass@host/path`)剥掉 userinfo
/// 仅当 `@` 出现在第一个 `/` 之前时才视为凭据。
fn strip_bare_userinfo(input: &str) -> &str {
let authority_end = input.find('/').unwrap_or(input.len());
match input[..authority_end].rfind('@') {
Some(at) => &input[at + 1..],
None => input,
}
}
pub(crate) fn redact_url_for_log(url_str: &str) -> String {
redact_url_for_log_with_secrets(url_str, &[])
}
/// 为日志脱敏 URL:剥掉 userinfo(user:pass@) 与整个 query/fragment,保留
/// scheme/host/port/path 供诊断(如 base_url 配错路径导致 404),最后再抹掉已知密钥值。
pub(crate) fn redact_url_for_log_with_secrets(url_str: &str, known_secrets: &[String]) -> String {
let scheme_relative = url_str.starts_with("//");
let parsed = if scheme_relative {
url::Url::parse(&format!("https:{url_str}"))
} else {
url::Url::parse(url_str)
};
let sanitized = match parsed {
Ok(mut url) if url.has_host() => {
let _ = url.set_username("");
let _ = url.set_password(None);
url.set_query(None);
url.set_fragment(None);
let rendered = url.as_str();
if scheme_relative {
rendered
.strip_prefix("https:")
.unwrap_or(rendered)
.to_string()
} else {
rendered.to_string()
}
}
_ => {
// 解析失败(相对路径、含裸 userinfo 的非法 URL 等):丢掉 query/fragment
// 尽力剥掉 userinfo,其余原样保留。
let without_tail = url_str.split(['?', '#']).next().unwrap_or(url_str);
strip_bare_userinfo(without_tail).to_string()
}
};
redact_known_secrets(&sanitized, known_secrets)
}
/// 只保留 `scheme://host:port`,丢掉 path/query/userinfo。用于我们手里没有任何
/// 已知密钥可脱敏 path 的场景——凭据可能整个内嵌在 base_url 的 path 里,此时
/// 记录 path 无法保证不泄漏,只能退回到 origin。
pub(crate) fn redact_url_origin_for_log(url_str: &str) -> String {
let scheme_relative = url_str.starts_with("//");
let parsed = if scheme_relative {
url::Url::parse(&format!("https:{url_str}"))
} else {
url::Url::parse(url_str)
};
match parsed {
Ok(url) if url.has_host() => {
let authority = &url[url::Position::BeforeHost..url::Position::AfterPort];
if scheme_relative {
format!("//{authority}")
} else {
format!("{}://{authority}", url.scheme())
}
}
_ => "[invalid target]".to_string(),
}
}
fn runtime_log_level_allows(level: log::Level, max_level: log::LevelFilter) -> bool {
max_level.to_level().is_some_and(|maximum| level <= maximum)
}
/// 统一处理 ccswitch:// 深链接 URL
@@ -131,9 +233,10 @@ fn handle_deeplink_url(
return false;
}
let redacted_url = redact_url_for_log(url_str);
log::info!("✓ Deep link URL detected from {source}: {redacted_url}");
log::debug!("Deep link URL (raw) from {source}: {url_str}");
log::info!(
"✓ Deep link URL detected from {source}: {}",
url_for_log(url_str)
);
match crate::deeplink::parse_deeplink_url(url_str) {
Ok(request) => {
@@ -229,7 +332,7 @@ pub fn run() {
log::info!("=== Single Instance Callback Triggered ===");
log::debug!("Args count: {}", args.len());
for (i, arg) in args.iter().enumerate() {
log::debug!(" arg[{i}]: {}", redact_url_for_log(arg));
log::debug!(" arg[{i}]: {}", url_for_log(arg));
}
if crate::lightweight::is_lightweight_mode() {
@@ -314,21 +417,8 @@ pub fn run() {
// 预先刷新 Store 覆盖配置,确保后续路径读取正确(日志/数据库等)
app_store::refresh_app_config_dir_override(app.handle());
panic_hook::init_app_config_dir(crate::config::get_app_config_dir());
#[cfg(target_os = "windows")]
set_windows_app_user_model_id(app.handle());
// 注册 Updater 插件(桌面端
#[cfg(desktop)]
{
if let Err(e) = app
.handle()
.plugin(tauri_plugin_updater::Builder::new().build())
{
// 若配置不完整(如缺少 pubkey),跳过 Updater 而不中断应用
log::warn!("初始化 Updater 插件失败,已跳过:{e}");
}
}
// 初始化日志(单文件输出到 <app_config_dir>/logs/cc-switch.log
// 初始化日志(输出到 <app_config_dir>/logs/cc-switch.log
{
use tauri_plugin_log::{RotationStrategy, Target, TargetKind, TimezoneStrategy};
@@ -339,14 +429,16 @@ pub fn run() {
eprintln!("创建日志目录失败: {e}");
}
// 启动时删除旧日志文件,实现单文件覆盖效果
let log_file_path = log_dir.join("cc-switch.log");
let _ = std::fs::remove_file(&log_file_path);
app.handle().plugin(
tauri_plugin_log::Builder::default()
// 初始化为 Trace,允许后续通过 log::set_max_level() 动态调级别
// 底层保留 Trace 能力,便于加载用户配置后动态调级别
// 插件注册后会立即把全局级别收紧到 Info,避免启动阶段全量 Trace。
.level(log::LevelFilter::Trace)
// plugin-log 的前端 command 会直达 logger,绕过 log 宏的全局
// max_level;在分发层补一次过滤,确保动态总开关同样约束前端日志。
.filter(|metadata| {
runtime_log_level_allows(metadata.level(), log::max_level())
})
.targets([
Target::new(TargetKind::Stdout),
Target::new(TargetKind::Folder {
@@ -354,15 +446,36 @@ pub fn run() {
file_name: Some("cc-switch".into()),
}),
])
// 单文件模式:启动时删除旧文件,达到大小时轮转
// 注意:KeepSome(n) 内部会做 n-2 运算,n=1 会导致 usize 下溢
// KeepSome(2) 是最小安全值,表示不保留轮转文件
.rotation_strategy(RotationStrategy::KeepSome(2))
// 单文件大小限制 1GB
.max_file_size(1024 * 1024 * 1024)
// KeepSome(4) 保留 4 个轮转归档,加上当前文件最多约 100 MiB。
// 轮转仅按大小触发;跨重启继续追加,不再丢失上一次运行的日志。
.rotation_strategy(RotationStrategy::KeepSome(4))
.max_file_size(20 * 1024 * 1024)
.timezone_strategy(TimezoneStrategy::UseLocal)
.build(),
)?;
// 用户配置存在数据库中,数据库尚未打开时使用保守的 Info 级别。
log::set_max_level(log::LevelFilter::Info);
log::info!("=== CC Switch v{} started ===", env!("CARGO_PKG_VERSION"));
}
// 首次读取覆盖路径时 logger 尚未可用;此处重放一次,
// 让 Store 损坏或路径无效等启动警告能够真正落盘。
let _ = app_store::refresh_app_config_dir_override(app.handle());
#[cfg(target_os = "windows")]
set_windows_app_user_model_id(app.handle());
// 注册 Updater 插件(桌面端);放在 logger 之后,确保失败可诊断。
#[cfg(desktop)]
{
if let Err(e) = app
.handle()
.plugin(tauri_plugin_updater::Builder::new().build())
{
// 若配置不完整(如缺少 pubkey),跳过 Updater 而不中断应用
log::warn!("初始化 Updater 插件失败,已跳过:{e}");
}
}
// 注入 AppHandle 给 usage_events,让无 AppHandle 持有的写日志路径
@@ -459,6 +572,23 @@ pub fn run() {
}
};
// 数据库可用后立即应用持久化日志级别,避免后续服务初始化
// 继续使用启动阶段的 Info 回退。损坏配置显式 fail-closed 到 Info。
match db.get_log_config() {
Ok(log_config) => {
log::set_max_level(log_config.to_level_filter());
log::info!(
"已加载日志配置: enabled={}, level={}",
log_config.enabled,
log_config.level
);
}
Err(e) => {
log::set_max_level(log::LevelFilter::Info);
log::warn!("读取日志配置失败,已回退到 info: {e}");
}
}
// 如果有预加载的配置,执行迁移
if let Some(config) = migration_config {
log::info!("开始执行数据迁移...");
@@ -669,32 +799,33 @@ pub fn run() {
// 1.6. 自动同步 OpenCode / OpenClaw 的 live providers 到数据库
//
// additive 模式(OpenCode / OpenClaw)的 import 函数本身按 id 幂等
// 已有的 provider 会被跳过,所以每次启动都跑是安全的——既保证新装
// 用户开箱可见 live 中的供应商,也让外部修改的 live 文件能在重启
// 后同步到数据库(与之前依赖前端"导入当前配置"按钮手动触发不同)。
// additive 模式(OpenCode / OpenClaw)的 import 函数按 id 幂等——
// 新 id 执行导入,已有 id 则更新 settings 和 display name,所以每次
// 启动都跑是安全的:既保证新装用户开箱可见 live 中的供应商,也让外部
// 修改的 live 文件能在重启后同步到数据库(与之前依赖前端"导入当前配置"
// 按钮手动触发不同)。
//
// 底层 read_*_config 在文件不存在时返回默认空配置,因此新装且无
// live 文件的用户走 Ok(0) 路径,不会产生错误日志噪音。
match crate::services::provider::import_opencode_providers_from_live(&app_state) {
Ok(count) if count > 0 => {
log::info!("Imported {count} OpenCode provider(s) from live config");
log::info!("Synced {count} OpenCode provider(s) from live config");
}
Ok(_) => log::debug!("○ No new OpenCode providers to import"),
Ok(_) => log::debug!("○ No OpenCode provider changes from live config"),
Err(e) => log::warn!("✗ Failed to import OpenCode providers: {e}"),
}
match crate::services::provider::import_openclaw_providers_from_live(&app_state) {
Ok(count) if count > 0 => {
log::info!("Imported {count} OpenClaw provider(s) from live config");
log::info!("Synced {count} OpenClaw provider(s) from live config");
}
Ok(_) => log::debug!("○ No new OpenClaw providers to import"),
Ok(_) => log::debug!("○ No OpenClaw provider changes from live config"),
Err(e) => log::warn!("✗ Failed to import OpenClaw providers: {e}"),
}
match crate::services::provider::import_hermes_providers_from_live(&app_state) {
Ok(count) if count > 0 => {
log::info!("Imported {count} Hermes provider(s) from live config");
log::info!("Synced {count} Hermes provider(s) from live config");
}
Ok(_) => log::debug!("○ No new Hermes providers to import"),
Ok(_) => log::debug!("○ No Hermes provider changes from live config"),
Err(e) => log::warn!("✗ Failed to import Hermes providers: {e}"),
}
@@ -777,6 +908,14 @@ pub fn run() {
Err(e) => log::warn!("✗ Failed to import Gemini MCP: {e}"),
}
match crate::services::mcp::McpService::import_from_grokbuild(&app_state) {
Ok(count) if count > 0 => {
log::info!("✓ Imported {count} MCP server(s) from Grok Build");
}
Ok(_) => log::debug!("○ No Grok Build MCP servers found to import"),
Err(e) => log::warn!("✗ Failed to import Grok Build MCP: {e}"),
}
match crate::services::mcp::McpService::import_from_opencode(&app_state) {
Ok(count) if count > 0 => {
log::info!("✓ Imported {count} MCP server(s) from OpenCode");
@@ -802,6 +941,7 @@ pub fn run() {
crate::app_config::AppType::Claude,
crate::app_config::AppType::Codex,
crate::app_config::AppType::Gemini,
crate::app_config::AppType::GrokBuild,
crate::app_config::AppType::OpenCode,
crate::app_config::AppType::OpenClaw,
crate::app_config::AppType::Hermes,
@@ -880,7 +1020,7 @@ pub fn run() {
for (i, url) in urls.iter().enumerate() {
let url_str = url.as_str();
log::debug!(" URL[{i}]: {}", redact_url_for_log(url_str));
log::debug!(" URL[{i}]: {}", url_for_log(url_str));
if handle_deeplink_url(&app_handle, url_str, true, "on_open_url") {
break; // Process only first ccswitch:// URL
@@ -948,19 +1088,6 @@ pub fn run() {
// 将同一个实例注入到全局状态,避免重复创建导致的不一致
app.manage(app_state);
// 从数据库加载日志配置并应用
{
let db = &app.state::<AppState>().db;
if let Ok(log_config) = db.get_log_config() {
log::set_max_level(log_config.to_level_filter());
log::info!(
"已加载日志配置: enabled={}, level={}",
log_config.enabled,
log_config.level
);
}
}
// 初始化 SkillService
let skill_service = SkillService::new();
app.manage(commands::skill::SkillServiceState(Arc::new(skill_service)));
@@ -989,6 +1116,18 @@ pub fn run() {
log::info!("✓ CodexOAuthManager initialized");
}
// 初始化 xAI OAuthManager (Grok API 反代)
{
use crate::proxy::providers::xai_oauth_auth::XaiOAuthManager;
use commands::XaiOAuthState;
use tokio::sync::RwLock;
let app_config_dir = crate::config::get_app_config_dir();
let xai_oauth_manager = XaiOAuthManager::new(app_config_dir);
app.manage(XaiOAuthState(Arc::new(RwLock::new(xai_oauth_manager))));
log::info!("✓ XaiOAuthManager initialized");
}
// 初始化全局出站代理 HTTP 客户端
{
let db = &app.state::<AppState>().db;
@@ -1076,59 +1215,42 @@ pub fn run() {
tauri::async_runtime::spawn(async move {
const SESSION_SYNC_INTERVAL_SECS: u64 = 60;
fn run_step<T>(name: &str, result: Result<T, crate::error::AppError>) {
if let Err(e) = result {
log::warn!("{name} failed: {e}");
async fn run_session_sync(db: std::sync::Arc<crate::database::Database>, backfill: bool) {
let _guard = crate::services::session_usage::session_sync_mutex()
.lock()
.await;
let task = tauri::async_runtime::spawn_blocking(move || {
if backfill {
if let Err(error) = db.backfill_missing_usage_costs() {
log::warn!("Usage cost startup backfill failed: {error}");
}
}
crate::services::session_usage::sync_all_unlocked(&db)
});
match task.await {
Ok(result) if !result.errors.is_empty() => {
log::warn!(
"Session usage sync completed with {} error(s)",
result.errors.len()
);
}
Ok(_) => {}
Err(error) => log::warn!("Session usage blocking task failed: {error}"),
}
}
let db = &db_for_session_sync;
// 首次同步
run_step(
"Usage cost startup backfill",
db.backfill_missing_usage_costs(),
);
run_step(
"Session usage initial sync",
crate::services::session_usage::sync_claude_session_logs(db),
);
run_step(
"Codex usage initial sync",
crate::services::session_usage_codex::sync_codex_usage(db),
);
run_step(
"Gemini usage initial sync",
crate::services::session_usage_gemini::sync_gemini_usage(db),
);
run_step(
"OpenCode usage initial sync",
crate::services::session_usage_opencode::sync_opencode_usage(db),
);
// 首次同步(含费用回填)
run_session_sync(db_for_session_sync.clone(), true).await;
// 定期同步
let mut interval = tokio::time::interval(std::time::Duration::from_secs(
SESSION_SYNC_INTERVAL_SECS,
));
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
interval.tick().await; // skip immediate first tick
loop {
interval.tick().await;
run_step(
"Session usage periodic sync",
crate::services::session_usage::sync_claude_session_logs(db),
);
run_step(
"Codex usage periodic sync",
crate::services::session_usage_codex::sync_codex_usage(db),
);
run_step(
"Gemini usage periodic sync",
crate::services::session_usage_gemini::sync_gemini_usage(db),
);
run_step(
"OpenCode usage periodic sync",
crate::services::session_usage_opencode::sync_opencode_usage(db),
);
run_session_sync(db_for_session_sync.clone(), false).await;
}
});
});
@@ -1194,6 +1316,8 @@ pub fn run() {
commands::get_claude_desktop_default_routes,
commands::import_claude_desktop_providers_from_claude,
commands::ensure_claude_desktop_official_provider,
commands::ensure_codex_official_provider,
commands::ensure_grokbuild_official_provider,
commands::get_claude_config_status,
commands::get_config_status,
commands::get_claude_code_config_path,
@@ -1210,6 +1334,7 @@ pub fn run() {
commands::set_claude_common_config_snippet,
commands::get_common_config_snippet,
commands::set_common_config_snippet,
commands::update_toml_common_config_snippet,
commands::extract_common_config_snippet,
commands::read_live_provider_settings,
commands::get_settings,
@@ -1249,6 +1374,8 @@ pub fn run() {
commands::get_subscription_quota,
commands::get_codex_oauth_quota,
commands::get_codex_oauth_models,
commands::get_xai_oauth_models,
commands::get_xai_oauth_quota,
commands::get_coding_plan_quota,
commands::get_balance,
// New MCP via config.json (SSOT)
@@ -1269,6 +1396,13 @@ pub fn run() {
commands::enable_prompt,
commands::import_prompt_from_file,
commands::get_current_prompt_file_content,
// Profile management (项目配置方案)
commands::list_profiles,
commands::create_profile,
commands::update_profile,
commands::delete_profile,
commands::clear_current_profile,
commands::apply_profile,
// model list fetch (OpenAI-compatible /v1/models)
commands::fetch_models_for_config,
// ours: endpoint speed test + custom endpoint management
@@ -1391,6 +1525,7 @@ pub fn run() {
commands::check_provider_limits,
// Session usage sync
commands::sync_session_usage,
commands::rebuild_codex_usage,
commands::get_usage_data_sources,
// Stream health check
commands::stream_check_provider,
@@ -1585,7 +1720,10 @@ pub fn run() {
RunEvent::Opened { urls } => {
if let Some(url) = urls.first() {
let url_str = url.to_string();
log::info!("RunEvent::Opened with URL: {url_str}");
log::info!(
"RunEvent::Opened with URL: {}",
url_for_log(&url_str)
);
if url_str.starts_with("ccswitch://") {
if crate::lightweight::is_lightweight_mode() {
@@ -1725,16 +1863,25 @@ pub(crate) fn remove_tray_icon_before_exit(app_handle: &tauri::AppHandle) {
///
/// 检查 `proxy_config.enabled` 字段,如果有任一应用的状态为 `true`,
/// 则自动启动代理服务并接管对应应用的 Live 配置。
async fn restore_proxy_state_on_startup(state: &store::AppState) {
// 收集需要恢复接管的应用列表(从 proxy_config.enabled 读取)
let mut apps_to_restore = Vec::new();
for app_type in ["claude", "codex", "gemini"] {
if let Ok(config) = state.db.get_proxy_config_for_app(app_type).await {
if config.enabled {
apps_to_restore.push(app_type);
}
const PROXY_STARTUP_APP_TYPES: [&str; 4] = ["claude", "codex", "gemini", "grokbuild"];
async fn enabled_proxy_apps_on_startup(db: &database::Database) -> Vec<&'static str> {
let mut apps = Vec::new();
for app_type in PROXY_STARTUP_APP_TYPES {
if db
.get_proxy_config_for_app(app_type)
.await
.is_ok_and(|config| config.enabled)
{
apps.push(app_type);
}
}
apps
}
async fn restore_proxy_state_on_startup(state: &store::AppState) {
// 收集需要恢复接管的应用列表(从 proxy_config.enabled 读取)
let apps_to_restore = enabled_proxy_apps_on_startup(&state.db).await;
if apps_to_restore.is_empty() {
log::debug!("启动时无需恢复代理状态");
@@ -2052,7 +2199,93 @@ pub fn restart_process(app_handle: &tauri::AppHandle) -> ! {
#[cfg(test)]
mod tests {
use super::{classify_exit_request, ExitRequestAction};
use super::{
classify_exit_request, enabled_proxy_apps_on_startup, redact_url_for_log,
redact_url_for_log_with_secrets, redact_url_origin_for_log, runtime_log_level_allows,
ExitRequestAction,
};
use crate::database::Database;
#[test]
fn log_url_redaction_strips_credentials_and_query_keeps_path() {
// userinfo 与整个 query 剥离,path 保留用于诊断 base_url 配错。
assert_eq!(
redact_url_for_log(
"https://user:secret@example.com:8443/v1/models?key=top-secret&alt=sse"
),
"https://example.com:8443/v1/models"
);
// scheme-relative 保持形态,userinfo 去掉。
assert_eq!(
redact_url_for_log("//user:sk-secret@gw.example.com/v1"),
"//gw.example.com/v1"
);
// 无 scheme 的裸 userinfo。
assert_eq!(
redact_url_for_log("user:sk-secret@gw.example.com/v1"),
"gw.example.com/v1"
);
// 无法解析为绝对 URL 时:丢 query,其余原样保留。
assert_eq!(redact_url_for_log("not-a-url?token=secret"), "not-a-url");
// 不再对 path 段做“看起来像密钥”的形状猜测,正常路径完整保留。
assert_eq!(
redact_url_for_log("https://host.example/v1/models/gemini-2.5-pro"),
"https://host.example/v1/models/gemini-2.5-pro"
);
}
#[test]
fn log_url_redaction_replaces_known_secret_values() {
// 精确匹配已知密钥值:无论它出现在 path 还是别处都被抹掉。
let secrets = vec!["k-9f3a7c2b1e".to_string()];
assert_eq!(
redact_url_for_log_with_secrets("https://gw.example.com/k-9f3a7c2b1e/v1", &secrets),
"https://gw.example.com/[REDACTED]/v1"
);
// 过短(<8)的已知值不参与子串脱敏,避免误伤 /v1/ 之类的正常路径。
let short_secrets = vec!["api".to_string()];
assert_eq!(
redact_url_for_log_with_secrets("https://api.example.com/v1", &short_secrets),
"https://api.example.com/v1"
);
}
#[test]
fn log_url_origin_drops_path_for_credential_in_path() {
// 没有已知密钥可脱敏时,凭据可能整个内嵌在 path,只记 origin。
assert_eq!(
redact_url_origin_for_log("https://gw.example.com/k-9f3a7c2b1e/v1"),
"https://gw.example.com"
);
assert_eq!(
redact_url_origin_for_log("https://user:pass@gw.example.com:8443/secret/v1"),
"https://gw.example.com:8443"
);
assert_eq!(
redact_url_origin_for_log("//gw.example.com/secret/v1"),
"//gw.example.com"
);
}
#[test]
fn runtime_log_filter_honors_dynamic_max_level() {
assert!(!runtime_log_level_allows(
log::Level::Error,
log::LevelFilter::Off
));
assert!(runtime_log_level_allows(
log::Level::Error,
log::LevelFilter::Info
));
assert!(runtime_log_level_allows(
log::Level::Info,
log::LevelFilter::Info
));
assert!(!runtime_log_level_allows(
log::Level::Debug,
log::LevelFilter::Info
));
}
#[test]
fn no_code_keeps_app_alive_in_tray() {
@@ -2078,4 +2311,21 @@ mod tests {
ExitRequestAction::CleanupAndExit
);
}
#[tokio::test]
async fn startup_restore_includes_enabled_grokbuild_route() {
let db = Database::memory().expect("initialize database");
let mut config = db
.get_proxy_config_for_app("grokbuild")
.await
.expect("read Grok Build proxy config");
config.enabled = true;
db.update_proxy_config_for_app(config)
.await
.expect("enable Grok Build proxy config");
let apps = enabled_proxy_apps_on_startup(&db).await;
assert_eq!(apps, vec!["grokbuild"]);
}
}
+1
View File
@@ -91,6 +91,7 @@ pub fn import_from_claude(config: &mut MultiAppConfig) -> Result<usize, AppError
claude: true,
codex: false,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
+51 -15
View File
@@ -84,7 +84,9 @@ pub fn import_from_codex(config: &mut MultiAppConfig) -> Result<usize, AppError>
// 核心字段(需要手动处理的字段)
let core_fields = match typ {
"stdio" => vec!["type", "command", "args", "env", "cwd"],
"http" | "sse" => vec!["type", "url", "http_headers"],
// DB 中的统一规范使用 headersCodex TOML 使用 http_headers。
// 两者都必须视为核心字段,避免鉴权值落入通用日志路径。
"http" | "sse" => vec!["type", "url", "headers", "http_headers"],
_ => vec!["type"],
};
@@ -204,7 +206,7 @@ pub fn import_from_codex(config: &mut MultiAppConfig) -> Result<usize, AppError>
if let Some(val) = json_val {
spec.insert(key.clone(), val);
log::debug!("导入扩展字段 '{key}' = {toml_val:?}");
log::debug!("导入扩展字段 '{key}'(值已省略)");
}
}
@@ -235,6 +237,7 @@ pub fn import_from_codex(config: &mut MultiAppConfig) -> Result<usize, AppError>
claude: false,
codex: true,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -361,14 +364,11 @@ pub fn sync_single_server_to_codex(
let mut doc = if config_path.exists() {
let content =
std::fs::read_to_string(&config_path).map_err(|e| AppError::io(&config_path, e))?;
// 尝试解析现有配置,如果失败则创建新文档(容错处理)
match content.parse::<toml_edit::DocumentMut>() {
Ok(doc) => doc,
Err(e) => {
log::warn!("解析 Codex config.toml 失败: {e},将创建新配置");
toml_edit::DocumentMut::new()
}
}
// 解析失败必须报错而不是用空文档顶替:写回空文档会把用户
// config.toml 里的其它段落(model/model_providers/注释等)整体清空
content
.parse::<toml_edit::DocumentMut>()
.map_err(|e| AppError::McpValidation(format!("解析 config.toml 失败: {e}")))?
} else {
toml_edit::DocumentMut::new()
};
@@ -559,7 +559,7 @@ fn json_value_to_toml_item(value: &Value, field_name: &str) -> Option<toml_edit:
/// 1. 核心字段(type, command, args, url, headers, env, cwd)使用强类型处理
/// 2. 扩展字段(timeout、retry 等)通过白名单列表自动转换
/// 3. 其他未知字段使用通用转换器尝试转换
fn json_server_to_toml_table(spec: &Value) -> Result<toml_edit::Table, AppError> {
pub(super) fn json_server_to_toml_table(spec: &Value) -> Result<toml_edit::Table, AppError> {
use toml_edit::{Array, Item, Table};
let mut t = Table::new();
@@ -569,7 +569,7 @@ fn json_server_to_toml_table(spec: &Value) -> Result<toml_edit::Table, AppError>
// 定义核心字段(已在下方处理,跳过通用转换)
let core_fields = match typ {
"stdio" => vec!["type", "command", "args", "env", "cwd"],
"http" | "sse" => vec!["type", "url", "http_headers"],
"http" | "sse" => vec!["type", "url", "headers", "http_headers"],
_ => vec!["type"],
};
@@ -666,11 +666,11 @@ fn json_server_to_toml_table(spec: &Value) -> Result<toml_edit::Table, AppError>
if let Some(toml_item) = json_value_to_toml_item(value, key) {
t[&key[..]] = toml_item;
// 记录扩展字段的处理
// 记录字段名:未知字段同样可能携带 token / secret。
if extended_fields.contains(&key.as_str()) {
log::debug!("已转换扩展字段 '{key}' = {value:?}");
log::debug!("已转换扩展字段 '{key}'(值已省略)");
} else {
log::info!("已转换自定义字段 '{key}' = {value:?}");
log::debug!("已转换自定义字段 '{key}'(值已省略)");
}
}
}
@@ -678,3 +678,39 @@ fn json_server_to_toml_table(spec: &Value) -> Result<toml_edit::Table, AppError>
Ok(t)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn http_headers_are_only_written_to_codex_http_headers() {
let table = json_server_to_toml_table(&json!({
"type": "http",
"url": "https://mcp.example.com",
"headers": {
"Authorization": "Bearer top-secret",
"X-Api-Key": "also-secret"
},
"timeout": 30
}))
.unwrap();
let headers = table
.get("http_headers")
.and_then(|item| item.as_table())
.expect("Codex http_headers table should be written");
assert_eq!(
headers.get("Authorization").and_then(|item| item.as_str()),
Some("Bearer top-secret")
);
assert!(
table.get("headers").is_none(),
"legacy headers must not be emitted a second time"
);
assert_eq!(
table.get("timeout").and_then(|item| item.as_integer()),
Some(30)
);
}
}
+1
View File
@@ -87,6 +87,7 @@ pub fn import_from_gemini(config: &mut MultiAppConfig) -> Result<usize, AppError
claude: false,
codex: false,
gemini: true,
grokbuild: false,
opencode: false,
hermes: false,
},
+221
View File
@@ -0,0 +1,221 @@
//! Grok Build MCP synchronization and import.
//!
//! Grok Build uses the same top-level `[mcp_servers]` TOML layout as Codex,
//! stored alongside its model configuration in `~/.grok/config.toml`.
use serde_json::{json, Value};
use crate::app_config::{McpApps, McpServer, MultiAppConfig};
use crate::error::AppError;
use super::codex::json_server_to_toml_table;
use super::validation::validate_server_spec;
fn should_sync_grokbuild_mcp() -> bool {
crate::grok_config::get_grok_config_dir().exists()
}
fn read_config_text() -> Result<String, AppError> {
let path = crate::grok_config::get_grok_config_path();
if !path.exists() {
return Ok(String::new());
}
std::fs::read_to_string(&path).map_err(|e| AppError::io(&path, e))
}
fn json_server_to_grokbuild_toml_table(server_spec: &Value) -> Result<toml_edit::Table, AppError> {
let mut table = json_server_to_toml_table(server_spec)?;
// Grok infers transport from `command` or `url` and uses `headers`, while
// Codex writes an explicit `type` plus `http_headers`.
table.remove("type");
if let Some(headers) = table.remove("http_headers") {
table.insert("headers", headers);
}
Ok(table)
}
fn toml_server_to_json(entry: &toml::value::Table) -> Value {
fn convert(value: &toml::Value) -> Option<Value> {
match value {
toml::Value::String(value) => Some(json!(value)),
toml::Value::Integer(value) => Some(json!(value)),
toml::Value::Float(value) => Some(json!(value)),
toml::Value::Boolean(value) => Some(json!(value)),
toml::Value::Datetime(value) => Some(json!(value.to_string())),
toml::Value::Array(values) => Some(Value::Array(
values.iter().filter_map(convert).collect::<Vec<_>>(),
)),
toml::Value::Table(values) => Some(Value::Object(
values
.iter()
.filter_map(|(key, value)| convert(value).map(|value| (key.clone(), value)))
.collect(),
)),
}
}
let mut spec = serde_json::Map::new();
for (key, value) in entry {
let output_key = if key == "http_headers" {
"headers"
} else {
key
};
if let Some(value) = convert(value) {
spec.insert(output_key.to_string(), value);
}
}
let default_type = if spec.contains_key("url") {
"http"
} else {
"stdio"
};
spec.entry("type".to_string())
.or_insert_with(|| json!(default_type));
Value::Object(spec)
}
pub fn import_from_grokbuild(config: &mut MultiAppConfig) -> Result<usize, AppError> {
let text = read_config_text()?;
if text.trim().is_empty() {
return Ok(0);
}
let root: toml::Table = toml::from_str(&text)
.map_err(|e| AppError::McpValidation(format!("解析 ~/.grok/config.toml 失败: {e}")))?;
let Some(entries) = root.get("mcp_servers").and_then(toml::Value::as_table) else {
return Ok(0);
};
let servers = config
.mcp
.servers
.get_or_insert_with(std::collections::HashMap::new);
let mut changed = 0;
for (id, entry) in entries {
let Some(entry) = entry.as_table() else {
continue;
};
let spec = toml_server_to_json(entry);
if let Err(error) = validate_server_spec(&spec) {
log::warn!("跳过无效 Grok Build MCP 项 '{id}': {error}");
continue;
}
if let Some(existing) = servers.get_mut(id) {
if !existing.apps.grokbuild {
existing.apps.grokbuild = true;
changed += 1;
}
} else {
servers.insert(
id.clone(),
McpServer {
id: id.clone(),
name: id.clone(),
server: spec,
apps: McpApps {
grokbuild: true,
..Default::default()
},
description: None,
homepage: None,
docs: None,
tags: Vec::new(),
},
);
changed += 1;
}
}
Ok(changed)
}
pub fn sync_single_server_to_grokbuild(
_config: &MultiAppConfig,
id: &str,
server_spec: &Value,
) -> Result<(), AppError> {
if !should_sync_grokbuild_mcp() {
return Ok(());
}
use toml_edit::Item;
let path = crate::grok_config::get_grok_config_path();
let text = read_config_text()?;
let mut doc = if text.trim().is_empty() {
toml_edit::DocumentMut::new()
} else {
text.parse::<toml_edit::DocumentMut>().map_err(|e| {
AppError::McpValidation(format!("解析 Grok Build config.toml 失败: {e}"))
})?
};
if !doc.contains_key("mcp_servers") {
doc["mcp_servers"] = toml_edit::table();
}
doc["mcp_servers"][id] = Item::Table(json_server_to_grokbuild_toml_table(server_spec)?);
crate::config::write_text_file(&path, &doc.to_string())
}
pub fn remove_server_from_grokbuild(id: &str) -> Result<(), AppError> {
if !should_sync_grokbuild_mcp() {
return Ok(());
}
let path = crate::grok_config::get_grok_config_path();
if !path.exists() {
return Ok(());
}
let text = read_config_text()?;
let mut doc = match text.parse::<toml_edit::DocumentMut>() {
Ok(doc) => doc,
Err(error) => {
log::warn!("解析 Grok Build config.toml 失败: {error},跳过删除操作");
return Ok(());
}
};
if let Some(servers) = doc
.get_mut("mcp_servers")
.and_then(toml_edit::Item::as_table_mut)
{
servers.remove(id);
}
crate::config::write_text_file(&path, &doc.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn converts_http_headers_to_unified_headers() {
let entry: toml::value::Table = toml::from_str(
r#"type = "http"
url = "https://example.com/mcp"
http_headers = { Authorization = "Bearer token" }
"#,
)
.expect("parse table");
let spec = toml_server_to_json(&entry);
assert_eq!(spec["type"], "http");
assert_eq!(spec["headers"]["Authorization"], "Bearer token");
}
#[test]
fn writes_grokbuild_remote_server_without_codex_only_fields() {
let table = json_server_to_grokbuild_toml_table(&json!({
"type": "http",
"url": "https://example.com/mcp",
"headers": { "Authorization": "Bearer token" }
}))
.expect("convert server");
assert!(!table.contains_key("type"));
assert!(!table.contains_key("http_headers"));
assert_eq!(
table
.get("headers")
.and_then(toml_edit::Item::as_table)
.and_then(|headers| headers.get("Authorization"))
.and_then(toml_edit::Item::as_str),
Some("Bearer token")
);
}
}
+1
View File
@@ -315,6 +315,7 @@ pub fn import_from_hermes(config: &mut MultiAppConfig) -> Result<usize, AppError
claude: false,
codex: false,
gemini: false,
grokbuild: false,
opencode: false,
hermes: true,
},
+4
View File
@@ -14,6 +14,7 @@
mod claude;
mod codex;
mod gemini;
mod grokbuild;
mod hermes;
mod opencode;
mod validation;
@@ -30,6 +31,9 @@ pub use gemini::{
import_from_gemini, remove_server_from_gemini, sync_enabled_to_gemini,
sync_single_server_to_gemini,
};
pub use grokbuild::{
import_from_grokbuild, remove_server_from_grokbuild, sync_single_server_to_grokbuild,
};
pub use hermes::{import_from_hermes, remove_server_from_hermes, sync_single_server_to_hermes};
pub use opencode::{
import_from_opencode, remove_server_from_opencode, sync_single_server_to_opencode,
+1
View File
@@ -258,6 +258,7 @@ pub fn import_from_opencode(config: &mut MultiAppConfig) -> Result<usize, AppErr
claude: false,
codex: false,
gemini: false,
grokbuild: false,
opencode: true,
hermes: false,
},
+276
View File
@@ -0,0 +1,276 @@
use serde_json::Value;
/// Image-input capability shared by Codex catalog generation and proxy request
/// rectification.
///
/// `Unknown` is intentionally distinct from `Supported`: callers may choose
/// different execution policies without duplicating the model-name registry.
/// The Codex catalog treats unknown models as image-capable (fail open), while
/// the media rectifier leaves their request bodies untouched.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ImageInputCapability {
Supported,
Unsupported,
Unknown,
}
/// Resolve image-input capability from an explicit declaration first, then the
/// confirmed text-only model registry when the caller enables registry lookup.
pub(crate) fn resolve_image_input_capability(
model: &str,
declared_support: Option<bool>,
use_confirmed_registry: bool,
) -> ImageInputCapability {
match declared_support {
Some(true) => ImageInputCapability::Supported,
Some(false) => ImageInputCapability::Unsupported,
None if use_confirmed_registry && is_confirmed_text_only_model(model) => {
ImageInputCapability::Unsupported
}
None => ImageInputCapability::Unknown,
}
}
/// Resolve a model's image-input capability from the provider settings shapes
/// accepted by the proxy (`modelCatalog.models`, `modelCatalog`, or `models`).
pub(crate) fn image_input_capability_from_settings(
settings: &Value,
model: &str,
use_confirmed_registry: bool,
) -> ImageInputCapability {
resolve_image_input_capability(
model,
declared_model_image_support(settings, model),
use_confirmed_registry,
)
}
/// Convert a catalog row's explicit modality list into the shared capability
/// representation, falling back to the text-only registry when omitted.
pub(crate) fn image_input_capability_from_modalities(
model: &str,
modalities: Option<&[String]>,
) -> ImageInputCapability {
let declared_support = modalities.map(|items| {
items
.iter()
.any(|item| item.trim().eq_ignore_ascii_case("image"))
});
resolve_image_input_capability(model, declared_support, true)
}
/// Models that CC Switch is willing to advertise to clients as text-only.
///
/// This registry is deliberately exact and fail-open. A new suffix is not
/// inherited automatically: it remains image-capable until its capability is
/// confirmed, preventing a future `-vision`/`-vl` variant from being blocked by
/// the Codex client before a request can reach the proxy.
pub(crate) fn is_confirmed_text_only_model(model: &str) -> bool {
let normalized = normalize_model_id(model);
let tail = normalized.rsplit('/').next().unwrap_or(normalized.as_str());
const CONFIRMED_TAILS: &[&str] = &[
"ark-code-latest",
"deepseek-chat",
"deepseek-reasoner",
"deepseek-v4-flash",
"deepseek-v4-pro",
"glm-5.1",
// Exact rather than prefix matching: GLM visual models use a `v`
// suffix (for example glm-5.2v), which must remain image-capable.
"glm-5.2",
"kat-coder",
"kat-coder-pro",
"kat-coder-pro v1",
"kat-coder-pro v2",
"kat-coder-pro-v1",
"kat-coder-pro-v2",
"ling-2.5-1t",
"longcat-2.0",
"longcat-flash-chat",
"minimax-m2.7",
"minimax-m2.7-highspeed",
"mimo-v2.5-pro",
"qwen3-coder-480b",
"qwen3-coder-480b-a35b-instruct",
"qwen3-coder-flash",
"qwen3-coder-next",
"qwen3-coder-plus",
"step-3.5-flash",
"step-3.5-flash-2603",
"us.deepseek.r1-v1",
];
CONFIRMED_TAILS.contains(&tail)
}
fn declared_model_image_support(settings: &Value, model: &str) -> Option<bool> {
[
settings
.get("modelCatalog")
.and_then(|catalog| catalog.get("models")),
settings.get("modelCatalog"),
settings.get("models"),
]
.into_iter()
.flatten()
.find_map(|value| declared_model_image_support_in_value(value, model))
}
fn declared_model_image_support_in_value(value: &Value, model: &str) -> Option<bool> {
if let Some(models) = value.as_array() {
return models.iter().find_map(|entry| {
model_entry_matches(entry, None, model).then(|| explicit_image_support(entry))?
});
}
let object = value.as_object()?;
object.iter().find_map(|(key, entry)| {
model_entry_matches(entry, Some(key), model).then(|| explicit_image_support(entry))?
})
}
fn explicit_image_support(entry: &Value) -> Option<bool> {
if let Some(value) = entry
.get("supportsImage")
.or_else(|| entry.get("supports_image"))
.or_else(|| entry.get("vision"))
.and_then(Value::as_bool)
{
return Some(value);
}
[
entry.get("input"),
entry.pointer("/modalities/input"),
entry.get("input_modalities"),
entry.get("inputModalities"),
]
.into_iter()
.flatten()
.find_map(input_modalities_support_image)
}
fn input_modalities_support_image(value: &Value) -> Option<bool> {
let modalities = value.as_array()?;
Some(modalities.iter().any(|item| {
item.as_str()
.map(str::trim)
.is_some_and(|item| item.eq_ignore_ascii_case("image"))
}))
}
fn model_entry_matches(entry: &Value, key: Option<&str>, model: &str) -> bool {
key.is_some_and(|key| model_ids_match(key, model))
|| ["model", "id", "name"]
.into_iter()
.filter_map(|field| entry.get(field).and_then(Value::as_str))
.any(|candidate| model_ids_match(candidate, model))
}
fn model_ids_match(candidate: &str, model: &str) -> bool {
let candidate = normalize_model_id(candidate);
let model = normalize_model_id(model);
if candidate.is_empty() || model.is_empty() {
return false;
}
if candidate == model {
return true;
}
let candidate_tail = candidate.rsplit('/').next().unwrap_or(candidate.as_str());
let model_tail = model.rsplit('/').next().unwrap_or(model.as_str());
candidate_tail == model_tail || candidate == model_tail || candidate_tail == model
}
fn normalize_model_id(value: &str) -> String {
let mut normalized = value
.trim()
.trim_start_matches("models/")
.trim()
.to_ascii_lowercase();
if let Some(stripped) =
normalized.strip_suffix(crate::claude_desktop_config::ONE_M_CONTEXT_MARKER)
{
normalized = stripped.trim().to_string();
}
normalized
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn gpt_and_unknown_models_remain_unknown_without_declarations() {
for model in ["gpt-5.4", "gpt-5.5", "gpt-5.6-sol", "custom-alias"] {
assert_eq!(
resolve_image_input_capability(model, None, true),
ImageInputCapability::Unknown,
"{model} must fail open"
);
}
}
#[test]
fn confirmed_text_only_registry_normalizes_namespaces_and_context_markers() {
assert!(is_confirmed_text_only_model("deepseek/deepseek-v4-pro"));
assert!(is_confirmed_text_only_model("GLM-5.2[1M]"));
assert!(is_confirmed_text_only_model("qwen/qwen3-coder-plus"));
assert!(is_confirmed_text_only_model(
"Qwen/Qwen3-Coder-480B-A35B-Instruct"
));
assert!(is_confirmed_text_only_model("MiniMax-M2.7-Highspeed"));
assert!(is_confirmed_text_only_model("step-3.5-flash-2603"));
assert!(!is_confirmed_text_only_model("glm-5.2v"));
}
#[test]
fn unconfirmed_family_suffixes_fail_open() {
for model in [
"minimax-m2.7-vision",
"qwen3-coder-ultra",
"qwen3-coder-vl",
"step-3.5-flash-vision",
] {
assert!(
!is_confirmed_text_only_model(model),
"unconfirmed variant {model} must not be hard-gated"
);
}
}
#[test]
fn explicit_capability_overrides_the_registry() {
assert_eq!(
resolve_image_input_capability("deepseek-v4-pro", Some(true), true),
ImageInputCapability::Supported
);
assert_eq!(
resolve_image_input_capability("gpt-5.4", Some(false), true),
ImageInputCapability::Unsupported
);
}
#[test]
fn provider_settings_support_multiple_capability_shapes() {
let settings = json!({
"modelCatalog": {
"models": [
{ "model": "vision", "modalities": { "input": ["text", "image"] } },
{ "model": "text", "inputModalities": ["text"] }
]
}
});
assert_eq!(
image_input_capability_from_settings(&settings, "vision", true),
ImageInputCapability::Supported
);
assert_eq!(
image_input_capability_from_settings(&settings, "text", true),
ImageInputCapability::Unsupported
);
}
}
+103 -8
View File
@@ -3,16 +3,19 @@
//! 在应用崩溃时捕获 panic 信息并记录到 `<app_config_dir>/crash.log` 文件中(默认 `~/.cc-switch/crash.log`)。
//! 便于用户和开发者诊断闪退问题。
use std::fs::OpenOptions;
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::panic;
use std::path::PathBuf;
use std::sync::OnceLock;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, OnceLock};
/// 应用版本号(从 Cargo.toml 读取)
const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
const CRASH_LOG_MAX_SIZE: u64 = 5 * 1024 * 1024;
const CRASH_LOG_ARCHIVES_TO_KEEP: usize = 2;
static APP_CONFIG_DIR: OnceLock<PathBuf> = OnceLock::new();
static CRASH_LOG_LOCK: Mutex<()> = Mutex::new(());
pub fn init_app_config_dir(dir: PathBuf) {
let _ = APP_CONFIG_DIR.set(dir);
@@ -38,6 +41,50 @@ fn get_crash_log_path() -> PathBuf {
get_app_config_dir().join("crash.log")
}
fn rotated_crash_log_path(path: &Path, index: usize) -> PathBuf {
let mut rotated = path.as_os_str().to_os_string();
rotated.push(format!(".{index}"));
PathBuf::from(rotated)
}
fn rotate_crash_log_if_needed_with_limit(
path: &Path,
max_size: u64,
archives_to_keep: usize,
) -> std::io::Result<()> {
let size = match fs::metadata(path) {
Ok(metadata) => metadata.len(),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()),
Err(e) => return Err(e),
};
if size < max_size || archives_to_keep == 0 {
return Ok(());
}
for index in (1..=archives_to_keep).rev() {
let source = if index == 1 {
path.to_path_buf()
} else {
rotated_crash_log_path(path, index - 1)
};
if !source.exists() {
continue;
}
let destination = rotated_crash_log_path(path, index);
if destination.exists() {
fs::remove_file(&destination)?;
}
fs::rename(source, destination)?;
}
Ok(())
}
fn rotate_crash_log_if_needed(path: &Path) -> std::io::Result<()> {
rotate_crash_log_if_needed_with_limit(path, CRASH_LOG_MAX_SIZE, CRASH_LOG_ARCHIVES_TO_KEEP)
}
/// 获取日志目录路径
pub fn get_log_dir() -> PathBuf {
get_app_config_dir().join("logs")
@@ -167,12 +214,23 @@ Stack Trace (Backtrace)
"#
);
// 写入文件(追加模式)
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(&log_path) {
let _ = file.write_all(crash_entry.as_bytes());
let _ = file.flush();
// 将 size check、轮转和追加合成同一个临界区,避免多线程同时 panic
// 时两个 hook 竞争 rename 而丢失归档。
let crash_log_guard = CRASH_LOG_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let _ = rotate_crash_log_if_needed(&log_path);
let saved =
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(&log_path) {
let _ = file.write_all(crash_entry.as_bytes());
let _ = file.flush();
true
} else {
false
};
drop(crash_log_guard);
// 记录日志文件位置到 stderr
if saved {
eprintln!("\n[CC-Switch] Crash log saved to: {}", log_path.display());
}
@@ -202,4 +260,41 @@ mod tests {
assert!(info.contains("Arch:"));
assert!(info.contains("App Version:"));
}
#[test]
fn crash_log_rotation_keeps_bounded_archives() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("crash.log");
fs::write(&path, b"first").unwrap();
rotate_crash_log_if_needed_with_limit(&path, 4, 2).unwrap();
assert!(!path.exists());
assert_eq!(
fs::read(rotated_crash_log_path(&path, 1)).unwrap(),
b"first"
);
fs::write(&path, b"second").unwrap();
rotate_crash_log_if_needed_with_limit(&path, 4, 2).unwrap();
assert_eq!(
fs::read(rotated_crash_log_path(&path, 1)).unwrap(),
b"second"
);
assert_eq!(
fs::read(rotated_crash_log_path(&path, 2)).unwrap(),
b"first"
);
fs::write(&path, b"third").unwrap();
rotate_crash_log_if_needed_with_limit(&path, 4, 2).unwrap();
assert_eq!(
fs::read(rotated_crash_log_path(&path, 1)).unwrap(),
b"third"
);
assert_eq!(
fs::read(rotated_crash_log_path(&path, 2)).unwrap(),
b"second"
);
assert!(!rotated_crash_log_path(&path, 3).exists());
}
}

Some files were not shown because too many files have changed in this diff Show More