diff --git a/CHANGELOG.md b/CHANGELOG.md index 5be62964c..292b4daa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,76 @@ 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). -## [Unreleased] +## [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_` 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.]` 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::`, 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 ` 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 ` 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 @@ -30,8 +95,6 @@ Development since v3.16.4 reworks the Codex native-Responses path — restoring ### 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. - **Decoupled Codex Model Mapping From the Local-Routing Toggle**: Aligns the Codex provider form with Claude Code — the model-mapping catalog is now independent of route takeover, since native Responses providers (MiMo, Doubao, MiniMax) need it for proxy-less direct connect while Chat providers use the proxy regardless of any per-provider flag. The "Needs Local Routing" toggle is removed: it had no backend field and only gated catalog/reasoning persistence, which is equivalent to whether the mapping is filled. Model mapping is now always shown for non-official providers and persisted whenever the list is non-empty (the backend already keys off `modelCatalog.models`), while reasoning visibility/persistence is gated on the Chat format instead of the toggle. The Chat upstream-format option is marked "routing required" with a refreshed advanced-section hint across all four locales (zh/en/ja/zh-TW), dead `localRouting*` keys are dropped, and the model-mapping block moves above the custom User-Agent block. Also fixes `useCodexConfigState` dropping `supportsParallelToolCalls`/`inputModalities`/`baseInstructions` when loading a saved provider — which silently lost parallel tools, image input, and the official base instructions on edit — by preserving them on load (camelCase and snake_case) and comparing them in the row sync. - **Auto-Sync Claude Common Config From Live settings.json on Switch**: When switching away from a Claude provider that has `common_config_enabled`, the service now re-extracts the shareable portion of its live `settings.json` and replaces the stored common-config snippet, instead of only writing the snippet one way. This captures config the user added directly in the running app (`enabledPlugins`, `hooks`, `env`, `theme`, shared prefs) so it isn't silently lost on the next switch, and it propagates deletions so a removed key isn't re-injected later. The sync in `services/provider/mod.rs` is scoped strictly to Claude providers with `common_config_enabled`, is skipped when the snippet was explicitly cleared, and all failures are non-fatal (warn-only) and never block the switch. Four integration tests cover capture / delete-sync / opt-out / cleared. - **Default Sonnet Tier Now Pins to Claude Sonnet 5**: Bumped every default Sonnet pin from `claude-sonnet-4-6`/`claude-sonnet-4.6` to `claude-sonnet-5` across all provider presets (`claudeProviderPresets`, `claudeDesktopProviderPresets`, `hermesProviderPresets`, `openclawProviderPresets`, `opencodeProviderPresets`, and universal `NEWAPI_DEFAULT_MODELS`), covering the `ANTHROPIC_MODEL` / `ANTHROPIC_DEFAULT_SONNET_MODEL` / `ANTHROPIC_DEFAULT_OPUS_MODEL` env keys and prefixed variants like `anthropic/claude-sonnet-5` and `global.anthropic.claude-sonnet-5`. The Claude Desktop `DEFAULT_PROXY_ROUTES` sonnet `route_id` in `claude_desktop_config.rs` also moves to `claude-sonnet-5` (`supports_1m` preserved), and the route-derivation test expectations were updated to match. Non-Anthropic pins (gpt/gemini/glm/sonnet-4-5) were left untouched. diff --git a/package.json b/package.json index 9c07d1650..31d6964a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cc-switch", - "version": "3.16.5", + "version": "3.17.0", "description": "All-in-One Assistant for Claude Code, Codex & Gemini CLI", "type": "module", "scripts": { diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a1e858710..eb60f4441 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -758,7 +758,7 @@ dependencies = [ [[package]] name = "cc-switch" -version = "3.16.5" +version = "3.17.0" dependencies = [ "anyhow", "arboard", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index e0a13d056..93934cf8c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cc-switch" -version = "3.16.5" +version = "3.17.0" description = "All-in-One Assistant for Claude Code, Codex & Gemini CLI" authors = ["Jason Young"] license = "MIT" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 313d0c9c9..5f7a87a0d 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "CC Switch", - "version": "3.16.5", + "version": "3.17.0", "identifier": "com.ccswitch.desktop", "build": { "frontendDist": "../dist",