- Add v3.16.4 release notes in zh/en/ja.
- Add a trilingual FAQ explaining why custom models don't show in the Codex desktop model picker (upstream Codex Desktop login-identity gating, not a cc-switch bug) and the keep-official-login mitigation; cross-linked both ways with the official-auth preservation guide.
- Align the CHANGELOG 3.16.4 release date to 2026-06-27.
Add a Changed entry for the removed Fable 5 Verified commemorative banner (16922917) and refresh the stats line to the content-scoped count (53 commits | 126 files | +8,149 / -1,016), excluding the changelog meta commits.
Summarize the commits since v3.16.3 across Added/Changed/Fixed/Docs: the SubRouter and OpenCode Go partner presets, native Codex Responses migration for CN providers, proxy hardening (zstd decompression, OAuth-over-proxy, DeepSeek effort strip), and usage/pricing tooling (Volcengine Ark AK/SK queries, models.dev import, live date ranges, GLM-5.2/Doubao pricing).
The Claude Desktop OpenCode Go preset was missed by the referral/promo update that landed for Claude Code / Codex / OpenCode, so its websiteUrl pointed at the bare opencode.ai domain with no apiKeyUrl or partnerPromotionKey. Align it with the other three: websiteUrl -> /go, apiKeyUrl -> the ref=2YTRG2NGTX referral link, and partnerPromotionKey "opencode_go" so the in-app promo banner shows. All four OpenCode Go presets now carry the referral link and promo copy.
Swap the SubRouter apiKeyUrl across all 7 client presets from the
neutral console/token page to the affiliate registration link
(?aff=l3ri), matching the project's referral-link convention.
Sync the preset test constant accordingly.
Decouple the in-app promotion banner from isPartner so a preset can show a
promo phrase plus referral link without earning the paid-partner star.
- ApiKeySection: gate the banner on partnerPromotionKey alone; the star is
still driven solely by isPartner. This also un-suppresses the existing
MiniMax cn/en promos, which already carried copy without isPartner.
- OpenCode Go (claude/codex/opencode): point apiKeyUrl at the referral
link and add partnerPromotionKey "opencode_go" (no isPartner set).
- i18n: add the opencode_go promo string across zh/en/ja/zh-TW.
Add OpenCode Go (opencode.ai/zen/go) provider presets across clients:
- Codex: openai_chat conversion with model catalog (GLM/Kimi/DeepSeek/MiMo), no static codexChatReasoning so per-model capability is inferred
- OpenCode: @ai-sdk/openai-compatible against /zen/go/v1
- Claude: add apiKeyUrl (opencode.ai/auth) and align websiteUrl to /go
Auth is a plain pasteable API key (no OAuth). Go splits wire format by model: OpenAI /chat/completions for GLM/Kimi/DeepSeek/MiMo, Anthropic /messages for MiniMax/Qwen; these presets target the chat-format models.
Refresh the Kimi sponsor blurb to the K2.7 Code release (coding-focused
agentic model, ~30% lower thinking-token usage vs K2.6) and bump the
banner alt text from K2.6 to K2.7 Code. EN/ZH copy provided by the
vendor; JA/DE translated to match.
Commit 273cc48c migrated 7 CN providers from openai_chat to native
openai_responses and refreshed model ids on the remaining chat-only
providers, but the codexChatProviderPresets snapshot was not updated,
breaking the unit tests on main.
Drop the migrated providers (DouBaoSeed, Bailian, Longcat, MiniMax,
MiniMax en, Xiaomi MiMo x2) from the chat list, sync GLM/StepFun/Ling
model ids, and add a test locking the migrated presets to
openai_responses with no modelCatalog (so the local route-mapping
toggle stays unchecked).
Switch the Kimi K2.6 sponsor banner from the Moonshot CDN URL to
in-repo assets. Chinese README uses kimi-banner-zh.png; English,
Japanese and German use kimi-banner-en.png.
Several Chinese providers now expose a native OpenAI Responses API
endpoint, so Codex can reach them directly without the Responses->Chat
route-takeover conversion. Switch these presets to apiFormat
"openai_responses" and drop the now-unused codexChatReasoning and
modelCatalog (removing modelCatalog also keeps the "local route mapping"
toggle unchecked by default):
- Qwen / DashScope Bailian (/compatible-mode/v1/responses)
- Xiaomi MiMo + Token Plan (api.xiaomimimo.com/v1)
- Volcengine Doubao (/api/v3/responses)
- Meituan LongCat (/openai/v1)
- MiniMax CN + intl (/v1/responses, confirmed in the official API reference)
SiliconFlow-hosted MiniMax stays openai_chat (third-party endpoint, not
MiniMax's own base_url).
Also refresh stale model ids on the remaining chat-only providers:
GLM 5.1->5.2, StepFun 3.5-flash-2603->3.7-flash, Ling 2.5-1T->2.6-1T.
- Add an optional `enabled` flag to useScanUnmanagedSkills; the Skills panel
scans once on mount (enabled:true), with 30s staleTime + keepPreviousData to
dedupe disk IO across navigations.
- App subscribes to the shared query (enabled:false) and renders a green dot +
tooltip on the top-bar Import button when unmanaged skills are available.
The Codex provider form tied Chat-format conversion and route takeover (model
mapping) to one toggle, so a provider serving a native Responses API could not
use model mapping without forcing Chat Completions conversion.
- Promote the upstream format (Chat Completions / Responses) to an independent,
always-visible selector that triggers no sub-menus on its own.
- The local-routing toggle now solely gates the advanced sub-sections: model
mapping catalog, plus reasoning capability when the format is Chat.
- Persist modelCatalog / codexChatReasoning based on the takeover toggle and
derive its initial state from saved catalog presence (no new persisted field).
- Refresh codexConfig i18n (zh/en/ja/zh-TW): add upstreamFormat* keys and reword
the routing/advanced hints to reflect the decoupling.
- Fix codexChatProviderPresets test expectation for the Doubao Seed 2.1 Pro rename.
The account-level AccessKey is buried in the Volcengine console menus,
so surface a clickable, visible link to the IAM key management page
(https://console.volcengine.com/iam/keymanage) right under the AK/SK
hint. Opens via settingsApi.openExternal; adds the volcengineKeyConsoleLink
label across all four locales.
Point DouBaoSeed at doubao-seed-2-1-pro across all six clients (claude, claude-desktop, codex, opencode, openclaw, hermes), replacing doubao-seed-2-0-code-preview-latest. Sync display names to "Doubao Seed 2.1 Pro" and correct the openclaw cost field (0.002/0.006 -> 0.84/4.2 USD/1M) to match the new model.
* fix(proxy): decompress Codex request body before forward, support zstd
Codex Desktop sends zstd-compressed request bodies when authenticated
against the Codex backend, which broke local proxy routing because the
handlers parsed the raw bytes with serde_json directly.
Reworked on top of current main so it preserves the response_processor
behavior that landed after this PR was first opened:
- Extract content-encoding helpers into a shared proxy::content_encoding
module. decompress_body keeps returning Option<Vec<u8>> so unknown
encodings stay pass-through with their content-encoding header intact,
and keeps the deflate zlib-then-raw fallback (RFC 9110).
- Add zstd/zst support (zstd 0.13) and disable reqwest's auto zstd
decompression via .no_zstd() for parity with gzip/br/deflate.
- Decompress the request body before JSON parsing in the three Codex
handlers (chat_completions / responses / responses_compact) and strip
the stale content-encoding / content-length / transfer-encoding headers
so the forwarder regenerates them.
- Support stacked codings (e.g. "gzip, zstd") by decoding in reverse
order and merge repeated Content-Encoding headers via get_all.
Fixes#3764Fixes#3696
Co-authored-by: chenx-dust <16610294+chenx-dust@users.noreply.github.com>
* fix(proxy): decompress upstream error bodies before reading them
The forwarder error branch consumes non-2xx responses via String::from_utf8
directly, bypassing read_decoded_body. reqwest has no auto-decompression
feature enabled, so a compressed error body (gzip/br/deflate/zstd) arrives
as raw bytes, fails from_utf8, and gets dropped, hiding upstream rate-limit
and auth details from the client.
Decode the error body with the shared proxy::content_encoding helper,
mirroring the success path. Falls back to the raw bytes when the encoding is
unsupported or decoding fails.
Co-authored-by: chenx-dust <16610294+chenx-dust@users.noreply.github.com>
---------
Co-authored-by: Jason <farion1231@gmail.com>
Co-authored-by: chenx-dust <16610294+chenx-dust@users.noreply.github.com>
* feat(db): in-app recovery screen with upgrade button when DB version is too new
When the SQLite user_version is newer than the app supports (SCHEMA_VERSION),
Database::init() fails and previously dead-ended in a native Retry/Exit dialog
(Retry just fails again). The app now boots a dedicated recovery screen instead.
- Detect the recoverable "version too new" case and surface it via init_status
(kind="db_version_too_new" + db_version/supported_version); force-show the main
window and skip normal AppState boot (recovery commands need only AppHandle).
- The recovery screen first checks for an available update:
- update available -> "Upgrade app" runs the updater (download + install +
restart) with a download progress bar.
- no update (already latest) -> warns that the DB is too new even for the latest
build (likely a third-party client), so upgrading cannot help.
- install_update_and_restart now emits `update-download-progress` events; new
`check_app_update_available` command.
- i18n: en / ja / zh / zh-TW.
Verified: pnpm typecheck + format:check + test:unit (351) green; cross-model
review of Rust correctness and recovery-mode safety (no AppState panic).
* fix(db): pre-check too-new DB before schema writes; exit on close in recovery mode
Addresses review feedback on the too-new-DB recovery flow.
- P1: stored_user_version_exceeds_supported() is now checked BEFORE
Database::init(), so create_tables()'s DDL (incl. the unconditional
DROP INDEX/DROP TABLE IF EXISTS failover_queue) never runs against a
database whose user_version we cannot understand. The earlier
post-init-failure recovery branch is removed; the pre-check is the
single authoritative guard, so "don't write to a DB we can't read"
now holds from the very first DB access.
- P2: the window CloseRequested handler now detects recovery mode
(init_error kind = db_version_too_new) and exits the app instead of
honoring minimize_to_tray_on_close. Recovery mode returns before the
tray is created, so hiding the window would leave the app running with
no tray to bring it back; native-close now quits cleanly.
Verified: cargo fmt --check clean; cross-model Rust review of both fixes
(compile-correctness + no normal-startup/close regression).
* add glm-5.2 pricing
* feat(usage): add live end time option for custom date range
Add a "End time follows current time" checkbox in the custom date range
picker. When enabled, the end time becomes read-only and automatically
tracks the current moment, so usage data always reflects up-to-the-second
consumption from the chosen start time.
This is especially useful under the Coding Plan 5-hour quota window —
users can set the start time to when their 5h window began and keep
the end time live to monitor real-time token consumption.
* style(usage): fix prettier formatting for UsageDashboard
* fix(usage): include liveEndTime in React Query cache keys
Without liveEndTime in the query key, a live custom range and a fixed
custom range with the same stored endpoints share the same cache entry.
After the live range refreshes (endDate = now), switching to the fixed
range shows stale data fetched through "now" instead of the original
end time.
* ci: add Windows ARM64 release artifacts
* ci: keep release matrix jobs independent
* ci: fix pnpm cache path on Windows runners
* ci: setup pnpm with corepack on Windows ARM64
* ci: fix Windows ARM64 release build
* ci: retry transient release bundler downloads
* ci: remove non-minimal release workflow changes
* ci: keep release matrix jobs independent
macOS signing fails in forks without Apple secrets and, with default
matrix fail-fast, cancels the sibling jobs (including Windows ARM64)
before they finish. Disable fail-fast so each platform runs to completion.
---------
Co-authored-by: MoonDreamStars <moondreamstar1@gmail.com>
Set the default Claude Code auto-compact window to 262144 for the
Kimi For Coding provider preset, matching the official Kimi docs:
https://www.kimi.com/code/docs/en/third-party-tools/other-coding-agents.html
Use templateValues so users can customize the value (e.g. for future
models or performance tuning) while keeping 262144 as the default.
Append a clickable CTA hook to the end of the Kimi K2.6 slogan in all
four README locales (en/zh/ja/de), pointing to the existing
aff=cc-switch Moonshot console link.
CI cargo fmt --check failed on transform_codex_chat.rs (the wrapping
introduced upstream did not match rustfmt 1.95.0). Apply cargo fmt so the
chat_legacy_function_call_to_response_item call uses the expected layout.
The official-first/prime-partner sort changed Original mode order, but the
selector tests still asserted the old raw array order. Update the three
stale assertions and add a dedicated case covering the prime-partner group
and the exclusivity rule (an official preset also flagged primePartner
stays only in the official group).
Query coding-plan / agent-plan usage for Volcengine Ark via the
control-plane OpenAPI (open.volcengineapi.com), which requires
account-level AccessKey signing rather than the inference API key.
- Implement Volcengine Signature V4 (an AWS SigV4 variant: fixed
canonical header order, "HMAC-SHA256" algorithm without the AWS4
prefix, scope ending in "request", service "ark") in
services/coding_plan.rs
- Auto-detect the plan by probing GetAFPUsage (Agent Plan, AFP
five-hour/weekly/monthly quotas) first, falling back to
GetCodingPlanUsage (Coding Plan, session/weekly/monthly percentages);
a single credential covers whichever plan the account holds
- Parse the real response shape: the window label lives in the `Level`
field; guard ResetTimestamp <= 0 (session returns -1)
- Store account-level credentials on UsageScript (accessKeyId /
secretAccessKey), threaded through both the test command and the
TOKEN_PLAN auto-refresh path
- Add an independent AK/SK input block with a detailed hint pointing to
the Volcengine console -> API Access Keys
- Add the `monthly` tier label (frontend TIER_I18N_KEYS + tray
TIER_LABEL_GROUPS + subscription service)
- Sync zh / en / ja / zh-TW locales and the usage-query docs
- Reduce title->tabs and tabs->card vertical gaps from 24px to 12px
- Add an always-visible footer hint guiding users to fill the fields
below after choosing a preset (pinned footer, never scrolls off)
- FullScreenPanel: add optional contentClassName prop to scope content
padding overrides without affecting the other panels that share it
- i18n: add provider.addFooterHint in zh/en/ja/zh-TW
The OpenCode and OpenClaw presets were labeled "Kimi K2.7 Code", unlike
the plain "Kimi" used by the other apps. Rename the preset (and the
OpenCode provider display name) to "Kimi" for consistency; the model
label stays "Kimi K2.7 Code" since it describes the actual model.
In the default (Original) sort mode, float official-category presets to
the very top, followed by prime partners (Kimi), then the rest in their
original array order. Implemented as a three-way partition+concat so each
group keeps its internal order and no preset is listed twice.
Replace the inline Kimi placeholder mark with the vendor's refreshed
logo. The K glyph uses `currentColor` so it follows the theme text color
(dark in light mode, white in dark mode), while the brand accent dot is
pinned to the new #1783FF. Align the metadata fallback color accordingly.
The vendor renamed its domain, API endpoints, and trademark from CTok to
ETok. Update all user-facing branding accordingly:
- Presets (claude, claude-desktop, codex, gemini, hermes, openclaw,
opencode): ctok.ai -> etok.ai, api.ctok.ai -> api.etok.ai, internal
provider id ctok -> etok, display name CTok.ai -> ETok.ai
- Icons: replace inline placeholder SVG with the new ETok brand PNG,
moved from `icons` to `iconUrls`; key ctok -> etok; banner logo
ctok.png -> etok.png
- README (en/zh/ja/de) partner banner: new logo, links, and copy
- i18n (en/ja/zh/zh-TW): partnerPromotion key ctok -> etok
Keep "ctok" in the Codex history-migration whitelist as a legacy id and
add "etok", so existing users' local session history stays correctly
bucketed after the rename.
* Chat API: skip tool calls with missing function names
Some providers send empty or absent function names in streaming
tool call deltas. Previously these produced invalid output items.
- Don't overwrite accumulated state.name with empty deltas
- Skip tool calls that never received a valid name (instead of
falling back to 'unknown_tool')
- Apply the same defensive guard in finalize_tools and the
non-streaming path
* Address review: defer empty-name skip to finalization
Require both call_id and name before triggering should_add,
instead of skipping eagerly when name is absent in the first
delta. This handles providers that send id before name,
as suggested in the Codex review.
* Guard legacy function_call against empty name
Return Option<Value> from chat_legacy_function_call_to_response_item,
returning None when function_call.name is missing or empty. This covers
the legacy message.function_call path that the original guard missed.
* Remove unreachable unknown_tool fallback
---------
Co-authored-by: Jason <farion1231@gmail.com>
Introduce an optional primePartner flag on all provider preset interfaces.
Regular partners keep the gold star badge; prime partners render a solid
gold heart (no badge frame). Mark the first-party Moonshot Kimi presets
(Kimi / Kimi For Coding / Kimi K2.7 Code) across claude, codex, claude
desktop, opencode, openclaw and hermes as prime partners.
Replace the low-res ccsub.jpg with a vector SVG. Letterbox it from
2046x648 (3.157:1) to 2046x850 (~2.406:1) so it matches the other
sponsor-table banners and renders at the same height (62px @ width=150).
Point all four README locales at the new asset.
The session-history migration only scanned ~/.codex/state_5.sqlite and the
config.toml `sqlite_home` location. When Codex's SQLite state is relocated via
the CODEX_SQLITE_HOME env var, the state DB was never scanned and its threads
kept their old provider bucket. Add a CODEX_SQLITE_HOME fallback (config
`sqlite_home` still takes precedence) in the shared codex_state_db_paths helper
used by both the third-party and unified-session history migrations, plus serial
env tests.
* feat(usage): support importing model pricing from models.dev
Add an "Import from models.dev" button to the Add Pricing panel that
fetches https://models.dev/api.json, lists priced models sorted by
release date (newest 50 by default, full-text search across ~4800),
and bulk-imports the selected entries through the same
update_model_pricing command as manual entry.
- Normalize imported model IDs to match the backend's
clean_model_id_for_pricing rules (strip vendor prefix, lowercase,
truncate ':' suffix, map '@' to '-', drop the [1m] marker) so the
stored rows actually match cost-attribution lookups
- Dedupe selections that collapse to the same model_id and report
skipped duplicates in the success toast
- Invalidate usage queries on settled (not just success) so partial
import failures still refresh the pricing list
- Keep ESC inside the picker's search input from closing the dialog
and discarding the selection
- Add i18n keys for zh/en/zh-TW/ja and unit tests for the
normalization, price formatting and flattening logic
Fixes#4017
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(usage): match scoped cost backfill against raw model aliases
The scoped backfill selected zero-cost rows via exact SQL string match,
but log columns store raw model strings (route prefixes, :free variants,
date suffixes), so alias rows were skipped until the next full backfill
on startup. Filter rows in Rust with the same model_pricing_candidates
normalization used by the pricing lookup; pricing decision logic is
untouched. Pre-existing gap from schema v11, surfaced by bulk import.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(usage): restrict models.dev pricing import to a single model
Each update_model_pricing call triggers a backfill pass that loads
every zero-cost usage row before filtering by model, so bulk-importing
N entries scaled as selectedModels x allZeroCostLogs full scans.
Re-importing pricing is rare, so drop the batch path instead of
optimizing it: the picker is now single-select, one import runs exactly
one update_model_pricing call and one backfill pass. This also removes
the normalized-ID dedup logic and the useImportModelPricing hook in
favor of the existing useUpdateModelPricing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(provider-preset-selector): after searching presets, it is impossible to select search results.
feat(provider-preset-selector): add keyboard shortcut for search input and improve focus handling.
* fix(provider-list): prevent keydown event from triggering when default is prevented
* fix(provider-preset): keep preset clickable after search and restore keyboard UX
- ProviderList: scope the typing guard to the Ctrl/Cmd+F branch so Escape
still closes the search panel (the top-level early return swallowed it);
reuse isTextEditableTarget instead of re-implementing the check.
- ProviderPresetSelector: drop the rAF select() that raced with typing and
ate the first character (gateway -> ateway), and restore the input
autoFocus for the open-by-click path; refocus via rAF when Ctrl/Cmd+F is
pressed while the box is already open so focus returns to the input.
- Add a regression test for the re-focus-on-shortcut behavior.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix(terminal): detect user default shell instead of hardcoded bash
All macOS/Linux terminal launch functions in misc.rs hardcoded `bash` as
the execution shell, causing zsh users' .zshrc to not load. Now detect
the user's default shell via $SHELL (macOS fallback /bin/zsh, Linux
fallback /bin/bash) and use it consistently across shebang, AppleScript,
spawn args, and interactive shell flags.
- Add get_user_shell(), get_shell_name(), interactive_flags_for_shell()
- Replace 10 hardcoded bash locations with dynamic shell detection
- zsh gets --norcs --no-globalrcs, bash keeps --norc --noprofile
- run_tool_lifecycle_silently unchanged (bash-specific syntax)
- Fixes#1546, Related #2385
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(terminal): use POSIX sh for script execution, user shell for interactive session
Address code review issues:
1. Replace shell-specific `read -n 1 -s`/`read -k 1 -s` with POSIX `read -r _`
(press Enter to close) — ensures portability across bash/zsh/fish/dash
2. Unify AppleScript to use `sh` consistently (not mixing shell/shell_name)
3. Protect fish users: scripts always interpreted by POSIX sh, not fish
(fish doesn't support trap/exec). exec line switches to user's shell.
4. Use build_exec_line to avoid trailing space when interactive_flags is empty
All terminal launchers now execute scripts via `sh`. Script shebangs are
`#!/bin/sh`. The exec line at script end switches to the user's detected
shell with appropriate clean-start flags. Terminal launcher functions no
longer need shell/shell_name parameters — simplified signatures.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: validate user shell for terminal launch
* fix: load user shell config after terminal command
* test: align terminal shell exec expectations
* refactor(terminal): remove dead code, quote Warp path, unify shebangs
- Remove `interactive_flags_for_shell` (always returned "") and
`get_shell_name` (no longer needed after removing flags); simplify
`build_exec_line` to single-arg form
- Quote the Warp launcher script path with `shell_single_quote` for
consistency with the main launcher scripts
- Unify all script shebangs to `#!/usr/bin/env sh` for better
portability (e.g. NixOS where /bin/sh may not exist)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(terminal): fix linux shell exec helper call
* fix(terminal): run provider command through user shell
* fix(terminal): validate user shell executable
* fix(terminal): avoid interactive provider shell job
* fix(terminal): harden user shell launcher
Load zsh provider commands through the user's login and interactive environment, preserve launch cwd handling, and replace Terminal/iTerm launcher shells so the final user shell is restored cleanly.
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat(session-manager): show source file name in session detail header
Display the session log file name (with full path on hover) alongside
the project directory, so users can locate and copy the underlying
JSONL file directly from the UI.
* fix(session-manager): truncate long source file name in detail header
Long, space-less JSONL basenames (e.g. Codex rollout files at ~70 chars)
overflowed the flex meta row and bled into the action-button area on
narrow windows. Mirror the sibling project-dir span by capping the
filename at max-w-[200px] with truncation; the full path stays available
via the hover tooltip and click-to-copy.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix(codex): restore cached tool call fields
* refactor(codex): merge duplicate enrich loops in chat history
enrich_call_item_from_cache copied the fill-if-empty loop for
reasoning_content/reasoning. The two loops are identical and key
order is irrelevant, so fold both key sets into a single loop.
Pure refactor, no behavior change; codex_chat_history tests pass.
---------
Co-authored-by: Jason <farion1231@gmail.com>
DeepSeek's Anthropic-compatible endpoint rejects requests where
thinking.type=disabled coexists with effort parameters, returning
HTTP 400. This breaks Claude Code 2.1.166+ sub-agents (Workflow/Dynamic
Workflow), which hardcode thinking:disabled.
Rather than overriding thinking:disabled, remove the conflicting effort
parameters (output_config.effort / reasoning_effort) to respect Claude
Code's intent — sub-agents don't need to display reasoning.
Fixes: https://github.com/deepseek-ai/DeepSeek-V3/issues/1397
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New zh/en/ja guide for the unified Codex session-history toggle: what opt-in migration (on enable) and ledger-based restore (on disable) actually do, why session data is never truly deleted (tag-only rewrite + automatic backups), and how to verify files on disk versus just being filed under another provider drawer. Includes a symptom reference table for the common "my sessions are gone" misunderstanding and on-disk verification commands for macOS/Linux/Windows.
Link the guide as the lead item in the "Usage Guides" section of the v3.16.3 release notes (zh/en/ja).
- Replace activity/agentplan links with the new codingplan invite URL
(ac=MMAP8JTTCAQ2, rc=6J6FV5N2) across all 6 app presets
- Refresh partnerPromotion copy in all 4 locales: two-month 75% off plus
invite code 6J6FV5N2; correct product name from Agent Plan to Coding Plan
- README: swap the CN-mainland redirect link in EN/DE, refresh the ZH promo
copy, and add the redirect link to the JA BytePlus entry
Remove the gold star badge and the partner promotion banner for MiniMax by
deleting the isPartner flag from all 12 MiniMax presets (cn + en) across
claude, claude-desktop, codex, opencode, openclaw, and hermes.
Both the preset-selector star (gated on isPartner) and the API-key promotion
banner (gated on isPartner && partnerPromotionKey) disappear as a result.
The partnerPromotionKey and the minimax_cn/minimax_en i18n copy are kept
dormant so the partnership can be re-enabled with a single line if needed.
MiniMax stays as a regular cn_official provider, keeping its icon and theme.
Codex ships as an npm platform-dispatch package (JS launcher @openai/codex + per-platform binary optional deps like @openai/codex-darwin-arm64). The upgrade chain ran `<bin>/codex update || <bin>/npm i -g @openai/codex@latest`, which can leave codex throwing "Missing optional dependency @openai/codex-darwin-arm64":
- `codex update` on an npm install is a bare `npm install -g @openai/codex` that prints success and exits 0 even when the platform binary fails to land, short-circuiting the `||` npm fallback.
- The npm fallback is a no-op when version==latest and only targets the main package, so it can never re-land the missing platform binary.
Fixes: (1) remove codex from prefers_official_update (Posix+Windows) so npm-managed codex no longer runs the false-success `codex update`; (2) add a runnable=false gate in installs_anchored_command emitting an uninstall+install self-heal — the only repair that re-lands the platform binary; (3) narrow by source/real to npm-managed sources (nvm/fnm/mise/homebrew, non-brew-formula) so broken brew-formula/volta/bun installs fall back to their own anchored commands instead of being mis-repaired with npm. Reuses the existing enumerate runnable signal; no new FS probing.
Settings uses Radix tabs, which unmount inactive tab content. Every time
the About tab was reopened, AboutSection remounted and its mount effect
re-ran all six tool version probes (a `--version` subprocess plus an
npm/github/pypi request each) -- wasteful, since versions rarely change
within a session and a manual Refresh already exists.
Add a module-scoped cache (lives for the app session, survives
unmount/remount) with a 10-minute TTL:
- on remount within the TTL the cached results are reused, skipping all
probes; state is lazily initialized from the cache so the first paint
shows the values with no loading flash
- a stale cache shows the old values immediately and revalidates per
tool in the background (stale-while-revalidate)
- the Refresh button forces a re-probe; single-tool refreshes (shell
change / post-update) update cached data without resetting the TTL
- cold-cache entries start at at=0 (a "not yet fully loaded" sentinel)
so a partial cache left by a mid-probe tab switch is treated as stale
and re-fetched rather than served as if complete; the real timestamp
is only stamped once a full load finishes
The app's own version is cached too, purely to avoid a loading flash on
remount.
getVersion() is a local, millisecond call but was awaited together with
loadAllToolVersions() in a single Promise.all, so setVersion and
setIsLoadingVersion only fired after all six tool checks finished. The
version badge under the app icon therefore waited for the whole batch.
Split the mount effect into two independent chains: loadAppVersion sets
the app version (and clears its loading flag) the moment getVersion()
resolves, while loadAllToolVersions runs its own progressive fan-out.
The two no longer block each other.
Fan out the six tool version probes concurrently instead of awaiting
one sequential batch, so each tool card updates the moment its own
check finishes rather than waiting for the slowest one.
- loadAllToolVersions now calls refreshToolVersions per tool via
Promise.all, reusing the existing per-name merge and per-tool
loading flags
- card loading derives from per-tool state
(loadingTools[t] || (isLoadingTools && !resolved)) so a resolved
card leaves loading independently while the batch is still in flight
- WSL shell/flag selects reuse the per-card loading flag, matching the
install/update buttons' early-enable behavior
This also drops total wall time from the sum of six sequential probes
to the slowest single probe. get_single_tool_version_impl is already
isolated and read-only (shared HTTP client, spawn_blocking subprocess,
no shared state), so concurrent probes are safe.
- useUsageQuery: retry once + keep-last-good — show the last successful
result for up to 10min when a query fails transiently (network/timeout/
HTTP 5xx), so a single blip no longer flips the card to red. Deterministic
failures (auth, empty key, unknown provider, 4xx) surface immediately and
clear the snapshot so a stale quota can't resurface after credentials change.
- bump native balance/coding-plan/subscription request timeouts 10s -> 15s
for slow cross-border endpoints.
- coding_plan: return explicit errors ("API key is empty" / "Unknown coding
plan provider") instead of a blank failure, mirroring balance.
- add unit tests for keep-last-good and transient/deterministic classification.
Display the Fable 5 Verified banner to the right of the app name and version block on the settings About page, marking this as a special build. Center the version badge under the app name so the two rows share a common axis.
The app-side asset lives at src/assets/fable5-verified.png (imported via the @ alias and bundled by Vite); the original source banner is kept under assets/partners/banners/ alongside the other partner banners.
Official providers (Claude/Codex/Gemini/Claude Desktop) use OAuth with an intentionally empty base_url and connect via the client's default endpoint, so cc-switch has no reliable reachability target. Probing a guessed endpoint either hits the wrong target or returns a meaningless green light. Hide the connectivity button for category === 'official'; reachability stays available for copilot/codex-oauth/third-party/custom providers, which is where the old real-request probe produced false negatives. Revert the official base_url fallback added earlier — resolve_base_url is back to extract-or-error.
The 1500ms degraded threshold was too strict; normal ~1s probe latencies showed as 'slow'. Restore the original 6000ms scale (default + config panel + per-provider range). Keep the reachability-appropriate 8s timeout / 1 retry.
The provider panel health check sent a real streaming model request, which many third-party providers block (401/403/WAF), causing false negatives while only stable official endpoints passed. Replace it with a lightweight reachability probe: GET the provider base_url and treat any HTTP response (200/4xx/5xx) as reachable; only DNS/connect/TLS/timeout count as failure. Latency is the probe's TTFB.
Backend (services/stream_check.rs): rewrite ~2200 -> ~350 lines, dropping real-request building, format conversion, auth and API-path resolution while keeping per-app base_url extraction. Defaults: 8s timeout, 1 retry, 1500ms degraded threshold.
Failover invariant: the reachability check must never reset the circuit breaker (reachable != usable; a 403 host is reachable but broken for real traffic). Remove the resetCircuitBreaker call from useStreamCheck; failover failure detection stays driven solely by real proxy traffic (forwarder/circuit_breaker untouched). useResetCircuitBreaker is kept dormant for a future manual-recovery entry.
Open the check to all providers: drop the official/copilot/codex-oauth/third-party gating and the 'sends a real request' confirm dialog. For official providers whose base_url is intentionally empty, fall back to the endpoint the client actually uses (Claude -> api.anthropic.com, Codex -> chatgpt.com/backend-api/codex, Gemini -> generativelanguage). Non-official providers with a missing base_url still error to avoid a false green light. Claude Desktop Official is native 1P mode (talks to claude.ai, cc-switch not in the request path, no reliable endpoint) so its button stays hidden.
Slim StreamCheckConfig and per-provider testConfig to timeout/threshold/retries (drop test model + prompt); sync zh/en/ja/zh-TW. Retain the now-unused anthropic_to_openai/anthropic_to_gemini transform utilities and their test suites.
* 调整预设供应商按钮外观与搜索框位置
1. 调整预设供应商按钮外观,显示默认图标,大小统一;
2. 调整预设供应商搜索框位置。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test(provider): 新增预设按钮外观与 inline 搜索的单元测试
覆盖:
1. 所有预设按钮固定 200px 宽度,视觉对齐一致
2. preset.icon 存在时按钮内渲染 ProviderIcon
3. preset 无 icon 且无 theme.icon 时渲染占位元素保持文字对齐
4. 点击放大镜 inline 切换搜索输入框可见性,ESC 收起并清空
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(provider-preset): responsive grid layout and search polish
- Replace fixed-width preset buttons with a responsive CSS grid (auto-fill, 150px min column)
- Add a leading placeholder to the custom button so its label aligns with iconed presets
- Close the inline search box on outside click, restoring the old Popover behavior
- Span the empty-state hint across the full grid row
- Update component tests for the new layout and behaviors
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
The Kimi For Coding preset was removed (74104946) because the coding
endpoint (api.kimi.com/coding/v1) enforces a User-Agent whitelist that
rejects Codex's default codex-cli UA with 403. The provider-level custom
User-Agent feature now lets users override the UA (to claude-cli/*) under
proxy takeover, so the preset can be restored.
- Re-add the Codex Kimi For Coding preset (openai_chat, kimi-for-coding,
256K context) in the same position it was removed from.
- Enable thinking mode by default via codexChatReasoning (supportsThinking
true, thinkingParam "thinking"), mirroring the general Kimi preset since
both target the same Moonshot model family. The proxy injects
thinking:{type:enabled} when Codex requests reasoning.
- Restore the trilingual user-manual row to "Kimi / Kimi For Coding".
Note: this preset requires proxy takeover and a whitelisted custom
User-Agent to work; the default codex-cli UA still gets 403.
Update the Codex chat preset test's Kimi expectation (kimi-k2.6 -> kimi-k2.7-code) after the Kimi K2.7 upgrade, update the Claude Desktop form test for the four-tier (Sonnet/Opus/Fable/Haiku) routes, and reformat UsageDateRangePicker imports (prettier).
cc-switch-main/src/config/universalProviderPresets.ts was an outdated duplicate of the real preset file, introduced by mistake in #667 and referenced by no code. Remove the orphaned nested directory.
Add kimi-k2.7-code pricing seed (in $0.95 / out $4.00 / cache-read $0.19
per 1M tokens, 256K context) and point all six official Moonshot Kimi
presets (Claude Code, Codex, Claude Desktop, Hermes, OpenCode, OpenClaw)
at the new model. Rename the version-tagged OpenCode/OpenClaw presets to
"Kimi K2.7 Code" and correct the OpenClaw context window to 262144.
The seed is applied via the idempotent INSERT OR IGNORE path that runs on
every startup, so existing users pick up the new pricing without a schema
migration. Kimi For Coding and Nvidia presets are intentionally untouched.
The preset search text also included websiteUrl and the shared category label, producing imprecise matches: a single category term matched the whole group, and URL fragments like "com"/"api" matched nearly everything. Restrict the search text to the display name and raw name; category labels are still used for rendering.
PopoverContent and TooltipContent used z-50, below FullScreenPanel's z-[60] opaque overlay. Popovers such as the provider preset search therefore rendered behind the panel and looked unresponsive on click. Bump both to z-[100], matching the select dropdown: above fullscreen panels, below modal dialogs (z-[110]).
The menu display name and request model columns used mismatched example
brands (DeepSeek vs Kimi), implying a display name maps to an unrelated
request model. Derive both placeholders from the row role so each row
stays brand-consistent, and route the lightweight Haiku tier to a flash
example (deepseek-v4-flash) while other tiers use deepseek-v4-pro.
- Wire claude-fable-5 as a fourth tier on both proxy paths, with a
fable -> opus -> default fallback mirroring the official downgrade.
- Whitelist the fable- prefix for the Desktop 1.12603.1+ validator.
- Clarify fallbackModelHint (zh/en/ja/zh-TW): a blank tier on
third-party endpoints forwards the literal model name and 404s.
Refs #3980, #4026, #4049.
- Reduce all four filter controls to w-[100px] h-9
- Add ChevronDown icon to date picker trigger for consistency
- Suppress focus border highlight on select triggers after close
- Enable dialog gains a checkbox (default off) to migrate existing
official sessions from the built-in "openai" bucket into the shared
"custom" bucket, with per-generation backups; failed migrations retry
at startup
- Disable dialog offers a precise restore driven by the backup ledger:
only sessions recorded as "openai" in backups are flipped back, and
sessions created while the toggle was on are never touched
- Completion marker and backup generations are bound to the canonical
Codex config dir; migrate/restore serialize on an op lock and the
marker is written conditionally inside the settings write lock
- save_settings rolls back the toggle and fails the save when the live
rewrite fails; migration additionally requires the live config to
actually route to the shared bucket (skips with live_not_unified so
refused injection or proxy takeover can't split history)
- Restore refuses to run while the toggle is (re-)enabled and reports
nothing_to_restore instead of a zero-count success; local migration
markers are now backend-owned in merge_settings_for_save so stale
frontend payloads can't resurrect them
- Settings autosave reverts optimistic form state on failure so a
failed toggle change can't be replayed by an unrelated save
- ConfirmDialog supports an optional checkbox; all four locales updated
Codex buckets resume history by the model_provider id recorded in each
session: official runs (no key, built-in "openai") and cc-switch
third-party runs (shared "custom") are mutually invisible in the resume
picker. Add an opt-in setting that runs official providers under the
shared "custom" id so future official sessions land in the same history
bucket as third-party ones. Forward-only by design: existing sessions
are not migrated.
When enabled, official live config.toml gets model_provider = "custom"
plus a [model_providers.custom] entry that mirrors the built-in openai
provider (requires_openai_auth routes auth to the ChatGPT login in
auth.json, name "OpenAI" keeps is_openai() feature gates, explicit
supports_websockets/wire_api restore built-in defaults). auth.json is
untouched.
Key invariants:
- Injection lives only in the live config: switch-away backfill strips
the exact injected shape, so stored provider configs stay clean and
turning the toggle off fully reverts on the next write.
- Toggle changes apply immediately via a takeover-aware reapply: when
the proxy owns the live config (backup/placeholder present), only the
live backup is updated, mirroring the provider-switch path.
- The takeover backup path runs the same injection so a takeover
release restores a config that still carries the unified routing.
- Injection refuses to activate a foreign [model_providers.custom]
table (e.g. stale entry with a third-party base_url) to avoid routing
ChatGPT OAuth traffic to an unknown backend.
The toggle lives under Settings → Codex App Enhancements; the
description warns that resuming old sessions across providers may fail
because encrypted_content reasoning only decrypts on the backend that
created it (upstream treats cross-provider resume as unsupported).
- LemonData: delete the provider preset from all apps (claude, claude-desktop,
codex, gemini, hermes, opencode, openclaw), remove partner promotion copy
(zh/en/ja/zh-TW), icon index/metadata entries, sponsor ads in all READMEs,
and the logo/icon PNG assets.
- SudoCode: drop the isPartner flag and partnerPromotionKey across all app
presets and remove the now-orphaned partner promotion copy; it stays as a
regular third_party provider, keeping its icon.
Replace the click-to-cycle refresh button with a Select matching the
source/model filters. The "off" option now shows a localized label
(zh/en/ja/zh-TW) instead of the cryptic "--", and changing the interval
still invalidates all usage queries for an immediate refresh.
Align the top-bar controls into two width groups: source and model
selects at 120px, refresh select and date range trigger at 150px. The
date range button moves from auto width to fixed, so its long custom
range label gets truncate + hover title, and the calendar icon is
shrink-proofed.
The provider/model filters only lived inside the request-log table, so
there was no way to see "how much did app X spend on source Y" across
the whole dashboard. Promote them to the top bar next to the app
filter, applying globally to the hero summary, trend chart, request
logs, and both stats tabs.
Backend: the five stats queries (summary, summary-by-app, trends,
provider stats, model stats) accept optional provider_name/model
filters, applied to both the detail and daily-rollup branches (the
rollup PK already carries provider_id/model/pricing_model). Sources
match by exact display name via provider_name_coalesce, so session
placeholder rows like "Claude (Session)" are selectable; models match
by effective pricing model (pricing_model falling back to model), the
same grouping key the model-stats tab uses. Request-log filtering
switches from LIKE to these exact semantics.
Frontend: two truncating dropdowns list only sources/models that have
data in the current range, with the model list cascading from the
selected source. Dynamic option values are prefix-encoded so a source
literally named "all" cannot collide with the sentinel, query keys
fall back to null instead of "all", and the option queries follow the
dashboard refresh interval (otherwise their default 30s polling drags
same-key stats queries along even with refresh disabled). The
request-log filter bar keeps only the log-specific status-code select.
Labels read "sources" rather than "providers" because direct-connect
session buckets sit alongside real providers. i18n updated across
zh/en/ja/zh-TW.
The text tabs (All / Claude Code / Codex / Gemini / OpenCode) wrapped
awkwardly in narrow windows. Render app icons via ProviderIcon instead,
mirroring AppSwitcher's icon mapping (codex reuses the openai icon),
with a LayoutGrid icon for "all". Localized labels are kept as
title/aria-label on each button.
The Desktop gateway's proxy traffic is still recorded under its own
app_type for route-takeover billing audit (the request detail panel
shows the real value), but the dashboard now folds it into `claude`
for display. A standalone "Claude Desktop" bucket only ever showed a
partial number: Desktop chat usage never passes through the proxy and
has no scannable local source, while its Code-tab sessions are just the
embedded Claude Code runtime writing into the shared ~/.claude/projects
tree — so a separate bucket misled users into reading it as Desktop's
full usage.
Backend: new `folded_app_type_sql` helper wraps the app_type column in
every dashboard read path (10 filter sites + the by-app projection) so
`= 'claude'` also matches claude-desktop and GROUP BY merges the two,
without changing bound-param counts. Dedup matching and provider-limit
checks keep exact comparison; get_request_logs folds only the WHERE
filter and keeps the raw app_type in its row projection.
Frontend: drop claude-desktop from the dashboard AppType/KNOWN_APP_TYPES
filter list, the UsageHero theme, and the now-dead appFilter locale key
in all four languages (the managed-app apps.claude-desktop label stays).
Adds test_claude_desktop_folds_into_claude_for_display.
* fix(proxy): preserve Codex OAuth auth token on takeover
* style(proxy): format Codex OAuth takeover fix
* fix(proxy): unconditionally inject AUTH_TOKEN placeholder for codex takeover
The preserve-if-exists condition left #3784 unfixed on three paths:
hot-switch passes the provider's settings (presets carry no
ANTHROPIC_AUTH_TOKEN key), fresh installs never had the key, and live
configs already stripped by older releases stay stripped.
- Fold the bool parameter into the policy enum as
ManagedAccount { keep_auth_token } so every construction site
declares intent
- Decide via !is_github_copilot() within the managed branch so
URL-only codex providers (no provider_type meta) are covered,
matching the predicate family used for policy selection
- Inject the placeholder unconditionally instead of only when the
key pre-exists; Copilot behavior is unchanged (API_KEY only)
- Pin the previously uncovered cases with tests: codex without a
pre-existing key, URL-only codex, and Copilot removing a stale
AUTH_TOKEN
---------
Co-authored-by: codeasier <liuyekang@huawei.com>
Co-authored-by: Jason <farion1231@gmail.com>
Insert a new 'claude-mythos-5' model tuple into src-tauri/src/database/schema.rs. The tuple ("claude-mythos-5", "Claude Mythos 5", "10", "50", "1.00", "12.50") is added to the models list (placed before the Claude 4.8 series) to register the Mythos 5 model with the Database schema.
Changing app_config_dir relocates the SQLite database, so a restart
triggered while proxy takeover is active used to strand the live
configs: the new instance reads a fresh DB with no live backups, the
first-run import then persisted the PROXY_MANAGED placeholder as the
`default` provider, and the no-backup recovery path wrote that
placeholder right back to the live files — leaving Claude/Codex/Gemini
pointed at a dead local proxy with no automatic way out.
Three orthogonal fixes, defense in depth:
- restart_app now awaits cleanup_before_exit() before app.restart().
Since #4069 the ExitRequested handler intentionally defers restart
requests to Tauri's default re-exec without custom cleanup, which is
correct for same-DB restarts but not for this command's dir-change
use case: only the old instance holds the backups needed to restore
the taken-over live files, so it must restore them while its event
loop is still alive.
- import_default_config refuses to import a live config that is under
proxy takeover (placeholder detected), instead of persisting it as
the current provider.
- restore_live_from_ssot_for_app validates that the current provider's
settings_config does not itself contain takeover placeholders before
writing it back; polluted SSOT now falls through to the placeholder
cleanup fallback.
Regression tests cover the import guard and the no-backup recovery
path (the latter fails before this change by writing PROXY_MANAGED
back to live).
* fix(updater): drive download/install/restart from backend to avoid hang
The 3.16/3.16.1 update flow was frontend-driven: downloadAndInstall()
then relaunchApp(). relaunch() routed through AppHandle::restart(), and
the old WebView had to keep running JS after the .app bundle was already
swapped — an unstable window that could hang the update or leave the old
version running until a manual restart.
Move the whole download -> install -> cleanup -> restart chain into a new
backend command install_update_and_restart, so it no longer depends on the
old WebView running JS after the bundle is swapped.
Platform-aware install ordering (install() behaves differently per OS):
- Windows: install() launches the external installer and exits the process
internally, so cleanup + single-instance destroy must run before install.
Surface a recovery hint on failure since the proxy may already be stopped.
- macOS/Linux: install() returns, so install first then cleanup — an install
failure no longer wrongly stops the proxy / reverts takeover.
Eliminate the restart vs single-instance race: restart_process() destroys
the single-instance lock (remove socket on macOS, ReleaseMutex on Windows)
before tauri::process::restart(), so the freshly spawned process can't
connect to the old listener and exit itself.
Also remove the now-dead frontend update plumbing (relaunchApp,
UpdateHandle, mapUpdateHandle) and surface backend errors in the toast.
Adapted from the original af4271f4 while rebasing onto #4069: the
ExitRequested handler changes were dropped entirely — the classifier from
#4069 already routes RESTART_EXIT_CODE to Tauri's default restart flow,
and the original should_restart branch (prevent_exit + async cleanup)
would have reintroduced the window-state deadlock that #4069 fixed.
install_update_and_restart bypasses ExitRequested entirely, so the two
fixes compose cleanly.
* fix(updater): clear tray icon on the direct-restart update path
restart_process re-execs via tauri::process::restart (spawn + exit(0)),
which skips Tauri's internal cleanup_before_exit and all RunEvent::Exit
plugin hooks. Window state, proxy/live restore and the single-instance
lock were already compensated explicitly; the tray icon was not.
On macOS/Linux the OS drops the status item when the process dies, so
the gap there was cosmetic at most. The real residue risk is the
Windows branch, which never reaches restart_process at all:
update.install() exits the process inside the updater plugin
(std::process::exit(0)), bypassing TrayIcon::drop — no NIM_DELETE is
sent and a stale icon lingers in the shell until hovered, the same
failure remove_tray_icon_before_exit was originally added for on the
quit path.
Call remove_tray_icon_before_exit (set_visible(false), proxied to the
main thread via run_item_main_thread) in restart_process and before
the Windows install. Deliberately not AppHandle::cleanup_before_exit():
it drops tray icons on the calling thread, which is not safe off the
main thread on macOS (NSStatusItem).
The updater's relaunch() (and app.restart()) triggers ExitRequested
with code RESTART_EXIT_CODE, which the handler treated as a regular
exit: it called api.prevent_exit() and spawned an async cleanup task.
However Tauri silently ignores prevent_exit() for restart requests
(see ExitRequestApi::prevent_exit docs), so the event loop keeps
shutting down regardless and fires every plugin's RunEvent::Exit hook.
Two threads then deadlock:
- the spawned cleanup task runs save_window_state on a tokio worker,
holding the window-state plugin's internal mutex while querying
window geometry, which dispatches to the main thread and blocks;
- the main thread, already inside the plugin's own RunEvent::Exit
hook, blocks on that same mutex.
The app freezes forever on the restarting screen with the update
already installed; force-quit + reopen comes back on the new version
(#3998). Confirmed on macOS by sampling the frozen process: main
thread parked in tauri_plugin_window_state save_window_state mutex
lock, tokio worker parked in is_maximized -> mpsc recv.
Fix: classify exit requests (None / restart / user exit) and let
restart requests fall through untouched to Tauri's default restart
flow (RunEvent::Exit -> re-exec). On that path window state is saved
by the plugin's exit hook on the main thread, the tray icon is
cleaned up by Tauri's internal cleanup_before_exit, and proxy/live
config restore is unnecessary because the new instance takes over
immediately. The regular exit path (tray quit) is unchanged.
With the fix, a simulated updater relaunch (request_restart) re-execs
the new process in under 200ms, 3/3 runs; normal quit still performs
full cleanup.
Co-authored-by: thisTom <19346741+thisTom@users.noreply.github.com>
Add Unity2.ai, a high-performance AI API relay partner, as a preset for
Claude, Codex, Gemini, OpenCode, OpenClaw, Claude Desktop, and Hermes.
Each preset carries the referral signup link as apiKeyUrl.
- Register the unity2 icon via iconUrls (PNG URL import) + metadata
- Add partnerPromotion copy in zh/en/ja/zh-TW; backfill the missing
zh-TW ccsub entry
- List Unity2.ai in the sponsor section of all README locales
- Codex uses the bare base URL (gateway exposes /responses at root);
OpenCode/OpenClaw/Hermes use the /v1 chat-completions endpoint with
gpt-5.5 as the only preset model
- Trim CCSub OpenCode/OpenClaw/Hermes model lists to gpt-5.5 to match
- Normalize unity2/ccsub banners to the standard 2.41 aspect ratio
The Claude/Codex format-transform non-stream branch returned an opaque 422
"Failed to parse upstream response" whenever a 2xx upstream body was not
valid JSON. The common case: MaaS gateways force-stream a stream:false
request and return an SSE body with a non-SSE Content-Type, defeating the
header-only is_sse() check.
On serde failure, sniff for SSE and aggregate the chunks into a single
JSON, then run the existing converter so clients still receive a valid
non-stream response.
- chat_sse_to_response_value: aggregate chat.completion.chunk SSE
(content / reasoning / refusal / tool_calls / legacy function_call),
tool_calls index-keyed via BTreeMap to avoid unbounded densification,
first-wins finish_reason, message-snapshot override, completeness and
error-event guards; synthesize an id when the upstream omits one
- responses_sse_to_response_value: process the residual trailing block,
tolerating truncation and skipping it once a completed event was seen
- enrich remaining parse failures with content-type / content-encoding /
body-snippet diagnostics
- deflate: try zlib (RFC 9110) before raw; keep the content-encoding
header for unsupported encodings
- gate zero-usage rows on the Claude transform path
- Fold local routing toggle, model mapping, reasoning overrides and custom
User-Agent into a single collapsible advanced section, mirroring the
Claude form (auto-expands when UA is set or local routing is enabled)
- Custom User-Agent becomes configurable for native Responses providers;
it was previously reachable only when openai_chat routing was on
- Collapsed hint names local routing as the entry point for Chat
Completions / non-GPT providers
- Backfill all missing codexConfig keys in zh-TW locale
Polish the provider-level User-Agent override UI on the Claude and Codex forms.
- Add a shared CustomUserAgentField (label + input + preset dropdown + live
validation) so both forms stay in sync.
- Provide curated UA presets (Claude Code / Kilo Code families that pass
coding-plan UA whitelists per #3671); the first is Claude Code's real
`claude-cli/x (external, cli)` format. Whitelists gate on the name prefix,
not the version, so static values stay valid across upgrades.
- Expose presets via a dropdown to the right of the input (z-[200] so it
renders above the dialog layers) instead of inline chips.
- Move the field into the existing advanced/reasoning collapsibles.
- userAgent.ts mirrors the backend byte rule (reject only control chars;
non-ASCII is allowed) for a non-blocking inline hint.
- i18n for all four locales (zh/en/ja/zh-TW).
Extract a shared `parse_custom_user_agent` helper in provider.rs returning
`Result<Option<HeaderValue>>`, and reuse it in the forwarder, stream check,
and model fetch paths so detection, forwarding, and model listing all apply
the same provider-level User-Agent. Previously only the forwarder honored it,
so stream check could fail (or model listing 403) on UA-gated upstreams that
the proxy itself handled fine.
- stream_check injects the provider's custom UA on the claude/codex paths and
still skips the GitHub Copilot fingerprint UA.
- model_fetch service + command and the model-fetch.ts wrapper thread an
optional UA through to GET /v1/models.
- runtime callers silently ignore invalid values via `.ok().flatten()`
(no save-time block, so deeplink imports stay lenient).
- add claude-desktop to AppType/KNOWN_APP_TYPES and the dashboard app
filter; it was hidden because its rows looked like pure failure
noise, which was the app_type attribution bug fixed on the backend
- request detail panel now shows the requested model and the pricing
model when they differ from the response model, making route-takeover
bills auditable from the UI
- locale keys added for zh/en/ja/zh-TW
The model mapped for takeover (env mapping, Claude Desktop routes,
Copilot normalization, Codex chat override) was discarded inside the
forwarder, so usage attribution depended entirely on the upstream
echoing it back. When the upstream omitted the model or mirrored the
client alias, kimi/glm tokens were recorded and priced as claude-*
(roughly 5-25x overstatement).
- capture the final outbound model in forward(), return it via
ForwardResult, and store it on the request context
- attribution fallback order is now: upstream echo (empty string
treated as missing) -> outbound model -> client-requested model
- 'request' pricing mode anchors to the outbound model instead of the
pre-mapping client alias; unchanged when no mapping applies
- persist the resolved pricing_model on every usage row
- Claude Desktop rows now log app_type "claude-desktop" on streaming
and transform paths too (was hardcoded "claude", silently dropping
desktop provider pricing overrides and splitting the cost basis by
the stream flag); its global pricing defaults inherit the claude
config since proxy_config only allows claude/codex/gemini rows
- proxy_request_logs: add pricing_model column recording the basis actually
used at write time (NULL = pre-v11 rows, '' = unpriced error rows)
- cost backfill recomputes strictly by the persisted basis; the
request_model fallback now only applies to placeholder models, so
real-but-unpriced takeover rows stay at zero cost until pricing is
added instead of being permanently frozen at the alias's price
- backfill_missing_usage_costs_for_model can locate rows by pricing_model
- usage_daily_rollups: rebuild with request_model + pricing_model in the
primary key so the alias-to-real-model mapping and the pricing basis
survive the 30-day prune; legacy rows migrate with ''
- rollup_and_prune backfills costs before pruning: prune is irreversible
and used to run before the startup backfill, permanently booking
then-unpriced rows as zero
- get_model_stats groups by the effective pricing model
(COALESCE(NULLIF(pricing_model,''), model)) so costs aggregate under
the model whose prices produced them; response-mode behavior unchanged
The Zhipu quota API returns two TOKENS_LIMIT entries whose identity was
inferred by sorting nextResetTime ascending (nearest = five_hour). In the
last hours of each weekly cycle the weekly window resets sooner than the
current 5-hour session window, so the two buckets were swapped exactly
when users check their weekly quota most.
Classify by the explicit unit field instead (3 = hour window -> five_hour,
6 = week window -> weekly_limit; same shape on bigmodel.cn and api.z.ai,
weekly observed with number 7 and 1 so only unit is matched), falling back
to the old reset-time heuristic when the field is missing.
- Replace the fixed Zap glyph in the usage hero with the selected app's
brand icon via a new AppGlyph component, reusing APP_ICON_MAP
(cloneElement scales 14px -> 20px); falls back to Zap for the "all" view.
- Recolor the Codex title theme from emerald to neutral gray to match
OpenAI's monochrome branding. neutral-500/10 stays visible in both
light and dark modes, unlike a flat black tint.
Codex /responses requests routed to text-only OpenAI-chat upstreams
(e.g. DeepSeek deepseek-v4-flash) failed with HTTP 400 "unknown variant
image_url" when images were sent: the responses->chat conversion turns
input_image items into image_url blocks the model rejects. The media
rectifier previously covered only the Claude adapter, so neither the
proactive strip nor the reactive retry fired for Codex.
- media_retry_should_trigger: accept "Codex" adapter, not just "Claude"
- contains_image_blocks / replace_images: also scan responses `input`
(input_image) in addition to chat `messages`
- is_image_block_type: match image | image_url | input_image
- is_unsupported_image_error: add "unknown variant" hint for the
deserialize error
- forward(): proactively run apply_media_prevention for Codex after the
responses->chat conversion
Proactively strips images for known text-only models (heuristic on by
default) and reactively retries with images replaced on upstream
image-unsupported errors. Adds tests for chat image_url, codex
input_image, the reactive trigger, and the deserialize error match.
Builds on #2774 (which fixed cache_read for the streaming openai_chat path).
Two gaps remained, both double-counting cache tokens when a Claude client
meters as app_type="claude" (input_includes_cache_read=false):
1. cache_read was still added to input on the non-streaming openai_chat path
(transform.rs openai_to_anthropic) and the whole openai_responses family
(transform_responses.rs build_anthropic_usage_from_responses, covering the
non-streaming call site and both streaming_responses call sites).
2. cache_creation was never subtracted on any converted path, including the
streaming openai_chat path #2774 had already touched. Claude billing treats
cache_creation as a separate bucket, so an inclusive upstream carrying a
direct cache_creation_input_tokens field billed it twice.
All four metering points now compute:
input = prompt_tokens - cache_read - cache_creation
restoring the invariant input + cache_read + cache_creation == prompt_tokens.
Pure OpenAI upstreams are unaffected (no cache_creation concept/field).
Tests: update direct-cache assertions (40->20), add a streaming conservation
regression test, and pin prompt<cache underflow (saturating clamp to 0) for all
three metering functions. cargo test 1573 pass, clippy clean.
Note: fix is forward-only; historical rows are not recomputed (cost is frozen at
log time and app_type="claude" mixes native + converted rows).
Audited all proxy format-conversion paths (Chat<->Message, Chat<->Response,
Gemini<->Message) for usage/cache metering. Five issues found and fixed.
The dedup mechanism (request_id PK, proxy/session source isolation) is
untouched, so no double-counting is introduced.
- A (Claude + openai_chat, streaming): inject stream_options.include_usage
so OpenAI-compatible upstreams emit usage in the SSE tail. Without it the
converted Anthropic message_delta was all-zero and the whole request's
input/output/cache was dropped. Same root cause as the already-fixed
Codex Chat path; the injection is extracted into a shared helper
(transform::inject_openai_stream_include_usage) reused by both paths.
- C (Claude + gemini_native): subtract cachedContentTokenCount from
input_tokens in build_anthropic_usage so input becomes fresh input
(Anthropic semantics). Previously the cache-hit tokens were billed twice
because this path meters as app_type="claude" (input_includes_cache_read
= false) while Gemini's promptTokenCount includes the cache.
- D (Codex + openai_chat, streaming): gate log_usage on
has_billable_tokens() to skip the synthetic all-zero usage the converter
emits when a non-compliant upstream omits usage, preventing empty-row
request-count inflation.
- P2 (from_claude_stream_events): use has_billable_tokens() for the return
gate instead of input>0||output>0, so a fully-cached streamed request
(cache_read>0, input==output==0) is still recorded. Affects all
Claude-streaming paths, not just Gemini.
- P3 (Codex Chat->Responses, non-streaming): apply the same
has_billable_tokens() filter the streaming branch got, since the
synthesized all-zero usage makes from_codex_response return Some and
bypass the `if let Some` guard.
Add TokenUsage::has_billable_tokens() as the unified predicate. New tests
cover include_usage injection, gemini input subtraction, the gate itself,
cache-only stream recording, and synthetic all-zero codex usage.
Full lib suite: 1569 passed.
The local session-log scanner dropped any assistant message that lacked
a stop_reason or had output_tokens==0. Claude Code Workflow / sub-agent
fan-out frequently produces messages that only wrote a message_start
snapshot (output=1, stop_reason=None) without a final block, yet their
input + cache_read + cache_creation tokens are already billed by
Anthropic (charged once the request is accepted). Dropping them
under-counted usage by ~4.1% overall, 92% concentrated in
workflow/subagent transcripts.
Replace the stop_reason/output gate with a billable-token check (any of
input/output/cache_read/cache_creation > 0). The per-message-id dedup
selection is unchanged, and request_id = "session:"+msg_id PRIMARY KEY
with INSERT OR IGNORE keeps each message single-inserted, so relaxing
the gate cannot double-count. Add a regression test covering a
stop_reason-less message with real cache cost plus an all-zero skip.
This is the parser-layer half of the Workflow under-counting fixed at
the collector layer in 8d332925.
collect_jsonl_files only walked <project>/<session>/subagents/*.jsonl,
so it missed Workflow sub-agent transcripts which live one level deeper
at subagents/workflows/wf_*/agent-*.jsonl. As a result all Workflow
token usage was invisible to the no-proxy session-log accounting.
Descend into subagents/workflows/wf_*/ as well, via a new
push_jsonl_children helper that keeps the fixed-depth, no-recursion
design. journal.jsonl carries no assistant rows so it is skipped at
parse time and needs no filename special-casing. Existing dedup
(request_id PK + INSERT OR IGNORE + should_skip_session_insert) keeps
the next sync's backfill idempotent.
Add test_collect_jsonl_files_includes_workflow_subagents.
The JS-script usage path resolved {{apiKey}}/{{baseUrl}} with env-only
field guessing, so apps that store credentials elsewhere (Codex:
auth.OPENAI_API_KEY + config.toml base_url) always got empty values and
custom-template queries failed despite a fully configured provider.
- query_usage / test_usage_script now delegate to
Provider::resolve_usage_credentials, the same per-app resolver used by
the native balance/coding-plan path and mirrored by the frontend
getProviderCredentials; explicit non-empty script values still win
- test_usage_script loads the provider and applies the same fallback,
so testing matches what a saved script does
- the custom-template variable preview shows the effective values
(script overrides first, then provider config) instead of always
showing provider credentials
- extract_codex_base_url documents and test-locks the frontend-mirror
invariant: non-active [model_providers.*] sections are never read
Reworked from the original patch to reuse the existing resolver instead
of duplicating per-app extraction.
Co-authored-by: Jason <farion1231@gmail.com>
* fix: prevent duplicate YAML keys in Hermes config
Three changes in hermes_config.rs:
1. deduplicate_top_level_keys() - scan and remove duplicate top-level
keys before YAML parsing, preventing "duplicate entry" parse errors
2. remove_all_sections() - helper to strip all occurrences of a given
top-level key from raw YAML text
3. replace_yaml_section() now calls remove_all_sections() on the
remainder after replacing the primary occurrence, preventing
duplicate sections from accumulating on repeated writes
Fixes the issue where mcp_servers (or any top-level key) gets
duplicated in config.yaml, causing "Failed to parse Hermes config
as YAML: duplicate entry with key" errors.
Co-Authored-By: que3sui <204201112+que3sui@users.noreply.github.com>
* fix: handle CRLF and LF line endings in top-level key deduplication
is_top_level_key_line only accepted empty, space, or tab after the colon,
but deduplicate_top_level_keys uses split_inclusive('\n'), so lines end
with \n (LF) or \r\n (CRLF). Without accepting \r and \n as valid
post-colon characters, the dedup safety net never activates.
Add \r and \n checks to is_top_level_key_line, and three tests covering
LF, CRLF, and first-occurrence preservation.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(hermes): keep last occurrence when healing duplicate YAML keys
Reworks the healing layers on top of the CRLF root-cause fix:
- deduplicate_top_level_keys: keep the LAST occurrence of each duplicated
key instead of the first. Duplicates come from section replacement
degrading into appends (#3633), so the last block is the newest data --
and Hermes itself reads the config with PyYAML, whose duplicate-key
semantics are last-wins. Keeping the first occurrence would silently
roll users back to stale config and diverge from what Hermes runs with.
Healthy files take a fast path and are returned untouched.
- Drop the unused dup_key variable (fails cargo clippy -- -D warnings,
which CI enforces).
- replace_yaml_section: clean residual duplicate sections from the
remainder via remove_all_sections; values come from the keep-last
healed read, so dropping all stale on-disk copies loses nothing.
- Add regression tests for the actual root cause (find/replace on CRLF
input must replace in place, not append), keep-last semantics,
identity on healthy files, end-to-end heal-then-parse, and duplicate
cleanup on write.
Fixes#3633#2973#2529#3310#3762
---------
Co-authored-by: que3sui <204201112+que3sui@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
Problem: Kimi and Moonshot preset links were user-clickable without the cc-switch affiliate query.\n\nDecision: Update only UI-facing preset website/API-key links and leave API request endpoints untouched.\n\nChange: Add aff=cc-switch to Kimi/Moonshot websiteUrl values and Codex/OpenCode API-key links.
Co-authored-by: xumingyuan <xumingyuan@msh.team>
Move the CCSub preset to sit right after DouBaoSeed, at the end of the
partner block and before the first non-partner provider, so its position
is consistent across all six apps:
- Codex / OpenCode: moved up from the 2nd slot (between Shengsuanyun and
the next partner) to the block tail
- OpenClaw / Hermes: moved up from the aggregator section to the block tail
- Claude / Claude Desktop: already at the block tail
Also add the missing CHANGELOG entry for the CCSub preset, and drop the
provider preset order test that enforced a now-unneeded ordering invariant.
Add CCSub, a multi-model aggregator partner, as a preset for Claude, Codex, OpenCode, OpenClaw, Claude Desktop, and Hermes. Each preset carries the referral signup link as apiKeyUrl.
- Register the ccsub icon via iconUrls (1.1MB SVG URL import) + metadata
- Add partnerPromotion copy in zh/en/ja
- List CCSub in the sponsor section of all README locales
- Use gpt-5.5 and gemini-3.1-pro as the OpenAI/Gemini model ids
Add the v3.16.2 CHANGELOG entry covering the 41 commits since v3.16.1,
bump the version across package.json, tauri.conf.json, Cargo.toml, and
Cargo.lock, and add trilingual (zh/en/ja) release notes.
* fix(proxy): strip cache_control from OpenAI format conversion (#3805)
- Remove cache_control passthrough from system messages, text blocks,
and tools to prevent 400 errors on strict OpenAI-compatible endpoints
- Always simplify single text block content to plain string format
- Fixes two format conversion bugs reported in issue #3805
* fix(proxy): apply cargo fmt to fix CI formatting check
The proxy-takeover block previously fell back to the isOfficial heuristic
(empty base_url / missing key) when category was absent. That misjudged
custom providers whose endpoint lives in meta or whose fields are simply
unfilled: their switch button got disabled, making users think the config
was broken. That extra UI block was also "virtual" — the executor in
useProviderActions only ever honored category === "official", so the
front end blocked more than the backend would enforce.
Gate the block solely on explicit category === "official", matching the
executor and unifying both verdicts on a single source of truth.
Also rework the blocked-state UI:
- drop the red "blocked" badge for a plain disabled Enable button
- move title/cursor onto a wrapper span (disabled buttons set
pointer-events:none, so an on-button title/cursor never fired)
- replace the account-ban warning tooltip with a lighter hint
(provider.blockedByProxyHint), four locales kept in sync
Convert Responses input_file (requiring file_id or file_data, never file_url which Chat file parts do not support) and input_audio parts into their Chat Completions equivalents, and handle top-level input_* items that previously fell through and were dropped, clearing stale pending reasoning for non-assistant messages.
Replace the unconditional finalize at chat-to-responses stream end with a three-way guard: complete normally when finish_reason or [DONE] arrived, emit an incomplete response when substantive output exists without a finish_reason, and emit a failed (stream_truncated) event for empty truncation instead of masking it as completed. Also propagate late-arriving reasoning_content onto still-active tool-call items.
Generalize the cross-turn reasoning cache in codex chat history from function_call only to the full tool-call triad (function_call, custom_tool_call, tool_search_call) and their *_output counterparts, so apply_patch and tool-search calls keep their reasoning_content when restored via previous_response_id.
Switch website/apiKey URLs to sssaicodeapi.com and replace base URL
nodes with node-hk.sssaicodeapi.com (default), node-hk.sssaiapi.com,
and node-cf.sssaicodeapi.com across all 7 app presets.
When listen_port is 0 the OS assigns the port at bind time, so the
configured value can no longer be trusted for building takeover URLs.
- server: read listener.local_addr() after bind and propagate the
actual port to the global proxy port, status, and ProxyServerInfo
- services: start the proxy before takeover when port is 0 so live
configs get the real port instead of :0, and persist the resolved
port back to the DB for DB-only URL paths; stop the pre-started
server on any takeover failure
- claude_desktop: reject an unresolved :0 port instead of emitting a
broken gateway URL
- build_proxy_urls: prefer the running server's port and error out if
the port is still 0
Add tests for takeover with an ephemeral port and the claude_desktop
:0 rejection; switch existing codex takeover tests to an ephemeral
port for isolation.
* feat(proxy): add GET /v1/models endpoint for Codex CLI reachability check
Codex CLI probes GET /v1/models at startup. Without this endpoint the proxy
returns 404, causing Codex to fail before any request reaches the upstream
LLM.
Return an OpenAI-compatible model list derived from the cc-switch–managed
model catalog file.
Fixes#3812
* fix(proxy): return Codex catalog schema from /v1/models
Codex deserializes the response as a catalog with a top-level `models`
field, not the OpenAI `{"object":"list","data":[...]}` envelope.
Return the catalog file content directly so the format matches what
Codex expects.
Co-authored-by: Codex review bot
* fix(proxy): guard /v1/models against serving stale catalog
Only return the model catalog when config.toml still references it via
`model_catalog_json`. After switching to a provider without a custom
catalog, the old file lingers on disk — serving it unconditionally
would advertise the previous provider's models to Codex.
Co-authored-by: Codex review bot
* fix(proxy): match relative model_catalog_json in stale-guard
cc-switch writes `model_catalog_json = "cc-switch-model-catalog.json"`
(relative) via set_codex_model_catalog_json_field. Match on the
filename constant rather than the absolute path so the guard works
with both relative and absolute paths.
Co-authored-by: Codex review bot
* fix(proxy): parse model_catalog_json field instead of substring match
Replace raw config_text.contains() with proper TOML field parsing so
commented-out lines and stray mentions of the filename in other fields
don't defeat the stale guard. Also switch from contains() to exact
filename match (Path::new(val).file_name() == Some(...)) to stay
consistent with resolve_cc_switch_catalog_path in codex_config.rs.
Add log::debug! when the guard blocks serving so the operator can
distinguish "no models configured" from "guard blocked stale catalog".
* refactor(proxy): reuse resolve_cc_switch_catalog_path in handle_models
Replace the inline config.toml parsing and filename match in
handle_models with the existing resolve_cc_switch_catalog_path helper
(now pub(crate)). This removes the duplicated stale-guard logic, keeps
a single source of truth for catalog-path ownership, and makes the
handler honor absolute model_catalog_json paths the same way Codex
live-setting import does.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* Fix Codex VS Code session previews
* fix(codex): use last IDE request heading for session previews
A markdown heading inside the active selection / open file could precede the real injected request, so matching the first "## My request for Codex:" heading picked selection content instead of the user prompt. Scan for the last matching heading (the IDE injects the real request as the final section) on both the Rust title path and the frontend TOC preview path.
Add regression tests for the selection-heading case, and pin the known best-effort limitation when the request body itself repeats the heading.
---------
Co-authored-by: Jason <farion1231@gmail.com>
On Windows, Path::strip_prefix produces backslash-separated relative
paths. The update-check matching logic uses rsplit('/') to extract the
install name, so subdirectory skills (e.g. skills/my-skill) never
matched and updates were silently skipped. Replace backslashes with
forward slashes when building the directory string.
Some Anthropic-compatible SSE providers (e.g. qwen, minimax) report the
full context (fresh + cached) as input_tokens in message_start, double
counting the cached portion that is also reported in
cache_read_input_tokens. This inflated the cacheable-input denominator
and pushed the displayed cache hit rate artificially low.
When a message_delta carries a smaller positive input_tokens, prefer it
over the message_start value and adopt the cache counts from the same
usage block to avoid double counting; fall back to the start cache
values when the delta omits them. Native Claude (no input in delta) and
OpenRouter-converted (input only in delta) paths are unchanged.
Refs #3580
APINebula is an OpenAI-compatible relay (its base URL ends in /v1, matching
its Codex/OpenClaw/Hermes presets), but the OpenCode preset loaded the
@ai-sdk/openai package, which targets the OpenAI Responses API and fails
against chat-completions-only upstreams. Switch the npm field to
@ai-sdk/openai-compatible so requests use the OpenAI Chat Completions format.
Fixes#3701.
`query_zhipu` was hard-coded to `https://api.z.ai`, so a user who
configured the mainland China preset (`Zhipu GLM` on
`open.bigmodel.cn`) could not retrieve usage once the international
endpoint became unreachable from their network (or vice versa).
The two endpoints share the same quota path (`/api/monitor/usage/quota/limit`)
and return JSON in the same shape, and — crucially — each user only
ever uses one of them: the quota host is the same host they're already
running coding on. So we can route by the configured `base_url` and
skip the cross-host fallback entirely.
What this PR changes
--------------------
A single helper that maps the user's `base_url` to the matching quota
host, and `query_zhipu` rebuilt to take `base_url` and pick the right
host:
fn zhipu_quota_base(base_url: &str) -> &'static str {
if base_url.contains("bigmodel.cn") {
"https://open.bigmodel.cn"
} else {
"https://api.z.ai"
}
}
async fn query_zhipu(base_url: &str, api_key: &str) -> SubscriptionQuota {
let url = format!(
"{}/api/monitor/usage/quota/limit",
zhipu_quota_base(base_url),
);
// ... original 401/403 -> Expired / make_error / parse path, unchanged
}
The dispatcher already distinguishes `ZhipuCn` from `ZhipuEn` via
`detect_provider()` and routes the call through
`query_zhipu(base_url, api_key)` in the same match arm.
Why no cross-host fallback
--------------------------
Farion's review pointed out that adding a fallback would be
over-engineered and actively harmful:
1. Reachability is determined by the preset the user chose. Their
configured host is the host they are already using to run coding;
if it were unreachable, the user could not have reached the
"query usage" step at all.
2. The fallback path required distinguishing "both 401/403" (genuine
bad key) from "one 401/403 + one network error" (regional block),
which silently misclassified the second case as a generic query
failure and hid the upstream "Session expired" UX for invalid
keys.
3. It also cost the worst-case ~10s+10s≈20s serial timeout for users
on a working primary.
With the URL-based routing in place, 401/403 returns to the original
`CredentialStatus::Expired` semantics — same UX as `query_kimi` and
`query_minimax`.
Files changed
-------------
- `src-tauri/src/services/coding_plan.rs` — 1 file, +35 / -20
Testing
-------
- 3 new `zhipu_quota_base_*` routing tests
- 15 existing `coding_plan` parser tests still pass
- `cargo fmt --check` clean
- `cargo clippy --lib --no-deps -- -D warnings` clean
Co-authored-by: Yongmao Luo <yongmao.luo@columbia.edu>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
When previous stop_with_restore() failed to restore the user's original
Live (e.g. app crash mid-stop, settings.json unwritable, or any pre-existing
state where Live carries the proxy placeholders), the next
start_with_takeover would read the still-placeholder Live and overwrite the
good backup row with the proxy config itself. After that, every subsequent
stop would restore the proxy placeholder back to Live — making the proxy
toggle a no-op and leaving the client pinned at http://127.0.0.1:15721.
Fix: in both backup write paths (`backup_live_configs` and
`backup_live_config_strict`) detect that Live is already a proxy
placeholder and skip the save, preserving any existing good backup. In
`restore_live_config_for_app_with_fallback_inner`, detect the same
condition in the parsed backup and fall through to the existing
SSOT (current provider DB) path that was added in c3d810a.
Both sides share a new `live_has_proxy_placeholder_for_app` dispatch
helper so the placeholder check stays in lockstep with the existing
per-app detection functions.
Co-authored-by: Yongmao Luo <yongmao.luo@columbia.edu>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat(usage): add OpenCode session usage sync
Add OpenCode as a fourth app type in the usage statistics system.
Reads per-message token data from opencode's local SQLite database
(~/.local/share/opencode/opencode.db) and imports into proxy_request_logs.
- New session_usage_opencode.rs module following Codex/Gemini pattern
- Parses assistant message.data JSON for tokens, cost, model
- Adds "opencode" to AppType union and filter tabs
- Updates dedup filters to include opencode_session data_source
- Adds i18n keys for all 4 locales
* fix(usage): add opencode to UsageHero title themes
* fix: respect XDG_DATA_HOME and platform defaults for OpenCode DB path
- Support OPENCODE_DB env var override (absolute and relative paths)
- Use ~/Library/Application Support/opencode/ on macOS
- Use XDG_DATA_HOME/opencode/ when set
- Fall back to ~/.local/share/opencode/ on Linux
- Rename misleading test to test_parse_message_data_ignores_role
* fix(usage): use ~/.local/share/opencode on all platforms
OpenCode relies on xdg-basedir, which ignores macOS/Windows conventions,
so its DB always lives at ~/.local/share/opencode. The previous macOS
default pointed at ~/Library/Application Support/opencode, which does not
exist, making the sync a silent no-op for macOS users without
XDG_DATA_HOME set.
* fix(usage): include opencode -wal mtime in freshness check
OpenCode runs its SQLite DB in WAL mode; new commits land in the -wal
file and the main DB file's mtime only advances on checkpoint. Keying
the freshness gate solely on opencode.db could skip newly written
sessions until a checkpoint occurred. Take the max of the db and -wal
mtimes instead.
* style(usage): apply cargo fmt to opencode session sync
Fixes Backend Checks cargo fmt --check failure.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(usage): ignore empty OPENCODE_DB and XDG_DATA_HOME env vars
An empty OPENCODE_DB collapsed the path to the data dir (dropping the opencode.db filename); an empty XDG_DATA_HOME produced a relative "opencode" path. Treat empty strings as unset, per the XDG spec.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(usage): harden OpenCode session sync error handling and labeling
- Map '_opencode_session' provider_id to 'OpenCode (Session)' display name
- Return accurate inserted flag from insert_opencode_message (was always true)
- Do not advance file/session sync_state when a session errors, so failed
inserts are retried next run instead of being permanently skipped
- Surface per-message insert failures into the sync result errors
- Add opencode_session data-source icon and i18n labels (zh/zh-TW/en/ja)
- Add provider-stats labeling test for opencode session rows
* fix(usage): only import finalized OpenCode messages
An in-progress assistant message holds partial tokens; OpenCode updates the same message_id with final values later. Since request_id is fixed and the insert uses INSERT OR IGNORE, a partial row could never be corrected. Skip messages without time.completed so each turn is imported once with final usage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(usage): keep OpenCode incomplete sessions retryable
---------
Co-authored-by: Eira Hazel <kip3vx9ma@mozmail.com>
Co-authored-by: Eria hazel <git config --global user.email your@email.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
* Add S3 Cloud Sync design document
Design for adding AWS S3 as a new Cloud Sync backend alongside WebDAV.
Hybrid approach: extract shared sync protocol, add independent S3 transport.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add S3 cloud sync implementation design (reqwest + Sig V4)
Updated design based on 2026-03-06 draft: switches from rust-s3 crate
to hand-rolled AWS Sig V4 on existing reqwest for broader S3-compatible
service support (AWS, MinIO, R2, Alibaba OSS, Tencent COS, Huawei OBS).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add S3 cloud sync implementation plan (11 tasks, TDD)
Detailed step-by-step plan covering: sync_protocol extraction, S3 Sig V4
transport, settings, sync/auto-sync modules, Tauri commands, frontend
presets/dynamic form, and i18n.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* deps: add hmac crate for S3 Sig V4 signing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract sync_protocol.rs from webdav_sync.rs for shared use
Move transport-agnostic sync protocol logic (constants, types, snapshot
building, manifest validation, artifact verification, snapshot application,
utilities) into a new shared sync_protocol module so both WebDAV and the
upcoming S3 transport can reuse it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use transport-neutral error keys in sync_protocol
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add S3 transport layer with AWS Sig V4 signing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add S3SyncSettings to AppSettings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add S3 sync module with upload/download/fetch
Implements the S3 sync protocol layer (s3_sync.rs) that combines the
shared sync_protocol with the S3 transport. Mirrors the WebDAV sync
module structure with independent sync mutex, connection check,
upload, download, fetch_remote_info, and sync status persistence.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add S3 auto sync worker with debounce
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add S3 sync Tauri commands and auto sync worker startup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add S3 sync TypeScript types and API layer
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add S3 sync i18n translations (en/zh/ja)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add S3 sync presets and dynamic form to sync settings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: preserve HTTP scheme for S3 custom endpoints (MinIO support)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add live S3 integration tests (env-var driven, --ignored)
Run with: S3_TEST_AK=... S3_TEST_SK=... S3_TEST_BUCKET=... cargo test --lib services::s3::integration_tests -- --ignored
Verifies test_connection, put_object, get_object, head_object, and 404
handling against a real S3 bucket using the project's own Sig V4 signing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove internal design docs before PR
* fix: wire S3 auto-sync to DB hook & sync UI state on async load
- P1: Add s3_auto_sync::notify_db_changed call in SQLite update_hook
so S3 auto-sync worker receives DB change signals (was only wired
for WebDAV, leaving S3 worker idle)
- P2: Add useEffect to update syncType selector when s3Config loads
asynchronously, preventing stale "webdav" default for S3 users
* fix: satisfy clippy for s3 sync
* fix: address s3 sync review feedback
---------
Co-authored-by: Keith (via OpenClaw) <keithyt06@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
Strict OpenAI-compatible upstreams (vLLM, enterprise gateways) reject
requests that carry tool_choice or parallel_tool_calls without a
non-empty tools array, returning 503/400 with:
"When using `tool_choice`, `tools` must be set."
The Responses→Chat Completions converter unconditionally forwarded
tool_choice and parallel_tool_calls even when tools ended up absent
or empty after conversion. This commit adds a guard that removes both
fields when the resulting tools array is missing or empty.
Added 9 regression tests covering:
- tool_choice dropped when tools absent
- tool_choice dropped when tools is empty array
- parallel_tool_calls dropped when tools absent
- tool_choice dropped when all tools filtered (e.g., missing name)
- tool_choice preserved when tools present (auto + function type)
- clean output when neither tool_choice nor tools present
- tool_choice 'none' dropped when no tools
- tool_search_output providing tools keeps tool_choice
Refs #3557
Co-authored-by: yueqi.guo <guo_yueqi@qq.com>
An upstream gateway (e.g. nginx client_max_body_size) rejecting an oversized request body with HTTP 413 was wrapped as "CC Switch local proxy failed ..." with the full nginx HTML page echoed as the cause. This misled users into thinking CC Switch imposed the limit, when the local DefaultBodyLimit is already 200MB and the real cap lives on the provider's server.
413 now gets a dedicated message that points at the upstream gateway, states it is the provider's server-side limit (not CC Switch), and gives actionable recovery steps (/compact, drop large logs/images, ask the provider to raise its limit). Structured fields (upstream_status, provider, model, endpoint) are preserved; other error paths are unchanged.
Refs #666
Claude Desktop appends a local [1m] marker to the model name when the
1M-context beta is active (e.g. claude-opus-4-8[1m]). The proxy route
matcher compared this raw name against clean route IDs, so every match
tier failed and the is_claude_safe_model_id guard also blocked the role
keyword fallback, surfacing as route_unknown (HTTP 400) when switching
to a 1M-capable model mid-conversation.
Strip the [1m] suffix inside map_proxy_request_model before lookup so
exact/alias/legacy/role matching all see the clean ID, while keeping the
original name in the route_unknown error for diagnostics. The upstream
request still carries the mapped real model; 1M capability is negotiated
via the anthropic-beta header, not the model-name suffix.
Fixes#3588
Replace unsupported Anthropic image blocks with an [Unsupported Image] marker when routed models are text-only or upstream rejects image input.
Add rectifier settings for media fallback and heuristic model detection, wire the controls into the settings UI, and cover the sanitizer and forwarder gates with regression tests.
* fix(copilot): raise infinite-whitespace threshold from 20 to 500
The previous threshold of 20 falsely aborted legitimate tool calls whose
arguments contain indented code (write_file / edit_file with 4-8 levels
of indentation in Python / YAML / Rust / Markdown easily exceed 20
consecutive whitespace chars, especially when newlines are counted).
The real infinite-whitespace bug emits hundreds to thousands of
consecutive whitespace characters, so 500 keeps the safety net while
drastically reducing false positives.
Refs #2646
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: clarify whitespace threshold comment
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
* feat: add CherryIN preset provider for Claude Code and Codex
CherryIN (open.cherryin.net) is an API aggregator gateway. Add it as a quick-config preset for both Claude Code (Anthropic format) and Codex (OpenAI-compatible), placed next to AiHubMix, with the official brand icon. Endpoints and model IDs verified against CherryIN's live pricing API.
* feat: add CherryIN preset to Gemini, Claude Desktop, OpenCode, OpenClaw, Hermes
Extend CherryIN coverage to all remaining apps, each placed next to AiHubMix. Anthropic-native (open.cherryin.net) for Claude Desktop/OpenClaw/Hermes, @ai-sdk/anthropic (/v1) for OpenCode, Gemini-compatible endpoint for Gemini CLI. Model IDs verified against CherryIN's live pricing API.
* fix(presets): update Zhipu coding plan endpoints
* fix(model-fetch): probe /models on versioned /vN base URLs
The model-list probe assumed any base URL not ending in /v1 needs /v1/models appended. For providers whose base URL already ends in a version segment like /v4 (Zhipu/Z.AI GLM Coding Plan at .../api/coding/paas/v4), this produced .../v4/v1/models which 404s, so the "Fetch models" button always failed.
Detect a trailing /v{N} version segment and probe {base}/models first, keeping /v1/models as a fallback candidate for non-/v1 versions. Fixes Codex/OpenCode/OpenClaw/Hermes GLM presets and any other vN-style endpoint, with no behavior change for /v1 or non-versioned URLs.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix(codex): use relative filename for model_catalog_json
Instead of writing an absolute path (which breaks on WSL/symlink setups),
write only the filename "cc-switch-model-catalog.json" to config.toml.
Codex CLI resolves relative paths from the config directory, and both
files always reside in the same directory (~/.codex/).
This eliminates the need for UNC-to-Linux path translation and makes
the config portable across Windows, WSL, and symlinked directories.
Also simplifies ownership checks in resolve_cc_switch_catalog_path()
and set_codex_model_catalog_json_field() by removing dead string-equality
comparisons that never matched on WSL.
Closesfarion1231/cc-switch#3573
Related: farion1231/cc-switch#3569
* style(codex): fix rustfmt violations in model_catalog tests
Wrap a >100-col UNC-path line and remove a trailing blank line that broke 'cargo fmt --check' in Backend CI. Style-only, no logic change.
---------
Co-authored-by: steponeerror <huxaio0207@qq.com>
Co-authored-by: Jason <farion1231@gmail.com>
* docs(usage): document pricing model matching rules
* docs: refresh user manual for current app support
* docs: clarify Hermes configuration files
* docs: align feature docs with visible app support
Custom (freeform) Codex tools are bridged through Chat Completions as
JSON `{"input": "..."}` functions, but the Chat->Responses stream still
re-emitted them via `response.function_call_arguments.*`, leaking the
JSON wrapper and using event types the Codex client does not route for
freeform tools.
Emit `response.custom_tool_call_input.delta`/`.done` (with the unwrapped
input text) for custom tool calls instead, suppressing the intermediate
function-argument deltas since a partial `{"input":` fragment cannot be
safely unwrapped mid-stream. Custom tool call items now use a `ctc_`
item-id prefix (matching the input events' item_id) consistently across
the streaming and non-streaming paths via a shared
response_tool_call_item_id_from_chat_name helper.
Covered by new streaming and non-streaming custom-tool tests.
When proxying Codex Responses requests through the Chat Completions
format (api_format=openai_chat), the request transform only forwarded
plain `function` tools and silently dropped `tool_search`, `namespace`
(MCP), and `custom` tools, so third-party APIs never saw the official
Codex plugins and could not call them.
Introduce CodexToolContext, built from the original Responses request,
which flattens all four tool kinds into Chat functions and keeps a
chat_name -> CodexToolSpec map. The response path (streaming and
non-streaming) looks names up in this map to restore the original
tool_search_call / custom_tool_call / namespaced function_call items
instead of reparsing the flattened name. Long namespaced names are
truncated with a sha256 suffix to fit the 64-char Chat tool-name limit.
Covered by new round-trip tests for all four tool kinds across both
streaming and non-streaming paths.
During proxy takeover, switching third-party Codex providers left the
client-visible provider name stale: sync_codex_live_from_provider_while_proxy_active
based the live config on the existing live file and only patched
base_url/wire_api/model, never refreshing model_provider or
model_providers.<id>.name. The Codex app kept showing the previous
provider in its bottom-right label.
Rebuild the effective settings from the DB for the selected provider so
the live config carries the correct provider key and display name, then
merge MCP servers back from the existing live config. base_url stays
pointed at the local proxy, and official OAuth in auth.json is untouched
(takeover writes config.toml only when auth preservation is enabled).
Generalize preserve_codex_mcp_servers_in_backup ->
preserve_codex_mcp_servers_from_existing_config since it now serves both
the backup and live-sync paths.
Restyle the proxy-takeover notice in the Codex editor from the boxed
Alert to the amber inline-hint style used by the endpoint hints, and drop
implementation jargon (127.0.0.1 / PROXY_MANAGED) that users could not
interpret. The notice and the auth/config hints now simply state that the
form shows the stored provider config rather than the proxy-managed live
config. i18n synced across en/zh/ja/zh-TW.
Gate provider sync and switching on the restore backup / live placeholder
("is this live file owned by takeover?") instead of the lagging
proxy_config.enabled and proxy-running flags. The backup is created
before enabled=true is committed, so during that activation window the
old guards were blind and a concurrent sync/switch could rewrite the
taken-over live file, clearing Codex auth.json for a mis-categorized
provider.
Acquire a per-app switch lock around both set_takeover_for_app and
provider switching so the two cannot interleave, splitting the locking
entry points into outer (lock) / inner (no-lock) pairs to stay
deadlock-free. Preserve the official OAuth auth in provider-rebuilt
restore backups by routing the provider token into config.toml. Refine
takeover idempotency to require the live config to point at the current
proxy URL, rebuilding from backup when it does not.
Add unit and integration tests covering the official -> DeepSeek ->
takeover on/off lifecycle and the stopped-proxy switch path.
The reported "OAuth access token disappears when enabling Codex proxy
takeover" was a display artifact, not data loss: auth.json on disk kept
the OAuth login the whole time. During takeover the edit dialog falls
back to the stored provider config (so it does not surface the proxy
placeholder), which for a third-party provider shows that provider's own
key instead of the live auth.json, making the OAuth token look gone.
Thread an isProxyTakeover flag from App through ProviderForm into the
Codex editor and show an explicit notice plus storage-aware auth/config
hints clarifying that the form displays the stored provider config while
the live config is temporarily managed by the proxy. Drop the
proxy-running condition so the notice shows whenever takeover is active,
even with the proxy stopped.
Add a regression test asserting the dialog does not read live settings
during takeover and renders the database config. i18n synced across
en/zh/ja/zh-TW.
Preserve-mode takeover routes the PROXY_MANAGED placeholder into
config.toml and must leave auth.json (the ChatGPT OAuth login) intact.
c9cadd6e covered only the None-provider write branch; the
Some(provider) branch still ran the category-based auth decision in
codex_config::write_codex_live_for_provider, whose first clause
(category == "official" && has_login_material) ignores the preserve
flag entirely. Because takeover stamps OPENAI_API_KEY = PROXY_MANAGED
into auth, codex_auth_has_login_material returns true, so a provider
stored with a stale/mis-classified "official" category (e.g. DeepSeek)
had its real auth.json overwritten with the placeholder — the access
token vanished on takeover and reappeared on cleanup.
Fix at the takeover entry instead of patching the fragile category
logic: add write_codex_takeover_live_for_provider, which under
preservation detects the PROXY_MANAGED placeholder in auth and writes
only config.toml (bearer token + catalog projection), never touching
auth.json. Gating on the placeholder is orthogonal to category, so any
mis-classification is handled. All four takeover sites
(sync-while-active, takeover_live_configs, _strict, _best_effort) now
route through it; restore (verbatim) and non-takeover provider writes
are unchanged.
Also projects the model catalog via
prepare_codex_live_config_text_with_optional_catalog, so the
model_catalog_json pointer survives takeover too.
Add a regression test: official-category provider + preserve enabled +
proxy takeover must keep auth.json byte-identical while moving the
placeholder into config.toml.
Turning proxy takeover off restores Live from a stored backup via
write_codex_live_verbatim. That path mishandled the Codex model catalog
for two backup shapes that need opposite treatment:
- Snapshot backup (read_codex_live_settings -> {auth, config}): no inline
modelCatalog, but the config.toml text already carries the live
model_catalog_json pointer. The old code ran catalog projection, which
saw no specs and stripped the pointer.
- Provider-rebuilt backup (update_live_backup_from_provider): inline
modelCatalog (DB SSOT) with a pointer-less config text. A pure verbatim
write ignored the inline catalog and never regenerated the pointer.
The projection decision is orthogonal to auth: a provider-rebuilt backup
can pair an inline modelCatalog with empty auth.json ({}) when the API key
lives in the config's experimental_bearer_token. The empty-auth branch
raw-wrote config and skipped projection entirely, so that shape lost its
mapping too.
Decide the config.toml text once, before splitting on auth, via the new
prepare_codex_live_config_text_with_optional_catalog helper: project only
when an inline modelCatalog is present, else keep the text raw (preserving
any existing pointer). Every config-writing branch (write-auth,
delete-auth, no-auth) now applies it consistently. Add regression tests
covering all three shapes.
`modelCatalog` is a cc-switch-private field whose SSOT is the database; Live's
config.toml only carries a lossy `model_catalog_json` projection. Proxy
takeover/restore cycles and the official Codex.app rewriting config.toml can
drop that projection, so `read_live_settings` reconstructs an empty catalog.
Two paths then overwrote the stored mapping with that empty Live snapshot:
- Switch-away backfill (`switch_normal` -> `restore_live_settings_for_provider_backfill`):
now overlays the DB provider's `modelCatalog`, falling back to the
Live-reconstructed one only when the DB has none.
- Edit dialog (`EditProviderDialog`): when editing the active Codex provider it
preferred Live over the DB SSOT; now keeps the DB `modelCatalog` so opening +
saving no longer clears the mapping table.
Add Rust backfill tests (preserve DB catalog when Live lacks it; keep Live
catalog when DB has none) and a frontend regression test for the edit dialog.
* fix(codex): always update model catalog JSON on provider switch
Without this fix, the cc-switch-model-catalog.json file and the
model_catalog_json field in config.toml were only regenerated when
should_sync_backup was true (proxy active or backup exists). Switching
providers while the proxy was idle left the catalog pointing at the
previous provider's models, requiring a full restart to take effect.
After the existing should_sync_backup block, unconditionally call
write_codex_provider_live_with_catalog when switching a Codex provider
and the proxy is not currently active (live_taken_over == false). This
mirrors what live.rs already does during a normal provider apply.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(codex): refresh catalog on restored hot switch
---------
Co-authored-by: yueqi.guo <guo_yueqi@qq.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
* fix(usage): resolve per-app credentials for native balance/coding-plan queries
The native usage-query paths (balance + coding_plan) in
`query_provider_usage_inner` read credentials only from `env.ANTHROPIC_*`.
That matches Claude providers, but Codex stores its key in
`auth.OPENAI_API_KEY` with the base URL inside a TOML `config` string,
Hermes/OpenClaw flatten them at the top level, and OpenCode nests them under
`options`. So the card "refresh usage" / auto-query returned empty
credentials and failed ("查询失败") for those apps, even though the
config-page "Test" button worked (the frontend extracts per-app correctly).
Introduce a single per-app resolver `Provider::resolve_usage_credentials`
that mirrors the frontend `getProviderCredentials`, and route both native
branches through it. Add `extract_codex_base_url` to `codex_config` as the
canonical Codex TOML base-URL parser and make the proxy adapter delegate to
it (removing a duplicate copy). Align the frontend `getProviderCredentials`
to cover OpenCode and Claude Desktop and to use the same OpenRouter/Google
key fallbacks as the backend.
Fixes#3158Fixes#3100Fixes#2625
* refactor(usage): explicit AppType arms + frontend trailing-slash trim
Address two review nits on the per-app credential resolver:
- provider.rs: replace the catch-all `_` arm in resolve_usage_credentials with an explicit `AppType::Claude | AppType::ClaudeDesktop` arm, so a new AppType variant fails to compile here instead of silently defaulting to the Anthropic env shape.
- UsageScriptModal.tsx: normalize getProviderCredentials baseUrl through a single trailing-slash trim so the frontend 'Test' path matches the backend resolver (trim_end_matches), keeping front/back truly in lockstep.
No behavior change for well-formed configs; tests/typecheck/fmt/clippy clean.
* fix(usage): skip empty primary credential fields in fallback chain
`obj.get(key)` returns `Some` for a present-but-empty field, so the
`.or_else()` fallback chains for the Claude/ClaudeDesktop and Gemini api-key
lookups only skipped *absent* keys, not empty ones. Presets seed fields like
`ANTHROPIC_AUTH_TOKEN` as present-but-empty placeholders, so a provider whose
real key lives in a fallback field (`ANTHROPIC_API_KEY` / `OPENROUTER_API_KEY`
/ `GOOGLE_API_KEY`, or `GOOGLE_API_KEY` for Gemini) resolved to an empty key on
the native balance/coding-plan path — while the frontend `a || b` (which skips
empty strings) still found it, reproducing the same Test-works / refresh-fails
divergence this PR removes.
Add a `first_non_empty` helper that skips present-but-empty values, matching
the frontend `||` semantics, and use it for both fallback chains. Tests cover
empty primary + populated fallback for Claude and Gemini.
* fix(codex): add multi-platform CLI discovery and static gpt-5.5 template fallback for model catalog generation
When cc-switch generates a Codex model catalog for third-party providers,
it needs the gpt-5.5 model definition as a template. Previously it relied
on either ~/.codex/models_cache.json (only exists when Codex has connected
to OpenAI) or running 'codex debug models --bundled' (fails when codex
is not on the Tauri app's PATH, common in macOS GUI environments).
This commit adds a three-tier fallback:
1. Try 'codex' from PATH, then platform-specific common paths
(/opt/homebrew/bin/codex, /usr/local/bin/codex, etc.)
2. If all CLI attempts fail, use a compile-time embedded gpt-5.5
template (extracted from codex 0.135.0 bundled models)
Also adds tests for the static template validity and CLI candidates.
* fix(codex): discover user node codex installs
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix: add kimi/moonshot to Anthropic tool thinking history normalizer
Kimi's Anthropic-compatible endpoint requires reasoning_content in
assistant tool call messages when thinking is enabled, same as DeepSeek.
The is_reasoning_content_compatible_identifier already included kimi/moonshot
but is_anthropic_tool_thinking_history_identifier was missing them, causing
400 errors on multi-turn conversations with tool calls.
Closes#3351
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: unify reasoning vendor hints into a single SSOT list
Replace the two separately-maintained vendor identifier lists with one
shared REASONING_VENDOR_HINTS constant and a single
is_reasoning_vendor_identifier predicate. The Anthropic
tool-thinking-history matcher and the openai_chat reasoning_content
matcher previously kept independent lists, which is exactly how
kimi/moonshot ended up in one but not the other (#3351). A single source
of truth keeps the two from drifting apart again.
Also add a Kimi regression test for the Anthropic tool-history
normalization path.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
When "preserve official auth on switch" is enabled, proxy takeover routes
the PROXY_MANAGED placeholder into config.toml's experimental_bearer_token
and leaves auth.json (the ChatGPT OAuth login) untouched. Takeover detection
only inspected auth.json's OPENAI_API_KEY, so it never recognized this state
and returned a false negative, which led downstream paths to clobber the
preserved OAuth login.
- Detection: is_codex_live_taken_over now also matches a config.toml
experimental_bearer_token equal to the placeholder, fixing detect/cleanup/
restore/startup-recovery in one place.
- Cleanup: remove the config.toml bearer token only when it equals the
placeholder (new remove_codex_experimental_bearer_token_if predicate), so a
real third-party key is never stripped.
- Write: under preservation, the None-provider takeover path writes only
config.toml and keeps auth.json intact, matching the provider path.
- Settings: rename the section to "Codex App Enhancements" and reword the
description across all four locales.
- Add tests covering OAuth preservation on takeover and placeholder-only
cleanup.
Collapse the two duplicated write_codex_live_atomic branches in
write_codex_live_for_provider into a single should_write_auth guard.
This is behavior-preserving: `if A {X} else if B {X} else {Y}` becomes
`if A || B {X} else {Y}`.
Adapt the Codex switch tests to the new opt-in default for
preserve_codex_official_auth_on_switch (flipped off in 3f59ab37):
add an enable_codex_official_auth_preservation() test helper for the
cases that assert the auth-preserving path, and tag the official login
provider with category="official" so it routes through the official
branch rather than relying on the global preservation flag.
Add a regression test locking the default (preservation off) behavior:
switching to a third-party provider rewrites auth.json with the new
API key and discards the existing ChatGPT OAuth login. This is the
dual of the existing preserve-and-backfill test, which only covered
the opt-in path.
When forward_with_retry fails for Codex endpoints (/chat/completions, /responses, /responses/compact), the handlers previously returned the bare ProxyError, surfacing a terse body to the client. Build a richer JSON error instead: the message embeds provider, model, endpoint and upstream status, and the error object carries those as structured fields alongside a stable cc_switch_* code.
Align map_proxy_error_to_status with ProxyError::into_response so the manually built responses use the canonical status codes (AuthError->401, Config/InvalidRequest->400, TransformError->422, StreamIdleTimeout->504, AlreadyRunning->409, NotRunning->503); previously these forwarder-level errors collapsed to 500.
Flip preserve_codex_official_auth_on_switch from true to false so
third-party Codex switches overwrite auth.json by default, matching the
expectation that switching providers also swaps credentials. Users who
rely on keeping the ChatGPT login in auth.json while on a third-party
provider (for official plugins / remote login) can enable it in
Settings -> Codex Authentication.
The toggle field ships for the first time here (it is not in v3.16.0),
so no existing settings.json holds an explicit value -- every user lands
on the new default and no migration is required.
Also set the flag explicitly in the preservation unit test instead of
relying on the global default, keeping it valid now that the default is
false.
The version probe ran `cmd /C "\"{path}\" --version"` on Windows. Rust's
own arg quoting plus the manual quotes produced nested quotes that cmd
(without /S) misparsed, so it reported even working tools as an
unrecognized command -> non-zero exit -> shown as "installed but not
runnable". cmd's error is emitted in the OEM code page (e.g. GBK on
zh-CN) and was decoded as UTF-8, turning it into mojibake.
- Add decode_command_output: try strict UTF-8, then OEM (GetOEMCP), then
ANSI (GetACP) code page via MultiByteToWideChar; route all command
output decoding through it (probes, lifecycle output, osascript,
terminal launchers).
- Add run_windows_tool_version_command: run .exe directly (no cmd);
invoke .cmd/.bat via `cmd /D /S /C call <quoted> --version` with
raw_arg to bypass Rust's quoting and keep deterministic quote handling.
- Add windows-sys Win32_Globalization dependency.
Claude Desktop's 3P validation only accepts claude-{sonnet,opus,haiku}-*
role IDs, so providers must map every tier. Bring the Desktop mapping flow
in line with Claude Code and fix the fallout that broke sub-agent Haiku calls.
- Proxy form: replace the dynamic route list with fixed Sonnet/Opus/Haiku
tiers; blank tiers backfill from the first filled tier (Sonnet first) on
submit and inherit its supports1m flag
- Backend: add a role-keyword fallback in map_proxy_request_model so dated
official names (e.g. claude-haiku-4-5-20251001) resolve to the right tier,
guarded by is_claude_safe_model_id so [1m]-suffixed IDs stay rejected
- Tighten is_claude_safe_model_id / isClaudeSafeRoute to reject degenerate
role IDs like "claude-sonnet-"
- Fix the seed-effect race where normalizing empty routes to three blank
tiers blocked the default-route backfill
- Sync switch hints, placeholders, and the zh/en/ja/zh-TW locales to the
three-role-ID rule
- Update zh/en/ja user manual (2.1, 2.6), calling out legacy Claude IDs
(claude-3-5-sonnet-...) as a rejected example
Tests: 282 frontend + 34 backend claude_desktop; typecheck, clippy, fmt clean.
The websiteUrl for ShengSuanYun presets pointed at the bare domain while
only apiKeyUrl carried the from=CH_4HHXMRYF referral code. Append the same
referral param to websiteUrl across all provider presets so the in-app
'open website' jump is also attributed to the channel.
Bump default model names project-wide: gpt-5.4 -> gpt-5.5,
gemini-3.x -> gemini-3.5-flash, glm-5 -> glm-5.1, and
grok-code-fast-1 -> grok-build-0.1 across all provider presets
(claude, codex, gemini, hermes, openclaw, opencode, universal),
Gemini config, and stream check defaults.
Pricing:
- Seed gemini-3.5-flash, gemini-3.1-flash-lite, step-3.5-flash-2603,
doubao-seed-2.0-code, mimo-v2.5(/pro), qwen3-coder-480b, grok-build-0.1.
- Correct deepseek-v4-flash/pro, glm-5/5.1, grok pricing.
- Add repair_current_model_pricing: idempotent pass that fixes only
rows still equal to the outdated built-in values, preserving any
user-customized prices (seed uses INSERT OR IGNORE and cannot update
existing rows).
Fixes from review:
- opencode: drop duplicate gemini-3.5-flash variant (unreachable via
.find), keep the entry with the full minimal/low/medium/high set.
- Align stale display names/costs to gemini-3.5-flash (hermes, openclaw,
opencode); openclaw cost -> {1.5, 9, 0.15} to match seed.
- i18n (zh/en/ja/zh-TW): refresh OMO category tooltips for new model
names; fix writing tooltip to Kimi K2.5 to match its recommended.
Update tests accordingly and add a regression test asserting unique
model ids in the Google opencode preset variants.
Bump the default Opus route/model from claude-opus-4-7 to claude-opus-4-8
across provider presets (claude, claudeDesktop, hermes, openclaw, opencode,
universal), i18n locales (zh/en/ja/zh-TW), pricing seed data, and the
user-manual docs.
- Add claude-opus-4-8 pricing row ($5/$25/$0.50/$6.25); keep the 4-7 row
for historical usage stats (seeded via INSERT OR IGNORE).
- Claude Desktop proxy: accept bidirectional opus 4-7 <-> 4-8 route alias
during rollout so previously saved routes keep resolving.
- thinking_optimizer: route opus-4-8 through adaptive thinking and normalize
dotted model ids (also fixes dotted 4-6/4-7 falling back to legacy).
- usage_stats: normalize Bedrock/Vertex/aggregator opus-4-8 ids to base
pricing.
Also merge role:"system" messages into the Gemini systemInstruction in the
Anthropic->Gemini transform.
When importing a Claude provider via deep link, the preview dialog
correctly shows every env field carried in the `config` payload — but
on confirmation `build_claude_settings` only persisted the standard
fields mapped from URL params (`apiKey`, `endpoint`, `model`,
`haiku/sonnet/opusModel`). Any non-standard env keys such as
`ANTHROPIC_CUSTOM_HEADERS`, `API_TIMEOUT_MS`, or
`CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS` were silently dropped,
making the preview and the actual write diverge.
Start the env map from the inline `config` payload (best-effort
decode via the new `extract_claude_config_env` helper) and then
overlay the URL-param-derived standard fields on top, so:
- URL params remain authoritative for standard fields (unchanged)
- Custom env keys present in `config` survive the import
- Deep links without a `config` payload behave exactly as before
Adds two regression tests:
- `test_build_claude_provider_preserves_custom_env_fields` proves
custom fields are kept and URL params override same-named config
fields.
- `test_build_claude_provider_without_config_unchanged` guards the
backward-compatible path (no `config` → only standard env keys).
Closes#2927
Co-authored-by: lipeng31 <lipeng31@tanyang.co>
The usage dashboard previously only refreshed on app restart for users
who don't route through the cc-switch proxy. Two issues were involved:
1. The session-sync background task panicked when a Codex model name
contained non-ASCII characters (e.g. `【官】glm-5.1`), because
`normalize_codex_model` sliced `&name[name.len() - 11..]` without
verifying char boundaries. Once the task panicked, no session logs
were imported until the app was restarted (where startup-time
`rollup_and_prune` happened to flush pending data).
2. Even with sync working, the dashboard only polled every 30s and
skipped polling when the window was unfocused, so freshly-imported
data was invisible until the next poll or window refocus.
Fixes
-----
* `normalize_codex_model`: guard the 11-byte ISO-date suffix slice with
`is_char_boundary` + `is_ascii` checks. ASCII-only suffix means the
date-stripping logic is correct, and non-ASCII names (which can never
be valid date suffixes anyway) now bypass the slice safely.
* New `usage_events` module that emits `usage-log-recorded` to the
frontend whenever `proxy_request_logs` actually gains a new row.
Sources covered: proxy `log_request`, Claude/Codex/Gemini session
sync, and startup `rollup_and_prune`. Notifications use a global
`OnceLock<AppHandle>` so call sites that don't already hold an
`AppHandle` (e.g. `UsageLogger`) can notify without signature churn.
* 200ms debounce in `notify_log_recorded` collapses bursts (a single
Codex sync importing 3000+ entries triggers ~2 emits, not 3000) so
the frontend's `invalidateQueries` is never spammed.
* Frontend `useUsageEventBridge` listens for the event and invalidates
`usageKeys.all`. Hook is mounted only on `UsageDashboard`, so the
listener is unsubscribed automatically when the user navigates away.
Verification
------------
* `cargo check` passes (existing 25 dead-code warnings in
`commands/misc.rs` are pre-existing and unrelated).
* `tsc --noEmit` passes.
* Manually verified end-to-end: a Codex sync run that imported 3145
entries produced 2 debounced emits, both logged as `emit
usage-log-recorded 成功`, and the dashboard updated within ~200ms.
Behaviour notes
---------------
* `INSERT OR IGNORE` paths (Claude/Codex session sync) only notify when
the row is actually inserted, so dedup-skipped writes don't trigger
empty refreshes.
* Gemini's `INSERT … ON CONFLICT … DO UPDATE` path reuses the existing
`conn.changes() > 0` check and only notifies when token counts truly
changed.
* `rollup_and_prune` notifies once per pruning cycle (at most once per
app start) so the dashboard reflects the new aggregate state.
Co-authored-by: in30mn1a <in30mn1a@users.noreply.github.com>
Use semver comparison instead of string inequality so locally-ahead prerelease builds aren't misreported as outdated; backend now fetches the full npm dist-tags and, when the local version leads latest, surfaces the tool's prerelease channel (claude=next) as latest.
- Always emit `model_provider = "custom"` from deep link, UniversalProvider, and the universal form modal so future writes share one stable routing key.
- Add `codex_provider_template_v1` local migration that rewrites legacy keys (aihubmix/ccswitch/...) under `[model_providers.custom]`, updates profile refs, and backs up the original settings_config under `~/.cc-switch/backups/<timestamp>/providers/`.
- Tighten history migration source detection to a whitelist plus `[model_providers.<id>]` existence check so user-authored keys are never rewritten in jsonl/state DB.
- Encode deep link name/model/endpoint through `toml_edit::Value` so display names containing quotes or backslashes no longer break the generated config.toml.
- Stabilize provider settings backup filename hash with Sha256 (was process-random SipHash).
The one-time history migration already consolidates legacy provider IDs
into a single bucket; there is no need to normalize on every write.
This preserves user-chosen provider identities through the full
write/backup/restore cycle.
The Fill Recommended button was misleading — it showed toast.success even
when most slots couldn't be filled due to model ID mismatches with the
user's configured providers.
Changes:
- Sync OMO_BUILTIN_AGENTS/CATEGORIES recommended fields with upstream
oh-my-openagent model-requirements (gpt-5.4→5.5, kimi-k2.5→claude-sonnet-4-6, etc.)
- Add toast.warning tier when unmatched >= filled, showing slot:model pairs
(e.g. "Sisyphus: claude-opus-4-7") so users know exactly what to configure
- Upgrade fillRecommendedNoMatch to also show examples
- Add fillRecommendedMostlyUnmatched i18n key (zh/en/ja/zh-TW)
Scan Codex archived_sessions alongside active sessions so archived conversations appear in the session manager.
Allow deletion from any validated Codex session root while keeping path safety checks in place.
* Add Traditional Chinese localization
* fix: address zh-TW formatting and token units
- Format `zh-TW.json` with Prettier.
- Use Traditional Chinese `萬` and `億` units for zh-TW token summaries.
- Add usage formatting coverage for Traditional Chinese locale aliases.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* refactor: replace JSON.parse(JSON.stringify()) with structuredClone and extract useTauriEvent hook
Replace all `JSON.parse(JSON.stringify())` deep copy patterns with native
`structuredClone()` across production source (9 occurrences), tests (11
occurrences), and a hand-rolled `deepClone` utility in providerConfigUtils.ts.
Add "ES2022" to tsconfig lib for type support.
Extract a `useTauriEvent` hook to eliminate the repeated Tauri event listener
boilerplate (`useEffect` + `active/disposed` flag + async `listen`) that was
duplicated across App.tsx (3 listeners) and useUsageCacheBridge.ts. The hook
handles async registration, race-condition guards, and cleanup automatically.
* fix: add compatible deepClone helper
- Add a shared deepClone helper with a structuredClone runtime guard and fallback.
- Route clone call sites through the helper.
- Preserve universal-provider-synced listener ordering and drop the dead-directory diff.
* fix: harden Tauri event handling
- Guard WebDAV sync status events against missing payloads.
- Preserve settings query invalidation ordering before showing auto-sync errors.
- Simplify useTauriEvent subscriptions to avoid dependency-driven re-listens.
---------
Co-authored-by: zcb <zhangchongbiao@qiyuanlab.com>
Co-authored-by: Jason <farion1231@gmail.com>
* fix: sync Claude Desktop profile during proxy takeover
* fix(provider): skip Claude Desktop backup refresh during takeover
- Route Claude Desktop takeover updates directly through the 3P profile writer.
- Keep takeover startup backup state unchanged when provider metadata changes.
- Narrow platform-specific test helpers and environment setup with cfg gates.
* fix(provider): restore PathBuf import for CI tests
- Restore an unconditional PathBuf import for provider tests.
- Keep Linux cargo test builds compiling while preserving Claude Desktop cfg-gated helpers.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* feat: add MiMo reasoning_content support for Claude Code proxy
MiMo requires reasoning_content to be passed back in multi-turn
conversations with tool calls. Add "mimo" and "xiaomimimo" to the
preserve_reasoning_content whitelist so thinking blocks are converted
to reasoning_content when proxying Claude Code → MiMo.
Also handle redacted_thinking blocks (encrypted by Claude Code across
turns) by injecting a placeholder to prevent MiMo 400 errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: support MiMo reasoning histories
- Fix the PR CI clippy failure for redacted thinking handling.
- Preserve MiMo reasoning content on the OpenAI Chat proxy path.
- Normalize Claude Desktop Anthropic thinking history for MiMo local routes.
- Reject unsupported Claude Desktop direct model remaps.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
* docs: add German README translation (README_DE.md)
Adds README_DE.md, a full German translation modelled on README_JA.md,
and extends the language switcher in README.md, README_ZH.md and
README_JA.md with a Deutsch link.
* docs: remove outdated German sponsor entry
- Remove the AICoding.sh sponsor block from README_DE.md.
- Keep the German README aligned with the current sponsor list.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix(ui): remove fixed width constraint on AppSwitcher text to prevent clipping
App names like "Claude Desktop" were being clipped by the max-w-[90px]
constraint on the text label. Changed to max-w-none so text adapts to
content width. Also removed overflow-x-hidden from the parent toolbar
container to prevent clipping of the wider content.
Fixes text truncation in the top-right app switcher segmented control.
* - fix(ui): preserve AppSwitcher compact overflow behavior
- Restore toolbar overflow clipping required by useAutoCompact.
- Cap expanded AppSwitcher labels at 120px so Claude Desktop fits while compact animation remains smooth.
---------
Co-authored-by: Jason <farion1231@gmail.com>
Remove AICoding (aicoding.sh) sponsor entry from the three README files
and the provider preset across all seven app_types (claude, codex,
gemini, hermes, opencode, openclaw, claudeDesktop). Also drop the
matching partner_promotion.aicoding string in zh/en/ja locales.
The icon (src/icons/extracted/index.ts) and the legacy provider id in
codex_history_migration.rs are kept intentionally: the former for
possible later re-use, the latter so existing users' Codex history
still gets bucketed into "custom" on upgrade.
- codex_config: rewrite `!(A && !B)` as `!A || B` (De Morgan-
equivalent) — also reads more directly as "no OAuth, or
already has provider API key".
- UsageScriptModal: reflow conditional onto one line per prettier.
Codex CLI filters conversations by model_provider — after normalizing
all third-party providers to "custom", old sessions with their original
provider IDs become invisible. This migration rewrites JSONL session
files and state_5.sqlite threads to use the unified ID.
Uses a three-tier trust model for source ID collection:
- Known legacy preset whitelist (46 IDs, case-insensitive)
- cc-switch-created custom providers (identified by created_at)
- User-maintained custom providers are explicitly skipped
Adds scanned_history_files flag to prevent stale v1 markers from
blocking re-runs with the improved source ID collection logic.
When the 5-hour bucket is at 0% utilization, ZhiPu's API omits
nextResetTime. The old i64::MAX sentinel sorted these entries last,
causing the weekly bucket to incorrectly claim the five_hour slot.
Codex provider switches now only write config.toml for third-party providers,
injecting the API key as experimental_bearer_token. The user's auth.json
(ChatGPT OAuth tokens) is preserved. Official providers with login material
still write auth.json normally. Backfill restores bearer tokens into stored
provider auth.OPENAI_API_KEY to maintain canonical shape.
Extend the mktemp+bash installer pattern from hermes (commit 20d943be) to
claude and opencode, and remove the cfg gate that locked install_command_for
to non-Windows targets — Windows host + WSL tools now correctly route
through the POSIX install priority instead of falling back to bare npm.
Frontend "one-click install commands" displayed in the About section now
diverge per platform to match what the backend actually runs.
Backend (src-tauri/src/commands/misc.rs):
- New CLAUDE_INSTALL_UNIX and OPENCODE_INSTALL_UNIX constants use the same
`bash -c 'tmp=$(mktemp) && curl -fsSL .../install.sh -o $tmp && bash
$tmp; status=$?; rm -f $tmp; exit $status'` shape as HERMES_INSTALL_UNIX.
The shared comment about why we avoid `curl | bash` is now hoisted to the
top of all three constants — the constraint (WSL `sh -c` sub-shells
don't inherit outer pipefail; default shell may be dash/ash) applies to
every shell installer, not just Hermes.
- New installer_with_npm_fallback helper deduplicates the
"official installer || npm fallback" chain construction; reuses
chain_update_commands(LifecycleCommandShell::Posix) so the wiring matches
the update-chain logic.
- install_command_for split into cross-platform posix_install_command_for
+ thin #[cfg(not(target_os = "windows"))] wrapper. The cfg gate had
been hiding a real bug: on Windows hosts, the WSL branch called
wsl_tool_action_shell_command which forwarded install actions to the
Windows-target tool_action_shell_command, yielding bare `npm i -g` for
claude/opencode even though they have working native POSIX installers.
- wsl_tool_action_shell_command now branches on action: Install dispatches
to posix_install_command_for (native installer || npm chain); Update
keeps the existing behavior. Empty install commands collapse to None so
callers error on unsupported tools instead of running an empty command.
- build_tool_lifecycle_command pipefail comment updated: the original
justification (covering install's `curl | bash` path) no longer applies
to any current command, but the directive stays as defense-in-depth for
future pipe additions.
- build_tool_action_line WSL branch comment updated to reflect the
install/update split.
Frontend (src/components/settings/AboutSection.tsx):
- New posixScriptInstallCommand(url) helper builds the same mktemp+bash
string template the backend uses.
- New powershellEncodedCommand(script) mirrors the backend's UTF-16 LE +
base64 encoder (charCodeAt + String.fromCharCode(lo, hi) + btoa) so the
PowerShell EncodedCommand shown to users matches what the backend
executes.
- ONE_CLICK_INSTALL_COMMANDS split into POSIX_ONE_CLICK_INSTALL_COMMANDS
and WINDOWS_ONE_CLICK_INSTALL_COMMANDS, selected by isWindows():
POSIX shows claude/opencode/hermes with the mktemp+bash installer;
Windows shows claude/codex/gemini/opencode/openclaw with npm (matching
backend static_fallback_command on Windows) and hermes with the
PowerShell EncodedCommand. Display now mirrors backend execution
per platform, removing the show-vs-run mismatch.
- Stale comment in the probe-concurrency note mentioning "curl | bash"
updated to "官方 installer".
Tests (src-tauri/src/commands/misc.rs):
- New wsl_install_uses_posix_install_priority covers three
representatives: claude (positive: native installer with npm fallback),
opencode (positive), codex (negative: no native installer so falls back
to bare `npm i -g`). Reverse-asserts !contains("| bash") to lock out
the old pipe form.
- claude_install_chains_native_then_npm and
opencode_install_chains_native_then_npm strengthened with
!parts[0].contains('|') so the native installer portion stays
pipe-free even if future edits to the template sneak a pipe back in.
Validated:
- cargo check --tests: clean
- cargo test --lib commands::misc:: : 59 passed, 0 failed
- pnpm typecheck: 0 errors
- cargo fmt: clean
- pnpm format: clean (no files changed)
Promote anchored upgrades to "<bin> update || <package fallback>" for the
tools whose CLI knows how to self-update safely, and replace pip-based
hermes install/update with the official NousResearch installer scripts.
Frontend learns to detect "update reported success but version did not
change" so silent upstream no-ops surface as a soft warning.
Backend (src-tauri/src/commands/misc.rs):
- New prefers_official_update(tool, shell) per-platform allow-list:
POSIX {claude, codex, opencode, openclaw}. Windows drops opencode
pending anomalyco/opencode#17295 (its silent `upgrade` may prompt for
install-source detection and deadlock our lifecycle). gemini is absent
from both because `gemini update` is not implemented upstream
(google-gemini/gemini-cli#18618 / #16122 OPEN).
- New chain_update_commands joins primary/fallback per shell: POSIX
`||`, Windows `|| call` — batch transfers control to the `||` RHS
without `call`, skipping subsequent tools in multi-tool actions.
- New LifecycleCommandShell {Posix, WindowsBatch} threads the target
shell through tool_action_shell_command_for_shell, replacing the old
hermes-only WSL substitution hack with a uniform mechanism.
- anchored_command_from_paths refactored: hermes anchors to
`<bin> update`; tools in prefers_official_update chain self-update
with the package-manager command as fallback; brew formulae remain
brew-managed (never self-update what Homebrew owns); others use the
prior npm/volta/bun/pnpm anchoring unchanged. Mirror on Windows.
- Hermes commands switch from pip to the official scripts. POSIX/WSL
runs `bash -c 'tmp=\$(mktemp) && curl -fsSL .../install.sh -o \$tmp
&& bash \$tmp; status=\$?; rm -f \$tmp; exit \$status'`. The mktemp +
two-step download avoids `curl | bash` because WSL sub-shells inherit
neither outer `set -o pipefail` nor a guaranteed bash (default may be
dash/ash). Windows uses `powershell -NoProfile -ExecutionPolicy Bypass
-EncodedCommand <b64>` with a hand-rolled UTF-16 LE base64 encoder
for `irm .../install.ps1 | iex`, hiding the PowerShell pipe from
cmd.exe. Pip is abandoned because macOS system python3 is often 3.9
while hermes-agent requires >=3.11, and the pyenv `python` shim may
not exist — pip errors then misclassify as "command not found".
Frontend (src/components/settings/AboutSection.tsx):
- New versionUnchangedAfterUpdate four-AND short-circuit detects when
the upgrade command succeeded, the tool still reports a version, but
that version equals the pre-upgrade version while a different
latest_version is known. Guards against upstream no-op updaters that
return exit 0 without applying anything (openai/codex#21897).
- Soft-failure shape gains kind?: "notRunnable" | "versionUnchanged"
so the warning toast title can disambiguate the two cases.
- Hermes install command snippet swapped from `python3 -m pip ...` to
the official curl-bash one-liner shown to users.
i18n (zh / en / ja):
- New settings.toolActionVersionUnchangedTitle and
settings.toolActionVersionUnchanged, synchronized across all locales.
Tests (src-tauri/src/commands/misc.rs):
- Anchored upgrade assertions updated to `<bin> update || <pkg>` shape
across nvm / volta / bun / homebrew-npm-global / spaces / fnm
branches; brew formula explicitly does NOT chain self-update.
- New hermes anchor tests replace hermes_has_no_npm_anchor on both
POSIX and Windows. WSL hermes tests rewritten for the mktemp+bash
flow with `hermes update` fallback.
- Reverse-lock tests: gemini static fallback must NOT contain
`gemini update`; opencode Windows static fallback must NOT contain
`opencode upgrade`; hermes commands must NOT contain
`python`/`pip`/`powershell`/forbidden pipes.
Validated:
- cargo check --tests: clean
- pnpm typecheck: 0 errors
- cargo fmt: clean
The uninstall hint feature added in f6bdd4bb / 89e2339b printed a
copy-pasteable shell command (e.g. `brew uninstall <formula>`,
`<sibling-npm> rm -g <pkg>; [ -e bin ] && rm -f bin && rm -rf pkg_dir`)
under each detected install in the multi-install conflict panel. Even
with copy-only-never-execute UX, putting destructive commands one
button away from the user is risk we don't want to ship — `rm -rf`
under a misclassified path would be unrecoverable.
Conflict diagnostics themselves are kept: users still see the warning
banner + per-install row (source / path / version / Default badge), so
the "you have multiple installs and the upgrade only touches one"
signal survives. They just don't get a one-click destructive command.
Backend (src-tauri/src/commands/misc.rs):
- Remove `uninstall_command_from_paths` (POSIX + Windows pair).
- Remove `posix_quote_for_user_shell` and `win_quote_path_for_user_shell`
— both were uninstall-only quoters (whitelist + cmd-only forms).
`win_quote_path_for_batch` and `shell_single_quote` stay because the
anchored upgrade path and `build_shell_cd_command` still use them.
- Remove `quoted_sibling_or_bare` and `quoted_sibling_or_bare_with_ext`
thin wrappers (only callers were uninstall branches).
- Drop `uninstall_command` and `uninstall_command_needs_cmd_hint` from
`ToolInstallation`. Drop the eager `infer_install_source` ->
`uninstall_command_from_paths` call in `enumerate_tool_installations`
(saves ~16μs per probe but the real point is fewer surprises).
- Remove 14 `#[test]` cases for uninstall command shape (brew /
homebrew-npm-global / nvm / npm-fallback quoting / pnpm / volta / bun /
hermes / system / Windows percent-path) plus 4 `win_quote_path_for_user_shell`
helper tests and the `expect_user_shell_quoted_path` test mirror.
- Drop the `src()` test helper that was only used to feed the deleted
uninstall tests (`infer_install_source` is still called directly by
surviving tests).
- Cross-references in `anchored_command_from_paths` doc to its now-gone
uninstall "dual" are stripped.
Frontend (src/components/settings/AboutSection.tsx):
- Conflicts `<ul>` keeps the diagnostic header + per-install `ToolInstallRow`
but loses the inline command box, the trash + copy icons, the per-row
PowerShell-call-operator hint, and the `handleCopyCommand` callback.
- Remove the `Trash2` lucide import; `Copy` stays (the install-commands
copy button at line 1100 still uses it).
API (src/lib/api/settings.ts):
- Drop `uninstall_command` and `uninstall_command_needs_cmd_hint` from
the `ToolInstallation` TS interface (matches the Rust struct change).
i18n (zh / en / ja):
- Drop `settings.toolUninstallCopyHint`, `settings.toolUninstallCopied`,
`settings.toolUninstallPwshHint`. Other `tool*` and `skills.uninstall*`
keys are untouched (Skill uninstall is a separate, non-destructive
feature for managing the Skills repo and is unaffected).
Stats: 858 deletions, 5 insertions across 6 files.
Validated:
- cargo test --lib: 1322 passed, 0 failed
- cargo fmt --check: clean
- cargo build: clean
- cargo check --tests: clean
- pnpm typecheck: 0 errors
- JSON.parse on all three locale files: clean
Move uninstall hint command generation from frontend (which used a lossy
`source` string and couldn't tell brew formula from homebrew-npm-global apart)
into the Rust backend, where `brew_formula_from_path` + `infer_install_source`
together produce correct commands for each install. Conflict UI now reads
`inst.uninstall_command` directly.
Backend (src-tauri/src/commands/misc.rs):
- New `uninstall_command_from_paths` (POSIX + Windows) mirrors the
`anchored_command_from_paths` family. POSIX branches on brew formula /
volta / bun / pnpm / nvm|fnm|mise|homebrew / system. Windows branches on
volta / pnpm / npm.
- New `quoted_sibling_or_bare` (POSIX) + `quoted_sibling_or_bare_with_ext`
(Windows) deduplicate the `sibling_bin().map(quote).unwrap_or_else(bare)`
pattern across all uninstall branches.
- New `posix_quote_for_user_shell` whitelists `[A-Za-z0-9._/+=:@-]`; any
other character routes through `shell_single_quote`. Replaces
`quote_path_if_spaced` on the uninstall side because the fallback chain
is destructive (`rm -rf <pkg_dir>`), so a `;` / `$` / backtick in the
home dir would have let the chain inject extra commands.
- New `win_quote_path_for_user_shell` (no `%` 4x escape) splits from
`win_quote_path_for_batch`. Uninstall hints are copy-paste targets, not
`.bat + call` payloads, so the two-round percent expansion that
motivates the 4x escape doesn't apply.
- POSIX `nvm|fnm|mise|homebrew` branch appends physical-delete fallback
`; [ -e <bin> ] && rm -f <bin> && rm -rf <pkg_dir>` because nvm v18.20.8
was observed to silently no-op `npm rm -g` (exit 0 with `up to date`
message, no filesystem change). Anchored `<node_root>/{bin,lib}` layout
assumption documented as branch-specific.
- `ToolInstallation` gains `uninstall_command: String` and
`uninstall_command_needs_cmd_hint: bool`. The bool is computed as
`cfg!(target_os = "windows") && uninstall_command.contains('"')` — a
compile-time platform gate avoids false positives from POSIX
`shell_single_quote`'s `'"'"'` escape form, which also contains `"`.
- `enumerate_tool_installations` computes `source` once and threads it
into `uninstall_command_from_paths`, removing a redundant
`infer_install_source` call (review N1).
Frontend (src/components/settings/AboutSection.tsx + src/lib/api/settings.ts):
- Remove the JS helpers `buildUninstallCommand`, `dirOfPath`,
`isWindowsPath`, `shellQuote`, `TOOL_NPM_PACKAGES`. Conflict rows now
read `inst.uninstall_command` directly.
- Render a PowerShell-call-operator hint when
`inst.uninstall_command_needs_cmd_hint` is true. Avoids string-match
inference from the command (which fails on macOS user names containing
`'`, e.g. `o'leary`, because `shell_single_quote`'s escape contains `"`).
- `ToolInstallation` TS interface gains the two new fields.
i18n (zh/en/ja):
- New key `settings.toolUninstallPwshHint` documents the PowerShell
`&` (call operator) requirement for quoted paths.
Tests: 30 new cases under `commands::misc::tests::anchored_upgrade` cover
each POSIX branch (brew formula extraction, npm anchored with fallback,
pnpm, volta, bun, hermes), strong-quoting under `;` / `'` paths, the
fallback-only-for-anchored-branches inverse guard, plus mirror Windows
helpers for the user-shell quoter and a `path%foo%` integration case
demonstrating the literal `%` preservation contract. Total
`commands::misc` test count: 68.
Apply macOS-side anchored upgrades (9984eccb) to Windows. Six tools now
upgrade via an absolute path derived from the install actually hit by the
command line, instead of bare `npm i -g` landing on PATH-first npm. WSL
tools explicitly bypass anchoring (Windows-host paths invalid in distro).
Scope: 7+ Windows package managers compress to 3 idioms in practice
(Scoop/Chocolatey/winget/nvm-windows/MS Store all just install node; the
global-package layer is still npm):
- volta -> <sibling>\volta.exe install <pkg>
- pnpm -> <sibling>\pnpm.cmd add -g <pkg>@latest
- others -> <sibling>\npm.cmd i -g <pkg>@latest
Cross-platform:
- infer_install_source: add /volta/ (no dot) and /pnpm/ matches so
%LOCALAPPDATA%\{Volta,pnpm} route correctly.
- parent_dir: recognize both \ and /, take rightmost (Option<usize>
Ord lets None<Some, so rfind('\\').max(rfind('/')) auto-picks).
- npm_package_for, default_install, installs_anchored_command:
promoted from cfg(not(windows)) to all-platforms.
- plan_command_for: merged the two cfg branches; Windows+WSL short-
circuits to wsl_tool_action_shell_command (not static_fallback) so
the command shown to the user matches what build_tool_action_line
actually runs.
- ToolInstallation: add #[serde(skip)] real: PathBuf, populated by
enumerate, read by installs_anchored_command — no more double
canonicalize, and closes a TOCTOU window between enumerate and anchor.
Windows-only helpers (#[cfg(target_os = "windows")]):
- sibling_bin_with_ext reads fs to pick .cmd vs .exe (Node installer
drops .cmd, Volta drops .exe; pure string can't disambiguate).
- win_double_quote shared primitive; win_quote_path_for_batch handles
cmd token-boundary chars (space, &, (, ), ^, ;, <, >, |, ,) by
wrapping in quotes, and handles `%` via 4x escape (%%%%) to survive
both .bat parser expansion AND `call`'s own second percent-expansion
pass (Microsoft `call /?`: "percent (%) expansion is performed on
each parameter").
- wsl_tool_action_shell_command wraps tool_action_shell_command so
hermes inside WSL gets python3||python instead of Windows-target
py||python (py launcher is Windows-only, missing in WSL distros).
Wire-up:
- build_tool_action_line Windows: WSL -> wsl_tool_action_shell_command
+ wsl.exe wrap; native update -> enumerate + anchored + static
fallback. Everything gets `call ` prefix (.bat needs it for .cmd
without replacing the current script; harmless for .exe).
Tests: 33 new — 10 platform-agnostic (install_source_classification,
parent_dir_cases) + 23 cfg-gated Windows-only (anchored_upgrade_windows,
windows_helpers). Anchored expected values use a small expect_quoted_path
mirror in the test mod so assertions stay correct on Windows hosts whose
%TEMP% itself contains spaces/special chars (e.g. user accounts named
"John Doe"); the 7 hardcoded-literal win_quote_* unit tests independently
lock the quoting rules themselves, so mirror drift would be caught by
either side.
Limitation: Windows cfg-gated tests don't run on macOS cargo test.
Validated on macOS host: cargo fmt clean, clippy --all-targets 0
warnings, cargo test --lib 1322 passed. Windows CI runs only on release
tag (release.yml has windows-2022); production validation needs cargo
test on a Windows machine before broad rollout.
- misc: extend sibling_bin derivation to claude/brew/volta/bun in addition
to npm. Return Option<String> so the anchored-path invariant cannot be
silently violated — when bin_path has no parent directory, propagate
None and fall back to the static command at the call site instead of
emitting a bare command that depends on the GUI process PATH.
- misc: factor out quote_path_if_spaced to keep all five anchored
branches consistent when the resolved path contains spaces.
- AboutSection: add preflightTools lock + try/finally around
probeToolInstallations so fast double-clicks cannot race two probe
rounds into concurrent executeRun calls or duplicate confirm dialogs.
- Tests: cover sibling_bin None branch and space-quoting for every
anchored branch (claude/brew/volta/bun).
Bare `npm i -g <pkg>` was the install command for every managed tool, but
Anthropic and SST now both recommend their native installers over npm. The
update path already anchors to native installs (`claude update` and friends),
so install was the last place still forcing npm — even when the user's
intent was clearly to get the upstream-recommended install.
- New `install_command_for(tool)` (non-Windows only): claude and opencode
now run the upstream installer with a `|| npm i -g ...@latest` POSIX
short-circuit fallback, so the tool still installs if the URL is
unreachable.
- codex / gemini / openclaw / hermes pass through to the original static
command — they have no independent official installer today.
- `build_tool_lifecycle_command` now opens `set -o pipefail` alongside
`set -e`: without it, `curl ... | bash` where curl fails would have bash
exit 0 on empty stdin, silently masking the failure and skipping the
`||` fallback.
- 6 new `install_strategy` unit tests pin the per-tool install command
shape, mirroring the existing anchored_upgrade regression suite.
Update routing and Windows install behavior are unchanged.
When a managed-account provider (GitHub Copilot or Codex OAuth) is taken
over by the local proxy, the *_MODEL_NAME entries written to Claude live
config used to be snapshotted from the live file on disk, which still
held the *previous* provider's values during a switch. The result was a
stale display name lingering after the switch.
Read the model snapshot from the target provider's effective settings
(common-config merged) when the provider uses managed-account auth;
preserve the existing behavior of reading from the live config for
normal providers. The three takeover entry points now resolve the
effective provider up front so common-config writes flow through.
Rework the About tool-upgrade flow so upgrades target the install the
command line is actually using, instead of running bare `npm i -g` and
landing on PATH's first npm — which used to move upgrades to the wrong
location or fail outright when the resolved bin had no sibling npm.
Anchored upgrade command (update path, non-Windows):
- claude native installer (~/.local/share/claude/versions/) -> `claude update`
- Homebrew formula (canonical path under /Cellar/) -> `brew upgrade <formula>`
- volta / bun -> `volta install` / `bun add -g`
- Node managers with sibling npm (nvm/fnm/mise/homebrew-npm) -> that
directory's npm with `i -g <pkg>@latest`
- system / unknown sources (e.g. opencode install.sh under ~/.opencode/bin,
~/go/bin) -> fall back to the bare command, surfaced as anchored=false
so the UI shows an honest "default entry not determinable" hint instead
of pretending the upgrade is targeted.
Multi-install confirmation:
- When the probe finds >=2 installs, pop ToolUpgradeConfirmDialog showing
each install (source badge + path + version + Default badge) and the
resolved command before executing. Top-level text only asserts what is
universally true ("won't update all of them; see each tool below");
per-card text adapts to anchored vs unanchored.
- Pending upgrade state stores the full upgrade set; the dialog only
shows the subset that needs confirmation, so non-conflicting tools in
a batch still get upgraded after confirm.
Unified probe command:
- Merge the previous diagnose_tool_installations and plan_tool_upgrades
into a single probe_tool_installations returning installs + is_conflict
+ needs_confirmation + command + anchored. Diagnose button, post-upgrade
auto-diagnose, and pre-upgrade confirmation all share one IPC. The
diagnose button's previous Promise.all of 6 IPCs is now a single call.
resolve_path_default robustness:
- The previous lines().next() was poisoned by interactive .zshrc output
(welcome banners, p10k instant prompts, etc.), causing default-install
detection to fail and dropping multi-install scenarios into the
unanchored fallback even when one install was clearly the native one.
- Replaced with first_abs_path_line that scans for the first
absolute-path line and ignores noise, mirroring how try_get_version
already tolerates banner noise via a regex.
Diagnostic staleness fix:
- diagnoseToolSilently now clears stale diagnostics in the else branch
when the conflict no longer holds (previously only wrote on conflict,
never cleared, so externally resolving a conflict left the card stuck
on a stale list until the user clicked Diagnose).
- Auto-diagnose now triggers after any successful update, not only when
the version didn't change, so the card reflects the latest conflict
state even when an anchored upgrade succeeds while another install
remains.
Other:
- Shared ToolInstallRow between the conflict list and the confirmation
dialog removes the duplicated per-install row JSX.
- Drop the new shell_quote_path helper in favor of reusing the file's
existing POSIX-correct shell_single_quote, gated on whitespace.
- Collapse displayName lookup into a stable module-level helper to remove
the `as ToolName` cast and avoid recreating the closure each render.
- 17 backend unit tests covering anchored command generation across each
source, brew formula extraction, is_conflicting thresholds, default
install resolution, and the welcome-banner scenario.
- i18n (zh/en/ja): new keys for confirmation dialog
title/hint/will-run/confirm-button and the unanchored hint.
Add a "Diagnose installs" action to the About section that enumerates every
installation of each managed CLI, flags real conflicts (diverging versions
or mixed runnable state), and lists them under the affected tool card.
- backend: enumerate_tool_installations + diagnose_tool_installations command,
sharing build_tool_search_paths with the single-probe scan; resolve the PATH
default via the same login shell as version probing so the "Default" marker
matches what the command line (and an upgrade) actually targets
- frontend: global diagnose button left of Refresh, per-card conflict list,
and a copy-only uninstall command per install -- source-aware (npm/volta/
bun/pip) and anchored to that install's own npm to avoid removing the wrong
node version; marked with a red trash icon, never executed
- auto-diagnose silently after an upgrade that leaves the version unchanged
or installs something that can't run
- i18n: zh/en/ja
After an install/update command succeeds, re-probe the tool version: if it
still can't be detected the tool is installed-but-unrunnable (e.g. Node too
old), so report a warning toast and show "installed · can't run" with the
cause, instead of a misleading "success" plus a stale version number.
Read the backend `installed_but_broken` flag rather than matching error
text; the card's current-version line, action button (no useless "install"
for a broken tool), and a "check environment" hint all key off it. Drop the
now-redundant version-clearing setState (the refresh merge already nulls it),
flatten the action ternary, and use extractErrorMessage in the catch path.
Adds toolNotRunnable / toolActionInstalledNotRunnable / installedNotRunnable
/ toolCheckEnv i18n keys (zh/en/ja).
Unify the three probe paths (try_get_version / try_get_version_wsl /
scan_cli_version) on a cross-platform ShellProbe enum that distinguishes
"not installed" (shell exit 127) from "installed but --version failed"
(e.g. Node too old to run the freshly-upgraded CLI).
The found-but-broken case is now reported as-is and no longer falls back to
scan_cli_version. Previously the fallback surfaced a stale runnable copy
installed elsewhere (e.g. a Homebrew node vs the nvm node the upgrade wrote
to), making an upgrade look like it had no effect ("succeeded but version
unchanged").
Expose the state via a structured `installed_but_broken` flag on ToolVersion
so the frontend no longer infers it by string-matching the error text. Also
fixes a WSL path that could mislabel a genuinely-missing tool as broken, and
extracts the NOT_INSTALLED constant.
Update All previously sent every tool to the backend as one script, where
set -e (or errorlevel on Windows) aborts the whole batch on the first
failure, skipping the rest and not refreshing versions for tools that did
succeed. Run each tool as its own call instead: failures are isolated,
each tool's version refreshes as soon as it finishes, and the result is
summarized in a toast (success / partial / failure) listing which tools
failed.
Also derive an isAnyBusy flag and disable all install/update buttons,
the refresh button and the WSL shell selectors while any action is
running, so users can't trigger concurrent global npm/pip writes.
Add the toolActionPartial i18n key (zh/en/ja).
Extend scan_cli_version search paths with the PATH environment variable
and common Windows Node/version-manager locations (pnpm, Volta, nvm,
Scoop, Yarn). Entries from PATH are skipped when they point at the
Microsoft\WindowsApps App Execution Alias directory, which is what
previously caused bogus launches on Windows.
Add unit tests for PATH dedup, child-dir suffix expansion, and the
WindowsApps alias skip.
Add Hermes to the environment-check table and app-switcher list, fix the
OpenCode install command (opencode-ai), and describe the new update/install
flow. Align with the final implementation: drop the removed Install Missing
action, note that installs run silently with in-button progress, and rename
the section to Manual Install Commands (now collapsible).
List all managed apps with current/latest versions, with per-tool install
and update buttons plus an Update All action. Installs and updates now run
silently: the button shows a spinner for the full duration, versions refresh
automatically when done, and failures surface the backend error detail in a
toast.
While loading, the button keeps its label and only swaps the icon for a
spinner, so width stays constant across zh/en/ja instead of jumping when the
text changes (e.g. Update -> Updating...).
Also collapses and renames the install-commands area to Manual Install
Commands, and removes the managed-apps badge row and the Install Missing
batch button.
Expand tool version detection to all six managed apps (including Hermes
via PyPI), and add a run_tool_lifecycle_action command that installs or
updates CLI tools silently: it captures the subprocess output and blocks
until the command actually finishes instead of popping a terminal window
(Windows uses CREATE_NO_WINDOW). On failure the last lines of stderr are
returned so the UI can surface a useful message.
Also restores the Windows version-detection path by gating try_get_version
behind cfg(not(windows)) so it no longer risks triggering the App
Execution Alias / protocol handler that previously disabled Windows.
Harden the proxy module against panics by removing optimistic
unwrap()/expect() calls in favor of pattern matching and graceful
fallbacks:
- copilot_optimizer/cache_injector: bind Value::Array/String directly
instead of is_array()+as_array().unwrap(); use is_none_or and in-place
string mutation
- hyper_client: gate the raw-write path with if-let + filter instead of
has_cases + unwrap()
- gemini_shadow: recover poisoned RwLock via into_inner() rather than
panicking, with warn logging
- streaming_codex_chat: replace expect("tool state exists") with
let-else (return/continue)
- merge_tool_results: early-return when messages is absent
- sse: fall back to from_utf8_lossy on the UTF-8 boundary slice
No behavior change on the happy path; all proxy tests pass.
CI on main was red because two tests did not follow intentional changes
that were already merged into the codebase:
- codexChatProviderPresets.test.ts still expected the "Kimi For Coding"
preset, which was removed in 16c3ef3f. Drop the corresponding entry
from the expected presets map.
- import_export_sync.rs still asserted the legacy per-provider
model_provider id ("rightcode"), but 9fac15b8 unified Codex
third-party providers into the stable "custom" history bucket. Mirror
the assertion update already applied to provider_service.rs.
No production code changed; both fixes only update test expectations.
No-argument tool calls produced empty argument strings that passed
through both the streaming response path and the request transform
verbatim. Strict OpenAI-compatible upstreams (e.g. Minimax) reject
`arguments: ""` with a 400 "invalid function arguments json string",
while lenient ones (OpenAI, Kimi) silently treat it as an empty object.
Add canonicalize_tool_arguments / canonicalize_tool_arguments_str helpers
that coerce empty/whitespace arguments to "{}" and apply them at the four
tool-call argument sites (streaming finalize + three request/response
transform paths). Leave function_call_output content untouched, where an
empty string is valid.
Add a "Reasoning Auto-Detection" subsection to the Codex local-routing
manual (zh/en/ja) with the provider capability matrix, and record the
feature plus the streaming-usage and tool-call reasoning backfill fixes
in the changelog. Drop the removed Kimi For Coding preset from the
changelog's Codex Chat preset list.
Auto-detect each Chat-routed Codex provider's reasoning interface from
its name, base URL, and model, then inject the matching thinking
parameter without manual configuration:
- Platform-first inference (OpenRouter, SiliconFlow) overrides model
rules, since the same model exposes different reasoning controls
depending on the hosting platform.
- Effort tiers are forwarded only to providers that support them
(DeepSeek, OpenRouter, and StepFun's step-3.5-flash-2603); on/off-only
providers (Kimi, GLM, Qwen, MiniMax, MiMo, SiliconFlow) drop the level
instead of sending a field the upstream rejects.
- OpenRouter uses the native reasoning:{effort} object, clamps max to
xhigh (its enum has no max), and forwards an explicit effort:"none" so
reasoning can be turned off.
- StepFun falls back to inference so per-model effort support is honored
(the static preset would have forced effort on step-3.5-flash too).
Includes the Codex provider-form reasoning controls, i18n strings
(zh/en/ja), and response-side reasoning extraction.
Responses-to-Chat conversion did not set stream_options.include_usage,
so OpenAI-compatible upstreams (kimi/MiniMax, etc.) omitted the trailing
usage chunk in streaming mode. This caused token/cost/cache stats to be
recorded as zero for third-party streaming requests routed through the
Codex Chat path.
Inject include_usage when the request is streaming, merging into any
client-provided stream_options instead of overwriting it. Add tests for
the streaming, non-streaming, and merge cases.
The Kimi For Coding preset pins users to a single coding-only model
(kimi-for-coding), restricting model selection. Drop it from the Codex
presets and trim the corresponding row in the trilingual user manual.
The general Kimi preset (Moonshot platform) is kept, and presets for
other apps are unchanged.
Thinking models like kimi/Moonshot and DeepSeek reject any assistant
message carrying tool_calls without a non-empty reasoning_content. When
cross-turn history recovery misses (proxy restart drops the in-memory
cache, ambiguous call_id, or a turn produced no reasoning upstream), the
converted assistant message has none and the whole request fails with
'reasoning_content is missing in assistant tool call message'.
Add a final-stage backfill that runs after all input items are
processed, so genuine trailing reasoning items still attach first and a
placeholder is only injected when none remains. Mirrors the placeholder
behavior in transform::anthropic_to_openai_with_reasoning_content.
ClaudeAPI supports the health/stream check, but the third_party
category triggers the isClaudeThirdParty gate in ProviderCard that
disables the model test button for third-party Claude providers.
Recategorizing as aggregator restores the test action; the partner
star is unaffected since it is driven by isPartner, not category.
Applied to both the Claude Code and Claude Desktop presets.
Backfill the empty [Unreleased] CHANGELOG section with the Codex Chat
Completions feature (Chat-to-Responses bridge, 23 third-party presets,
model mapping table, Stream Check routing, error-envelope conversion,
"custom" history bucket) plus the community fixes landed since v3.15.0.
Update the zh/en/ja user manual: split the Codex preset tables by
upstream protocol (native Responses vs Chat Completions), add a "Codex
Local Routing and Model Mapping" section covering the Needs Local
Routing toggle and the model catalog, and note Chat-format probing in
the Stream Check guide. Manual wording matches the live UI strings.
The Codex Chat-to-Responses bridge already rewrites successful upstream
responses to the Responses shape, but the error branch in
handle_codex_chat_to_responses_transform passed Chat-shaped error bodies
through untouched (MiniMax base_resp, raw OpenAI Chat error, text/plain
"Unauthorized" pages, etc.), leaving Codex clients unable to recognize the
error.
Add chat_error_to_response_error in transform_codex_chat to regularize all
upstream error shapes into the standard {error: {message, type, code, param}}
envelope, then wire it through a new handle_codex_chat_error_response that
preserves the original HTTP status code. Non-JSON error bodies (HTML, plain
text) are wrapped as Value::String and truncated to 1KB at a UTF-8 char
boundary to keep diagnostic context without flooding the response.
Also fix a pre-existing append-vs-insert pitfall in three rebuilt-body
branches (Claude transform, Codex Chat normal, Codex Chat error): http
Builder::header is append, so leaking the upstream Content-Type produced
two Content-Type headers when the rewritten body was JSON. Remove the
upstream value before writing application/json.
Codex Chat providers (apiFormat=openai_chat, e.g. DeepSeek, MiniMax, Kimi)
were incorrectly probed via /v1/responses by Stream Check, which the upstream
rejects. Detect chat routing via codex_provider_uses_chat_completions and
issue the probe against /chat/completions with a Chat-shaped body.
Also align URL fallback order with the production CodexAdapter::build_url:
origin-only base URLs (https://api.deepseek.com) must hit /v1/<endpoint>
first; bare /<endpoint> only as a fallback. The previous order made any
non-404 error on the bare path (401/400/405) flag the provider as down even
though /v1/<endpoint> would have succeeded.
- Lift origin-only detection into proxy::providers::is_origin_only_url so
both build_url and Stream Check share a single source of truth.
- Collapse resolve_codex_stream_urls and resolve_codex_chat_stream_urls into
a generic resolve_codex_endpoint_urls(base_url, is_full_url, endpoint),
which also gives the Responses path a symmetric "full endpoint without
is_full_url flag" fallback for free.
- Restrict reasoning_effort propagation in the Chat branch to OpenAI o-series
models, mirroring transform_codex_chat's runtime check.
Reframe OFF/ON hints as action guidance (when to enable) rather than
scenario descriptions, mirroring the Claude Desktop model mapping copy
style. Synced across zh/en/ja locales and the component-level
defaultValue fallback. The switch label "Needs Local Routing" is kept
unchanged to preserve the routing-layer semantics specific to Codex.
MiniMax's OpenAI-compatible chat endpoint strict-rejects any non-leading
role=system message with "invalid params, chat content has invalid message
role: system (2013)". The Codex client uses role=developer (and occasionally
role=system) to inject collaboration_mode / permissions / skills blocks
mid-conversation, and responses_role_to_chat_role maps both to chat's
system role. The converted messages array therefore frequently contained
system entries past index 0, which DeepSeek and OpenAI tolerate but MiniMax
flags as 2013.
Collapse all system messages into a single leading system message before
returning the chat request. The rewrite preserves every system fragment
(joined by "\n\n" in original order) and leaves non-system messages
untouched, so it is lossless for permissive backends as well.
- Add collapse_system_messages_to_head in transform_codex_chat.rs.
- Run it on the messages vector at the end of responses_to_chat_completions
before serializing.
- Cover the new path with two unit tests: one repros the MiniMax-shaped
input (developer items between users) and asserts no system role past
index 0; the other verifies non-system order is preserved and content
is joined with "\n\n".
Enable openai_chat routing with explicit model catalogs across the major
Chinese/Asian providers (DeepSeek, Zhipu GLM, Kimi, MiniMax, Baidu Qianfan,
Bailian, StepFun, Volcengine Agent Plan, BytePlus, DouBaoSeed, ModelScope,
Longcat, BaiLing, Xiaomi MiMo, SiliconFlow, Novita AI, Nvidia, etc.). Each
preset declares its context window so the UI can size catalog rows when the
preset is picked.
Also lands two consistency fixes uncovered along the way:
- Include setCodexCatalogModels in resetCodexConfig's useCallback deps to
match the new third parameter it consumes.
- Realign TheRouter Codex test to the "custom" model_provider bucket
established by the recent third-party unification; the previous assertion
predated that refactor and had been failing on HEAD.
Editing the currently active Codex provider triggered `read_live_settings`,
which returned only { auth, config }, omitting `modelCatalog`. The form's
mapping table then initialized to empty, and a subsequent save wiped the
DB's `modelCatalog` field — silently destroying user-configured model
mappings after every CC Switch restart.
The mappings already live on disk as a projection in
`~/.codex/cc-switch-model-catalog.json` (pointed at by `config.toml`'s
`model_catalog_json`). Reverse-parse that file so live reads return the
same shape the save path writes.
- Add `read_codex_model_catalog_simplified_from_live` to recover
`{ model, displayName?, contextWindow? }` entries from the catalog file.
- Skip user-managed external catalogs (filename != cc-switch-model-catalog.json)
so we don't downgrade their richer structure to the simplified table.
- Squash display_name == slug and context_window == default (config's
`model_context_window`, or 128_000 fallback) so blank inputs round-trip
back to blank instead of materializing fallback values in the UI.
- Collapse all failure modes (missing file, parse error, no matching
field) to Ok(None) so the editor stays openable when the projection
file is absent or corrupt.
- Wire the new function into `read_live_settings`'s Codex branch.
- Cover the new pure helpers with 7 unit tests in codex_config::tests.
Codex filters resume history by `model_provider`, so switching between
provider-specific ids like `rightcode` and `aihubmix` made past sessions
appear to vanish. Collapse all third-party providers into a single
stable bucket so cross-switch history stays visible.
- Normalize live `model_provider` to "custom" on every Codex write
(reserved built-in ids like openai/ollama are preserved).
- Add device-level one-shot migration that rewrites historical JSONL
session files and the `state_5.sqlite` threads table from legacy
provider ids into the "custom" bucket. Backs up originals under
`~/.cc-switch/backups/codex-history-provider-migration-v1/` and uses
the SQLite Backup API for the state DB.
- Record completion in `settings.json` under `localMigrations` so the
migration is strictly idempotent across launches.
- Update Codex provider preset templates to emit `model_provider = "custom"`
out of the box.
When Codex client sends a model from the catalog (e.g., user selected via /model),
preserve that choice instead of always replacing with config.toml's default model.
- Check if request model exists in modelCatalog before falling back to config model
- Remove CODEX_CHAT_CLIENT_MODEL constant (no longer needed)
- Add test coverage for catalog model preservation
Break bidirectional sync cycle between catalogRows (child) and catalogModels (parent):
- Remove catalogModels from child→parent effect dependencies
- Track last sent data in ref to avoid redundant callbacks
- Sync ref when parent pushes new data to prevent false positives
Fixes severe UI jittering when adding/editing catalog entries.
- Remove mergeCodexDefaultCatalogModelForSave implicit injection (P1)
The model mapping table is now the single source of truth; no hidden
entries are prepended on save.
- Sync first catalog row model into config.toml on save
Ensures Codex default request model matches the table's first entry
instead of retaining a stale template value.
- Remove API Format selector from CodexFormFields (P3)
wire_api is always 'responses'; the selector confused users into
thinking they were changing the upstream protocol. Only the 'Needs
Local Routing' toggle remains.
- Add restart hint to model mapping i18n text (P2)
model_catalog_json is loaded at Codex startup; users are now informed
that a restart is needed after changes.
- Unify write_codex_live_with_catalog helper (P4)
Replaces three scattered prepare+write call sites in config.rs,
provider/live.rs, and proxy.rs with a single entry point.
- Clean up useCodexConfigState dead state (P3 follow-up)
Remove codexModelName, codexContextWindow, codexAutoCompactLimit and
their handlers/effects since no component consumes them after the UI
consolidation.
- Add Codex provider API format selection and model mapping for Chat-only upstreams.
- Convert Codex Responses requests to Chat Completions and rebuild Chat responses as Responses output.
- Preserve reasoning_content across non-streaming, streaming, tool calls, and previous_response_id follow-ups.
- Add a bounded Codex Chat history cache for restoring tool calls before tool outputs.
- Cover Chat bridge, compact routing, streaming, and history recovery with focused tests.
- Remove personal tap requirement from all READMEs (en/zh/ja)
- Update v3.15.0 release notes to highlight official cask availability
- Add celebration banner noting Homebrew official repository inclusion
- Simplify installation to single command: brew install --cask cc-switch
- Split leading <think> blocks from Chat content into Responses reasoning output.
- Keep assistant text output free of inline thinking tags.
- Support streamed inline thinking blocks before normal text deltas.
- Add regression coverage for MiniMax-style inline thinking responses.
- Add a Codex API format selector and routing badge for Chat Completions providers.
- Convert Codex Responses requests to upstream Chat Completions when routing is required.
- Convert Chat Completions JSON and SSE responses back to Responses format.
- Keep generated Codex wire_api values on Responses for Codex compatibility.
- Add i18n labels, provider metadata handling, and focused conversion tests.
- Provider: add uses_managed_account_auth / is_github_copilot helpers
to identify managed-account providers (GitHub Copilot / Codex OAuth)
- ProxyService: choose auth policy by provider type when taking over
Claude Live config. Managed accounts drop token env keys and write
only the ANTHROPIC_API_KEY placeholder; other providers keep the
existing ANTHROPIC_AUTH_TOKEN fallback behavior
- Forwarder: add outbound guard that refuses to send the PROXY_MANAGED
placeholder upstream to *.githubcopilot.com and chatgpt.com
/backend-api/codex
- Add unit tests covering detection, injection, and the outbound guard
- Remove LionCC sponsor entry from all README files (en/zh/ja)
- Remove LionCCAPI presets from all provider configs
- Remove lionccapi i18n keys from all locales
- Keep lioncc.png icon file as requested
- Sync zh/en/ja manuals with Claude Desktop and Hermes support
- Update install requirements, official channels, and release asset guidance
- Document Usage Hero, Codex OAuth live models, Save Anyway, Hermes sessions, and Warp launch
- Correct tray and app-scope descriptions to match current implementation
* fix(skills): install correct skill from skills.sh search results
When multiple skills share the same directory name across different repos,
SkillCard was passing directory to onInstall/onUninstall, causing handleInstall
to always match the first result. Switch to using the unique key field
(directory:repoOwner:repoName) for precise identification.
* test(skills): add regression test for skills.sh install by key
Verifies that clicking install on the second card when two skills share
the same directory name correctly installs the second skill, not the first.
---------
Co-authored-by: mrzhao <mrzhao@iflytek.com>
The step was 0.01, preventing input of prices like DeepSeek's cache read
cost ($0.0028/million tokens). Extract step value to a constant and apply
to all four price fields.
Closes#2503
When Ghostty is already running, `open -a` silently ignores `--args`,
and `open -na` clones all existing tabs into the new instance.
Add a dedicated `launch_macos_ghostty` that uses
`--quit-after-last-window-closed=true` and `-e bash <script>` to spawn
a single clean window running claude.
Also change `launch_macos_open_app` from `open -a` to `open -na` so
other terminals (Alacritty/Kitty/WezTerm/Kaku) correctly open a new
window when already running.
Closes#2798
* feat: add Xiaomi MiMo token plan presets
* fix: update Xiaomi MiMo provider presets
* fix: align MiMo V2.5 model specs with official documentation
- Update maxTokens from 32000 to 131072 (128K) for mimo-v2.5-pro and mimo-v2.5
- Update contextWindow from 262144 to 1048576 (1M) for mimo-v2.5
- Aligns with official specs from Xiaomi MiMo documentation
- Ensures consistency between OpenClaw and OpenCode presets
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix(gemini-native): resolve functionResponse.name and thought_signature replay for synthesized tool call IDs
Two related bugs in the Gemini Native format conversion layer:
1. **functionResponse.name resolution** (422 error): When Gemini's parallel
function calls omit the id field, cc-switch synthesizes gemini_synth_*
IDs. These are stored in the shadow store but can be lost in long sessions,
causing subsequent tool_result blocks to fail. Fix: pre-scan all assistant
messages in the request body to seed the tool_name_by_id map, and add a
last-resort fallback that scans the current content array for matching
tool_use blocks.
2. **thought_signature replay** (400 error): The Anthropic Messages format
strips thoughtSignature from tool_use blocks, but Gemini requires it on
every functionCall in multi-turn tool-use exchanges. Fix: build a
thought_signature_by_id map from shadow turns and attach thoughtSignature
when converting tool_use back to functionCall.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* style: run cargo fmt on transform_gemini.rs
---------
Co-authored-by: Tiancrimson <tiancrimson@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
* fix(session): 修复session log模式下子Agent token统计遗漏
collect_jsonl_files() 只扫描了两层目录,遗漏了子Agent的JSONL日志文件,
导致子Agent的独立token使用数据完全未统计到session费用中。
(仅影响session log模式,proxy代理模式不受影响)
* refactor(session): optimize collect_jsonl_files logic
- Replace two independent if statements with if-else for mutually exclusive conditions
- Remove unnecessary clone() when pushing file paths
- Add clarifying comments for main session vs subagent files
- Apply cargo fmt for consistent formatting
Performance improvement: Eliminates redundant clone() operations when
processing .jsonl files, as a path cannot be both a file and a directory.
---------
Co-authored-by: Jason <farion1231@gmail.com>
- Add active flag pattern to 3 useEffect hooks in App.tsx to prevent
event listener leaks when component unmounts before async setup completes
- Add guard check in useSettings.ts to prevent undefined from being
stored in localStorage when payload.language is missing
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
- Add the Claude Desktop provider guide in English, Chinese, and Japanese.
- Add localized screenshots for import, provider setup, model mapping, and local routing.
- Link the guide from the v3.15.0 release notes and user manual indexes.
- Add Claude Desktop Official to the Claude Desktop preset list.
- Treat selected official presets as official mode in the form.
- Cover the official preset with a preset-order regression test.
- Mark `should_force_identity_encoding` as test-only.
- Keep runtime forwarding behavior unchanged.
- Verified with local CI checks and no-bundle Tauri build.
Remove the category-based grouping logic from ProviderPresetSelector,
letting the array position in each preset config file be the single
source of truth for display order. Move partner presets (PatewayAI,
火山Agentplan, BytePlus, DouBaoSeed) right after Shengsuanyun across
all 6 config files so they appear earlier in the UI.
Add BytePlus (international Volcengine) to Claude, Claude Desktop,
Hermes, OpenCode, and OpenClaw with byteplus icon, 256K context window,
and trilingual promotion text.
Switch Anthropic-format base URL from /api/coding to /api/compatible,
update website/apiKey URLs to Volcengine console with tracking params,
and promote DouBaoSeed to partner with trilingual promotion text.
Add RelaxyCode as a new third-party provider with support for:
- Claude Code preset (Anthropic native protocol)
- Codex preset (gpt-5.5 model)
- Claude Desktop preset (direct mode with passthrough routes)
RelaxyCode is an enterprise-grade AI programming platform providing
unified access to Claude Code, Codex, and Gemini CLI models.
Add RunAPI as a new partner provider with support for:
- Claude Code preset (Anthropic native protocol)
- Codex preset (gpt-5.5 model)
- Claude Desktop preset (direct mode with passthrough routes)
- OpenCode preset (@ai-sdk/anthropic)
- OpenClaw preset (anthropic-messages protocol)
- Hermes preset (anthropic_messages mode)
- Icon configuration (runapi.jpg)
- i18n support (zh/en/ja) with ¥14 free credit promotion
RunAPI is a high-performance AI model API gateway supporting 150+
mainstream models (OpenAI, Claude, Gemini, DeepSeek, Grok) with
prices as low as 10% of official rates.
Add ClaudeCN as a new partner provider with support for:
- Claude Code preset (Anthropic native protocol)
- Codex preset (gpt-5.5 model)
- Claude Desktop preset (direct mode with passthrough routes)
- OpenCode preset (@ai-sdk/anthropic)
- OpenClaw preset (anthropic-messages protocol)
- Hermes preset (anthropic_messages mode)
- Icon configuration (claudecn.png)
- i18n support (zh/en/ja) with enterprise service promotion
ClaudeCN is an enterprise-grade AI gateway operated by a registered
company, supporting enterprise procurement processes with corporate
payments, contracts, and compliance guarantees.
Add ClaudeAPI as a new partner provider with support for:
- Claude Code preset (using ANTHROPIC_AUTH_TOKEN field)
- Claude Desktop preset (direct mode with passthrough routes)
- Icon configuration (ClaudeApi.png)
- i18n support (zh/en/ja) with test credit promotion
ClaudeAPI provides official Anthropic API keys and AWS Bedrock
routing with support for Tool Use and 1M context.
- Change mode from "proxy" to "direct" for 20 third-party/aggregator providers
- Simplify PipeLLM from mappedRoutes to passthroughRoutes for consistency
- Reduces unnecessary proxy layer overhead for providers that support direct API calls
Affected providers: ShengSuanYun, AIHubMix, DMXAPI, PackyAPI, PatewayAI,
Cubence, AIGoCode, RightCodes, AICodeMirror, AICoding, CrazyRouter,
SSSAICode, ModelVerse, CompShare, MicuAPI, CTOK, E-FlowCode,
VibeCodingAPI, LemonData, PipeLLM
Add PatewayAI as a new partner provider with support for:
- Claude Code preset (using ANTHROPIC_API_KEY field)
- Codex preset (gpt-5.5 model)
- Claude Desktop preset (proxy mode with passthrough routes)
- Icon configuration (pateway.jpg)
- i18n support (zh/en/ja) with $3 registration bonus promotion
PatewayAI provides reliable API routing services for Claude Code,
Codex, and Gemini models.
- Forwarder buffers non-streaming bodies and primes streaming first
chunk before signaling success, so body timeouts and SSE first-chunk
failures route through the circuit breaker instead of being recorded
as success on response-header arrival
- Atomic enable-failover: switch to P1 before persisting the flag, and
roll back auto-added queue entries when the switch is rejected
(e.g. official providers)
- Hot-reload circuit breaker config on per-app proxy config change
instead of waiting for a proxy restart
- FailoverToggle / FailoverQueueManager / AutoFailoverConfigPanel
require proxy takeover for the active app; the backend command also
rejects enabling when takeover is off
- ProviderHealthBadge consumes the backend is_healthy flag instead of
hardcoding the 5-failure threshold
Cleanup:
- impl From<&AppProxyConfig> for CircuitBreakerConfig and use it from
the command layer
- Collapse three identical TabsContent blocks into a single map
Add visual indicators for routing capabilities on provider cards:
- Claude Code: "Needs Routing" badge for non-official providers with non-anthropic API formats
- Claude Code: "No Routing Support" badge for official providers
- Codex: "No Routing Support" badge for official providers
The badges help users understand which providers support format conversion through routing.
Replace all openclaudecode.cn URLs with micuapi.ai across all
provider presets (Claude, Codex, Hermes, OpenClaw, OpenCode,
Claude Desktop). This includes website URLs, API key URLs, and
base URLs.
Update all CrazyRouter baseURL configurations from crazyrouter.com
to cn.crazyrouter.com across all supported applications (Claude,
Codex, Gemini, Hermes, OpenClaw, OpenCode, Claude Desktop).
Website and registration URLs remain unchanged.
Most third-party Claude Code providers now reject requests from
non-official clients, so the model test button would just produce
noisy failures (or worse, trigger risk controls on the provider
side). Treat third-party Claude providers the same way as official /
Copilot / Codex OAuth: pass onTest=undefined so ProviderActions
renders the test button in its existing disabled visual state.
- Add `get_codex_oauth_models` Tauri command reusing the managed OAuth
access token to hit `chatgpt.com/backend-api/codex/models`; HTTP and
multi-shape JSON parsing live in `services::codex_oauth_models` so the
command stays thin.
- Unify the Claude form's "fetch models" button across normal / Copilot /
Codex OAuth presets, drop the auto-load effect for Copilot in favor of
explicit clicks, and guard against stale responses with a requestId ref.
- Add Vitest coverage for both Copilot and Codex OAuth paths asserting no
request on mount and the correct account id on click; add Rust unit
tests for the four model-list payload shapes.
When proxy takeover is active, write per-role *_MODEL aliases for routing
and *_MODEL_NAME with the upstream provider's real model name so the
Claude Code model menu reflects the active provider instead of stale
display names from a previous switch. Preserves the [1M] capability marker
for Sonnet/Opus, and strips it from implicit display names.
* model pricing routing: extend prefix-match families (gpt-/o1-o5/
gemini-/deepseek-/qwen-/glm-/kimi-/minimax-) with per-family dash
thresholds so short base IDs like gpt-5 no longer mis-match
gpt-5-mini; strip ISO and 8-digit date suffixes via UTF-8-safe
byte matching so claude-haiku-4-5-20251001 falls back to
claude-haiku-4-5 pricing
* SSE collector: SseUsageFinishGuard (RAII) guarantees finish() on
early return or panic; AtomicBool fast path lets push() skip the
Mutex once first-event time is recorded
* validation: shared validate_cost_multiplier / validate_pricing_source
helpers across DAO and service layers; PRICING_SOURCE_RESPONSE /
PRICING_SOURCE_REQUEST constants replace string literals; price
fields in update_model_pricing now reject empty / non-decimal /
negative input before INSERT
* backfill: add backfill_missing_usage_costs_for_model so a single
price edit only scans matching rows instead of the full log table;
startup backfill remains full-scan
* session_usage{,_codex,_gemini}: share find_model_pricing helper from
usage_stats; metadata_modified_nanos centralizes mtime precision
* frontend: NON_NEGATIVE_DECIMAL_REGEX + isNonNegativeDecimalString
replace three copies of the same multiplier regex; isUnpricedUsage
surfaces zero-cost rows that have usage tokens (cached per row to
avoid double evaluation); invalidate usageKeys.all on pricing mutate
so backfilled rows refresh
* stream_check: thread Result from get_auth_headers via map_err so
the workspace builds again
* forwarder: scope rectifier / budget-rectifier flags per-provider so
failover can still apply rectification on the next attempt
* forwarder: categorize before record_result; route NonRetryable and
ClientAbort through release_permit_neutral so client-side failures
don't pollute circuit breaker or DB health
* handler_context: parse Gemini model from uri.path() and strip both
?query and :action verb defensively in extract_gemini_model_from_path
* forwarder + response_processor + handlers: introduce
ActiveConnectionGuard (RAII) so active_connections decrement covers
the full streaming body lifetime, not just response headers
* claude_desktop_config: use sort_by_key to clear the clippy gate
The signature (RECT-003) and budget (RECT-012) rectifier branches each
carried ~50 lines of identical "provider error -> record + continue /
client error -> release permit + return" handling. The only piece that
varied between them was a log label ("整流" vs "budget 整流").
Move the shared logic into RequestForwarder::handle_rectifier_retry_failure
that returns Option<ForwardError> — None means "continue to the next
provider", Some(err) means "terminal failure, return to the client".
Each call site shrinks from ~50 lines to ~17, drops one level of
indentation, and the two branches now provably cannot drift apart.
forwarder.rs nets ~40 lines smaller.
claude.rs and gemini.rs each defined an identical `hv` closure that wrapped
`HeaderValue::from_str` into a ProxyError::AuthError result, and codex.rs
spelled the same conversion out inline. /simplify reviewers flagged this
as drift-prone copy-paste.
Move the conversion into a single `pub fn auth_header_value` in
providers/adapter.rs and have the three adapters import it locally. Same
error wording everywhere, one place to update if HeaderValue semantics
ever change.
The backend already understands `::` -> `::1` and wraps IPv6 literals
in brackets (services/proxy.rs), but the panel's save-time validator
only accepted localhost, 0.0.0.0, and IPv4 dotted-quads. Users who
wanted to listen on an IPv6 loopback had to bypass the UI and edit
config directly.
Add an isValidIpv6 helper that requires at least one ':' and round-trips
through `new URL('http://[<addr>]/')` so the platform's built-in IPv6
parser does the heavy lifting (covers compressed `::`, full 8-group
form, zone IDs). Update the invalidAddress copy in zh / en / ja so the
error message reflects the new accepted set.
The forwarder used to call client.post(&url) / http::Method::POST in
both the reqwest and hyper paths, and the Gemini route table only
registered POST /v1beta/*. As a result anything the Gemini SDK / CLI
sent as GET (models list, models/<id> info) hit a 404 at the router
and bypassed the local proxy's stats, rectifiers, and failover.
Thread the request method end-to-end:
- ProviderAdapter forwarder API now takes the http::Method by reference
per attempt and dispatches client.request(method, &url) for reqwest
and method.clone() for the hyper raw path.
- All five callers in handlers.rs (handle_messages_for_app for Claude /
Claude Desktop, handle_chat_completions, handle_responses,
handle_responses_compact, handle_gemini) pull the method out of the
incoming axum::extract::Request and pass it on.
- handle_gemini tolerates an empty body (GET endpoints have none) and
the forwarder skips serializing / sending a body for GET / HEAD —
attaching JSON to a GET makes Gemini reject the request.
- server.rs swaps the Gemini routes to any(handle_gemini) so the same
handler handles GET / POST / PUT / DELETE, and adds /gemini/v1/*
for the GA path version.
Three statistics-shape issues fixed together so the dashboard reflects
client requests, not provider attempts:
1. active_connections never moved off zero — the field had no caller in
the entire crate. Wrap forward_with_retry into a thin entry point
that saturating_add(1) on enter and saturating_sub(1) on exit; every
inner return path is covered automatically.
2. total_requests counted attempts, not requests. A single client call
that failed over P1 -> P2 -> success was recorded as
total=2 / success=1 -> 50% success rate. Move the increment and the
last_request_at refresh into the wrapper so they fire once per
client request regardless of how many providers were tried.
3. current_provider / current_provider_id stay inside the inner loop
because they are intentionally per-attempt ("what am I trying right
now?") — moving them would break the live-failover indicator.
Refactor: split forward_with_retry into a public wrapper + private
forward_with_retry_inner. Every existing `return Err(...)` inside inner
remains correct because the wrapper always runs the decrement on its
return.
The UI has exposed "请求失败时的重试次数 (0-10, default 3)" since the
auto-failover panel was added, but the value was silently dropped —
RequestForwarder never received it and the per-provider loop walked the
whole list regardless. From the user's perspective the setting was
inert.
Thread AppProxyConfig.max_retries through create_forwarder into
RequestForwarder, derive max_attempts = max_retries + 1 (so max_retries=0
matches the UI copy "0 retries" = single attempt), and break the loop
once attempts hit the cap. The check is placed before the circuit
breaker allow-permit so an over-cap iteration does not waste a HalfOpen
probe slot.
When auto-failover is disabled we also force max_retries to 0, mirroring
how timeouts already bypass in that mode — "no failover" should mean
"one provider, one try", not "limited retries against the same list".
The Chat-Completions transformer used to forward tool_choice verbatim,
but the two APIs disagree on shape:
Anthropic "any" | {"type":"tool","name":"X"}
OpenAI Chat "required" | {"type":"function","function":{"name":"X"}}
Pass-through made the upstream return 400 for any tool-forcing client
(Claude Code, Copilot, etc.). The Responses-API transformer already had
the equivalent map_tool_choice_to_responses helper; this commit adds a
sibling map_tool_choice_to_chat with the chat-specific *nested* function
selector and five regression tests covering string / object × any /
auto / none / tool.
The two helpers are intentionally not merged: the difference between
flat and nested function selectors is exactly what the original bug
was, so keeping them as separate self-documenting functions reduces the
chance of the same regression returning.
Two related changes to make per-provider failover behave correctly.
1. Bucket UpstreamError by status code in categorize_proxy_error.
The old "every UpstreamError is Retryable" rule meant a malformed
client request (400 / 422) would be replayed against every provider
in the queue: errors amplified N-fold, the circuit breaker accrued
unwarranted failure counts, and quota was burned. Now
400 / 405 / 406 / 413 / 414 / 415 / 422 / 501 are NonRetryable since
the request itself is wrong and no provider will accept it.
401 / 403 / 404 / 408 / 409 / 429 / 451 and all 5xx remain Retryable
because the next provider may carry a different key, quota, region,
or model mapping.
2. Make the rectifier-retry path participate in failover.
Both the signature (RECT-003) and budget (RECT-012) rectifier branches
used to "return Err(...)" after the retry failed, short-circuiting the
per-provider loop. A provider-side failure (5xx / Timeout /
ForwardFailed) now records the circuit breaker, accumulates into
last_error / last_provider, and "continue"s to the next provider —
matching the normal Retryable arm. Client-side failures still return
immediately since a different provider cannot fix a malformed payload.
Two related drift bugs in the takeover state machine:
1. The "already taken over?" guard used has_backup OR live_taken_over, so
either condition alone would short-circuit. After a user or anomalous
flow restores Live manually the backup row still made set_takeover
return success, leaving the UI claiming takeover while requests bypass
the local proxy. Tighten to AND so the rebuild branch repairs the two
"split brain" states (backup-only and placeholder-only).
2. Disabling takeover called the bare restore_live_config_for_app, which
silently Ok()s when the backup is missing. If the backup was lost while
Live still held proxy placeholders (PROXY_MANAGED token / local proxy
URL), the client config was left broken with no error surfaced. Route
the disable path through the already-existing
restore_live_config_for_app_with_fallback (backup → SSOT → cleanup).
The line 354 takeover-failure rollback intentionally keeps the bare
variant since that path must preserve the backup for retry.
split('/') strips the slashes, so find(|s| s.starts_with("models/")) never
matched any segment and request_model fell through to "unknown" for every
Gemini call, poisoning usage records, per-request billing, and logs.
Match the literal "models" segment and take the next one, stripping any
:action suffix and query string. The extraction is now a pub(crate) free
function so it can be unit-tested directly; seven regression tests cover
action suffixes, dotted versions, the /gemini/ proxy prefix, query
strings, the bare list endpoint, and missing-segment paths.
User-pasted API keys can contain control chars or CR/LF that make
HeaderValue::from_str return Err; the previous unwrap inside every
adapter turned such input into a process-wide panic instead of a request
error. The trait now returns Result<_, ProxyError>; Claude/Codex/Gemini
impls propagate ProxyError::AuthError so the client sees a 401 with the
underlying parse error instead of a crash. Adds a regression test that
pastes a CRLF-containing key and asserts AuthError.
- Split CostCalculator into per-app cache semantics: Anthropic's
input_tokens is already fresh input, while Codex/Gemini include
cached tokens in their prompt count. The old shared formula
double-subtracted cache_read for Claude, under-billing input cost.
- Backfill now reads cost_multiplier from the per-log snapshot column
instead of re-querying providers.meta, so historical rows are no
longer rewritten with the current multiplier.
- Move the "pricing not found" warn out of find_model_pricing_row;
emit it only when a brand new log is written, and skip placeholder
models (unknown / empty / null / none) entirely.
- Broaden model id normalization: strip namespace prefixes
(anthropic./openai./global./bedrock.), bedrock-style -vN suffixes,
reasoning effort suffixes (-low/-medium/-high/-xhigh/-minimal),
Claude Desktop's claude-<non-anthropic> wrapper, dot-to-dash for
Claude, and try a LIKE prefix match for Claude short route ids
(e.g. claude-haiku-4-5 -> claude-haiku-4-5-20251001).
- Fall back to request_model when the stored model is missing, so
early Codex session rows with model=unknown can still be priced.
- Replace the four flat env inputs with a Sonnet/Opus/Haiku role table.
Each row exposes ANTHROPIC_DEFAULT_*_MODEL plus a new display name
field ANTHROPIC_DEFAULT_*_MODEL_NAME, and Sonnet/Opus gain a
"Declare 1M" checkbox that toggles the [1M] suffix.
- Strip the [1M] context-capability marker before forwarding non-Copilot
requests upstream. Copilot keeps its existing [1m]->-1m normalization.
- Claude Desktop import now consumes ANTHROPIC_DEFAULT_*_MODEL_NAME as
label_override, closing the Claude Code -> Claude Desktop displayName
pipeline; add_route's merge logic is shared between hashmap branches.
- Unify the [1M] marker as ONE_M_CONTEXT_MARKER across
claude_desktop_config and proxy::model_mapper; rename the strip
helper to strip_one_m_suffix_for_upstream.
- Collapse useModelState's seven duplicated useState initializers and
the useEffect parse block into a single parseModelsFromConfig call.
- Add tests/hooks/useModelState.test.tsx and a Claude Desktop import
test covering Kimi K2 -> label_override. i18n (en/ja/zh) updated.
Adapt to Claude Desktop 1.6259.1+ fail-all validation which only
accepts claude-(sonnet|opus|haiku)-* route IDs. Branded model names
(DeepSeek, Kimi, GLM, etc.) now live in a new labelOverride field
instead of being embedded in route IDs.
- Backend auto-repairs legacy unsafe routes to the next free
sonnet/opus/haiku slot instead of erroring
- Frontend swaps the free-form route input for a role dropdown plus
menu display name field
- Add CLAUDE_DESKTOP_ROLE_ROUTE_IDS as the single source of truth
for role-to-route mapping; presets and form both consume it
- Drop the dead displayName alias on ClaudeDesktopModelRoute and the
ineffective /v1/models display_name injection (UI ignores it)
- Update i18n (en/ja/zh) and form focus test for the new fields
- Normalize OpenAI/Gemini input_tokens semantics in SQL via the new
fresh_input_sql helper (cache_read subtracted at query time, no data
migration). Recovers correct cache hit rates for Codex/Gemini.
- Add get_usage_summary_by_app endpoint for per-app split (single
UNION ALL + GROUP BY, avoids N+1).
- Replace UsageSummaryCards + AppBreakdownRail with a single
filter-driven UsageHero card; clicking a filter button now truly
changes the displayed numbers and the title accent color.
- Tighten KNOWN_APP_TYPES to the 3 app_types whose token data is
reliably collected (claude/codex/gemini); hide claude-desktop,
hermes, opencode, openclaw filter buttons and i18n keys.
- Flag cache_creation as N/A for OpenAI-style protocols (Codex,
Gemini); show a "partial" tooltip when the All view mixes both
protocol families.
- Derive route keys from the upstream model name (pass-through style)
instead of fixed Claude aliases, and translate the legacy [1M] suffix
into the supports1m field at the import boundary. Three Claude aliases
mapped to the same upstream now collapse to a single route (e.g.
MiniMax-M2 across SONNET/OPUS/HAIKU env produces one
claude-MiniMax-M2 -> MiniMax-M2 row), with [1M] OR-aggregated.
- Add an import-time safety net that rebuilds claude-desktop-official
when missing, so users who deleted it can recover via the normal
import button without losing customizations on other providers.
- Hide API key and endpoint URL inputs in the official provider edit
form to mirror Claude Code's behavior and prevent user confusion.
- Reword the empty-state import button label for clarity.
inferenceModels entries now emit {name, supports1m: true} objects when
1M is enabled (plain strings otherwise), instead of appending a " [1M]"
suffix to model IDs. Route IDs and upstream model IDs are stored
verbatim; the suffix is rejected on input rather than silently stripped,
and proxy request mapping now requires an exact route_id match.
The Monitor glyph's visual weight skews upward (screen rect dominates
while the stand is two thin lines), making it appear off-center inside
the 11px Claude Desktop badge. Add a per-badge offsetY config and
apply translateY(0.5px) to compensate.
Claude Desktop's new model menu reads model IDs directly and ignores the
display_name field, so a separate displayName slot added UI noise without
any product value. Collapse the routeId / model / displayName tuple down
to routeId / model, and let the route ID carry the user-visible name
through a non-editable claude- prefix rendered next to the input.
Drop display_name from ClaudeDesktopModelRoute, ClaudeDesktopDefaultRoute,
and ResolvedModelRoute on the Rust side plus the matching TS interfaces,
stop emitting it in /v1/models responses, derive route IDs from upstream
model IDs when picked via the model dropdown, and update zh/en/ja copy to
describe the new two-field layout.
Claude Desktop disables Skills, Prompts, Sessions, and MCP, which left
the secondary toolbar capsule next to the app switcher completely empty
but still rendered as a grey rounded pill. Wrap the capsule in an
activeApp !== "claude-desktop" guard so it disappears entirely, and
drop the two inner guards that this outer check makes redundant.
The app visibility section in Settings showed "Claude" for the first
entry, identical to "Claude Desktop" at a glance. Add a dedicated
i18n key apps.claudeCode and point the settings panel at it, while
leaving apps.claude untouched so other panels (MCP, Skills, Usage,
etc.) keep their shorter "Claude" label.
The two Claude entries shared the same orange logo, making them hard to
tell apart at a glance. Rename the first entry to "Claude Code" and
overlay a Terminal badge on its logo; overlay a Monitor badge on the
Claude Desktop logo. Changes are scoped to AppSwitcher only; other
panels (MCP, Skills, Usage, etc.) continue to show "Claude".
Each tagged release now leads with the canonical official website
in three languages, ensuring every Release page (which is indexed
independently by Google) becomes a dofollow backlink to ccswitch.io.
Add an "Only Official Website" header to the three READMEs, an
About panel button, and a tray menu entry — all pointing to
ccswitch.io. Consolidates brand and SEO signals on the canonical
domain across docs, GUI, and system tray.
- Guard debug body serialization with `log::log_enabled!`; previously
serialized the filtered body to a throwaway String on every forward,
even with debug logging off.
- Skip SSE parse + UTF-8 buffer loop when no usage collector and debug
is off; the per-chunk `serde_json::from_str::<Value>` ran even in
pure passthrough mode.
- Add cheap per-app SSE event pre-filter (string `contains`) so usage
collectors only parse events that could contain usage (e.g. Claude
`message_start` / `message_delta`).
- Skip non-streaming response body JSON parse when usage logging is
disabled.
- Move `ProviderRouter::record_result` off the success response path
via `tokio::spawn` for non-HalfOpen state; that call internally does
`get_proxy_config_for_app` + `update_provider_health`, two SQLite
ops that previously blocked TTFB.
Also: dedupe `usage_logging_enabled` (was duplicated in handlers.rs)
and merge `SseUsageCollector::{new, new_filtered}` into a single
constructor that takes `Option<StreamUsageEventFilter>`.
prompt_cache_key was falling back to provider.id when the client did not
supply a session, which collapsed every conversation onto a single key
and defeated upstream prefix caching. Only emit the key when a real
client-provided session/thread identity is available; otherwise let the
upstream use its default matching behaviour.
Additional fixes that affect cache stability:
- Canonicalise (sort) JSON keys in outgoing request bodies and in
tool_call arguments / tool_result content so semantically identical
requests produce identical byte sequences for upstream prefix caches.
- Exempt JSON Schema property maps (properties, patternProperties,
definitions, \$defs) from the underscore-prefix filter so user-defined
schema keys like _id and _meta survive.
- Add a [CacheTrace] debug log with stable hashes for instructions,
tools, input and include to help diagnose cache misses.
- Thread session_id into the usage logger for request correlation.
Resized to 512x512 to match the existing extracted-icons size convention
(hermes.png / lemondata.png). Source uploads were oversized (7.3 MB
8635x8635 for ClaudeCN; 800x800 for RunAPI) and would have bloated the
bundle if imported as-is.
Note: not yet wired into index.ts / metadata.ts; register there when
they need to surface in the app UI.
Insert sponsor row in all three README locales linking to runapi.co.
EN/JA copy adapted from the Chinese original. Banner center-cropped to
the project standard 1920x798 aspect ratio (preserves the logo and
slogan, trims the decorative top/bottom padding) and saved as JPEG to
keep file size reasonable (1.3 MB PNG -> 206 KB JPEG).
Insert sponsor row in all three README locales linking to claudecn.top.
EN/JA copy adapted from the Chinese original. Banner normalized to the
project standard sponsor image spec (1920x798 RGB on white background)
and recompressed; alt text unified to "ClaudeCN" across all three files.
Replace byteplus.png with localized huoshan.png (Volcengine/火山引擎)
in README_ZH.md so Chinese readers see the regional brand. EN/JA
README continue to use the BytePlus logo and link to byteplus.com.
The new logo is normalized to the project's standard sponsor image
spec: 1920x798 RGB on a white background.
Insert sponsor row in all three README locales. EN/JA point to
byteplus.com/modelark; ZH points to volcengine.com/agentplan
(Volcengine being the China-region counterpart). Logo normalized
to the project-standard 1920x798 RGB white background and
recompressed (432K -> 84K).
- New src/config/claudeDesktopProviderPresets.ts with the Claude Code
preset list re-shaped into Desktop's three-segment route format
(routeId / upstreamModel / displayName); excludes OAuth providers,
AWS Bedrock (no SigV4 support) and KAT-Coder (placeholder URL).
- Non-Claude upstream presets show upstream model id as displayName
(e.g. deepseek-v4-pro) so the Desktop model list reflects what is
actually being requested. OpenRouter/TheRouter/PIPELLM keep
Sonnet/Opus/Haiku since their upstream really is Anthropic Claude.
- Wire ProviderPresetSelector into ClaudeDesktopProviderForm so
selecting a preset back-fills baseUrl, mode, routes and apiFormat.
- Drop the hard-coded ANTHROPIC_AUTH_TOKEN write in handleSubmit so
ANTHROPIC_API_KEY presets (LemonData / AiHubMix / Gemini Native)
save under the correct env key, and clear the opposite key on switch.
- Hide the universal-providers tab for claude-desktop because its
meta-driven routing has no analogue in the universal flat-env shape.
- Add apps."claude-desktop" i18n key (zh/en/ja) so the dialog tab
label resolves instead of showing the literal key.
- Apply rustfmt diffs in claude_desktop_config.rs
- Allow needless_return on current_platform_paths (cfg-mirrored arms)
- Allow too_many_arguments on RequestForwarder::forward
- Replace `let mut + reassign` with struct literals in tests
(settings, backup, provider, response_processor)
- Use Path::new instead of PathBuf::from to fix cmp_owned in misc tests
- Replace 3.14 with 3.5 in config test to avoid approx_constant lint
- Add ClaudeAPI as new sponsor (all 3 languages)
- Remove ChefShop sponsor entry (all 3 languages)
- Convert shengsuanyun logo from SVG to PNG
- Standardize partner logos to 1920x798 canvas
- Fix ClaudeAPI alt text typo and spacing
- Sync sponsor order across zh/en/ja READMEs
Direct-mode providers no longer display a badge since routing is
optional for them. Proxy-mode providers now show "需要路由" / "Requires
routing" to clarify that local routing must be active.
Claude Desktop strips the [1M] suffix from model IDs when sending
requests, causing route lookup to fail with "model route is not
configured". Fall back to base-name comparison when exact match misses.
- Remove "Import from Claude" button from main provider list (keep in empty state)
- Remove "Desktop model" column from proxy mode mapping table; route names are now auto-generated
- Rename "upstream model" label to "requested model" and equalize column widths
- Rewrite model mapping hint and toggle description for end-user clarity
- Update validation messages and remove dead i18n keys (routeModelLabel)
The route toggle in the top-right corner already communicates proxy
state; the extra warning banner was redundant and inconsistent with
other managed apps.
- services/proxy.rs: collapse 10 repeated `OpenCode | OpenClaw | Hermes |
ClaudeDesktop` match arms into `_` fallthroughs.
- claude_desktop_config.rs: extract a `with_rollback` closure shared by
apply_provider_to_paths and restore_official_at_paths.
- useProviderActions.ts: replace the triple-nested ternary picking the
switch-success toast message with a flat let/if/else block.
Net -36 lines. No behavior change; cargo test and pnpm typecheck pass.
Adds a new ClaudeDesktop AppType that writes Claude Desktop's third-party
inference profile under configLibrary/, sharing _meta.json with other
launchers (Ollama-compatible) so cc-switch can coexist with them.
Two switch modes:
- direct: provider already exposes claude-* / anthropic/claude-* model
ids on Anthropic Messages, Claude Desktop connects to it directly.
- proxy: cc-switch's local proxy acts as the inference gateway,
presenting only claude-* route names to Claude Desktop and mapping
them to real upstream models. Required after Anthropic restricted
Claude Desktop to claude-family ids.
Backend:
- New module claude_desktop_config with snapshot/rollback, official seed
bypass, /claude-desktop/v1/{models,messages} routes, and a single
source of truth for default proxy routes.
- Gateway token persisted in SQLite, validated on every proxied request.
- get_claude_desktop_status surfaces drift signals (stale models,
missing routes, proxy stopped, base URL mismatch, missing token).
Frontend:
- Slim ClaudeDesktopProviderForm independent from ProviderForm,
controlled by a top-level appId guard.
- ProviderList banner consumes the status query (5s polling) and
renders actionable diagnostics.
- ClaudeDesktopRouteToggle in the header to start/stop the local
gateway without touching takeover state.
- Three-locale i18n synchronised.
The hyper raw-write path preserves original header casing but rebuilds
TCP+TLS on every request — there is no connection pool — which was the
root cause of slow reverse-proxy throughput.
Only Anthropic-native requests actually need exact header-case
preservation. Route OpenAI/Copilot/Codex/Gemini/codex_oauth requests
through the pooled reqwest client (pool_max_idle_per_host=10,
tcp_keepalive=60s) instead, so warm connections get reused.
Streaming requests get a precise first-byte timeout via
tokio::time::timeout around reqwest's send() (which resolves on
response headers), with the body phase handed off to response_processor.
The streaming-detection helper now also covers Gemini SSE endpoints
and Accept: text/event-stream, not just body.stream.
Prevents `git fetch origin pull/<N>/head:main` failures on fork PRs
whose head branch is also named `main` — using a SHA puts the runner
in detached HEAD so the main ref is free for the action's internal fetch.
Now that the view transition animation is gone, setTheme no longer
needs click coordinates. Reduce the API surface to (theme: Theme) =>
void and simplify the call sites in mode-toggle and ThemeSettings.
The View Transitions API used here crashes WebKitGTK with SIGSEGV on
Linux. Rather than gating document.startViewTransition per platform
(see PR #2502), drop the animation entirely — it's a low-value visual
flourish on a low-frequency action that doesn't justify a permanent
platform branch.
Removes the ::view-transition-* CSS block and the coordinate plumbing
in setTheme. The optional event parameter is kept on the API surface
to keep call sites compiling; they'll be cleaned up in a follow-up
commit.
Cap was too tight — review tasks need 8-15 turns to read files, analyze,
and post results. The first run after the previous prompt change failed
with error_max_turns at turn 6. The disallowedTools list already keeps
the agent in read-only mode, so an explicit turn cap is redundant.
Previous prompt produced 5-10 findings per PR including dead-code/style
nits mixed with real bugs. New prompt adds severity tiering, an 80
confidence threshold, an explicit anti-pattern list, a 5-nit cap, and
permits zero-comment LGTM as a valid outcome. Calibration follows
Anthropic Code Review docs and the claude-plugins-official prompt.
- Restrict to repo collaborators via author_association check
- Use OAuth token for subscription billing
- Disable Edit/Write tools and set contents:read permission to enforce review-only
Anthropic SDK assigns distinct semantics to the two env vars:
- ANTHROPIC_API_KEY -> x-api-key
- ANTHROPIC_AUTH_TOKEN -> Authorization: Bearer
The Claude adapter previously collapsed both into AuthStrategy::Anthropic
and then emitted Authorization: Bearer regardless, breaking strict
Anthropic-protocol endpoints (Anthropic official, Cloudflare AI Gateway,
OpenCode Go, DashScope) and silently overriding the user's intended auth
scheme.
- claude::extract_auth: infer strategy from env var name
(ANTHROPIC_AUTH_TOKEN -> ClaudeAuth, ANTHROPIC_API_KEY -> Anthropic),
matching the precedence already used by extract_key.
- claude::get_auth_headers: split the Anthropic arm so it emits
x-api-key, while ClaudeAuth and Bearer continue to use Bearer.
- stream_check: reuse ClaudeAdapter::get_auth_headers as the single
source of truth, replacing the prior "always Bearer + maybe x-api-key"
double injection that produced auth conflicts and false-negative
health checks.
- Cover each strategy -> header mapping and env-var precedence with
new unit tests in claude.rs.
Refs #2368, #2380
Claude Code injects a dynamic `x-anthropic-billing-header` line at the
start of `system` content. Its rotating `cch=` token was forwarded into
OpenAI Responses `instructions` and Chat system messages, which broke
upstream prefix prompt cache reuse — a stable ~95k-token prefix was
getting re-charged on every request.
Strip only the leading occurrence in both anthropic_to_openai and
anthropic_to_responses; later occurrences are preserved so user-authored
prompt text containing the same string is not lost.
Hermes aggregates all in-process API calls into a single sessions row
with the `model` field locked to the initial model, so the usage
dashboard cannot cleanly surface per-call billing context. Two rounds
of UI workarounds (raw mapping, then `<model> @ <host>` display) did
not resolve the user-facing confusion, so the whole tracking
integration is dropped for now.
Removes session_usage_hermes service (and its 17 tests), sync wiring
in commands/usage.rs and lib.rs, _hermes_session/hermes_session
entries in usage_stats SQL (provider_name_coalesce CASE and
effective_usage_log_filter IN clause), frontend Tab/banner/dropdown/
icon entries, and four i18n keys per locale.
Hermes app integration outside usage tracking (proxy routing,
session manager, config) is preserved. Pre-existing hermes rows in
proxy_request_logs are left as orphans — filtered out by the
updated SQL and never surfaced in the UI.
Zhipu's `data.limits[]` returns 1 entry for legacy plans (subscribed
before 2026-02-12) and 2 entries for current plans. Previously every
TOKENS_LIMIT entry was hardcoded as `five_hour`, so the weekly bucket
was rendered with the 5-hour i18n label.
Sort TOKENS_LIMIT entries by nextResetTime ascending and assign
`five_hour` to index 0, `weekly_limit` to index 1. Legacy plans
naturally degrade to a single five_hour tier.
Also harden the parser: case-insensitive type match (defends against
upstream casing changes), reuse TIER_FIVE_HOUR/TIER_WEEKLY_LIMIT
constants, and add 8 unit tests covering both plan shapes plus
defensive edge cases.
* fix(dashscope): enhance usage parsing robustness to prevent VSCode crashes
Enhanced build_anthropic_usage_from_responses() to handle null, missing, empty,
and partial usage fields gracefully. This prevents VSCode Extension crashes with
"Cannot read properties of null (reading 'output_tokens')" when connecting to
DashScope (Alibaba Cloud Bailian) models.
Changes:
- Added defensive null checks and empty object detection
- Implemented OpenAI field name fallbacks (prompt_tokens/completion_tokens)
- Added comprehensive logging for malformed usage scenarios
- Fixed streaming SSE event handlers with null-safe usage access
- Preserved cache token fields even when input/output tokens are missing
This ensures the proxy never crashes on malformed Responses API usage objects,
returning valid Anthropic-compatible usage structures (input_tokens/output_tokens)
in all cases.
* fix(proxy): tighten Responses API usage fix per review
- Drop redundant fallback in streaming.rs Chat Completions path; the
existing if-let-Some guard already prevents usage:null, so the extra
layer was dead code and caused a fmt-breaking indentation issue.
- Demote partial-usage warn to debug. Streaming chunks legitimately
arrive with partial token counts and the warn-level log was noisy.
- Rewrite CHANGELOG entry: reference #2422, broaden scope from
DashScope-only to all api_format=openai_responses users (Codex OAuth
is the strongest signal; DashScope compatible-mode/v1/responses is
the original report).
- cargo fmt to clear 12 formatting differences vs main.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix(config): sort JSON keys alphabetically for deterministic output
Ensures settings.json keys are written in sorted order, preventing
non-deterministic git diffs when switching configs.
* test(config): add unit tests for sort_json_keys and fix formatting
Cover top-level sort, nested recursion, array order preservation,
primitive pass-through, empty collections, and the core determinism
guarantee (different insertion orders must yield identical output).
Also fix line-length in write_json_file flagged by `cargo fmt --check`.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* Keep Codex history stable across provider switches
* Restore template Codex provider id when backfilling live config
Backfill writes the current Codex live config back to the previous
provider's stored template after a switch. Because the live file now
carries a normalized stable model_provider id, the previous provider's
template would lose its own provider-specific id (and any matching
[profiles.*] references) on every subsequent switch.
Reverse the normalization at backfill time by rewriting model_provider,
the active model_providers section, and matching profile references back
to the template's original id.
---------
Co-authored-by: Jason <farion1231@gmail.com>
Hermes:
- Parse ~/.hermes/state.db sessions (incl. profiles/*/state.db) into
proxy_request_logs with data_source='hermes_session', WAL-aware
incremental sync, Hermes-reported cost preferred over model_pricing
fallback
Zero-cost bug (dashboard showed \$0 totals):
- GPT-5.5 family default pricing (~83% of affected rows used GPT-5.5)
- find_model_pricing_row: ASCII-lowercase normalization so
"OpenAI/GPT-5.5@HIGH" matches seeded "gpt-5.5"
- Startup cost backfill in async task: scan rows where total_cost <= 0
but tokens > 0, recompute via model_pricing in a single transaction
Performance:
- Add (app_type, created_at DESC) covering index for dashboard range
queries
- Add expression index on COALESCE(data_source, 'proxy') so dedup EXISTS
subqueries use index lookup instead of full scan; drop superseded
idx_request_logs_dedup_lookup
Refactor:
- row_to_request_log_detail helper (3-way de-dup; fixes cost_multiplier
\"1\" vs \"1.0\" drift between callers)
- Promote get_sync_state/update_sync_state to shared session_usage
module (4 copies -> 1)
- run_step helper in lib.rs replaces 9 if-let-Err blocks
- maybe_backfill_log_costs returns bool to skip duplicate total_cost
parsing in caller
Proxy writes and session-log sync wrote to proxy_request_logs with
mismatched request_ids: only Claude on a native Anthropic backend used the
shared `session:{message_id}` key. Codex/Gemini and Claude-through-OpenAI
providers always produced distinct ids, so primary-key dedup never fired
and every real request was recorded twice.
Adds a 7-dim fingerprint dedup (app_type, 4 token counts, 2xx status,
model with case-insensitive match, ±10min window) wired into three layers:
- Write path: should_skip_session_insert() blocks duplicate session rows
before INSERT, unifying the previously-divergent Claude/Codex/Gemini
paths through a single DedupKey-based helper.
- Read path: effective_usage_log_filter() excludes already-covered session
rows from every aggregation query.
- Rollup path: same filter applied so usage_daily_rollups never absorbs
duplicates.
Also adds a covering index (idx_request_logs_dedup_lookup) so the EXISTS
subquery stays index-only, and a transform.rs regression test that pins
openai_to_anthropic id preservation - the missing piece that lets
Claude+OpenAI-compatible providers reuse the session: id scheme.
Sync the preset's websiteUrl from the legacy /coding/docs/ path to
the current /code/docs/ path across all four app presets (claude,
hermes, openclaw, opencode).
The query_siliconflow function received an is_cn flag that only switched
the request domain (.cn vs .com) but the response builder hardcoded
unit="CNY" for both sites. International users at api.siliconflow.com
saw their USD balance labelled as CNY. Now unit and plan_name follow
is_cn, so the EN site shows USD and "SiliconFlow (EN)".
Codex models no longer accept model_context_window=1000000, so the
toggle and its paired auto-compact-limit input are commented out in
the provider edit form. State hooks, helper imports, and i18n keys
are preserved so the UI can be restored in one batch if upstream
support returns. The TOML editor remains visible, allowing manual
edits if needed.
- Deduplicate repeated upstream `finish_reason` chunks so only one Anthropic `message_delta` is emitted.
- Preserve late `choices: []` usage-only chunks before sending the final `message_delta`.
- Keep stream error paths from emitting successful terminal events.
- Add regressions for duplicate finish reasons, usage-only chunks, missing `[DONE]`, and truncated streams.
* feat(provider-form): soften business-rule validation with "save anyway" prompt
Refactor handleSubmit so empty-field / missing-item validations (provider
name, endpoint, API key, opencode model, template variables, provider key
required) no longer hard-reject with toast.error. Instead they are collected
into an issues list and presented via a ConfirmDialog; the user can cancel
or choose "Save anyway" to proceed.
Integrity constraints stay as hard rejections:
- providerKey regex / duplicate (would corrupt other providers)
- Copilot / Codex OAuth not authenticated (no token, cannot establish)
- omo Other Fields JSON not an object / parse failure
This aligns the frontend with the backend's existing "relaxed save / strict
switch" split (see gemini_config.rs: validate_gemini_settings vs
validate_gemini_settings_strict) and unblocks legitimate configs such as
AWS Bedrock, Vertex AI, and custom Gemini base URLs that the UI previously
refused to save.
Refs: #2196, #1204
* fix(provider-form): address review feedback on soft-validation
P1: move empty providerKey back to hard rejection for OpenCode / OpenClaw /
Hermes. Since providerKey is the primary identity for these apps and the
mutations layer throws "Provider key is required" when absent, letting users
click "save anyway" would surface a generic error toast instead of a
precise, actionable one. Treat empty providerKey as an integrity constraint
alongside regex / duplicate checks.
P2: give the soft-confirm submit path its own submitting state. The
confirm-dialog path bypassed react-hook-form's isSubmitting lifecycle, so
slow or failing saves left the outer submit button responsive and could
spawn unhandled rejections. Now the confirm handler awaits performSubmit
inside try/catch/finally, uses an isConfirmSubmitting flag to gate both
confirm and cancel clicks, and folds the flag into the outer disabled
state and onSubmittingChange callback.
Refs: #2307 review comments
* chore(clippy): use push for single char '…' in truncate_body
Clippy 1.95 added single_char_add_str which flagged the push_str("…")
in truncate_body. Rebased onto latest upstream/main and applied the
suggested fix so the Backend Checks clippy job passes.
Unrelated to this PR's core changes; bundled in so the PR is mergeable
without waiting for a separate upstream fix.
---------
Co-authored-by: Allen <allen@AllenMacBook-M4-Pro.local>
Introduce a dedicated "Compshare Coding Plan" variant pointing to
https://cp.compshare.cn (with /v1 for OpenAI-compatible apps). Reuses
the existing ucloud icon and promotion copy, while adding a new
providerForm.presets.ucloudCoding key in zh/en/ja.
The $2 sign-up credit is no longer granted automatically — users now
need to contact Crazyrouter's customer support after registering to
claim it. Sync all three README variants and drop the outdated
"instantly / 即時進呈" wording. Also fix a stray double space in the
English sentence.
Update the Compshare entry to describe the new offering: per-use
domestic-model Coding Plan packages with officially-relayed overseas
models, replacing the outdated "60-80% off" discount wording. Keep all
three README variants in sync.
The original lioncc.png had a transparent background, which made the
black "LionCC" wordmark blend into GitHub's dark-mode page and become
hard to read. Composite the artwork onto solid white so the logo stays
legible in every theme without changing any markup.
DeepSeek released V4 flash/pro; legacy IDs deepseek-chat / deepseek-reasoner
now alias to deepseek-v4-flash and will be deprecated.
- Update claude/hermes/opencode/openclaw presets to v4-pro / v4-flash,
context 128K -> 1M; Claude Anthropic-compat endpoint routes OPUS/SONNET
to v4-pro and HAIKU to v4-flash, plus an explicit modelsUrl override.
- Seed deepseek-v4-flash ($0.14/$0.28 per 1M) and deepseek-v4-pro
($1.68/$3.36 per 1M) into model_pricing; older v3.x / chat / reasoner
rows kept for historical usage stats (INSERT OR IGNORE).
- Refresh user-manual (zh/en/ja) pricing table and note that legacy model
IDs are billed at v4-flash rates.
Providers like DeepSeek, Kimi, Zhipu GLM and MiniMax expose the
Anthropic-compatible API on a subpath (e.g. /anthropic) while the
OpenAI-style /models endpoint lives at the API root. The previous
heuristic blindly appended /v1/models to the Base URL, so every such
provider returned 404 and the UI mislabeled it as "provider does not
support fetching models".
Backend now generates a candidate list and tries them in order:
preset override -> baseURL /v1/models -> stripped-subpath /v1/models ->
stripped-subpath /models. Non-404/405 responses (auth, network) stop
immediately so we never retry against hostile status codes. Known
compat suffixes are kept in a length-descending constant so the
longest match wins; response bodies are truncated to 512 chars to
avoid HTML 404 pages bloating the error string.
Preset type gains an optional modelsUrl (DeepSeek points at
https://api.deepseek.com/models). Frontend threads the override
through fetchModelsForConfig when the current Base URL still matches
the preset default. A new fetchModelsEndpointNotFound i18n key
replaces the misleading "not supported" toast for exhausted-candidate
and 404/405 cases (zh/en/ja).
Copilot upstream returns model_not_supported when the client sends
dash-form Claude IDs (claude-sonnet-4-6, claude-sonnet-4-6[1m]) while
/models only accepts dot form (claude-sonnet-4.6, -1m suffix).
- Add copilot_model_map: syntax normalize (dash->dot, [1m]->-1m) plus
live /models exact match and family-version fallback, reusing the
existing 5 min auth cache. Returns None when the whole family is
absent so upstream surfaces an explicit error instead of silently
switching families.
- Wire into forwarder Copilot hook; runs before anthropic_to_openai
conversion.
- Default Opus slot in the Copilot preset maps to Sonnet 4.6: Pro
dropped all Opus on 2026-04-20 and Pro+ bills Opus 4.7 at 7.5x.
Users who want real Opus can switch manually in the UI.
Refs: https://github.com/farion1231/cc-switch/issues/2016
- Add v3.14.1 release notes (en/zh/ja) covering tray usage visibility,
Codex OAuth stability fixes, Skills import/install reliability, and
removal of the Hermes config health scanner
- Cut [Unreleased] into [3.14.1] in CHANGELOG with PR references
- Bump version in package.json, Cargo.toml, Cargo.lock, tauri.conf.json
dc04165f surfaced tray usage badges for Claude/Codex/Gemini official
OAuth only. Chinese coding-plan providers already expose 5h + weekly
windows through coding_plan::get_coding_plan_quota, but two gaps kept
the tray from rendering them.
- format_script_summary read only data.first(), truncating the tier-
flattened UsageResult to a single window. Detect plan_name matching
TIER_FIVE_HOUR / TIER_WEEKLY_LIMIT and emit the "🟢 h12% w80%" layout
used by format_subscription_summary; worst utilization drives the
emoji. Copilot / balance / custom scripts keep the legacy single-
bucket output via fallback.
- usage_script previously required manual activation through
UsageScriptModal. Auto-inject meta.usage_script on Claude provider
creation when ANTHROPIC_BASE_URL matches a known coding plan, so the
tray lights up without the user opening the modal. Does not overwrite
existing usage_script on update.
Extract the URL route table out of UsageScriptModal into a shared
codingPlanProviders module so the modal, the creation hook, and the
Rust coding_plan::detect_provider mirror all agree on one list.
Add TIER_WEEKLY_LIMIT alongside TIER_FIVE_HOUR and a createUsageScript()
factory to collapse the duplicated default fields across four call
sites and drop the remaining stringly-typed tier names.
The Hermes config.yaml schema has stabilized and users have migrated to
the current provider fields, so the value of scanning for model.provider
dangling references, custom_providers shape errors, v12 migration residue
etc. no longer justifies the maintenance surface — and the scan produces
false positives when users keep some providers under Hermes' v12+
providers: dict (Hermes' runtime merges both shapes, but CC Switch's
scanner only looked at the list form).
Removes the whole HermesHealthWarning type, scan_hermes_config_health
command, HermesHealthBanner React component, useHermesHealth hook,
warnings field on HermesWriteOutcome, and the three helper functions
(yaml_as_non_empty_str, collect_mapping_string_keys, hermes_warning)
that only served the scanner. Drops the matching i18n keys in
zh/en/ja and the fixInWebUI button label that only the banner used.
* feat: add Rust-side write-through usage cache
Introduce an in-memory UsageCache on AppState that the existing usage
query commands populate on success. The cache is read-only to the rest
of the app today; the next commit consumes it from the tray menu.
- New services::usage_cache module with split maps: subscription keyed
by AppType, script keyed by (AppType, provider_id).
- AppType gains Eq + Hash so it can be used as a HashMap key.
- commands::subscription::get_subscription_quota now takes State<AppState>
and writes through on success (signature change is invisible to the
frontend — Tauri injects State automatically).
- commands::provider::queryProviderUsage body extracted into an inner
async fn; the public command wraps it with write-through, covering
Copilot, coding-plan, balance, and generic script paths uniformly.
Cache is in-memory only; auto-query interval and the upcoming tray
refresh action rebuild it after restarts.
* feat(tray): surface cached usage in the system tray menu
Read UsageCache populated by the previous commit and render it in three
places, scoped to whatever TRAY_SECTIONS covers (Claude/Codex/Gemini):
1. Inline suffix on each provider submenu item
"AnyProvider · 🟢 5h 18% / 7d 23%"
2. Disabled summary row per visible app under "Show Main"
"Claude · Anthropic Official · 🟢 5h 18% / 7d 23%"
3. "Refresh all usage" menu item that triggers get_subscription_quota +
queryProviderUsage for every applicable provider, then rebuilds the
tray menu via the existing refresh_tray_menu path.
Color encoding uses emoji (🟢 <70% / 🟠 70-89% / 🔴 ≥90%) since Tauri 2
tray labels are plain text. Missing cache entry leaves the label
unchanged — tray never issues network requests when opened. Three new
i18n-ready strings live in TrayTexts (en/zh/ja), following the existing
pattern for tray text.
Closes#2178.
* feat(usage): bridge tray UsageCache writes to frontend React Query
Why: tray hover triggers backend-only refresh that wrote to UsageCache but
never notified the frontend, leaving main UI stale while tray showed fresh
numbers. Emit a payload-carrying event after each cache write so React Query
can setQueryData directly, keeping both views in sync without duplicate fetches.
* fix(tray): skip hidden apps on hover refresh and drop stale disabled-script cache
Address P2 findings from automated review on #2184:
1. refresh_all_usage_in_tray now filters TRAY_SECTIONS by settings.visible_apps
before scheduling subscription/script queries, matching create_tray_menu and
preventing wasted external API calls (and rate-limit/auth-error log noise)
for apps the user has hidden.
2. format_usage_suffix only trusts the script cache when provider.meta.usage_script
is still enabled; when a script is disabled/removed the cached suffix is now
invalidated so the tray label no longer shows stale data indefinitely.
* refactor: consolidate codex provider helpers and fix test semantics
- Add Provider::is_codex_oauth() and Provider::codex_fast_mode_enabled()
to eliminate duplicated meta extraction in claude.rs and stream_check.rs
- Fix non-codex-oauth tests to pass codex_fast_mode=false (was true, harmless
but semantically misleading)
- Remove redundant is_dir() guard after resolve_skill_source_dir already
guarantees the returned path is a directory
* style: apply cargo fmt
* fix(tray): reflect failed refreshes in cache and support Gemini flash-lite
Follow-up to the tray usage-display feature addressing review feedback:
- Write snapshots for both Ok(success:false) and Err paths in
queryProviderUsage / get_subscription_quota so stale success data
no longer persists across failed refreshes; the original Err is
still returned to the frontend onError handler.
- Include gemini_flash_lite tier in the tray summary with label "l".
Matches the frontend SubscriptionQuotaFooter and keeps the worst
emoji correct when lite is the highest utilization.
- Add TIER_GEMINI_PRO / _FLASH / _FLASH_LITE constants in
services/subscription.rs and reuse them in classify_gemini_model
and sort_order.
- Extract Provider::has_usage_script_enabled() to remove the
duplicated meta.usage_script chain at two call sites.
- Use db.get_provider_by_id in refresh_all_usage_in_tray instead of
materialising the full provider map, and parallelise subscription
and script futures via futures::future::join.
- Narrow refresh_all_usage_in_tray to each section's effective
current provider (script if enabled, else subscription when the
provider is official). Hover refreshes now issue at most
TRAY_SECTIONS.len() outbound requests.
- Add 10 unit tests in tray::tests covering Claude/Codex h/w dispatch,
Gemini p/f/l dispatch (including lite-only and lite-worst cases),
and success/failure guards.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* Add Codex OAuth FAST mode toggle
* fix(codex-oauth): default FAST mode to off to avoid surprise quota burn
service_tier="priority" consumes ChatGPT subscription quota at a higher
rate. Users must now opt in explicitly rather than inherit FAST mode
silently when this feature ships.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* Stabilize Codex OAuth cache routing
Codex OAuth-backed Claude proxy requests now reuse a client-provided session identity for prompt cache routing and send Codex-like session headers when that identity exists. Generated proxy UUIDs are intentionally excluded so they do not fragment cache locality.\n\nThe same path exposed two runtime issues during validation: rustls needed an explicit process crypto provider, and Codex OAuth can return Responses SSE even when the original Claude request is non-streaming. Those are handled so cache-routed requests can complete instead of panicking or being parsed as JSON.\n\nConstraint: Official Codex uses conversation identity and Responses session headers for prompt cache routing.\nRejected: Always use generated proxy session IDs | generated IDs change per request and reduce cache reuse.\nConfidence: medium\nScope-risk: moderate\nDirective: Do not remove the client-provided-session guard unless generated session IDs become stable per conversation.\nTested: cargo test codex_oauth\nTested: Local dev app health check on 127.0.0.1:15721\nTested: Local proxy logs showed cache_read_tokens after restart\nNot-tested: Full cargo test without local cc-switch port conflict\nRelated: #2217
* feat(proxy): aggregate forced Codex OAuth SSE into JSON for non-streaming clients
Narrow override on top of #2235's streaming fallback.
Codex OAuth always forces upstream openai_responses into SSE, even
when the original Claude request is stream:false. #2235 handles this
by routing such responses through the streaming transform so the
client receives text/event-stream — that avoids the 422 that JSON
parsing would produce, and it also protects any other provider that
unexpectedly returns SSE (the response.is_sse() guard).
But for Claude SDK callers that sent stream:false, returning SSE
still violates the Anthropic non-streaming contract. This commit
adds an override on exactly one combination — non-streaming client
+ codex_oauth + openai_responses — to aggregate the upstream
Responses SSE into a synthetic Responses JSON and then run the
regular responses_to_anthropic non-streaming transform. All other
paths, including the generic response.is_sse() fallback, remain
on the streaming path from #2235.
The aggregator reuses proxy::sse::take_sse_block / strip_sse_field,
which support both \n\n and \r\n\r\n delimiters; a hand-rolled
split("\n\n") would silently fail on real HTTPS upstreams.
Tests cover the happy path, CRLF delimiters, response.failed
errors, and the missing response.completed defensive branch.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix(codex): use TOML parser instead of regex for model extraction
Regex only matched model=... on first line, TOML parser handles
multiline TOML correctly.
Fixes#2222
* fix(stream_check): drop unused regex::Regex import
The previous commit replaced the only Regex usage in stream_check.rs
with toml::Table parsing, leaving `use regex::Regex;` orphaned.
Without this removal, `cargo clippy -- -D warnings` (run in CI)
fails with `unused import: regex::Regex`.
---------
Co-authored-by: Jason <farion1231@gmail.com>
Closes#2139
Two related defects let the installed-skills count balloon when users
tap the import confirm button multiple times — either deliberately or
because the button is still clickable while a slow import is in flight:
- The confirm button only disabled itself while `selected.size === 0`,
so it stayed clickable during a pending mutation. Each extra click
triggered another `importFromApps` mutation.
- `useImportSkillsFromApps` appended the server response to the
installed cache without deduping, so re-firing the mutation stacked
the same skills into the list again.
Disable the confirm (and cancel) buttons while the mutation is pending
— matching the `isRestoring` / `isDeleting` pattern already used by
`RestoreSkillsDialog` — and merge success payloads by
`InstalledSkill.id` so repeated results overwrite rather than
accumulate.
The merge is extracted as a pure `mergeImportedSkills` reducer to make
the behaviour unit-testable and to short-circuit on an empty payload,
returning the existing reference so React Query does not notify
subscribers about a no-op cache update.
Moonshot's official USD pricing for kimi-k2.6 is $0.95 input /
$4.00 output / $0.16 cache-hit per 1M tokens (~58-60% higher than
K2.5). The previous commit copied K2.5's $0.60/$2.50/$0.10, which
would have under-billed K2.6 traffic in the usage dashboard.
No migration needed since this version is unreleased; INSERT OR
IGNORE will write the correct values on first launch.
Bump model id and display name from K2.5 to K2.6 in Hermes, OpenClaw,
OpenCode, and Claude (direct api.moonshot.cn) presets. Pricing,
context window, and base URL are unchanged.
Add kimi-k2.6 row to model_pricing seed; no migration needed since
seed_model_pricing uses INSERT OR IGNORE and runs on every startup
via ensure_model_pricing_seeded. Old kimi-k2.5 row is kept to
preserve historical usage stats.
Nvidia aggregator forwards (moonshotai/kimi-k2.5) intentionally keep
the K2.5 SKU until Nvidia's catalog confirms K2.6.
Cover Hermes Agent onboarding (6th managed app), Claude Opus 4.7
rollout, Gemini Native API proxy, "Local Routing" rename,
application-level window controls, Copilot premium consumption
deep optimization, session list virtualization, Usage date range
picker, Stream Check error classification, pricing v8->v9 reseed,
and related breaking changes.
18 external PR contributions credited in all three locales.
Fold six late commits into the v3.14.0 section (Added / Changed / Fixed)
and refresh the stats header against the actual v3.13.0..HEAD diff:
100 commits | 219 files | +20,548 / -3,569.
Late additions covered: Hermes dashboard toolbar launch, LemonData
preset across all six apps, DDSHub Codex endpoint, Hermes toolbar
icon + MCP reorder, Hermes health-check schema fix, Usage modal
support for Hermes/OpenClaw.
DDSHub now exposes a Codex-compatible endpoint at the same host as
its Claude service. Base URL omits the /v1 suffix since the gateway
auto-routes OpenAI SDK paths.
Swap ExternalLink for LayoutDashboard on the Hermes Web UI button —
clicking it may launch `hermes dashboard` rather than just open a URL,
so a panel-style glyph reads truer than a generic external-link icon.
Also reorder the toolbar so MCP sits in the final slot, matching the
claude/codex/gemini/opencode layout.
Hermes providers were routed through check_additive_app_stream, the
OpenClaw dispatcher, which reads camelCase fields (baseUrl/apiKey/api)
and emits "OpenClaw is missing ..." errors. Hermes stores snake_case
fields (base_url/api_key/api_mode) with different protocol tags, so
users saw "OpenClaw provider is missing baseUrl" even after filling in
every Hermes field correctly.
Introduce check_hermes_stream with Hermes-specific extractors. Route
api_mode (chat_completions / anthropic_messages / codex_responses) to
the matching check_claude_stream api_format, and return bedrock_converse
as unsupported. Resolve api_mode before extracting URL/API key so users
who picked bedrock_converse see the real cause first rather than a
misleading "missing base_url" message.
Extend getProviderCredentials to read flat settingsConfig fields for
Hermes (snake_case base_url / api_key) and OpenClaw (camelCase baseUrl
/ apiKey), so the "official balance" template auto-selects for matching
providers like SiliconFlow.
Also refactor the BALANCE and TOKEN_PLAN test paths in handleTest to
reuse the precomputed providerCredentials instead of re-reading
env.ANTHROPIC_* directly, which previously caused empty key errors for
non-Claude apps even when the key was configured.
When the Hermes Web UI probe fails, the toolbar entry now opens an info
confirm dialog offering to run `hermes dashboard` in the user's preferred
terminal. Accepting spawns it via a temp bash/batch script; `hermes
dashboard` itself opens the browser once ready, so we do not poll.
The Memory panel and Health banner keep the existing toast behavior.
Also corrects the stale `hermes web` hint in the offline toast (the real
command is `hermes dashboard`) and reorders Linux terminal detection to
try `which` before stat'ing /usr/bin, /bin, /usr/local/bin.
Summarize the 94 commits since v3.13.0 (216 files, +19,923 / -3,554)
into Keep-a-Changelog entries: Hermes Agent as the 6th managed app,
Claude Opus 4.7 rollout, Gemini Native API proxy, Copilot GHES,
session list virtualization, usage date range picker, and the
"Local Proxy Takeover" -> "Local Routing" rename.
Breaking changes collected in a dedicated section: explicit Hermes
api_mode, ANTHROPIC_REASONING_MODEL removal, per-provider proxy
removal, schema bumps (v8->v9 pricing reseed, v9->v10 Hermes columns),
and XCodeAPI preset removal.
useAutoCompact cached normalWidthRef = el.scrollWidth on every
non-compact resize, but per DOM spec scrollWidth === clientWidth
when content fits. Maximizing the window (content no longer
overflows) therefore wrote the container width into
normalWidthRef, making it impossible to re-enter compact when
the window was restored to its original size.
Move the assignment inside the overflow branch so the cache is
only written at the actual compact threshold, where scrollWidth
reflects the real content width.
Normalize all icon-only ghost buttons in the header toolbar to
32x32 (w-8 px-2). Previously Hermes/OpenClaw used default sm
padding (~40px) while Claude's Skills/Sessions used w-8 px-2,
so switching apps caused a width jump and put useAutoCompact's
cached normalWidthRef out of sync across apps.
Wire hermes through SkillApps struct, DAO SQL, command parser, and
SKILLS_APP_IDS. Add a Skills entry to the Hermes toolbar. Simplify
skill_sync test fixtures to use SkillApps::default().
Hermes 0.10.0 tightened custom_providers validation (commit 2cdae233):
invalid base_urls are rejected, unknown fields produce warnings, and
new fields (rate_limit_delay, bedrock_converse, key_env) landed.
- Add bedrock_converse to the api_mode selector (and i18n labels)
- Expose rate_limit_delay in a provider-level advanced panel
- Validate base_url client-side (URL shape, template-token friendly)
- Drop per-model max_tokens — not in _VALID_CUSTOM_PROVIDER_FIELDS
- Round-trip test asserts set_provider preserves rate_limit_delay /
key_env / any unknown forward-compat field
Three unrelated test failures surfaced after rebase:
- McpFormModal expected the apps boolean set without `hermes`; Hermes MCP
support is now wired, so the fixture must include `hermes: false`.
- therouter Gemini preset was bumped to `gemini-3.1-pro` in a later
commit; update the assertions to match current config.
- openclaw_config tests mutate process-level `CC_SWITCH_TEST_HOME` and
`HOME` inside a module-local Mutex, but hermes_config does the same
under its own separate Mutex. Running both modules in parallel let the
env races corrupt hermes_config's `with_test_home`. Tag the four
env-mutating openclaw tests with `#[serial]` so they serialize across
modules via serial_test's process-wide default key.
The model-mapping quick-set button referenced an undefined `reasoningModel`
prop and wrote `ANTHROPIC_REASONING_MODEL`, which the backend explicitly
marks as deprecated legacy (see services/provider/mod.rs and
services/proxy.rs). Remove all three references so typecheck passes and
the button matches the provider model schema.
Hermes' built-in api_mode detection only matches a handful of official
endpoints (api.openai.com, api.anthropic.com, api.x.ai, AWS Bedrock);
third-party / proxy endpoints silently fall back to chat_completions,
which causes opaque 401/404s on Anthropic-protocol or Codex-Responses
providers. The "Auto" option was misleading for the common third-party
case.
- Drop the "Auto" option from the API Mode dropdown; remove the
HermesApiModeChoice sentinel type so writes always emit api_mode.
- Default new providers and legacy entries lacking api_mode to
chat_completions (only persisted on user save).
- Deeplink imports now write api_mode: chat_completions explicitly
instead of relying on URL heuristics; test renamed accordingly.
- Rename the "Codex Responses (Copilot / OpenCode)" label to
"OpenAI Responses" to match OpenAI's /v1/responses naming.
After /simplify review of the P1-3 second wave, two small cleanups:
- Lift the `_cc_source` / `providers_dict` magic strings out of
ProviderCard into a shared helper (`isHermesReadOnlyProvider`) and
named constants in hermesProviderPresets.ts. Front-end and back-end
now document the same marker contract in two mirrored places
instead of drifting strings.
- Replace the duplicate `is_dict_only_provider` + `format!` branches
at the top of `set_provider` / `remove_provider` with a single
`ensure_provider_writable(config, name, verb)` guard. Future error
copy tweaks only have to happen once.
No behaviour change; all 52 hermes_config tests stay green.
ProviderCard now detects Hermes provider entries sourced from the
v12+ `providers:` dict via the `_cc_source` marker that the backend
injects, and renders a "Hermes Managed" badge beside the title.
ProviderActions receives an `isReadOnly` prop that disables the Edit
and Delete buttons (with a tooltip pointing the user at Hermes Web
UI) while keeping Switch and Duplicate enabled — switching only
touches `model.*`, and duplicate lets users fork the overlay into
their own `custom_providers:` list.
Three-locale i18n keys `provider.managedByHermes` /
`provider.managedByHermesHint` added.
Hermes v12+ migrated some provider entries from the `custom_providers:`
list into a `providers:` dict (keyed by id). CC Switch previously
ignored that source entirely, leaving users blind to providers they had
configured via Hermes' own Web UI; the only feedback was a generic
migration warning in the health banner.
`get_providers()` now unions both sources, matching upstream
`get_compatible_custom_providers` dedup order (list wins on name
collision). Entries coming from the dict carry a `_cc_source =
"providers_dict"` marker plus the original `provider_key`, which the
UI layer will use to render them read-only. `set_provider` and
`remove_provider` now refuse to touch dict-only entries, steering the
user to Hermes Web UI. `sanitize_hermes_provider_keys` strips the UI
markers on write so they never reach YAML.
The `schema_migrated_v12` health warning copy reframes the situation:
entries are shown read-only in CC Switch rather than invisible.
DeepLink Hermes import was emitting camelCase (baseUrl / apiKey /
apiMode) that the Hermes runtime does not recognise, poisoning
`custom_providers:` entries on activation. The MCP sync path was
also stripping `auth: oauth` on round-trip, silently downgrading
OAuth-type servers to unauthenticated calls.
The Hermes deeplink branch now emits snake_case via a dedicated
builder; `sanitize_hermes_provider_keys` runs on both `set_provider`
and `get_providers` so legacy DB records heal on next access.
`HERMES_EXTRA_FIELDS` preserves `auth`. The `api_mode` dropdown gains
`codex_responses` (Copilot / OpenCode), and the schema-migrated
warning copy no longer hard-codes "v12" (upstream `_config_version`
is now 19).
Replaces the greyed-out "Memory is disabled" banner with a real Switch
at the top of each memory tab. Users can now toggle Hermes' memory/user
blobs without leaving CC Switch; the underlying write goes through the
merge-aware `set_memory_enabled`, so budgets and external-provider
settings survive toggle operations. The new `useToggleHermesMemoryEnabled`
mutation invalidates the limits query so the Switch state and the
amber disabled-hint update in lockstep.
Reworks the `schema_migrated_v12` health banner copy to match the
simplified "CC Switch only manages custom_providers" posture — it now
tells users to reconcile migrated dict entries via Hermes Web UI,
instead of the earlier (and now inaccurate) "CC Switch reads both".
Adds a dedicated Hermes row to the directory-override settings so users
can point CC Switch at alternate Hermes config locations (e.g. a second
profile directory for work/personal split). `get_config_dir` on the
Rust side already supports hermes; this just wires up the frontend row.
Wiring it through `useDirectorySettings` revealed a scaling problem:
every supported app required five parallel ternary chains across
`computeDefaultConfigDir`, `updateDirectory`, `browseDirectory`,
`resetDirectory`, and `updateDirectoryState`. Replaces those with two
lookup tables (`APP_DIRECTORY_META`, `DIRECTORY_KEY_TO_SETTINGS_FIELD`)
so adding the next app is two entries, not fifteen edit sites.
Drive-by cleanup from the same touch:
* `resetAllDirectories` takes a `ResolvedAppDirectoryOverrides` object
instead of five positional optional strings.
* `setResolvedDirs` returns the same reference when the sanitized
value is unchanged, so no-op edits don't cascade renders.
Also lands all i18n updates for this series (`hermesConfigDir` and
placeholder, Memory section's enable/disable/toggleFailed copy, and
the reworded `schemaMigratedV12` warning) in zh/en/ja together.
Drops the v11→v12 providers-dict compat layer: CC Switch now only
reads/writes `custom_providers:`, leaving migrated `providers:` dict
entries to Hermes Web UI for reconciliation (Hermes' runtime already
merges both shapes via `get_compatible_custom_providers`). The
`schema_migrated_v12` health warning now points users there when a
dict-migrated config is detected.
Adds forward-compat merge to `set_provider`: when updating an existing
entry, on-disk fields the UI payload didn't submit (e.g. Hermes-only
`request_timeout_seconds`, `key_env`) are carried over. Without this,
editing one field via CC Switch would silently strip the rest.
Adds `set_memory_enabled` + `set_hermes_memory_enabled` Tauri command
for the upcoming memory-switch UI. Writes go through a merge-aware
section replacement so character budgets and external-provider fields
survive toggle operations.
Removes four dict-only helpers (`normalize_providers_dict_entry_for_read`,
`rename_alias_key`, `json_obj_non_empty_str`,
`resolve_provider_name_from_yaml_entry`) and the multi-section write
helper. Simplifies `get_providers` / `remove_provider` / health scan
back to list-only. Replaces nine obsolete dict-related tests with
`set_provider_preserves_unknown_fields_on_update` and
`set_memory_enabled_preserves_other_fields`.
Hermes has no slash-prompt concept (templates live as Skills), so the
Prompts tab for the Hermes app was always empty. Swap the toolbar Book
button for a Brain button that opens a new Memory panel editing
~/.hermes/memories/{MEMORY,USER}.md — Hermes' first-class memory store
which its Web UI exposes only as on/off toggles, never as an editor.
The panel shows each file in its own tab with a character-budget bar
read from config.yaml's nested memory.* section (memory_char_limit /
user_char_limit, default 2200 / 1375). Edits are written atomically;
Hermes picks them up on the next session start per MemoryStore.
Also extract useDarkMode to src/hooks/useDarkMode.ts — the codebase
already repeats the same MutationObserver pattern in 12+ places; this
PR introduces the shared hook and uses it once, leaving the migration
of the other copies to a follow-up.
Slim the Hermes surface in CC Switch to match its core positioning —
cross-client provider switching and shared MCP/prompts/skills — and
delegate deep configuration (model, agent, env, skills, cron, logs)
to the Hermes Web UI at http://127.0.0.1:9119.
- Drop AgentPanel/EnvPanel/ModelPanel and their mutation commands,
hooks, types, and i18n keys across zh/en/ja.
- Add open_hermes_web_ui Tauri command that probes /api/status and
launches the URL in the system browser. Hermes injects its own
session token into the returned HTML, so CC Switch doesn't need
to touch auth.
- Surface the launcher from the Hermes toolbar and the health banner
via a shared useOpenHermesWebUI() hook; the offline error code is
defined once per side and referenced across the contract.
- Keep read-only access to model.provider so ProviderList can still
highlight the active supplier; apply_switch_defaults continues to
write the top-level model section when switching providers.
Net diff: +152 / -1253.
Migrate 18 Hermes provider presets from anthropic_messages to
chat_completions to sidestep known upstream Hermes bugs (model-name
dot-mangling in normalize_model_name, api_mode drop after v11->v12
migration, and auxiliary_client OpenAI hardcode).
Native providers now target each vendor's official OpenAI-compatible
endpoint with correct model IDs: Kimi (kimi-k2.5-preview on /v1),
Bailian (compatible-mode/v1 with Qwen3 defaults), Xiaomi MiMo, Longcat
(/openai/v1), Zhipu GLM (/api/paas/v4), ModelScope, MiniMax, SiliconFlow,
and Novita (/v3/openai).
Aggregators (Shengsuanyun, AiHubMix, DMXAPI, Compshare, TheRouter)
default to GPT-5.4 on chat_completions, mirroring the Codex preset
lineup. TheRouter omits gpt-5.4-pro since that variant is Responses-only
and Hermes implements only chat_completions. OpenRouter's existing
openai/gpt-5 entry is bumped to openai/gpt-5.4.
Claude-only proxies are left on anthropic_messages; their Codex
counterparts use wire_api=responses, so there is no evidence their
chat_completions endpoints serve OpenAI models.
Remove the Anthropic, OpenAI, and Google AI presets from the Hermes
preset list. They were placeholder samples introduced when the Hermes
module first landed and do not match the actual user paths in
CC Switch (Claude / Codex go through OAuth, Gemini Native is its own
adapter), and the upstream endpoints are not reachable for most of
the target users anyway.
Fix the Nous Research preset: its base_url was a fabricated domain
(inference.nous.hermes.dev) that has never resolved. Point it at the
real Nous Portal endpoint (inference-api.nousresearch.com/v1) and
add apiKeyUrl so users can jump straight to portal.nousresearch.com
to provision a key.
Drop the now-orphan providerForm.presets.{anthropic,openai,googleai}
i18n keys from zh / en / ja since no preset references them anymore.
Import 38 Claude presets into Hermes by mapping env-style
ANTHROPIC_BASE_URL/AUTH_TOKEN to flat base_url/api_key, deriving
api_mode from apiFormat (anthropic_messages or chat_completions),
deduping ANTHROPIC_*_MODEL into models[], and pointing
suggestedDefaults at ANTHROPIC_MODEL. Skip OAuth-only presets
(Codex, Copilot), Bedrock SigV4, Gemini Native, and the three
already shipped on the Hermes side (OpenRouter, Anthropic,
DeepSeek). Place Shengsuanyun at the head of the Hermes array so
the partner shows first in the preset panel.
In the Claude preset list, restore Shengsuanyun back ahead of
Gemini Native. The Gemini Native preset (#1918) was inserted
between Claude Official and Shengsuanyun, which made the
third_party category register first in the reduce-based grouping
and pushed the aggregator block (and Shengsuanyun) behind it.
Backfill the missing providerForm.presets translations across zh,
en, and ja (openrouter, anthropic, openai, googleai, deepseek,
together; plus shengsuanyun for en and ja) so existing Hermes
preset names no longer render literal i18n keys.
Switching a Hermes provider previously only fired a toast because the frontend treated Hermes as non-additive (unlike backend AppType::is_additive_mode, which lists OpenCode | OpenClaw | Hermes) and relied on the unused is_current DB flag for highlighting. Align the UI model with the backend:
- Include Hermes in ProviderActions' isAdditiveMode so the main button switches between "Add" and "Remove".
- Drive the "current" highlight from model.provider (via useHermesModelConfig) instead of the DB is_current field; model.provider is Hermes's real SSOT for the active provider.
- Reuse OpenClaw's set-as-default button slot to expose an "Enable" action on Hermes that calls switchProvider, so providers already in config can be activated without re-adding. switch_normal + apply_switch_defaults already atomically update custom_providers and model.provider, so no backend change is needed.
- Invalidate liveProviderIds + modelConfig + health in parallel after add/update/delete/switch via a new invalidateHermesProviderCaches helper, replacing four copies of three sequential awaits.
Writing to the v12+ `providers:` dict broke every anthropic_messages
provider. Hermes `runtime_provider.py::_get_named_custom_provider` has a
bug in its `providers:` branch: the returned entry drops `api_mode`,
`transport`, `models`, and singular `model:`, and
`_resolve_named_custom_runtime` then falls back to `chat_completions` —
so an Anthropic-format endpoint receives OpenAI-format requests and
returns 404.
Keep using the legacy `custom_providers:` list; its normalization path
(`_normalize_custom_provider_entry`) preserves every field. In addition,
write a singular `model:` alongside the plural `models:` dict so the
Hermes runtime and `/model` picker see the default model id.
Also keep the `apply_switch_defaults` fix from the prior attempt:
`model.provider` is always updated, and `model.default` is only
overwritten when the new provider declares at least one model — so
switching to an incomplete provider no longer silently no-ops.
The Hermes provider form previously only exposed Base URL and API Key,
forcing users to drop into the Model panel to hand-edit model IDs after
adding a provider. Following OpenClaw's shape, the form now carries:
- An API Mode selector (auto-detect / chat_completions / anthropic_messages).
"auto" is a UI-only sentinel — selecting it deletes api_mode from the
config so the YAML doesn't leak a redundant field.
- A model list editor where the first row is badged as the default and
each row has a collapsible Advanced panel for context_length and
max_tokens. Adding/removing rows uses a UUID-keyed ref so typing in
one input doesn't drop focus when another row is added.
- A Fetch Models button that pulls /v1/models from the configured
endpoint and exposes the catalog in a per-row dropdown, identical to
OpenClaw's flow. The vendor grouping is memoized so keystrokes don't
trigger a reduce+sort per model row — Radix DropdownMenuContent does
not lazy-mount, so the inner JSX evaluates on every render regardless
of whether the menu is open.
Three-locale i18n keys are added together (zh/en/ja).
Hermes custom_providers entries now carry an ordered models array
(id / context_length / max_tokens) plus suggestedDefaults. The backend
serializes the array to the YAML dict shape Hermes expects on write and
inverts it on read, preserving insertion order via the preserve_order
feature on serde_json.
When a user switches providers, switch_normal calls apply_switch_defaults
so the top-level model.default / model.provider follow the selected
provider's first model. Previously switching a Hermes provider only
shuffled custom_providers[] and left Hermes pointing at whatever
model.provider was set before.
Seven existing Hermes presets now ship with a curated models list so
switching lands on a working default without a detour through the
Model panel.
Copilot routes through OpenAI-compatible endpoints that reject Anthropic's
thinking and redacted_thinking blocks. Previously the request would fail
upstream, burning one premium interaction, and only then trigger
thinking_rectifier to retry. This adds a proactive strip_thinking_blocks
pass in the Copilot optimization pipeline (step 3.5, after tool_result
merging). Signature fields and top-level thinking are left alone — those
are the reactive rectifier's job on the error path.
Also fixes a default-value inconsistency where CopilotOptimizerConfig's
Default impl used "gpt-4o-mini" while the serde default function returned
"gpt-5-mini" (aligned to gpt-5-mini, matching the reference implementation).
Aligned with yuegongzi/copilot-api's /v1/messages handler behavior.
Drop the ¥0.5/$ pricing claim and monthly/paygo line from the SSSAiCode
entry in all three READMEs, keeping only the fast-invoicing mention.
Also collapse a duplicate blank line after the LemonData row in the JA
README to match the ZH version.
The previous Hermes icon was an inline purple "H" SVG unrelated to the
real Hermes Agent brand. Replace it with the official Nous Research
avatar sourced from hermes-agent/landingpage/icon-512.png, routed
through iconUrls as a Vite URL import. The PNG is post-processed to
strip the 4px black rectangle border (cropped inward by 6px and pasted
back onto a 512x512 transparent canvas). Also switch defaultColor to
black to match the monochrome artwork when the asset falls back to an
initial glyph.
- OpenClaw: replace opus-4-6 with opus-4-7 across 17 aggregator presets
(id, name, primary, modelCatalog); AWS Bedrock entry rewritten to new
SKU anthropic.claude-opus-4-7 (drops -v1 and dated suffix per official
4.7 model card) and pricing corrected to $5/$25/$0.50/$6.25 during the
SKU swap, aligning with schema.rs source of truth
- OpenCode: same replacement for 13 aggregators plus
OPENCODE_PRESET_MODEL_VARIANTS entries for @ai-sdk/amazon-bedrock and
@ai-sdk/anthropic, plus AWS Bedrock provider models map
- OpenRouter / TheRouter / GitHub Copilot in claudeProviderPresets use
dot-style id; update to anthropic/claude-opus-4.7 (missed by 509d2250)
- omo: switch agent/category recommended to opus-4-7; replace key in
OMO_BACKGROUND_TASK_PLACEHOLDER priority map
- hermes_config.rs: update doc comments and test fixtures to opus-4-7;
Hermes ModelPanel placeholder and i18n defaultHint examples follow
- i18n unspecifiedHigh category description bumped to 'Claude Opus 4.7
max variant' to match omo recommended
- Test fixtures updated: therouter preset assertion and opencode Bedrock
variant lookup now check for opus-4-7
- Sonnet 4.6 / Haiku 4.5 untouched - no official 4.7 release for them
- Seed claude-opus-4-7 pricing (same tier as 4.6: $5 / $25 / $0.50 /
$6.25 per million tokens). Relies on incremental INSERT OR IGNORE
seeding; no SCHEMA_VERSION bump needed.
- Whitelist opus-4-7 in thinking optimizer so it uses adaptive
thinking + max effort + 1M context beta, matching 4.6 behavior.
- Bump default OPUS model in PIPELLM and AWS Bedrock (AKSK / API Key)
presets to 4.7. Bedrock SKU drops the -v1 suffix per the official
4.7 model card (anthropic.claude-opus-4-7 and
global.anthropic.claude-opus-4-7).
Align the New API usage query template with the GENERAL template by
including "User-Agent: cc-switch/1.0" in its request headers, so
cc-switch requests are identifiable in provider server logs and less
likely to be blocked by UA-based rate limiting on some New API
deployments.
Per Anthropic's effort parameter docs, "high" is the API default and
setting effortLevel="high" is equivalent to omitting the field entirely.
The toggle previously produced no effect.
Claude Opus 4.6, Sonnet 4.6, and Opus 4.7 now support a "max" level
that enables unconstrained reasoning. Rename the checkbox (effortHigh
-> effortMax) and write effortLevel="max" when toggled on. Existing
"high" values in user configs are left untouched.
Updates zh/en/ja locales and user-manual entries accordingly.
- Add "hermes" to AppId union type and all exhaustive Record<AppId>
- Add HermesModelConfig, HermesAgentConfig, HermesEnvConfig types
- Add hermes field to VisibleApps, McpApps, ProxyTakeoverStatus
- Create src/lib/api/hermes.ts with Tauri invoke wrappers
- Create src/hooks/useHermes.ts with 5 query + 3 mutation hooks
- Register hermes in APP_IDS, APP_ICON_MAP (violet color scheme)
- Split MCP_SKILLS_APP_IDS into MCP_APP_IDS (includes hermes) and
SKILLS_APP_IDS (excludes hermes, since Hermes has no Skills support)
- Wire hermes additive-mode into App.tsx (remove/duplicate handlers),
ProviderList.tsx (live provider ID query + In Config badge),
mutations.ts (cache invalidation on switch/add/delete)
- Add Hermes checkbox to McpFormModal
- Add basic hermes i18n keys (en/zh/ja)
Add mcp/hermes.rs with bidirectional MCP format conversion:
- convert_to_hermes_format: strip type field, infer from command/url
- convert_from_hermes_format: infer type, strip Hermes-specific fields
- Merge-on-write: existing Hermes fields (tools, sampling, timeout,
roots, enabled) preserved when user has customized them
- update_mcp_servers_yaml: closure-based read-modify-write under write
lock to prevent TOCTOU races in concurrent sync operations
- 9 unit tests for format conversion and merge logic
Wire up all MCP service dispatch:
- Replace Hermes TODO stubs with real sync/remove calls
- Remove Hermes from sync_all_enabled skip list
- Enable deep link hermes MCP flag (apps.hermes = true)
- Add Hermes import to import_mcp_from_apps command
Add hermes_config.rs (~1190 lines) with YAML section-level replacement
that preserves comments and formatting in unmanaged sections:
- Type definitions: HermesModelConfig, HermesAgentConfig, HermesEnvConfig
- YAML section finder (find_yaml_section_range) with column-0 key detection
- Provider CRUD on custom_providers array (indexed by name field)
- Model/Agent config get/set via yaml<->json conversion
- .env dotenv read/write preserving comments and line ordering
- Health check, backup with rotation, write lock (OnceLock<Mutex>)
- MCP section access stubs for Phase 4
- 19 unit tests
Add commands/hermes.rs with 10 Tauri commands registered in lib.rs.
Replace all Hermes TODO stubs in services/provider/live.rs with real
implementations (import, remove, write-to-live, read-live-settings).
- Bump SCHEMA_VERSION from 9 to 10
- Add enabled_hermes column to mcp_servers and skills tables
- Add migrate_v9_to_v10 with table_exists guard for skills (may not
exist in databases migrated from very old versions)
- Update dao/mcp.rs to fully read/write enabled_hermes in all queries
- Update dao/skills.rs: don't SELECT enabled_hermes (Hermes doesn't
support Skills yet), keep column indices clean
Register AppType::Hermes across the entire Rust backend:
- Add Hermes variant to AppType enum with additive mode and MCP support
- Add hermes field to McpApps, SkillApps, CommonConfigSnippets, and all
per-app structs (McpRoot, PromptRoot, VisibleApps, AppSettings)
- Create minimal hermes_config.rs with get_hermes_dir() respecting
settings override, matching the pattern of other app config modules
- Update all match arms in commands, services, deeplink, proxy, mcp,
session_manager, and test files
- Extract shared build_additive_app_settings() to eliminate duplication
between OpenClaw and Hermes deep link handling
- Combine identical OpenClaw/Hermes proxy match arms into unified arms
The takeover.failed i18n template lacked the {{detail}} placeholder
and three useProxyStatus onError callbacks omitted the detail variable,
so proxy start/stop/takeover failures all displayed a generic message
regardless of the underlying cause.
ANTHROPIC_REASONING_MODEL was a non-official env var that forced all
requests with thinking params to use a single "reasoning model",
overriding the user's /model selection. Since new Claude Code versions
send adaptive thinking by default, this caused /model to silently fail.
- Remove reasoning_model field and has_thinking_enabled() from model_mapper
- Simplify map_model() to pure type-based matching (haiku/sonnet/opus)
- Remove reasoning model UI field from provider form
- Retain ANTHROPIC_REASONING_MODEL in ENV_EXCLUDES and override-key
cleanup lists so legacy configs don't leak into common config
* fix(claude-plugin): sync current provider config to settings.json on toggle enable
- Extract syncClaudePluginIfChanged to share logic between autoSaveSettings and saveSettings
- Fix P1: enableClaudePluginIntegration toggle in General tab now actually syncs ~/.claude/settings.json
- Fix P2: check syncCurrentProvidersLiveSafe() return value and show toast on failure
- Fix P3: sync providers on both enable and disable, not just enable
- Fix P4: avoid double syncCurrentProvidersLiveSafe when plugin toggle + dir change happen together
- Remove duplicate comment
- Add missing providersApi.getCurrent/getAll mocks in tests
* style: reformat after rebase onto main
Prettier flagged a line-break introduced by the openclaw directory
change (from main) after rebase.
* fix(claude-plugin): read prev enabled state from live cache to avoid stale closure
syncClaudePluginIfChanged compared enabled against data?.enableClaudePluginIntegration
captured in a useCallback closure. After invalidateQueries + refetch, the React
Query cache is up to date, but the consuming hook's closure does not see the new
value until React re-renders. Quick on->off toggles could therefore skip
applyClaudePluginConfig, leaving ~/.claude/config.json in the previously enabled
state even though settings.json was persisted as disabled.
Read the previous value synchronously from queryClient.getQueryData(["settings"])
before saveMutation.mutateAsync(), then pass it to the helper as prevEnabled.
getQueryData bypasses the closure and reflects the live cache at call time.
Test covers the race: closure data stays at false while the cache reports true;
the helper must still call applyClaudePluginConfig({ official: true }).
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix: launch Ghostty via shell command
Use Ghostty's shell execution path instead of injecting raw terminal input so Claude resume commands run reliably when opening a session terminal.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ghostty): pass cwd via --working-directory instead of shell string
Use Ghostty's native --working-directory flag to set the working
directory, matching the pattern used by Alacritty. This avoids shell
expansion of special characters (e.g. $VAR, spaces) in project paths.
The command is now passed directly to -c without a cd prefix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ui): add quick-set button for model mapping fields
Add a "一键设置" (Quick Set) button in the model mapping section to
simplify provider configuration. When users enter a model name in any
of the five model fields, they can now click this button to populate
all fields with that same value.
This addresses the UX friction of manually filling all five model
mapping fields (主模型, 推理模型, Haiku, Sonnet, Opus) when the
provider uses the same model name across all request types.
Implementation:
- Add Wand2 icon import from lucide-react
- Insert quick-set button alongside existing fetch-models button
- Logic picks first non-empty model value and applies to all fields
- Show success toast after applying
- Disabled state when all model fields are empty
- Add i18n strings for zh, en, ja locales
Relates to user feedback about tedious model configuration workflow.
* style(ui): format ClaudeFormFields component code
Apply consistent code formatting to ClaudeFormFields.tsx following
project linting rules. Includes multi-line import statements and
improved readability for conditional expressions.
* feat(copilot): add GitHub Enterprise Server support
* fix(copilot): address GHES PR review findings (P1 + 2×P2)
- P1: Use composite account ID (domain:user_id) for GHES to prevent
cross-instance ID collisions; github.com keeps plain numeric ID for
backward compatibilit
- P2-a: Use get_api_endpoint() for model list URL with automatic
fallback to static URL when dynamic endpoint resolution fails
- P2-b: Add normalize_github_domain() as backend SSOT for domain
normalization (lowercase, strip protocol/path/query, reject userinfo)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
`import_from_apps()` saves skills to the database but does not create
symlinks/copies in the target app directories (e.g. `~/.claude/skills/`).
This causes skills to appear as "installed" in the UI while the actual
files are missing from the app directories.
Add `sync_to_app_dir()` calls after `db.save_skill()` in the import
loop, matching the pattern used by `install()` and `toggle_app()`.
* refactor(proxy): extract take_sse_block helper with CRLF delimiter support
Replace inline `buffer.find("\n\n")` SSE splitting logic across streaming,
streaming_responses, response_handler, and response_processor with a shared
`take_sse_block` function that handles both `\n\n` and `\r\n\r\n` delimiters.
* feat(proxy): add Gemini Native URL builder and full-URL resolver
Introduce gemini_url module that normalizes legacy Gemini/OpenAI-compatible
base URLs into canonical models/*:generateContent endpoints. Supports both
structured Gemini URLs (auto-normalized) and opaque relay URLs (pass-through
with query params only).
* feat(proxy): add Gemini Native schema, shadow store, transform, and streaming
- gemini_schema: Gemini generateContent request/response type definitions
- gemini_shadow: session-scoped shadow store for thinking signature and
tool-call state replay across streaming chunks
- transform_gemini: bidirectional Anthropic Messages ↔ Gemini Native
request/response conversion with thinking block and tool-use support
- streaming_gemini: Gemini SSE → Anthropic SSE streaming adapter with
incremental thinking/text/tool_use delta emission
* feat(proxy): wire Gemini Native format into proxy core and Claude adapter
Integrate gemini_native api_format throughout the proxy pipeline:
- ClaudeAdapter: detect Gemini provider type, Google/GoogleOAuth auth
strategies, and suppress Anthropic-specific headers for Gemini targets
- Forwarder: Gemini URL resolution, shadow store threading, endpoint
rewriting to models/*:generateContent with stream/non-stream variants
- Handlers: route Gemini streaming through streaming_gemini adapter and
non-streaming through transform_gemini converter
- Server/State: add GeminiShadowStore to shared ProxyState
- StreamCheck: support gemini_native health check with proper auth headers
* feat(ui): add Gemini Native provider preset and api format option
- Add gemini_native to ClaudeApiFormat type and ProviderMeta.apiFormat
- Add "Gemini Native" provider preset with default Google AI endpoints
- Show Gemini-specific endpoint hints and full-URL mode guidance
- Add gemini_native option to API format selector in ClaudeFormFields
- Add i18n strings for zh/en/ja
* feat(proxy): add Gemini Native tool argument rectification
* feat(proxy): update Gemini streaming and transformation logic
* fix(proxy): align shadow turns to tail on client history truncation
* fix: revert unrelated cache_key change in claude proxy transform
Restore .unwrap_or(&provider.id) fallback for cache_key to match main
branch behavior. Only gemini_native related changes should be in this branch.
* Prevent Gemini review regressions in streaming and tool rectification
PR #1918 review feedback exposed two correctness issues in the Gemini Native adapter path. Gemini SSE buffering was still using lossy UTF-8 decoding, which could corrupt split multibyte payloads and drop streamed output. Tool arg rectification also removed top-level parameters eagerly, which broke tools that legitimately define a parameters field.
This change moves Gemini SSE buffering onto the existing append_utf8_safe path and makes parameters flattening conditional on the schema actually expecting nested extraction. The old Skill rectification path stays intact, and new regression tests cover both the preserved parameters case and UTF-8-split JSON payloads.
Constraint: Existing PR #1918 review feedback must be fixed without staging unrelated local docs and artifact files
Rejected: Keep String::from_utf8_lossy in Gemini SSE buffering | corrupts split multibyte payloads and can drop JSON chunks
Rejected: Always preserve the parameters wrapper | regresses the existing nested-parameters rectification path for Skill-style tools
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Gemini SSE buffering on the UTF-8-safe accumulator path and only unwrap parameters when the target schema does not declare it as a legitimate field
Tested: cargo fmt --manifest-path src-tauri/Cargo.toml --all; cargo test --manifest-path src-tauri/Cargo.toml preserves_utf8_boundaries_when_json_payload_spans_chunks; cargo test --manifest-path src-tauri/Cargo.toml gemini_to_anthropic_rectifies_tool_args_from_schema_hints; cargo test --manifest-path src-tauri/Cargo.toml rectifies_streamed_skill_args_from_nested_parameters; cargo test --manifest-path src-tauri/Cargo.toml gemini_to_anthropic_preserves_legitimate_parameters_arg
Not-tested: Full src-tauri test suite; live end-to-end Gemini relay traffic against upstream services
* Keep Gemini tool replay stable across Claude request boundaries
Claude Code follow-up requests were still falling back to locally reconstructed functionCall parts, which dropped Gemini thought signatures and triggered INVALID_ARGUMENT errors from the official Gemini API. The replay path needed to survive real Claude request boundaries, not just idealized in-process test flows.
This change makes Claude requests reuse X-Claude-Code-Session-Id as the shadow session key, records streamed Gemini tool turns before tool_use events are fully drained, and matches assistant tool_use turns to shadow state by tool_use id and normalized tool name before positional fallback. Together these fixes keep thoughtSignature-bearing Gemini tool calls available for the next request in the loop.
Constraint: Claude Code sends a stable X-Claude-Code-Session-Id header while metadata.session_id may be absent on follow-up requests
Rejected: Rely on metadata-only Claude session extraction | generated fresh session ids and broke cross-request shadow replay
Rejected: Record Gemini shadow only after streaming completes | loses the race when the client sends the next request immediately after tool_use
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve Gemini shadow continuity across requests by keying Claude sessions from the header first and persisting tool-call shadow before yielding tool_use events downstream
Tested: cargo fmt --manifest-path src-tauri/Cargo.toml --all; cargo test --manifest-path src-tauri/Cargo.toml test_extract_session_from_claude_header; cargo test --manifest-path src-tauri/Cargo.toml test_extract_session_from_claude_header_precedes_metadata; cargo test --manifest-path src-tauri/Cargo.toml stores_tool_shadow_before_tool_use_events_are_fully_drained; cargo test --manifest-path src-tauri/Cargo.toml shadow_replay_matches_tool_use_turn_by_id_when_position_drifts; cargo test --manifest-path src-tauri/Cargo.toml shadow_replay_aligns_to_latest_turns_after_client_truncation
Not-tested: Full src-tauri test suite without test filters; live end-to-end Gemini relay after this exact commit hash
* style: apply cargo fmt to pass Backend Checks CI
Wrap prompt_cache_key chained call across lines per rustfmt default
formatting. Pure formatting change, no behavior difference.
* fix(proxy/gemini): synthesize unique ids for no-id tool calls + enforce object params schema
P1 — Parallel tool calls without Gemini-assigned ids no longer collapse.
Gemini 2.x native parallel `functionCall` entries may omit the `id` field.
The previous `merge_tool_call_snapshots` fell back to matching by `name`,
which silently merged two parallel calls to the same function into one
entry — dropping the first call's args. The non-streaming path and shadow
store further bottlenecked on empty-string ids: multiple `tool_use` blocks
shared the same id, and `tool_name_by_id.get("")` could only return one
mapping, causing later `tool_result` round-trips to fail with
`Unable to resolve Gemini functionResponse.name` or bind to the wrong tool.
Fix: introduce `synthesize_tool_call_id()` producing `gemini_synth_<uuid>`.
Both streaming and non-streaming response paths now guarantee every
Anthropic-visible tool_use carries a unique id. `merge_tool_call_snapshots`
matches by id first, falling back to the `parts` array position (for the
cumulative-streaming case) while preserving the synthesized id across
chunks. `convert_message_content_to_parts` detects the synthetic prefix
and strips the id from outbound `functionCall`/`functionResponse` so the
internal identifier never leaks upstream. `shadow_parts` performs the
same strip when replaying a recorded assistant turn.
P2 — Vertex AI rejects empty `parameters` schemas. When an Anthropic tool
arrives with missing or empty `input_schema`, the proxy used to emit
`"parameters": {}` (no `type`), which fails Vertex AI validation with
`functionDeclaration parameters schema should be of type OBJECT`.
Contrary to the automated-review suggestion, the fix is not to omit
`parameters` (that too is rejected) but to normalize to the canonical
empty-object form `{type: "object", properties: {}}`.
Refs: google-gemini/generative-ai-python#423, BerriAI/litellm#5055.
Fix: new `ensure_object_schema` helper in `gemini_schema` promotes
missing `type` to `"object"` and adds empty `properties` when absent,
while leaving atomic (non-object) schemas untouched.
Tests: seven new regressions covering parallel no-id calls, cumulative
chunk id reuse, synthetic-id round-trip both directions, shadow replay
id stripping, and the three Vertex-AI schema shapes.
The two existing wrapper functions (`gemini_to_anthropic` and
`gemini_to_anthropic_with_shadow`) gain `#[allow(dead_code)]` to clear
a pre-existing clippy -D warnings failure — they are part of the public
transform API surface and intentionally kept for future callers.
Addresses Codex review P1/P2 on #1918.
* fix(proxy/gemini): narrow URL normalization + guard empty OAuth access_token
P2a — Preserve opaque relay URLs that contain `/v1/models/` prefixes.
`should_normalize_gemini_full_url` previously flagged any full URL whose
path merely contained `/v1beta/models/` or `/v1/models/` as a structured
Gemini endpoint, forcing rewrite to `.../v1beta/models/{model}:method`.
This silently dropped legitimate relay route segments (e.g.
`https://relay.example/v1/models/invoke` → `.../v1beta/models/...:generateContent`,
losing `/invoke`) and sent traffic to the wrong upstream path.
Replace the bare `contains(...)` checks with
`matches_structured_gemini_models_path`, which requires the
`/models/` segment to be followed by a canonical Gemini method call
(`*:generateContent` or `*:streamGenerateContent`). The
`matches_bare_gemini_models_path` helper is generalized (and renamed) to
handle both `/v1beta/models/` and `/v1/models/` alongside the original
bare `/models/` shape.
P2b — Reject empty Gemini OAuth access_tokens before they reach the
bearer header.
`GeminiAdapter::parse_oauth_credentials` accepts refresh-token-only JSON
(and surfaces `{"access_token": "", ...}` for expired credentials) with
`access_token` defaulting to `""`. The Claude adapter's GeminiCli branch
then called `AuthInfo::with_access_token(key, creds.access_token)`
unconditionally, so the bearer-header builder at
`AuthStrategy::GoogleOAuth` resolved to `Authorization: Bearer ` — a
deterministic 401 from upstream.
CC Switch does not currently exchange the refresh_token for a fresh
access_token (`OAuthCredentials::needs_refresh` / `can_refresh` are
annotated `#[allow(dead_code)]`). Until that exists, only attach
`access_token` when it is non-empty; fall back to plain GoogleOAuth
strategy with the raw key and log a warn pointing users at
`~/.gemini/oauth_creds.json` so the failure mode is observable.
Tests:
- gemini_url.rs: three new regressions — opaque `/v1/models/invoke`,
opaque `/v1beta/models/route`, and the positive counter-case where a
structured `/v1/models/...:generateContent` path still normalizes.
- claude.rs: three new `test_extract_auth_gemini_cli_*` tests covering
refresh-only JSON, empty-string access_token JSON, and the valid-JSON
pass-through.
All 839 lib tests pass; cargo fmt + clippy -D warnings clean.
Addresses Codex review P2 findings on #1918.
* fix(proxy/gemini): treat empty-string functionCall id as missing in streaming path
Follow-up to the earlier P1 fix: some Gemini relays serialize an absent
functionCall id as `"id": ""` instead of omitting the field. The
non-streaming `extract_tool_call_meta` already filters these via
`.filter(|s| !s.is_empty())`, but the streaming counterpart
`extract_tool_calls` passed the empty string straight through
`function_call.get("id").and_then(|v| v.as_str())` into
`GeminiToolCallMeta::new`, producing a `Some("")` id.
Downstream, `merge_tool_call_snapshots` would then match two parallel
no-id calls against each other on their shared empty-string id,
collapsing them into a single snapshot (silent data loss for the first
call) and emitting an Anthropic `tool_use.id: ""` that breaks tool_result
correlation on the Claude Code client.
Fix:
- `extract_tool_calls`: apply the same `filter(|s| !s.is_empty())` guard
used in the non-streaming path so empty strings become `None` before
reaching the shadow meta.
- `merge_tool_call_snapshots`: defensively collapse any incoming
`Some("")` to `None` up front — keeps the "missing vs present" invariant
local to the merge step for future callers that might build
`GeminiToolCallMeta` by hand.
Tests (2 new, both in streaming_gemini):
- `parallel_empty_string_id_calls_are_treated_as_missing_and_preserved`
covers two parallel calls with explicit `"id": ""` — asserts both
surface, no empty tool_use id leaks, and each gets a unique
`gemini_synth_` id.
- `single_empty_string_id_tool_call_gets_synthesized_id` covers the
non-parallel degraded-relay case.
All 841 lib tests pass; cargo fmt + clippy -D warnings clean.
Addresses Codex follow-up P1 on #1918.
* fix(proxy/gemini): gate generic REST path suffixes behind Google host whitelist
`should_normalize_gemini_full_url` previously treated any full URL whose
path ends with `/v1`, `/v1/models`, `/models`, `/v1/openai`, or `/openai`
as a structured Gemini endpoint and rewrote it to
`/v1beta/models/{model}:generateContent`. These are ubiquitous REST
conventions — opaque relays such as `https://relay.example/custom/v1`
legitimately use them for fixed endpoints — so the rewrite silently
routed traffic to the wrong upstream path.
Split the predicate into two layers:
- **Unconditional**: `matches_structured_gemini_models_path` (i.e. a
`/models/...:generateContent` method call anywhere in the path), the
Google-specific `/v1beta*` family, and the deep OpenAI-compat paths
(`/v1beta/openai/chat/completions`, `/openai/chat/completions`, and
their `responses` siblings). These remain host-agnostic because the
path grammar itself is Gemini-specific.
- **Google-host gated**: `/v1`, `/v1/models`, `/models`, `/v1/openai`,
`/openai`. Only normalized when the host is one of
`generativelanguage.googleapis.com`, `aiplatform.googleapis.com`, or a
real `*-aiplatform.googleapis.com` Vertex regional endpoint. The match
is exact/suffix (not `contains`), so lookalike hosts like
`aiplatform.example.com` are correctly treated as opaque relays.
Tests (8 new in `gemini_url::tests`):
- Four opaque-relay cases: `/custom/v1`, `/custom/models`,
`/custom/v1/models`, `/custom/openai` — all preserved as-is.
- Three Google-host counter-cases: `/v1`, `/models`, and
`us-central1-aiplatform.googleapis.com/v1` still normalize.
- One lookalike safety case: `aiplatform.example.com/v1` is NOT
treated as Google.
All 849 lib tests pass; cargo fmt + clippy -D warnings clean.
Addresses Codex review P2 on #1918.
* fix(proxy/gemini): align shadow id with client-visible id in non-streaming path
When Gemini returns a `functionCall` without an id (common in 2.x
parallel calls), `gemini_to_anthropic_with_shadow_and_hints` previously
generated TWO independent synthesized UUIDs:
1. Line 186-197 — synthesized id `A` used for the Anthropic-visible
`content[tool_use].id` returned to the client.
2. Line 850-881 — `extract_tool_call_meta` independently synthesized
id `B ≠ A`, which populated `shadow_turn.tool_calls[i].id`.
`shadow_content` (line 225-228, cloned from `rectified_parts`) retained
the original missing/empty id. Result: the client sees id `A`, the
shadow store holds id `B`.
On the next turn, `convert_messages_to_contents` builds
`tool_name_by_id` from `build_tool_name_map_from_shadow_turns`, which
uses `tool_calls[i].id` — so the map contains `B → name` but not
`A → name`. When the client sends back `tool_result(tool_use_id=A)`,
resolution fails with:
Unable to resolve Gemini functionResponse.name for tool_use_id `A`
This affects both truncated histories (client sends only the
tool_result) and full histories (shadow-replay branch at line 342-354
skips `convert_message_content_to_parts`, so the assistant tool_use
block never registers id `A` itself).
Fix: make `rectified_parts` the single source of truth. After
`rectify_tool_call_parts`, run a pre-pass that writes
`synthesize_tool_call_id()` back into any `functionCall` that lacks a
non-empty id. All three readers — the content builder (186-197), the
shadow_content clone (225-228), and `extract_tool_call_meta` — then
observe the same id. `shadow_parts()` already strips synthesized ids on
replay (line 616-628), so the internal identifier never leaks to
Gemini upstream.
This mirrors the streaming path, which already has single-source-of-
truth semantics via `tool_call_snapshots` in `streaming_gemini.rs` —
no change needed there.
Tests (5 new in `transform_gemini::tests`):
- `non_stream_shadow_id_matches_client_visible_id`: asserts
`response.content[0].id == shadow.tool_calls[0].id ==
shadow.assistant_content.parts[0].functionCall.id`.
- `non_stream_missing_id_scenario_a_truncated_history_resolves`: turn 2
sends only `[tool_result(id=A)]`; resolution must succeed.
- `non_stream_missing_id_scenario_b_full_history_replay_resolves`: turn 2
sends `[assistant(tool_use=A), tool_result(A)]`; shadow-replay branch
strips the synth id from outgoing `functionCall` while still
resolving the subsequent `tool_result`.
- `non_stream_preserves_original_gemini_id_when_present`: regression —
genuine Gemini ids flow through unchanged.
- `non_stream_synthesized_id_not_leaked_to_gemini_via_shadow_replay`:
defensive — shadow-replay path must strip synth ids from both
`functionCall.id` and `functionResponse.id`.
All 854 lib tests pass; cargo fmt + clippy -D warnings clean.
Addresses Codex follow-up P1 on #1918.
* refactor(proxy/gemini): share build_anthropic_usage between stream and non-stream paths
`streaming_gemini::anthropic_usage_from_gemini` and
`transform_gemini::build_anthropic_usage` were byte-for-byte identical
(32 lines each) — both converting Gemini `usageMetadata` into the
Anthropic `usage` shape including `cache_read_input_tokens` mapping.
Promote the non-streaming version to `pub(crate)` and reuse it from the
streaming SSE converter. Removes ~30 lines of duplication and guarantees
the two paths cannot drift apart.
No behavioral change; all 854 lib tests pass; cargo fmt + clippy -D
warnings clean.
* fix(proxy/gemini): gate /v1beta behind Google host + normalize models/ model id prefix
Two related P2 corrections to the Gemini Native URL surface, both
folding into the existing Google-host-whitelist architecture.
## P2a — `/v1beta` suffix should not unconditionally trigger rewrite
`should_normalize_gemini_full_url` placed `/v1beta` and `/v1beta/models`
in the unconditional layer on the reasoning that `/v1beta` is
Google-specific. In practice an opaque relay fronting a non-Gemini
service at `https://relay.example/custom/v1beta` would still be
silently rewritten to `/v1beta/models/{model}:generateContent`,
breaking the deployment.
Move `/v1beta`, `/v1beta/models`, and `/v1beta/openai` into the
Google-host gated layer alongside `/v1`, `/models`, and friends. The
unconditional layer now only accepts paths whose grammar is
intrinsically Gemini — `/models/...:generateContent` method calls and
the deep OpenAI-compat endpoints like `/openai/chat/completions` and
`/openai/responses`. Pasted AI-Studio URLs such as
`https://generativelanguage.googleapis.com/v1beta` still normalize
because the host matches the whitelist.
## P2b — `model: "models/gemini-2.5-pro"` produced doubled path prefix
Gemini SDKs (and the official `list_models` response) commonly surface
model ids in resource-name form `models/gemini-2.5-pro`. Raw
interpolation into `format!("/v1beta/models/{model}:...")` produced
`/v1beta/models/models/gemini-2.5-pro:streamGenerateContent` which
upstream rejects — yielding false-negative health checks for otherwise
valid provider configs.
Introduce `normalize_gemini_model_id(&str) -> &str` in `gemini_url`
as the single source of truth: strips an optional leading `/` then an
optional `models/` prefix, leaving bare ids untouched. Apply in the
three call sites that build a Gemini method URL:
- `services/stream_check.rs::resolve_claude_stream_url` (unified path)
- `services/stream_check.rs::check_gemini_stream` (Gemini-only path)
- `proxy/forwarder.rs::rewrite_claude_transform_endpoint` (production)
Tests (9 new):
- `gemini_url`: 3 regressions for opaque vs Google-host `/v1beta*`
handling + 5 unit tests pinning `normalize_gemini_model_id` behavior
(strip prefix, leave bare id, preserve nested slashes past the one
stripped prefix, tolerate leading slash, pass through empty input).
- `stream_check`: one end-to-end regression confirming
`models/gemini-2.5-pro` collapses to the expected single-prefix URL.
- `forwarder`: one end-to-end regression on the production rewrite
path.
All 864 lib tests pass; cargo fmt + clippy -D warnings clean.
Addresses Codex P2 feedback on #1918.
* fix(proxy/gemini): trim API key before provider-type detection and OAuth parsing
Leading whitespace on a copied oauth_creds.json (e.g. trailing newline
when the user copies the file content as-is) would slip past the
`starts_with("ya29.") || starts_with('{')` prefix check in
`ClaudeAdapter::provider_type`, causing the provider to be misclassified
as raw-API-key Gemini and fall back to `x-goog-api-key` with the raw
JSON as the key — which upstream rejects with 401.
The frontend's `handleApiKeyChange` already trims on keystrokes but
deep-link imports, the JSON editor, and live-config backfill all bypass
that path. Trim at every backend extraction point so the coverage is
uniform:
- `ClaudeAdapter::extract_key` (5 env / fallback branches) gets
`.map(str::trim)` before `.filter(|s| !s.is_empty())` so that
whitespace-only values are also treated as missing.
- `GeminiAdapter::extract_key_raw` gets the same chain (including
the `.filter` it was missing before).
- `GeminiAdapter::parse_oauth_credentials` gets a defensive
`let key = key.trim();` at the entry as a belt-and-suspenders guard.
Adds two regression tests covering JSON and bare `ya29.` keys with
leading newline/space.
* fix(proxy/gemini): gate generic REST suffix stripping behind Google host in non-full-URL mode
`build_gemini_native_url` unconditionally stripped `/v1`, `/v1beta`,
`/models`, and `/openai` suffixes from the base path regardless of
host. This worked for Google's own endpoints but silently rewrote
third-party relay URLs like `https://relay.example/custom/v1` to
`.../custom/v1beta/models/...`, breaking any relay that mounts its
Gemini-compatible namespace under a versioned prefix.
The result was also asymmetric with the previously-fixed full-URL
branch: toggling the "full URL" switch changed the outbound URL for
the same base_url, which is exactly the kind of invisible behavior
that makes debugging proxy deployments painful.
Align `normalize_gemini_base_path` with
`should_normalize_gemini_full_url`'s layered model:
- Unconditional: `/models/...:method` structured paths and deep
OpenAI-compat endpoints (`/openai/chat/completions`,
`/openai/responses` and their versioned variants) — these are
unambiguous Gemini-specific grammar on any host.
- Google-host gated: generic `/v1`, `/v1beta`, `/models`, `/openai`
suffixes only get stripped on `generativelanguage.googleapis.com`,
`aiplatform.googleapis.com`, or `*-aiplatform.googleapis.com`.
Other hosts preserve the prefix verbatim so relays keep their
intended routing.
Adds seven regression tests for the non-full-URL flow: opaque relay
preservation (v1 / v1beta / models / openai suffix variants), Google
host normalization (counter-case), and boundary cases (structured
method path and deep OpenAI-compat endpoint stripped regardless of
host).
Test count: 864 -> 873.
* Revert "fix(proxy/gemini): gate generic REST suffix stripping behind Google host in non-full-URL mode"
This reverts commit d19ff09cb7.
* test(proxy/gemini): pin non-full-URL versioned relay base stripping
Adds two regression tests that lock in the intentional asymmetry
between full-URL and non-full-URL modes:
- Full-URL mode: opaque base path (e.g. `https://relay.example/custom/v1beta`)
is preserved verbatim. Already covered by
`preserves_opaque_full_url_with_bare_v1beta_suffix`.
- Non-full-URL mode: base path MUST strip `/v1`, `/v1beta`, etc. so the
standard `/v1beta/models/{model}:method` endpoint can be appended
without producing a doubled `/v1beta/v1beta/models/...` path.
The non-full-URL contract is "base URL + cc-switch appends the
canonical Gemini endpoint". A user who needs a relay's custom
namespace (e.g. `/v1/models/...`) must use full-URL mode and paste
the complete method path. This commit adds regression coverage so a
future attempt to mirror full-URL's host-whitelist gating into
`normalize_gemini_base_path` will fail the test suite immediately.
* chore(lint): address clippy 1.95 findings in existing modules
CI upgraded to Rust 1.95 and flagged ten pre-existing warnings that
older toolchains did not enforce. None relate to the Gemini proxy
integration PR itself but they block CI on the feature branch, so
clean them up here as a separate commit for easy review:
collapsible_match:
- proxy/providers/gemini_schema.rs: `"items" if value.is_object()`
match guard instead of nested if.
- proxy/providers/transform_responses.rs: fold
`map_responses_stop_reason`'s `"completed"` / `"incomplete"` arms
into match guards, relying on the existing `_ => "end_turn"` fall-
through for non-matching guard conditions (semantics preserved).
- services/session_usage_codex.rs: fold
`"session_meta" if state.session_id.is_none()` guard, relying on
the existing `_ => {}` fall-through.
unnecessary_sort_by:
- services/provider/endpoints.rs: `sort_by_key(|ep| Reverse(ep.added_at))`.
- services/skill.rs (backup list): same Reverse idiom on `created_at`.
- services/skill.rs (skill listings x2): `sort_by_key(|s| s.name.to_lowercase())`.
useless_conversion:
- services/skill.rs: drop the explicit `.into_iter()` on `zip`'s argument.
while_let_loop:
- services/webdav_auto_sync.rs: `while let Some(wait_for) = ...`
instead of `loop { let Some(...) = ... else { break }; ... }`.
All changes are mechanical and preserve behavior. `cargo test --lib`
remains green (868 passed).
* fix(proxy/gemini): reconcile synthesized tool-call ids with later real ids + preserve thoughtSignature
Three related findings on `streaming_gemini.rs` for Gemini's cumulative
`streamGenerateContent` stream, all centered on `merge_tool_call_snapshots`:
1. (P1) Match upgraded tool-call IDs by position.
When Gemini delivers a `functionCall` without an id on chunk 1
(cc-switch synthesizes `gemini_synth_*`) and then upgrades it to a
real id on chunk 2, the `Some(incoming_id)` branch only matched by
id and missed the existing synthesized snapshot. A second entry
would be pushed, yielding duplicate `tool_use` content blocks at
stream end — one with the synthesized id, one with the real id —
which could trigger duplicate tool execution and break tool_result
correlation. Add a positional fallback: when no id match exists but
the same-position slot holds a synthesized id, merge into it.
`or(preserved_id)` already lets the real id win the merge.
2. (P2) Preserve prior thoughtSignature when merging snapshots.
`tool_call_snapshots[index] = tool_call` overwrote the slot
entirely, dropping any `thoughtSignature` captured on an earlier
chunk if the current cumulative snapshot omitted it. Since
`build_shadow_assistant_parts` writes `thoughtSignature` into the
shadow turn from `tool_call.thought_signature`, a dropped signature
would cause later replay requests to Gemini to be rejected with
invalid-signature errors. Preserve the existing signature when the
incoming chunk does not carry one.
3. (P2) Document the part-order streaming trade-off.
All `tool_use` content blocks are emitted after the final text
`content_block_stop`, so interleaved [text, functionCall, text,
functionCall] parts arrive at the Anthropic client as [text(concat),
tool_use, tool_use] — different from the non-streaming transformer,
which preserves part order. This is intentional given the cumulative
snapshot model and the consumers we target (claude-code-like clients
don't depend on strict interleaving for tool execution correctness).
Add a block comment at the flush site describing the trade-off and
what a strict-order fix would entail, so this isn't rediscovered as
a bug later.
Regression tests:
- upgraded_real_id_merges_into_existing_synthesized_snapshot
- thought_signature_preserved_when_later_chunk_omits_it
Test count: 868 -> 870. clippy 1.95 clean. fmt clean.
* fix(proxy/gemini): prefer exact tool-call id over normalized-name fallback
The shadow-turn matcher used a three-branch `||` chain (id / full name /
normalized name). When two tools share a suffix (e.g. `server_a:search`
and `server_b:search`), the normalized-name clause could short-circuit
on an earlier turn whose id is actually wrong for the incoming tool_use,
mis-routing replay state (functionCall id / thoughtSignature) for later
tool_result resolution.
Split matching into two layers: when the incoming message carries any
tool_use ids, run id-based lookup first and return on the earliest hit.
Only fall back to full-name / normalized-name matching when the incoming
ids are absent or none of them resolve.
Add two regressions:
- shadow_replay_prefers_exact_id_match_over_normalized_name_collision
Two shadow turns with colliding normalized names and two assistant
messages whose ids cross the positional order; asserts each message
replays the id-correct shadow turn (including thoughtSignature).
- shadow_replay_falls_back_to_name_when_ids_absent
Shadow turn with no id and incoming tool_use with an empty id;
asserts the name fallback still populates the replayed part.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* feat(usage): enhance usage stats backend and query hooks
* feat(usage): redesign calendar date range picker with auto-switch and simplified layout
* refactor(usage): streamline dashboard layout and stats components
* refactor(usage): compact request log table with merged cache/multiplier columns and centered layout
* feat(i18n): add cache short labels and usage stats translations for zh/en/ja
* Align usage dashboard stats with range boundaries
The usage dashboard mixed second-precision detail rows with day-level rollups, which caused custom half-day ranges to overcount historical rollup data and left the request log paginator on stale pages after top-level filter changes.
This change limits rollups to fully covered local days, aligns multi-day trend buckets with natural local days, and resets request log pagination when the dashboard range or app filter changes.
Constraint: usage_daily_rollups stores only daily aggregates after pruning old detail rows
Rejected: Include partial boundary rollups proportionally | historical intra-day detail is unavailable after pruning
Rejected: Force RequestLogTable remount on range change | would discard local draft filters unnecessarily
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep summary, trends, provider stats, and model stats on the same rollup-boundary rules
Tested: cargo test --manifest-path src-tauri/Cargo.toml usage_stats
Tested: pnpm exec vitest run tests/components/RequestLogTable.test.tsx
Tested: pnpm typecheck
Not-tested: Manual UI validation in the Tauri app
* Preserve full-day usage filters at minute precision
The latest review surfaced two interaction bugs in the usage dashboard: rollup-backed stats undercounted end days selected via the minute-precision picker, and immediate select changes accidentally applied unsubmitted text drafts from the request log filters.
This change treats 23:59 as a fully selected local end day for rollup inclusion and narrows select-side state syncing so app/status updates do not commit provider/model drafts.
Constraint: The custom range picker emits minute-precision timestamps, while rollups are stored at day granularity
Rejected: Require exact 23:59:59 end timestamps | unreachable from the current picker UI
Rejected: Rebuild applied filters from the full draft state on select changes | silently commits unsaved text input
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep request-log text fields on explicit apply semantics even when select filters remain immediate
Tested: cargo test --manifest-path src-tauri/Cargo.toml usage_stats
Tested: pnpm exec vitest run tests/components/RequestLogTable.test.tsx
Tested: pnpm typecheck
Not-tested: Manual Tauri dashboard interaction
* refactor(usage): move range presets into date picker, single-row layout
- UsageDateRangePicker: add preset shortcuts (今天/1d/7d/14d/30d) inside
popover top; clicking a preset applies immediately and closes popover
- UsageDashboard: collapse to single row (app filters + refresh + picker);
remove standalone preset buttons and summary stats bar
- RequestLogTable: replace static Calendar badge with interactive
UsageDateRangePicker via onRangeChange prop; single filter row
* Keep usage pagination regression coverage aligned with the rendered UI
The new regression test was asserting a non-existent pagination label and page summary text, so it failed before it could verify the real page-reset behavior. This commit switches the assertions to the numbered pagination buttons that the component actually renders and validates the reset through the query hook arguments.
Constraint: RequestLogTable exposes numbered pagination buttons, not a "Next page" label or "2 / 6" summary text
Rejected: Add synthetic pagination labels solely for the test | would couple production markup to a test-only assumption
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Prefer pagination assertions that follow the rendered controls or hook inputs instead of invented summary text
Tested: pnpm vitest run tests/components/RequestLogTable.test.tsx; pnpm typecheck; pnpm test:unit
* refactor(usage): clean up dead code and polish date range picker
- Remove unused exports MAX_CUSTOM_USAGE_RANGE_SECONDS,
timestampToLocalDatetime, and localDatetimeToTimestamp from
usageRange.ts (replaced by the calendar picker)
- Deduplicate getPresetLabel from UsageDashboard and
UsageDateRangePicker into shared getUsageRangePresetLabel helper
- Add aria-label, aria-current and aria-pressed to calendar day
buttons so screen readers can disambiguate same-numbered days
across adjacent months
- Drop unused cacheReadShort and cacheWriteShort i18n keys (zh/en/ja);
the request log table renders R/W prefixes inline
- Align customRangeHint copy with the removed 30-day limit by
dropping "up to 30 days" wording (zh/en/ja)
* fix(usage): align rollup cutoff to local midnight to keep days complete
`rollup_and_prune` previously used `Utc::now() - retain_days * 86400`
as the cutoff. Because rollups are bucketed by *local* date and detail
rows below the cutoff are pruned, an unaligned cutoff left the youngest
rolled-up day half-rolled-up and half-pruned. Combined with the new
`compute_rollup_date_bounds` boundary trimming (which excludes any
rollup day not fully covered by the requested range), custom range
queries that touch that day silently under-count summary, trend,
provider, and model stats.
Fix the invariant at the source: snap the cutoff to the next local
midnight after `(now - retain_days)`. Every rollup row now reflects a
complete local day, so the boundary trimmer's all-or-nothing assumption
holds.
Includes unit tests for the cutoff math (typical case + already-on-
midnight case). DST gap is handled defensively by bumping forward by
an hour.
Addresses Codex P2 review finding on PR #2002.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* chore(stream-check): update default health check models to latest
Replaces deprecated gpt-5.1-codex@low with gpt-5.4@low and switches
the Gemini default from gemini-3-pro-preview to gemini-3-flash-preview
to pick the lightest variant of the latest series for fast, low-cost
health checks.
https://claude.ai/code/session_01NGWLchcTP76rJHjiP5Ehte
* feat(stream-check): detect model-not-found errors with dedicated toast
Health check previously classified failures purely by HTTP status code,
which meant deprecated/invalid models showed up as a generic "Not found
(404)" error pointing users to check the Base URL — misleading when the
URL is fine and only the test model is wrong (e.g. gpt-5.1-codex after
it was retired).
Backend: add detect_error_category() that inspects 4xx response bodies
for model-not-found indicators (model_not_found, does not exist,
invalid model, not_found_error, etc.) and returns a "modelNotFound"
category. Thread the resolved test model through build_stream_check_result
so the failed result carries it in model_used. Add StreamCheckResult
.error_category field (serde-skipped when None).
Frontend: useStreamCheck branches on errorCategory === "modelNotFound"
before the HTTP-status fallback and renders a toast.error with the model
name and a description pointing to Model Test Config. Add i18n keys
(modelNotFound / modelNotFoundHint) for zh/en/ja.
Tests: unit-test detect_error_category against real OpenAI/Anthropic
error shapes, 5xx false-positive avoidance, and plain 401 auth errors.
https://claude.ai/code/session_01NGWLchcTP76rJHjiP5Ehte
* fix(stream-check): add missing error_category field in fallback
The error_category field was added to StreamCheckResult in this branch
but the fallback constructor in stream_check_all_providers was not
updated, which broke cargo build.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(opencode): use json5 parser for trailing comma tolerance
OpenCode CLI writes opencode.json with trailing commas (valid JSONC),
but CC Switch parsed it with serde_json (strict JSON), causing errors
like 'trailing comma at line 35 column 3'.
Switch to json5::from_str which accepts both JSON and JSONC. The json5
crate is already a project dependency. Change error type from
AppError::json() to AppError::Config() since json5::Error differs from
serde_json::Error.
* style(opencode): apply rustfmt to satisfy cargo fmt --check
The previous commit's .map_err(...) chain exceeded rustfmt's default
100-char max_width, breaking CI's `cargo fmt --check`. Let rustfmt
wrap the closure body as a multi-line block. No behavior change.
---------
Co-authored-by: 18067889926 <ming.flute@outlook.com>
Co-authored-by: Jason <farion1231@gmail.com>
write_gemini_live() unconditionally cleared env_map for GoogleOfficial
auth type, discarding user-configured env vars (e.g. GEMINI_MODEL).
Remove the env_map.clear() call so the user's settings_config.env is
written as-is, and merge identical Packycode/Generic match arms.
Distinguish between "provider rejects probe" (yellow warning) and
"genuinely broken" (red error) in health check results.
Backend: add AppError::HttpStatus variant to carry structured HTTP
status codes, populate http_status on error results, classify codes
into short labels (e.g. "Auth rejected (401)"), and truncate overly
long response bodies.
Frontend: route 401/403/400/429/5xx to toast.warning with localized
hints explaining the error may not indicate actual unusability; route
404/402/connection errors to toast.error. Add i18n keys for all three
locales (zh/en/ja).
Also deduplicate check_once by reusing build_stream_check_result.
When a search query matches text beyond the collapse point, the message
automatically expands to show the highlighted match. Also adds
aria-expanded for accessibility.
Messages over 3000 characters are now truncated to 1500 characters by
default, with an expand/collapse toggle. This avoids expensive browser
text layout for large AI responses containing code or tool output.
Replace full DOM rendering with @tanstack/react-virtual to only render
visible messages (~25 DOM nodes instead of N). Wrap SessionMessageItem
in React.memo to prevent unnecessary re-renders on state changes.
When a repo itself is a single skill (SKILL.md at repo root), the
discovery phase sets directory to the repo name, but after ZIP
extraction (which strips the root folder), no matching subdirectory
exists. Add a fallback to check if SKILL.md exists directly in the
extracted temp directory before reporting SKILL_DIR_NOT_FOUND.
Fixes installation of repos like zlbigger/Google-SEOs.skill.
The request count and total cost were displayed both in the app filter
bar and in the UsageSummaryCards below it. Remove the redundant inline
summary and its unused imports.
Replace all remaining "代理服务/Proxy Service/プロキシサービス" references
in the local routing feature context with "路由服务/Routing Service/
ルーティングサービス". This covers service settings, status messages,
tooltips, field descriptions, and tab labels.
Global Proxy, HTTP proxy hints, and AI Agent references are unchanged.
Rename 4.2-takeover.md to 4.2-routing.md in zh/en/ja user manuals,
replacing all "接管/takeover" terminology with "路由/routing" to match
the rebranded feature name. Update README index links accordingly.
Replace all user-facing references to "本地代理接管/Local Proxy/Takeover"
with "本地路由/Local Routing/ローカルルーティング" across zh/en/ja to
eliminate naming confusion with the separate "Global Proxy" feature.
Only i18n string values are changed; keys, code identifiers, and
database schema remain untouched.
The per-provider proxy configuration (meta.proxyConfig) is removed
because its scope is too narrow and covered by global proxy settings
and proxy takeover mode. Users can achieve the same result via the
global proxy panel.
Changes:
- Remove ProviderProxyConfig type (frontend TS + backend Rust)
- Remove ProviderAdvancedConfig proxy UI block, keep testConfig/pricingConfig
- Simplify http_client: delete build_proxy_url_from_config,
build_client_for_provider, get_for_provider
- Simplify forwarder/stream_check/model_fetch to use global client
- Remove i18n keys (en/zh/ja)
- Fix pre-existing test bug in transform.rs (extra None arg)
- Fix request classification: treat messages containing tool_result as
agent continuation instead of user-initiated, preventing false premium
charges on every tool call
- Add subagent detection via __SUBAGENT_MARKER__ and metadata._agent_
fallback, setting x-interaction-type=conversation-subagent
- Add deterministic x-interaction-id derived from session ID to group
requests into a single billing interaction
- Add orphan tool_result sanitization to prevent upstream API errors
that could cause retries and duplicate billing
- Reorder pipeline: classify (on original body) → sanitize → merge →
warmup, ensuring classification sees raw tool_result semantics
- Enable warmup downgrade by default with gpt-5-mini model
- Enhance session ID extraction priority chain for Copilot cache keys
- Detect infinite whitespace bug in streaming tool call arguments
SSRF protection (private IP blocking, suspicious hostname detection) was
originally added for web-server threat models but is unnecessary for a
local desktop app where the user already has full network access. This
removal unblocks legitimate use cases like enterprise intranet APIs,
Docker container addresses, and self-hosted services.
Retained: HTTPS enforcement and same-origin checks which still provide
meaningful security (protecting API keys in transit and preventing
scripts from leaking keys to unrelated domains).
The RequestLogTable had a hardcoded 24-hour rolling window, ignoring the
dashboard's time range selector. Now it accepts a timeRange prop and
dynamically adjusts the query window, so users can view logs beyond just
the last day.
Show first 3 and last 3 page buttons instead of just first/last, with
Set-based deduplication for clean edge merging. Add a page number input
field with Go button for direct page navigation.
OpenClaw gateway injects `[message_id: UUID]` metadata at the end of
every message, wasting display space. Strip this suffix from both title
and summary fields.
Also change session title display from single-line truncate to
line-clamp-2, so longer titles (e.g. OpenClaw's timestamp-prefixed
messages) can show more meaningful content across two lines.
Previously Codex and OpenClaw sessions only showed the working directory
basename as the title, making it hard to distinguish sessions in the same
project. Now both providers extract the first real user message as the
session title, matching the existing Claude Code behavior.
- Codex: first user message → dir basename (skips AGENTS.md injection)
- OpenClaw: displayName (sessions.json) → first user message → dir basename
- Move TITLE_MAX_CHARS constant to shared utils.rs
- Use Option<&HashMap> for OpenClaw parse_session to avoid leaky abstraction
Extract message_id from Claude API responses (msg_xxx) and use it to
generate a shared request_id format (session:{msg_xxx}) between the
proxy logger and session log sync. When session sync encounters the
same request_id via INSERT OR IGNORE, it skips the duplicate.
- Add message_id field to TokenUsage, extracted from Claude responses
- Add TokenUsage::dedup_request_id() to generate shared request IDs
- Define SESSION_REQUEST_ID_PREFIX constant to eliminate magic strings
- Change proxy logger to INSERT OR REPLACE for richer-data-wins semantics
Add pricing data for 4 new providers (Qwen, xAI Grok, Mistral, Cohere)
and supplement existing providers (MiniMax M2.5/M2.7, GLM-5/5.1,
Doubao Seed 2.0, MiMo V2 Pro, OpenAI o1/o3/codex-mini/gpt-5-mini/nano).
Fix outdated prices for deepseek-chat, deepseek-reasoner, and kimi-k2.5.
Fix display_name casing "Mimo" → "MiMo" for consistency.
Use prepared statement in seed_model_pricing() to avoid recompiling SQL
on each of ~130 INSERT iterations.
Schema migration v8→v9: DELETE + re-seed model_pricing for existing users.
Prevent users from switching to official providers (Anthropic/OpenAI/Google)
when proxy takeover is active, as using a proxy with official APIs may cause
account bans.
Defense-in-depth across 4 layers:
- Backend: ProviderService::switch(), hot_switch_provider(), switch_proxy_provider command
- Frontend: useProviderActions soft guard with error toast
- UI: ProviderActions button disabled with ShieldAlert icon
- Tray menu: official provider items disabled with ⛔ indicator
Also warns when enabling proxy takeover while current provider is official.
* Preserve cache hints when collapsing system prompts
Strict OpenAI-compatible chat backends still need fragmented Claude\nsystem prompts collapsed into one leading system message, but that\nnormalization should not silently drop stable cache hints. Preserve\nmessage-level cache_control when the merged system fragments agree,\nand fall back to omitting it when the fragments conflict.\n\nConstraint: Must keep single-system normalization for Nvidia/Qwen-style chat backends\nRejected: Always copy the first cache_control | could misrepresent conflicting cache boundaries\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: If system prompt merging changes again, preserve cache_control whenever the merged metadata is unambiguous\nTested: cargo test proxy::providers::transform --manifest-path src-tauri/Cargo.toml\nNot-tested: End-to-end prompt caching behavior against cache-aware OpenAI-compatible upstreams\nRelated: #1881
* Tighten cache hint inheritance for merged system prompts
The follow-up cache hint fix still treated mixed present/absent\ncache_control across fragmented system prompts as inheritable, which\nexpanded the cache scope after prompt collapse. Treat that mix as\nambiguous and only preserve cache_control when every merged fragment\nexplicitly agrees on the same value.\n\nConstraint: Must preserve strict-backend system prompt normalization from #1942\nRejected: Inherit first present cache_control | widens cache scope when later fragments were intentionally uncached\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Any future merged-system cache hint logic should treat missing cache_control as semantically significant\nTested: cargo test proxy::providers::transform --manifest-path src-tauri/Cargo.toml\nNot-tested: End-to-end upstream caching behavior against cache-aware relays\nRelated: #1881\nRelated: #1946
* Keep cache-control merge regressions easy to review
Reflow the two long cache-control regression assertions in transform.rs so the neighboring merge cases stay rustfmt-aligned and easier to scan.
This keeps the preserved code change separate from the untracked Markdown design notes the user did not want committed.
Constraint: Exclude Markdown design files from the commit while preserving the local code change
Rejected: Include docs in the same commit | user explicitly asked to leave Markdown files out
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Treat this as a readability-only test change; do not infer runtime behavior changes from it
Tested: cargo test --manifest-path src-tauri/Cargo.toml test_anthropic_to_openai_drops_ --lib
Tested: cargo check --manifest-path src-tauri/Cargo.toml --tests
Tested: pnpm format:check
Tested: pnpm typecheck
Not-tested: Full application integration and manual flows
Responses conversions still use promptCacheKey, but chat completions now stay a pure shape transform. This keeps Claude -> chat requests aligned with providers that do not understand the field and keeps stream checks consistent with production behavior.
Constraint: Issue #1919 requires removing prompt_cache_key from Claude -> OpenAI Chat requests
Rejected: Add a runtime toggle for chat injection | requested behavior is unconditional removal
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep promptCacheKey limited to Claude -> Responses conversions unless a provider-specific contract is proven
Tested: cargo test anthropic_to_openai
Tested: cargo test anthropic_to_responses_with_cache_key
Tested: cargo test transform_claude_request_for_api_format_responses
Not-tested: Full src-tauri test suite
Related: #1919
* feat(window): add app-level window controls with settings toggle
Add a persistent settings toggle to enable app-level minimize/maximize/close controls and hide system decorations when enabled, providing a Wayland-friendly fallback for broken native titlebar interactions.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(window): restrict app-level window controls to Linux only and fix startup flicker
- Guard useAppWindowControls with isLinux() in App.tsx so it's always
false on macOS/Windows even if persisted as true
- Wrap set_decorations call in lib.rs with #[cfg(target_os = "linux")]
- Only show the toggle in WindowSettings on Linux
- Skip setDecorations effect while settingsData is still loading to
prevent the Rust-side decoration state from being overridden by the
undefined->false fallback, which caused a brief title bar flicker
---------
Co-authored-by: wzk <wx13571681304@outlook.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Jason <farion1231@gmail.com>
- Simplify "ChatGPT Plus / Pro" → "ChatGPT" across all three languages
- Clarify Codex OAuth description to highlight Claude Code usage
- Add "requires manual activation" note for Token Plan and third-party balances
- Add Copilot API consumption caveat to the interaction optimizer section
- Update overview with skills.sh search, usage tracking, and onboarding mentions
- Add PR credits for TheRouter (@cmzz), Kaku/OMO Slim/Thinking fallback/auth tab (@yovinchen)
Update base URL to cc-api.pipellm.ai, add complete model definitions
(Opus/Sonnet/Haiku), add Codex preset with custom TOML config, add
pipellm PNG icon, and set apiKeyUrl to referral link.
Multi-protocol provider: Anthropic for Claude, OpenAI Responses for
Codex/OpenClaw, OpenAI for OpenCode, custom Gemini config. Includes
PNG icon via URL import and provider-specific settings like effortLevel,
enabledPlugins, and custom TOML config for Codex.
Add Shengsuanyun (胜算云) as an aggregator partner across all five apps,
positioned right after official providers. Uses anthropic-messages
protocol for OpenCode/OpenClaw. Includes URL-based icon import (217KB
SVG), partner promotion i18n for zh/en/ja, and localized display name.
The icon index (index.ts) is hand-curated with optimized SVG content
and custom name mappings. Running the generation script destroys these
optimizations. Remove the script entirely to eliminate the risk.
Add LionCCAPI as a third-party partner provider across all five apps
(Claude, Codex, Gemini, OpenCode, OpenClaw) with anthropic-messages
protocol for OpenCode and OpenClaw. Include partner promotion i18n
entries for zh/en/ja locales and lioncc icon.
When thinking.type is "adaptive" (Claude's maximum thinking mode) and
output_config.effort is absent, resolve_reasoning_effort() incorrectly
mapped it to "high" instead of "xhigh" in OpenAI format conversions.
Replace the old low-res x-code inline SVG (7.6KB) with a new high-res
xcode.svg (286KB) loaded via Vite URL import. Update all three provider
preset files to reference the new icon name.
Add dual rendering mode to the icon system: small optimized SVGs
continue to be inlined via dangerouslySetInnerHTML, while large SVGs
and raster images (png/jpg/webp/etc) use Vite URL imports rendered
as <img> tags. Added dds.svg (1.4MB) as the first URL-based icon.
Updated generate-icon-index.js to support multi-format icons with
a manual URL_ICONS control list. Updated ProviderIcon to handle
both inline SVG and URL-based rendering paths.
cc-switch could already persist arbitrary OMO Slim agent keys and top-level fields, but the built-in metadata and UI copy still reflected the pre-council agent set. This made the upstream council feature look unsupported and pushed users toward manual JSON-only setup.
Promote council to a built-in OMO Slim agent, add copy that points top-level plugin settings at Other Fields, and lock the behavior with regression tests.
Constraint: oh-my-opencode-slim exposes council through both agents.council and top-level council config
Rejected: Add a dedicated council editor UI now | too much surface area for issue #1981
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep OMO Slim built-in agent metadata aligned with upstream agent additions before shipping UI support
Tested: pnpm exec vitest run tests/utils/omoConfig.test.ts tests/components/OmoFormFields.mergeCustomModelsIntoStore.test.ts
Tested: pnpm typecheck
Not-tested: End-to-end validation against a live oh-my-opencode-slim installation
Related: farion1231/cc-switch#1981
Kaku is a WezTerm-derived macOS terminal, so reusing the existing WezTerm-compatible launch path keeps the change small while making it selectable in settings and session resume flows.
Constraint: Kaku support should stay macOS-only and avoid introducing a separate launcher model
Rejected: Treat Kaku as a silent WezTerm fallback | users could not explicitly choose it in settings
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Kaku on the shared WezTerm-compatible launch path unless upstream drops the start-compatible CLI
Tested: pnpm typecheck; pnpm format:check; cargo check --manifest-path src-tauri/Cargo.toml; cargo fmt --manifest-path src-tauri/Cargo.toml --check; cargo test --manifest-path src-tauri/Cargo.toml --lib session_manager::terminal::tests
Not-tested: End-to-end launch against a locally installed Kaku.app
Related: #1954
The Claude provider form reopened with an empty Thinking model after users saved only a main model. This updates model-state hydration to mirror the existing Haiku-style fallback semantics: read ANTHROPIC_REASONING_MODEL when present, otherwise display ANTHROPIC_MODEL, without writing a synthetic reasoning field back into config.
Constraint: Existing Haiku, Sonnet, and Opus selectors already rely on read-time fallback behavior
Rejected: Persist ANTHROPIC_REASONING_MODEL from ANTHROPIC_MODEL automatically | would diverge from Haiku behavior and silently rewrite saved config
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Thinking fallback read-only unless all model-mapping fields are intentionally migrated to write-through semantics
Tested: pnpm typecheck
Tested: pnpm test:unit (1 unrelated pre-existing failure in tests/components/UnifiedSkillsPanel.test.tsx mock setup)
Not-tested: Manual add-provider reopen flow in the desktop UI
The settings page already routes the auth tab label through the shared i18n key, but the locale bundles never defined that key. Adding the missing entries fixes the label with the same simple pattern used by the other tabs.
Constraint: Keep the fix aligned with the existing settings-tab i18n flow
Rejected: Add component-level bilingual rendering | unnecessary for a missing translation key
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: When adding settings tabs, define locale keys in every bundled language before relying on fallback text
Tested: pnpm format:check; pnpm typecheck
Not-tested: Manual verification in the desktop UI
- Make migrate_v6_to_v7 check skills table existence before ALTER
- Make migrate_v7_to_v8 check model_pricing table existence before UPDATE
- Fix SessionManagerPage test: use getByRole heading instead of getAllByText
which breaks when highlightText splits text across <mark> elements
- Add content_hash and updated_at fields to 4 InstalledSkill literals in skill_sync.rs
- Add useCheckSkillUpdates and useUpdateSkill to UnifiedSkillsPanel test mock
- Suppress unused import warning in auto_launch.rs test module
- Pre-filter sessions by provider before indexing to prevent result
truncation when FlexSearch limit cuts across providers
- Switch tokenizer from "forward" to "full" for Chinese substring matching
- Preserve FlexSearch relevance ranking when search query is present
Instead of showing directory basenames for all Claude sessions, extract
titles from JSONL content with a priority chain:
1. custom-title metadata (set via /rename in Claude Code)
2. First real user message (skipping /clear, /compact caveats)
3. Directory basename (fallback)
Display a one-time informational dialog explaining the Common Config
Snippet feature when users first open the add/edit provider form.
Uses a derived isOpen state from settings to avoid race conditions.
Adds commonConfigConfirmed flag to both TS and Rust settings types.
Add an informational alert block at the top of the common config snippet
editor modal (Claude/Codex/Gemini) explaining what the feature is, why
it exists, and how to use it. Also add an empty state prompt when no
snippet has been extracted yet, guiding users to click "Extract from
Editor". Includes i18n support for zh/en/ja.
CLI-credential-based subscriptions (Claude/Codex/Gemini) read from a
single global credential file, so the quota always reflects the last
CLI login rather than a specific provider. Showing it on non-current
cards is misleading when multiple official subscriptions exist.
Apply the same isCurrent + autoQuery pattern already used by Copilot
and Codex OAuth: only query and render the quota footer when the
provider is the currently active one.
When switching to Copilot/ChatGPT/OpenAI-format providers with the proxy
not running, two toasts appeared: a "proxy required" warning followed by
a "switch success" toast. Unify the post-switch toast logic so that all
provider types show a single success toast, and skip it entirely when
a proxy-required warning was already shown.
Introduce a one-time welcome dialog that explains CC Switch's workflow
to new users: how their existing config is preserved as a "default"
provider and how the bundled "Official" preset enables one-click revert.
Upgrade users are excluded by checking is_providers_empty() at startup
and never see the dialog.
Persistence follows the existing *_confirmed convention in AppSettings
(proxy/usage/stream_check/failover), stored in settings.json. The field
is only written when the user explicitly clicks the confirm button,
keeping its semantics strictly about user acknowledgement.
Also adds two reusable DAO helpers:
- Database::is_providers_empty for fresh-install detection, using
EXISTS(SELECT 1) for a short-circuit query.
- Database::get_bool_flag accepting "true" | "1", with
init_default_official_providers migrated to use it.
Dialog copy in zh/en/ja uses conditional phrasing so it stays
accurate whether or not existing live config was found.
Refresh the user manual to cover the v3.13.0 feature set so users can
discover and correctly use new functionality without cross-referencing
the release notes. All three language versions are updated
line-by-line symmetric.
Highlights:
- Lightweight Mode: tray-only running state added in 1.5-settings,
with a comparison table against "Minimize to tray" and a new OAuth
Auth Center (Beta) section
- Quota & Balance display restructured in 2.5-usage-query: split into
auto-query (Claude/Codex/Gemini official, Copilot, Codex OAuth) vs
manual-enable (Token Plan, third-party balances). Explains why
manual enabling is required: the same API URL may expose both
plan-quota and balance query modes
- Codex OAuth reverse proxy: full usage guide in 2.1-add with two
entry points (Add Provider panel / OAuth Auth Center), Device Code
login flow, token auto-refresh, multi-account management, quota
display, common failures, and risk notice
- Full URL Endpoint Mode: new advanced option in 2.1-add
- Per-app tray submenus: 2.2-switch refactored to reflect the 5-app
submenu structure and cross-link to Lightweight Mode
- Skills workflow: remove obsolete "automatic update not supported"
section in 3.3-skills, add SHA-256 update detection, single/batch
update, storage location switch, and skills.sh registry search
- Directory picker for Claude terminal resume in 3.4-sessions
- Usage stats in 4.4-usage: document the new CLI session log source
(no proxy required) and per-app filtering for Claude/Codex/Gemini;
note CNY->USD pricing corrections and MiniMax quota fixes
- Stream Check coverage extended to OpenCode/OpenClaw in 4.5-model-test
- New FAQs in 5.2-questions: quota visibility (auto vs manual),
Codex OAuth risks and login flow, deep link wake in Lightweight Mode
- v3.13.0 highlights navigation block added to top-level README and
each per-language README; version bumped to v3.13.0 / 2026-04-08
Drops the friction of clicking the manual "Import current config" button
for OpenCode and OpenClaw — they now match the auto-import behavior the
previous commit added for Claude/Codex/Gemini.
- New "1.6." startup block in lib.rs runs both
import_opencode_providers_from_live and import_openclaw_providers_from_live
on every launch. The functions are id-keyed and idempotent, so re-running
just picks up new providers added externally to the live JSON files.
- Both functions now use a new Database::get_provider_ids() helper
(HashSet<String> from a single SELECT id-only query) instead of
get_all_providers(), avoiding the N+1 endpoint sub-queries that would
otherwise hit the startup hot path on every launch.
New and existing users now see a built-in "Claude Official" / "OpenAI
Official" / "Google Official" entry in their provider list, so switching
back to the official endpoint is one click away instead of buried in the
README.
- New providers_seed.rs holds the three seeds (id, name, settings_config,
icon) keyed by AppType, with a single is_official_seed_id() helper that
scans OFFICIAL_SEEDS so the id list has one source of truth.
- Database::init_default_official_providers() runs once per database
(gated by an official_providers_seeded setting flag), appends each seed
to the end of the sort order, and never touches is_current.
- Startup also auto-imports the live config (settings.json / auth.json /
.env) as a "default" provider before seeding, so users with an existing
manual config don't lose it when they click the official preset.
- Database::has_non_official_seed_provider() replaces the get_all_providers
call in import_default_config's gating check with an id-only scan,
dropping the N+1 endpoint sub-queries from every startup.
CopilotQuotaFooter and CodexOauthQuotaFooter called their hooks with a
hardcoded `enabled: true` plus an unconditional 5-minute refetch and
refetchOnWindowFocus, so non-current reverse-proxy cards kept polling
in violation of the project's "only the active provider auto-queries
on cooldown" rule. With multiple Copilot or ChatGPT accounts bound to
different cards, every card kept hitting its own usage endpoint.
Adopt the same pattern as useUsageQuery: keep `enabled` independent of
isCurrent so first-fetch and manual refresh still work, but gate
refetchInterval / refetchIntervalInBackground / refetchOnWindowFocus on
a new `autoQuery` option, and thread `isCurrent` from ProviderCard
through the footers into the hooks.
Draft trilingual release notes for the upcoming v3.13.0 feature release
covering lightweight mode, quota and balance visibility, provider model
auto-fetch, Codex OAuth reverse proxy, tray per-app submenus, the
Hyper-based proxy forwarding stack, Skills discovery and batch updates,
session workflow upgrades, OpenCode/OpenClaw Stream Check coverage, the
full URL endpoint mode, and the Copilot interaction optimizer, plus the
accompanying Copilot auth, UTF-8 streaming boundary, system prompt
normalization, WebDAV password, and Linux startup fixes. Contributor
attribution follows industry convention using ASCII punctuation inside
PR references across all three locales, and the Codex OAuth reverse
proxy carries its own risk notice alongside a backward link to the
v3.12.3 Copilot risk notice.
Document two additional Fixed entries for the Unreleased section after
rebasing onto the latest origin/main: the SSE streaming UTF-8 chunk
boundary fix that prevents U+FFFD replacement characters in multi-byte
output via the Copilot reverse proxy, and the OpenAI-compatible chat
transform fix that normalizes fragmented Claude system prompts into a
single leading system message for strict backends like Nvidia and
Qwen-style providers.
Post-merge cleanup from a simplify review pass on the phase 1-4
OpenCode/OpenClaw changes.
- Rename check_once_opencode_like → check_once_without_adapter. The
new name directly expresses the intent (bypass get_adapter) instead
of suggesting the function is somehow "like" OpenCode.
- Drop two "Phase 4 会美化错误消息" phase-history markers from
docstrings; git history is the right place for them.
- Document in resolve_opencode_base_url why its default endpoints
cannot be merged with ProviderType::default_endpoint(): the former
encode AI SDK package defaults (e.g. @ai-sdk/openai ships with the
/v1 suffix) while the latter encode proxy upstream hosts. They
happen to overlap but are two independent truth sources.
Phase 4: polish the four remaining edge cases uncovered by Phase 1-3.
Custom headers passthrough
- check_claude_stream and check_gemini_stream now accept an optional
extra_headers map which is appended after all built-in headers so it
can override defaults (e.g. a custom User-Agent).
- OpenClaw reads from settings_config.headers.
- OpenCode reads from settings_config.options.headers.
- All pre-existing Claude/Codex/Gemini call sites pass None.
OpenClaw custom auth header (Longcat-style)
- When settings_config.authHeader is true, the provider expects a
custom auth header whose name is only known to the OpenClaw gateway
itself. Return a dedicated openclaw_auth_header_not_supported error
so the user sees a meaningful explanation instead of a 401.
Bedrock error polish
- The bedrock-converse-stream (OpenClaw) and @ai-sdk/amazon-bedrock
(OpenCode) branches now explain why (SigV4 signing) and point to
the official consoles as an alternative test path.
OpenCode baseURL fallback
- resolve_opencode_base_url: when options.baseURL is empty and the
npm package has a canonical default endpoint (@ai-sdk/openai,
@ai-sdk/anthropic, @ai-sdk/google), fall back to that endpoint.
@ai-sdk/openai-compatible still requires an explicit baseURL
because its whole purpose is to point at a custom OpenAI clone.
Tests
- 8 new unit tests covering authHeader detection, baseURL resolution
(explicit / fallback / error), and header map extraction on both
apps. Total stream_check tests: 18 → 26.
Phase 3: implement stream check for OpenCode providers by mapping the
`settings_config.npm` (AI SDK package name) to the corresponding API
protocol and delegating to the existing stream checkers.
Package mapping:
- @ai-sdk/openai-compatible → openai_chat
- @ai-sdk/openai → openai_responses
- @ai-sdk/anthropic → anthropic (ClaudeAuth strategy)
- @ai-sdk/google → gemini (Google strategy)
- @ai-sdk/amazon-bedrock → not supported (phase 4 message polish)
Note: OpenCode nests baseURL/apiKey under `settings_config.options`
(different from OpenClaw's root-level fields) and uses `baseURL` with
a capital L. Three new extractors (base_url / api_key / npm) encode
these shape differences so check_opencode_stream stays symmetric with
check_openclaw_stream.
Frontend: drop the remaining `appId !== "opencode"` filter in
ProviderList.tsx — both apps can now test providers.
Phase 2: extend check_openclaw_stream to cover the full non-Bedrock
protocol set declared by openclawApiProtocols.
- openai-responses → check_claude_stream(api_format="openai_responses")
- anthropic-messages → check_claude_stream(api_format="anthropic"),
using AuthStrategy::ClaudeAuth (Bearer-only) so Claude relay
services that reject a simultaneous x-api-key still work. Official
Anthropic also accepts pure Bearer on /v1/messages.
- google-generative-ai → check_gemini_stream with AuthStrategy::Google.
bedrock-converse-stream still errors out but with a dedicated
openclaw_bedrock_not_supported key; its user-facing message will be
polished in phase 4.
Each protocol now builds its own AuthInfo inside the match arm because
the auth strategy is protocol-specific.
Phase 1 of extending stream health check to OpenCode/OpenClaw apps.
- Add early-dispatch path for OpenCode/OpenClaw in check_once so they
bypass the adapter layer (which only knows Claude/Codex/Gemini
settings_config shapes).
- Introduce check_openclaw_stream dispatcher that reads the `api` field
from settings_config and routes to the existing check_claude_stream
with api_format="openai_chat" for "openai-completions". Other
protocols return localized errors to be lit up in phases 2 and 4.
- Extract build_stream_check_result helper to avoid duplicating the
StreamCheckResult construction logic between the two code paths.
- Unblock the test button for OpenClaw providers in ProviderList.tsx.
OpenCode still returns the "not yet supported" error; it will be
enabled in phase 3.
These OAuth providers ship with non-empty ANTHROPIC_BASE_URL, so the
isOfficialProvider() heuristic (which checks for a missing base URL)
returned false and left the health-check and usage-config buttons
enabled — inconsistent with other official OAuth cards. Extend the
button disabling logic at the call site with isCopilot / isCodexOauth,
matching the pattern already used for the quota footer branch above.
Update the "Codex (ChatGPT Plus/Pro)" entry in Claude Code presets to
the new GPT-5.4 naming, which drops the legacy `-codex` suffix. Map the
Haiku tier to `gpt-5.4-mini` for lower-cost lightweight calls while
keeping Sonnet/Opus on the standard `gpt-5.4`.
Linux users reported the window UI (including native title bar buttons)
couldn't receive clicks until manually maximizing and restoring the
window. Root causes: (1) Tauri webview did not acquire focus on startup
so first clicks were consumed by X11/Wayland click-to-activate
(Tauri #10746, wry #637); (2) GTK surface input region failed to
renegotiate on the visible:false + show() path under some
WebKitGTK/compositor combinations.
- Add linux_fix::nudge_main_window helper that performs set_focus plus
a ±1px no-op resize after window show, with a 500ms reconciliation
readback to compensate for dropped resize requests on slow
compositors.
- Wire the helper into every window re-show path: normal startup,
deeplink, single_instance, tray show_main, and lightweight exit.
- Set WEBKIT_DISABLE_COMPOSITING_MODE=1 at startup to avoid resize
crashes and Wayland surface negotiation issues.
- Remove data-tauri-drag-region on Linux from App.tsx header and the
shared FullScreenPanel (used by all provider/MCP/workspace forms)
to avoid Tauri #13440 in Wayland sessions. Extract drag-region
constants to src/lib/platform.ts for reuse.
All Rust changes are gated by #[cfg(target_os = "linux")]; frontend
changes preserve macOS/Windows behavior via runtime isLinux() checks.
Known limitation: tiling Wayland compositors ignore set_size, so
GDK_BACKEND=x11 remains the user-side workaround.
Use "Skills" consistently in skillStorage title/description and
skillSync title to match the upstream Agent Skills wording and the
existing English label style used elsewhere on the settings page.
- Trim Auth Center section descriptions to focus on user intent
- Remove duplicate outer heading on the auth settings tab
- Swap Sparkles glyph for CodexIcon on the ChatGPT card
- Generalize codexOauth.authStatus to a neutral "Auth status"
- Register settings.authCenter.* keys across zh/en/ja locales
Codex OAuth (ChatGPT Plus/Pro) providers previously fell through to the
default UsageFooter branch and showed no quota at all, while Copilot and
official Codex providers already had a wham/usage-backed quota footer.
This wires up the same five-hour / seven-day tier badges for codex_oauth
provider cards by reusing the existing query_codex_quota function and
SubscriptionQuotaFooter rendering, parameterized to keep both the CLI
credential path ("codex") and the cc-switch managed OAuth path
("codex_oauth") working from a single source of truth.
- Parameterize services::subscription::query_codex_quota with tool_label
and expired_message; promote SubscriptionQuota constructors to
pub(crate). The CLI path keeps its existing "codex" label and the
"re-login with Codex CLI" message; the new path passes "codex_oauth"
and a cc-switch-specific re-login hint.
- Add a new get_codex_oauth_quota Tauri command in commands/codex_oauth.rs
that resolves the ChatGPT account (explicit binding > default account
> not_found), pulls a valid access_token from CodexOAuthManager
(auto-refresh handled), and delegates to query_codex_quota.
- Extract SubscriptionQuotaFooter's render body into a pure
SubscriptionQuotaView component (props: quota / loading / refetch /
appIdForExpiredHint / inline). The existing SubscriptionQuotaFooter
becomes a thin wrapper with identical props and behavior, so
CopilotQuotaFooter and the official Claude/Codex/Gemini paths are
untouched. This avoids duplicating ~280 lines of five-state rendering.
- Add CodexOauthQuotaFooter, a 38-line wrapper that calls the new
useCodexOauthQuota hook and forwards to SubscriptionQuotaView.
- ProviderCard inserts an isCodexOauth branch between isCopilot and
isOfficial, keyed off PROVIDER_TYPES.CODEX_OAUTH (newly added to
config/constants.ts to centralize the previously scattered string).
- Frontend hook caches per (codex_oauth, accountId) so multiple cards
bound to the same ChatGPT account share one fetch via react-query
dedup; cards bound to different accounts get independent fetches.
- No new i18n keys: existing subscription.fiveHour / sevenDay / expired /
refresh / queryFailed / expiredHint are reused.
Adds a new managed OAuth provider that lets Claude Code route requests
through a user's ChatGPT Plus/Pro subscription via the chatgpt.com
backend-api/codex endpoint.
- CodexOAuthManager: OpenAI Device Code flow with multi-account support,
JWT-based account identification, and automatic access_token refresh.
- Reuses the generic managed-auth command surface (auth_start_login,
auth_poll_for_account, etc.) via provider dispatch in commands/auth.rs.
- ClaudeAdapter detects codex_oauth providers, forces the base URL to
the ChatGPT backend, pins api_format to openai_responses, and emits
Authorization + originator headers; the forwarder injects the dynamic
access_token and ChatGPT-Account-Id per request.
- transform_responses gains an is_codex_oauth path that aligns the body
with OpenAI's codex-rs ResponsesApiRequest contract: sets store:false,
appends reasoning.encrypted_content to include, strips max_output_tokens
/ temperature / top_p, injects default instructions/tools/parallel_tool_calls,
and forces stream:true. Covered by 9 new unit tests plus regression
guards for the non-Codex path.
- Stream check reuses the same transform flag so detection matches the
production request shape.
- Frontend adds CodexOAuthSection + useCodexOauth hook, integrates it
into ClaudeFormFields / ProviderForm / AuthCenterPanel, ships a new
"Codex (ChatGPT Plus/Pro)" preset, and adds zh/en/ja i18n strings.
Copilot usage query API was implemented but never surfaced on the main
provider list. Add CopilotQuotaFooter component that auto-detects
github_copilot providers and displays premium interaction utilization
inline, reusing the existing TierBadge UI from SubscriptionQuotaFooter.
Session logs use placeholder provider_ids (_session, _codex_session,
_gemini_session) that don't exist in the providers table, causing LEFT
JOIN to return NULL and display "Unknown". Add COALESCE fallback in all
4 usage queries to show meaningful names like "Claude (Session)".
Add dashboard-level app type filter to usage statistics, replacing the
DataSourceBar with a more useful segmented control. All components
(summary cards, trend chart, provider stats, model stats, request logs)
now respond to the selected app filter.
Backend: add optional app_type parameter to get_usage_summary,
get_daily_trends, get_provider_stats, and get_model_stats queries.
Frontend: new AppTypeFilter type, updated query keys with appType
dimension for proper cache separation, and RequestLogTable local
filter auto-locks when dashboard filter is active.
- Use UPSERT with WHERE guard instead of INSERT OR IGNORE, so updated
token values on existing messages are properly synced without
unnecessary rewrites of unchanged rows
- Include cached tokens in the skip-zero filter to stop silently
discarding pure cache-hit records
- Restrict file collection to session-*.json to match documented scope
and prevent ingesting non-session JSON files
Parse ~/.gemini/tmp/*/chats/session-*.json for precise per-message
token data (input/output/cached/thoughts). Integrates with existing
background sync and manual sync button alongside Claude and Codex.
Normalize model names from JSONL session logs before storage and pricing
lookup: lowercase, strip provider prefix (openai/), strip date suffixes
(-YYYY-MM-DD, -YYYYMMDD). Also clamp cached tokens to not exceed input.
- Fix 13 Chinese model prices that were stored as CNY values in USD
fields (DeepSeek, Kimi, MiniMax, GLM, Doubao, Mimo)
- Add 12 new models: GPT-5.4/mini/nano, o3, o4-mini, GPT-4.1/mini/nano,
Gemini 3.1 Pro/Flash Lite, Gemini 2.5 Flash Lite, Gemini 2.0 Flash,
DeepSeek Chat/Reasoner, Kimi K2.5
- Merge pricing migration into existing v7→v8 to avoid extra version bump
Parse Claude Code JSONL session files (~/.claude/projects/) and Codex
SQLite database (~/.codex/state_5.sqlite) to track API usage without
requiring proxy interception. This enables usage statistics for users
who don't use the proxy feature.
Key changes:
- Add session_usage.rs: incremental JSONL parser with message.id dedup
- Add session_usage_codex.rs: import thread-level token data from Codex
- Add data_source column to proxy_request_logs (proxy/session_log/codex_db)
- Add session_log_sync table for tracking parse offsets
- Background sync every 60s + manual sync via DataSourceBar UI
- Schema migration v7→v8
- i18n support for zh/en/ja
- Hide "暂无描述" text when skill has no description (skills.sh API
doesn't return descriptions), show empty spacer instead
- Change skills.sh result link from guessed subdirectory path to repo
root URL, since skillId doesn't reflect the actual nested path
Add skills.sh API integration allowing users to search and install from
a catalog of 91K+ agent skills directly within CC Switch. The search
results are converted to DiscoverableSkill objects and reuse the existing
install pipeline. Includes fallback directory search for repos where
skills are nested in subdirectories, and filters out non-GitHub sources.
Allow users to choose between storing skills in CC Switch's managed
directory (~/.cc-switch/skills/) or the Agent Skills open standard
directory (~/.agents/skills/). Includes migration logic that safely
moves files before updating settings, with confirmation dialog for
non-empty installations.
- Add content_hash and updated_at fields to skills table (DB migration v6→v7)
- Compute directory content hash on install/import/restore for version tracking
- Add check_updates command: downloads repos, compares hashes, returns update list
- Add update_skill command: backs up old files, re-downloads and replaces SSOT
- Backfill content_hash for existing skills on first update check
- Add "Check Updates" button and per-skill update badge/button in UnifiedSkillsPanel
- Add i18n keys for zh/en/ja
- Change default autoQueryInterval from 0 (disabled) to 5 minutes for
new usage scripts (Token Plan, Balance, and general templates)
- Fix controlled number inputs (timeout & interval) that couldn't be
cleared: defer validation to onBlur so users can delete and retype
Test and ConfigureUsage buttons are now always rendered instead of
conditionally, with a disabled style for providers that don't support
them (e.g. official subscriptions). This ensures consistent button
container width so the usage display aligns uniformly across all cards.
Add a new "Official" (官方) template type in the usage query panel that
queries account balance via each provider's native API endpoint.
Follows the same zero-script pattern as Token Plan — Rust handles the
HTTP call, frontend auto-detects the provider from base URL.
Supported providers and endpoints:
- DeepSeek: GET /user/balance
- StepFun: GET /v1/accounts
- SiliconFlow: GET /v1/user/info (cn + com)
- OpenRouter: GET /api/v1/credits
- Novita AI: GET /v3/user/balance
* fix: handle UTF-8 multi-byte characters split across stream chunk boundaries
Replace String::from_utf8_lossy with append_utf8_safe in all four SSE
streaming paths. When a multi-byte UTF-8 character (e.g. Chinese, emoji)
is split across TCP chunk boundaries, from_utf8_lossy silently replaces
the incomplete halves with U+FFFD (�). This caused intermittent garbled
output in Claude Code when using the Copilot reverse proxy, because the
format conversion streams reconstruct SSE events from the corrupted buffer.
The new append_utf8_safe function preserves incomplete trailing bytes in
a remainder buffer and merges them with the next chunk before decoding,
ensuring characters are never split during UTF-8 conversion.
Fixes: intermittent U+FFFD replacement characters in Claude Code output
via Copilot proxy (not reproducible with direct Copilot connections like
opencode because they pass through raw bytes without format conversion).
* style: fix cargo fmt formatting in UTF-8 boundary tests
---------
Co-authored-by: Cod1ng <codingts@gmail.com>
Co-authored-by: encodets <encodets@gmail.com>
Some OpenAI-compatible chat providers reject requests when Claude-side\nsystem fragments arrive as multiple system messages. Normalize the\nconverted OpenAI chat payload so system content becomes a single\nleading system message while leaving the rest of the message stream\nunchanged.\n\nConstraint: Nvidia/Qwen-style chat completions require a single leading system prompt\nRejected: Reorder system messages only | still leaves fragmented system prompts for strict backends\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep OpenAI chat system prompts normalized unless a provider explicitly requires fragmented system messages\nTested: cargo test proxy::providers::transform --manifest-path src-tauri/Cargo.toml\nNot-tested: Full end-to-end proxy capture against Nvidia upstream in this session\nRelated: #1881
The function is only called from read_gemini_credentials_from_keychain
which is already macOS-only. Without the gate, non-macOS CI fails with
dead-code error due to -D warnings.
usage_count is remaining quota (starts at total, decreases to 0),
not used count. Invert calculation so all providers consistently
show 0% when fresh and 100% when exhausted.
Add a new "Token Plan" template type in the usage query panel that
natively queries quota/usage from Chinese coding plan providers
(Kimi For Coding, Zhipu GLM, MiniMax) without requiring custom scripts.
- Rust backend: new coding_plan service with provider-specific API
queries (Kimi /v1/usages, Zhipu /api/monitor/usage/quota/limit,
MiniMax /coding_plan/remains) normalized into UsageResult
- Frontend: Token Plan template in UsageScriptModal with auto-detection
of provider based on ANTHROPIC_BASE_URL pattern matching
- Follows the same pattern as GitHub Copilot template (dedicated API
path in queryProviderUsage, no JS script needed)
Remove the hard block that prevented switching to providers requiring
proxy (OpenAI format, Copilot, full URL mode) when the proxy is not
running. Now the switch proceeds with a warning toast. Also deduplicate
the proxy hint info toast so it doesn't appear alongside the warning.
Re-enable GitHub Copilot provider preset and the OAuth auth center tab
that were temporarily hidden due to abnormal consumption rates. The
Copilot optimizer introduced in the previous commit addresses the
underlying issue.
Implement request classification, tool result merging, compact detection,
deterministic request IDs, and warmup downgrade for Copilot proxy.
The root cause was x-initiator being hardcoded to "user", making Copilot
count every API request (including tool callbacks and agent continuations)
as a separate premium interaction. The optimizer dynamically classifies
requests as "user" or "agent" based on message content analysis.
Closes#1813
Official providers use built-in subscription quota display instead of
custom usage scripts, and stream check is not applicable. Hide both
action buttons when isOfficialProvider is true.
- Read Gemini OAuth credentials from macOS Keychain (gemini-cli-oauth)
or legacy file (~/.gemini/oauth_creds.json)
- Auto-refresh expired access tokens using refresh_token (Google OAuth
tokens expire in ~1h, unlike Claude/Codex)
- Two-step API: loadCodeAssist for project ID, then retrieveUserQuota
for per-model quota buckets
- Classify models into Pro/Flash/Flash Lite categories, show min
remaining fraction as utilization percentage
- Extend isOfficialProvider() for Gemini (no API key + no base URL)
- Parameterize expiredHint i18n key with tool name for all three apps
Read Codex OAuth credentials from ~/.codex/auth.json (with macOS
Keychain fallback) and query chatgpt.com/backend-api/wham/usage to
show rate limit utilization on official Codex provider cards. Reuses
the same tier naming (five_hour, seven_day) for frontend i18n compat.
Read Claude OAuth credentials from macOS Keychain (with file fallback)
and query the Anthropic usage API to show quota utilization inline on
official provider cards. Includes compact countdown timer for reset
windows and hides the rarely-used seven_day_sonnet tier in inline mode.
Each app type (Claude/Codex/Gemini) now renders as a submenu instead
of flat items, keeping the top-level tray menu compact regardless of
provider count. The submenu label shows the current provider name
(e.g. "Claude · OpenRouter") for at-a-glance visibility.
Distinguish between missing API key, missing endpoint, auth failure,
unsupported provider (404/405), and timeout errors instead of showing
a generic failure toast for all cases.
Add ability to fetch available models from third-party aggregation
providers (SiliconFlow, OpenRouter, etc.) via OpenAI-compatible
GET /v1/models endpoint. Users can click "Fetch Models" button in
the provider form, then select models from a dropdown on each
model input field.
- Backend: new model_fetch service + Tauri command (Rust)
- Frontend: ModelInputWithFetch shared component
- Integrated into all 5 app forms (Claude/Codex/Gemini/OpenCode/OpenClaw)
- i18n support for zh/en/ja
* fix(copilot): 修复 GitHub Copilot 400 认证错误
问题:使用 GitHub Copilot provider 时报错 400 bad request
根因:与 copilot-api 项目对比发现多处差异
修复内容:
- 更新版本号 0.26.7 到 0.38.2
- 更新 API 版本 2025-04-01 到 2025-10-01
- 添加缺失的关键 headers
- 修正 openai-intent 值
- 添加动态 API endpoint 支持
- 同步更新 stream_check.rs headers
Closes#1777
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: flush stream after write_all in hyper_client proxy
Add explicit flush() calls after write_all() for TLS stream, plain TCP
stream, and CONNECT tunnel requests to ensure buffered data is sent
immediately, preventing connection hangs in Copilot auth header flow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* 修复登录时的剪切板在mac与linux端可能没复制验证码
* fix: flush stream after write_all in hyper_client proxy
Add explicit flush() calls after write_all() for TLS stream, plain TCP
stream, and CONNECT tunnel requests to ensure buffered data is sent
immediately, preventing connection hangs in Copilot auth header flow.
* 修复登录时的剪切板在mac与linux端可能没复制验证码
* 1、修复不同类型的个人商业等不同类型的copilot账号问题
2、将验证码复制改为异步操作
* fix: address PR review comments for Copilot auth │
│ │
│ - Fix clipboard blocking by using spawn_blocking for arboard ops │
│ - Implement dynamic endpoint routing for enterprise Copilot users │
│ - Add api_endpoints cache cleanup in remove_account() and clear_auth() │
│ - Change API endpoint log level from info to debug │
│ - Fix clear_auth() to continue cleanup even if file deletion fails │
│ - Add 9 unit tests for Copilot detection and api_endpoints cachin
* style: fix cargo fmt formatting
* Fix Copilot dynamic endpoint handling
* fix: restore clear_auth() memory-first cleanup order and fix cache leaks
- Restore clear_auth() to clean memory state before deleting the storage
file. The previous order (file deletion first) caused a regression where
users could get stuck in a "cannot log out" state if file removal failed.
- Add missing copilot_models.clear() in clear_auth() — this cache was
cleaned in remove_account() but never in the full clear path.
- Add endpoint_locks cleanup in both remove_account() and clear_auth()
to prevent minor in-process memory leaks.
- Update test to assert the correct behavior: memory should be cleaned
even when file deletion fails.
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: 周梦泽 <mengze.zhou@dafeng-tech.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
- Mark issues as stale after 60 days of inactivity
- Auto-close after 14 more days without response
- Bilingual (zh/en) stale and close messages
- Exempt security and performance labeled issues
- Runs daily at UTC 00:00, processes up to 100 issues per run
- Only affects issues, not pull requests
- Fix JA README macOS FAQ still claiming app is unsigned (now code-signed & notarized)
- Update SessionManager description from "Claude Code only" to "all supported apps" (EN/ZH/JA)
- Replace non-existent Flatpak download entry with self-build reference (EN/ZH/JA)
- Add OpenCode and OpenClaw filesystem paths to Flatpak minimal permissions
- Bump user manual version to v3.12.3
- Use get_home_dir() instead of dirs::home_dir() in get_opencode_dir()
and get_openclaw_dir() to respect CC_SWITCH_TEST_HOME override
- Add CC_SWITCH_TEST_HOME to all TempHome implementations
- Add #[serial] to all with_test_home tests to share serialization
with other env-mutating tests
- Remove --test-threads=1 workaround from CI
Users reported that Copilot support causes excessively fast token
consumption. Temporarily hide the feature by adding a `hidden` field
to ProviderPreset interface and commenting out the auth center tab
in settings. Existing Copilot providers in DB still work via proxy.
Add three community health files with bilingual (EN/ZH) support:
- CODE_OF_CONDUCT.md: Contributor Covenant v2.1 with official Chinese translation
- SECURITY.md: security policy pointing to GitHub Security Advisories
- CONTRIBUTING.md: contribution guide with dev setup, code style, PR guidelines,
i18n rules, and AI-assisted contribution policy
Add GitHub issue templates (bug report, feature request, documentation
issue, question) and PR template. All templates use bilingual format
(English + Chinese) to support the international community.
Keep Claude's live settings aligned with the latest provider state while proxy takeover is active, without breaking takeover fields or restore behavior.
Co-authored-by: Jason <farion1231@gmail.com>
* feat(provider): support additive provider key lifecycle management
Add `addToLive` parameter to add_provider so callers can opt out of
writing to the live config (e.g. when duplicating an inactive provider).
Add `originalId` parameter to update_provider to support provider key
renames — the old key is removed from live config before the new one
is written.
Frontend: ProviderForm now exposes provider-key input for openclaw app
type, and EditProviderDialog forwards originalId on save. Deep-link
import passes addToLive=true to preserve existing behavior.
* test(provider): add integration tests for additive provider key flows
Cover openclaw provider duplication scenario to verify that a generated
provider key is assigned automatically. Add MSW handlers for
get_openclaw_live_provider_ids, get_openclaw_default_model,
scan_openclaw_config_health, and check_env_conflicts endpoints.
Update EditProviderDialog mock to pass originalId alongside provider.
* fix(openclaw): replace json-five serializer to prevent panic on empty collections
json-five 0.3.1 panics when pretty-printing nested empty maps/arrays.
Switch value_to_rt_value() to serde_json::to_string_pretty() which
produces valid JSON5 output without the panic. Add regression test for
removing the last provider (empty providers map).
* style: apply rustfmt formatting to proxy and provider modules
Reformat chained .header() calls in ClaudeAdapter and StreamCheckService
for consistent alignment. Reorder imports alphabetically in stream_check.
Fix trailing whitespace in transform.rs and merge import lines in
provider/mod.rs.
* style: fix clippy warnings in live.rs and tray.rs
* refactor(provider): simplify live_config_managed and deduplicate tolerant live config checks
- Change live_config_managed from Option<bool> to bool with #[serde(default)]
- Extract repeated tolerant live config query into check_live_config_exists helper
- Fix duplicate key generation to also check live-only provider IDs
- Fix updateProvider test to match new { provider, originalId } call signature
- Add streaming_responses test type annotation for compiler inference
* fix(provider): distinguish legacy providers from db-only when tolerating live config errors
Change `ProviderMeta.live_config_managed` from `bool` to `Option<bool>`
to introduce a three-state semantic:
- `Some(true)`: provider has been written to live config
- `Some(false)`: explicitly db-only, never written to live config
- `None`: legacy data or unknown state (pre-existing providers)
Previously, legacy providers defaulted to `live_config_managed = false`
via `#[serde(default)]`, which silently swallowed live config parse
errors. This could mask genuine configuration issues for providers that
had actually been synced to live config before the field was introduced.
Now, only providers with an explicit `Some(false)` marker tolerate parse
errors; legacy `None` providers surface errors as before, preserving
safety for already-managed configurations.
Also wrap the `ensureQueryData` call for live provider IDs during
duplication in a try/catch so that a malformed config file shows a
user-facing toast instead of silently failing.
Add tests for both the legacy error propagation path and the frontend
duplication failure scenario.
* refactor(provider): unify OMO variant updates with atomic file-then-db writes and rollback
Consolidate the duplicated omo/omo-slim update branches into a single
match on the variant. Write the OMO config file from the in-memory
provider state *before* persisting to the database, so a file-write or
plugin-sync failure leaves the database unchanged. If `add_plugin`
fails after the config file is already written, roll back to the
previous on-disk contents via snapshot/restore.
Also:
- `sync_all_providers_to_live` now skips db-only providers
(`live_config_managed == Some(false)`) instead of attempting to write
them to live config.
- `import_{opencode,openclaw}_providers_from_live` mark imported
providers as `live_config_managed: Some(true)` so they are correctly
recognized during subsequent syncs.
- Extract OmoService helpers: `profile_data_from_provider`,
`snapshot_config_file`, `restore_config_file`, `write_profile_config`,
and the new public `write_provider_config_to_file`.
- Add 9 new tests covering sync skip, legacy restore, import marking,
OMO persistence, file-write failure, and plugin-sync rollback.
* fix(provider): fix additive provider delete/switch regressions and redundancy
- fix(delete): replace stale live_config_managed flag check with
check_live_config_exists so providers written to live before the
flag-flip logic was introduced are still cleaned up on delete
- fix(switch): make write_live_with_common_config return Err instead of
silently returning Ok when config structure is invalid, preventing
live_config_managed from being incorrectly flipped to true
- fix(update): block provider key rename for OMO/OMO Slim categories to
prevent orphaned current-state markers breaking OMO file syncs
- fix(switch): flip live_config_managed to true after successful live
write for DB-only additive providers so sync_all_providers_to_live
includes them on future syncs; roll back live write if DB update fails
- refactor(delete): merge symmetric OMO/OMO-Slim blocks into single
match-on-variant path; hoist DB read to top of additive branch
- refactor(remove_from_live_config): merge OMO/OMO-Slim if/else-if
into single match-on-variant path
- refactor(switch_normal): merge two OMO/OMO-Slim if blocks into one
OpenCode guard with (enable, disable) variant pair
- fix(update): remove redundant duplicate return Ok(true) after OMO
current-state write
* fix(test): use preferred_filename after OMO field rename
The merge from main brought in #1746 which renamed
OmoVariant.filename → preferred_filename, but the test helper
omo_config_path() was not updated, breaking compilation of all
new provider tests.
---------
Co-authored-by: Jason <farion1231@gmail.com>
Concurrent failover switches for the same app could cause is_current,
local settings, and Live backup to point at different providers.
- Add SwitchLockManager with per-app mutexes (different apps still parallel)
- Unify scattered switch logic into ProxyService::hot_switch_provider
- Fix TOCTOU in set_current_provider via mutate_settings
- Add logical_target_changed to skip redundant UI refreshes
- Add tests for serialization and restore-waits-for-switch scenarios
* fix(provider): show persistent config highlight for additive-mode providers
Closes https://github.com/farion1231/cc-switch/issues/1692
* fix(provider): limit persistent config highlight to opencode only
* Add directory picker before launching Claude terminal
* fix(terminal): preserve cwd path and strip Windows verbatim prefix
- Stop trimming non-empty paths so directories with leading/trailing
spaces on Unix are handled correctly
- Strip \\?\ extended-length prefix from canonicalized paths on Windows
to prevent batch script cd failures
* fix(terminal): restore UNC paths when stripping Windows verbatim prefix
Handle \\?\UNC\server\share form separately from regular \\?\ prefix,
converting it back to \\server\share so network/WSL directory paths
remain valid in batch cd commands.
* fix(terminal): use pushd for UNC paths in Windows batch launcher
`cmd.exe` cannot set a UNC path (e.g. `\\wsl$\...`) as the current
directory via `cd /d`; it errors with "CMD does not support UNC paths
as current directories". Switch to `pushd` which temporarily maps the
UNC share to a drive letter.
Rename `build_windows_cd_command` → `build_windows_cwd_command` to
reflect the broader semantics. Extract `build_windows_cwd_command_str`
and `is_windows_unc_path` helpers for testability, and add unit tests
covering drive paths, UNC paths, and batch metacharacter escaping.
Also fix minor style issues: sort mod declarations alphabetically,
add missing EOF newline in lightweight.rs, add explicit type annotation
in streaming_responses test, and reformat tray menu builder chain.
* style(frontend): reformat provider forms, constants and hooks
Apply prettier formatting across 5 frontend files. No logic changes.
Changed files:
- AddProviderDialog.tsx: reformat generic type annotation and callback
- ClaudeFormFields.tsx: consolidate multi-line useState and Collapsible props
- CodexConfigSections.tsx: expand single-line React imports to multi-line,
collapse removeCodexTopLevelField() call
- constants.ts: merge TemplateType into single line
- useSkills.ts: expand single-line TanStack Query imports to multi-line,
reformat uninstallSkill mutationFn chain
* deps(proxy): add hyper ecosystem crates and manual decompression libs
reqwest internally normalizes all header names to lowercase and does not
preserve insertion order, causing proxied requests to differ from the
original client requests. To achieve transparent header forwarding with
original casing and order, introduce lower-level hyper HTTP client libs.
New dependencies:
- hyper-util 0.1: TokioExecutor + legacy Client with
preserve_header_case support for HTTP/1.1
- hyper-rustls 0.27: rustls-based TLS connector for hyper
- http 1 / http-body 1 / http-body-util 0.1: HTTP type crates for
hyper 1.x request/response construction
- flate2 1: manual gzip/deflate decompression (replaces reqwest auto)
- brotli 7: manual brotli decompression
Changed dependencies:
- serde_json: enable preserve_order feature to keep JSON field order
- reqwest: drop gzip feature to prevent reqwest from overriding the
client's original accept-encoding header
* refactor(proxy): use hyper client for header-case preserving forwarding
Previously the proxy used reqwest for all upstream requests. reqwest
normalizes header names to lowercase and reorders them internally,
making proxied requests distinguishable from direct CLI requests.
Some upstream providers are sensitive to these differences.
This commit replaces reqwest with a hyper-based HTTP client on the
default (non-proxy) path, achieving wire-level header fidelity:
Server layer (server.rs):
- Replace axum::serve with a manual hyper HTTP/1.1 accept loop
- Enable preserve_header_case(true) so incoming header casing is
captured in a HeaderCaseMap extension on each request
- Bridge hyper requests to axum Router via tower::Service
New hyper client module (hyper_client.rs):
- Lazy-initialized hyper-util Client with preserve_header_case
- ProxyResponse enum wrapping both hyper::Response and reqwest::Response
behind a unified interface (status, headers, bytes, bytes_stream)
- send_request() builds requests with ordered HeaderMap + case map
Request handlers (handlers.rs):
- Switch from (HeaderMap, Json<Value>) extractors to raw
axum::extract::Request to preserve Extensions (containing the
HeaderCaseMap from the accept loop)
- Pass extensions through the forwarding chain
Forwarder (forwarder.rs):
- Remove HEADER_BLACKLIST array; replace with ordered header iteration
that preserves original header sequence and casing
- Build ordered_headers by iterating client headers, skipping only
auth/host/content-length, and inserting auth headers at the original
authorization position to maintain order
- Handle anthropic-beta (ensure claude-code-20250219 tag) and
anthropic-version (passthrough or default) inline during iteration
- Remove should_force_identity_encoding() — accept-encoding is now
transparently forwarded to upstream
- Use hyper client by default; fall back to reqwest only when an
HTTP/SOCKS5 proxy tunnel is configured
Provider adapters (adapter.rs, claude.rs, codex.rs, gemini.rs):
- Replace add_auth_headers(RequestBuilder) -> RequestBuilder with
get_auth_headers(AuthInfo) -> Vec<(HeaderName, HeaderValue)>
- Adapters now return header pairs instead of mutating a reqwest builder
- Claude adapter: merge Anthropic/ClaudeAuth/Bearer into single branch;
move Copilot fingerprint headers into get_auth_headers
Response processing (response_processor.rs):
- Add manual decompression (gzip/deflate/brotli via flate2 + brotli)
for non-streaming responses, since reqwest auto-decompression is now
disabled to allow accept-encoding passthrough
- Add compressed-SSE warning log for streaming responses
- Accept ProxyResponse instead of reqwest::Response
HTTP client (http_client.rs):
- Disable reqwest auto-decompression (.no_gzip/.no_brotli/.no_deflate)
on both global and per-provider clients
Streaming adapters (streaming.rs, streaming_responses.rs):
- Generalize stream error type from reqwest::Error to generic E: Error
Misc:
- log_codes.rs: add SRV-005 (ACCEPT_ERR) and SRV-006 (CONN_ERR)
- stream_check.rs: reformat copilot header lines
- transform.rs: fix trailing whitespace alignment
* fix(lint): resolve 35 clippy warnings across Rust codebase
Fix all clippy warnings reported by `cargo clippy --lib`:
- codex_config.rs: fix doc_overindented_list_items (3 spaces -> 2)
- commands/copilot.rs: inline format args in 2 log::error! calls
- commands/provider.rs: inline format args in 3 map_err closures
- proxy/hyper_client.rs: inline format arg in log::debug! call
- proxy/providers/copilot_auth.rs: inline format args in 16 locations
(log macros, format! in headers, error constructors)
- proxy/thinking_optimizer.rs: inline format args in 2 log::info! calls
- services/skill.rs: inline format args in log::debug! call
- services/webdav_sync.rs: inline format args in 6 format! calls
(version compat messages, download limit messages)
- services/webdav_sync/archive.rs: inline format args in 2 format! calls
- session_manager/providers/opencode.rs: inline format args in
source_path format!
All fixes use the clippy::uninlined_format_args suggestion pattern:
format!("msg: {}", var) -> format!("msg: {var}")
* deps(proxy): add raw HTTP write and native TLS cert dependencies
Add crates required for the raw TCP/TLS write path that bypasses
hyper's header encoder to preserve original header name casing:
- httparse: parse raw TCP peek bytes to capture header casings
- tokio-rustls + rustls: direct TLS connections for raw write path
- webpki-roots: Mozilla CA bundle baseline
- rustls-native-certs: load system keychain CAs (trusts proxy MITM
certificates from Clash, mitmproxy, etc.)
* fix(proxy): address code review feedback on response handling
Fixes from PR #1714 code review:
- Extract `read_decoded_body()` and `strip_entity_headers_for_rebuilt_body()`
in response_processor to properly clean content-encoding/content-length
headers after decompression
- Reuse `read_decoded_body()` in handlers.rs for Claude transform path,
ensuring compressed responses are decoded before format conversion
- Make `build_proxy_url_from_config()` public so forwarder can pass proxy
URL to the hyper raw write path
- Add `has_system_proxy_env()` utility with test coverage
- Add 50ms backoff after accept() failures in server.rs to prevent
tight-loop CPU spin on transient socket errors
* feat(proxy): implement raw TCP/TLS write with HTTP CONNECT tunnel
Rewrite hyper_client with a two-tier strategy for header case preservation:
Primary path (raw write):
- Peek raw TCP bytes in server.rs to capture OriginalHeaderCases before
hyper lowercases them
- Build raw HTTP/1.1 request bytes with exact original header name casing
- Write directly to TLS stream, then use WriteFilter to let hyper parse
the response while discarding its duplicate request writes
- Support HTTP CONNECT tunneling through upstream proxies, so header case
is preserved even when a proxy (Clash, V2Ray) is configured
Fallback path (hyper-util Client):
- Used when OriginalHeaderCases is empty or raw write fails
- Configured with title_case_headers(true) for best-effort casing
TLS improvements:
- Load native system certificates alongside webpki roots so proxy MITM
CAs (installed in system keychain) are trusted through CONNECT tunnels
Key types added:
- OriginalHeaderCases: maps lowercase name → original wire-casing bytes
- WriteFilter<S>: AsyncRead+AsyncWrite wrapper that discards writes
- connect_via_proxy(): HTTP CONNECT tunnel establishment
- ExtensionDebugMarker: diagnostic marker for extension chain debugging
* refactor(proxy): route requests through hyper with proxy-aware forwarding
Rework forwarder request dispatch to always prefer the hyper raw write
path (header case preservation) over reqwest:
Request routing:
- HTTP/HTTPS proxy: hyper raw write through CONNECT tunnel (case preserved)
- SOCKS5 proxy: reqwest fallback (CONNECT not supported for SOCKS5)
- No proxy: hyper raw write direct connection
Header handling improvements:
- Replace host header in-place at original position instead of
skip-and-append, preserving client's header ordering
- Preserve client's original accept-encoding for transparent passthrough;
only force identity encoding when transform path needs decompression
- Add should_force_identity_encoding() to centralize the decision
- Remove hardcoded 'br, gzip, deflate' override that masked client values
Proxy URL resolution (priority order):
1. Provider-specific proxy config (if enabled)
2. Global proxy URL configured in CC Switch
3. Direct connection (no proxy)
* chore(proxy): remove dead code, redundant tests and debug scaffolding
- Inline should_force_identity_encoding() (was just `needs_transform`)
and delete its 5 test cases
- Remove ExtensionDebugMarker diagnostic type
- Remove unused has_system_proxy_env() and its test
- Remove strip_entity_headers test
- Simplify hyper path: remove redundant is_socks_proxy ternary
- Update hyper_client module doc to reflect CONNECT tunnel support
* fix(proxy): block direct-connect fallback and complete CONNECT tunnel support
* feat(hooks): improve proxy requirement warnings with specific reasons
- Remove redundant OpenAI format hint toast messages
- Add detailed reason detection for proxy requirements (OpenAI Chat, OpenAI Responses, full URL mode)
- Update i18n files with new reason-specific keys
* style(*): format code with prettier
- Remove extra whitespace in http_client.rs
- Fix formatting issues in useProviderActions.ts
* fix(proxy): post-merge fixes for forward return type and clippy warnings
- Restore forward() return type to (ProxyResponse, Option<String>)
to pass claude_api_format through to callers
- Inline format args in log::warn! macro (clippy::uninlined_format_args)
- Suppress too_many_arguments for check_claude_stream
* refactor(proxy): preserve original header wire order and add non-streaming body timeout
- Rewrite build_raw_request to emit headers in original
client-sent sequence instead of hash-map order
- Remove unused OriginalHeaderCases::get_all method
- Add body_timeout to read_decoded_body to prevent
requests hanging when upstream stalls after headers
* fix: route copilot claude openai models to responses
* fix(i18n): add copilotProxyHint translation key for all locales
The copilotProxyHint message was using inline defaultValue with Chinese
text, which would show Chinese to English and Japanese users. Added
proper translation keys in zh/en/ja locale files and removed the
hardcoded defaultValue fallback.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* feat(proxy): add full URL mode and refactor endpoint rewriting
- Add `isFullUrl` provider meta to treat base_url as complete API endpoint
- Remove hardcoded `?beta=true` from Claude adapter, pass through from client
- Refactor forwarder endpoint rewriting with proper query string handling
- Block provider switching when proxy is required but not running
- Add full URL toggle UI in endpoint field with i18n (zh/en/ja)
* refactor(proxy): remove beta query handling
* fix(proxy): strip beta query when rewriting Claude endpoints
* feat(codex): complete full URL support
* refactor(ui): refine full URL endpoint hint
Add StepFun Step Plan (阶跃星辰编程计划) as an OpenCode provider preset.
StepFun Step Plan is a subscription-based coding AI service that uses a
dedicated API endpoint separate from the standard StepFun provider.
- Base URL: https://api.stepfun.com/step_plan/v1
- Model: step-3.5-flash (196B MoE, optimized for agent and coding tasks)
- Category: cn_official
- Supports tool_call, reasoning, temperature control
Ref: https://platform.stepfun.com/docs/zh/step-plan/overview
Made-with: Cursor
Co-authored-by: sky-wang-salvation <sky-wang-salvation@users.noreply.github.com>
Add risk disclaimer section in all three languages (EN/ZH/JA) warning
users about potential GitHub ToS violations, account suspension risks,
and no long-term availability guarantee for the Copilot reverse proxy.
Tauri's built-in DMG styling relies on AppleScript/Finder access which
silently fails on CI (tauri-apps/tauri#1731). Switch to create-dmg tool
which works on GitHub Actions macOS runners.
- Replace Tauri DMG with create-dmg: background image, icon positions,
app-drop-link, codesign, hide-extension
- Regenerate background image at 2x Retina resolution (1320x800)
- Revert tauri.conf.json dmg config (ineffective on CI)
- Reorder steps: Prepare → Notarize DMG → Verify
- Notarize and Verify now use release-assets/ path for DMG
- Add DMG background image with drag-to-install arrow guide
- Configure window size (660x400), app and Applications icon positions
- Center icons horizontally with visual arrow between them
Tauri only notarizes the .app bundle, not the DMG container. This caused
stapler staple to fail with "Record not found" for the DMG.
- Add "Notarize macOS DMG" step using xcrun notarytool with retry logic
- Add retry logic (3 attempts) to macOS build step for transient network failures
- Add hdiutil verify before DMG notarization submission
- Import Developer ID Application certificate into temporary keychain
- Inject APPLE_SIGNING_IDENTITY/APPLE_ID/APPLE_PASSWORD/APPLE_TEAM_ID
into Tauri build step for automatic signing and notarization
- Staple notarization tickets to both .app and .dmg (hard-fail)
- Add verification step: codesign --verify + spctl -a + stapler validate
for both .app and .dmg, gating the release on success
- Collect .dmg alongside .tar.gz and .zip in release assets
- Clean up temporary keychain with original default restored
- Update release notes to recommend .dmg and note Apple notarization
- Remove all xattr workarounds and "unidentified developer" warnings
from README, README_ZH, installation guides, and FAQ (EN/ZH/JA)
Two components (ProviderList, UsageScriptModal) directly spread the full
settings object from the query into settingsApi.save(), which includes
webdavSync with an empty password (cleared by get_settings_for_frontend
for security). The backend merge_settings_for_save only preserved
existing WebDAV config when the incoming field was None, not when it was
present with an empty password.
Frontend fix: strip webdavSync before saving in both components, matching
the pattern already used by useSettings hook.
Backend defense-in-depth: merge_settings_for_save now backfills the
existing password when the incoming one is empty, preventing future
regressions from similar oversights.
Replace map_thinking_to_reasoning_effort() with resolve_reasoning_effort()
that uses a two-tier priority system:
1. Explicit output_config.effort: low/medium/high map 1:1, max → xhigh
2. Fallback: thinking.type + budget_tokens thresholds (<4k → low,
4k-16k → medium, ≥16k → high, adaptive → high)
Both Chat Completions and Responses API paths share the same helper,
ensuring consistent mapping across all OpenAI-compatible endpoints.
Claude Opus 4.6 and Sonnet 4.6 1M context window is now GA and no
longer requires a beta header. Update contextWindow from 200k to 1M
for all OpenClaw/OpenCode presets (27 entries in OpenClaw, 1 in
OpenCode Bedrock). Also add claude-sonnet-4-6 model pricing seed.
- Remove AuthCenterPanel import and OAuth TabsContent
- Narrow activeTab type from three values to "app-specific" | "universal"
- Simplify footer by removing oauth branch, reducing to two-way conditional
- Change TabsList from grid-cols-3 to grid-cols-2
- OAuth authentication remains available in settings page and CopilotAuthSection
- Make header constants in copilot_auth.rs public, add COPILOT_INTEGRATION_ID
- Unify User-Agent across 4 internal methods (eliminate "CC-Switch" leakage) and version string
- claude.rs add_auth_headers now references shared constants, adds user-agent / api-version / openai-intent
- forwarder.rs filters all 6 fixed fingerprint headers for Copilot requests to prevent reqwest append duplication
- stream_check.rs health check pipeline aligned with new fingerprint
* refactor(toolsearch): replace binary patch with ENABLE_TOOL_SEARCH env var toggle
- Remove toolsearch_patch.rs binary patching mechanism (~590 lines)
- Delete `toolsearch_patch.rs` and `commands/toolsearch.rs`
- Remove auto-patch startup logic and command registration from lib.rs
- Remove `tool_search_bypass` field from settings.rs
- Remove frontend settings ToggleRow, useSettings hook sync logic, and API methods
- Clean up zh/en/ja i18n keys (notifications + settings)
- Add ENABLE_TOOL_SEARCH toggle to Claude provider form
- Add checkbox in CommonConfigEditor.tsx (alongside teammates toggle)
- When enabled, writes `"env": { "ENABLE_TOOL_SEARCH": "true" }`
- When disabled, removes the key; takes effect on provider switch
- Add zh/en/ja i18n key: `claudeConfig.enableToolSearch`
Claude Code 2.1.76+ natively supports this env var, eliminating the need for binary patching.
* feat(claude): add effortLevel high toggle to provider form
- Add "high-effort thinking" checkbox to Claude provider config form
- When checked, writes `"effortLevel": "high"`; when unchecked, removes the field
- Add zh/en/ja i18n translations
* refactor(claude): remove deprecated alwaysThinking toggle
- Claude Code now enables extended thinking by default; alwaysThinkingEnabled is a no-op
- Thinking control is now handled via effortLevel (added in prior commit)
- Remove state, switch case, and checkbox UI from CommonConfigEditor
- Clean up alwaysThinking i18n keys across zh/en/ja locales
* feat(opencode): add setCacheKey: true to all provider presets
- Add setCacheKey: true to options in all 33 regular presets
- Add setCacheKey: true to OPENCODE_DEFAULT_CONFIG for custom providers
- Exclude 2 OMO presets (Oh My OpenCode / Slim) which have their own config mechanism
Closes#1523
* fix(codex): resolve 1M context window toggle causing MCP editor flicker
- Add localValueRef to short-circuit duplicate CodeMirror updateListener callbacks,
breaking the React state → CodeMirror → stale onChange → React state feedback loop
- Use localValueRef.current in handleContextWindowToggle and handleCompactLimitChange
to avoid stale closure reads
- Change compact limit input from type="number" to type="text" with inputMode="numeric"
to remove unnecessary spinner buttons
* feat(codex): add 1M context window toggle utilities and i18n keys
- Add extractCodexTopLevelInt, setCodexTopLevelInt, removeCodexTopLevelField
TOML helpers in providerConfigUtils.ts
- Add i18n keys for contextWindow1M, autoCompactLimit in zh/en/ja locales
* feat(claude): collapse model mapping fields by default
- Wrap 5 model mapping inputs in a Collapsible, collapsed by default
- Auto-expand when any model value is present (including preset-filled)
- Show hint text when collapsed explaining most users need no config
- Add zh/en/ja i18n keys for toggle label and collapsed hint
- Use variant={null} to avoid ghost button hover style clash in dark mode
* feat(claude): merge advanced fields into single collapsible section
- Merge API format, auth field, and model mapping into a unified "Advanced Options" collapsible
- Extend smart-expand logic to detect non-default values across all advanced fields
- Preserve model mapping sub-header and hint with a separator line
- Update zh/en/ja i18n keys (advancedOptionsToggle, advancedOptionsHint, modelMappingLabel, modelMappingHint)
* feat(copilot): add GitHub Copilot reverse proxy support
Add GitHub Copilot as a Claude provider variant with OAuth device code
authentication and Anthropic ↔ OpenAI format transformation.
Backend:
- Add CopilotAuthManager for GitHub OAuth device code flow
- Implement Copilot token auto-refresh (60s before expiry)
- Persist GitHub token to ~/.cc-switch/copilot_auth.json
- Add ProviderType::GitHubCopilot and AuthStrategy::GitHubCopilot
- Modify forwarder to use /chat/completions for Copilot
- Add Copilot-specific headers (Editor-Version, Editor-Plugin-Version)
Frontend:
- Add CopilotAuthSection component for OAuth UI
- Add useCopilotAuth hook for OAuth state management
- Auto-copy user code to clipboard and open browser
- Use 8-second polling interval to avoid GitHub rate limits
- Skip API Key validation for Copilot providers
- Add GitHub Copilot preset with claude-sonnet-4 model
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
* fix(copilot): remove is_expired() calls from tests
Remove references to deleted is_expired() method in test code.
Only is_expiring_soon() is needed for token refresh logic.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
* feat(copilot): add real-time model listing from Copilot API
- Add fetch_models() to CopilotAuthManager calling GET /models endpoint
- Add copilot_get_models Tauri command
- Add copilotGetModels() frontend API wrapper
- Modify ClaudeFormFields to show model dropdown for Copilot providers
- Fetches available models on component mount when isCopilotPreset
- Groups models by vendor (Anthropic, OpenAI, Google, etc.)
- Input + dropdown button combo allows both manual entry and selection
- Non-Copilot providers keep original plain Input behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(copilot): add usage query integration
- Add Copilot usage API integration (fetch_usage method)
- Add copilot_get_usage Tauri command
- Add GitHub Copilot template in usage query modal
- Unify naming: copilot → github_copilot
- Add constants management (TEMPLATE_TYPES, PROVIDER_TYPES)
- Improve error handling with detailed error messages
- Add database migration (v5 → v6) for template type update
- Add i18n translations (zh, en, ja)
- Improve type safety with TemplateType
- Apply code formatting (cargo fmt, prettier)
* 修复github 登录和注销问题 ,模型选择问题
* feat(copilot): add multi-account support for GitHub Copilot
- Add multi-account storage structure with v1 to v2 migration
- Add per-account token caching and auto-refresh
- Add new Tauri commands for account management
- Integrate account selection in Proxy forwarder
- Add account selection UI in CopilotAuthSection
- Save githubAccountId to ProviderMeta
- Add i18n translations for multi-account features (zh/en/ja)
* 修复用量查询Reset字段出现多余字符
* refactor(auth-binding): introduce generic provider auth binding primitives
- add shared authBinding types in Rust and TypeScript while keeping githubAccountId as a compatibility field\n- resolve Copilot token, models, and usage through provider-bound account lookup instead of only the implicit default account\n- fix the Unix build regression in settings.rs by restoring std::io::Write for write_all()\n- remove the accidental .github ignore entry and drop leftover Copilot form debug logs\n- keep the first migration step non-breaking by writing both authBinding and the legacy githubAccountId field from the form
* refactor(auth-service): add managed auth command surface and explicit default account state
- introduce generic managed auth commands and frontend auth API wrappers for provider-scoped login, status, account listing, removal, logout, and default-account selection\n- store an explicit Copilot default_account_id instead of relying on HashMap iteration order, and use it consistently for fallback token/model/usage resolution\n- sort managed accounts deterministically and surface default-account state to the UI\n- refactor the Copilot form hook to wrap a generic useManagedAuth implementation while preserving the existing component contract\n- add default-account controls to the Copilot auth section and extend Copilot auth status serialization/tests for the new state
* feat(auth-center): add a dedicated settings entrypoint for managed OAuth accounts
- add an Auth Center tab to Settings so managed OAuth accounts are no longer hidden inside individual provider forms\n- introduce a first AuthCenterPanel that hosts GitHub Copilot account management as the initial managed auth provider\n- keep the provider form experience intact while establishing a global account-management surface for future providers such as OpenAI\n- validate that the new settings tab works cleanly with the generic managed auth hook and existing Copilot account controls
* feat(add-provider): expose managed OAuth sources alongside universal providers
- add an OAuth tab to the Add Provider flow so managed auth sources sit beside app-specific and universal providers\n- reuse the new Auth Center panel inside the dialog, keeping account management discoverable during provider creation\n- make the dialog footer adapt to the OAuth tab so account setup does not pretend to create a provider directly\n- align the add-provider UX with the new architecture where OAuth accounts are global assets and providers bind to them later
* fix(auth-reliability): harden managed auth persistence and refresh behavior
- replace direct Copilot auth store writes with private temp-file writes and atomic rename semantics, and document the local token storage limitation\n- add per-account refresh locks plus a double-check path so concurrent requests do not stampede GitHub token refresh\n- surface legacy migration failures through auth status, expose them in the UI, and add translated copy for the new account-state labels\n- stop writing the legacy githubAccountId field from the provider form while keeping compatibility reads in place\n- add logout error recovery and Copilot model-load toasts so auth failures are no longer silently swallowed
* refactor(copilot-detection): prefer provider type before URL fallbacks
- update forwarder endpoint rewriting to treat providerType as the primary GitHub Copilot signal\n- keep githubcopilot.com string matching only as a compatibility fallback for older provider records without providerType\n- reduce one more path where Copilot behavior depended purely on URL heuristics
* fix(copilot-auth): add cancel button to error state in CopilotAuthSection
- 错误状态下仅有"重试"按钮,用户无法退出(如不可恢复的 403 未订阅错误)
- 新增"取消"按钮,复用已有的 cancelAuth 逻辑重置为 idle 状态
* 修复打包后github账号头像显示异常
* 修复github copilot 来源的模型测试报错
* feat(copilot-preset): add default model presets for GitHub Copilot
- 补充 Copilot 预设的默认模型配置,用户选完预设即可直接使用
- ANTHROPIC_MODEL: claude-opus-4.6
- ANTHROPIC_DEFAULT_HAIKU_MODEL: claude-haiku-4.5
- ANTHROPIC_DEFAULT_SONNET_MODEL: claude-sonnet-4.6
- ANTHROPIC_DEFAULT_OPUS_MODEL: claude-opus-4.6
---------
Co-authored-by: Jason <farion1231@gmail.com>
Co-authored-by: 周梦泽 <mengze.zhou@dafeng-tech.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Update CHANGELOG.md with full v3.12.3 entry, create release notes in
three languages (en/zh/ja), bump macOS minimumSystemVersion from 10.15
to 12.0 (Monterey) to match actual runtime requirements, and update
README version badges and links.
All content panels used h-[calc(100vh-8rem)] which didn't match the
actual available space (CONTENT_TOP_OFFSET is 92px on Mac, 64px on
Win/Linux, not 128px), causing a visible gap at the bottom.
Replace with flex-1 min-h-0 to let panels fill the remaining space
after <main> and any sibling elements (e.g. OpenClaw health banner).
Introduce list/restore/delete commands for skill backups created during
uninstall. Restore copies files back to SSOT, saves the DB record, and
syncs to the current app with rollback on failure. Delete removes the
backup directory after a confirmation dialog. ConfirmDialog gains a
configurable zIndex prop to support nested dialog stacking.
Create a local backup under ~/.cc-switch/skill-backups/ before removing
skill directories. The backup includes all skill files and a meta.json
with original skill metadata. Old backups are pruned to keep at most 20.
The backup path is returned to the frontend and shown in the success
toast. Bump version to 3.12.3.
The AppToggleGroup component added in 7097a0d7 uses Radix UI Tooltip
which requires a TooltipProvider context. Without it, opening the
import dialog crashes with a runtime error and renders a blank page.
Non-streaming requests were forced to use `Accept-Encoding: identity`,
preventing upstream response compression and increasing bandwidth usage.
Now only streaming requests conservatively keep `identity` to avoid
decompression errors on interrupted SSE streams. Non-streaming requests
let reqwest auto-negotiate gzip and transparently decompress responses.
Responses API uses max_output_tokens for all models including o-series.
The o-series max_completion_tokens fix should only apply to Chat Completions API.
Split the expanded model panel into two editing areas:
- "Model Properties" for top-level fields (variants, cost, etc.)
- "SDK Options" for model.options fields (provider routing, etc.)
Also guard against renaming extra fields to reserved keys (name, limit,
options) or duplicate names, and fix ModelOptionKeyInput blur desync when
a rename is rejected by the parent handler.
* fix(proxy): use max_completion_tokens for o1/o3 series models
When converting Anthropic requests to OpenAI format for o1/o3 series
models (like o1-mini, o3-mini), use max_completion_tokens instead of
max_tokens to avoid unsupported_parameter errors.
Fixes#1448
* fix: revert incorrect o-series max_completion_tokens in Responses API path
Responses API uses max_output_tokens for all models including o-series.
The o-series max_completion_tokens fix should only apply to Chat Completions API.
---------
Co-authored-by: Hajen Teowideo <hajen.teowideo@example.com>
Co-authored-by: Jason Young <44939412+farion1231@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
Make ProviderForm.handleSubmit async and await onSubmit so react-hook-form's
isSubmitting state is tracked. Disable submit buttons in AddProviderDialog and
EditProviderDialog while submission is in flight via onSubmittingChange callback.
Resolve the active `claude` command from PATH and apply an equal-length
byte patch to remove the domain whitelist check. Backups are stored in
~/.cc-switch/toolsearch-backups/ (SHA-256 of path) so they survive
Claude Code version upgrades. The patch auto-reapplies on app startup
when the setting is enabled.
Frontend checks PatchResult.success and rolls back the setting on failure.
Skills import previously inferred app enablement from filesystem presence,
causing incorrect multi-app activation when the same skill directory existed
under multiple app paths. Now the frontend submits explicit app selections
via ImportSkillSelection, and schema migration preserves a snapshot of
legacy app mappings to avoid lossy reconstruction.
Also adds reconciliation to sync_to_app (removes disabled/orphaned symlinks)
and MCP sync_all_enabled (removes disabled servers from live config).
When proxy takeover is active, update_live_backup_from_provider rebuilds the Codex restore snapshot from the current provider and common-config state. We were then replacing the new mcp_servers table with the previous backup's table wholesale, which meant stopping takeover could restore stale MCP entries and silently discard MCP changes made through provider/common-config updates.
Change the backup preservation step to merge MCP entries by server id instead of replacing the entire table. New provider/common-config MCP definitions now win on conflict, while backup-only servers are retained so live-only MCP state still survives a hot-switch.
Add regression coverage for the existing preservation case and for the conflict case where both the old backup and the new provider define the same MCP server.
Rewrite setCodexBaseUrl/extractCodexBaseUrl to understand TOML section
boundaries, ensuring base_url is written into the correct
[model_providers.<name>] section instead of being appended to file end.
- Add section-aware TOML helpers in providerConfigUtils.ts
- Extract shared update_codex_toml_field/remove_codex_toml_base_url_if
in codex_config.rs, deduplicate proxy.rs TOML editing logic
- Replace scattered inline base_url regexes with extractCodexBaseUrl()
- Add comprehensive tests for both Rust and TypeScript implementations
- Make sync_current_provider_for_app takeover-aware: update restore
backup instead of overwriting live config when proxy is active
- Introduce explicit "cleared" flag for common config snippets to
prevent auto-extraction from resurrecting user-cleared snippets
- Reorder startup: extract snippets from clean live files before
restoring proxy takeover state
- Add one-time migration flag to skip legacy commonConfigEnabled
migration on subsequent startups
- Add regression tests for takeover backup preservation, explicit
clear semantics, and migration flag roundtrip
Update takeover backup generation to rebuild effective provider settings with common config applied before saving restore snapshots.
Keep Codex mcp_servers entries when hot-switching providers under takeover so restore does not drop live-only MCP config.
Migrate legacy providers with inferred common-config usage to explicit commonConfigEnabled=true markers during startup and default imports, and cover the new behavior with proxy and provider regression tests.
Add optional `authHeader` boolean to support vendor-specific auth headers
(e.g. Longcat). Refactor `resetOpenclawState` to use the shared
`OpenClawProviderConfig` type instead of an inline duplicate definition.
Replace SDS logos with SiliconFlow logos, add SiliconFlow sponsor entries
to all three README files (en/zh/ja), fix incorrect alt attribute, and
update all SiliconFlow provider preset apiKeyUrl to affiliate link.
The auto-open useEffect in CodexConfigEditor and GeminiConfigEditor
created an inescapable loop: commonConfigError triggered modal open,
closing the modal didn't clear the error, so the effect immediately
reopened it — locking the entire UI.
- Remove auto-open useEffect from both Codex and Gemini config editors
- Convert common config modals to draft editing (edit locally, validate
before save) instead of persisting on every keystroke
- Add TOML/JSON pre-validation via parseCommonConfigSnippet before any
merge operation to prevent invalid content from being persisted
- Expose clearCommonConfigError so editors can clear stale errors on
modal close
- Add XCodeAPI sponsor entry to all three READMEs (zh, en, ja)
- Fix XCodeAPI logo alt from "Micu" to "XCodeAPI" in README_ZH.md
- Fix Crazyrouter logo alt from "AICoding" to "Crazyrouter" in all READMEs
Add Micu (米醋API) sponsor entry to all three README files (EN/ZH/JA)
with logo and description. Update apiKeyUrl for Micu provider presets
across claude, codex, opencode, and openclaw to use affiliate
registration link.
Add optional `nameKey` field to all preset interfaces for localized
display names. The preset selector and form now resolve `nameKey` via
i18next, falling back to `name` when unset.
- zh: 优云智算 / en+ja: Compshare
- Update model ID prefix ucloud/ → compshare/ in OpenClaw presets
- Update partner promotion copy and README sponsor descriptions
Replace useRef+useEffect async index rebuild with useMemo so the
FlexSearch index and the sessions array always reference the same data.
This ensures filtered search results update immediately when a session
is deleted via TanStack Query setQueryData.
Split LOCAL_ONLY_TABLES into SYNC_SKIP_TABLES (export) and
SYNC_PRESERVE_TABLES (import). provider_health is skipped on export
but no longer restored on import, since it has a foreign key on
providers(id, app_type) that may not match the remote dataset.
Health data is ephemeral and rebuilds automatically at runtime.
* feat(openClaw form): add input type selection for models Advanced Options / 为模型高级选项添加输入类型选择
* fix(i18n): add missing openclaw.inputTypes key to all locales
The new inputTypes field in OpenClawFormFields used defaultValue fallback,
causing English and Japanese users to see Chinese text.
---------
Co-authored-by: xu.liu2 <xu.liu2@brgroup.com>
Co-authored-by: Jason <farion1231@gmail.com>
Fixes issue where Longcat models were failing with 404 error due to missing
authHeader configuration. According to Longcat API documentation, this
setting is required for proper authentication.
Closes: #1376
The toolbar container used `justify-end` which caused content to overflow
to the left side. Browser `scrollWidth` does not account for left-side
overflow, so `useAutoCompact` could never detect it and compact mode
never activated — resulting in clipped tab labels (e.g. "aude" instead
of "Claude") at minimum window width.
Fix: remove `justify-end` from the toolbar container and use `ml-auto`
on the child element instead. This keeps the right-aligned appearance
but makes overflow go rightward, where `scrollWidth` correctly detects
it and triggers compact mode.
Co-authored-by: zuolan <zuolan1102@qq.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Update Ucloud links to coding-plan page across READMEs
- Remove hardcoded model overrides from Claude preset
- Switch Codex model from zai-org/glm-5 to gpt-5.4
- Replace GLM-5/MiniMax models with claude-opus-4-6 in OpenClaw preset
- Add templateValues and suggestedDefaults for Ucloud and Micu OpenClaw presets
Add a switch in the OpenClaw provider form to optionally send a browser
User-Agent header. The toggle defaults to off — only providers that
explicitly include headers in their preset or config will have it enabled.
Remove the previous auto-injection logic that force-added User-Agent on
every preset load and new provider creation.
- Add X-Code provider presets with x-code.cn endpoint
- Fix ANTHROPIC_API_KEY to ANTHROPIC_AUTH_TOKEN in Claude preset
- Register x-code icon SVG in icon index
- Add partnerPromotion.x-code i18n keys for zh, en, ja locales
- Add Micu provider presets with openclaudecode.cn endpoint
- Register micu icon SVG in icon index with namespaced CSS classes
- Rename miku.svg to micu.svg to match icon key
- Add partnerPromotion.micu i18n keys for zh, en, ja locales
- Add Ucloud provider presets with modelverse.cn API endpoint
- Register ucloud icon SVG in icon index with namespaced gradient IDs
- Add partnerPromotion.ucloud i18n keys for zh, en, ja locales
Add enableFailoverToggle setting to control failover toggle visibility
on the main page, decoupled from proxy takeover state. First-time
enable shows a ConfirmDialog (same pattern as proxy toggle). The toggle
row is placed in the Auto Failover accordion section in settings.
Common config snippets are now dynamically overlaid when writing live
files, rather than being pre-merged into provider snapshots at edit time.
This ensures that updating a snippet immediately takes effect for the
current provider and automatically propagates to other providers on
their next switch.
Key changes:
- Add write_live_with_common_config() overlay pipeline
- Strip common config from live before backfilling provider snapshots
- Normalize provider snapshots on save to keep them snippet-free
- Add explicit commonConfigEnabled flag in ProviderMeta (Option<bool>)
- Migrate legacy providers on snippet save (infer flag from subset check)
- Add Codex TOML snippet validation in set_common_config_snippet
- Stabilize onConfigChange callbacks with useCallback in ProviderForm
- Add 69 missing translation keys to zh/en/ja (usage, proxy, sessionManager,
deeplink, codexConfig, openclaw, circuitBreaker, streamCheck, etc.)
- Replace 15 hardcoded Chinese strings in CircuitBreakerConfigPanel with t() calls
- Fix ColorPicker component to use i18n for default label
- Add i18n interpolation params to ProxyToggle tooltip translations
- All three language files synchronized at 1838 keys
Separate protocol version from database compatibility version in WebDAV
sync paths. Upload writes to v2/db-v6/<profile>, download falls back to
legacy v2/<profile> when current path has no data. Extend manifest with
optional dbCompatVersion field and add legacy layout detection to UI.
- Add usage_daily_rollups table (schema v6) to aggregate proxy request
logs into daily summaries, reducing query overhead for statistics
- Add rollup_and_prune DAO that aggregates old detail logs (>N days)
into rollup rows and deletes the originals
- Update all usage stats queries to UNION detail logs with rollup data
- Introduce incremental auto-vacuum for SQLite, with startup and
periodic cleanup of old stream_check_logs and request log rollups
- Split backup export/import into full vs sync variants: WebDAV sync
now skips local-only table data (proxy_request_logs,
stream_check_logs, provider_health, proxy_live_backup,
usage_daily_rollups) while preserving them on import
- Add enable_logging guard to skip request log writes when disabled
- Apply cargo fmt formatting fixes across multiple modules
Rewrite tool call handling in streaming format conversion to properly
track multiple concurrent tool blocks with independent Anthropic content
indices. Fix block interleaving (thinking/text/tool_use) with correct
content_block_start/stop events, buffer tool arguments until both id and
name are available, and add tool result message conversion in transform.
- Extract shared map_responses_stop_reason and build_anthropic_usage_from_responses into transform_responses.rs as pub(crate)
- Align cache token extraction priority: OpenAI nested details as fallback, direct Anthropic fields as override
- Extract resolve_content_index helper to eliminate 3x copy-paste in streaming_responses.rs
- Add streaming reasoning/thinking event handlers (response.reasoning.delta/done)
- Add explanatory comment to transform_response heuristic detection
- Add openai_responses to api_format doc comment and needs_transform test
- Add explicit no-op match arms for lifecycle events
- Add promptCacheKey to TS ProviderMeta type
- Update toast i18n key to be generic for both OpenAI formats (zh/en/ja)
Support Anthropic ↔ OpenAI Responses API format conversion alongside existing
Chat Completions conversion. The Responses API uses a flat input/output structure
with lifted function_call/function_call_output items and named SSE lifecycle events.
* fix:Add a new vendor page, API endpoint, and model name. Fix the bug where, after entering characters, line breaks cannot be fully deleted.
* fix: add missing i18n key codexConfig.modelNameHint for zh/en/ja
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix: skills count not displaying when adding
* fix: get real skill count by awaiting discovery after adding repo
The previous approach computed count before discovery, so it was always 0.
Now we await refetchDiscoverable() after the mutation, then filter the
fresh skills list to get the actual count for the toast message.
Also reverts the SkillRepo.count field — keep the interface clean since
count is a transient UI concern, not a data model property.
---------
Co-authored-by: Jason <farion1231@gmail.com>
The text-based approach (string append + substring matching) failed to
detect already-merged snippets when config.toml was reformatted by
external tools (MCP sync, Codex CLI). Replace with smol-toml parse/
stringify + existing deepMerge/isSubset/deepRemove for correct
structural operations. Falls back to text matching on parse failure.
- Add useOpenClawModelOptions hook to aggregate models from all configured OpenClaw providers
- Replace read-only primary model display with a searchable Select dropdown
- Replace comma-separated fallback text input with add/remove Select rows
- Filter out already-selected models from fallback options
- Show "(not configured)" marker for values whose provider has been deleted
- Unify terminology: rename "主模型/Primary Model" to "默认模型/Default Model"
- Add Primary/Fallback badge to each model card in OpenClaw form
- Update modelsHint to explain model ordering semantics
- Reorder 11 aggregator/third-party presets to put Opus first
The status code from a simple GET request reflects route matching,
not actual endpoint availability. Users only need latency and
reachability info, which the latency number already conveys.
Show an informational dialog when users first click the health check
button, explaining its limitations (OAuth providers, relay services,
Bedrock). The dialog persists the confirmation in settings so it only
appears once per device.
Stream Check always used Anthropic Messages API format, causing false
failures for providers with api_format="openai_chat" (e.g. NVIDIA).
Now detects api_format from provider meta/settings_config and uses
the correct endpoint (/v1/chat/completions) and headers accordingly.
Re-enable the stream check feature that was hidden in v3.11.0.
All backend code, database schema, and i18n keys were preserved;
only the frontend UI needed uncommenting across 4 files.
OpenCode and OpenClaw are excluded as the backend does not support them.
Reorganize docs/user-manual/ from flat structure to language subdirectories
(zh/, en/, ja/) with shared assets/. Move existing Chinese docs into zh/,
fix image paths, add multilingual navigation README, and translate all 23
markdown files (~4500 lines each) to English and Japanese.
During app startup, iterate all app types and extract non-provider-specific
config fields from live configuration files into the database. This runs
only when no snippet exists yet for a given app type, enabling incremental
extraction as new apps are configured.
The i18next t() calls for proxy.takeover.enabled/disabled were missing
the `app` interpolation parameter, causing {{app}} placeholders in
translation strings to render literally instead of showing the app name.
After moving ProxyToggle/FailoverToggle outside toolbarRef, the flex-1
class was accidentally left only on the outer wrapper. Without flex-1,
toolbarRef.clientWidth reflects content width instead of available space,
causing useAutoCompact's exit condition to never trigger.
Pass "system" to set_window_theme instead of explicitly detecting dark/light,
so Tauri uses window.set_theme(None) and the WebView's prefers-color-scheme
media query stays in sync with the real OS theme.
Move the proxy on/off switch from the accordion header into the panel
content area, placing it right above the app takeover section. This
ensures users see the takeover options immediately after enabling the
proxy, preventing the common pitfall of running the proxy without
actually taking over any app.
- Simplify accordion trigger to standard style with Badge only
- Add AnimatePresence animation for takeover section reveal
- Remove duplicate takeover switches from running info card
- Update stoppedDescription i18n to reference "above toggle"
- Add proxy.takeover.hint key in zh/en/ja
Revert the partial key-field merging refactoring introduced in 992dda5c,
along with two dependent commits (24fa8a18, 87604b18) that referenced
the now-removed ClaudeQuickToggles component.
The whitelist-based partial merge approach had critical issues:
- Non-whitelisted custom fields were lost during provider switching
- Backfill permanently stripped non-key fields from the database
- Whitelist required constant maintenance to track upstream changes
This restores the proven "full config overwrite + Common Config Snippet"
architecture where each provider stores its complete configuration and
shared settings are managed via a separate snippet mechanism.
Reverted commits:
- 24fa8a18: context-aware JSON editor hint + hide quick toggles
- 87604b18: hide ClaudeQuickToggles when creating
- 992dda5c: partial key-field merging refactoring
Restored:
- Full config snapshot write (write_live_snapshot) for Claude/Codex/Gemini
- Full config backfill (settings_config = live_config)
- Common Config Snippet UI and backend commands
- 6 frontend components/hooks for common config editing
- configApi barrel export and DB snippet methods
Removed:
- ClaudeQuickToggles component
- write_live_partial / backfill_key_fields / patch_claude_live
- All KEY_FIELDS constants
Previously OpenCode and OpenClaw auto-imported providers from live config
on app startup, which could confuse users. Now they follow the same
pattern as Claude/Codex/Gemini: manual import via the empty state button.
Expand the partial key-field merging section with Before/After explanation
and migration guide. Mark it as a breaking change in Highlights and Notes
sections across all three languages (zh/en/ja) and CHANGELOG.md.
- Update version numbers in package.json, Cargo.toml, tauri.conf.json
- Add CHANGELOG.md entry for v3.11.0
- Add trilingual release notes (zh/en/ja)
- Update user manual version info
backup_database_file() returning Ok(None) was silently resolved as null
on the frontend, bypassing try/catch and showing a success toast without
actually creating a backup file. Now None is converted to an explicit
Err so the frontend correctly displays an error toast.
The hasApiKeyField() gate added for Bedrock AKSK/API Key distinction
was incorrectly hiding the API Key input for all new providers with
empty env config. Scope the gate to cloud_provider category only.
JSON does not support duplicate keys — the second `openclaw` object was
silently overwriting the first, causing all provider form field translations
(providerKey, apiProtocol, baseUrl, models, etc.) to be lost at runtime.
OMO and OMO Slim are OpenCode plugins, not standalone apps — users
should be able to fully remove them. Remove the count-based guard that
prevented deleting the last active provider, and clean up the now-unused
provider-count API surface across the full stack.
Each OMO provider now stores its complete configuration directly in
settings_config.otherFields instead of relying on a shared OmoGlobalConfig
merged at write time. This simplifies the data flow from a 4-tuple
(agents, categories, otherFields, useCommonConfig) to a 3-tuple and
eliminates an entire DB table, two Tauri commands, and ~1700 lines of
merge/sync code across frontend and backend.
Backend:
- Delete database/dao/omo.rs (OmoGlobalConfig struct + get/save methods)
- Remove get/set_config_snippet from settings DAO
- Remove get/set_common_config_snippet Tauri commands
- Replace merge_config() with build_config() in services/omo.rs
- Simplify OmoVariant (remove config_key, known_keys)
- Simplify import_from_local and build_local_file_data
- Rewrite all OMO service tests
Frontend:
- Delete OmoCommonConfigEditor.tsx and OmoGlobalConfigFields.tsx
- Delete src/lib/api/config.ts
- Remove OmoGlobalConfig type and merge preview functions
- Remove useGlobalConfig/useSaveGlobalConfig query hooks
- Simplify useOmoDraftState (remove all common config state)
- Replace OmoCommonConfigEditor with read-only JsonEditor preview
- Clean i18n keys (zh/en/ja)
When activating an OMO provider, deactivate all OMO Slim providers
in the same transaction and delete the Slim config file, and vice
versa. This prevents both plugin variants from being active
simultaneously.
OMO Slim queries (["omo-slim", ...]) were not invalidated alongside
OMO queries, causing stale UI state when switching/adding/deleting
OMO Slim providers.
Regular agents & categories: align with oh-my-opencode model-requirements.ts
fallback chains (oracle→gpt-5.2, librarian→gemini-3-flash, etc.)
Slim agents: derive from Regular's design philosophy — match each agent
to its functional counterpart's first-choice model instead of using
the outdated Slim defaults. Also fix provider/model format to pure
model IDs for suffix matching compatibility.
- Parallelize 5 provider scans using std::thread::scope
- Add read_head_tail_lines() to read only first 10 + last 30 lines from JSONL files, skipping potentially large middle sections
- Cache Codex UUID regex with LazyLock to avoid repeated compilation
- Skip expensive I/O in OpenCode when session title is already available
The hint text and ClaudeQuickToggles were misleading when editing
non-current providers or creating new ones, since the editor only
contains a config snippet rather than the full live settings.json.
The quick toggles (hide AI attribution, extended thinking, teammates
mode) patch the live config of the currently active provider, which
is incorrect during provider creation. Only show them in edit mode.
Update Sonnet and Opus model IDs/names to 4.6 across Claude, OpenClaw,
and OpenCode provider preset configurations. OPENCODE_PRESET_MODEL_VARIANTS
(SDK model catalog) is intentionally left unchanged.
Add SSAI Code as a partner provider across all five apps with endpoint,
API key URL, and partner promotion config. Rename brand from SSSAiCode
to SSAI Code. Rename sssaicoding.svg to sssaicode.svg and register icon.
Add trilingual promotion text for $10 bonus credit. Add missing models
arrays in OpenClaw presets for CrazyRouter and SSAI Code.
Add CrazyRouter as a partner provider across all five apps with endpoint,
API key URL, and partner promotion config. Add trilingual promotion text
for the 30% bonus credit offer. Register aicoding and crazyrouter icons
in the icon index. Fix indentation in openclawProviderPresets.
Add AICoding as a partner provider across all five apps (Claude, Codex,
Gemini, OpenClaw, OpenCode) with endpoint, API key URL, and partner
promotion configuration. Add trilingual (zh/en/ja) promotion text for
the first top-up discount.
Several code paths only checked for "omo" category but missed "omo-slim",
causing OMO Slim providers to be treated as regular OpenCode providers
(triggering invalid write_live_snapshot, requiring manual provider key,
and showing wrong form fields).
* Add AWS Bedrock provider integration design document
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add AWS Bedrock provider implementation plan
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update implementation plan: add OpenCode Bedrock support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add cloud_provider category to ProviderCategory type
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add AWS Bedrock (AKSK) Claude Code provider preset with tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add AWS Bedrock (API Key) Claude Code provider preset with tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add AWS Bedrock OpenCode provider preset with @ai-sdk/amazon-bedrock
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add AWS Bedrock provider feature summary for PR
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove internal planning documents
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add AWS Bedrock support to README (EN/ZH/JA)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add AWS Bedrock UI merge design document
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add AWS Bedrock UI merge implementation plan
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: skip optional template values in validation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: support isSecret template fields and hide base URL for Bedrock
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Bedrock validation, cleanup, and isBedrock prop in ProviderForm
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: extend TemplateValueConfig and merge Bedrock presets
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: mask Bedrock API Key as secret and support GovCloud regions
- Add isSecret: true to BEDROCK_API_KEY template value
- Update region regex to support multi-segment regions (us-gov-west-1)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: replace AWS icon with updated logo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: replace AWS icon with updated logo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: replace AWS icon with new PNG image
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address code review findings
- Fix AWS icon: use SVG with embedded <image> instead of raw <img> tag
- Hide duplicate ApiKeySection for Bedrock (auth via template fields only)
- Guard settingsConfig cleanup against unresolved template placeholders
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove planning documents
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: address PR review - split Bedrock into two presets, restore SVG icon
Based on maintainer review feedback on PR #1047:
1. Split merged "AWS Bedrock" back into two separate presets:
- "AWS Bedrock (AKSK)": uses AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY
- "AWS Bedrock (API Key)": uses top-level apiKey field via standard UI input
2. Restore aws.svg to pure vector SVG (was PNG-in-SVG)
3. Remove all Bedrock-specific logic from shared components:
- Remove isBedrock prop from ClaudeFormFields
- Remove Bedrock validation/cleanup blocks from ProviderForm
- Remove optional/isSecret from TemplateValueConfig
- Remove optional skip from useTemplateValues
4. Add cloud_provider category handling:
- Skip API Key/Base URL required validation
- Hide Speed Test and Base URL for cloud_provider
- Hide API format selector for cloud_provider (always Anthropic)
- Show API Key input only when config has apiKey field
5. Fix providerConfigUtils to support top-level apiKey:
- getApiKeyFromConfig: check config.apiKey before env fields
- setApiKeyInConfig: write to config.apiKey when present
- hasApiKeyField: detect top-level apiKey property
6. Add OpenClaw Bedrock preset (bedrock-converse-stream protocol)
7. Update model IDs:
- Sonnet: global.anthropic.claude-sonnet-4-6
- Opus: global.anthropic.claude-opus-4-6-v1
- Haiku: global.anthropic.claude-haiku-4-5-20251001-v1:0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(test): align Bedrock API Key test assertions with preset implementation
The API Key preset was refactored to use standard UI input (apiKey: "")
instead of template variables, but the tests were not updated accordingly.
---------
Co-authored-by: root <root@ip-10-0-11-189.ap-northeast-1.compute.internal>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
Add open_workspace_directory Tauri command to open workspace/memory dirs
in the system file manager. Rename dailyMemory.createToday across all locales.
Add backend search command that performs case-insensitive matching
across all daily memory files, supporting both date and content queries.
Frontend includes animated search bar (⌘F), debounced input, snippet
display with match count badge, and search state preservation across
edits.
* refactor(provider): switch from full config overwrite to partial key-field merging
Replace the provider switching mechanism for Claude/Codex/Gemini from
full settings_config overwrite to partial key-field replacement, preserving
user's non-provider settings (plugins, MCP, permissions, etc.) across switches.
- Add write_live_partial() with per-app implementations for Claude (JSON env
merge), Codex (auth replace + TOML partial merge), and Gemini (env merge)
- Add backfill_key_fields() to extract only provider-specific fields when
saving live config back to provider entries
- Update switch_normal, sync_current_to_live, add, update to use partial merge
- Remove common config snippet feature for Claude/Codex/Gemini (no longer
needed with partial merging); preserve OMO common config
- Delete 6 frontend files (3 components + 3 hooks), clean up 11 modified files
- Remove backend extract_common_config_* methods, 3 Tauri commands,
CommonConfigSnippets struct, and related migration code
- Update integration tests to validate key-field-only backfill behavior
* refactor(cleanup): remove dead code and redundant MCP sync after partial-merge refactor
- Remove ConfigService legacy full-overwrite sync methods (~150 lines)
- Remove redundant McpService::sync_all_enabled from switch_normal
- Switch proxy fallback recovery from write_live_snapshot to write_live_partial
- Remove dead ProviderService::write_gemini_live wrapper
- Update tests to reflect partial-merge behavior (MCP preserved, not re-synced)
* feat(claude): add Quick Toggles for common Claude Code preferences
Add checkbox toggles for hideAttribution, alwaysThinking, and
enableTeammates that write directly to the live settings file via
RFC 7396 JSON Merge Patch. Mirror changes to the form editor using
form.watch for reactive updates.
* fix(provider): add missing key fields to partial-merge constants
Add provider-specific fields verified against official docs to prevent
key residue or loss during provider switching:
- Claude: CLAUDE_CODE_SUBAGENT_MODEL (env), model (top-level)
- Codex: review_model, plan_mode_reasoning_effort
- Gemini: GOOGLE_API_KEY (official alternative to GEMINI_API_KEY)
* fix(provider): expand partial-merge key fields for Bedrock, Vertex, Foundry and behavior settings
Add missing env/top-level fields to CLAUDE_KEY_ENV_FIELDS and
CLAUDE_KEY_TOP_LEVEL so that provider switching correctly replaces
(and clears) credentials and flags for AWS Bedrock, Google Vertex AI,
Microsoft Foundry, and provider behavior overrides like max output
tokens and prompt caching.
* feat(provider): add auth field selector for Claude providers (AUTH_TOKEN / API_KEY)
Allow users to choose between ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY
when creating or editing custom Claude providers, persisted in meta.apiKeyField.
* refactor(preset): remove AiHubMix hardcoded API_KEY in favor of generic auth selector
AiHubMix was the only preset that hardcoded ANTHROPIC_API_KEY before the
generic auth field selector was introduced. Now that users can freely
choose between AUTH_TOKEN and API_KEY via the UI, remove the special-case
and default AiHubMix to the standard ANTHROPIC_AUTH_TOKEN.
Extract backup & restore into a standalone AccordionItem in Advanced settings.
Add configurable auto-backup interval (disabled/6h/12h/24h/48h/7d) and retention
count (3-50) via settings. Add per-backup rename with inline editing UI.
Four improvements to the database backup mechanism:
1. Auto backup before schema migration - creates a snapshot when
upgrading from an older database version, providing a safety net
beyond the existing SAVEPOINT rollback mechanism.
2. Periodic startup backup - checks on app launch whether the latest
backup is older than 24 hours and creates a new one if needed,
ensuring all users have recent backups regardless of usage patterns.
3. Backfill failure notification - switch now returns SwitchResult with
warnings instead of silently ignoring backfill errors, so users are
informed when their manual config changes may not have been saved.
4. Backup management UI - new BackupListSection in Settings > Data
Management showing all backup snapshots with restore capability,
including a confirmation dialog and automatic safety backup before
restore.
Prevent accidental activation of advanced features by showing a one-time
info dialog. Once confirmed, the flag is persisted in settings.json and
the dialog never appears again.
- Proxy: confirmation when toggling proxy server ON for the first time
- Usage: confirmation when enabling usage query inside UsageScriptModal
- Enhanced ConfirmDialog with "info" variant (blue icon + default button)
- Added i18n translations for zh, en, ja
Replace conditional rendering with always-rendered span driven by CSS
max-width + opacity animation. Add time-lock in useAutoCompact to prevent
ResizeObserver flicker during expand animation.
The toolbar wrapper's fixed h-[32px] was smaller than AppSwitcher's
natural 40px height (32px buttons + 8px padding), causing visual
clipping. Removed the constraint and let flex layout handle sizing
within the 64px header.
Extract proxy-related accordion items (Local Proxy, Failover, Rectifier,
Global Outbound Proxy) into a dedicated Proxy tab via ProxyTabContent
component. Move Pricing config panel to UsageDashboard as a collapsible
accordion. This reduces SettingsPage from ~716 to ~426 lines and improves
settings discoverability with a 5-tab layout: General | Proxy | Advanced |
Usage | About.
* feat: more granular local environment checks
* refactor: improve PR #870 with i18n, shadcn Select, and testable helpers
- Extract is_valid_shell, is_valid_shell_flag, default_flag_for_shell
to module-level #[cfg(windows)] functions for testability
- Add unit tests for extracted helper functions
- Replace native <select> with shadcn/ui Select components
- Extract env badge ternary to ENV_BADGE_CONFIG Record lookup
- Add i18n keys for env badges and WSL selectors (zh/en/ja)
- Unify initial useEffect load path with loadAllToolVersions()
* fix: prevent useEffect re-firing on wslShellByTool changes
The useEffect that loads initial tool versions depended on
loadAllToolVersions, which in turn depended on wslShellByTool.
This caused a full re-fetch of all 4 tools every time the user
changed a WSL shell or flag, racing with the single-tool refresh.
Fix: use empty deps [] since this is a mount-only effect. The
refresh button and shell/flag handlers cover subsequent updates.
---------
Co-authored-by: Jason <farion1231@gmail.com>
Remove the startup loop in lib.rs that auto-imported default providers
for Claude/Codex/Gemini. Move config snippet extraction logic into the
import_default_config command so it works when triggered manually.
Add an "Import Current Config" button to ProviderEmptyState, wired via
useMutation in ProviderList (shown only for standard apps). Update i18n
keys (zh/en/ja) with new button labels and revised empty state text.
The Auto (Failover) toggle in the system tray is no longer shown.
The feature remains fully functional via the Settings page.
All related backend code (handle_auto_click, AUTO_SUFFIX, etc.)
is preserved for easy re-enablement.
The primary model field in the Agents defaults panel now displays as
read-only, eliminating the duplicate edit entry with the "Set as Default
Model" button on provider cards. Fallback models and runtime parameters
remain editable.
- Sort daily memory list by filename (YYYY-MM-DD.md) instead of mtime
so editing an older file no longer bumps it to the top
- Move daily memory card inline with workspace MD file buttons
- Shorten card description across all locales
- Defer file creation until user actually saves (no empty file on create)
The stream check feature is unreliable due to diverse provider request
formats. Comment out the model test config UI in settings page, provider
advanced config, and the test button in provider actions. Backend code
and i18n keys are preserved for future restoration.
New users often accidentally trigger ProxyToggle/FailoverToggle on the
main page. Add a settings toggle (default off) so the proxy controls
only appear when explicitly enabled. The proxy service start/stop in
settings remains independent of this visibility flag.
Introduce OmoVariant struct with STANDARD/SLIM constants to eliminate
~250 lines of copy-pasted code across DAO, service, commands, and
frontend layers. Adding a new OMO variant now requires only a single
const declaration instead of duplicating ~400 lines.
Implement full OMO Slim profile management to align with ai-toolbox:
- Backend: Slim service methods, DAO, Tauri commands, plugin conflict handling
- Frontend: types, API, query hooks, form integration with isSlim parameterization
- Slim variant: 6 agents (no categories), separate config file and plugin name
- Mutual exclusion: standard OMO and Slim cannot coexist as plugins
- i18n: zh/en/ja translations for all Slim agent descriptions
Split ProviderForm.tsx (2227 → 1526 lines) by extracting:
- opencodeFormUtils.ts: pure functions and default config constants
- useOmoModelSource: OMO model source collection from OpenCode providers
- useOpencodeFormState: OpenCode provider form state and handlers
- useOmoDraftState: OMO profile draft editing state
- useOpenclawFormState: OpenClaw provider form state and handlers
Replace React Fragment with a div using space-y-6 to add proper
vertical spacing between the app config directory and directory
override sections in Settings > Advanced > Directory Settings.
Fixes Nvidia provider and other providers using apiFormat="openai_chat".
The ClaudeAdapter::build_url() method was incorrectly adding ?beta=true
to both /v1/messages and /v1/chat/completions endpoints. This caused
the Nvidia provider to fail because:
1. Nvidia uses apiFormat="openai_chat"
2. Requests are transformed to OpenAI format and sent to /v1/chat/completions
3. The URL gets ?beta=true appended (Anthropic-specific parameter)
4. Nvidia's API rejects requests with this parameter
Fix:
- Only add ?beta=true to /v1/messages endpoint
- Exclude /v1/chat/completions from getting this parameter
Tested:
- Anthropic /v1/messages still gets ?beta=true ✓
- OpenAI Chat Completions /v1/chat/completions does NOT get ?beta=true ✓
- All 13 Claude adapter tests pass ✓
Co-authored-by: jnorthrup <jnorthrup@example.com>
Add 3 quick-toggle checkboxes above the JSON editor in Claude provider settings:
- Hide AI Attribution (sets attribution.commit/pr to empty string)
- Extended Thinking (sets alwaysThinkingEnabled to true)
- Teammates Mode (sets env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS to "1")
Uses local state to keep toggles and JsonEditor in sync without
triggering ProviderForm re-renders. Includes i18n for zh/en/ja.
Co-authored-by: Jason <farion1231@gmail.com>
* fix(skill): resolve symlinks in ZIP extraction for GitHub repos (#1001)
- detect symlink entries via is_symlink() during ZIP extraction and collect target paths
- add resolve_symlinks_in_dir() to copy symlink target content into link location
- canonicalize base_dir to fix macOS /tmp → /private/tmp path comparison issue
- add path traversal safety check to block symlinks pointing outside repo boundary
- apply symlink resolution to both download_and_extract and extract_local_zip paths
Closes https://github.com/farion1231/cc-switch/issues/1001
* fix(skill): change search to match name and repo instead of description
* feat(skill): support importing skills from ~/.agents/skills/ directory
- Scan ~/.agents/skills/ in scan_unmanaged() for skill discovery
- Parse ~/.agents/.skill-lock.json to extract repo owner/name metadata
- Auto-add discovered repos to skill_repos management on import
- Add path field to UnmanagedSkill to show discovered location in UI
Closes#980
* fix(skill): use metadata name or ZIP filename for root-level SKILL.md imports (#1000)
When a ZIP contains SKILL.md at the root without a wrapper directory,
the install name was derived from the temp directory name (e.g. .tmpDZKGpF).
Now falls back to SKILL.md frontmatter name, then ZIP filename stem.
* feat(skill): scan ~/.cc-switch/skills/ for unmanaged skill discovery and import
* refactor(skill): unify scan/import logic with lock file skillPath and repo saving
- Deduplicate scan_unmanaged and import_from_apps using shared source list
- Replace hand-written AppType match with as_str() and AppType::all()
- Extract read_skill_name_desc, build_repo_info_from_lock, save_repos_from_lock helpers
- Add SkillApps::from_labels for building enable state from source labels
- Parse skillPath from .skill-lock.json for correct readme URLs
- Save skill repos to skill_repos table in both import and migration paths
* fix(skill): resolve symlink and path traversal issues in ZIP skill import
* fix(skill): separate source path validation and add canonicalization for symlink safety
Anthropic SDK auto-appends /v1 to baseUrl, so including it in the
preset causes double-path (/v1/v1/messages). Affects AiHubMix, DMXAPI,
PackyCode, Cubence, and AIGoCode.
Keep only model ID and display name visible by default,
collapse context window, max tokens, reasoning, and cost
fields into the advanced options section.
- Add 25 missing i18n keys for OpenClawFormFields in all 3 locales (P0)
- Replace key={index} with stable crypto.randomUUID() keys in EnvPanel,
ToolsPanel, and OpenClawFormFields to prevent list state bugs (P1)
- Exclude openclaw from ProxyToggle/FailoverToggle in App.tsx (P1)
- Add merge_additive_config() for openclaw/opencode deep link imports (P1)
- Normalize serde(flatten) field naming to `extra` + HashMap (P2)
- Add directory existence check in remove_openclaw_provider_from_live (P2)
- Remove dead code in import_default_config and openclaw API methods (P2)
- Add duplicate key validation in EnvPanel before save (P2)
- Add openclawConfigDir to Settings type (P2)
- Add staleTime to OpenClaw query hooks (P3)
- Fix type-unsafe delete via destructuring in mutations.ts (P3)
Centralize query keys and extract reusable hooks (useOpenClaw.ts),
replacing manual useState/useEffect load/save patterns in Env, Tools,
and AgentsDefaults panels for consistency with MCP/Skills modules.
- Fix EnvPanel visibleKeys using entry key name instead of array index
to prevent visibility state corruption after deletion
- Add NaN guard in AgentsDefaultsPanel numeric field parsing
- Validate provider id and models before importing from live config
- Upgrade import failure log level from debug to warn for OpenCode/OpenClaw
Add a dedicated panel to read/write OpenClaw's 6 workspace bootstrap files
(AGENTS.md, SOUL.md, USER.md, IDENTITY.md, TOOLS.md, MEMORY.md) directly
from ~/.openclaw/workspace/, with a whitelist-secured backend and Markdown
editor UI. Also fix prompt auto-import missing OpenCode/OpenClaw and the
PromptFormPanel filenameMap type exclusion.
Expand McpApps interface, APP_IDS, APP_ICON_MAP, enabledCounts
initializers, and test mock data to include the openclaw key,
resolving TypeScript errors after AppId union was extended.
Tauri IPC calls are in-process (microsecond latency), so the 5-minute
staleTime and disabled refetchOnWindowFocus from web app templates are
counterproductive. Set staleTime to 0 and enable refetchOnWindowFocus
so external config changes are reflected immediately.
- Add "Enable as Default" button to OpenClaw provider cards
- Place enable button before add/remove button for better UX
- Disable remove button when provider is set as default
- Add sessionsApi re-export from api barrel file to fix white screen
- Add i18n keys for default model feature (zh/en/ja)
- Fix Opus pricing from $15/$75 (Opus 4 price) to $5/$25 in openclaw presets
- Upgrade model ID from claude-opus-4-5-20251101 to claude-opus-4-6
- Update all suggestedDefaults references (fallbacks + modelCatalog)
- Apply same model upgrade in opencode presets (7 providers)
When adding an OpenClaw provider with suggestedDefaults, automatically
merge its model catalog into the allowlist and set the default model
if not already configured.
Add cache invalidation for openclawLiveProviderIds in useSwitchProviderMutation
to ensure button state updates correctly after adding/removing providers.
Also fix toast message to show "已添加到配置" for OpenClaw (same as OpenCode).
- Add providerKey input field in ProviderForm for OpenClaw
- Implement duplicate key detection by querying existing providers
- Add format validation (lowercase letters, numbers, hyphens only)
- Disable field in edit mode (key cannot be changed after creation)
- Update mutations.ts to support OpenClaw providerKey
- Add i18n translations for zh/en/ja
- Add getOpenClawLiveProviderIds() API for querying live config
- Update ProviderList to query OpenClaw live IDs
- Rename isOpenCodeMode to isAdditiveMode (covers OpenCode + OpenClaw)
- Update ProviderCard shouldAutoQuery and isActiveProvider logic
- Update App.tsx onRemoveFromConfig and invalidateQueries for OpenClaw
- Add types for default model config (primary + fallbacks)
- Add types for model catalog/allowlist with aliases
- Extend OpenClawModelEntry with cost and contextWindow fields
- Add Tauri commands: get/set_openclaw_default_model, get/set_openclaw_model_catalog
- Create frontend API (src/lib/api/openclaw.ts)
- Add suggestedDefaults to provider presets with model metadata
- Add i18n translations (zh/en/ja) for openclawConfig.*
OpenClaw only needs provider management functionality, not MCP, Skills,
or Prompts features. This commit removes the incorrectly added support:
- Revert SCHEMA_VERSION from 6 to 5 (remove v5->v6 migration)
- Remove enabled_openclaw field from mcp_servers and skills tables
- Remove openclaw field from McpApps and SkillApps structs
- Update DAO queries to exclude enabled_openclaw column
- Fix frontend components and types to exclude openclaw from MCP/Prompts
- Update all related test files
- Add "openclaw": "OpenClaw" to apps translations in zh.json
- Add "openclaw": "OpenClaw" to apps translations in en.json
- Add "openclaw": "OpenClaw" to apps translations in ja.json
- Update App.tsx with openclaw visibility and skills fallback
- Add OpenClaw to AppSwitcher icon and display name maps
- Update McpFormModal with openclaw in enabled apps
- Update PromptFormModal/Panel with openclaw filename map
- Update EndpointSpeedTest with openclaw timeout
- Update AppVisibilitySettings with openclaw toggle
- Update test state with openclaw defaults
- Create openclawProviderPresets.ts with provider templates
- Include Chinese officials (DeepSeek, Zhipu, Qwen, Kimi, MiniMax)
- Include aggregators (AiHubMix, DMXAPI, OpenRouter, ModelScope)
- Include third party partners (PackyCode, Cubence, AIGoCode)
- Include OpenAI Compatible custom template
- Add OpenClawProviderConfig and OpenClawModel interfaces
- Add openclaw to McpApps, VisibleApps, ProxyTakeoverStatus
- Add "openclaw" to AppId union type
- Add openclaw to SkillApps interface
- Register openclaw_config module in lib.rs
- Add OpenClaw commands and provider import on startup
- Add OpenClaw branches to config and provider commands
- Add build_openclaw_settings() for deeplink imports
- Update MCP handlers with openclaw field in McpApps
- Add OpenClaw prompt file path
- Add OpenClaw branches to proxy service (not supported)
- Add OpenClaw to MCP service (skip sync, MCP still in development)
- Add OpenClaw skills directory path
- Update ProxyTakeoverStatus with openclaw field
- Add OpenClaw to stream check (not supported)
- Add import_openclaw_providers_from_live() function
- Add remove_openclaw_provider_from_live() function
- Update write_live_snapshot() for OpenClaw
- Add openclaw fields to VisibleApps and AppSettings
- Add get_openclaw_override_dir() function
On some Linux systems with AMD GPUs (e.g., AMD Cezanne/Radeon Vega),
WebKitGTK's GPU process fails to initialize EGL, causing the app to
show a white screen on startup.
Root cause: `amdgpu_query_info(ACCEL_WORKING)` returns EACCES (-13),
which causes EGL display creation to fail with EGL_BAD_PARAMETER,
crashing the GPU process.
Fix: Use WebKitGTK's `set_hardware_acceleration_policy(Never)` API to
disable GPU acceleration on Linux, falling back to software rendering.
This is applied at startup via Tauri's `with_webview` API.
Co-authored-by: Naozhong AI <oursnoah@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(omo): use lowercase keys for builtin agent definitions
OMO config schema expects all agent keys to be lowercase.
Updated OMO_BUILTIN_AGENTS keys (Sisyphus → sisyphus, Hephaestus →
hephaestus, etc.) and aligned Rust test fixtures accordingly.
* feat(omo): add i18n support and tooltips for agent/category descriptions
* feat(omo): add preset model variants for thinking level support
Add OPENCODE_PRESET_MODEL_VARIANTS constant with variant definitions
for Google, OpenAI, and Anthropic models. The omoModelVariantsMap
builder now falls back to presets when config-defined variants are
absent, enabling the variant selector for supported models.
* feat(omo): replace model select with searchable combobox and improve fallback handling
* feat(omo): enrich preset model defaults and metadata fallback
* fix(omo): preserve custom fields and align otherFields import/validation
* fix: resolve omo clippy warnings and include app update
Session manager backend only supports Claude and Codex providers.
Hide the nav button for Gemini/OpenCode with animated transition,
and auto-fallback to providers view when switching apps.
Use the actual branch returned by download_repo instead of the
configured branch, fixing 404s when repos default to master but
the URL was hardcoded to main. Also switch URL format from /tree/
to /blob/ and always point to the SKILL.md file.
Closesfarion1231/cc-switch#968
* feat(omo): integrate Oh My OpenCode profile management into Provider system
Adds full-stack OMO support: backend config read/write/import, OMO-specific
provider CRUD with exclusive switching, frontend profile editor with
agent/category/model configuration, global config management, and i18n support.
* feat(omo): add model/variant dropdowns from enabled providers
Replace model text inputs with Select dropdowns sourced from enabled
OpenCode providers, add thinking-level variant selection, and prevent
auto-enabling newly added OMO providers.
* fix(omo): use standard provider action styles for OMO switch button
* fix(omo): replace hardcoded isZh strings with proper i18n t() calls
- replace Qwen Coder preset with Bailian for Claude and OpenCode
- add Bailian icon asset and metadata mapping
- remove Bailian preset default model values
- rename Claude preset Kimi k2 to Kimi
- Add claude-opus-4-6-20260206 pricing (same as opus-4-5)
- Add gpt-5.3-codex series pricing (same as gpt-5.2-codex)
- Change seed_model_pricing to INSERT OR IGNORE for incremental upsert
- Remove count==0 guard in ensure_model_pricing_seeded so new models
are appended on every startup without overwriting user customizations
* style: format code and apply clippy lint fixes
* feat(usage): enhance dashboard with auto-refresh control and robust formatting
- Add configurable auto-refresh interval toggle (off/5s/10s/30s/60s) to usage dashboard
- Extract shared format utilities (fmtUsd, fmtInt, parseFiniteNumber, getLocaleFromLanguage)
- Refactor request log time filtering to rolling vs fixed mode with validation
- Use stable serializable query keys instead of filter objects
- Handle NaN/Infinity safely in number formatting across all usage components
- Use RFC 3339 date format in backend trend data
- Remove terminal selector from Session Manager page
- Backend now reads terminal preference from global settings
- Add terminal name mapping (iterm2 → iterm) for compatibility
- Add WezTerm support to macOS terminal options
- Add WezTerm translations for zh/en/ja
- Add i18n keys for relative time (justNow, minutesAgo, hoursAgo, daysAgo)
- Add i18n keys for role labels (roleUser, roleSystem, roleTool)
- Add i18n keys for UI elements (tocTitle, searchSessions, clickToCopyPath)
- Update formatRelativeTime and getRoleLabel to accept t function
- Add useTranslation hook to SessionToc component
Provider request formats are complex and varied, making it difficult
to create a unified test mechanism. Users may incorrectly assume
request format issues indicate provider unavailability.
Code is commented out (not deleted) for easy restoration if needed.
v3.10.3 introduced HOME env priority on Windows for test isolation,
which caused database path to change when HOME differs from USERPROFILE
(common in Git/MSYS environments), making providers appear to disappear.
Changes:
- Use CC_SWITCH_TEST_HOME for test isolation instead of HOME
- Add legacy fallback to detect v3.10.3 database location on Windows
- Add logging for legacy path detection to aid debugging
* feat: init session manger
* feat: persist selected app to localStorage
- Save app selection when switching providers
- Restore last selected app on page load
* feat: persist current view to localStorage
- Save view selection when switching tabs
- Restore last selected view on page load
* styles: update ui
* feat: Improve macOS Terminal activation and refactor Kitty launch to use command with user's default shell.
* fix: session view
* feat: toc
* feat: Implement FlexSearch for improved session search functionality.
* feat: Redesign session manager search and filter UI for a more compact and dynamic experience.
* refactor: modularize session manager by extracting components and utility functions into dedicated files.
* feat: Enhance session terminal launching with support for iTerm2, Ghostty, WezTerm, and Alacritty, including UI and custom configuration options.
* feat: Conditionally render terminal selection and resume session buttons only on macOS.
Previously, check_claude_stream always added the x-api-key header,
ignoring the provider's auth_mode setting. This caused health check
failures for proxy services that only support Bearer authentication.
Now the function respects the auth.strategy field:
- AuthStrategy::Anthropic: Authorization Bearer + x-api-key
- AuthStrategy::ClaudeAuth: Authorization Bearer only
- AuthStrategy::Bearer: Authorization Bearer only
This aligns with the behavior of ClaudeAdapter::add_auth_headers
and fixes health checks for proxy providers with auth_mode="bearer_only".
Changes:
- Modified check_claude_stream to conditionally add x-api-key header
- Added AuthStrategy import
- Added test_auth_strategy_imports unit test
Tests: All passing (7/7 for stream_check module)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(skills): handle Windows path separator in installed status matching
Use regex to split directory path by both / and \ to correctly extract
the install name on Windows, fixing the issue where installed skills
were not showing as installed in the discovery page filter.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(skills): add repository filter to skills discovery page
- Add dropdown to filter skills by repository (owner/name)
- Extract unique repos from discoverable skills list
- Add truncate style for long repo names with hover title
- Add i18n translations for zh/en/ja
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add apiHintOAI i18n key for OpenAI Chat Completions format hint
- Update ClaudeFormFields to show format-specific endpoint hints
- When API format is "openai_chat", show OAI-specific hint
- Maintains consistency between hint and selected API format
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Add xdg-utils dependency for xdg-mime binary required by AppImage bundler
- Remove unused McpStatus struct from gemini_mcp.rs (duplicate of claude_mcp.rs)
- Add #![allow(dead_code)] to proxy models reserved for future type-safe API
- Add ubuntu-22.04-arm runner to build matrix
- Rename Linux artifacts with architecture suffix (x86_64/arm64)
- Update pnpm cache key with runner.arch to avoid cross-arch pollution
- Add linux-aarch64 platform to latest.json for Tauri updater
Provider add/update/delete mutations were using error.message directly,
which doesn't extract Tauri invoke errors properly. Now using the same
extractErrorMessage pattern as the rest of the codebase.
- Update version in package.json, Cargo.toml, and tauri.conf.json
- Add missing changelog entries for OpenCode API key link and AICodeMirror preset
- Fix Prettier formatting for new hook files
- Add OpenCode version detection with Go path scanning
- Add GitHub Releases API for fetching latest OpenCode version
- Add OpenCode install command to one-click install section
- Update i18n hints to include OpenCode across all locales
- Fix SettingsPage indentation formatting
Display an info toast when switching to a provider that uses OpenAI Chat
format (apiFormat === "openai_chat"), reminding users to enable the proxy
service. Move toast logic from mutation to useProviderActions for better
control over notification content based on provider properties.
The max_tokens restriction was too aggressive and should be handled
upstream or by the provider itself. Simplify anthropic_to_openai by
removing provider parameter since model mapping is already done by
proxy::model_mapper.
- Add NVIDIA NIM provider preset with API configuration
- Add nvidia.svg icon and register in icon system
- Add nvidia metadata with keywords and default color (#74B71B)
- Add model parameter to request logs for better debugging
- Fix duplicate /v1/v1 in URL when both base_url and endpoint have version
- Extend ?beta=true parameter to /v1/chat/completions endpoint
- Remove model mapping from transform layer (now handled by model_mapper)
- Add DeepSeek max_tokens clamping (1-8192 range)
Move api_format storage from settings_config to ProviderMeta to prevent
polluting ~/.claude/settings.json when switching providers.
- Add api_format field to ProviderMeta (Rust + TypeScript)
- Update ProviderForm to read/write apiFormat from meta
- Maintain backward compatibility for legacy settings_config.api_format
and openrouter_compat_mode fields (read-only fallback)
- Strip api_format from settings_config before writing to live config
Extend backward compatibility support for legacy openrouter_compat_mode field:
- Support number type (1 = enabled, 0 = disabled)
- Support string type ("true"/"1" = enabled)
- Add corresponding test cases for number and string types
Replace the OpenRouter-specific compatibility toggle with a generic
API format selector that allows all Claude providers to choose between:
- Anthropic Messages (native): Direct passthrough, no conversion
- OpenAI Chat Completions: Enables Anthropic ↔ OpenAI format conversion
Changes:
- Add ClaudeApiFormat type ("anthropic" | "openai_chat") to types.ts
- Replace openRouterCompatToggle with apiFormat dropdown in ClaudeFormFields
- Update ProviderForm to manage apiFormat state via settingsConfig.api_format
- Refactor claude.rs: add get_api_format() method, update needs_transform()
- Maintain backward compatibility with legacy openrouter_compat_mode field
- Update i18n translations (zh, en, ja)
- Add open_zip_file_dialog command for selecting ZIP files
- Add install_from_zip service method with recursive skill scanning
- Add install_skills_from_zip Tauri command
- Add frontend API methods and useInstallSkillsFromZip hook
- Add "Install from ZIP" button in Skills management page
- Support local skill ID format: local:{directory}
- Add i18n translations for new feature and error messages
- Move ~/.local/bin to first position in version scan search paths
- Update one-click install commands to use official native installation
(curl script) instead of npm for Claude Code
* fix(proxy): fix Codex 404 errors with custom base_url prefixes
- handlers.rs:268: Remove hardcoded /v1 prefix in Codex forwarding
- codex.rs:140: Only add /v1 for origin-only base_urls, dedupe /v1/v1
- stream_check.rs:364: Try /responses first, fallback to /v1/responses
- provider.rs:427: Don't force /v1 for custom prefix base_urls
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(codex): always add /v1 for custom prefix base_urls
Changed logic to always add /v1 prefix unless base_url already ends with /v1.
This fixes 504 timeout errors with relay services that expect /v1 in the path.
- Most relay services follow OpenAI standard format: /v1/responses
- Users can opt-out by adding /v1 to their base_url configuration
- Updated test case to reflect new behavior
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(proxy): allow system proxy on localhost with different ports
- Only bypass system proxy if it points to CC Switch's own port (15721)
- Allow other localhost proxies (e.g., Clash on 7890) to be used
- Add INFO level logging for request URLs to aid debugging
This fixes connection timeout issues when using local proxy tools.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(codex): don't add /v1 for custom prefix base_urls
Reverted logic to not add /v1 for base_urls with custom prefixes.
Many relay services use custom paths without /v1.
- Pure origin (e.g., https://api.openai.com) → adds /v1
- With /v1 (e.g., https://api.openai.com/v1) → no change
- Custom prefix (e.g., https://example.com/openai) → no /v1
This fixes 404 errors with relay services that don't use /v1 in their paths.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(proxy): use dynamic port for system proxy detection
Instead of hardcoding port 15721, now uses the actual configured
listen_port from proxy settings.
- Added set_proxy_port() to update the port when proxy server starts
- Added get_proxy_port() to retrieve current port for detection
- Updated server.rs to call set_proxy_port() on startup
- Updated tests to reflect new behavior
This allows users to change the proxy port in settings without
breaking the system proxy detection logic.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(proxy): change default proxy port from 15721 to 5000
Update the default fallback port in get_proxy_port() from 15721 to 5000
to match the project's standard default port configuration.
Also updated test cases to use port 5000 consistently.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(proxy): revert default port back to 15721
Revert the default fallback port in get_proxy_port() from 5000 back to 15721
to align with the project's updated default port configuration.
Also updated test cases to use port 15721 consistently.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
---------
Co-authored-by: ozbombor <ozbombor@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Frontend fallback value for visibleApps had gemini: false, which was
inconsistent with backend default (gemini: true). This caused Gemini
to be hidden by default on first install.
Fixes#817
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Update Moonshot preset models from kimi-k2-thinking to kimi-k2.5
- Update OpenCode Kimi preset name and model to k2.5
- Remove explicit model config from Kimi preset (use API defaults)
- Establish proper flexbox height chain from App to SettingsPage
- Move save button to fixed footer outside scrollable area
- Align footer styling with FullScreenPanel for consistency
* feat(db): add pricing config fields to proxy_config table
- Add default_cost_multiplier field per app type
- Add pricing_model_source field (request/response)
- Add request_model field to proxy_request_logs table
- Implement schema migration v5
* feat(api): add pricing config commands and provider meta fields
- Add get/set commands for default cost multiplier
- Add get/set commands for pricing model source
- Extend ProviderMeta with cost_multiplier and pricing_model_source
- Register new commands in Tauri invoke handler
* fix(proxy): apply cost multiplier to total cost only
- Move multiplier calculation from per-item to total cost
- Add resolve_pricing_config for provider-level override
- Include request_model and cost_multiplier in usage logs
- Return new fields in get_request_logs API
* feat(ui): add pricing config UI and usage log enhancements
- Add pricing config section to provider advanced settings
- Refactor PricingConfigPanel to compact table layout
- Display all three apps (Claude/Codex/Gemini) in one view
- Add multiplier column and request model display to logs
- Add frontend API wrappers for pricing config
* feat(i18n): add pricing config translations
- Add zh/en/ja translations for pricing defaults config
- Add translations for multiplier, requestModel, responseModel
- Add provider pricing config translations
* fix(pricing): align backfill cost calculation with real-time logic
- Fix backfill to deduct cache_read_tokens from input (avoid double billing)
- Apply multiplier only to total cost, not to each item
- Add multiplier display in request detail panel with i18n support
- Use AppError::localized for backend error messages
- Fix init_proxy_config_rows to use per-app default values
- Fix silent failure in set_default_cost_multiplier/set_pricing_model_source
- Add clippy allow annotation for test mutex across await
* style: format code with cargo fmt and prettier
* fix(tests): correct error type assertions in proxy DAO tests
The tests expected AppError::InvalidInput but the DAO functions use
AppError::localized() which returns AppError::Localized variant.
Updated assertions to match the correct error type with key validation.
---------
Co-authored-by: Jason <farion1231@gmail.com>
Add additive mode support for OpenCode in sync_current_to_live:
- Add AppType::is_additive_mode() to distinguish switch vs additive mode
- Add AppType::all() iterator to avoid hardcoding app lists
- Add sync_all_providers_to_live() for additive mode apps
- Refactor sync_current_to_live to handle both modes
Frontend changes (directory settings):
- Track opencodeDirChanged in useDirectorySettings
- Trigger syncCurrentProvidersLiveSafe when OpenCode dir changes
- Add i18n strings for OpenCode directory settings
Follow-up to #644: Extract duplicate get_home_dir() implementations
into a single pub fn in config.rs, reducing code duplication across
codex_config.rs, gemini_config.rs, and settings.rs.
Also adds documentation comments to build.rs explaining the Windows
manifest workaround for test binaries.
Merge macOS Alacritty/Kitty/Ghostty launchers into a single generic
`launch_macos_open_app` function with a `use_e_flag` parameter.
Replace Windows cmd/PowerShell/wt launchers with a shared
`run_windows_start_command` helper. Reduces ~98 lines of duplicate code.
- Add directory conflict detection before installation
- Fix installed status check to match repo owner and name
- Add i18n translations for conflict error messages
- Replace blue dot indicator with lucide ArrowUpCircle icon
- Change color scheme from blue to green for better visibility
- Enlarge button (h-8 w-8) and icon (h-5 w-5) for better UX
- Move UpdateBadge from title area to after settings icon
- Add SyncMethod enum (Auto/Symlink/Copy) in Rust backend
- Implement sync_to_app_dir with symlink support (cross-platform)
- Add SkillSyncMethodSettings UI component (simplified 2-button selector)
- Add i18n support for zh/en/ja
- Replace copy_to_app with configurable sync_to_app_dir
- Add skill_sync_method field to AppSettings
User can now choose between symlink (disk space saving) or copy (best compatibility) in Settings > General.
When disabling a prompt, check if any other prompts remain enabled.
If all prompts are disabled, clear the prompt file to ensure UI state
matches the actual configuration that Claude Code reads.
Add `extra` field with serde flatten to OpenCodeModel struct to capture
custom fields like cost, modalities, thinking, and variants that were
previously lost during save operations.
Use lazy initialization in useState to parse model values from config
on first render, matching the pattern used in useApiKeyState. This
fixes an issue where model fields were not populated in edit mode.
- Bump version to 3.10.1 across all config files
- Update CHANGELOG with all fixes since v3.10.0
- Fix Rust Clippy warning by using derive(Default)
- Apply code formatting
Change the default state of the rectifier from enabled to disabled.
This allows users to opt-in to the rectifier feature rather than having it enabled by default.
Changes:
- Set RectifierConfig::default() enabled and request_thinking_signature to false
- Update serde default attributes from default_true to default
- Update unit tests to reflect new default behavior
- Import new detailed RC icon from rc.svg (128x128 viewBox)
- Add icon configuration to Claude provider preset
- Remove duplicate old RC icon definition to fix TS1117 error
Enhance the effectiveColor logic in ProviderIcon to properly validate
the color prop before using it. Now only uses color when it's a valid
non-empty string, otherwise falls back to icon metadata defaultColor.
This fixes the issue where Gemini icons would turn black when selected
in ProviderCard due to null/undefined/empty string color values being
passed through.
Use /K instead of /C flag to prevent cmd window from closing
immediately after batch file execution. Also remove the buggy
errorlevel check that was checking del command's return value
instead of claude's.
Fixes#726
OpenCode uses ~/.config/opencode on all platforms, not %APPDATA%\opencode
on Windows. Remove the platform-specific path handling to use unified
~/.config/opencode path across all operating systems.
Add max-w-[56rem] and mx-auto to Skills, MCP, and Prompts panels
to match the header layout constraints, ensuring the back button
and action buttons align with the content area.
- Add rc.svg for RightCode (optimized from 96KB to 581 bytes)
- Add catcoder.svg for KAT-Coder (快手)
- Add mcp.svg for Model Context Protocol
- Add siliconflow.svg for SiliconFlow
- Register all new icons in index.ts
- Add icon config for RightCode in claude/codex presets
- Add icon config for KAT-Coder in claude/opencode presets
- Fix SiliconFlow iconColor from #000000 to #6E29F6
- Add JimLiu/baoyu-skills to default skill repositories
- Change init_default_skill_repos() from "first-run only" to "supplement missing"
- New preset repos will now auto-appear for existing users on upgrade
When ProviderIcon is used without a color prop, it now automatically
fetches the defaultColor from icon metadata. This fixes the issue where
Gemini app icon turns black when selected in AppSwitcher, because the
component was inheriting the parent's text-foreground color.
Previously, enabling auto failover kept using the current provider until
the first failure, causing inconsistency when the current provider was
not in the failover queue. When stopping proxy, the restored config
would not match user expectations.
New behavior:
- Enable auto failover = immediately switch to queue P1
- Subsequent routing follows queue order (P1→P2→...)
- Auto-add current provider to queue if queue is empty
Changes:
- Add switch_proxy_target() for hot-switching during proxy mode
- Update provider_router to use queue order when failover enabled
- Sync tray menu Auto click with the same logic
- Update UI tooltips to reflect new semantics
- Add tests for queue-only routing scenario
- Format Rust code with rustfmt (misc.rs, types.rs)
- Format TypeScript/React code with Prettier (4 files)
- Fix ProviderList test by wrapping with QueryClientProvider
Allow users to choose which apps (Claude, Codex, Gemini, OpenCode) to display on the homepage.
- Add VisibleApps type and settings field in both frontend and backend
- Refactor AppSwitcher to render apps dynamically based on visibility
- Extract ToggleRow component for reuse
- Add i18n support for app visibility settings
* refactor(ui): simplify UpdateBadge to minimal dot indicator
* feat(provider): add individual test and proxy config for providers
Add support for provider-specific model test and proxy configurations:
- Add ProviderTestConfig and ProviderProxyConfig types in Rust and TypeScript
- Create ProviderAdvancedConfig component with collapsible panels
- Update stream_check service to merge provider config with global config
- Proxy config UI follows global proxy style (single URL input)
Provider-level configs stored in meta field, no database schema changes needed.
* feat(ui): add failover toggle and improve proxy controls
- Add FailoverToggle component with slide animation
- Simplify ProxyToggle style to match FailoverToggle
- Add usage statistics button when proxy is active
- Fix i18n parameter passing for failover messages
- Add missing failover translation keys (inQueue, addQueue, priority)
- Replace AboutSection icon with app logo
* fix(proxy): support system proxy fallback and provider-level proxy config
- Remove no_proxy() calls in http_client.rs to allow system proxy fallback
- Add get_for_provider() to build HTTP client with provider-specific proxy
- Update forwarder.rs and stream_check.rs to use provider proxy config
- Fix EditProviderDialog.tsx to include provider.meta in useMemo deps
- Add useEffect in ProviderAdvancedConfig.tsx to sync expand state
Fixes#636Fixes#583
* fix(ui): sync toast theme with app setting
* feat(settings): add log config management
Fixes#612Fixes#514
* fix(proxy): increase request body size limit to 200MB
Fixes#666
* docs(proxy): update timeout config descriptions and defaults
Fixes#612
* fix(proxy): filter x-goog-api-key header to prevent duplication
* fix(proxy): prevent proxy recursion when system proxy points to localhost
Detect if HTTP_PROXY, HTTPS_PROXY, or ALL_PROXY environment variables
point to loopback addresses (localhost, 127.0.0.1), and bypass system
proxy in such cases to avoid infinite request loops.
* fix(i18n): add providerAdvanced i18n keys and fix failover toast parameter
- Add providerAdvanced.* i18n keys to en.json, zh.json, and ja.json
- Fix failover toggleFailed toast to pass detail parameter
- Remove Chinese fallback text from UI for English/Japanese users
* fix(tray): restore tray-provider events and enable Auto failover properly
- Emit provider-switched event on tray provider click (backward compatibility)
- Auto button now: starts proxy, takes over live config, enables failover
* fix(log): enable dynamic log level and single file mode
- Initialize log at Trace level for dynamic adjustment
- Change rotation strategy to KeepSome(1) for single file
- Set max file size to 1GB
- Delete old log file on startup for clean start
* fix(tray): fix clippy uninlined format args warning
Use inline format arguments: {app_type_str} instead of {}
* fix(provider): allow typing :// in endpoint URL inputs
Change input type from "url" to "text" to prevent browser
URL validation from blocking :// input.
Closes#681
* fix(stream-check): use Gemini native streaming API format
- Change endpoint from OpenAI-compatible to native streamGenerateContent
- Add alt=sse parameter for SSE format response
- Use x-goog-api-key header instead of Bearer token
- Convert request body to Gemini contents/parts format
* feat(proxy): add request logging for debugging
Add debug logs for outgoing requests including URL and body content
with byte size, matching the existing response logging format.
* fix(log): prevent usize underflow in KeepSome rotation strategy
KeepSome(n) internally computes n-2, so n=1 causes underflow.
Use KeepSome(2) as the minimum safe value.
Add support for configuring per-model options like provider routing.
Each model row now has an expand/collapse toggle to show a key-value
editor for model-specific options (e.g., provider order, fallbacks).
- Add options field to OpenCodeModel in Rust and TypeScript
- Add expandable key-value editor UI for each model
- Use local state pattern for option key input to prevent focus loss
- Add i18n translations for zh/en/ja
Add key-value pair editor for configuring additional SDK options like
timeout, setCacheKey, etc. Values are automatically parsed to appropriate
types (number, boolean, object) on save.
- Add `extra` field with serde flatten in Rust backend
- Add index signature to OpenCodeProviderOptions type
- Create ExtraOptionKeyInput component with local state pattern
- Place extra options section above models configuration
Use local state + onBlur pattern for ModelIdInput to keep React key
stable during editing. Previously, each keystroke changed the object
key, causing React to unmount/remount the input and lose focus.
OpenCode lacks a dedicated adapter and falls back to Codex adapter,
which has incompatible config structure. Hide the test button in UI
to prevent users from triggering unsupported operations.
OpenCode uses user-provided provider keys as IDs instead of random UUIDs.
When duplicating a provider, generate a unique key by appending "-copy"
suffix (or "-copy-2", "-copy-3", etc. if already exists).
Add startup import logic for OpenCode providers and MCP servers.
Unlike other apps that use replacement mode, OpenCode uses additive
mode where multiple providers can coexist in the config file.
- Add Provider Key input field for OpenCode providers (between icon and name)
- User must manually enter a unique key instead of auto-generating from name
- Real-time validation: format check and duplicate detection
- Key is immutable after creation (disabled in edit mode)
- Remove slugify auto-generation logic from mutations
- Add beforeNameSlot prop to BasicFormFields for extensibility
- Add i18n translations for zh/en/ja
- Create separate removeFromLiveConfig API for additive mode apps
(remove only removes from live config, not database)
- Fix useSwitchProviderMutation to invalidate opencodeLiveProviderIds
cache so button state updates correctly after add operation
- Show appropriate toast messages:
- Add: "已添加到配置" / "Added to config"
- Remove: "已从配置移除" / "Removed from config"
- Add i18n texts for addToConfigSuccess and removeFromConfigSuccess
Separate the confirmation dialogs for "remove from config" and "delete
provider" operations in OpenCode mode to help users understand the
different impacts of each action.
For OpenCode (additive mode), use isInConfig instead of isCurrent to
determine whether to enable usage auto-query. This allows providers
that have been added to the config to have their usage queried
automatically.
OpenCode uses additive mode where the main "Remove" button removes from
live config, while the delete button should delete from database. The
delete button should always be enabled for OpenCode providers.
OpenCode uses additive mode where all providers coexist in config file,
so there's no "current" provider concept. This commit:
- Skip setting is_current in switch_normal for OpenCode
- Return empty string from ProviderService::current for OpenCode
- Disable active provider highlight in ProviderCard for OpenCode
Previously Base URL was only shown for @ai-sdk/openai-compatible.
Now it's always visible to support proxy scenarios for official SDKs
like DeepSeek, Anthropic, etc.
OpenCode's read_live_settings returns the full opencode.json file
instead of just the provider fragment. This caused the edit dialog
to save the complete config structure as settingsConfig, creating
nested provider configurations.
For OpenCode's additive mode, use DB config directly since each
provider's config is stored independently.
- Skip backfill logic for OpenCode (additive mode doesn't need it)
- Add defensive check in write_live_snapshot to extract provider fragment
- Use slugified name as provider ID for readable config keys
- Add 19 new provider presets for OpenCode (cn_official, aggregator, third_party)
- Add OpenCode handling branch in handlePresetChange to properly populate
form fields (baseURL, apiKey, npm, models) when selecting a preset
- Add OpenCode reset logic in custom mode branch
Add missing OpenCode branch in parse_app_type() and include OpenCode
in all app iteration loops for skills operations (uninstall, scan,
import, migrate).
- Add `enabled` parameter to useCommonConfigSnippet hook
- Skip all loading and auto-merge logic when enabled=false
- Replace CommonConfigEditor with simplified JsonEditor for OpenCode
- Prevent Claude's common config snippet from being injected into OpenCode
- Increase SelectContent z-index from z-50 to z-[100] to appear above FullScreenPanel (z-[60])
- Replace form.watch() with form.getValues() in useCallback handlers for correct react-hook-form usage
- Remove max-w-[56rem] constraints from various panels for consistent full-width layout
Remove dead code that was never called after v3.7.0 architecture change:
- mcp/opencode.rs: sync_enabled_to_opencode, collect_enabled_servers
- opencode_config.rs: 8 unused utility functions
- provider.rs: OpenCodeProviderConfig impl block (4 methods)
These functions were designed for batch operations but McpService uses
per-server sync pattern instead. No functionality affected.
- Add opencode to AppType and SkillApps interfaces in skills.ts
- Add OpenCode Switch component to UnifiedMcpPanel list items
- Add OpenCode Switch component to UnifiedSkillsPanel list items
- Include OpenCode in enabled counts and header statistics for both panels
OpenCode uses additive provider management where providers can exist in
the database but not necessarily in the live opencode.json config. This
commit implements proper isInConfig state:
Backend:
- Add get_opencode_live_provider_ids command to query live config
Frontend:
- Add getOpenCodeLiveProviderIds API method
- ProviderList queries live provider IDs and computes isInConfig
- ProviderCard receives and passes isInConfig to ProviderActions
- ProviderActions already handles the add/remove button logic
OpenCode uses additive provider management (multiple providers coexist),
so proxy/failover features are not applicable. This commit:
- Conditionally renders ProxyToggle only for non-OpenCode apps
- Fixes toast message label to properly handle opencode app type
- Fix MCP server not removed from opencode.json when unchecked in edit modal
- Fix Windows atomic write failure when opencode.json already exists
- Fix i18n keys mismatch in OpenCodeFormFields (use opencode.* namespace)
- Fix unit test missing apps.opencode field assertion
- Add import_opencode_providers_from_live command to provider.rs
- Register new command in lib.rs invoke_handler
- Update commands/mcp.rs: include OpenCode in sync_other_side logic
- Add McpService::import_from_opencode to import_mcp_from_apps
- Implement MCP sync/remove for OpenCode in services/mcp.rs
- sync_server_to_app_no_config now calls sync_single_server_to_opencode
- remove_server_from_app now calls remove_server_from_opencode
Implement OpenCode-specific provider service logic with additive mode:
- add(): Always write to live config (no is_current check needed)
- update(): Always sync changes to live config
- delete(): Remove from both DB and live config (no is_current check)
New helper functions in live.rs:
- write_live_snapshot(): Write provider to opencode.json provider section
- remove_opencode_provider_from_live(): Remove provider from live config
- import_opencode_providers_from_live(): Import existing providers from
~/.config/opencode/opencode.json into CC Switch database
Key design: OpenCode uses additive mode where all providers coexist
in the config file, unlike Claude/Codex/Gemini which use replacement
mode with a single active provider.
Add mcp/opencode.rs with format conversion between CC Switch and OpenCode:
- stdio ↔ local type conversion
- command+args ↔ command array format
- env ↔ environment field mapping
- sse/http ↔ remote type conversion
Public API:
- sync_enabled_to_opencode: Batch sync all enabled servers
- sync_single_server_to_opencode: Sync individual server
- remove_server_from_opencode: Remove from live config
- import_from_opencode: Import servers from OpenCode config
Also fix test files to include new opencode field in McpApps struct.
All 4 unit tests pass for format conversion.
Add OpenCode-specific configuration structures for the AI SDK format:
- OpenCodeProviderConfig: Main config with npm package, options, and models
- OpenCodeProviderOptions: baseURL, apiKey, and headers support
- OpenCodeModel: Model definition with name and token limits
- OpenCodeModelLimit: Context and output token limits
Key features:
- Supports environment variable references (e.g., "{env:API_KEY}")
- Custom headers support for specialized providers
- Model-level token limits for context management
- Helper methods for parsing and validation
Add OpenCode as the 4th supported application with additive provider management:
- Add OpenCode variant to AppType enum with all related match statements
- Add enabled_opencode field to McpApps and SkillApps structures
- Add opencode field to McpRoot and PromptRoot
- Add database schema migration v3→v4 with enabled_opencode columns
- Add settings.rs support for opencode_config_dir and current_provider_opencode
- Create opencode_config.rs module for config file I/O operations
- Update all services (proxy, mcp, skill, provider, stream_check) for OpenCode
- Add OpenCode support to deeplink provider and MCP parsing
- Update commands/config.rs for OpenCode config status and paths
Key design decisions:
- OpenCode uses additive mode (no is_current needed, no proxy support)
- Config path: ~/.config/opencode/opencode.json
- MCP format: stdio→local, sse/http→remote conversion planned
- Stream check returns error (not yet implemented for OpenCode)
* feat(usage): improve custom template with variables display and explicit type detection
Combine two feature improvements:
1. Display supported variables ({{baseUrl}}, {{apiKey}}) with actual values in custom template mode
2. Add explicit templateType field for accurate template mode detection
## Changes
### Frontend
- Display template variables with actual values extracted from provider settings
- Add templateType field to UsageScript for explicit mode detection
- Support template mode persistence across sessions
### Backend
- Add template_type field to UsageScript struct
- Improve validation logic based on explicit template type
- Maintain backward compatibility with type inference
### I18n
- Add "Supported Variables" section translation (zh/en/ja)
### Benefits
- More accurate template mode detection (no more guessing)
- Better user experience with variable hints
- Clearer validation rules per template type
* fix(usage): resolve custom template cache and validation issues
Combine three bug fixes to make custom template mode work correctly:
1. **Update cache after test**: Testing usage script successfully now updates the main list cache immediately
2. **Fix same-origin check**: Custom template mode can now access different domains (SSRF protection still active)
3. **Fix field naming**: Unified to use autoQueryInterval consistently between frontend and backend
## Problems Solved
- Main provider list showing "Query failed" after successful test
- Custom templates blocked by overly strict same-origin validation
- Auto-query intervals not saved correctly due to inconsistent naming
## Changes
### Frontend (UsageScriptModal)
- Import useQueryClient and update cache after successful test
- Invalidate usage cache when saving script configuration
- Use standardized autoQueryInterval field name
### Backend (usage_script.rs)
- Allow custom template mode to bypass same-origin checks
- Maintain SSRF protection for all modes
### Hooks (useProviderActions)
- Invalidate usage query cache when saving script
## Impact
Users can now use custom templates freely while security validations remain intact for general templates.
* fix(usage): correct provider credential field names
- Claude: support both ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN
- Gemini: use GEMINI_API_KEY instead of GOOGLE_GEMINI_API_KEY
- Codex: use OPENAI_API_KEY and parse base_url from TOML config string
Addresses review feedback from PR #628
* style: format code
---------
Co-authored-by: Jason <farion1231@gmail.com>
* feat(proxy): add thinking signature rectifier for Claude API
Add automatic request rectification when Anthropic API returns signature
validation errors. This improves compatibility when switching between
different Claude providers or when historical messages contain incompatible
thinking block signatures.
- Add thinking_rectifier.rs module with trigger detection and rectification
- Integrate rectifier into forwarder error handling flow
- Remove thinking/redacted_thinking blocks and signature fields on retry
- Delete top-level thinking field when assistant message lacks thinking prefix
* fix(proxy): complete rectifier retry path with failover switch and chain continuation
- Add failover switch trigger on rectifier retry success when provider differs from start
- Replace direct error return with error categorization on rectifier retry failure
- Continue failover chain for retryable errors instead of terminating early
* feat(proxy): add rectifier config with master switch
- Add RectifierConfig struct with enabled and requestThinkingSignature fields
- Update should_rectify_thinking_signature to check master switch first
- Add tests for master switch functionality
* feat(db): add rectifier config storage in settings table
Store rectifier config as JSON in single key for extensibility
* feat(commands): add get/set rectifier config commands
* feat(ui): add rectifier config panel in advanced settings
- Add RectifierConfigPanel component with master switch and thinking signature toggle
- Add API wrapper for rectifier config
- Add i18n translations for zh/en/ja
* feat(proxy): integrate rectifier config into request forwarding
- Load rectifier config from database in RequestContext
- Pass config to RequestForwarder for runtime checking
- Use should_rectify_thinking_signature with config parameter
* test(proxy): add nested JSON error detection test for thinking rectifier
* fix(proxy): resolve HalfOpen permit leak and RectifierConfig default values
- Fix RectifierConfig::default() to return enabled=true (was false due to derive)
- Add release_permit_neutral() for releasing permits without affecting health stats
- Fix 3 permit leak points in rectifier retry branches
- Add unit tests for default values and permit release
* style(ui): format ProviderCard style attribute
* fix(rectifier): add detection for signature field required error
Add support for detecting "signature: Field required" error pattern
in the thinking signature rectifier. This enables automatic request
rectification when upstream API returns this specific validation error.
Replace Dialog component with FullScreenPanel in PricingEditModal
to match the UI style of other edit dialogs (provider, MCP).
Changes:
- Switch from small centered Dialog to full-screen panel
- Add back button in header and fixed footer for actions
- Add Save/Plus icons to submit button
* feat(misc): add WSL tool version detection with security hardening (#608)
- Add WSL distro detection from UNC path (wsl$/wsl.localhost)
- Add distro name validation to prevent command injection
- Add defensive assertion for tool parameter
- Unify cfg macros to target_os = "windows"
- Standardize error message format to [WSL:{distro}]
Closes#608
* fix(misc): add CREATE_NO_WINDOW flag and unify error messages to English
- Add CREATE_NO_WINDOW flag to wsl.exe command to prevent console window flash
- Standardize error messages from Chinese to English for consistency
---------
Co-authored-by: Jason <farion1231@gmail.com>
- Add configurable test prompt field to StreamCheckConfig
- Implement Claude CLI-compatible request format with proper headers:
- Authorization + x-api-key dual auth
- anthropic-beta, anthropic-version headers
- x-stainless-* SDK headers with dynamic OS/arch detection
- URL with ?beta=true parameter
- Implement Codex CLI-compatible Responses API format:
- /v1/responses endpoint
- input array format with reasoning effort support
- codex_cli_rs user-agent and originator headers
- Add dynamic OS name and CPU architecture detection
- Internationalize error messages (Chinese -> English)
- Add test prompt Textarea UI component with i18n support
- Remove obsolete testPromptDesc translation key
* refactor(proxy): simplify logging for better readability
- Delete 17 verbose debug logs from handlers, streaming, and response_processor
- Convert excessive INFO logs to DEBUG level for internal processing details
- Add 2 critical INFO logs in forwarder.rs for failover scenarios:
- Log when switching to next provider after failure
- Log when all providers have been exhausted
- Fix clippy uninlined_format_args warning
This reduces log noise while maintaining visibility into key user-facing decisions.
* fix: replace unsafe unwrap() calls with proper error handling
- database/dao/mcp.rs: Use map_err for serde_json serialization
- database/dao/providers.rs: Use map_err for settings_config and meta serialization
- commands/misc.rs: Use expect() for compile-time regex pattern
- services/prompt.rs: Use unwrap_or_default() for SystemTime
- deeplink/provider.rs: Replace unwrap() with is_none_or pattern for Option checks
Reduces potential panic points from 26 to 1 (static regex init, safe).
* refactor(proxy): simplify verbose logging output
- Remove response JSON full output logging in response_processor
- Remove per-request INFO logs in provider_router (failover status, provider selection)
- Change model mapping log from INFO to DEBUG
- Change usage logging failure from INFO to WARN
- Remove redundant debug logs for circuit breaker operations
Reduces log noise significantly while preserving important warnings and errors.
* feat(proxy): add structured log codes for i18n support
Add error code system to proxy module logs for multi-language support:
- CB-001~006: Circuit breaker state transitions and triggers
- SRV-001~004: Proxy server lifecycle events
- FWD-001~002: Request forwarding and failover
- FO-001~005: Failover switch operations
- USG-001~002: Usage logging errors
Log format: [CODE] Chinese message
Frontend/log tools can map codes to any language.
New file: src/proxy/log_codes.rs - centralized code definitions
* chore: bump version to 3.9.1
* style: format code with prettier and rustfmt
* fix(ui): allow number inputs to be fully cleared before saving
- Convert numeric state to string type for controlled inputs
- Use isNaN() check instead of || fallback to allow 0 values
- Apply fix to ProxyPanel, CircuitBreakerConfigPanel,
AutoFailoverConfigPanel, and ModelTestConfigPanel
* feat(pricing): support @ separator in model name matching
- Refactor model name cleaning into chained method calls
- Add @ to - replacement (e.g., gpt-5.2-codex@low → gpt-5.2-codex-low)
- Add test case for @ separator matching
* feat(proxy): add global proxy settings support
Add ability to configure a global HTTP/HTTPS proxy for all outbound
requests including provider API calls, speed tests, and stream checks.
* fix(proxy): improve validation and error handling in proxy config panels
- Add StopTimeout/StopFailed error types for proper stop() error reporting
- Replace silent clamp with validation-and-block in config panels
- Add listenAddress format validation in ProxyPanel
- Use log_codes constants instead of hardcoded strings
- Use once_cell::Lazy for regex precompilation
* fix(proxy): harden error handling and input validation
- Handle RwLock poisoning in settings.rs with unwrap_or_else
- Add fallback for dirs::home_dir() in config modules
- Normalize localhost to 127.0.0.1 in ProxyPanel
- Format IPv6 addresses with brackets for valid URLs
- Strict port validation with pure digit regex
- Treat NaN as validation failure in config panels
- Log warning on cost_multiplier parse failure
- Align timeoutSeconds range to [0, 300] across all panels
* feat(proxy): add local proxy auto-scan and fix hot-reload
- Add scan_local_proxies command to detect common proxy ports
- Fix SkillService not using updated proxy after hot-reload
- Move global proxy settings to advanced tab
- Add error handling for scan failures
* fix(proxy): allow localhost input in proxy address field
* fix(proxy): restore request timeout and fix proxy hot-reload issues
- Add URL scheme validation in build_client (http/https/socks5/socks5h)
- Restore per-request timeout for speedtest, stream_check, usage_script, forwarder
- Fix set_global_proxy_url to validate before persisting to DB
- Mask proxy credentials in all log outputs
- Fix forwarder hot-reload by fetching client on each request
* style: format code with prettier
* fix(proxy): improve global proxy stability and error handling
- Fix RwLock silent failures with explicit error propagation
- Handle init() duplicate calls gracefully with warning log
- Align fallback client config with build_client settings
- Make scan_local_proxies async to avoid UI blocking
- Add mixed mode support for Clash 7890 port (http+socks5)
- Use multiple test targets for better proxy connectivity test
- Clear invalid proxy config on init failure
- Restore timeout constraints in usage_script
- Fix mask_url output for URLs without port
- Add structured error codes [GP-001 to GP-009]
* feat(proxy): add username/password authentication support
- Add separate username and password input fields
- Implement password visibility toggle with eye icon
- Add clear button to reset all proxy fields
- Auto-extract auth info from saved URL and merge on save
- Update i18n translations (zh/en/ja)
* fix(proxy): fix double encoding issue in proxy auth and add debug logs
- Remove encodeURIComponent in mergeAuth() since URL object's
username/password setters already do percent-encoding automatically
- Add GP-010 debug log for database read operations
- Add GP-011 debug log to track incoming URL info (length, has_auth)
- Fix username.trim() in fallback branch for consistent behavior
- Add endpointAutoSelect field to ProviderMeta for persistence
- Lift autoSelect state from EndpointSpeedTest to ProviderForm
- Save auto-select preference when provider is saved
- Restore preference when editing existing provider
Fixes https://github.com/farion1231/cc-switch/issues/589
* feat(deeplink): support comma-separated multiple endpoints in URL
Allow importing multiple API endpoints via single endpoint parameter.
First URL becomes primary endpoint, rest are added as custom endpoints.
* feat(deeplink): add usage query fields to deeplink generator
Add form fields for usage query configuration in deeplink HTML generator:
- usageEnabled, usageBaseUrl, usageApiKey
- usageScript, usageAutoInterval
- usageAccessToken, usageUserId
* fix(deeplink): auto-infer homepage and improve multi-endpoint display
- Auto-infer homepage from primary endpoint when not provided
- Display multiple endpoints as list in import dialog (primary marked)
- Update deeplink parser in deplink.html to show multi-endpoint info
- Add test for homepage inference from endpoint
- Minor log format fix in live.rs
* fix(deeplink): use primary endpoint for usage script base_url
- Fix usage_script.base_url getting comma-separated string when multiple endpoints
- Add i18n support for primary endpoint label in DeepLinkImportDialog
Merged PR #452 which adds:
- Terminal button for Claude providers to launch with provider-specific config
- Cross-platform support (macOS/Linux/Windows)
- Auto-cleanup of temporary config files
When merging Gemini config from local env file during deeplink import,
check GOOGLE_GEMINI_BASE_URL first (official variable name) before
falling back to GEMINI_BASE_URL.
* fix(mcp): skip cmd /c wrapper for WSL target paths
When the Claude config directory is set to a WSL network path
(e.g., \wsl$\Ubuntu\home\user\.claude), the MCP export should
not wrap npx/npm commands with cmd /c since WSL runs Linux.
- Add is_wsl_path() to detect \wsl$\ and \wsl.localhost\ paths
- Skip wrap_command_for_windows() when target is WSL path
- Add comprehensive tests for various WSL distributions
* chore(mcp): add debug log for WSL path detection
* refactor(mcp): optimize is_wsl_path with next() and rename variable
* refactor(proxy): simplify logging for better readability
- Delete 17 verbose debug logs from handlers, streaming, and response_processor
- Convert excessive INFO logs to DEBUG level for internal processing details
- Add 2 critical INFO logs in forwarder.rs for failover scenarios:
- Log when switching to next provider after failure
- Log when all providers have been exhausted
- Fix clippy uninlined_format_args warning
This reduces log noise while maintaining visibility into key user-facing decisions.
* fix: replace unsafe unwrap() calls with proper error handling
- database/dao/mcp.rs: Use map_err for serde_json serialization
- database/dao/providers.rs: Use map_err for settings_config and meta serialization
- commands/misc.rs: Use expect() for compile-time regex pattern
- services/prompt.rs: Use unwrap_or_default() for SystemTime
- deeplink/provider.rs: Replace unwrap() with is_none_or pattern for Option checks
Reduces potential panic points from 26 to 1 (static regex init, safe).
* refactor(proxy): simplify verbose logging output
- Remove response JSON full output logging in response_processor
- Remove per-request INFO logs in provider_router (failover status, provider selection)
- Change model mapping log from INFO to DEBUG
- Change usage logging failure from INFO to WARN
- Remove redundant debug logs for circuit breaker operations
Reduces log noise significantly while preserving important warnings and errors.
* feat(proxy): add structured log codes for i18n support
Add error code system to proxy module logs for multi-language support:
- CB-001~006: Circuit breaker state transitions and triggers
- SRV-001~004: Proxy server lifecycle events
- FWD-001~002: Request forwarding and failover
- FO-001~005: Failover switch operations
- USG-001~002: Usage logging errors
Log format: [CODE] Chinese message
Frontend/log tools can map codes to any language.
New file: src/proxy/log_codes.rs - centralized code definitions
* chore: bump version to 3.9.1
* style: format code with prettier and rustfmt
* fix(ui): allow number inputs to be fully cleared before saving
- Convert numeric state to string type for controlled inputs
- Use isNaN() check instead of || fallback to allow 0 values
- Apply fix to ProxyPanel, CircuitBreakerConfigPanel,
AutoFailoverConfigPanel, and ModelTestConfigPanel
* feat(pricing): support @ separator in model name matching
- Refactor model name cleaning into chained method calls
- Add @ to - replacement (e.g., gpt-5.2-codex@low → gpt-5.2-codex-low)
- Add test case for @ separator matching
* fix(proxy): improve validation and error handling in proxy config panels
- Add StopTimeout/StopFailed error types for proper stop() error reporting
- Replace silent clamp with validation-and-block in config panels
- Add listenAddress format validation in ProxyPanel
- Use log_codes constants instead of hardcoded strings
- Use once_cell::Lazy for regex precompilation
* fix(proxy): harden error handling and input validation
- Handle RwLock poisoning in settings.rs with unwrap_or_else
- Add fallback for dirs::home_dir() in config modules
- Normalize localhost to 127.0.0.1 in ProxyPanel
- Format IPv6 addresses with brackets for valid URLs
- Strict port validation with pure digit regex
- Treat NaN as validation failure in config panels
- Log warning on cost_multiplier parse failure
- Align timeoutSeconds range to [0, 300] across all panels
When users change app config directories (claudeConfigDir, codexConfigDir,
geminiConfigDir), MCP servers were being synced to the new paths but Skills
were not. This adds Skill synchronization to sync_current_to_live() to ensure
installed Skills are also copied to the new app directories.
Fix state synchronization in useBaseUrlState and useApiKeyState hooks
to properly clear values when config is reset. Previously, when switching
from a preset to "custom", the baseUrl and apiKey states would retain
their old values because the sync logic only updated when new values
existed, not when they were cleared.
Changes:
- useBaseUrlState: Always sync baseUrl to config value (empty if undefined)
- useApiKeyState: Remove hasApiKeyField check that prevented clearing
Add key prop to UsageScriptModal to ensure component remounts when
switching between different providers. This fixes issue #569 where
configuring usage query for one provider would incorrectly apply
the same configuration to all providers.
The root cause was that useState initialization only runs on first
mount, and due to useLastValidValue hook keeping the modal rendered
during close animation, the component might not fully unmount when
switching providers rapidly.
Closes#569
OpenRouter now natively supports Claude Code compatible API (/v1/messages),
so format transformation (Anthropic ↔ OpenAI) is no longer needed by default.
- Change default value from `true` to `false` in both frontend and backend
- Hide the "OpenRouter Compatibility Mode" toggle in provider form
- Users can still enable it manually by adding `"openrouter_compat_mode": true` in config JSON
- Update unit tests to reflect new default behavior
Claude Code/Codex uses startup_timeout_sec and tool_timeout_sec,
but Gemini CLI only supports a single timeout param in milliseconds.
- Collect startup and tool timeout separately with defaults
- startup_timeout_sec default: 10s
- tool_timeout_sec default: 60s
- Take max of both values as final timeout
- Support both sec and ms variants
- Remove original fields and insert Gemini-compatible timeout
Flatpak build has persistent issues with libdbusmenu dependencies.
Removing it for now to allow release. Can be re-added later with
proper libayatana dependency configuration.
intltool was removed from org.gnome.Sdk in 2019. libdbusmenu's configure
script requires it even with --disable-nls. Using cleanup: ["*"] ensures
intltool is only used at build time.
- Update version in package.json, tauri.conf.json, Cargo.toml
- Update version badges and current version in README files
- Add v3.9.1 changelog entry with bug fixes and improvements
- Add missing "title" field to tauri.windows.conf.json to display
"CC Switch" instead of default "Tauri app"
- Make DRAG_BAR_HEIGHT platform-aware: 0px on Windows/Linux (native
titlebar), 28px on macOS (Overlay mode needs traffic light space)
- Apply same fix to FullScreenPanel component for consistency
Fixes the issue where Windows showed wrong title and had ~28px extra
blank space below the native titlebar introduced in v3.9.0.
- Add AiGoCode colored icon (blue body #5B7FFF, purple star #7C6AEF)
- Add original SVG source file for reference
- Add icon metadata with keywords and default color
- Add iconColor to AiGoCode presets for Claude, Codex, and Gemini
- Add partner promotion messages in zh/en/ja locales
* feat(logging): add crash logging and improve log management
- Add panic hook to capture crash info to ~/.cc-switch/crash.log
- Records timestamp, app version, OS/arch, thread info
- Full stack trace with force_capture for release builds
- Safe error handling (no nested panics)
- Enable logging for both Debug and Release builds
- Info level for all builds
- Output to console and ~/.cc-switch/logs/
- 5MB max file size with rotation
- Add log cleanup on startup
- Keep only 2 most recent log files
- Works on all platforms
- Change panic strategy from "abort" to "unwind"
- Required for backtrace capture in release builds
* fix(logging): use OnceLock for config dir and add URL redaction
- Use OnceLock to support custom config directory override for crash.log
- Add redact_url_for_log() to protect sensitive URL parameters in logs
- Change verbose deep link logs from info to debug level
- Move Store refresh before panic_hook init to ensure correct path
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix(proxy): change default port from 5000 to 15721
Port 5000 conflicts with AirPlay Receiver on macOS 12+.
Also adds error handling for proxy toggle and i18n placeholder updates.
* fix(proxy): replace unwrap/expect with graceful error handling
- Handle HTTP client initialization failure with no_proxy fallback
- Fix potential panic on Unicode slicing in API key preview
- Add proper error handling for response body builder
- Handle edge case where SystemTime is before UNIX_EPOCH
* fix(proxy): handle UTF-8 char boundary when truncating request body log
Rust strings are UTF-8 encoded, slicing at a fixed byte index may cut
in the middle of a multi-byte character (e.g., Chinese, emoji), causing
a panic. Use is_char_boundary() to find the nearest safe cut point.
* fix(proxy): improve robustness and prevent panics
- Add reqwest socks feature to support SOCKS proxy environments
- Fix UTF-8 safety in masked_key/masked_access_token (use chars() instead of byte slicing)
- Fix UTF-8 boundary check in usage_script HTTP response truncation
- Add defensive checks for JSON operations in proxy service
- Remove verbose debug logs that could trigger panic-prone code paths
The Flatpak was failing to load because libayatana-appindicator3 is not
included in org.gnome.Platform runtime. This adds the required modules:
- libayatana-ido
- libdbusmenu-gtk3
- libayatana-indicator
- libayatana-appindicator
Fixes panic: 'Failed to load ayatana-appindicator3 or appindicator3 dynamic library'
Co-authored-by: Said John <said.john@gmail.com>
- Convert system requirements to table format with architecture info
- Add Windows/macOS file descriptions with recommended options
- Replace Linux section with comprehensive distro-based table
- Include complete installation commands for deb/rpm/AppImage/flatpak
- Add helpful tips for macOS Gatekeeper and AppImage usage
* docs: add download options for each system version in 3.9.0 release note.
* docs: add download options for each system version in 3.9.0 release note.
- Bump version from 3.9.0-3 to 3.9.0 across all config files
- Add comprehensive release notes in English, Chinese, and Japanese
- Update CHANGELOG with v3.9.0 stable and v3.9.0-2 entries
- Update README badges and release note links to v3.9.0
* fix(proxy): improve header handling for Claude API compatibility
- Streamline header blacklist by removing overly aggressive filtering
(browser-specific headers like sec-fetch-*, accept-language)
- Ensure anthropic-beta header always includes 'claude-code-20250219'
marker required by upstream services for request validation
- Centralize anthropic-version header handling in forwarder to prevent
duplicate headers across different auth strategies
- Add ?beta=true query parameter to /v1/messages endpoint for
compatibility with certain upstream services (e.g., DuckCoding)
- Remove redundant anthropic-version from ClaudeAdapter auth headers
as it's now managed exclusively by the forwarder
This improves proxy reliability with various Claude API endpoints
and third-party services that have specific header requirements.
* style(services): use inline format arguments in format strings
Apply Rust 1.58+ format string syntax across provider and skill
services. This replaces format!("msg {}", var) with format!("msg {var}")
for improved readability and consistency with modern Rust idioms.
Changed files:
- services/provider/mod.rs: 1 format string
- services/skill.rs: 10 format strings (error messages, log statements)
No functional changes, purely stylistic improvement.
* fix(proxy): restrict Anthropic headers to Claude adapter only
- Move anthropic-beta and anthropic-version header handling inside
Claude-specific condition to avoid sending unnecessary headers
to Codex and Gemini APIs
- Update test cases to reflect ?beta=true query parameter behavior
- Add edge case tests for non-messages endpoints and existing queries
- Add defaultTab prop to SettingsPage for external tab control
- UpdateBadge click now opens settings directly to About tab
- Settings button still opens to General tab (default behavior)
- Change update badge text from version number to "Update available"
- Add user-friendly dialog when database init or schema migration fails
- Support retry mechanism instead of crashing the app
- Include bilingual (Chinese/English) error messages with troubleshooting tips
- Add comprehensive test for v3.8 → current schema migration path
- Add RPM bundle to Linux build targets in CI workflow
- Add Flatpak manifest, desktop entry, and AppStream metainfo
- Update release workflow to build and publish .rpm and .flatpak artifacts
- Update README docs with new Linux package formats and installation instructions
- Add .gitignore rules for Flatpak build artifacts
When proxy takeover is enabled, switching providers no longer writes to
the Live config. However, if model override fields (ANTHROPIC_MODEL,
ANTHROPIC_REASONING_MODEL, etc.) remain in the Live config, Claude Code
continues sending requests with the old model name, causing failures
when the new provider doesn't support that model.
This fix:
- Removes model override env keys from Claude Live config during takeover
- Adds cleanup when switching providers in takeover mode
- Fixes has_mapping() to include reasoning_model in the check
- Adds test coverage for reasoning-only model mapping scenarios
The common config checkbox was only auto-enabled for the first preset
(custom) because hasInitializedNewMode ref was permanently locked to
true after first initialization.
Add selectedPresetId parameter to all three common config hooks and
reset the initialization flag when preset changes, allowing the
initialization logic to re-run for each preset switch.
Previously only removed base_url from model_providers.* tables. Now removes
the entire model_providers section since all its fields (name, base_url,
wire_api, requires_openai_auth) are provider-specific configuration.
MCP servers configuration remains preserved as it's provider-agnostic.
- Change extraction source from current active provider to editor's live content
- Add i18n support for JSON parse error messages via invalid_json_format_error()
- Simplify API by removing unused providerId parameter
- Update button labels and error messages in zh/en/ja locales
- Replace regex patterns with toml_edit for precise field removal
- Only remove top-level model/model_provider/base_url fields
- Only remove base_url from [model_providers.*] tables
- Add regression test to ensure [mcp_servers.*] base_url is preserved
- Add backend command to extract common config snippet from current provider
- Automatically extract common config on first run after importing default provider
- Auto-enable common config checkbox in new provider mode when snippet exists
- Refactor Gemini common config to operate on .env instead of config.json
- Add "Extract from Current" button to all three common config modals
- Update i18n translations for new extraction feature
- Add import button next to discover button in skills page header
- Expose openImport method via UnifiedSkillsPanel ref
- Show toast instead of dialog when no unmanaged skills found
- Remove redundant buttons from empty state view
The hover:scale-[1.01] effect caused cards to overflow their container
boundaries. Keeping only hover:shadow-lg provides sufficient visual
feedback without the overflow issue.
Use isFetching instead of just isLoading to show the loading spinner.
isLoading is only true on initial load, while isFetching is true
during any fetch operation including refetch.
The refresh button is only needed in the discovery panel (to fetch
latest skills from GitHub). The installed skills panel uses local
database which auto-updates on install/uninstall operations.
- Remove "Claude" prefix from Skills management title
- Update descriptions to include Gemini alongside Claude Code/Codex
- Applied to all three locales: zh, en, ja
- Use same DRAG_BAR_HEIGHT (28px) and HEADER_HEIGHT (64px) as App.tsx
- Remove border-b divider line from header
- Add rounded-lg class to back button for consistency
- Remove duplicate "Repo Manager" button from installed skills view
(should only appear in discovery view)
- Remove redundant back button from SkillsPage component
(header already provides unified navigation)
- Clean up unused openRepoManagerOnDiscovery state and related useEffect
- Remove unused onClose prop and ArrowLeft import from SkillsPage
- Introduce SSOT (Single Source of Truth) at ~/.cc-switch/skills/
- Add three-app toggle support (Claude/Codex/Gemini) for each skill
- Refactor frontend to use TanStack Query hooks instead of manual state
- Add UnifiedSkillsPanel for managing installed skills with app toggles
- Add useSkills.ts with declarative data fetching hooks
- Extend skills.ts API with unified install/uninstall/toggle methods
- Support importing unmanaged skills from app directories
- Add v2→v3 database migration for new skills table structure
* i18n: update cache terminology across all languages
- Change 'Cache Read' to 'Cache Hit' in all languages
- Change 'Cache Write' to 'Cache Creation' in all languages
- Update zh: 缓存读取 → 缓存命中, 缓存写入 → 缓存创建
- Update en: Cache Read → Cache Hit, Cache Write → Cache Creation
- Update ja: キャッシュ読取 → キャッシュヒット, キャッシュ書込 → キャッシュ作成
Affected keys: cacheReadTokens, cacheCreationTokens, cacheReadCost,
cacheWriteCost, cacheRead, cacheWrite
* feat(usage): add cache metrics to trend chart
- Add cache creation tokens visualization (orange line)
- Add cache hit tokens visualization (purple line)
- Add gradient definitions for new cache metrics
- Include cache data in hourly aggregation
- Display cache metrics alongside input/output tokens
This provides better visibility into cache usage patterns over time.
* fix(usage): fix timezone handling in datetime picker
- Add timestampToLocalDatetime() to convert Unix timestamp to local datetime
- Add localDatetimeToTimestamp() with validation for incomplete input
- Fix issue where typing hours/minutes would jump to previous day
- Validate datetime format completeness before conversion
- Use local timezone instead of UTC for datetime-local input
This resolves the issue where users couldn't fine-tune time selection
and the input would jump unexpectedly when editing hours or minutes.
* feat(usage): add auto-refresh for usage statistics
- Add 30-second auto-refresh interval for all usage queries
- Disable background refresh to save resources
- Apply to: summary, trends, provider stats, model stats, request logs
- Queries automatically update when tab is active
- Pause refresh when user switches to another tab
This keeps usage data fresh without manual refresh.
* fix(proxy): improve usage logging and cache token parsing
- Log requests even when usage parsing fails (with default values)
- Add detailed debug logging for usage metrics
- Support cache_read_input_tokens field in Codex responses
- Fallback to input_tokens_details.cached_tokens if needed
- Add test case for cached_tokens in input_tokens_details
- Ensure all requests are tracked in database for analytics
This fixes missing request logs when API responses lack usage data
and improves cache token detection across different response formats.
* style(rust): use inline format args in format! macros
- Replace format!("...", var) with format!("...{var}")
- Update universal provider ID formatting
- Update error message formatting
- Update config.toml generation in Codex provider
Fixes clippy::uninlined_format_args warnings.
* feat(proxy): enhance provider router logging
- Add debug logs for failover queue provider count
- Log circuit breaker state for each provider check
- Add logs for missing current provider scenarios
- Log when no current provider is configured
- Use inline format args for better readability
This improves debugging of provider selection and failover behavior.
* feat(database): update model pricing data
- Update Claude models to full version format (e.g. claude-opus-4-5-20251101)
- Add GPT-5.2 series model pricing (10 models)
- Add GPT-5.1 series model pricing (10 models)
- Add GPT-5 series model pricing (12 models)
- Add Gemini 3 series model pricing (2 models)
- Update Gemini 2.5 series model ID format (use dot separator)
- Unify display names by removing thinking level suffixes
* fix(usage): correct Gemini output token calculation
Fix Gemini API output token parsing to use totalTokenCount - promptTokenCount
instead of candidatesTokenCount alone. This ensures thoughtsTokenCount is
included in output statistics.
- Update from_gemini_response to calculate output from total - input
- Update from_gemini_stream_chunks with same logic for consistency
- Fix from_codex_stream_events to use adjusted token calculation
- Add test case for responses with thoughtsTokenCount
- Update existing tests to match new calculation logic
* fix(usage): correct cache token billing and add Codex format auto-detection
- Avoid double-billing cache tokens by subtracting from input before calculation
- Add smart Codex parser that auto-detects OpenAI vs Codex API format
- Extract model name from Codex responses for accurate tracking
* fix(proxy): improve takeover detection with live config check
- Add live config takeover detection for hot-switch decision
- Rebuild takeover when backup is missing or placeholder remains
- Make detect_takeover_in_live_config_for_app public
- Fix is_takeover_active to use actual takeover status
* refactor(usage): simplify model pricing lookup by removing suffix fallback
Replace complex suffix-stripping fallback with direct prefix/suffix cleanup.
Model IDs are now cleaned by removing vendor prefix (before /) and colon
suffix (after :), then matched exactly against pricing table.
* feat(database): add Chinese AI model pricing data
Add pricing for domestic AI models (CNY/1M tokens):
- Doubao-Seed-Code (ByteDance)
- DeepSeek V3/V3.1/V3.2
- Kimi K2/K2-Thinking/K2-Turbo (Moonshot)
- MiniMax M2/M2.1/M2.1-Lightning
- GLM-4.6/4.7 (Zhipu)
- Mimo V2 Flash (Xiaomi)
Also fix test case to use correct model ID and remove invalid currency column.
* refactor(proxy): improve header forwarding with blacklist approach
Change from whitelist to blacklist mode for request header forwarding.
Only skip headers that will be overridden (auth, host, content-length).
This preserves client's original headers and improves compatibility.
* fix(proxy): bypass timeout and retry configs when failover is disabled
When auto_failover_enabled is false, timeout and retry configurations
should not affect normal request flow. This change ensures:
- create_forwarder: passes 0 for all timeout/retry params when failover
is disabled, effectively bypassing these checks
- streaming_timeout_config: returns 0 for both first_byte_timeout and
idle_timeout when failover is disabled
This prevents unnecessary timeout errors and retry attempts when users
have explicitly disabled the failover feature.
* fix(proxy): handle zero value input in failover config fields
* refactor(proxy): remove retry logic and add enabled check for failover
* refactor(proxy): distinguish circuit-open from no-provider errors
* Align usage stats to sliding windows
* feat(proxy): add body and header filtering for upstream requests
* feat(proxy): enable transparent passthrough for headers
- Passthrough anthropic-beta header as-is from client
- Passthrough anthropic-version header from client
- Passthrough client IP headers (x-forwarded-for, x-real-ip) by default
- Filter private params (underscore-prefixed fields) from request body
- No database changes required
* feat(proxy): extract session ID from client requests for logging
- Add SessionIdExtractor to parse session ID from Claude/Codex requests
- Support extraction from metadata.user_id, headers, previous_response_id
- Pass session_id through RequestContext to usage logger
- Enable request correlation by session in proxy_request_logs
- Change from UTC to local timezone for daily/hourly trends
- Use SQLite 'localtime' modifier for date grouping
- Replace single().unwrap() with earliest().unwrap_or_else()
to handle DST transition edge cases gracefully
On Windows, npx, npm, yarn, pnpm, node, bun, and deno are actually
.cmd batch files that require cmd /c wrapper to execute properly.
This fixes the Claude Code /doctor warning:
"Windows requires 'cmd /c' wrapper to execute npx"
The transformation is applied when exporting MCP config to ~/.claude.json:
- Before: {"command": "npx", "args": ["-y", "foo"]}
- After: {"command": "cmd", "args": ["/c", "npx", "-y", "foo"]}
Uses conditional compilation (#[cfg(windows)]) for zero overhead on
other platforms.
Closes#453
On Windows, opening the Settings > About section would spawn three
terminal windows when checking CLI tool versions (claude, codex, gemini).
Root cause:
- scan_cli_version() directly executed .cmd files, but child processes
(node.exe) spawned by these batch scripts didn't inherit CREATE_NO_WINDOW
- PATH separator used Unix-style ":" instead of Windows ";"
Fix:
- Wrap command execution with `cmd /C` to ensure all child processes
run within the same hidden console session
- Use platform-specific PATH separators via conditional compilation
Two bugs were caused by ref synchronization race condition in commit 7d495aa:
- EditProviderDialog: provider prop was null on first render
- UsageScriptModal: conditional render guard was false on first click
Root cause: useEffect updates ref asynchronously, but render happens before
effect runs. On first click, ref is still null causing components to fail.
Solution: Create useLastValidValue hook that updates ref synchronously during
render phase instead of in useEffect. This ensures ref is always in sync with
state, eliminating the race condition.
Changes:
- Add useLastValidValue hook for preserving last valid value during animations
- Replace manual ref + useEffect pattern with the new hook
- Remove non-null assertions (!) that were needed as workaround
* fix(schema): add missing base columns migration for proxy_config
Add compatibility migration for older databases that may be missing
the basic proxy_config columns (proxy_enabled, listen_address,
listen_port, enable_logging) before adding newer timeout fields.
* fix: add proxy_config base column patches for v3.9.0-2 upgrade
Add base config column patches in create_tables_on_conn():
- proxy_enabled
- listen_address
- listen_port
- enable_logging
Ensures v3.9.0-2 users (user_version=2 but missing columns)
can properly upgrade with all required fields added.
* fix: migrate proxy_config singleton to per-app on startup for v2 databases
Add startup migration for legacy proxy_config tables that still have
singleton structure (no app_type column) even with user_version=2.
This fixes the issue where v3.9.0-2 databases with v2 schema but legacy
proxy_config structure would fail with "no such column: app_type" error.
- Call migrate_proxy_config_to_per_app in create_tables_on_conn
- Add regression test to verify the fix
* style: cargo fmt
---------
Co-authored-by: Jason <farion1231@gmail.com>
* chore: 更新 vite 版本 && 使用 code-inspector-plugin 方便从前端定位到代码位置
* fix: update tailwind config path and conditionally load code-inspector-plugin
- Update components.json to reference tailwind.config.cjs instead of deleted tailwind.config.js
- Load codeInspectorPlugin only in dev mode to avoid unnecessary code in production builds
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix(mcp): 移除同步Codex Provider时的旧MCP同步调用
sync_enabled_to_codex使用旧的config.mcp.codex结构,
在v3.7.0统一结构中该字段为空,导致MCP配置被错误清除。
MCP同步应通过McpService进行。
Fixes#403
* test(mcp): update test to reflect new MCP sync architecture
Remove MCP-related assertions from sync_codex_provider_writes_auth_and_config
test since provider switching no longer triggers MCP sync in v3.7.0+.
MCP synchronization is now handled independently by McpService,
not as part of the provider switch flow.
---------
Co-authored-by: Jason <farion1231@gmail.com>
On macOS, the auto-launch library requires the .app bundle path (e.g.,
/Applications/CC Switch.app) rather than the binary path inside the bundle
(e.g., .app/Contents/MacOS/CC Switch). Using the binary path directly
causes AppleScript login items to open a terminal window.
This fix extracts the .app bundle path from current_exe() on macOS,
ensuring proper integration with macOS login items.
Closes#375
* feat: add Universal Provider feature
- Add Universal Provider data structures and type definitions
- Implement backend CRUD operations and sync functionality
- Add frontend UI components (UniversalProviderPanel, Card, FormModal)
- Add NewAPI icon and preset configuration
- Support cross-app (Claude/Codex/Gemini) configuration sync
- Add website URL field for providers
- Implement real-time refresh via event notifications
- Add i18n support (Chinese/English/Japanese)
* feat: integrate universal provider presets into add provider dialog
- Add universal provider presets (NewAPI, Custom Gateway) to preset selector
- Show universal presets with Layers icon badge in preset selector
- Open UniversalProviderFormModal when universal preset is clicked
- Pass initialPreset to auto-fill form when opened from add dialog
- Add i18n keys for addSuccess/addFailed messages
- Keep separate universal provider panel for management
* refactor: move universal provider management to add dialog
- Remove Layers button from main navigation header
- Add 'Manage' button next to universal provider presets
- Open UniversalProviderPanel from within add provider dialog
- Add i18n keys for 'manage' in all locales
* style: display universal provider presets on separate line
- Move universal provider section to a new row with border separator
- Add label '统一供应商:' to clarify the section
* style: unify universal provider label style with preset label
- Use FormLabel component for consistent styling
- Add background to 'Manage' button matching preset buttons
- Update icon size and button padding for consistency
* feat: add sync functionality and JSON preview for Universal Provider
* fix: add missing in_failover_queue field to Provider structs
After rebasing to main, the Provider struct gained a new
`in_failover_queue` field. This fix adds the missing field
to the three to_*_provider() methods in UniversalProvider.
* refactor: redesign AddProviderDialog with tab-based layout
- Add tabs to separate app-specific providers and universal providers
- Move "Add Universal Provider" button from panel header to footer
- Remove unused handleAdd callback and clean up imports
- Update emptyHint i18n text to reference the footer button
* fix: append /v1 suffix to Codex base_url in Universal Provider
Codex uses OpenAI-compatible API which requires the /v1 endpoint suffix.
The Universal Provider now automatically appends /v1 to base_url when
generating Codex provider config if not already present.
- Handle trailing slashes to avoid double slashes
- Apply fix to both backend (to_codex_provider) and frontend preview
* feat: auto-sync universal provider to apps on creation
Previously, users had to manually click sync after adding a universal
provider. Now it automatically syncs to Claude/Codex/Gemini on creation,
providing a smoother user experience.
---------
Co-authored-by: Jason <farion1231@gmail.com>
- Make all sponsor logos clickable links in README files (EN/ZH/JA)
- Replace ShanDianShuo with DMXAPI sponsor
- Add DMXAPI logo images (dmx-en.jpg, dmx-zh.jpeg)
- Unify sponsor list across all language versions
Replace hardcoded Tailwind color classes with design system CSS variables
to improve text visibility in dark mode:
- text-gray-900 dark:text-gray-100 → text-foreground
- text-gray-500/600 dark:text-gray-400 → text-muted-foreground
- bg-white dark:bg-gray-800 → bg-background
Add has_column check before inserting seed data into proxy_config table.
This prevents SQL errors when upgrading from older databases where
proxy_config was a singleton table without the app_type column.
The migration function will handle the table structure upgrade and
insert the three rows after converting to the new schema.
* feat(proxy): extract model name from API response for accurate usage tracking
- Add model field extraction in TokenUsage parsing for Claude, OpenAI, and Codex
- Prioritize response model over request model in usage logging
- Update model extractors to use parsed usage.model first
- Add tests for model extraction in stream and non-stream responses
* feat(proxy): implement streaming timeout control with validation
- Add first byte timeout (0 or 1-180s) for streaming requests
- Add idle timeout (0 or 60-600s) for streaming data gaps
- Add non-streaming timeout (0 or 60-1800s) for total request
- Implement timeout logic in response processor
- Add 1800s global timeout fallback when disabled
- Add database schema migration for timeout fields
- Add i18n translations for timeout settings
* feat(proxy): add model mapping module for provider-based model substitution
- Add model_mapper.rs with ModelMapping struct to extract model configs from Provider
- Support ANTHROPIC_MODEL, ANTHROPIC_REASONING_MODEL, and default models for haiku/sonnet/opus
- Implement thinking mode detection for reasoning model priority
- Include comprehensive unit tests for all mapping scenarios
* fix(proxy): bypass circuit breaker for single provider scenario
When failover is disabled (single provider), circuit breaker open state
would block all requests causing poor UX. Now bypasses circuit breaker
check in this scenario. Also integrates model mapping into request flow.
* feat(ui): add reasoning model field to Claude provider form
Add ANTHROPIC_REASONING_MODEL configuration field for Claude providers,
allowing users to specify a dedicated model for thinking/reasoning tasks.
* feat(proxy): add openrouter_compat_mode for optional format conversion
Add configurable OpenRouter compatibility mode that enables Anthropic to
OpenAI format conversion. When enabled, rewrites endpoint to /v1/chat/completions
and transforms request/response formats. Defaults to enabled for OpenRouter.
* feat(ui): add OpenRouter compatibility mode toggle
Add UI toggle for OpenRouter providers to enable/disable compatibility
mode which uses OpenAI Chat Completions format with SSE conversion.
* feat(stream-check): use provider-configured model for health checks
Extract model from provider's settings_config (ANTHROPIC_MODEL, GEMINI_MODEL,
or Codex config.toml) instead of always using default test models.
* refactor(ui): remove timeout settings from AutoFailoverConfigPanel
Remove streaming/non-streaming timeout configuration from failover panel
as these settings have been moved to a dedicated location.
* refactor(database): migrate proxy_config to per-app three-row structure
Replace singleton proxy_config table with app_type primary key structure,
allowing independent proxy settings for Claude, Codex, and Gemini.
Add GlobalProxyConfig queries and per-app config management in DAO layer.
* feat(proxy): add GlobalProxyConfig and AppProxyConfig types
Add new type definitions for the refactored proxy configuration:
- GlobalProxyConfig: shared settings (enabled, address, port, logging)
- AppProxyConfig: per-app settings (failover, timeouts, circuit breaker)
* refactor(proxy): update service layer for per-app config structure
Adapt proxy service, handler context, and provider router to use
the new per-app configuration model. Read enabled/timeout settings
from proxy_config table instead of settings table.
* feat(commands): add global and per-app proxy config commands
Add new Tauri commands for the refactored proxy configuration:
- get_global_proxy_config / update_global_proxy_config
- get_proxy_config_for_app / update_proxy_config_for_app
Update startup restore logic to read from proxy_config table.
* feat(api): add frontend API and Query hooks for proxy config
Add TypeScript wrappers and TanStack Query hooks for:
- Global proxy config (address, port, logging)
- Per-app proxy config (failover, timeouts, circuit breaker)
- Proxy takeover status management
* refactor(ui): redesign proxy panel with inline config controls
Replace ProxySettingsDialog with inline controls in ProxyPanel.
Add per-app takeover switches and global address/port settings.
Simplify AutoFailoverConfigPanel by removing timeout settings.
* feat(i18n): add proxy takeover translations and update types
Add i18n strings for proxy takeover status in zh/en/ja.
Update TypeScript types for GlobalProxyConfig and AppProxyConfig.
* refactor(proxy): load circuit breaker config per-app instead of globally
Extract app_type from router key and read circuit breaker settings
from the corresponding proxy_config row for each application.
Add a terminal button next to each provider card that opens a new terminal
window with that provider's specific API configuration. This allows using
different providers independently without changing the global setting.
Changes:
- Backend: Add `open_provider_terminal` command that extracts provider
config and creates a temporary claude settings file
- Frontend: Add terminal button to provider cards with proper callback
propagation through component hierarchy
- Support macOS (Terminal.app), Linux (gnome-terminal, konsole, etc.),
and Windows (cmd)
Each provider gets a unique config file named `claude_<providerId>_<pid>.json`
in the temp directory, containing the provider's API configuration.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Wrap FullScreenPanel content with AnimatePresence for exit animation
- Add exit={{ opacity: 0 }} to enable fade-out on close
- Use useRef + useEffect to preserve provider data during exit animation
- Follow React best practices by updating refs in useEffect instead of render
Affected components:
- EditProviderDialog
- UsageScriptModal
- AddProviderDialog
- McpFormModal
- ProxySettingsDialog
- Change right-side button container from min-h-[40px] to h-[32px]
for more compact header layout
- Remove conditional padding (pt-6/pt-4) from main content area
to eliminate layout jump during view transitions
* feat(failover): add auto-failover master switch with proxy integration
- Add persistent auto_failover_enabled setting in database
- Add get/set_auto_failover_enabled commands
- Provider router respects master switch state
- Proxy shutdown automatically disables failover
- Enabling failover auto-starts proxy server
- Optimistic updates for failover queue toggle
* feat(proxy): persist proxy takeover state across app restarts
- Add proxy_takeover_{app_type} settings for per-app state tracking
- Restore proxy takeover state automatically on app startup
- Preserve state on normal exit, clear on manual stop
- Add stop_with_restore_keep_state method for graceful shutdown
* fix(proxy): set takeover state for all apps in start_with_takeover
* fix(windows): hide console window when checking CLI versions
Add CREATE_NO_WINDOW flag to prevent command prompt from flashing
when detecting claude/codex/gemini CLI versions on Windows.
* refactor(failover): make auto-failover toggle per-app independent
- Change setting key from 'auto_failover_enabled' to 'auto_failover_enabled_{app_type}'
- Update provider_router to check per-app failover setting
- When failover disabled, use current provider only; when enabled, use queue order
- Add unit tests for failover enabled/disabled behavior
* feat(failover): auto-switch to higher priority provider on recovery
- After circuit breaker reset, check if recovered provider has higher priority
- Automatically switch back if queue_order is lower (higher priority)
- Stream health check now resets circuit breaker on success/degraded
* chore: remove unused start_proxy_with_takeover command
- Remove command registration from lib.rs
- Add comment clarifying failover queue is preserved on proxy stop
* feat(ui): integrate failover controls into provider cards
- Add failover toggle button to provider card actions
- Show priority badge (P1, P2, ...) for queued providers
- Highlight active provider with green border in failover mode
- Sync drag-drop order with failover queue
- Move per-app failover toggle to FailoverQueueManager
- Simplify SettingsPage failover section
* test(providers): add mocks for failover hooks in ProviderList tests
* refactor(failover): merge failover_queue table into providers
- Add in_failover_queue field to providers table
- Remove standalone failover_queue table and related indexes
- Simplify queue ordering by reusing sort_index field
- Remove reorder_failover_queue and set_failover_item_enabled commands
- Update frontend to use simplified FailoverQueueItem type
* fix(database): ensure in_failover_queue column exists for v2 databases
Add column check in create_tables to handle existing v2 databases
that were created before the failover queue refactor.
* fix(ui): differentiate active provider border color by proxy mode
- Use green border/gradient when proxy takeover is active
- Use blue border/gradient in normal mode (no proxy)
- Improves visual distinction between proxy and non-proxy states
* fix(database): clear provider health record when removing from failover queue
When a provider is removed from the failover queue, its health monitoring
is no longer needed. This change ensures the health record is also deleted
from the database to prevent stale data.
* fix(failover): improve cache cleanup for provider health and circuit breaker
- Use removeQueries instead of invalidateQueries when stopping proxy to
completely clear health and circuit breaker caches
- Clear provider health and circuit breaker caches when removing from
failover queue
- Refresh failover queue after drag-sort since queue order depends on
sort_index
- Only show health badge when provider is in failover queue
* style: apply prettier formatting to App.tsx and ProviderList.tsx
* fix(proxy): handle missing health records and clear health on proxy stop
- Return default healthy state when provider health record not found
- Add clear_provider_health_for_app to clear health for specific app
- Clear app health records when stopping proxy takeover
* fix(proxy): track actual provider used in forwarding for accurate logging
Introduce ForwardResult and ForwardError structs to return the actual
provider that handled the request. This ensures usage statistics and
error logs reflect the correct provider after failover.
Add min-height to right-side button container and ml-auto to add buttons
in MCP/Prompts views to maintain consistent header height and button
position across all views.
* feat(failover): add auto-failover master switch with proxy integration
- Add persistent auto_failover_enabled setting in database
- Add get/set_auto_failover_enabled commands
- Provider router respects master switch state
- Proxy shutdown automatically disables failover
- Enabling failover auto-starts proxy server
- Optimistic updates for failover queue toggle
* feat(proxy): persist proxy takeover state across app restarts
- Add proxy_takeover_{app_type} settings for per-app state tracking
- Restore proxy takeover state automatically on app startup
- Preserve state on normal exit, clear on manual stop
- Add stop_with_restore_keep_state method for graceful shutdown
* fix(proxy): set takeover state for all apps in start_with_takeover
- Add AnimatePresence + motion.div wrapper for provider list
- Use key={activeApp} to trigger enter/exit animations on app switch
- Remove redundant animate-slide-up from ProviderList to prevent
animation conflicts and visual jitter
Add a new setting to automatically skip Claude Code's onboarding screen
by writing hasCompletedOnboarding=true to ~/.claude.json. The setting
defaults to enabled for better user experience.
- Add set/clear_has_completed_onboarding functions in claude_mcp.rs
- Add Tauri commands and frontend API integration
- Add toggle in WindowSettings with i18n support (en/zh/ja)
- Fix hardcoded Chinese text in tests to use i18n keys
- Track (unchecked): lighten in light mode (gray-300 → gray-200),
darken in dark mode (gray-700 → gray-900) to blend with background
- Thumb: soften in dark mode (white → gray-400) to reduce glare
- Use consistent h-8 fixed height for all inner elements
- Standardize border-radius to rounded-xl across all sections
- Remove background from ProxyToggle for cleaner appearance
- Simplify ProxyToggle structure with nested container
- Add SVG icons for OpenRouter, LongCat, ModelScope, and AiHubMix
- Register icons in index.ts and metadata.ts with search keywords
- Link icons to corresponding provider presets in claudeProviderPresets.ts
OpenRouter now supports Claude Code compatible endpoint (/v1/messages),
eliminating the need for Anthropic ↔ OpenAI format conversion.
- Disable format transformation for OpenRouter (keep old logic as fallback)
- Pass through original endpoint instead of redirecting to /v1/chat/completions
- Add anthropic-version header for ClaudeAuth and Bearer strategies
- Update tests to reflect new passthrough behavior
Tauri 2.0 platform config merging is shallow, not deep. The Windows
config only specified titleBarStyle, causing minWidth/minHeight to
be missing on Windows. This allowed users to resize the window below
900px, causing header elements to misalign.
- Add support for ANTHROPIC_API_KEY in Claude auth extraction
- Only update existing token fields during sync, avoid adding fields
that weren't originally configured by the user
- Add tests for both scenarios
- Detect takeover residue in Live configs even when proxy is not running
- Implement 3-tier fallback: backup → SSOT → cleanup placeholders
- Only delete backup after successful restore to prevent data loss
- Fix EditProviderDialog to check current app's takeover status only
- Remove global proxy auto-start flag from config and UI.
- Simplify per-app takeover start/stop and stop server when the last takeover is disabled.
- Restore live takeover detection used for crash recovery.
- Keep proxy_config.enabled column but always write 0 for compatibility.
- Tests: not run (not requested).
- Remove setTimeout delay that could be cancelled on component unmount
- Invalidate all providers cache (not just current app) since import affects all apps
- Call onImportSuccess before sync to ensure UI refresh even if sync fails
- Update i18n: "Data refreshed" (past tense, reflecting immediate action)
* fix(ui): improve AboutSection styling and version detection
- Add framer-motion animations for smooth page transitions
- Unify button sizes and add icons for consistency
- Add gradient backgrounds and hover effects to cards
- Add notInstalled i18n translations (zh/en/ja)
- Fix version detection when stdout/stderr is empty
* fix(proxy): persist per-app takeover state across app restarts
- Fix proxy toggle color to reflect current app's takeover state only
- Restore proxy service on startup if Live config is still in takeover state
- Preserve per-app backup records instead of clearing all on restart
- Only recover Live config when proxy service fails to start
The #[cfg(target_os = "macos")] restriction was a historical artifact
from when RunEvent was only used for macOS-specific events (Reopen, Opened).
After c9ea13a added ExitRequested handling for all platforms, the import
should have been updated but was overlooked.
The Skills button transition was accidentally removed in commit 1fb2d5e
when adding multi-app support. This restores the smooth fade-in/out
animation when switching between apps that support Skills (Claude/Codex)
and those that don't (Gemini).
Add guard functions to check if Claude/Codex/Gemini CLI has been
initialized before attempting to sync MCP configurations. This prevents
creating unwanted config files in directories that don't exist.
- Claude: check ~/.claude dir OR ~/.claude.json file exists
- Codex: check ~/.codex dir exists
- Gemini: check ~/.gemini dir exists
When the target app is not installed, sync operations now silently
succeed without writing any files, allowing users to manage MCP servers
for apps they actually use without side effects on others.
- Remove server from live config when app is disabled during upsert
- Merge enabled flags instead of overwriting when importing from multiple apps
- Normalize Gemini MCP type field (url-only → sse, command → stdio)
- Use atomic write for Codex config updates
- Add tests for disable-removal, multi-app merge, and Gemini SSE import
Replace global live takeover with granular per-app control:
- Add start_proxy_server command (start without takeover)
- Add get_proxy_takeover_status to query each app's state
- Add set_proxy_takeover_for_app for individual app control
- Use live backup existence as SSOT for takeover state
- Refactor sync_live_to_provider to eliminate code duplication
- Update ProxyToggle to show status per active app
- Update Codex `model_providers.<model_provider>.base_url` to the proxy origin with `/v1`
- Add route fallbacks for `/responses` and `/chat/completions` (plus double-`/v1` safeguard)
- Add unit tests for the TOML base_url takeover logic
- Set takeover flag before writing proxy config to fix race condition
where crash during takeover left Live configs corrupted but flag unset
- Add fallback detection by checking for placeholder tokens in Live
configs when backups exist but flag is false (handles legacy/edge cases)
- Improve error handling with proper rollback at each stage of startup
- Clean up stale backups when Live configs are not in takeover state
to avoid long-term storage of sensitive tokens
Previously, UI sync was triggered only when failover happened (retry count > 1).
This missed cases where the first provider in the failover queue succeeded but
was different from the user's selected provider in settings.
Now we capture the current provider ID at request start and compare it with
the actually used provider. This ensures UI/tray always reflects the real
provider handling requests.
This commit addresses two critical issues in the proxy failover logic:
1. Circuit Breaker HalfOpen Concurrency Bug:
- Introduced `AllowResult` struct to track half-open permit usage
- Added state guard in `transition_to_half_open()` to prevent duplicate resets
- Replaced `fetch_sub` with CAS loop in `release_half_open_permit()` to prevent underflow
- Separated `is_available()` (routing) from `allow_request()` (permit acquisition)
2. Error Classification Conflation:
- Split retry logic into `should_retry_same_provider()` and `categorize_proxy_error()`
- Same-provider retry: only for transient errors (timeout, 429, 5xx)
- Cross-provider failover: now includes ConfigError, TransformError, AuthError
- 4xx errors (401/403) no longer waste retries on the same provider
- Skip live writes when takeover is active and proxy is running
- Refresh live backups from provider edits during takeover
- Sync live tokens to DB without clobbering real keys with placeholders
- Avoid injecting extra placeholder keys into Claude live env
- Reapply takeover after proxy listen address/port changes
- In takeover mode, edit dialog uses DB config and keeps API key state in sync
* feat(proxy): add error mapper for HTTP status code mapping
- Add error_mapper.rs module to map ProxyError to HTTP status codes
- Implement map_proxy_error_to_status() for error classification
- Implement get_error_message() for user-friendly error messages
- Support all error types: upstream, timeout, connection, provider failures
- Include comprehensive unit tests for all mappings
* feat(proxy): enhance error logging with context support
- Add log_error_with_context() method for detailed error recording
- Support streaming flag, session_id, and provider_type fields
- Remove dead_code warning from log_error() method
- Enable comprehensive error request tracking in database
* feat(proxy): implement error capture and logging in all handlers
- Capture and log all failed requests in handle_messages (Claude)
- Capture and log all failed requests in handle_gemini (Gemini)
- Capture and log all failed requests in handle_responses (Codex)
- Capture and log all failed requests in handle_chat_completions (Codex)
- Record error status codes, messages, and latency for all failures
- Generate unique session_id for each request
- Support both streaming and non-streaming error scenarios
* style: fix clippy warnings and typescript errors
- Add allow(dead_code) for CircuitBreaker::get_state (reserved for future)
- Fix all uninlined format string warnings (27 instances)
- Use inline format syntax for better readability
- Fix unused import and parameter warnings in ProviderActions.tsx
- Achieve zero warnings in both Rust and TypeScript
* style: apply code formatting
- Remove trailing whitespace in misc.rs
- Add trailing comma in App.tsx
- Format multi-line className in ProviderCard.tsx
* feat(proxy): add settings button to proxy panel
Add configuration buttons in both running and stopped states to
provide easy access to proxy settings dialog.
* fix(speedtest): skip client build for invalid inputs
* chore(clippy): fix uninlined format args
* Merge branch 'main' into feature/error-request-logging
- Remove `is_proxy_target` field from Provider struct (Rust & TypeScript)
- Remove related DAO methods: get_proxy_target_provider, set_proxy_target
- Remove deprecated Tauri commands: get_proxy_targets, set_proxy_target
- Add `is_available()` method to CircuitBreaker for availability checks
without consuming HalfOpen probe permits (used in select_providers)
- Keep `allow_request()` for actual request gating with permit tracking
- Update stream_check to use failover_queue instead of is_proxy_target
- Clean up commented-out reset circuit breaker button in ProviderActions
- Remove unused useProxyTargets and useSetProxyTarget hooks
Claude Code now supports hot reload, so users no longer need to restart
the terminal after switching providers. Also removes unused `restartClaude`
i18n string.
The previous fix (e081c75) only cleared the database records but
didn't invalidate the React Query cache. This caused stale health
badges to appear when restarting the proxy.
Now invalidates all providerHealth queries on stop, ensuring the
frontend fetches fresh data from the database.
Increase min-height of the name row from 20px to 28px (min-h-7)
to accommodate the health badge height, preventing layout shift
when the badge dynamically appears.
Clear all provider_health records when stopping the proxy server,
ensuring health badges reset to "healthy" state. This fixes the
inconsistency where circuit breakers (in memory) would reset on
stop but health badges (in database) would retain stale state.
Previously, only 429, 408, and 5xx errors triggered failover to the next
provider. Other 4xx errors (like 400) were considered non-retryable and
caused immediate disconnection.
This was problematic because different providers have different restrictions
(e.g., "Do not use this API outside Claude Code CLI"), and a 400 error from
one provider doesn't mean other providers will fail.
Now all upstream HTTP errors trigger failover, allowing the system to try
all configured providers before giving up.
Add FailoverSwitchManager to handle provider switching after successful
failover. This ensures the UI reflects the actual provider in use:
- Create failover_switch.rs with deduplication and async switching logic
- Pass AppHandle through ProxyService -> ProxyServer -> RequestForwarder
- Update is_current in database when failover succeeds
- Emit provider-switched event for frontend refresh
- Update tray menu and live backup synchronously
The switching runs asynchronously via tokio::spawn to avoid blocking
API responses while still providing immediate UI feedback.
- Make usage script credential fields optional with provider config fallback
- Optimize multi-plan card display: show plan count by default, expandable for details
- Add hint text to explain credential fallback mechanism
- Fix HalfOpen counter underflow: increment half_open_requests when
transitioning from Open to HalfOpen to prevent underflow in
record_success/record_failure
- Fix Gemini config field names: unify to GEMINI_API_KEY and
GOOGLE_GEMINI_BASE_URL (removed GOOGLE_API_KEY and GEMINI_API_BASE)
- Fix Codex proxy takeover: write base_url to config.toml instead of
OPENAI_BASE_URL in auth.json (Codex CLI reads from config.toml)
This commit addresses several critical issues in the failover system:
**Circuit breaker state persistence (previous fix)**
- Promote ProviderRouter to ProxyState for cross-request state sharing
- Remove redundant router.rs module
- Fix 429 errors to be retryable (rate limiting should try other providers)
**Hot-update circuit breaker config**
- Add update_circuit_breaker_configs() to ProxyServer and ProxyService
- Connect update_circuit_breaker_config command to running circuit breakers
- Add reset_provider_circuit_breaker() for manual breaker reset
**Fix HalfOpen deadlock bug**
- Change half_open_requests from cumulative count to in-flight count
- Release quota in record_success()/record_failure() when in HalfOpen state
- Prevents permanent deadlock when success_threshold > 1
**Fix duplicate select_providers() call**
- Store providers list in RequestContext, pass to forward_with_retry()
- Avoid consuming HalfOpen quota twice per request
- Single call to select_providers() per request lifecycle
**Add per-provider retry with exponential backoff**
- Implement forward_with_provider_retry() with configurable max_retries
- Backoff delays: 100ms, 200ms, 400ms, etc.
Add a new failover queue system that operates independently from provider
sortIndex, allowing users to configure failover order per app type.
Backend changes:
- Add failover_queue table to schema.rs for persistent storage
- Create dao/failover.rs with CRUD operations for queue management
- Add Tauri commands for queue operations (get, add, remove, reorder, toggle)
- Refactor provider_router.rs select_providers() to use failover queue:
- Current provider always takes first priority
- Queue providers ordered by queue_order as fallback
- Only providers with open circuit breakers are included
Frontend changes:
- Add FailoverQueueItem type to proxy.ts
- Extend failover.ts API with queue management methods
- Add React Query hooks for queue data fetching and mutations
- Create FailoverQueueManager component with drag-and-drop reordering
- Integrate queue management into SettingsPage under "Auto Failover"
- Add i18n translations for zh and en locales
When the app crashes or is force-killed while proxy mode is active,
the live config files remain pointing to the dead proxy server with
placeholder tokens, causing CLI tools to fail.
This change adds startup detection:
- Check `live_takeover_active` flag on app launch
- If flag is true but proxy is not running → abnormal exit detected
- Automatically restore live configs from database backup
- Clear takeover flag and delete backups
The recovery runs before auto-start, ensuring correct sequence even
when proxy auto-start is enabled.
Extract common request handling logic into dedicated modules:
- handler_config.rs: Usage parser configurations for each API type
- handler_context.rs: Request lifecycle context management
- response_processor.rs: Unified streaming/non-streaming response handling
Reduces handlers.rs from ~1130 lines to ~418 lines (-63%), eliminating
repeated initialization and response processing patterns across the
four API handlers (Claude, Codex Chat, Codex Responses, Gemini).
When proxy is active, switching providers only updated the database flags
but not the live backup. This caused the wrong provider config to be
restored when stopping the proxy.
Added `update_live_backup_from_provider()` method to ProxyService that
generates backup from provider's settings_config instead of reading from
live files (which are already taken over by proxy).
The previous cleanup logic only sent a shutdown signal but didn't wait
for the proxy server to actually stop. This caused a race condition
where the app would exit before cleanup completed, leaving Live configs
in an inconsistent state.
Changes:
- Add `server_handle` field to ProxyServer to track the spawned task
- Modify `stop()` to wait for server task completion (5s timeout)
- Add 100ms delay before process exit to ensure I/O flush
- Export ProxyService and fix test files that were missing proxy_service field
* feat(ui): add color prop support to ProviderIcon component
* feat(health): add stream check core functionality
Add new stream-based health check module to replace model_test:
- Add stream_check command layer with single and batch provider checks
- Add stream_check DAO layer for config and log persistence
- Add stream_check service layer with retry mechanism and health status evaluation
- Add frontend HealthStatusIndicator component
- Add frontend useStreamCheck hook
This provides more comprehensive health checking capabilities.
* refactor(health): replace model_test with stream_check
Replace model_test module with stream_check across the codebase:
- Remove model_test command and service modules
- Update command registry in lib.rs to use stream_check commands
- Update module exports in commands/mod.rs and services/mod.rs
- Remove frontend useModelTest hook
- Update stream_check command implementation
This refactoring provides clearer naming and better separation of concerns.
* refactor(db): clean up unused database tables and optimize schema
Remove deprecated and unused database tables:
- Remove proxy_usage table (replaced by proxy_request_logs)
- Remove usage_daily_stats table (aggregation done on-the-fly)
- Rename model_test_logs to stream_check_logs with updated schema
- Remove related DAO methods for proxy_usage
- Update usage_stats service to use proxy_request_logs only
- Refactor usage_script to work with new schema
This simplifies the database schema and removes redundant data storage.
* refactor(ui): update frontend components for stream check
Update frontend components to use stream check API:
- Refactor ModelTestConfigPanel to use stream check config
- Update API layer to use stream_check commands
- Add HealthStatus type and StreamCheckResult interface
- Update ProviderList to use new health check integration
- Update AutoFailoverConfigPanel with stream check references
- Improve UI layout and configuration options
This completes the frontend migration from model_test to stream_check.
* feat(health): add configurable test models and reasoning effort support
Enhance stream check service with configurable test models:
- Add claude_model, codex_model, gemini_model to StreamCheckConfig
- Support reasoning effort syntax (model@level or model#level)
- Parse and apply reasoning_effort for OpenAI-compatible models
- Remove hardcoded model names from check functions
- Add unit tests for model parsing logic
- Remove obsolete model_test source files
This allows users to customize which models are used for health checks.
* fix(misc): improve CLI version detection with path scanning
- Extract version helper to parse semver from raw output
- Add fallback path scanning when direct execution fails
- Scan common npm global paths: .npm-global, .local/bin, homebrew
- Support nvm by scanning all node versions under ~/.nvm/versions/node
- Add platform-specific paths for macOS, Linux, and Windows
- Ensure node is in PATH when executing CLI tools from scanned paths
* fix(ui): use semantic color tokens for text foreground
- Replace hardcoded gray-900/gray-100 with text-foreground in EndpointSpeedTest
- Add text-foreground class to Input component for consistent theming
- Ensures proper text color in both light and dark modes
- Add cleanup_before_exit() to gracefully stop proxy and restore live configs
- Handle ExitRequested event to catch Cmd+Q, Alt+F4, and tray quit
- Use std::process::exit(0) after cleanup to avoid infinite loop
- Remove unused start_proxy_server and stop_proxy_server commands
- Remove unused startMutation and stopMutation from useProxyStatus hook
- Simplify API to only expose takeover mode (start_proxy_with_takeover/stop_proxy_with_restore)
Previously, when proxy was started, the enabled flag was set to true and
persisted to database. However, stopping the proxy didn't reset this flag,
causing the proxy to auto-start on every subsequent app launch.
Now the enabled flag is set to false when proxy stops, ensuring the proxy
remains off after restart unless explicitly started by the user.
When proxy takeover is activated, tokens are replaced with placeholders
in live config files. However, the proxy reads tokens from the database,
not from live files. If the user's token only exists in the live config
(e.g., manually added), the database won't have it, causing auth failures.
Changes:
- Add sync_live_to_providers() to sync tokens from live configs to DB
- Add update_provider_settings_config() DAO method for partial updates
- Use "PROXY_MANAGED" placeholder instead of empty string to avoid
"missing API key" warnings in Claude Code status bar
- Integrate sync step into start_with_takeover() flow before clearing tokens
The new takeover flow:
1. setup_proxy_targets()
2. backup_live_configs()
3. sync_live_to_providers() <- NEW
4. takeover_live_configs()
5. Start proxy server
- Increase translate offset for usage footer to prevent overlap with action buttons
- Add ease-out timing function and extend duration to 300ms for smoother animation
- Temporarily disable circuit breaker reset button to reduce button clutter
- Change provider card gradient background from hover to selected state
- Use emerald color for card hover border, selected border, and gradient
when proxy takeover mode is active
- Update "CC Switch" title to emerald when proxy is running
- Update "Enable" button to emerald in proxy takeover mode
Changes:
- Modify provider_router to select provider based on is_current flag
instead of is_proxy_target queue
- Remove proxy target toggle UI from ProviderCard
- Remove proxyPriority and allProviders props from ProviderList
- Remove isProxyTarget prop from ProviderHealthBadge
- Use start_with_takeover() for auto-start to ensure proper setup
This simplifies the proxy architecture by directly using the current
provider for proxying, eliminating the need for separate proxy target
management. Switching providers now immediately takes effect in proxy
mode.
Issues fixed:
1. Route prefix mismatch - Live config was set to use /claude, /codex,
/gemini prefixes but server only had routes without prefixes
2. Proxy targets not auto-configured - start_with_takeover only modified
Live config but didn't set is_proxy_target=true for current providers
Changes:
- Add prefixed routes (/claude/v1/messages, /codex/v1/*, /gemini/v1beta/*)
to server.rs for backward compatibility
- Remove URL prefixes from takeover_live_configs() - server can route
by API endpoint path alone (/v1/messages=Claude, /v1/chat/completions=Codex)
- Add setup_proxy_targets() method that automatically sets current providers
as proxy targets when starting proxy with takeover
- Unify proxy toggle in SettingsPage to use takeover mode (same as main UI)
- Fix error message extraction in useProxyStatus hooks
- Fix provider switch logic to check both takeover flag AND proxy running state
* dependencies: url
* fix: comprehensive security improvements for usage script execution
🛡️ Security Fixes:
- Implement robust SSRF protection with same-origin URL validation
- Add precise IP address validation for IPv4/IPv6 private networks
- Fix port comparison to handle default ports correctly (443/80)
- Remove hardcoded domain whitelist, support custom domains flexibly
- Add comprehensive input validation and hostname security checks
🔧 Technical Improvements:
- Replace string-based IP checks with proper IpAddr parsing
- Use port_or_known_default() for accurate port validation
- Add comprehensive unit tests covering edge cases
- Implement CIDR-compliant private IP detection (RFC1918)
- Fix IPv6 address validation to prevent false positives
📊 Fixed Issues:
- Prevent access to private IP addresses while allowing public services
- Support Cloudflare (172.67.x.x) and other public 172.x.x.x ranges
- Fix port matching between explicit (e.g., :443) and implicit (default) ports
- Resolve IPv6 false positives for addresses containing ::1 substrings
- Maintain backward compatibility with existing script usage patterns
✅ Testing:
- Add comprehensive test suite for IP validation (IPv4/IPv6)
- Add port comparison tests for various scenarios
- Add edge case tests for CIDR boundaries
- All tests passing, ensuring no regressions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: add is_loopback_host for proper localhost validation
* fix: use Database::memory() in tests
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(skill): add database migration and Gemini support for multi-app skills
- Refactor skills table from single key to (directory, app_type) composite primary key
- Add migration logic to convert existing skill records
- Support skill installation/uninstallation for Claude/Codex/Gemini independently
- Add new Tauri commands: get_skills_for_app, install_skill_for_app, uninstall_skill_for_app
- Update frontend API and components to support app-specific skill operations
* fix(usage): correct cache token column order in request log table
- Swap cache read and cache creation columns to match data binding
- Add whitespace-nowrap to all table headers for better display
- Change Codex version source from GitHub `openai/openai-python` to npm `@openai/codex`
- Remove unused `fetch_github_latest_release` helper function
- All three tools (Claude, Codex, Gemini) now consistently use npm registry
* feat(db): add circuit breaker config table and provider proxy target APIs
Add database support for auto-failover feature:
- Add circuit_breaker_config table for storing failover thresholds
- Add get/update_circuit_breaker_config methods in proxy DAO
- Add reset_provider_health method for manual recovery
- Add set_proxy_target and get_proxy_targets methods in providers DAO
for managing multi-provider failover configuration
* feat(proxy): implement circuit breaker and provider router for auto-failover
Add core failover logic:
- CircuitBreaker: Tracks provider health with three states:
- Closed: Normal operation, requests pass through
- Open: Circuit broken after consecutive failures, skip provider
- HalfOpen: Testing recovery with limited requests
- ProviderRouter: Routes requests across multiple providers with:
- Health tracking and automatic failover
- Configurable failure/success thresholds
- Auto-disable proxy target after reaching failure threshold
- Support for manual circuit breaker reset
- Export new types in proxy module
* feat(proxy): add failover Tauri commands and integrate with forwarder
Expose failover functionality to frontend:
- Add Tauri commands: get_proxy_targets, set_proxy_target,
get_provider_health, reset_circuit_breaker,
get/update_circuit_breaker_config, get_circuit_breaker_stats
- Register all new commands in lib.rs invoke handler
- Update forwarder with improved error handling and logging
- Integrate ProviderRouter with proxy server startup
- Add provider health tracking in request handlers
* feat(frontend): add failover API layer and TanStack Query hooks
Add frontend data layer for failover management:
- Add failover.ts API: Tauri invoke wrappers for all failover commands
- Add failover.ts query hooks: TanStack Query mutations and queries
- useProxyTargets, useProviderHealth queries
- useSetProxyTarget, useResetCircuitBreaker mutations
- useCircuitBreakerConfig query and mutation
- Update queries.ts with provider health query key
- Update mutations.ts to invalidate health on provider changes
- Add CircuitBreakerConfig and ProviderHealth types
* feat(ui): add auto-failover configuration UI and provider health display
Add comprehensive UI for failover management:
Components:
- ProviderHealthBadge: Display provider health status with color coding
- CircuitBreakerConfigPanel: Configure failure/success thresholds,
timeout duration, and error rate limits
- AutoFailoverConfigPanel: Manage proxy targets with drag-and-drop
priority ordering and individual enable/disable controls
- ProxyPanel: Integrate failover tabs for unified proxy management
Provider enhancements:
- ProviderCard: Show health badge and proxy target indicator
- ProviderActions: Add "Set as Proxy Target" action
- EditProviderDialog: Add is_proxy_target toggle
- ProviderList: Support proxy target filtering mode
Other:
- Update App.tsx routing for settings integration
- Update useProviderActions hook with proxy target mutation
- Fix ProviderList tests for updated component API
* fix(usage): stabilize date range to prevent infinite re-renders
* feat(backend): add tool version check command
Add get_tool_versions command to check local and latest versions of
Claude, Codex, and Gemini CLI tools:
- Detect local installed versions via command line execution
- Fetch latest versions from npm registry (Claude, Gemini)
and GitHub releases API (Codex)
- Return comprehensive version info including error details
for uninstalled tools
- Register command in Tauri invoke handler
* style(ui): format accordion component code style
Apply consistent code formatting to accordion component:
- Convert double quotes to semicolons at line endings
- Adjust indentation to 2-space standard
- Align with project code style conventions
* refactor(providers): update provider card styling to use theme tokens
Replace hardcoded color classes with semantic design tokens:
- Use bg-card, border-border, text-card-foreground instead of glass-card
- Replace gray/white color literals with muted/foreground tokens
- Change proxy target indicator color from purple to green
- Improve hover states with border-border-active
- Ensure consistent dark mode support via CSS variables
* refactor(proxy): simplify auto-failover config panel structure
Restructure AutoFailoverConfigPanel for better integration:
- Remove internal Card wrapper and expansion toggle (now handled by parent)
- Extract enabled state to props for external control
- Simplify loading state display
- Clean up redundant CardHeader/CardContent wrappers
- ProxyPanel: reduce complexity by delegating to parent components
* feat(settings): enhance settings page with accordion layout and tool versions
Major settings page improvements:
AboutSection:
- Add local tool version detection (Claude, Codex, Gemini)
- Display installed vs latest version comparison with visual indicators
- Show update availability badges and environment check cards
SettingsPage:
- Reorganize advanced settings into collapsible accordion sections
- Add proxy control panel with inline status toggle
- Integrate auto-failover configuration with accordion UI
- Add database and cost calculation config sections
DirectorySettings & WindowSettings:
- Minor styling adjustments for consistency
settings.ts API:
- Add getToolVersions() wrapper for new backend command
* refactor(usage): restructure usage dashboard components
Comprehensive usage statistics panel refactoring:
UsageDashboard:
- Reorganize layout with improved section headers
- Add better loading states and empty state handling
ModelStatsTable & ProviderStatsTable:
- Minor styling updates for consistency
ModelTestConfigPanel & PricingConfigPanel:
- Simplify component structure
- Remove redundant Card wrappers
- Improve form field organization
RequestLogTable:
- Enhance table layout with better column sizing
- Improve pagination controls
UsageSummaryCards:
- Update card styling with semantic tokens
- Better responsive grid layout
UsageTrendChart:
- Refine chart container styling
- Improve legend and tooltip display
* chore(deps): add accordion and animation dependencies
Package updates:
- Add @radix-ui/react-accordion for collapsible sections
- Add cmdk for command palette support
- Add framer-motion for enhanced animations
Tailwind config:
- Add accordion-up/accordion-down animations
- Update darkMode config to support both selector and class
- Reorganize color and keyframe definitions for clarity
* style(app): update header and app switcher styling
App.tsx:
- Replace glass-header with explicit bg-background/80 backdrop-blur
- Update navigation button container to use bg-muted
AppSwitcher:
- Replace hardcoded gray colors with semantic muted/foreground tokens
- Ensure consistent dark mode support via CSS variables
- Add group class for better hover state transitions
* feat(skill): add multi-app skill support for Claude/Codex/Gemini
- Add app-specific skill management with AppType prefix in skill keys
- Implement per-app skill tracking in database schema
- Add get_skills_for_app command to retrieve skills by application
- Update SkillsPage to support app-specific skill loading with initialApp prop
- Parse app parameter and validate against supported app types
- Maintain backward compatibility with default claude app
* fix(usage): reorder cache columns and prevent header text wrapping
- Swap cache read and cache write columns order
- Add whitespace-nowrap to all table headers to prevent text wrapping
- Improves table readability and layout consistency
Extract last segment from skill directory path to prevent nested directory
issues during install/uninstall operations. For example, "skills/codex" now
correctly installs to "codex" instead of creating nested "skills/codex" path.
The `titleBarStyle: "Overlay"` setting in tauri.conf.json causes black
screen and crash on Windows due to WebView2 compatibility issues.
Add platform-specific config `tauri.windows.conf.json` to override
titleBarStyle to "Visible" on Windows while keeping the overlay style
on macOS for the immersive UI experience.
Fixes black screen issue on Windows for v3.8.x releases.
* feat(proxy): implement local HTTP proxy server with multi-provider failover
Add a complete HTTP proxy server implementation built on Axum framework,
enabling local API request forwarding with automatic provider failover
and load balancing capabilities.
Backend Implementation (Rust):
- Add proxy server module with 7 core components:
* server.rs: Axum HTTP server lifecycle management (start/stop/status)
* router.rs: API routing configuration for Claude/OpenAI/Gemini endpoints
* handlers.rs: Request/response handling and transformation
* forwarder.rs: Upstream forwarding logic with retry mechanism (652 lines)
* error.rs: Comprehensive error handling and HTTP status mapping
* types.rs: Shared types (ProxyConfig, ProxyStatus, ProxyServerInfo)
* health.rs: Provider health check infrastructure
Service Layer:
- Add ProxyService (services/proxy.rs, 157 lines):
* Manage proxy server lifecycle
* Handle configuration updates
* Track runtime status and metrics
Database Layer:
- Add proxy configuration DAO (dao/proxy.rs, 242 lines):
* Persist proxy settings (listen address, port, timeout)
* Store provider priority and availability flags
- Update schema with proxy_config table (schema.rs):
* Support runtime configuration persistence
Tauri Commands:
- Add 6 command endpoints (commands/proxy.rs):
* start_proxy_server: Launch proxy server
* stop_proxy_server: Gracefully shutdown server
* get_proxy_status: Query runtime status
* get_proxy_config: Retrieve current configuration
* update_proxy_config: Modify settings without restart
* is_proxy_running: Check server state
Frontend Implementation (React + TypeScript):
- Add ProxyPanel component (222 lines):
* Real-time server status display
* Start/stop controls
* Provider availability monitoring
- Add ProxySettingsDialog component (420 lines):
* Configuration editor (address, port, timeout)
* Provider priority management
* Settings validation
- Add React hooks:
* useProxyConfig: Manage proxy configuration state
* useProxyStatus: Poll and display server status
- Add TypeScript types (types/proxy.ts):
* Define ProxyConfig, ProxyStatus interfaces
Provider Integration:
- Extend Provider model with availability field (providers.rs):
* Track provider health for failover logic
- Update ProviderCard UI to display proxy status
- Integrate proxy controls in Settings page
Dependencies:
- Add Axum 0.7 (async web framework)
- Add Tower 0.4 (middleware and service abstractions)
- Add Tower-HTTP (CORS layer)
- Add Tokio sync primitives (oneshot, RwLock)
Technical Details:
- Graceful shutdown via oneshot channel
- Shared state with Arc<RwLock<T>> for thread-safe config updates
- CORS enabled for cross-origin frontend access
- Request/response streaming support
- Automatic retry with exponential backoff (forwarder)
- API key extraction from multiple config formats (Claude/Codex/Gemini)
File Statistics:
- 41 files changed
- 3491 insertions(+), 41 deletions(-)
- Core modules: 1393 lines (server + forwarder + handlers)
- Frontend UI: 642 lines (ProxyPanel + ProxySettingsDialog)
- Database/DAO: 326 lines
This implementation provides the foundation for advanced features like:
- Multi-provider load balancing
- Automatic failover on provider errors
- Request logging and analytics
- Usage tracking and cost monitoring
* fix(proxy): resolve UI/UX issues and database constraint error
Simplify proxy control interface and fix database persistence issues:
Backend Fixes:
- Fix NOT NULL constraint error in proxy_config.created_at field
* Use COALESCE to preserve created_at on updates
* Ensure proper INSERT OR REPLACE behavior
- Remove redundant enabled field validation on startup
* Auto-enable when user clicks start button
* Persist enabled state after successful start
- Preserve enabled state during config updates
* Prevent accidental service shutdown on config save
Frontend Improvements:
- Remove duplicate proxy enable switch from settings dialog
* Keep only runtime toggle in ProxyPanel
* Simplify user experience with single control point
- Hide proxy target button when proxy service is stopped
* Add isProxyRunning prop to ProviderCard
* Conditionally render proxy controls based on service status
- Update form schema to omit enabled field
* Managed automatically by backend
Files: 5 changed, 81 insertions(+), 94 deletions(-)
* fix(proxy): improve URL building and Gemini request handling
- Refactor URL construction with version path deduplication (/v1, /v1beta)
- Preserve query parameters for Gemini API requests
- Support GOOGLE_GEMINI_API_KEY field name (with fallback)
- Change default proxy port from 5000 to 15721
- Fix test: use Option type for is_proxy_target field
* refactor(proxy): remove unused request handlers and routes
- Remove unused GET/DELETE request forwarding methods
- Remove count_tokens, get/delete response handlers
- Simplify router by removing unused endpoints
- Keep only essential routes: /v1/messages, /v1/responses, /v1beta/*
* Merge branch 'main' into feat/proxy-server
* fix(proxy): resolve clippy warnings for dead code and uninlined format args
- Add #[allow(dead_code)] to unused ProviderUnhealthy variant
- Inline format string arguments in handlers.rs and codex.rs log macros
- Refactor error response handling to properly pass through upstream errors
- Add URL deduplication logic for /v1/v1 paths in CodexAdapter
* feat(proxy): implement provider adapter pattern with OpenRouter support
This major refactoring introduces a modular provider adapter architecture
to support format transformation between different AI API formats.
New features:
- Add ProviderAdapter trait for unified provider abstraction
- Implement Claude, Codex, and Gemini adapters with specific logic
- Add Anthropic ↔ OpenAI format transformation for OpenRouter compatibility
- Support model mapping from provider configuration (ANTHROPIC_MODEL, etc.)
- Add OpenRouter preset to Claude provider presets
Refactoring:
- Extract authentication logic into auth.rs with AuthInfo and AuthStrategy
- Move URL building and request transformation to individual adapters
- Simplify ProviderRouter to only use proxy target providers
- Refactor RequestForwarder to use adapter-based request/response handling
- Use whitelist mode for header forwarding (only pass necessary headers)
Architecture:
- providers/adapter.rs: ProviderAdapter trait definition
- providers/auth.rs: AuthInfo, AuthStrategy types
- providers/claude.rs: Claude adapter with OpenRouter detection
- providers/codex.rs: Codex (OpenAI) adapter
- providers/gemini.rs: Gemini (Google) adapter
- providers/models/: Anthropic and OpenAI API data models
- providers/transform.rs: Bidirectional format transformation
* feat(proxy): add streaming SSE transform and thinking parameter support
New features:
- Add OpenAI → Anthropic SSE streaming response transformation
- Support thinking parameter detection for reasoning model selection
- Add ANTHROPIC_REASONING_MODEL config option for extended thinking
Changes:
- streaming.rs: Implement SSE event parsing and Anthropic format conversion
- transform.rs: Add thinking detection logic and reasoning model mapping
- handlers.rs: Integrate streaming transform for OpenRouter compatibility
- Cargo.toml: Add async-stream and bytes dependencies
* feat(db): add usage tracking schema and types
Add database tables for proxy request logs and model pricing.
Extend Provider and error types to support usage statistics.
* feat(proxy): implement usage tracking subsystem
Add request logger with automatic cost calculation.
Implement token parser for Claude/OpenAI/Gemini responses.
Add cost calculator based on model pricing configuration.
* feat(proxy): integrate usage logging into request handlers
Add usage logging to forwarder and streaming handlers.
Track token usage and costs for each proxy request.
* feat(commands): add usage statistics Tauri commands
Register usage commands for summary, trends, logs, and pricing.
Expose usage stats service through Tauri command layer.
* feat(api): add frontend usage API and query hooks
Add TypeScript types for usage statistics.
Implement usage API with Tauri invoke calls.
Add TanStack Query hooks for usage data fetching.
* feat(ui): add usage dashboard components
Add UsageDashboard with summary cards, trend chart, and data tables.
Implement model pricing configuration panel.
Add request log viewer with filtering and detail panel.
* fix(ui): integrate usage dashboard and fix type errors
Add usage dashboard tab to settings page.
Fix UsageScriptModal TypeScript type annotations.
* deps: add recharts for charts and rust_decimal/uuid for usage tracking
- recharts: Chart visualization for usage trends
- rust_decimal: Precise cost calculations
- uuid: Request ID generation
* feat(proxy): add ProviderType enum for fine-grained provider detection
Introduce ProviderType enum to distinguish between different provider
implementations (Claude, ClaudeAuth, Codex, Gemini, GeminiCli, OpenRouter).
This enables proper authentication handling and request transformation
based on the actual provider type rather than just AppType.
- Add ProviderType enum with detection logic from config
- Enhance Claude adapter with OpenRouter detection
- Enhance Gemini adapter with CLI mode detection
- Add helper methods for provider type inference
* feat(database): extend schema with streaming and timing fields
Add new columns to proxy_request_logs table for enhanced usage tracking:
- first_token_ms and duration_ms for performance metrics
- provider_type and is_streaming for request classification
- cost_multiplier for flexible pricing
Update model pricing with accurate rates for Claude/GPT/Gemini models.
Add ensure_model_pricing_seeded() call on database initialization.
Add test for model pricing auto-seeding verification.
* feat(proxy/usage): enhance token parser and logger for multi-format support
Parser enhancements:
- Add OpenAI Chat Completions format parsing (prompt_tokens/completion_tokens)
- Add model field to TokenUsage for actual model name extraction
- Add from_codex_response_adjusted() for proper cache token handling
- Add debug logging for better stream event tracing
Logger enhancements:
- Add first_token_ms, provider_type, is_streaming, cost_multiplier fields
- Extend RequestLog struct with full metadata tracking
- Update log_with_calculation() signature for new fields
Calculator: Update tests with model field in TokenUsage.
* feat(proxy): enhance proxy server with session tracking and OpenAI route
Error handling:
- Add StreamIdleTimeout and AuthError variants for better error classification
Module exports:
- Export ResponseType, StreamHandler, NonStreamHandler from response_handler
- Export ProxySession, ClientFormat from session module
Server routing:
- Add /v1/chat/completions route for OpenAI Chat Completions API
Handlers:
- Add log_usage_with_session() for enhanced usage tracking with session context
- Add first_token_ms timing measurement for streaming responses
- Use SseUsageCollector with start_time for accurate latency calculation
- Track is_streaming flag in usage logs
* feat(services): add pagination and enhanced filtering for request logs
Usage stats service:
- Change get_request_logs() from limit/offset to page/page_size pagination
- Return PaginatedLogs with total count, page, and page_size
- Add appType and providerName filters with LIKE search
- Add is_streaming, first_token_ms, duration_ms to RequestLogDetail
- Join with providers table for provider name lookup
Commands:
- Update get_request_logs command signature for pagination params
Module exports:
- Export PaginatedLogs struct
* feat(frontend): update usage types and API for pagination support
Types (usage.ts):
- Add isStreaming, firstTokenMs, durationMs to RequestLog
- Add PaginatedLogs interface with data, total, page, pageSize
- Change LogFilters: providerId -> appType + providerName
API (usage.ts):
- Change getRequestLogs params from limit/offset to page/pageSize
- Return PaginatedLogs instead of RequestLog[]
- Pass filters object directly to backend
Query (usage.ts):
- Update usageKeys.logs key generation for pagination
- Update useRequestLogs hook signature
* refactor(ui): enhance RequestLogTable with filtering and pagination
UI improvements:
- Add filter bar with app type, provider name, model, status selectors
- Add date range picker (startDate/endDate)
- Add search/reset/refresh buttons
Pagination:
- Implement proper page-based pagination with page info display
- Show total count and current page range
- Add prev/next navigation buttons
Features:
- Default to last 24 hours filter
- Streamlined table columns layout
- Query invalidation on refresh
* style(config): format mcpPresets code style
Apply consistent formatting to createNpxCommand function and
sequential-thinking server configuration.
* fix(ui): update SettingsPage tab styles for improved appearance (#342)
* feat(model-test): add provider model availability testing
Implement standalone model testing feature to verify provider API connectivity:
- Add ModelTestService for Claude/Codex/Gemini endpoint testing
- Create model_test_logs table for test result persistence
- Add test button to ProviderCard with loading state
- Include ModelTestConfigPanel for customizing test parameters
* fix(proxy): resolve token parsing for OpenRouter streaming responses
Problem:
- OpenRouter and similar third-party services return streaming responses
where input_tokens appear in message_delta instead of message_start
- The previous implementation only extracted input_tokens from message_start,
causing input_tokens to be recorded as 0 for these providers
Changes:
- streaming.rs: Add prompt_tokens field to Usage struct and include
input_tokens in the transformed message_delta event when converting
OpenAI format to Anthropic format
- parser.rs: Update from_claude_stream_events() to handle input_tokens
from both message_start (native Claude API) and message_delta (OpenRouter)
- Use if-let pattern instead of direct unwrap for safer parsing
- Only update input_tokens from message_delta if not already set
- logger.rs: Adjust test parameters to match updated function signature
Tests:
- Add test_openrouter_stream_parsing() for OpenRouter format validation
- Add test_native_claude_stream_parsing() for native Claude API validation
* fix(pricing): standardize model ID format for pricing lookup
Normalize model IDs by removing vendor prefixes and converting dots to hyphens to ensure consistent pricing lookups across different API response formats.
Changes:
- Update seed data to use hyphen format (e.g., gpt-5-1, gemini-2-5-pro)
- Add normalize_model_id() function to strip vendor prefixes (anthropic/, openai/)
- Convert dots to hyphens in model IDs (claude-haiku-4.5 → claude-haiku-4-5)
- Try both original and normalized IDs for exact matching
- Use normalized ID for suffix-based fallback matching
- Add comprehensive test cases for prefix and dot handling
- Add warning log when no pricing found
This ensures pricing lookups work correctly for:
- Models with vendor prefixes: anthropic/claude-haiku-4.5
- Models with dots in version: claude-sonnet-4.5
- Models with date suffixes: claude-haiku-4-5-20240229
* style(rust): apply clippy formatting suggestions
Apply automatic clippy fixes for uninlined_format_args warnings across Rust codebase. Replace format string placeholders with inline variable syntax for improved readability.
Changes:
- Convert format!("{}", var) to format!("{var}")
- Apply to model_test.rs, parser.rs, and usage_stats.rs
- Fix line length issues by breaking long function calls
- Improve code formatting consistency
All changes are automatic formatting with no functional impact.
* fix(ui): restore card borders in usage statistics panels
Restore proper card styling for ModelTestConfigPanel and PricingConfigPanel by adding back border and rounded-lg classes. The transparent background styling was causing visual inconsistency.
Changes:
- Replace border-none bg-transparent shadow-none with border rounded-lg
- Apply to both loading and error states for consistency
- Format TypeScript code for better readability
- Break long function signatures across multiple lines
This ensures the usage statistics panels have consistent visual appearance with proper borders and rounded corners.
* feat(pricing): add GPT-5 Codex model pricing presets
Add pricing configuration for GPT-5 Codex variants to support cost tracking for Codex-specific models.
Changes:
- Add gpt-5-codex model with standard GPT-5 pricing
- Add gpt-5-1-codex model with standard GPT-5.1 pricing
- Input: $1.25/M tokens, Output: $10/M tokens
- Cache read: $0.125/M tokens, Cache creation: $0
This ensures accurate cost calculation for Codex API requests using GPT-5 Codex models.
crypto.randomUUID() is not available on older systems (macOS < 12.3,
Safari < 15.4). This caused "crypto.randomUUID is not a function"
error when adding providers.
- Add generateUUID() utility with getRandomValues() fallback
- Provide user-friendly error message if crypto API unavailable
Replace Node.js process.platform check with navigator.userAgent-based
detection from @/lib/platform. The previous implementation using
process.platform failed in Tauri's browser environment, causing
Windows platform to always return false and not apply the required
'cmd /c' wrapper for npx commands.
This fix ensures that on Windows, MCP presets like sequential-thinking
correctly use 'cmd /c npx' format instead of direct 'npx', eliminating
the warning: "Windows requires 'cmd /c' wrapper to execute npx"
Changes:
- Import isWindows() from @/lib/platform instead of defining locally
- Remove incorrect process.platform-based detection
- Now properly detects Windows in browser/WebView environment
Move 1px of horizontal padding from outer container to inner scroll
container, providing buffer space for the hover scale-[1.01] effect
without being cut off by overflow-hidden.
Providers page was using DndContext which may interfere with scroll wheel
events on Linux/Ubuntu WebKitGTK. Added independent scroll containers
with `overflow-y-auto` to all main pages, matching the pattern already
used by the MCP panel which works correctly.
Changes:
- App.tsx: Wrap ProviderList in independent scroll container
- SkillsPage: Use consistent h-[calc(100vh-8rem)] layout
- SettingsPage: Add overflow-hidden and overflow-x-hidden for consistency
- Update README_JA.md to reference Japanese screenshots
- Add Japanese screenshots (main-ja.png, add-ja.png)
- Update English and Chinese screenshots with latest UI
- Remove outdated README_i18n.md documentation
- Remove `.replace(/\/+$/, "")` from all base URL handlers in useBaseUrlState.ts
- Remove trailing slash stripping in useCodexConfigState.ts
- Remove trailing slash normalization in providerConfigUtils.ts setCodexBaseUrl()
- Update i18n hints (en/ja/zh) to instruct users to avoid trailing slashes
This gives users explicit control over URL format rather than silently modifying input.
The selected provider's visual highlight (background, border, glow)
was being overridden by .glass-card base styles due to CSS specificity.
Add dedicated .glass-card-active class to ensure active state persists.
The dialog backgrounds were transparent because Tailwind 3 requires explicit
color mappings in the config to use CSS variables. Added standard shadcn/ui
color mappings (background, foreground, card, primary, etc.) and removed
unnecessary overlayClassName overrides from dialog components.
- Use zIndex="alert" and semi-transparent overlay for consistency
- Apply border-b-0/border-t-0 bg-transparent to header/footer
- Replace emerald accent with blue to match app theme
- Use Input component instead of raw input elements
- Simplify button variants (outline for cancel)
The auto-launch crate has different API signatures on each platform:
- Windows/Linux: AutoLaunch::new() takes 3 arguments
- macOS: AutoLaunch::new() takes 4 arguments (includes hidden param)
The previous code used #[cfg(not(target_os = "windows"))] which incorrectly
applied macOS's 4-argument signature to Linux, causing build failures.
Switch to AutoLaunchBuilder which handles platform differences internally.
- Add MiniMax en preset with international API endpoint (minimaxi.io)
- Split MiniMax partner promotion into CN and EN versions
- Remove unnecessary icon config from Aliyun and Alibaba Lingma presets
- Add CHANGELOG entry for v3.8.0
- Update README.md and README_ZH.md with v3.8.0 features
- Add Japanese README (README_JA.md)
- Add release notes in English, Chinese, and Japanese
Remove the skillsPath field from SkillRepo and Skill structs since
recursive scanning now automatically discovers skills in all directories.
Simplify the UI by removing the path input field.
- Add JsonEditor mock to McpFormModal tests (component uses CodeMirror
instead of Textarea)
- Fix assertion for missing command error message
- Update ImportExportSection tests for new button behavior and file
display format
- Run cargo fmt to fix Rust code formatting (lib.rs)
- Add missing i18n keys: migration.success, agents.title (zh/en/ja)
- Replace hardcoded strings "Agents" and "MCP" with t() calls in App.tsx
- Fix test mocks and assertions:
- Add providersApi.updateTrayMenu to useSettings.test.tsx mock
- Update SettingsPage mock path in App.test.tsx
- Fix toast message assertion in integration/SettingsDialog.test.tsx
- Add autoSaveSettings to SettingsDialog component test mock
- Fix loading state test to check spinner instead of title
- Update import button name matching for selected file state
- Fix save button test to switch to advanced tab first
- Remove obsolete cancel button test (button no longer exists)
Test results improved from 99 passed / 17 failed to 104 passed / 11 failed
- Add complete Japanese translation file (ja.json)
- Update frontend types and hooks to support "ja" language option
- Add Japanese tray menu texts in Rust backend
- Add Japanese option to language settings component
- Update Zod schema to include Japanese language validation
- Add test case for Japanese language preference
- Update i18n documentation to reflect three-language support
- Replace global `is_empty_for_first_import()` with independent checks:
- `is_mcp_table_empty()` for MCP server imports
- `is_prompts_table_empty()` for prompt imports
- Skills and providers already have built-in idempotency checks
- Fix misleading logs in provider import:
- Change `import_default_config` return type from `Result<()>` to `Result<bool>`
- Return `true` when actually imported, `false` when skipped
- Only log success message when import actually occurred
- Add idempotency protection to `import_from_file_on_first_launch`
This allows each data type to be independently recovered if deleted,
rather than requiring all tables to be empty for any import to trigger.
Add recursive directory scanning to discover SKILL.md files in nested
directories. When a SKILL.md is found, treat sibling directories as
functional folders rather than separate skills.
Previously, selectedTemplate was initialized to null when no NEW_API
credentials were detected, which caused the credentials config section
to be hidden even for new configurations or GENERAL template users.
Now properly detects:
- NEW_API template (has accessToken or userId)
- GENERAL template (has apiKey or baseUrl)
- Default to GENERAL for new configurations (matches default code template)
- Remove redundant "Request Configuration" section (URL, method, headers, body editors)
- Move timeout and auto query interval fields to preset template section
- Simplify "Enable usage query" toggle styling
- Remove redundant hints and variables description
- Add i18n support for Base URL label
- Include "0 to disable" hint directly in auto interval label
Windows version of auto-launch crate takes 3 arguments while
Linux/macOS takes 4 (includes hidden parameter). Use conditional
compilation to handle the difference.
- Add icon/iconColor fields to CodexProviderPreset and GeminiProviderPreset interfaces
- Configure icons for Claude presets (DeepSeek, Zhipu, Qwen, Kimi, MiniMax, DouBao, etc.)
- Configure icons for Codex presets (OpenAI, Azure, PackyCode)
- Configure icons for Gemini presets (Google, PackyCode)
- Fix handlePresetChange to pass icon fields when resetting form
This ensures preset icons are displayed in the icon picker when selecting a provider preset.
Tailwind CSS v4 requires modern CSS features (@layer, @property, color-mix)
that are not supported in older WebView2 versions, causing styles to fail
on some Windows 11 systems.
Changes:
- Replace @tailwindcss/vite with postcss + autoprefixer
- Downgrade tailwindcss from 4.1.13 to 3.4.17
- Convert CSS imports from v4 syntax to v3 @tailwind directives
- Add darkMode: "selector" to tailwind.config.js
- Create postcss.config.js for PostCSS pipeline
This improves compatibility with older browsers and WebView2 runtimes
while maintaining the same visual appearance.
- Set WEBKIT_DISABLE_DMABUF_RENDERER=1 at startup to fix blank screen on Debian 13.2 and Nvidia GPUs
- Only register deep-link handler on first run to avoid overwriting user customizations
- Use correct Tauri path API (app.path().data_dir()) instead of dirs::data_dir() to match plugin's actual .desktop file location
- Show system dialog when config.json fails to load during migration
- User can retry loading or exit the program
- Exit before database creation ensures clean retry on next launch
- Support Chinese/English localization based on system locale
Add get_effective_current_provider() to validate local settings ID
against database, with automatic cleanup and fallback to DB is_current.
This fixes edge cases in multi-device cloud sync scenarios where local
settings may contain stale provider IDs:
- current(): now returns validated effective provider ID
- update(): correctly syncs live config when local ID differs from DB
- delete(): checks both local settings and DB to prevent deletion
- switch(): backfill logic now targets valid provider
- sync_current_to_live(): uses validated ID with auto-fallback
- tray menu: displays correct checkmark on startup
Also fixes test issues:
- Add missing test setup calls (mutex, reset_test_fs, ensure_test_home)
- Correct Gemini security settings path to ~/.gemini/settings.json
Complete the device-level settings separation for cloud sync support.
Backend changes:
- Modify switch() to update both local settings and database is_current
- Modify current() to read from local settings first, fallback to database
- Rename sync_current_from_db() to sync_current_to_live()
- Update tray menu to read current provider from local settings
Frontend changes:
- Update Settings interface: remove legacy fields (customEndpoints*, security)
- Add currentProviderClaude/Codex/Gemini fields
- Update settings schema accordingly
Test fixes:
- Update Gemini security tests to check ~/.gemini/settings.json
instead of ~/.cc-switch/settings.json (security field was never
stored in CC Switch settings)
This ensures each device maintains its own current provider selection
independently when database is synced across devices.
Separate device-level settings from database to support cloud sync scenarios
where multiple devices share the same database but need independent settings.
Changes:
- Remove database storage logic (bind_db, save_to_db, load_from_db)
- Remove legacy custom_endpoints_claude/codex fields (actual data in provider.meta)
- Add current_provider_claude/codex/gemini fields for device-level provider selection
- Add get_current_provider() and set_current_provider() helper functions
- Settings now stored only in ~/.cc-switch/settings.json
This ensures that when database is synced across devices, each device
maintains its own current provider selection independently.
write_live_snapshot was incorrectly passing the already-extracted env
sub-field to json_to_env, which expects the full settings_config object.
This caused json_to_env to look for env.env (nested), returning an empty
HashMap and writing an empty .env file.
Fix by delegating to write_gemini_live which correctly handles env file
writing and security flag configuration in one place.
- Remove redundant security.auth.selectedType from CC Switch settings
- Fix Generic provider type not writing security flag on switch
- All non-Google Official providers now correctly write "gemini-api-key"
- Delete unused ensure_packycode_security_flag function
- Clean up SecuritySettings and SecurityAuthSettings types from AppSettings
- Mark MiniMax as partner with custom theme color (#f64551)
- Add Black Friday promotional text for Starter plan ($2/mo, 80% OFF)
- Update apiKeyUrl to MiniMax coding plan subscription page
Add staleTime and gcTime to useUsageQuery to avoid triggering
unnecessary API calls when switching between app tabs. The staleTime
is set dynamically based on the auto-refresh interval (or 5 minutes
by default), and gcTime is set to 10 minutes to preserve cache after
component unmount.
- Remove header/footer borders and backgrounds for compact alert style
- Use theme-aware overlay color (bg-background/80) instead of black
- Add zIndex="alert" to ensure dialog appears above other dialogs
- Add validation for provider name (required for all providers)
- Add validation for API endpoint and API Key (required for non-official providers)
- Use toast notifications instead of inline form errors for better visibility
- Move name validation from zod schema to handleSubmit for consistent UX
- Add i18n keys: endpointRequired, apiKeyRequired
Remove obsolete methods that were superseded by SQLite-based
import/export in v3.7.0:
- export_config_to_path: replaced by Database::export_sql
- load_config_for_import: no longer used
- import_config_from_path: stub that only returned error
Also remove unused AppState import.
- Replace INSERT OR REPLACE with UPDATE for existing providers to avoid
triggering ON DELETE CASCADE on provider_endpoints table
- Fix endpoint merging logic to correctly mark database endpoints as
isCustom when they overlap with preset endpoints
The root cause was that INSERT OR REPLACE performs DELETE + INSERT under
the hood, which triggered the foreign key cascade and deleted all
associated endpoints from provider_endpoints table.
- Reorder toolbar buttons to keep Prompts and MCP icons aligned right
(consistent position between Claude and Codex apps)
- Add smooth fade-in/out transition for Skills button with opacity,
width, scale, and padding animations
- Hide Agents button temporarily (feature in development)
- Remove two divider lines for cleaner appearance
- Remove environment variable feature gate (CC_SWITCH_ENABLE_JSON_DB_MIGRATION)
- Automatically migrate config.json to SQLite when db doesn't exist
- Archive migrated config.json as config.json.migrated for recovery
- Add migration success flag in init_status.rs (one-time consumption)
- Add get_migration_result command for frontend to query
- Show toast notification on successful migration in App.tsx
Split the 1446-line services/provider.rs into 5 focused modules:
- gemini_auth.rs (250 lines): Gemini authentication type detection
- PackyCode, Google OAuth, and generic provider detection
- Security flag management for different auth types
- live.rs (300 lines): Live configuration operations
- LiveSnapshot backup/restore
- Reading and writing live config files
- Sync current provider to live config
- usage.rs (150 lines): Usage script execution
- Query and test usage scripts
- Format usage results
- Validate usage script configuration
- endpoints.rs (80 lines): Custom endpoints management
- CRUD operations for provider custom endpoints
- Last-used timestamp tracking
- mod.rs (650 lines): Core provider CRUD and service facade
- ProviderService struct with all public methods
- Provider add/update/delete/switch operations
- Claude model key normalization
- Credential extraction and validation
All 107 tests pass. This improves maintainability by:
- Separating concerns into cohesive modules
- Making Gemini-specific logic easier to find and modify
- Reducing cognitive load when working on specific features
The switch function was missing two important features after the SQLite
migration:
1. Backfill mechanism: Before switching providers, read the current live
config and save it back to the current provider. This preserves any
manual edits users made to the live config file.
2. Gemini security flags: When switching to a Gemini provider, set the
appropriate security.auth.selectedType:
- PackyCode providers: "gemini-api-key"
- Google OAuth providers: "oauth-personal"
Also update tests to:
- Use the new unified MCP structure (mcp.servers) instead of the legacy
per-app structure (mcp.codex.servers)
- Expect backfill behavior (was incorrectly marked as "no backfill")
- Remove assertions for provider-specific file deletion (v3.7.0+ uses
SSOT, no longer creates per-provider config files)
This merge brings the SQLite migration system from feat/sqlite-migration branch:
## New Features
- Schema version control with SCHEMA_VERSION constant
- Automatic migration of missing columns for providers table
- Dry-run validation mode for schema compatibility checks
- JSON→SQLite migration feature gate (CC_SWITCH_ENABLE_JSON_DB_MIGRATION)
- Settings reload mechanism after imports
## Test Updates
- Updated tests to use SQLite database instead of config.json
- Removed obsolete import_config_from_path tests (replaced by db.import_sql)
- Fixed MCP tests to use unified McpServer structure (v3.7.0+)
- Updated provider switch tests to reflect no-backfill behavior
- Adjusted error type matching for new error variants
- Add i18n support to McpConfirmation, PromptConfirmation, SkillConfirmation
- Add nested translation keys: deeplink.mcp.*, deeplink.prompt.*, deeplink.skill.*
- Replace all hardcoded Chinese strings with t() function calls
- Ensure consistent localization across all deeplink import dialogs
- Add missing i18n keys for prompt, MCP, and skill import dialogs
- Replace hardcoded toast messages with t() function calls
- Add translation keys: importPrompt, importMcp, importSkill, etc.
- Ensure all user-facing text supports both zh and en locales
* feat(components): add reusable full-screen panel components
Add new full-screen panel components to support the UI refactoring:
- FullScreenPanel: Reusable full-screen layout component with header,
content area, and optional footer. Provides consistent layout for
settings, prompts, and other full-screen views.
- PromptFormPanel: Dedicated panel for creating and editing prompts
with markdown preview support. Features real-time validation and
integrated save/cancel actions.
- AgentsPanel: Panel component for managing agent configurations.
Provides a consistent interface for agent CRUD operations.
- RepoManagerPanel: Full-featured repository manager panel for Skills.
Supports repository listing, addition, deletion, and configuration
management with integrated validation.
These components establish the foundation for the upcoming settings
page migration from dialog-based to full-screen layout.
* refactor(settings): migrate from dialog to full-screen page layout
Complete migration of settings from modal dialog to dedicated full-screen
page, improving UX and providing more space for configuration options.
Changes:
- Remove SettingsDialog component (legacy modal-based interface)
- Add SettingsPage component with full-screen layout using FullScreenPanel
- Refactor App.tsx routing to support dedicated settings page
* Add settings route handler
* Update navigation logic from dialog-based to page-based
* Integrate with existing app switcher and provider management
- Update ImportExportSection to work with new page layout
* Improve spacing and layout for better readability
* Enhanced error handling and user feedback
* Better integration with page-level actions
- Enhance useSettings hook to support page-based workflow
* Add navigation state management
* Improve settings persistence logic
* Better error boundary handling
Benefits:
- More intuitive navigation with dedicated settings page
- Better use of screen space for complex configurations
- Improved accessibility with clearer visual hierarchy
- Consistent with modern desktop application patterns
- Easier to extend with new settings sections
This change is part of the larger UI refactoring initiative to modernize
the application interface and improve user experience.
* refactor(forms): simplify and modernize form components
Comprehensive refactoring of form components to reduce complexity,
improve maintainability, and enhance user experience.
Provider Forms:
- CodexCommonConfigModal & CodexConfigSections
* Simplified state management with reduced boilerplate
* Improved field validation and error handling
* Better layout with consistent spacing
* Enhanced model selection with visual indicators
- GeminiCommonConfigModal & GeminiConfigSections
* Streamlined authentication flow (OAuth vs API Key)
* Cleaner form layout with better grouping
* Improved validation feedback
* Better integration with parent components
- CommonConfigEditor
* Reduced from 178 to 68 lines (-62% complexity)
* Extracted reusable form patterns
* Improved JSON editing with syntax validation
* Better error messages and recovery options
- EndpointSpeedTest
* Complete rewrite for better UX
* Real-time testing progress indicators
* Enhanced error handling with retry logic
* Visual feedback for test results (color-coded latency)
MCP & Prompts:
- McpFormModal
* Simplified from 581 to ~360 lines
* Better stdio/http server type handling
* Improved form validation
* Enhanced multi-app selection (Claude/Codex/Gemini)
- PromptPanel
* Cleaner integration with PromptFormPanel
* Improved list/grid view switching
* Better state management for editing workflows
* Enhanced delete confirmation with safety checks
Code Quality Improvements:
- Reduced total lines by ~251 lines (-24% code reduction)
- Eliminated duplicate validation logic
- Improved TypeScript type safety
- Better component composition and separation of concerns
- Enhanced accessibility with proper ARIA labels
These changes make forms more intuitive, responsive, and easier to
maintain while reducing bundle size and improving runtime performance.
* style(ui): modernize component layouts and visual design
Update UI components with improved layouts, visual hierarchy, and
modern design patterns for better user experience.
Navigation & Brand Components:
- AppSwitcher
* Enhanced visual design with better spacing
* Improved active state indicators
* Smoother transitions and hover effects
* Better mobile responsiveness
- BrandIcons
* Optimized icon rendering performance
* Added support for more provider icons
* Improved SVG handling and fallbacks
* Better scaling across different screen sizes
Editor Components:
- JsonEditor
* Enhanced syntax highlighting
* Better error visualization
* Improved code formatting options
* Added line numbers and code folding support
- UsageScriptModal
* Complete layout overhaul (1239 lines refactored)
* Better script editor integration
* Improved template selection UI
* Enhanced preview and testing panels
* Better error feedback and validation
Provider Components:
- ProviderCard
* Redesigned card layout with modern aesthetics
* Better information density and readability
* Improved action buttons placement
* Enhanced status indicators (active/inactive)
- ProviderList
* Better grid/list view layouts
* Improved drag-and-drop visual feedback
* Enhanced sorting indicators
- ProviderActions
* Streamlined action menu
* Better icon consistency
* Improved tooltips and accessibility
Usage & Footer:
- UsageFooter
* Redesigned footer layout
* Better quota visualization
* Improved refresh controls
* Enhanced error states
Design System Updates:
- dialog.tsx (shadcn/ui component)
* Updated to latest design tokens
* Better overlay animations
* Improved focus management
- index.css
* Added 65 lines of global utility classes
* New animation keyframes
* Enhanced color variables for dark mode
* Improved typography scale
- tailwind.config.js
* Extended theme with new design tokens
* Added custom animations and transitions
* New spacing and sizing utilities
* Enhanced color palette
Visual Improvements:
- Consistent border radius across components
- Unified shadow system for depth perception
- Better color contrast for accessibility (WCAG AA)
- Smoother animations and transitions
- Improved dark mode support
These changes create a more polished, modern interface while
maintaining consistency with the application's design language.
* chore: update dialogs, i18n and improve component integration
Various functional updates and improvements across provider dialogs,
MCP panel, skills page, and internationalization.
Provider Dialogs:
- AddProviderDialog
* Simplified form state management
* Improved preset selection workflow
* Better validation error messages
* Enhanced template variable handling
- EditProviderDialog
* Streamlined edit flow with better state synchronization
* Improved handling of live config backfilling
* Better error recovery for failed updates
* Enhanced integration with parent components
MCP & Skills:
- UnifiedMcpPanel
* Reduced complexity from 140+ to ~95 lines
* Improved multi-app server management
* Better server type detection (stdio/http)
* Enhanced server status indicators
* Cleaner integration with MCP form modal
- SkillsPage
* Simplified navigation and state management
* Better integration with RepoManagerPanel
* Improved error handling for repository operations
* Enhanced loading states
- SkillCard
* Minor layout adjustments
* Better action button placement
Environment & Configuration:
- EnvWarningBanner
* Improved conflict detection messages
* Better visual hierarchy for warnings
* Enhanced dismissal behavior
- tauri.conf.json
* Updated build configuration
* Added new window management options
Internationalization:
- en.json & zh.json
* Added 17 new translation keys for new features
* Updated existing keys for better clarity
* Added translations for new settings page
* Improved consistency across UI text
Code Cleanup:
- mutations.ts
* Removed 14 lines of unused mutation definitions
* Cleaned up deprecated query invalidation logic
* Better type safety for mutation parameters
Overall Impact:
- Reduced total lines by 51 (-10% in affected files)
- Improved component integration and data flow
- Better error handling and user feedback
- Enhanced i18n coverage for new features
These changes improve the overall polish and integration of various
components while removing technical debt and unused code.
* feat(backend): add auto-launch functionality
Implement system auto-launch feature to allow CC-Switch to start
automatically on system boot, improving user convenience.
Backend Implementation:
- auto_launch.rs: New module for auto-launch management
* Cross-platform support using auto-launch crate
* Enable/disable auto-launch with system integration
* Proper error handling for permission issues
* Platform-specific implementations (macOS/Windows/Linux)
Command Layer:
- Add get_auto_launch command to check current status
- Add set_auto_launch command to toggle auto-start
- Integrate commands with settings API
Settings Integration:
- Extend Settings struct with auto_launch field
- Persist auto-launch preference in settings store
- Automatic state synchronization on app startup
Dependencies:
- Add auto-launch ^0.5.0 to Cargo.toml
- Update Cargo.lock with new dependency tree
Technical Details:
- Uses platform-specific auto-launch mechanisms:
* macOS: Login Items via LaunchServices
* Windows: Registry Run key
* Linux: XDG autostart desktop files
- Handles edge cases like permission denials gracefully
- Maintains settings consistency across app restarts
This feature enables users to have CC-Switch readily available
after system boot without manual intervention, particularly useful
for users who frequently switch between API providers.
* refactor(settings): enhance settings page with auto-launch integration
Complete refactoring of settings page architecture to integrate auto-launch
feature and improve overall settings management workflow.
SettingsPage Component:
- Integrate auto-launch toggle with WindowSettings section
- Improve layout and spacing for better visual hierarchy
- Enhanced error handling for settings operations
- Better loading states during settings updates
- Improved accessibility with proper ARIA labels
WindowSettings Component:
- Add auto-launch switch with real-time status
- Integrate with backend auto-launch commands
- Proper error feedback for permission issues
- Visual indicators for current auto-launch state
- Tooltip guidance for auto-launch functionality
useSettings Hook (Major Refactoring):
- Complete rewrite reducing complexity by ~30%
- Better separation of concerns with dedicated handlers
- Improved state management using React Query
- Enhanced auto-launch state synchronization
* Fetch auto-launch status on mount
* Real-time updates on toggle
* Proper error recovery
- Optimized re-renders with better memoization
- Cleaner API for component integration
- Better TypeScript type safety
Settings API:
- Add getAutoLaunch() method
- Add setAutoLaunch(enabled: boolean) method
- Type-safe Tauri command invocations
- Proper error propagation to UI layer
Architecture Improvements:
- Reduced hook complexity from 197 to ~140 effective lines
- Eliminated redundant state management logic
- Better error boundaries and fallback handling
- Improved testability with clearer separation
User Experience Enhancements:
- Instant visual feedback on auto-launch toggle
- Clear error messages for permission issues
- Loading indicators during async operations
- Consistent behavior across all platforms
This refactoring provides a solid foundation for future settings
additions while maintaining code quality and user experience.
* refactor(ui): optimize FullScreenPanel, Dialog and App routing
Comprehensive refactoring of core UI components to improve code quality,
maintainability, and user experience.
FullScreenPanel Component:
- Enhanced props interface with better TypeScript types
- Improved layout flexibility with customizable padding
- Better header/footer composition patterns
- Enhanced scroll behavior for long content
- Added support for custom actions in header
- Improved responsive design for different screen sizes
- Better integration with parent components
- Cleaner prop drilling with context where appropriate
Dialog Component (shadcn/ui):
- Updated to latest component patterns
- Improved animation timing and easing
- Better focus trap management
- Enhanced overlay styling with backdrop blur
- Improved accessibility (ARIA labels, keyboard navigation)
- Better close button positioning and styling
- Enhanced mobile responsiveness
- Cleaner composition with DialogHeader/Footer
App Component Routing:
- Refactored routing logic for better clarity
- Improved state management for navigation
- Better integration with settings page
- Enhanced error boundary handling
- Cleaner separation of layout concerns
- Improved provider context propagation
- Better handling of deep links
- Optimized re-renders with React.memo where appropriate
Code Quality Improvements:
- Reduced prop drilling with better component composition
- Improved TypeScript type safety
- Better separation of concerns
- Enhanced code readability with clearer naming
- Eliminated redundant logic
Performance Optimizations:
- Reduced unnecessary re-renders
- Better memoization of callbacks
- Optimized component tree structure
- Improved event handler efficiency
User Experience:
- Smoother transitions and animations
- Better visual feedback for interactions
- Improved loading states
- More consistent behavior across features
These changes create a more maintainable and performant foundation
for the application's UI layer while improving the overall user
experience with smoother interactions and better visual polish.
* refactor(features): modernize Skills, Prompts and Agents components
Major refactoring of feature components to improve code quality,
user experience, and maintainability.
SkillsPage Component (299 lines refactored):
- Complete rewrite of layout and state management
- Better integration with RepoManagerPanel
- Improved navigation between list and detail views
- Enhanced error handling with user-friendly messages
- Better loading states with skeleton screens
- Optimized re-renders with proper memoization
- Cleaner separation between list and form views
- Improved skill card interactions
- Better responsive design for different screen sizes
RepoManagerPanel Component (370 lines refactored):
- Streamlined repository management workflow
- Enhanced form validation with real-time feedback
- Improved repository list with better visual hierarchy
- Better handling of git operations (clone, pull, delete)
- Enhanced error recovery for network issues
- Cleaner state management reducing complexity
- Improved TypeScript type safety
- Better integration with Skills backend API
- Enhanced loading indicators for async operations
PromptPanel Component (249 lines refactored):
- Modernized layout with FullScreenPanel integration
- Better separation between list and edit modes
- Improved prompt card design with better readability
- Enhanced search and filter functionality
- Cleaner state management for editing workflow
- Better integration with PromptFormPanel
- Improved delete confirmation with safety checks
- Enhanced keyboard navigation support
PromptFormPanel Component (238 lines refactored):
- Streamlined form layout and validation
- Better markdown editor integration
- Real-time preview with syntax highlighting
- Improved validation error display
- Enhanced save/cancel workflow
- Better handling of large prompt content
- Cleaner form state management
- Improved accessibility features
AgentsPanel Component (33 lines modified):
- Minor layout adjustments for consistency
- Better integration with FullScreenPanel
- Improved placeholder states
- Enhanced error boundaries
Type Definitions (types.ts):
- Added 10 new type definitions
- Better type safety for Skills/Prompts/Agents
- Enhanced interfaces for repository management
- Improved typing for form validations
Architecture Improvements:
- Reduced component coupling
- Better prop interfaces with explicit types
- Improved error boundaries
- Enhanced code reusability
- Better testing surface
User Experience Enhancements:
- Smoother transitions between views
- Better visual feedback for actions
- Improved error messages
- Enhanced loading states
- More intuitive navigation flows
- Better responsive layouts
Code Quality:
- Net reduction of 29 lines while adding features
- Improved code organization
- Better naming conventions
- Enhanced documentation
- Cleaner control flow
These changes significantly improve the maintainability and user
experience of core feature components while establishing consistent
patterns for future development.
* style(ui): refine component layouts and improve visual consistency
Comprehensive UI polish across multiple components to enhance visual
design, improve user experience, and maintain consistency.
UsageScriptModal Component (1302 lines refactored):
- Complete layout overhaul for better usability
- Improved script editor with syntax highlighting
- Better template selection interface
- Enhanced test/preview panels with clearer separation
- Improved error feedback and validation messages
- Better modal sizing and responsiveness
- Cleaner tab navigation between sections
- Enhanced code formatting and readability
- Improved loading states for async operations
- Better integration with parent components
MCP Components:
- McpFormModal (42 lines):
* Streamlined form layout
* Better server type selection (stdio/http)
* Improved field grouping and labels
* Enhanced validation feedback
- UnifiedMcpPanel (14 lines):
* Minor layout adjustments
* Better list item spacing
* Improved server status indicators
* Enhanced action button placement
Provider Components:
- ProviderCard (11 lines):
* Refined card layout and spacing
* Better visual hierarchy
* Improved badge placement
* Enhanced hover effects
- ProviderList (5 lines):
* Minor grid layout adjustments
* Better drag-and-drop visual feedback
- GeminiConfigSections (4 lines):
* Field label alignment
* Improved spacing consistency
Editor & Footer Components:
- JsonEditor (13 lines):
* Better editor height management
* Improved error display
* Enhanced syntax highlighting
- UsageFooter (10 lines):
* Refined footer layout
* Better quota display
* Improved refresh button placement
Settings & Environment:
- ImportExportSection (24 lines):
* Better button layout
* Improved action grouping
* Enhanced visual feedback
- EnvWarningBanner (4 lines):
* Refined alert styling
* Better dismiss button placement
Global Styles (index.css):
- Added 11 lines of utility classes
- Improved transition timing
- Better focus indicators
- Enhanced scrollbar styling
- Refined spacing utilities
Design Improvements:
- Consistent spacing using design tokens
- Unified color palette application
- Better typography hierarchy
- Improved shadow system for depth
- Enhanced interactive states (hover, active, focus)
- Better border radius consistency
- Refined animation timings
Accessibility:
- Improved focus indicators
- Better keyboard navigation
- Enhanced screen reader support
- Improved color contrast ratios
Code Quality:
- Net increase of 68 lines due to UsageScriptModal improvements
- Better component organization
- Cleaner style application
- Reduced style duplication
These visual refinements create a more polished and professional
interface while maintaining excellent usability and accessibility
standards across all components.
* chore(i18n): add auto-launch translation keys
Add translation keys for new auto-launch feature to support
multi-language interface.
Translation Keys Added:
- autoLaunch: Label for auto-launch toggle
- autoLaunchDescription: Explanation of auto-launch functionality
- autoLaunchEnabled: Status message when enabled
Languages Updated:
- Chinese (zh.json): 简体中文翻译
- English (en.json): English translations
The translations maintain consistency with existing terminology
and provide clear, user-friendly descriptions of the auto-launch
feature across both supported languages.
* test: update test suites to match component refactoring
Comprehensive test updates to align with recent component refactoring
and new auto-launch functionality.
Component Tests:
- AddProviderDialog.test.tsx (10 lines):
* Updated test cases for new dialog behavior
* Enhanced mock data for preset selection
* Improved assertions for validation
- ImportExportSection.test.tsx (16 lines):
* Updated for new settings page integration
* Enhanced test coverage for error scenarios
* Better mock state management
- McpFormModal.test.tsx (60 lines):
* Extensive updates for form refactoring
* New test cases for multi-app selection
* Enhanced validation testing
* Better coverage of stdio/http server types
- ProviderList.test.tsx (11 lines):
* Updated for new card layout
* Enhanced drag-and-drop testing
- SettingsDialog.test.tsx (96 lines):
* Major updates for SettingsPage migration
* New test cases for auto-launch functionality
* Enhanced integration test coverage
* Better async operation testing
Hook Tests:
- useDirectorySettings.test.tsx (32 lines):
* Updated for refactored hook logic
* Enhanced test coverage for edge cases
- useDragSort.test.tsx (36 lines):
* Simplified test cases
* Better mock implementation
* Improved assertions
- useImportExport tests (16 lines total):
* Updated for new error handling
* Enhanced test coverage
- useMcpValidation.test.tsx (23 lines):
* Updated validation test cases
* Better coverage of error scenarios
- useProviderActions.test.tsx (48 lines):
* Extensive updates for hook refactoring
* New test cases for provider operations
* Enhanced mock data
- useSettings.test.tsx (12 lines):
* New test cases for auto-launch
* Enhanced settings state testing
* Better async operation coverage
Integration Tests:
- App.test.tsx (41 lines):
* Updated for new routing logic
* Enhanced navigation testing
* Better component integration coverage
- SettingsDialog.test.tsx (88 lines):
* Complete rewrite for SettingsPage
* New integration test scenarios
* Enhanced user workflow testing
Mock Infrastructure:
- handlers.ts (117 lines):
* Major updates for MSW handlers
* New handlers for auto-launch commands
* Enhanced error simulation
* Better request/response mocking
- state.ts (37 lines):
* Updated mock state structure
* New state for auto-launch
* Enhanced state reset functionality
- tauriMocks.ts (10 lines):
* Updated mock implementations
* Better type safety
- server.ts & testQueryClient.ts:
* Minor cleanup (2 lines removed)
Test Infrastructure Improvements:
- Better test isolation
- Enhanced mock data consistency
- Improved async operation testing
- Better error scenario coverage
- Enhanced integration test patterns
Coverage Improvements:
- Net increase of 195 lines of test code
- Better coverage of edge cases
- Enhanced error path testing
- Improved integration test scenarios
- Better mock infrastructure
All tests now pass with the refactored components while maintaining
comprehensive coverage of functionality and edge cases.
* style(ui): improve window dragging and provider card styles
* fix(skills): resolve third-party skills installation failure
- Add skills_path field to Skill struct
- Use skills_path to construct correct source path during installation
- Fix installation for repos with custom skill subdirectories
* feat(icon): add icon type system and intelligent inference logic
Introduce a new icon system for provider customization:
- Add IconMetadata and IconPreset interfaces in src/types/icon.ts
- Define structure for icon name, display name, category, keywords
- Support default color configuration per icon
- Implement smart icon inference in src/config/iconInference.ts
- Create iconMappings for 25+ AI providers and cloud platforms
- Include Claude, DeepSeek, Qwen, Kimi, Google, AWS, Azure, etc.
- inferIconForPreset(): match provider name to icon config
- addIconsToPresets(): batch apply icons to preset arrays
- Support fuzzy matching for flexible name recognition
This foundation enables automatic icon assignment when users add
providers, improving visual identification in the provider list.
* feat(ui): add icon picker, color picker and provider icon components
Implement comprehensive icon selection system for provider customization:
## New Components
### ProviderIcon (src/components/ProviderIcon.tsx)
- Render SVG icons by name with automatic fallback
- Display provider initials when icon not found
- Support custom sizing via size prop
- Use dangerouslySetInnerHTML for inline SVG rendering
### IconPicker (src/components/IconPicker.tsx)
- Grid-based icon selection with visual preview
- Real-time search filtering by name and keywords
- Integration with icon metadata for display names
- Responsive grid layout (6-10 columns based on screen)
### ColorPicker (src/components/ColorPicker.tsx)
- 12 preset colors for quick selection
- Native color input for custom color picking
- Hex input field for precise color entry
- Visual feedback for selected color state
## Icon Assets (src/icons/extracted/)
- 38 high-quality SVG icons for AI providers and platforms
- Includes: OpenAI, Claude, DeepSeek, Qwen, Kimi, Gemini, etc.
- Cloud platforms: AWS, Azure, Google Cloud, Cloudflare
- Auto-generated index.ts with getIcon/hasIcon helpers
- Metadata system with searchable keywords per icon
## Build Scripts
- scripts/extract-icons.js: Extract icons from simple-icons
- scripts/generate-icon-index.js: Generate TypeScript index file
* feat(provider): integrate icon system into provider UI components
Add icon customization support to provider management interface:
## Type System Updates
### Provider Interface (src/types.ts)
- Add optional `icon` field for icon name (e.g., "openai", "anthropic")
- Add optional `iconColor` field for hex color (e.g., "#00A67E")
### Form Schema (src/lib/schemas/provider.ts)
- Extend providerSchema with icon and iconColor optional fields
- Maintain backward compatibility with existing providers
## UI Components
### ProviderCard (src/components/providers/ProviderCard.tsx)
- Display ProviderIcon alongside provider name
- Add icon container with hover animation effect
- Adjust layout spacing for icon placement
- Update translate offsets for action buttons
### BasicFormFields (src/components/providers/forms/BasicFormFields.tsx)
- Add icon preview section showing current selection
- Implement fullscreen icon picker dialog
- Auto-apply default color from icon metadata on selection
- Display provider name and icon status in preview
### AddProviderDialog & EditProviderDialog
- Pass icon fields through form submission
- Preserve icon data during provider updates
This enables users to visually distinguish providers in the list
with custom icons, improving UX for multi-provider setups.
* feat(backend): add icon fields to Provider model and default mappings
Extend Rust backend to support provider icon customization:
## Provider Model (src-tauri/src/provider.rs)
- Add `icon: Option<String>` field for icon name
- Add `icon_color: Option<String>` field for hex color
- Use serde rename `iconColor` for frontend compatibility
- Apply skip_serializing_if for clean JSON output
- Update Provider::new() to initialize icon fields as None
## Provider Defaults (src-tauri/src/provider_defaults.rs) [NEW]
- Define ProviderIcon struct with name and color fields
- Create DEFAULT_PROVIDER_ICONS static HashMap with 23 providers:
- AI providers: OpenAI, Anthropic, Claude, Google, Gemini,
DeepSeek, Kimi, Moonshot, Zhipu, MiniMax, Baidu, Alibaba,
Tencent, Meta, Microsoft, Cohere, Perplexity, Mistral, HuggingFace
- Cloud platforms: AWS, Azure, Huawei, Cloudflare
- Implement infer_provider_icon() with exact and fuzzy matching
- Add unit tests for matching logic (exact, fuzzy, case-insensitive)
## Deep Link Support (src-tauri/src/deeplink.rs)
- Initialize icon fields when creating Provider from deep link import
## Module Registration (src-tauri/src/lib.rs)
- Register provider_defaults module
## Dependencies (Cargo.toml)
- Add once_cell for lazy static initialization
This backend support enables icon persistence and future features
like auto-icon inference during provider creation.
* chore(i18n): add translations for icon picker and provider icon
Add Chinese and English translations for icon customization feature:
## Icon Picker (iconPicker)
- search: "Search Icons" / "搜索图标"
- searchPlaceholder: "Enter icon name..." / "输入图标名称..."
- noResults: "No matching icons found" / "未找到匹配的图标"
- category.aiProvider: "AI Providers" / "AI 服务商"
- category.cloud: "Cloud Platforms" / "云平台"
- category.tool: "Dev Tools" / "开发工具"
- category.other: "Other" / "其他"
## Provider Icon (providerIcon)
- label: "Icon" / "图标"
- colorLabel: "Icon Color" / "图标颜色"
- selectIcon: "Select Icon" / "选择图标"
- preview: "Preview" / "预览"
These translations support the new icon picker UI components
and provider form icon selection interface.
* style(ui): refine header layout and AppSwitcher color scheme
Improve application header and component styling:
## App.tsx Header Layout
- Wrap title and settings button in flex container with gap
- Add vertical divider between title and settings icon
- Apply responsive padding (pl-1 sm:pl-2)
- Reformat JSX for better readability (prettier)
- Fix string template formatting in className
## AppSwitcher Color Update
- Change Claude tab gradient from orange/amber to teal/emerald/green
- Update shadow color to match new teal theme
- Change hover color from orange-500 to teal-500
- Align visual style with emerald/teal brand colors
## Dialog Component Cleanup
- Remove default close button (X icon) from DialogContent
- Allow parent components to control close button placement
- Remove unused lucide-react X import
## index.css Header Border
- Add top border (2px solid) to glass-header
- Apply to both light and dark mode variants
- Improve visual separation of header area
These changes enhance visual consistency and modernize the UI
appearance with a cohesive teal color scheme.
* chore(deps): add icon library and update preset configurations
Add dependencies and utility scripts for icon system:
## Dependencies (package.json)
- Add @lobehub/icons-static-svg@1.73.0
- High-quality SVG icon library for AI providers
- Source for extracted icons in src/icons/extracted/
- Update pnpm-lock.yaml accordingly
## Provider Preset Updates (src/config/claudeProviderPresets.ts)
- Add optional `icon` and `iconColor` fields to ProviderPreset interface
- Apply to Anthropic Official preset as example:
- icon: "anthropic"
- iconColor: "#D4915D"
- Future presets can include default icon configurations
## Utility Script (scripts/filter-icons.js) [NEW]
- Helper script for filtering and managing icon assets
- Supports icon discovery and validation workflow
- Complements extract-icons.js and generate-icon-index.js
This completes the icon system infrastructure, providing all
necessary tools and dependencies for icon customization.
* refactor(ui): simplify AppSwitcher styles and migrate to local SVG icons
- Replace complex gradient animations with clean, minimal tab design
- Migrate from @lobehub/icons CDN to local SVG assets for better reliability
- Fix clippy warning in error.rs (use inline format args)
- Improve code formatting in skill service and commands
- Reduce CSS complexity in AppSwitcher component (removed blur effects and gradients)
- Update BrandIcons to use imported local SVG files instead of dynamic image loading
This improves performance, reduces external dependencies, and provides a cleaner UI experience.
* style(ui): hide scrollbars across all browsers and optimize form layout
- Hide scrollbars globally with cross-browser support:
* WebKit browsers (Chrome, Safari, Edge): ::-webkit-scrollbar { display: none }
* Firefox: scrollbar-width: none
* IE 10+: -ms-overflow-style: none
- Remove custom scrollbar styling (width, colors, hover states)
- Reorganize BasicFormFields layout:
* Move icon picker to top center as a clickable preview (80x80)
* Change name and notes fields to horizontal grid layout (md:grid-cols-2)
* Remove icon preview section from bottom
* Improve visual hierarchy and space efficiency
This provides a cleaner, more modern UI with hidden scrollbars while maintaining full scroll functionality.
* refactor(layout): standardize max-width to 60rem and optimize padding structure
- Unify container max-width across components:
* Replace max-w-4xl with max-w-[60rem] in App.tsx provider list
* Replace max-w-5xl with max-w-[60rem] in PromptPanel
* Move padding from header element to inner container for consistent spacing
- Optimize padding hierarchy:
* Remove px-6 from header element, add to inner header container
* Remove px-6 from main element, keep it only in provider list container
* Consolidate PromptPanel padding: move px-6 from nested divs to outer container
* Remove redundant pl-1 sm:pl-2 from logo/title area
- Benefits:
* Consistent 60rem max-width provides better readability on wide screens
* Simplified padding structure reduces CSS complexity
* Cleaner responsive behavior with unified spacing rules
This creates a more maintainable and visually consistent layout system.
* refactor(ui): unify layout system with 60rem max-width and consistent spacing
- Standardize container max-width across all panels:
* Replace max-w-4xl and max-w-5xl with unified max-w-[60rem]
* Apply to SettingsPage, UnifiedMcpPanel, SkillsPage, and FullScreenPanel
* Ensures consistent reading width and visual balance on wide screens
- Optimize padding hierarchy and structure:
* Move px-6 from parent elements to content containers
* FullScreenPanel: Add max-w-[60rem] wrapper to header, content, and footer
* Add border separators (border-t/border-b) to header and footer sections
* Consolidate nested padding in MCP, Skills, and Prompts panels
* Remove redundant padding layers for cleaner CSS
- Simplify component styling:
* MCP list items: Replace card-based layout with modern group-based design
* Remove unnecessary wrapper divs and flatten DOM structure
* Update card hover effects with smooth transitions
* Simplify icon selection dialog (remove description text in BasicFormFields)
- Benefits:
* Consistent 60rem max-width provides optimal readability
* Unified spacing rules reduce maintenance complexity
* Cleaner component hierarchy improves performance
* Better responsive behavior across different screen sizes
* More cohesive visual design language throughout the app
This creates a maintainable, scalable design system foundation.
* feat(deeplink): add Claude model fields support and enhance import dialog
- Add Claude-specific model field support in deeplink import:
* Support model (ANTHROPIC_MODEL) - general default model
* Support haikuModel (ANTHROPIC_DEFAULT_HAIKU_MODEL)
* Support sonnetModel (ANTHROPIC_DEFAULT_SONNET_MODEL)
* Support opusModel (ANTHROPIC_DEFAULT_OPUS_MODEL)
* Backend: Update DeepLinkImportRequest struct to include optional model fields
* Frontend: Add TypeScript type definitions for new model parameters
- Enhance deeplink demo page (deplink.html):
* Add 5 new Claude configuration examples showcasing different model setups
* Add parameter documentation with required/optional tags
* Include basic config (no models), single model, complete 4-model, partial models, and third-party provider examples
* Improve visual design with param-list component and color-coded badges
* Add detailed descriptions for each configuration scenario
- Redesign DeepLinkImportDialog layout:
* Switch from 3-column to compact 2-column grid layout
* Reduce dialog width from 500px to 650px for better content display
* Add dedicated section for Claude model configurations with blue highlight box
* Use uppercase labels and smaller text for more information density
* Add truncation and tooltips for long URLs
* Improve visual hierarchy with spacing and grouping
* Increase z-index to 9999 to ensure dialog appears on top
- Minor UI refinements:
* Update App.tsx layout adjustments
* Optimize McpFormModal styling
* Refine ProviderCard and BasicFormFields components
This enables users to import Claude providers with precise model configurations via deeplink.
* feat(deeplink): add config file support for deeplink import
Support importing provider configuration from embedded or remote config files.
- Add base64 dependency for config content encoding
- Support config, configFormat, and configUrl parameters
- Make homepage/endpoint/apiKey optional when config is provided
- Add config parsing and merging logic
* feat(deeplink): enhance dialog with config file preview
Add config file parsing and preview in deep link import dialog.
- Support Base64 encoded config display
- Add config file source indicator (embedded/remote)
- Parse and display config fields by app type (Claude/Codex/Gemini)
- Mask sensitive values in config preview
- Improve dialog layout and content organization
* refactor(ui): unify dialog styles and improve layout consistency
Standardize dialog and panel components across the application.
- Update dialog background to use semantic color tokens
- Adjust FullScreenPanel max-width to 56rem for better alignment
- Add drag region and prevent body scroll in full-screen panels
- Optimize button sizes and spacing in panel headers
- Apply consistent styling to all dialog-based components
* i18n: add deeplink config preview translations
Add missing translation keys for config file preview feature.
- Add configSource, configEmbedded, configRemote labels
- Add configDetails and configUrl display strings
- Support both Chinese and English versions
* feat(deeplink): enhance test page with v3.8 config file examples
Improve deeplink test page with comprehensive config file import examples.
- Add version badge for v3.8 features
- Add copy-to-clipboard functionality for all deep links
- Add Claude config file import examples (embedded/remote)
- Add Codex config file import examples (auth.json + config.toml)
- Add Gemini config file import examples (.env format)
- Add config generator tool for easy testing
- Update UI with better styling and layout
* feat(settings): add autoSaveSettings for lightweight auto-save
Add optimized auto-save function for General tab settings.
- Add autoSaveSettings method for non-destructive auto-save
- Only trigger system APIs when values actually change
- Avoid unnecessary auto-launch and plugin config updates
- Update tests to cover new functionality
* refactor(settings): simplify settings page layout and auto-save
Reorganize settings page structure and integrate autoSaveSettings.
- Move save button inline within Advanced tab content
- Remove sticky footer for cleaner layout
- Use autoSaveSettings for General tab settings
- Simplify dialog close behavior
- Refactor ImportExportSection layout
* style(providers): optimize card layout and action button sizes
Improve provider card visual density and action buttons.
- Reduce icon button sizes for compact layout
- Adjust drag handle and icon sizes
- Tighten spacing between action buttons
- Update hover translate values for better alignment
* refactor(mcp): improve form modal layout with adaptive height editor
Restructure MCP form modal for better space utilization.
- Split form into upper form fields and lower JSON editor sections
- Add full-height mode support for JsonEditor component
- Use flex layout for editor to fill available space
- Update PromptFormPanel to use full-height editor
- Fix locale text formatting
* style: unify list item styles with semantic colors
Apply consistent styling to list items across components.
- Replace hardcoded colors with semantic tokens in MCP and Prompt list items
- Add glass effect container to EndpointSpeedTest panel
- Format code for better readability
* style: format template literals for better readability
Improve code formatting for conditional className expressions.
- Break long template literals across multiple lines
- Maintain consistent formatting in MCP form and endpoint test components
* feat(deeplink): add config merge command for preview
Expose config merging functionality to frontend for preview.
- Add merge_deeplink_config Tauri command
- Make parse_and_merge_config public for reuse
- Enable frontend to display complete config before import
* feat(deeplink): merge and display config in import dialog
Enhance import dialog to fetch and display complete config.
- Call mergeDeeplinkConfig API when config is present
- Add UTF-8 base64 decoding support for config content
- Add scrollable content area with custom scrollbar styling
- Show complete configuration before user confirms import
* i18n: add config merge error message
Add translation for config file merge error handling.
* style(deeplink): format test page HTML for better readability
Improve HTML formatting in deeplink test page.
- Format multiline attributes for better readability
- Add consistent indentation to nested elements
- Break long lines in buttons and links
* refactor(usage): improve footer layout with two-row design
Reorganize usage footer for better readability and space efficiency.
- Split into two rows: update time + refresh button (row 1), usage stats (row 2)
- Move refresh button to top row next to update time
- Remove card background for cleaner look
- Add fallback text when never updated
- Improve spacing and alignment
- Format template literals for consistency
* feat(database): add SQLite database infrastructure
- Add rusqlite dependency (v0.32.1) and r2d2 connection pooling
- Implement Database module with CRUD operations for providers, MCP servers, prompts, and skills
- Add schema initialization with proper indexes
- Include data migration utilities from JSON config to SQLite
- Support timestamp tracking (created_at, updated_at)
* refactor(core): integrate SQLite database into application core
- Initialize database on app startup with migration from JSON config
- Update AppState to include Database instance alongside MultiAppConfig
- Simplify store module by removing unused session management code
- Add database initialization to app setup flow
- Support both database and legacy config during transition
* refactor(services): migrate service layer to use SQLite database
- Refactor ProviderService to use database queries instead of in-memory config
- Update McpService to fetch and store MCP servers in database
- Migrate PromptService to database-backed storage
- Simplify ConfigService by removing complex transaction logic
- Remove 648 lines of redundant code through database abstraction
* refactor(commands): update command layer to use database API
- Update config commands to query database for providers and settings
- Modify provider commands to pass database handle to services
- Update MCP commands to use database-backed operations
- Refactor prompt and skill commands to leverage database storage
- Simplify import/export commands with database integration
* refactor(backend): update supporting modules for database compatibility
- Add DatabaseError variant to AppError enum
- Update provider module to support database-backed operations
- Modify codex_config to work with new database structure
- Ensure error handling covers database operations
* refactor(frontend): update UI components for database migration
- Update UsageFooter component to handle new data structure
- Modify SkillsPage to work with database-backed skills management
- Ensure frontend compatibility with refactored backend
* feat(skills): add search functionality to Skills page
- Add search input with Search icon in SkillsPage component
- Implement useMemo-based filtering by skill name, description, and directory
- Display search results count when filtering is active
- Show "no results" message when no skills match the search query
- Add i18n translations for search UI (zh/en)
- Maintain responsive layout and consistent styling with existing UI
* refactor(backend): replace unsafe unwrap calls with proper error handling
- Add to_json_string helper for safe JSON serialization
- Add lock_conn macro for safe Mutex locking
- Replace 41 unwrap() calls with proper error handling:
- database.rs: JSON serialization and Mutex operations (31 fixes)
- lib.rs: macOS NSWindow and tray icon handling (3 fixes)
- services/provider.rs: Claude model normalization (1 fix)
- services/prompt.rs: timestamp generation (3 fixes)
- services/skill.rs: directory name extraction (2 fixes)
- mcp.rs: HashMap initialization and type conversions (5 fixes)
- app_config.rs: timestamp fallback (1 fix)
This improves application stability and prevents potential panics.
* feat(init): implement automatic data import on first launch
Add comprehensive first-launch data import system:
Database layer:
- Add is_empty_for_first_import() to detect empty database
- Add init_default_skill_repos() to initialize 3 default skill repositories
Services layer:
- Implement McpService::import_from_claude/codex/gemini()
to import MCP servers from existing config files
- Implement PromptService::import_from_file_on_first_launch()
to import prompt files (CLAUDE.md, AGENTS.md, GEMINI.md)
Startup flow (lib.rs):
- Check if database is empty on startup
- Import existing configurations if detected:
1. Initialize default skill repositories
2. Import provider configurations from live settings
3. Import MCP servers from config files
4. Import prompt files
- All imports are fault-tolerant and logged
This ensures seamless migration from file-based configs to database.
* fix(skills): auto-sync locally installed skills to database
Add automatic synchronization in get_skills command:
- Detect locally installed skills in ~/.claude/skills/
- Auto-add to database if not already tracked
- Ensures existing skills are recognized on first launch
This fixes the issue where user's existing skills were not
imported into the database on initial application run.
* docs(frontend): add code comments and improve formatting
- Add explanatory comment in EditProviderDialog about config assembly
- Improve import formatting in SkillsPage for better readability
* feat(deeplink): display all four Claude model fields in import dialog
- Show haiku/sonnet/opus/multiModel fields conditionally for Claude
- Maintain single model field display for Codex and Gemini
- Add i18n translations for new model field labels (zh/en)
* feat(backend): add database SQL export/import with backup
- Enable rusqlite backup feature for SQL dump support
- Implement export_sql to generate SQLite-compatible SQL dumps
- Implement import_sql with automatic backup before import
- Add snapshot_to_memory to avoid long-held database locks
- Add backup rotation to retain latest 10 backups
- Support atomic import with rollback on failure
* refactor(backend): migrate import/export to use SQL backup
- Reimplement export_config_to_file to use database.export_sql
- Reimplement import_config_from_file to use database.import_sql
- Add sync_current_from_db to sync live configs after import
- Add settings database binding on app initialization
- Remove deprecated JSON-based config import logic
* refactor(backend): migrate settings storage to database
- Add bind_db function to initialize database-backed settings
- Implement load_initial_settings with database fallback
- Replace direct file save with settings store management
- Add SETTINGS_DB static for database binding
- Maintain backward compatibility with file-based settings
- Keep settings.json for legacy migration support
* feat(frontend): update import/export UI for SQL backup
- Change default export filename from .json to .sql
- Update file format to timestamp format (YYYYMMDD_HHMMSS)
- Update error messages to reference SQL files
- Align with backend SQL export/import implementation
* feat(i18n): update translations for SQL backup feature
- Update Chinese translations for SQL import/export
- Update English translations for SQL import/export
- Change terminology from 'config file' to 'SQL backup'
- Update error messages and UI hints
* fix(backend): remove unnecessary dereference in backup operation
- Simplify Backup::new call by removing redundant dereference
- MutexGuard already implements DerefMut
* feat(icons): add PackyCode provider icon support
Add PackyCode as a supported AI provider icon with proper metadata
and filtering configuration.
Changes:
- Add 'packycode' to icon filter whitelist in filter-icons.js
- Register PackyCode metadata with display name, category, and keywords
- Import PackyCode SVG icon file
- Export icon through index.ts for use in provider configurations
The PackyCode icon uses currentColor to adapt to theme styling.
* feat(utils): add base64 encoding utility functions
Add reusable base64 encoding/decoding utility functions for handling
binary data and string conversions in deeplink imports.
Features:
- encodeBase64: Encode string to base64
- decodeBase64: Decode base64 to string
- Uses browser-native btoa/atob with proper UTF-8 handling
This utility will be used for encoding prompt content and configuration
files in deeplink URLs.
* feat(backend): add in-memory database mode for testing
Add support for creating in-memory SQLite database instances to
improve test isolation and performance.
Changes:
- Add Database::memory() constructor for in-memory database
- Enable foreign key constraints for data integrity
- Export Database type from lib.rs for test usage
- Initialize tables automatically on memory database creation
This enables unit tests to run without filesystem dependencies and
provides faster test execution with proper cleanup.
* refactor(deeplink): extend support for multi-resource imports
Extend the deeplink import system to support importing multiple
resource types beyond providers: prompts, MCP servers, and skills.
Breaking changes:
- DeepLinkImportRequest: Convert required fields to Optional to
support different resource types (app, name, homepage, endpoint,
apiKey are now Option<String>)
- Add resource-specific fields for prompt, mcp, and skill imports
New features:
- parse_prompt_deeplink: Parse prompt import URLs with base64 content
- parse_mcp_deeplink: Parse MCP server import URLs with config
- parse_skill_deeplink: Parse GitHub skill repository URLs
- import_prompt_from_deeplink: Import prompts to database
- import_mcp_from_deeplink: Batch import MCP servers with multi-app support
- import_skill_from_deeplink: Clone and install skill repositories
Data model additions:
- Prompt fields: content (base64), description, enabled
- MCP fields: apps (comma-separated), config, config_format
- Skill fields: repo, directory, branch, skills_path
- Common fields: icon (provider icon name)
McpImportResult type:
- imported_count: Number of successfully imported servers
- imported_ids: List of imported server IDs
- failed: List of failed imports with error messages
URL format examples:
- Prompt: ccswitch://v1/import?resource=prompt&app=claude&name=...&content=...
- MCP: ccswitch://v1/import?resource=mcp&apps=claude,codex&config=...
- Skill: ccswitch://v1/import?resource=skill&repo=owner/name&directory=...
This refactor enables one-click sharing of prompts, MCP configurations,
and skill repositories via deeplink URLs.
* feat(backend): add unified deeplink import command
Add a new unified command handler for importing all resource types
via deeplinks, replacing the legacy provider-only import command.
Changes:
- Add import_from_deeplink_unified command supporting all resource types
- Keep import_from_deeplink for backward compatibility (now marked legacy)
- Route imports based on request.resource field (provider/prompt/mcp/skill)
- Return typed ImportResult with resource-specific data
Return types:
- Provider: { type: "provider", id: string }
- Prompt: { type: "prompt", id: string }
- MCP: { type: "mcp", importedCount, importedIds, failed }
- Skill: { type: "skill", key: string }
The unified handler simplifies frontend logic by providing consistent
return types and error handling across all resource types.
* feat(frontend): extend deeplink API for multi-resource support
Update the frontend deeplink API to support importing multiple
resource types with proper TypeScript typing.
Changes:
- Add ResourceType union type: "provider" | "prompt" | "mcp" | "skill"
- Convert DeepLinkImportRequest fields to optional (matching backend)
- Add resource-specific field types (prompt, mcp, skill)
- Add ImportResult discriminated union for type-safe results
- Add McpImportResult interface for batch import results
- Update importFromDeeplink to use unified command
Type safety improvements:
- ImportResult discriminated union ensures proper type narrowing
- Each result type has its own specific return data structure
- Frontend can pattern match on result.type for correct handling
Breaking change:
- importFromDeeplink now returns ImportResult instead of string
- Callers must handle all resource types appropriately
* feat(frontend): add resource-specific confirmation dialog components
Add specialized confirmation UI components for each deeplink import
resource type (Prompt, MCP, Skill).
Components:
- PromptConfirmation: Display prompt name, app, description, and
content preview with markdown rendering
- McpConfirmation: Show MCP server list with target apps, supports
batch import display
- SkillConfirmation: Display GitHub repository info with branch and
directory details
Features:
- Consistent card-based layout with proper spacing
- Sensitive data masking (API keys shown as dots)
- Icon support for providers (ProviderIcon component)
- Badge components for visual status indicators
- Responsive design with proper text overflow handling
Each component focuses on displaying the most relevant information
for users to make informed import decisions.
* feat(frontend): update DeepLinkImportDialog for multi-resource imports
Refactor the main deeplink import dialog to handle all resource types
with proper confirmation UI and post-import actions.
Key changes:
- Add resource-specific confirmation components (Prompt/MCP/Skill)
- Implement typed result handling with discriminated unions
- Add MCP result type guard for backward compatibility
- Implement resource-specific cache invalidation strategies
- Add custom event dispatching for non-React-Query resources
Import flow improvements:
- Provider: Invalidate provider queries, show success toast
- Prompt: Dispatch "prompt-imported" event, trigger manual refresh
- MCP: Aggressive cache invalidation with refetchQueries, handle
partial success (show warning if some imports failed)
- Skill: Force refetch skills query with refetchType: "all"
Error handling:
- Graceful fallback for legacy backend responses (no type field)
- MCP-specific result detection via type guard
- Detailed error messages in toasts
UI improvements:
- Dynamic dialog title based on resource type
- Resource-specific confirmation content rendering
- Better visual feedback during import process
* feat(frontend): add deeplink import event listeners and UI improvements
Add event-driven refresh logic for deeplink imports and enhance
Skills page filtering capabilities.
PromptPanel changes:
- Add "prompt-imported" custom event listener
- Auto-reload prompts when deeplink import completes
- Filter events by app ID to avoid unnecessary refreshes
- Clean up event listener on component unmount
SkillsPage improvements:
- Add installation status filter (all/installed/uninstalled)
- Implement Select component for filter dropdown
- Combine status filter with existing search functionality
- Update filtered skills memo to include both filters
- Improve responsive layout for search and filter controls
Event flow:
1. DeepLinkImportDialog dispatches "prompt-imported" event
2. PromptPanel listens for event matching its app
3. Panel triggers reload to show newly imported prompt
4. Similar pattern can be used for other non-React-Query resources
These improvements enable seamless UI updates after deeplink imports
without requiring manual page refresh.
* test(deeplink): migrate tests to use in-memory database
Update deeplink import tests to use the new in-memory database
instead of filesystem-based configuration.
Changes:
- Replace MultiAppConfig with Database-based AppState
- Use Database::memory() for isolated test instances
- Update provider verification to query database directly
- Add icon field verification in test assertions
- Remove filesystem config.json validation (now DB-backed)
Test improvements:
- Faster execution (no disk I/O)
- Better isolation (each test gets fresh DB instance)
- No cleanup required (memory DB auto-discarded)
- Consistent with v3.8+ storage architecture
Updated tests:
- deeplink_import_claude_provider_persists_to_db
- deeplink_import_codex_provider_builds_auth_and_config
Both tests verify that deeplink imports correctly persist provider
data to the database with all expected fields.
* feat(i18n): add translations for deeplink and skills features
Add internationalization support for new deeplink import features
and skills page filtering.
Deeplink translations:
- Add "icon" field label for provider icon selection
- Both Chinese ("图标") and English ("Icon") translations
Skills page translations:
- Add filter placeholder and options
- Filter states: "all", "installed", "uninstalled"
- Chinese: "全部", "已安装", "未安装"
- English: "All", "Installed", "Not installed"
These translations ensure consistent multilingual support for the
new multi-resource deeplink import system and improved skills
management UI.
* chore: add deeplink testing HTML page
Add a local HTML page for testing deeplink protocol functionality
during development.
This page allows developers to:
- Test different deeplink URL formats (provider/prompt/mcp/skill)
- Verify URL parsing and parameter encoding
- Quickly validate deeplink imports without external tools
- Debug protocol registration on different platforms
Not intended for production use, only for development testing.
* refactor(ui): improve icon rendering consistency and spacing
Standardize icon sizes and improve rendering consistency across the
application interface.
Changes:
- ProviderIcon: Add fontSize sync with size prop for embedded SVG
scaling, improve fallback text sizing calculation
- AppSwitcher: Replace brand icon components with unified ProviderIcon,
standardize icon size to 20px across all app tabs
- ProviderCard: Reduce icon size from 26px to 20px for better visual
balance in card layout
- DeepLinkImportDialog: Increase confirmation icon size from 64px to
80px for better visibility
- BasicFormFields: Reduce icon picker button spacing from space-y-6 to
space-y-2 for improved layout density
Icon rendering improvements:
- Embedded SVGs now properly scale with fontSize set to match size prop
- Fallback initials use responsive font sizing (50% of icon size,
minimum 12px)
- Consistent 20px standard for navigation and card icons
- Better visual hierarchy with appropriate sizing for different contexts
This refactor improves visual consistency and makes icon rendering
more predictable across different components.
Core changes:
- Extract transaction logic into reusable migrate_from_json_tx() helper
- Add migrate_from_json_dry_run() for in-memory validation without disk writes
- Implement three-state migration mode enum (Disabled/DryRun/Enabled)
- Support CC_SWITCH_ENABLE_JSON_DB_MIGRATION=dryrun for safe testing
Code quality improvements:
- Remove redundant migration_needed variable
- Unify all log messages to English
- Use info level for user-initiated operations instead of warn
- Add explicit drop(tx) in dry-run to clarify intent
Testing:
- Add dry_run_does_not_write_to_disk test
- Add dry_run_validates_schema_compatibility test with real data
- All 6 database tests passing, zero clippy warnings
This enables users to safely validate migration compatibility before
committing to the database, catching schema mismatches early.
- Fix column name quoting in ALTER TABLE statements to prevent SQL
syntax errors with reserved keywords
- Use case-insensitive table name matching in table_exists()
- Change type declarations from INTEGER to BOOLEAN for semantic clarity
- Add CC_SWITCH_ENABLE_JSON_DB_MIGRATION env var to gate JSON→SQLite
migration (disabled by default until feature is stable)
- Refactor tests with shared legacy schema and column info helpers
- Add comprehensive test for column types and default values alignment
This commit addresses several critical issues in the database migration system:
**Schema Consistency**
- Unified DEFAULT and NOT NULL constraints between CREATE TABLE and ALTER TABLE
- Fixed inconsistencies in: providers.meta, mcp_servers.tags, skills.installed_at, skill_repos.branch
- Ensures new installations and migrations produce identical schemas
**Security & Validation**
- Added SQL identifier validation to prevent potential injection risks
- Validates table and column names contain only alphanumeric characters and underscores
**Error Handling**
- Fixed table_exists() to distinguish "table not found" from "query failed"
- Properly propagates database errors instead of silently ignoring them
- Uses pattern matching on rusqlite::Error::QueryReturnedNoRows
**Transaction Safety**
- Implemented SAVEPOINT-based transaction protection for migrations
- Ensures user_version stays consistent with actual schema on failures
- Graceful rollback on migration errors (within SQLite's ALTER TABLE limitations)
**Testing**
- Enhanced test coverage to verify NOT NULL constraints and DEFAULT values
- Validates that migration produces the expected schema structure
These improvements ensure database migrations are more reliable, secure, and maintainable.
Implement SQLite PRAGMA user_version based migration system:
- Track schema version with SCHEMA_VERSION constant
- Apply migrations automatically on init and import
- Reject databases from future versions for forward compatibility
- Add comprehensive tests for version transitions
- Prepare infrastructure for future schema evolution
This lays the foundation for safe incremental database upgrades.
This commit refactors all tests to work with the new database-based
architecture, replacing the previous JSON config approach.
Key changes:
- Add Database export to lib.rs for test access
- Create test helper functions in support.rs:
- create_test_state(): Creates empty test state with fresh DB
- create_test_state_with_config(): Migrates JSON config to DB
- Fix environment isolation in provider_service tests:
- provider_service_switch_missing_provider_returns_error
- provider_service_switch_codex_missing_auth_returns_error
- Replace ignored export tests with working alternatives:
- export_sql_writes_to_target_path (tests Database::export_sql)
- export_sql_returns_error_for_invalid_path (tests error handling)
- Update error type matching to align with current implementation
All tests now:
- Use isolated test environments (test_mutex + reset_test_fs)
- Access data via Database API instead of RwLock<MultiAppConfig>
- Work with SQLite persistence layer
- Pass without environment pollution or race conditions
Fixes test compilation errors after database migration.
Fixed two critical issues:
1. **Blocking Issue - restart_app return type error**
- Fixed compilation error where restart_app() didn't return a value
- Used async spawn with 100ms delay to allow response before restart
- Prevents "unreachable code" compiler error
2. **High Priority - Import SQL doesn't refresh AppSettings cache**
- Added reload_settings() function to refresh in-memory settings cache
- Integrated into import flow to ensure imported settings take effect
- Prevents imported settings being overwritten by stale memory cache
- Affects: language, config directories, auto-launch, custom endpoints
Changes:
- src/commands/settings.rs: Async delayed restart with proper return value
- src/settings.rs: New reload_settings() to sync memory cache from DB
- src/commands/import_export.rs: Call reload_settings() after SQL import
Verified: cargo clippy --lib and pnpm typecheck both pass
* feat(components): add reusable full-screen panel components
Add new full-screen panel components to support the UI refactoring:
- FullScreenPanel: Reusable full-screen layout component with header,
content area, and optional footer. Provides consistent layout for
settings, prompts, and other full-screen views.
- PromptFormPanel: Dedicated panel for creating and editing prompts
with markdown preview support. Features real-time validation and
integrated save/cancel actions.
- AgentsPanel: Panel component for managing agent configurations.
Provides a consistent interface for agent CRUD operations.
- RepoManagerPanel: Full-featured repository manager panel for Skills.
Supports repository listing, addition, deletion, and configuration
management with integrated validation.
These components establish the foundation for the upcoming settings
page migration from dialog-based to full-screen layout.
* refactor(settings): migrate from dialog to full-screen page layout
Complete migration of settings from modal dialog to dedicated full-screen
page, improving UX and providing more space for configuration options.
Changes:
- Remove SettingsDialog component (legacy modal-based interface)
- Add SettingsPage component with full-screen layout using FullScreenPanel
- Refactor App.tsx routing to support dedicated settings page
* Add settings route handler
* Update navigation logic from dialog-based to page-based
* Integrate with existing app switcher and provider management
- Update ImportExportSection to work with new page layout
* Improve spacing and layout for better readability
* Enhanced error handling and user feedback
* Better integration with page-level actions
- Enhance useSettings hook to support page-based workflow
* Add navigation state management
* Improve settings persistence logic
* Better error boundary handling
Benefits:
- More intuitive navigation with dedicated settings page
- Better use of screen space for complex configurations
- Improved accessibility with clearer visual hierarchy
- Consistent with modern desktop application patterns
- Easier to extend with new settings sections
This change is part of the larger UI refactoring initiative to modernize
the application interface and improve user experience.
* refactor(forms): simplify and modernize form components
Comprehensive refactoring of form components to reduce complexity,
improve maintainability, and enhance user experience.
Provider Forms:
- CodexCommonConfigModal & CodexConfigSections
* Simplified state management with reduced boilerplate
* Improved field validation and error handling
* Better layout with consistent spacing
* Enhanced model selection with visual indicators
- GeminiCommonConfigModal & GeminiConfigSections
* Streamlined authentication flow (OAuth vs API Key)
* Cleaner form layout with better grouping
* Improved validation feedback
* Better integration with parent components
- CommonConfigEditor
* Reduced from 178 to 68 lines (-62% complexity)
* Extracted reusable form patterns
* Improved JSON editing with syntax validation
* Better error messages and recovery options
- EndpointSpeedTest
* Complete rewrite for better UX
* Real-time testing progress indicators
* Enhanced error handling with retry logic
* Visual feedback for test results (color-coded latency)
MCP & Prompts:
- McpFormModal
* Simplified from 581 to ~360 lines
* Better stdio/http server type handling
* Improved form validation
* Enhanced multi-app selection (Claude/Codex/Gemini)
- PromptPanel
* Cleaner integration with PromptFormPanel
* Improved list/grid view switching
* Better state management for editing workflows
* Enhanced delete confirmation with safety checks
Code Quality Improvements:
- Reduced total lines by ~251 lines (-24% code reduction)
- Eliminated duplicate validation logic
- Improved TypeScript type safety
- Better component composition and separation of concerns
- Enhanced accessibility with proper ARIA labels
These changes make forms more intuitive, responsive, and easier to
maintain while reducing bundle size and improving runtime performance.
* style(ui): modernize component layouts and visual design
Update UI components with improved layouts, visual hierarchy, and
modern design patterns for better user experience.
Navigation & Brand Components:
- AppSwitcher
* Enhanced visual design with better spacing
* Improved active state indicators
* Smoother transitions and hover effects
* Better mobile responsiveness
- BrandIcons
* Optimized icon rendering performance
* Added support for more provider icons
* Improved SVG handling and fallbacks
* Better scaling across different screen sizes
Editor Components:
- JsonEditor
* Enhanced syntax highlighting
* Better error visualization
* Improved code formatting options
* Added line numbers and code folding support
- UsageScriptModal
* Complete layout overhaul (1239 lines refactored)
* Better script editor integration
* Improved template selection UI
* Enhanced preview and testing panels
* Better error feedback and validation
Provider Components:
- ProviderCard
* Redesigned card layout with modern aesthetics
* Better information density and readability
* Improved action buttons placement
* Enhanced status indicators (active/inactive)
- ProviderList
* Better grid/list view layouts
* Improved drag-and-drop visual feedback
* Enhanced sorting indicators
- ProviderActions
* Streamlined action menu
* Better icon consistency
* Improved tooltips and accessibility
Usage & Footer:
- UsageFooter
* Redesigned footer layout
* Better quota visualization
* Improved refresh controls
* Enhanced error states
Design System Updates:
- dialog.tsx (shadcn/ui component)
* Updated to latest design tokens
* Better overlay animations
* Improved focus management
- index.css
* Added 65 lines of global utility classes
* New animation keyframes
* Enhanced color variables for dark mode
* Improved typography scale
- tailwind.config.js
* Extended theme with new design tokens
* Added custom animations and transitions
* New spacing and sizing utilities
* Enhanced color palette
Visual Improvements:
- Consistent border radius across components
- Unified shadow system for depth perception
- Better color contrast for accessibility (WCAG AA)
- Smoother animations and transitions
- Improved dark mode support
These changes create a more polished, modern interface while
maintaining consistency with the application's design language.
* chore: update dialogs, i18n and improve component integration
Various functional updates and improvements across provider dialogs,
MCP panel, skills page, and internationalization.
Provider Dialogs:
- AddProviderDialog
* Simplified form state management
* Improved preset selection workflow
* Better validation error messages
* Enhanced template variable handling
- EditProviderDialog
* Streamlined edit flow with better state synchronization
* Improved handling of live config backfilling
* Better error recovery for failed updates
* Enhanced integration with parent components
MCP & Skills:
- UnifiedMcpPanel
* Reduced complexity from 140+ to ~95 lines
* Improved multi-app server management
* Better server type detection (stdio/http)
* Enhanced server status indicators
* Cleaner integration with MCP form modal
- SkillsPage
* Simplified navigation and state management
* Better integration with RepoManagerPanel
* Improved error handling for repository operations
* Enhanced loading states
- SkillCard
* Minor layout adjustments
* Better action button placement
Environment & Configuration:
- EnvWarningBanner
* Improved conflict detection messages
* Better visual hierarchy for warnings
* Enhanced dismissal behavior
- tauri.conf.json
* Updated build configuration
* Added new window management options
Internationalization:
- en.json & zh.json
* Added 17 new translation keys for new features
* Updated existing keys for better clarity
* Added translations for new settings page
* Improved consistency across UI text
Code Cleanup:
- mutations.ts
* Removed 14 lines of unused mutation definitions
* Cleaned up deprecated query invalidation logic
* Better type safety for mutation parameters
Overall Impact:
- Reduced total lines by 51 (-10% in affected files)
- Improved component integration and data flow
- Better error handling and user feedback
- Enhanced i18n coverage for new features
These changes improve the overall polish and integration of various
components while removing technical debt and unused code.
* feat(backend): add auto-launch functionality
Implement system auto-launch feature to allow CC-Switch to start
automatically on system boot, improving user convenience.
Backend Implementation:
- auto_launch.rs: New module for auto-launch management
* Cross-platform support using auto-launch crate
* Enable/disable auto-launch with system integration
* Proper error handling for permission issues
* Platform-specific implementations (macOS/Windows/Linux)
Command Layer:
- Add get_auto_launch command to check current status
- Add set_auto_launch command to toggle auto-start
- Integrate commands with settings API
Settings Integration:
- Extend Settings struct with auto_launch field
- Persist auto-launch preference in settings store
- Automatic state synchronization on app startup
Dependencies:
- Add auto-launch ^0.5.0 to Cargo.toml
- Update Cargo.lock with new dependency tree
Technical Details:
- Uses platform-specific auto-launch mechanisms:
* macOS: Login Items via LaunchServices
* Windows: Registry Run key
* Linux: XDG autostart desktop files
- Handles edge cases like permission denials gracefully
- Maintains settings consistency across app restarts
This feature enables users to have CC-Switch readily available
after system boot without manual intervention, particularly useful
for users who frequently switch between API providers.
* refactor(settings): enhance settings page with auto-launch integration
Complete refactoring of settings page architecture to integrate auto-launch
feature and improve overall settings management workflow.
SettingsPage Component:
- Integrate auto-launch toggle with WindowSettings section
- Improve layout and spacing for better visual hierarchy
- Enhanced error handling for settings operations
- Better loading states during settings updates
- Improved accessibility with proper ARIA labels
WindowSettings Component:
- Add auto-launch switch with real-time status
- Integrate with backend auto-launch commands
- Proper error feedback for permission issues
- Visual indicators for current auto-launch state
- Tooltip guidance for auto-launch functionality
useSettings Hook (Major Refactoring):
- Complete rewrite reducing complexity by ~30%
- Better separation of concerns with dedicated handlers
- Improved state management using React Query
- Enhanced auto-launch state synchronization
* Fetch auto-launch status on mount
* Real-time updates on toggle
* Proper error recovery
- Optimized re-renders with better memoization
- Cleaner API for component integration
- Better TypeScript type safety
Settings API:
- Add getAutoLaunch() method
- Add setAutoLaunch(enabled: boolean) method
- Type-safe Tauri command invocations
- Proper error propagation to UI layer
Architecture Improvements:
- Reduced hook complexity from 197 to ~140 effective lines
- Eliminated redundant state management logic
- Better error boundaries and fallback handling
- Improved testability with clearer separation
User Experience Enhancements:
- Instant visual feedback on auto-launch toggle
- Clear error messages for permission issues
- Loading indicators during async operations
- Consistent behavior across all platforms
This refactoring provides a solid foundation for future settings
additions while maintaining code quality and user experience.
* refactor(ui): optimize FullScreenPanel, Dialog and App routing
Comprehensive refactoring of core UI components to improve code quality,
maintainability, and user experience.
FullScreenPanel Component:
- Enhanced props interface with better TypeScript types
- Improved layout flexibility with customizable padding
- Better header/footer composition patterns
- Enhanced scroll behavior for long content
- Added support for custom actions in header
- Improved responsive design for different screen sizes
- Better integration with parent components
- Cleaner prop drilling with context where appropriate
Dialog Component (shadcn/ui):
- Updated to latest component patterns
- Improved animation timing and easing
- Better focus trap management
- Enhanced overlay styling with backdrop blur
- Improved accessibility (ARIA labels, keyboard navigation)
- Better close button positioning and styling
- Enhanced mobile responsiveness
- Cleaner composition with DialogHeader/Footer
App Component Routing:
- Refactored routing logic for better clarity
- Improved state management for navigation
- Better integration with settings page
- Enhanced error boundary handling
- Cleaner separation of layout concerns
- Improved provider context propagation
- Better handling of deep links
- Optimized re-renders with React.memo where appropriate
Code Quality Improvements:
- Reduced prop drilling with better component composition
- Improved TypeScript type safety
- Better separation of concerns
- Enhanced code readability with clearer naming
- Eliminated redundant logic
Performance Optimizations:
- Reduced unnecessary re-renders
- Better memoization of callbacks
- Optimized component tree structure
- Improved event handler efficiency
User Experience:
- Smoother transitions and animations
- Better visual feedback for interactions
- Improved loading states
- More consistent behavior across features
These changes create a more maintainable and performant foundation
for the application's UI layer while improving the overall user
experience with smoother interactions and better visual polish.
* refactor(features): modernize Skills, Prompts and Agents components
Major refactoring of feature components to improve code quality,
user experience, and maintainability.
SkillsPage Component (299 lines refactored):
- Complete rewrite of layout and state management
- Better integration with RepoManagerPanel
- Improved navigation between list and detail views
- Enhanced error handling with user-friendly messages
- Better loading states with skeleton screens
- Optimized re-renders with proper memoization
- Cleaner separation between list and form views
- Improved skill card interactions
- Better responsive design for different screen sizes
RepoManagerPanel Component (370 lines refactored):
- Streamlined repository management workflow
- Enhanced form validation with real-time feedback
- Improved repository list with better visual hierarchy
- Better handling of git operations (clone, pull, delete)
- Enhanced error recovery for network issues
- Cleaner state management reducing complexity
- Improved TypeScript type safety
- Better integration with Skills backend API
- Enhanced loading indicators for async operations
PromptPanel Component (249 lines refactored):
- Modernized layout with FullScreenPanel integration
- Better separation between list and edit modes
- Improved prompt card design with better readability
- Enhanced search and filter functionality
- Cleaner state management for editing workflow
- Better integration with PromptFormPanel
- Improved delete confirmation with safety checks
- Enhanced keyboard navigation support
PromptFormPanel Component (238 lines refactored):
- Streamlined form layout and validation
- Better markdown editor integration
- Real-time preview with syntax highlighting
- Improved validation error display
- Enhanced save/cancel workflow
- Better handling of large prompt content
- Cleaner form state management
- Improved accessibility features
AgentsPanel Component (33 lines modified):
- Minor layout adjustments for consistency
- Better integration with FullScreenPanel
- Improved placeholder states
- Enhanced error boundaries
Type Definitions (types.ts):
- Added 10 new type definitions
- Better type safety for Skills/Prompts/Agents
- Enhanced interfaces for repository management
- Improved typing for form validations
Architecture Improvements:
- Reduced component coupling
- Better prop interfaces with explicit types
- Improved error boundaries
- Enhanced code reusability
- Better testing surface
User Experience Enhancements:
- Smoother transitions between views
- Better visual feedback for actions
- Improved error messages
- Enhanced loading states
- More intuitive navigation flows
- Better responsive layouts
Code Quality:
- Net reduction of 29 lines while adding features
- Improved code organization
- Better naming conventions
- Enhanced documentation
- Cleaner control flow
These changes significantly improve the maintainability and user
experience of core feature components while establishing consistent
patterns for future development.
* style(ui): refine component layouts and improve visual consistency
Comprehensive UI polish across multiple components to enhance visual
design, improve user experience, and maintain consistency.
UsageScriptModal Component (1302 lines refactored):
- Complete layout overhaul for better usability
- Improved script editor with syntax highlighting
- Better template selection interface
- Enhanced test/preview panels with clearer separation
- Improved error feedback and validation messages
- Better modal sizing and responsiveness
- Cleaner tab navigation between sections
- Enhanced code formatting and readability
- Improved loading states for async operations
- Better integration with parent components
MCP Components:
- McpFormModal (42 lines):
* Streamlined form layout
* Better server type selection (stdio/http)
* Improved field grouping and labels
* Enhanced validation feedback
- UnifiedMcpPanel (14 lines):
* Minor layout adjustments
* Better list item spacing
* Improved server status indicators
* Enhanced action button placement
Provider Components:
- ProviderCard (11 lines):
* Refined card layout and spacing
* Better visual hierarchy
* Improved badge placement
* Enhanced hover effects
- ProviderList (5 lines):
* Minor grid layout adjustments
* Better drag-and-drop visual feedback
- GeminiConfigSections (4 lines):
* Field label alignment
* Improved spacing consistency
Editor & Footer Components:
- JsonEditor (13 lines):
* Better editor height management
* Improved error display
* Enhanced syntax highlighting
- UsageFooter (10 lines):
* Refined footer layout
* Better quota display
* Improved refresh button placement
Settings & Environment:
- ImportExportSection (24 lines):
* Better button layout
* Improved action grouping
* Enhanced visual feedback
- EnvWarningBanner (4 lines):
* Refined alert styling
* Better dismiss button placement
Global Styles (index.css):
- Added 11 lines of utility classes
- Improved transition timing
- Better focus indicators
- Enhanced scrollbar styling
- Refined spacing utilities
Design Improvements:
- Consistent spacing using design tokens
- Unified color palette application
- Better typography hierarchy
- Improved shadow system for depth
- Enhanced interactive states (hover, active, focus)
- Better border radius consistency
- Refined animation timings
Accessibility:
- Improved focus indicators
- Better keyboard navigation
- Enhanced screen reader support
- Improved color contrast ratios
Code Quality:
- Net increase of 68 lines due to UsageScriptModal improvements
- Better component organization
- Cleaner style application
- Reduced style duplication
These visual refinements create a more polished and professional
interface while maintaining excellent usability and accessibility
standards across all components.
* chore(i18n): add auto-launch translation keys
Add translation keys for new auto-launch feature to support
multi-language interface.
Translation Keys Added:
- autoLaunch: Label for auto-launch toggle
- autoLaunchDescription: Explanation of auto-launch functionality
- autoLaunchEnabled: Status message when enabled
Languages Updated:
- Chinese (zh.json): 简体中文翻译
- English (en.json): English translations
The translations maintain consistency with existing terminology
and provide clear, user-friendly descriptions of the auto-launch
feature across both supported languages.
* test: update test suites to match component refactoring
Comprehensive test updates to align with recent component refactoring
and new auto-launch functionality.
Component Tests:
- AddProviderDialog.test.tsx (10 lines):
* Updated test cases for new dialog behavior
* Enhanced mock data for preset selection
* Improved assertions for validation
- ImportExportSection.test.tsx (16 lines):
* Updated for new settings page integration
* Enhanced test coverage for error scenarios
* Better mock state management
- McpFormModal.test.tsx (60 lines):
* Extensive updates for form refactoring
* New test cases for multi-app selection
* Enhanced validation testing
* Better coverage of stdio/http server types
- ProviderList.test.tsx (11 lines):
* Updated for new card layout
* Enhanced drag-and-drop testing
- SettingsDialog.test.tsx (96 lines):
* Major updates for SettingsPage migration
* New test cases for auto-launch functionality
* Enhanced integration test coverage
* Better async operation testing
Hook Tests:
- useDirectorySettings.test.tsx (32 lines):
* Updated for refactored hook logic
* Enhanced test coverage for edge cases
- useDragSort.test.tsx (36 lines):
* Simplified test cases
* Better mock implementation
* Improved assertions
- useImportExport tests (16 lines total):
* Updated for new error handling
* Enhanced test coverage
- useMcpValidation.test.tsx (23 lines):
* Updated validation test cases
* Better coverage of error scenarios
- useProviderActions.test.tsx (48 lines):
* Extensive updates for hook refactoring
* New test cases for provider operations
* Enhanced mock data
- useSettings.test.tsx (12 lines):
* New test cases for auto-launch
* Enhanced settings state testing
* Better async operation coverage
Integration Tests:
- App.test.tsx (41 lines):
* Updated for new routing logic
* Enhanced navigation testing
* Better component integration coverage
- SettingsDialog.test.tsx (88 lines):
* Complete rewrite for SettingsPage
* New integration test scenarios
* Enhanced user workflow testing
Mock Infrastructure:
- handlers.ts (117 lines):
* Major updates for MSW handlers
* New handlers for auto-launch commands
* Enhanced error simulation
* Better request/response mocking
- state.ts (37 lines):
* Updated mock state structure
* New state for auto-launch
* Enhanced state reset functionality
- tauriMocks.ts (10 lines):
* Updated mock implementations
* Better type safety
- server.ts & testQueryClient.ts:
* Minor cleanup (2 lines removed)
Test Infrastructure Improvements:
- Better test isolation
- Enhanced mock data consistency
- Improved async operation testing
- Better error scenario coverage
- Enhanced integration test patterns
Coverage Improvements:
- Net increase of 195 lines of test code
- Better coverage of edge cases
- Enhanced error path testing
- Improved integration test scenarios
- Better mock infrastructure
All tests now pass with the refactored components while maintaining
comprehensive coverage of functionality and edge cases.
* style(ui): improve window dragging and provider card styles
* fix(skills): resolve third-party skills installation failure
- Add skills_path field to Skill struct
- Use skills_path to construct correct source path during installation
- Fix installation for repos with custom skill subdirectories
* feat(icon): add icon type system and intelligent inference logic
Introduce a new icon system for provider customization:
- Add IconMetadata and IconPreset interfaces in src/types/icon.ts
- Define structure for icon name, display name, category, keywords
- Support default color configuration per icon
- Implement smart icon inference in src/config/iconInference.ts
- Create iconMappings for 25+ AI providers and cloud platforms
- Include Claude, DeepSeek, Qwen, Kimi, Google, AWS, Azure, etc.
- inferIconForPreset(): match provider name to icon config
- addIconsToPresets(): batch apply icons to preset arrays
- Support fuzzy matching for flexible name recognition
This foundation enables automatic icon assignment when users add
providers, improving visual identification in the provider list.
* feat(ui): add icon picker, color picker and provider icon components
Implement comprehensive icon selection system for provider customization:
## New Components
### ProviderIcon (src/components/ProviderIcon.tsx)
- Render SVG icons by name with automatic fallback
- Display provider initials when icon not found
- Support custom sizing via size prop
- Use dangerouslySetInnerHTML for inline SVG rendering
### IconPicker (src/components/IconPicker.tsx)
- Grid-based icon selection with visual preview
- Real-time search filtering by name and keywords
- Integration with icon metadata for display names
- Responsive grid layout (6-10 columns based on screen)
### ColorPicker (src/components/ColorPicker.tsx)
- 12 preset colors for quick selection
- Native color input for custom color picking
- Hex input field for precise color entry
- Visual feedback for selected color state
## Icon Assets (src/icons/extracted/)
- 38 high-quality SVG icons for AI providers and platforms
- Includes: OpenAI, Claude, DeepSeek, Qwen, Kimi, Gemini, etc.
- Cloud platforms: AWS, Azure, Google Cloud, Cloudflare
- Auto-generated index.ts with getIcon/hasIcon helpers
- Metadata system with searchable keywords per icon
## Build Scripts
- scripts/extract-icons.js: Extract icons from simple-icons
- scripts/generate-icon-index.js: Generate TypeScript index file
* feat(provider): integrate icon system into provider UI components
Add icon customization support to provider management interface:
## Type System Updates
### Provider Interface (src/types.ts)
- Add optional `icon` field for icon name (e.g., "openai", "anthropic")
- Add optional `iconColor` field for hex color (e.g., "#00A67E")
### Form Schema (src/lib/schemas/provider.ts)
- Extend providerSchema with icon and iconColor optional fields
- Maintain backward compatibility with existing providers
## UI Components
### ProviderCard (src/components/providers/ProviderCard.tsx)
- Display ProviderIcon alongside provider name
- Add icon container with hover animation effect
- Adjust layout spacing for icon placement
- Update translate offsets for action buttons
### BasicFormFields (src/components/providers/forms/BasicFormFields.tsx)
- Add icon preview section showing current selection
- Implement fullscreen icon picker dialog
- Auto-apply default color from icon metadata on selection
- Display provider name and icon status in preview
### AddProviderDialog & EditProviderDialog
- Pass icon fields through form submission
- Preserve icon data during provider updates
This enables users to visually distinguish providers in the list
with custom icons, improving UX for multi-provider setups.
* feat(backend): add icon fields to Provider model and default mappings
Extend Rust backend to support provider icon customization:
## Provider Model (src-tauri/src/provider.rs)
- Add `icon: Option<String>` field for icon name
- Add `icon_color: Option<String>` field for hex color
- Use serde rename `iconColor` for frontend compatibility
- Apply skip_serializing_if for clean JSON output
- Update Provider::new() to initialize icon fields as None
## Provider Defaults (src-tauri/src/provider_defaults.rs) [NEW]
- Define ProviderIcon struct with name and color fields
- Create DEFAULT_PROVIDER_ICONS static HashMap with 23 providers:
- AI providers: OpenAI, Anthropic, Claude, Google, Gemini,
DeepSeek, Kimi, Moonshot, Zhipu, MiniMax, Baidu, Alibaba,
Tencent, Meta, Microsoft, Cohere, Perplexity, Mistral, HuggingFace
- Cloud platforms: AWS, Azure, Huawei, Cloudflare
- Implement infer_provider_icon() with exact and fuzzy matching
- Add unit tests for matching logic (exact, fuzzy, case-insensitive)
## Deep Link Support (src-tauri/src/deeplink.rs)
- Initialize icon fields when creating Provider from deep link import
## Module Registration (src-tauri/src/lib.rs)
- Register provider_defaults module
## Dependencies (Cargo.toml)
- Add once_cell for lazy static initialization
This backend support enables icon persistence and future features
like auto-icon inference during provider creation.
* chore(i18n): add translations for icon picker and provider icon
Add Chinese and English translations for icon customization feature:
## Icon Picker (iconPicker)
- search: "Search Icons" / "搜索图标"
- searchPlaceholder: "Enter icon name..." / "输入图标名称..."
- noResults: "No matching icons found" / "未找到匹配的图标"
- category.aiProvider: "AI Providers" / "AI 服务商"
- category.cloud: "Cloud Platforms" / "云平台"
- category.tool: "Dev Tools" / "开发工具"
- category.other: "Other" / "其他"
## Provider Icon (providerIcon)
- label: "Icon" / "图标"
- colorLabel: "Icon Color" / "图标颜色"
- selectIcon: "Select Icon" / "选择图标"
- preview: "Preview" / "预览"
These translations support the new icon picker UI components
and provider form icon selection interface.
* style(ui): refine header layout and AppSwitcher color scheme
Improve application header and component styling:
## App.tsx Header Layout
- Wrap title and settings button in flex container with gap
- Add vertical divider between title and settings icon
- Apply responsive padding (pl-1 sm:pl-2)
- Reformat JSX for better readability (prettier)
- Fix string template formatting in className
## AppSwitcher Color Update
- Change Claude tab gradient from orange/amber to teal/emerald/green
- Update shadow color to match new teal theme
- Change hover color from orange-500 to teal-500
- Align visual style with emerald/teal brand colors
## Dialog Component Cleanup
- Remove default close button (X icon) from DialogContent
- Allow parent components to control close button placement
- Remove unused lucide-react X import
## index.css Header Border
- Add top border (2px solid) to glass-header
- Apply to both light and dark mode variants
- Improve visual separation of header area
These changes enhance visual consistency and modernize the UI
appearance with a cohesive teal color scheme.
* chore(deps): add icon library and update preset configurations
Add dependencies and utility scripts for icon system:
## Dependencies (package.json)
- Add @lobehub/icons-static-svg@1.73.0
- High-quality SVG icon library for AI providers
- Source for extracted icons in src/icons/extracted/
- Update pnpm-lock.yaml accordingly
## Provider Preset Updates (src/config/claudeProviderPresets.ts)
- Add optional `icon` and `iconColor` fields to ProviderPreset interface
- Apply to Anthropic Official preset as example:
- icon: "anthropic"
- iconColor: "#D4915D"
- Future presets can include default icon configurations
## Utility Script (scripts/filter-icons.js) [NEW]
- Helper script for filtering and managing icon assets
- Supports icon discovery and validation workflow
- Complements extract-icons.js and generate-icon-index.js
This completes the icon system infrastructure, providing all
necessary tools and dependencies for icon customization.
* refactor(ui): simplify AppSwitcher styles and migrate to local SVG icons
- Replace complex gradient animations with clean, minimal tab design
- Migrate from @lobehub/icons CDN to local SVG assets for better reliability
- Fix clippy warning in error.rs (use inline format args)
- Improve code formatting in skill service and commands
- Reduce CSS complexity in AppSwitcher component (removed blur effects and gradients)
- Update BrandIcons to use imported local SVG files instead of dynamic image loading
This improves performance, reduces external dependencies, and provides a cleaner UI experience.
* style(ui): hide scrollbars across all browsers and optimize form layout
- Hide scrollbars globally with cross-browser support:
* WebKit browsers (Chrome, Safari, Edge): ::-webkit-scrollbar { display: none }
* Firefox: scrollbar-width: none
* IE 10+: -ms-overflow-style: none
- Remove custom scrollbar styling (width, colors, hover states)
- Reorganize BasicFormFields layout:
* Move icon picker to top center as a clickable preview (80x80)
* Change name and notes fields to horizontal grid layout (md:grid-cols-2)
* Remove icon preview section from bottom
* Improve visual hierarchy and space efficiency
This provides a cleaner, more modern UI with hidden scrollbars while maintaining full scroll functionality.
* refactor(layout): standardize max-width to 60rem and optimize padding structure
- Unify container max-width across components:
* Replace max-w-4xl with max-w-[60rem] in App.tsx provider list
* Replace max-w-5xl with max-w-[60rem] in PromptPanel
* Move padding from header element to inner container for consistent spacing
- Optimize padding hierarchy:
* Remove px-6 from header element, add to inner header container
* Remove px-6 from main element, keep it only in provider list container
* Consolidate PromptPanel padding: move px-6 from nested divs to outer container
* Remove redundant pl-1 sm:pl-2 from logo/title area
- Benefits:
* Consistent 60rem max-width provides better readability on wide screens
* Simplified padding structure reduces CSS complexity
* Cleaner responsive behavior with unified spacing rules
This creates a more maintainable and visually consistent layout system.
* refactor(ui): unify layout system with 60rem max-width and consistent spacing
- Standardize container max-width across all panels:
* Replace max-w-4xl and max-w-5xl with unified max-w-[60rem]
* Apply to SettingsPage, UnifiedMcpPanel, SkillsPage, and FullScreenPanel
* Ensures consistent reading width and visual balance on wide screens
- Optimize padding hierarchy and structure:
* Move px-6 from parent elements to content containers
* FullScreenPanel: Add max-w-[60rem] wrapper to header, content, and footer
* Add border separators (border-t/border-b) to header and footer sections
* Consolidate nested padding in MCP, Skills, and Prompts panels
* Remove redundant padding layers for cleaner CSS
- Simplify component styling:
* MCP list items: Replace card-based layout with modern group-based design
* Remove unnecessary wrapper divs and flatten DOM structure
* Update card hover effects with smooth transitions
* Simplify icon selection dialog (remove description text in BasicFormFields)
- Benefits:
* Consistent 60rem max-width provides optimal readability
* Unified spacing rules reduce maintenance complexity
* Cleaner component hierarchy improves performance
* Better responsive behavior across different screen sizes
* More cohesive visual design language throughout the app
This creates a maintainable, scalable design system foundation.
* feat(deeplink): add Claude model fields support and enhance import dialog
- Add Claude-specific model field support in deeplink import:
* Support model (ANTHROPIC_MODEL) - general default model
* Support haikuModel (ANTHROPIC_DEFAULT_HAIKU_MODEL)
* Support sonnetModel (ANTHROPIC_DEFAULT_SONNET_MODEL)
* Support opusModel (ANTHROPIC_DEFAULT_OPUS_MODEL)
* Backend: Update DeepLinkImportRequest struct to include optional model fields
* Frontend: Add TypeScript type definitions for new model parameters
- Enhance deeplink demo page (deplink.html):
* Add 5 new Claude configuration examples showcasing different model setups
* Add parameter documentation with required/optional tags
* Include basic config (no models), single model, complete 4-model, partial models, and third-party provider examples
* Improve visual design with param-list component and color-coded badges
* Add detailed descriptions for each configuration scenario
- Redesign DeepLinkImportDialog layout:
* Switch from 3-column to compact 2-column grid layout
* Reduce dialog width from 500px to 650px for better content display
* Add dedicated section for Claude model configurations with blue highlight box
* Use uppercase labels and smaller text for more information density
* Add truncation and tooltips for long URLs
* Improve visual hierarchy with spacing and grouping
* Increase z-index to 9999 to ensure dialog appears on top
- Minor UI refinements:
* Update App.tsx layout adjustments
* Optimize McpFormModal styling
* Refine ProviderCard and BasicFormFields components
This enables users to import Claude providers with precise model configurations via deeplink.
* feat(deeplink): add config file support for deeplink import
Support importing provider configuration from embedded or remote config files.
- Add base64 dependency for config content encoding
- Support config, configFormat, and configUrl parameters
- Make homepage/endpoint/apiKey optional when config is provided
- Add config parsing and merging logic
* feat(deeplink): enhance dialog with config file preview
Add config file parsing and preview in deep link import dialog.
- Support Base64 encoded config display
- Add config file source indicator (embedded/remote)
- Parse and display config fields by app type (Claude/Codex/Gemini)
- Mask sensitive values in config preview
- Improve dialog layout and content organization
* refactor(ui): unify dialog styles and improve layout consistency
Standardize dialog and panel components across the application.
- Update dialog background to use semantic color tokens
- Adjust FullScreenPanel max-width to 56rem for better alignment
- Add drag region and prevent body scroll in full-screen panels
- Optimize button sizes and spacing in panel headers
- Apply consistent styling to all dialog-based components
* i18n: add deeplink config preview translations
Add missing translation keys for config file preview feature.
- Add configSource, configEmbedded, configRemote labels
- Add configDetails and configUrl display strings
- Support both Chinese and English versions
* feat(deeplink): enhance test page with v3.8 config file examples
Improve deeplink test page with comprehensive config file import examples.
- Add version badge for v3.8 features
- Add copy-to-clipboard functionality for all deep links
- Add Claude config file import examples (embedded/remote)
- Add Codex config file import examples (auth.json + config.toml)
- Add Gemini config file import examples (.env format)
- Add config generator tool for easy testing
- Update UI with better styling and layout
* feat(settings): add autoSaveSettings for lightweight auto-save
Add optimized auto-save function for General tab settings.
- Add autoSaveSettings method for non-destructive auto-save
- Only trigger system APIs when values actually change
- Avoid unnecessary auto-launch and plugin config updates
- Update tests to cover new functionality
* refactor(settings): simplify settings page layout and auto-save
Reorganize settings page structure and integrate autoSaveSettings.
- Move save button inline within Advanced tab content
- Remove sticky footer for cleaner layout
- Use autoSaveSettings for General tab settings
- Simplify dialog close behavior
- Refactor ImportExportSection layout
* style(providers): optimize card layout and action button sizes
Improve provider card visual density and action buttons.
- Reduce icon button sizes for compact layout
- Adjust drag handle and icon sizes
- Tighten spacing between action buttons
- Update hover translate values for better alignment
* refactor(mcp): improve form modal layout with adaptive height editor
Restructure MCP form modal for better space utilization.
- Split form into upper form fields and lower JSON editor sections
- Add full-height mode support for JsonEditor component
- Use flex layout for editor to fill available space
- Update PromptFormPanel to use full-height editor
- Fix locale text formatting
* style: unify list item styles with semantic colors
Apply consistent styling to list items across components.
- Replace hardcoded colors with semantic tokens in MCP and Prompt list items
- Add glass effect container to EndpointSpeedTest panel
- Format code for better readability
* style: format template literals for better readability
Improve code formatting for conditional className expressions.
- Break long template literals across multiple lines
- Maintain consistent formatting in MCP form and endpoint test components
* feat(deeplink): add config merge command for preview
Expose config merging functionality to frontend for preview.
- Add merge_deeplink_config Tauri command
- Make parse_and_merge_config public for reuse
- Enable frontend to display complete config before import
* feat(deeplink): merge and display config in import dialog
Enhance import dialog to fetch and display complete config.
- Call mergeDeeplinkConfig API when config is present
- Add UTF-8 base64 decoding support for config content
- Add scrollable content area with custom scrollbar styling
- Show complete configuration before user confirms import
* i18n: add config merge error message
Add translation for config file merge error handling.
* style(deeplink): format test page HTML for better readability
Improve HTML formatting in deeplink test page.
- Format multiline attributes for better readability
- Add consistent indentation to nested elements
- Break long lines in buttons and links
* refactor(usage): improve footer layout with two-row design
Reorganize usage footer for better readability and space efficiency.
- Split into two rows: update time + refresh button (row 1), usage stats (row 2)
- Move refresh button to top row next to update time
- Remove card background for cleaner look
- Add fallback text when never updated
- Improve spacing and alignment
- Format template literals for consistency
* feat(database): add SQLite database infrastructure
- Add rusqlite dependency (v0.32.1) and r2d2 connection pooling
- Implement Database module with CRUD operations for providers, MCP servers, prompts, and skills
- Add schema initialization with proper indexes
- Include data migration utilities from JSON config to SQLite
- Support timestamp tracking (created_at, updated_at)
* refactor(core): integrate SQLite database into application core
- Initialize database on app startup with migration from JSON config
- Update AppState to include Database instance alongside MultiAppConfig
- Simplify store module by removing unused session management code
- Add database initialization to app setup flow
- Support both database and legacy config during transition
* refactor(services): migrate service layer to use SQLite database
- Refactor ProviderService to use database queries instead of in-memory config
- Update McpService to fetch and store MCP servers in database
- Migrate PromptService to database-backed storage
- Simplify ConfigService by removing complex transaction logic
- Remove 648 lines of redundant code through database abstraction
* refactor(commands): update command layer to use database API
- Update config commands to query database for providers and settings
- Modify provider commands to pass database handle to services
- Update MCP commands to use database-backed operations
- Refactor prompt and skill commands to leverage database storage
- Simplify import/export commands with database integration
* refactor(backend): update supporting modules for database compatibility
- Add DatabaseError variant to AppError enum
- Update provider module to support database-backed operations
- Modify codex_config to work with new database structure
- Ensure error handling covers database operations
* refactor(frontend): update UI components for database migration
- Update UsageFooter component to handle new data structure
- Modify SkillsPage to work with database-backed skills management
- Ensure frontend compatibility with refactored backend
* feat(skills): add search functionality to Skills page
- Add search input with Search icon in SkillsPage component
- Implement useMemo-based filtering by skill name, description, and directory
- Display search results count when filtering is active
- Show "no results" message when no skills match the search query
- Add i18n translations for search UI (zh/en)
- Maintain responsive layout and consistent styling with existing UI
* refactor(backend): replace unsafe unwrap calls with proper error handling
- Add to_json_string helper for safe JSON serialization
- Add lock_conn macro for safe Mutex locking
- Replace 41 unwrap() calls with proper error handling:
- database.rs: JSON serialization and Mutex operations (31 fixes)
- lib.rs: macOS NSWindow and tray icon handling (3 fixes)
- services/provider.rs: Claude model normalization (1 fix)
- services/prompt.rs: timestamp generation (3 fixes)
- services/skill.rs: directory name extraction (2 fixes)
- mcp.rs: HashMap initialization and type conversions (5 fixes)
- app_config.rs: timestamp fallback (1 fix)
This improves application stability and prevents potential panics.
* feat(init): implement automatic data import on first launch
Add comprehensive first-launch data import system:
Database layer:
- Add is_empty_for_first_import() to detect empty database
- Add init_default_skill_repos() to initialize 3 default skill repositories
Services layer:
- Implement McpService::import_from_claude/codex/gemini()
to import MCP servers from existing config files
- Implement PromptService::import_from_file_on_first_launch()
to import prompt files (CLAUDE.md, AGENTS.md, GEMINI.md)
Startup flow (lib.rs):
- Check if database is empty on startup
- Import existing configurations if detected:
1. Initialize default skill repositories
2. Import provider configurations from live settings
3. Import MCP servers from config files
4. Import prompt files
- All imports are fault-tolerant and logged
This ensures seamless migration from file-based configs to database.
* fix(skills): auto-sync locally installed skills to database
Add automatic synchronization in get_skills command:
- Detect locally installed skills in ~/.claude/skills/
- Auto-add to database if not already tracked
- Ensures existing skills are recognized on first launch
This fixes the issue where user's existing skills were not
imported into the database on initial application run.
* docs(frontend): add code comments and improve formatting
- Add explanatory comment in EditProviderDialog about config assembly
- Improve import formatting in SkillsPage for better readability
* feat(deeplink): display all four Claude model fields in import dialog
- Show haiku/sonnet/opus/multiModel fields conditionally for Claude
- Maintain single model field display for Codex and Gemini
- Add i18n translations for new model field labels (zh/en)
* feat(backend): add database SQL export/import with backup
- Enable rusqlite backup feature for SQL dump support
- Implement export_sql to generate SQLite-compatible SQL dumps
- Implement import_sql with automatic backup before import
- Add snapshot_to_memory to avoid long-held database locks
- Add backup rotation to retain latest 10 backups
- Support atomic import with rollback on failure
* refactor(backend): migrate import/export to use SQL backup
- Reimplement export_config_to_file to use database.export_sql
- Reimplement import_config_from_file to use database.import_sql
- Add sync_current_from_db to sync live configs after import
- Add settings database binding on app initialization
- Remove deprecated JSON-based config import logic
* refactor(backend): migrate settings storage to database
- Add bind_db function to initialize database-backed settings
- Implement load_initial_settings with database fallback
- Replace direct file save with settings store management
- Add SETTINGS_DB static for database binding
- Maintain backward compatibility with file-based settings
- Keep settings.json for legacy migration support
* feat(frontend): update import/export UI for SQL backup
- Change default export filename from .json to .sql
- Update file format to timestamp format (YYYYMMDD_HHMMSS)
- Update error messages to reference SQL files
- Align with backend SQL export/import implementation
* feat(i18n): update translations for SQL backup feature
- Update Chinese translations for SQL import/export
- Update English translations for SQL import/export
- Change terminology from 'config file' to 'SQL backup'
- Update error messages and UI hints
* fix(backend): remove unnecessary dereference in backup operation
- Simplify Backup::new call by removing redundant dereference
- MutexGuard already implements DerefMut
Codex CLI expects the field name to be `http_headers` (not `headers`)
in the MCP server configuration TOML format.
Changes:
- Update import_from_codex() to read from both `http_headers` (correct)
and `headers` (legacy) with priority to `http_headers`
- Update json_server_to_toml_table() to write `http_headers` instead
of `headers`
- Update core_fields lists to use `http_headers` for HTTP/SSE types
This follows the same pattern as the recent Gemini fix and ensures
backward compatibility while generating correct Codex-compatible configs.
* feat(components): add reusable full-screen panel components
Add new full-screen panel components to support the UI refactoring:
- FullScreenPanel: Reusable full-screen layout component with header,
content area, and optional footer. Provides consistent layout for
settings, prompts, and other full-screen views.
- PromptFormPanel: Dedicated panel for creating and editing prompts
with markdown preview support. Features real-time validation and
integrated save/cancel actions.
- AgentsPanel: Panel component for managing agent configurations.
Provides a consistent interface for agent CRUD operations.
- RepoManagerPanel: Full-featured repository manager panel for Skills.
Supports repository listing, addition, deletion, and configuration
management with integrated validation.
These components establish the foundation for the upcoming settings
page migration from dialog-based to full-screen layout.
* refactor(settings): migrate from dialog to full-screen page layout
Complete migration of settings from modal dialog to dedicated full-screen
page, improving UX and providing more space for configuration options.
Changes:
- Remove SettingsDialog component (legacy modal-based interface)
- Add SettingsPage component with full-screen layout using FullScreenPanel
- Refactor App.tsx routing to support dedicated settings page
* Add settings route handler
* Update navigation logic from dialog-based to page-based
* Integrate with existing app switcher and provider management
- Update ImportExportSection to work with new page layout
* Improve spacing and layout for better readability
* Enhanced error handling and user feedback
* Better integration with page-level actions
- Enhance useSettings hook to support page-based workflow
* Add navigation state management
* Improve settings persistence logic
* Better error boundary handling
Benefits:
- More intuitive navigation with dedicated settings page
- Better use of screen space for complex configurations
- Improved accessibility with clearer visual hierarchy
- Consistent with modern desktop application patterns
- Easier to extend with new settings sections
This change is part of the larger UI refactoring initiative to modernize
the application interface and improve user experience.
* refactor(forms): simplify and modernize form components
Comprehensive refactoring of form components to reduce complexity,
improve maintainability, and enhance user experience.
Provider Forms:
- CodexCommonConfigModal & CodexConfigSections
* Simplified state management with reduced boilerplate
* Improved field validation and error handling
* Better layout with consistent spacing
* Enhanced model selection with visual indicators
- GeminiCommonConfigModal & GeminiConfigSections
* Streamlined authentication flow (OAuth vs API Key)
* Cleaner form layout with better grouping
* Improved validation feedback
* Better integration with parent components
- CommonConfigEditor
* Reduced from 178 to 68 lines (-62% complexity)
* Extracted reusable form patterns
* Improved JSON editing with syntax validation
* Better error messages and recovery options
- EndpointSpeedTest
* Complete rewrite for better UX
* Real-time testing progress indicators
* Enhanced error handling with retry logic
* Visual feedback for test results (color-coded latency)
MCP & Prompts:
- McpFormModal
* Simplified from 581 to ~360 lines
* Better stdio/http server type handling
* Improved form validation
* Enhanced multi-app selection (Claude/Codex/Gemini)
- PromptPanel
* Cleaner integration with PromptFormPanel
* Improved list/grid view switching
* Better state management for editing workflows
* Enhanced delete confirmation with safety checks
Code Quality Improvements:
- Reduced total lines by ~251 lines (-24% code reduction)
- Eliminated duplicate validation logic
- Improved TypeScript type safety
- Better component composition and separation of concerns
- Enhanced accessibility with proper ARIA labels
These changes make forms more intuitive, responsive, and easier to
maintain while reducing bundle size and improving runtime performance.
* style(ui): modernize component layouts and visual design
Update UI components with improved layouts, visual hierarchy, and
modern design patterns for better user experience.
Navigation & Brand Components:
- AppSwitcher
* Enhanced visual design with better spacing
* Improved active state indicators
* Smoother transitions and hover effects
* Better mobile responsiveness
- BrandIcons
* Optimized icon rendering performance
* Added support for more provider icons
* Improved SVG handling and fallbacks
* Better scaling across different screen sizes
Editor Components:
- JsonEditor
* Enhanced syntax highlighting
* Better error visualization
* Improved code formatting options
* Added line numbers and code folding support
- UsageScriptModal
* Complete layout overhaul (1239 lines refactored)
* Better script editor integration
* Improved template selection UI
* Enhanced preview and testing panels
* Better error feedback and validation
Provider Components:
- ProviderCard
* Redesigned card layout with modern aesthetics
* Better information density and readability
* Improved action buttons placement
* Enhanced status indicators (active/inactive)
- ProviderList
* Better grid/list view layouts
* Improved drag-and-drop visual feedback
* Enhanced sorting indicators
- ProviderActions
* Streamlined action menu
* Better icon consistency
* Improved tooltips and accessibility
Usage & Footer:
- UsageFooter
* Redesigned footer layout
* Better quota visualization
* Improved refresh controls
* Enhanced error states
Design System Updates:
- dialog.tsx (shadcn/ui component)
* Updated to latest design tokens
* Better overlay animations
* Improved focus management
- index.css
* Added 65 lines of global utility classes
* New animation keyframes
* Enhanced color variables for dark mode
* Improved typography scale
- tailwind.config.js
* Extended theme with new design tokens
* Added custom animations and transitions
* New spacing and sizing utilities
* Enhanced color palette
Visual Improvements:
- Consistent border radius across components
- Unified shadow system for depth perception
- Better color contrast for accessibility (WCAG AA)
- Smoother animations and transitions
- Improved dark mode support
These changes create a more polished, modern interface while
maintaining consistency with the application's design language.
* chore: update dialogs, i18n and improve component integration
Various functional updates and improvements across provider dialogs,
MCP panel, skills page, and internationalization.
Provider Dialogs:
- AddProviderDialog
* Simplified form state management
* Improved preset selection workflow
* Better validation error messages
* Enhanced template variable handling
- EditProviderDialog
* Streamlined edit flow with better state synchronization
* Improved handling of live config backfilling
* Better error recovery for failed updates
* Enhanced integration with parent components
MCP & Skills:
- UnifiedMcpPanel
* Reduced complexity from 140+ to ~95 lines
* Improved multi-app server management
* Better server type detection (stdio/http)
* Enhanced server status indicators
* Cleaner integration with MCP form modal
- SkillsPage
* Simplified navigation and state management
* Better integration with RepoManagerPanel
* Improved error handling for repository operations
* Enhanced loading states
- SkillCard
* Minor layout adjustments
* Better action button placement
Environment & Configuration:
- EnvWarningBanner
* Improved conflict detection messages
* Better visual hierarchy for warnings
* Enhanced dismissal behavior
- tauri.conf.json
* Updated build configuration
* Added new window management options
Internationalization:
- en.json & zh.json
* Added 17 new translation keys for new features
* Updated existing keys for better clarity
* Added translations for new settings page
* Improved consistency across UI text
Code Cleanup:
- mutations.ts
* Removed 14 lines of unused mutation definitions
* Cleaned up deprecated query invalidation logic
* Better type safety for mutation parameters
Overall Impact:
- Reduced total lines by 51 (-10% in affected files)
- Improved component integration and data flow
- Better error handling and user feedback
- Enhanced i18n coverage for new features
These changes improve the overall polish and integration of various
components while removing technical debt and unused code.
* feat(backend): add auto-launch functionality
Implement system auto-launch feature to allow CC-Switch to start
automatically on system boot, improving user convenience.
Backend Implementation:
- auto_launch.rs: New module for auto-launch management
* Cross-platform support using auto-launch crate
* Enable/disable auto-launch with system integration
* Proper error handling for permission issues
* Platform-specific implementations (macOS/Windows/Linux)
Command Layer:
- Add get_auto_launch command to check current status
- Add set_auto_launch command to toggle auto-start
- Integrate commands with settings API
Settings Integration:
- Extend Settings struct with auto_launch field
- Persist auto-launch preference in settings store
- Automatic state synchronization on app startup
Dependencies:
- Add auto-launch ^0.5.0 to Cargo.toml
- Update Cargo.lock with new dependency tree
Technical Details:
- Uses platform-specific auto-launch mechanisms:
* macOS: Login Items via LaunchServices
* Windows: Registry Run key
* Linux: XDG autostart desktop files
- Handles edge cases like permission denials gracefully
- Maintains settings consistency across app restarts
This feature enables users to have CC-Switch readily available
after system boot without manual intervention, particularly useful
for users who frequently switch between API providers.
* refactor(settings): enhance settings page with auto-launch integration
Complete refactoring of settings page architecture to integrate auto-launch
feature and improve overall settings management workflow.
SettingsPage Component:
- Integrate auto-launch toggle with WindowSettings section
- Improve layout and spacing for better visual hierarchy
- Enhanced error handling for settings operations
- Better loading states during settings updates
- Improved accessibility with proper ARIA labels
WindowSettings Component:
- Add auto-launch switch with real-time status
- Integrate with backend auto-launch commands
- Proper error feedback for permission issues
- Visual indicators for current auto-launch state
- Tooltip guidance for auto-launch functionality
useSettings Hook (Major Refactoring):
- Complete rewrite reducing complexity by ~30%
- Better separation of concerns with dedicated handlers
- Improved state management using React Query
- Enhanced auto-launch state synchronization
* Fetch auto-launch status on mount
* Real-time updates on toggle
* Proper error recovery
- Optimized re-renders with better memoization
- Cleaner API for component integration
- Better TypeScript type safety
Settings API:
- Add getAutoLaunch() method
- Add setAutoLaunch(enabled: boolean) method
- Type-safe Tauri command invocations
- Proper error propagation to UI layer
Architecture Improvements:
- Reduced hook complexity from 197 to ~140 effective lines
- Eliminated redundant state management logic
- Better error boundaries and fallback handling
- Improved testability with clearer separation
User Experience Enhancements:
- Instant visual feedback on auto-launch toggle
- Clear error messages for permission issues
- Loading indicators during async operations
- Consistent behavior across all platforms
This refactoring provides a solid foundation for future settings
additions while maintaining code quality and user experience.
* refactor(ui): optimize FullScreenPanel, Dialog and App routing
Comprehensive refactoring of core UI components to improve code quality,
maintainability, and user experience.
FullScreenPanel Component:
- Enhanced props interface with better TypeScript types
- Improved layout flexibility with customizable padding
- Better header/footer composition patterns
- Enhanced scroll behavior for long content
- Added support for custom actions in header
- Improved responsive design for different screen sizes
- Better integration with parent components
- Cleaner prop drilling with context where appropriate
Dialog Component (shadcn/ui):
- Updated to latest component patterns
- Improved animation timing and easing
- Better focus trap management
- Enhanced overlay styling with backdrop blur
- Improved accessibility (ARIA labels, keyboard navigation)
- Better close button positioning and styling
- Enhanced mobile responsiveness
- Cleaner composition with DialogHeader/Footer
App Component Routing:
- Refactored routing logic for better clarity
- Improved state management for navigation
- Better integration with settings page
- Enhanced error boundary handling
- Cleaner separation of layout concerns
- Improved provider context propagation
- Better handling of deep links
- Optimized re-renders with React.memo where appropriate
Code Quality Improvements:
- Reduced prop drilling with better component composition
- Improved TypeScript type safety
- Better separation of concerns
- Enhanced code readability with clearer naming
- Eliminated redundant logic
Performance Optimizations:
- Reduced unnecessary re-renders
- Better memoization of callbacks
- Optimized component tree structure
- Improved event handler efficiency
User Experience:
- Smoother transitions and animations
- Better visual feedback for interactions
- Improved loading states
- More consistent behavior across features
These changes create a more maintainable and performant foundation
for the application's UI layer while improving the overall user
experience with smoother interactions and better visual polish.
* refactor(features): modernize Skills, Prompts and Agents components
Major refactoring of feature components to improve code quality,
user experience, and maintainability.
SkillsPage Component (299 lines refactored):
- Complete rewrite of layout and state management
- Better integration with RepoManagerPanel
- Improved navigation between list and detail views
- Enhanced error handling with user-friendly messages
- Better loading states with skeleton screens
- Optimized re-renders with proper memoization
- Cleaner separation between list and form views
- Improved skill card interactions
- Better responsive design for different screen sizes
RepoManagerPanel Component (370 lines refactored):
- Streamlined repository management workflow
- Enhanced form validation with real-time feedback
- Improved repository list with better visual hierarchy
- Better handling of git operations (clone, pull, delete)
- Enhanced error recovery for network issues
- Cleaner state management reducing complexity
- Improved TypeScript type safety
- Better integration with Skills backend API
- Enhanced loading indicators for async operations
PromptPanel Component (249 lines refactored):
- Modernized layout with FullScreenPanel integration
- Better separation between list and edit modes
- Improved prompt card design with better readability
- Enhanced search and filter functionality
- Cleaner state management for editing workflow
- Better integration with PromptFormPanel
- Improved delete confirmation with safety checks
- Enhanced keyboard navigation support
PromptFormPanel Component (238 lines refactored):
- Streamlined form layout and validation
- Better markdown editor integration
- Real-time preview with syntax highlighting
- Improved validation error display
- Enhanced save/cancel workflow
- Better handling of large prompt content
- Cleaner form state management
- Improved accessibility features
AgentsPanel Component (33 lines modified):
- Minor layout adjustments for consistency
- Better integration with FullScreenPanel
- Improved placeholder states
- Enhanced error boundaries
Type Definitions (types.ts):
- Added 10 new type definitions
- Better type safety for Skills/Prompts/Agents
- Enhanced interfaces for repository management
- Improved typing for form validations
Architecture Improvements:
- Reduced component coupling
- Better prop interfaces with explicit types
- Improved error boundaries
- Enhanced code reusability
- Better testing surface
User Experience Enhancements:
- Smoother transitions between views
- Better visual feedback for actions
- Improved error messages
- Enhanced loading states
- More intuitive navigation flows
- Better responsive layouts
Code Quality:
- Net reduction of 29 lines while adding features
- Improved code organization
- Better naming conventions
- Enhanced documentation
- Cleaner control flow
These changes significantly improve the maintainability and user
experience of core feature components while establishing consistent
patterns for future development.
* style(ui): refine component layouts and improve visual consistency
Comprehensive UI polish across multiple components to enhance visual
design, improve user experience, and maintain consistency.
UsageScriptModal Component (1302 lines refactored):
- Complete layout overhaul for better usability
- Improved script editor with syntax highlighting
- Better template selection interface
- Enhanced test/preview panels with clearer separation
- Improved error feedback and validation messages
- Better modal sizing and responsiveness
- Cleaner tab navigation between sections
- Enhanced code formatting and readability
- Improved loading states for async operations
- Better integration with parent components
MCP Components:
- McpFormModal (42 lines):
* Streamlined form layout
* Better server type selection (stdio/http)
* Improved field grouping and labels
* Enhanced validation feedback
- UnifiedMcpPanel (14 lines):
* Minor layout adjustments
* Better list item spacing
* Improved server status indicators
* Enhanced action button placement
Provider Components:
- ProviderCard (11 lines):
* Refined card layout and spacing
* Better visual hierarchy
* Improved badge placement
* Enhanced hover effects
- ProviderList (5 lines):
* Minor grid layout adjustments
* Better drag-and-drop visual feedback
- GeminiConfigSections (4 lines):
* Field label alignment
* Improved spacing consistency
Editor & Footer Components:
- JsonEditor (13 lines):
* Better editor height management
* Improved error display
* Enhanced syntax highlighting
- UsageFooter (10 lines):
* Refined footer layout
* Better quota display
* Improved refresh button placement
Settings & Environment:
- ImportExportSection (24 lines):
* Better button layout
* Improved action grouping
* Enhanced visual feedback
- EnvWarningBanner (4 lines):
* Refined alert styling
* Better dismiss button placement
Global Styles (index.css):
- Added 11 lines of utility classes
- Improved transition timing
- Better focus indicators
- Enhanced scrollbar styling
- Refined spacing utilities
Design Improvements:
- Consistent spacing using design tokens
- Unified color palette application
- Better typography hierarchy
- Improved shadow system for depth
- Enhanced interactive states (hover, active, focus)
- Better border radius consistency
- Refined animation timings
Accessibility:
- Improved focus indicators
- Better keyboard navigation
- Enhanced screen reader support
- Improved color contrast ratios
Code Quality:
- Net increase of 68 lines due to UsageScriptModal improvements
- Better component organization
- Cleaner style application
- Reduced style duplication
These visual refinements create a more polished and professional
interface while maintaining excellent usability and accessibility
standards across all components.
* chore(i18n): add auto-launch translation keys
Add translation keys for new auto-launch feature to support
multi-language interface.
Translation Keys Added:
- autoLaunch: Label for auto-launch toggle
- autoLaunchDescription: Explanation of auto-launch functionality
- autoLaunchEnabled: Status message when enabled
Languages Updated:
- Chinese (zh.json): 简体中文翻译
- English (en.json): English translations
The translations maintain consistency with existing terminology
and provide clear, user-friendly descriptions of the auto-launch
feature across both supported languages.
* test: update test suites to match component refactoring
Comprehensive test updates to align with recent component refactoring
and new auto-launch functionality.
Component Tests:
- AddProviderDialog.test.tsx (10 lines):
* Updated test cases for new dialog behavior
* Enhanced mock data for preset selection
* Improved assertions for validation
- ImportExportSection.test.tsx (16 lines):
* Updated for new settings page integration
* Enhanced test coverage for error scenarios
* Better mock state management
- McpFormModal.test.tsx (60 lines):
* Extensive updates for form refactoring
* New test cases for multi-app selection
* Enhanced validation testing
* Better coverage of stdio/http server types
- ProviderList.test.tsx (11 lines):
* Updated for new card layout
* Enhanced drag-and-drop testing
- SettingsDialog.test.tsx (96 lines):
* Major updates for SettingsPage migration
* New test cases for auto-launch functionality
* Enhanced integration test coverage
* Better async operation testing
Hook Tests:
- useDirectorySettings.test.tsx (32 lines):
* Updated for refactored hook logic
* Enhanced test coverage for edge cases
- useDragSort.test.tsx (36 lines):
* Simplified test cases
* Better mock implementation
* Improved assertions
- useImportExport tests (16 lines total):
* Updated for new error handling
* Enhanced test coverage
- useMcpValidation.test.tsx (23 lines):
* Updated validation test cases
* Better coverage of error scenarios
- useProviderActions.test.tsx (48 lines):
* Extensive updates for hook refactoring
* New test cases for provider operations
* Enhanced mock data
- useSettings.test.tsx (12 lines):
* New test cases for auto-launch
* Enhanced settings state testing
* Better async operation coverage
Integration Tests:
- App.test.tsx (41 lines):
* Updated for new routing logic
* Enhanced navigation testing
* Better component integration coverage
- SettingsDialog.test.tsx (88 lines):
* Complete rewrite for SettingsPage
* New integration test scenarios
* Enhanced user workflow testing
Mock Infrastructure:
- handlers.ts (117 lines):
* Major updates for MSW handlers
* New handlers for auto-launch commands
* Enhanced error simulation
* Better request/response mocking
- state.ts (37 lines):
* Updated mock state structure
* New state for auto-launch
* Enhanced state reset functionality
- tauriMocks.ts (10 lines):
* Updated mock implementations
* Better type safety
- server.ts & testQueryClient.ts:
* Minor cleanup (2 lines removed)
Test Infrastructure Improvements:
- Better test isolation
- Enhanced mock data consistency
- Improved async operation testing
- Better error scenario coverage
- Enhanced integration test patterns
Coverage Improvements:
- Net increase of 195 lines of test code
- Better coverage of edge cases
- Enhanced error path testing
- Improved integration test scenarios
- Better mock infrastructure
All tests now pass with the refactored components while maintaining
comprehensive coverage of functionality and edge cases.
* style(ui): improve window dragging and provider card styles
* fix(skills): resolve third-party skills installation failure
- Add skills_path field to Skill struct
- Use skills_path to construct correct source path during installation
- Fix installation for repos with custom skill subdirectories
* feat(icon): add icon type system and intelligent inference logic
Introduce a new icon system for provider customization:
- Add IconMetadata and IconPreset interfaces in src/types/icon.ts
- Define structure for icon name, display name, category, keywords
- Support default color configuration per icon
- Implement smart icon inference in src/config/iconInference.ts
- Create iconMappings for 25+ AI providers and cloud platforms
- Include Claude, DeepSeek, Qwen, Kimi, Google, AWS, Azure, etc.
- inferIconForPreset(): match provider name to icon config
- addIconsToPresets(): batch apply icons to preset arrays
- Support fuzzy matching for flexible name recognition
This foundation enables automatic icon assignment when users add
providers, improving visual identification in the provider list.
* feat(ui): add icon picker, color picker and provider icon components
Implement comprehensive icon selection system for provider customization:
## New Components
### ProviderIcon (src/components/ProviderIcon.tsx)
- Render SVG icons by name with automatic fallback
- Display provider initials when icon not found
- Support custom sizing via size prop
- Use dangerouslySetInnerHTML for inline SVG rendering
### IconPicker (src/components/IconPicker.tsx)
- Grid-based icon selection with visual preview
- Real-time search filtering by name and keywords
- Integration with icon metadata for display names
- Responsive grid layout (6-10 columns based on screen)
### ColorPicker (src/components/ColorPicker.tsx)
- 12 preset colors for quick selection
- Native color input for custom color picking
- Hex input field for precise color entry
- Visual feedback for selected color state
## Icon Assets (src/icons/extracted/)
- 38 high-quality SVG icons for AI providers and platforms
- Includes: OpenAI, Claude, DeepSeek, Qwen, Kimi, Gemini, etc.
- Cloud platforms: AWS, Azure, Google Cloud, Cloudflare
- Auto-generated index.ts with getIcon/hasIcon helpers
- Metadata system with searchable keywords per icon
## Build Scripts
- scripts/extract-icons.js: Extract icons from simple-icons
- scripts/generate-icon-index.js: Generate TypeScript index file
* feat(provider): integrate icon system into provider UI components
Add icon customization support to provider management interface:
## Type System Updates
### Provider Interface (src/types.ts)
- Add optional `icon` field for icon name (e.g., "openai", "anthropic")
- Add optional `iconColor` field for hex color (e.g., "#00A67E")
### Form Schema (src/lib/schemas/provider.ts)
- Extend providerSchema with icon and iconColor optional fields
- Maintain backward compatibility with existing providers
## UI Components
### ProviderCard (src/components/providers/ProviderCard.tsx)
- Display ProviderIcon alongside provider name
- Add icon container with hover animation effect
- Adjust layout spacing for icon placement
- Update translate offsets for action buttons
### BasicFormFields (src/components/providers/forms/BasicFormFields.tsx)
- Add icon preview section showing current selection
- Implement fullscreen icon picker dialog
- Auto-apply default color from icon metadata on selection
- Display provider name and icon status in preview
### AddProviderDialog & EditProviderDialog
- Pass icon fields through form submission
- Preserve icon data during provider updates
This enables users to visually distinguish providers in the list
with custom icons, improving UX for multi-provider setups.
* feat(backend): add icon fields to Provider model and default mappings
Extend Rust backend to support provider icon customization:
## Provider Model (src-tauri/src/provider.rs)
- Add `icon: Option<String>` field for icon name
- Add `icon_color: Option<String>` field for hex color
- Use serde rename `iconColor` for frontend compatibility
- Apply skip_serializing_if for clean JSON output
- Update Provider::new() to initialize icon fields as None
## Provider Defaults (src-tauri/src/provider_defaults.rs) [NEW]
- Define ProviderIcon struct with name and color fields
- Create DEFAULT_PROVIDER_ICONS static HashMap with 23 providers:
- AI providers: OpenAI, Anthropic, Claude, Google, Gemini,
DeepSeek, Kimi, Moonshot, Zhipu, MiniMax, Baidu, Alibaba,
Tencent, Meta, Microsoft, Cohere, Perplexity, Mistral, HuggingFace
- Cloud platforms: AWS, Azure, Huawei, Cloudflare
- Implement infer_provider_icon() with exact and fuzzy matching
- Add unit tests for matching logic (exact, fuzzy, case-insensitive)
## Deep Link Support (src-tauri/src/deeplink.rs)
- Initialize icon fields when creating Provider from deep link import
## Module Registration (src-tauri/src/lib.rs)
- Register provider_defaults module
## Dependencies (Cargo.toml)
- Add once_cell for lazy static initialization
This backend support enables icon persistence and future features
like auto-icon inference during provider creation.
* chore(i18n): add translations for icon picker and provider icon
Add Chinese and English translations for icon customization feature:
## Icon Picker (iconPicker)
- search: "Search Icons" / "搜索图标"
- searchPlaceholder: "Enter icon name..." / "输入图标名称..."
- noResults: "No matching icons found" / "未找到匹配的图标"
- category.aiProvider: "AI Providers" / "AI 服务商"
- category.cloud: "Cloud Platforms" / "云平台"
- category.tool: "Dev Tools" / "开发工具"
- category.other: "Other" / "其他"
## Provider Icon (providerIcon)
- label: "Icon" / "图标"
- colorLabel: "Icon Color" / "图标颜色"
- selectIcon: "Select Icon" / "选择图标"
- preview: "Preview" / "预览"
These translations support the new icon picker UI components
and provider form icon selection interface.
* style(ui): refine header layout and AppSwitcher color scheme
Improve application header and component styling:
## App.tsx Header Layout
- Wrap title and settings button in flex container with gap
- Add vertical divider between title and settings icon
- Apply responsive padding (pl-1 sm:pl-2)
- Reformat JSX for better readability (prettier)
- Fix string template formatting in className
## AppSwitcher Color Update
- Change Claude tab gradient from orange/amber to teal/emerald/green
- Update shadow color to match new teal theme
- Change hover color from orange-500 to teal-500
- Align visual style with emerald/teal brand colors
## Dialog Component Cleanup
- Remove default close button (X icon) from DialogContent
- Allow parent components to control close button placement
- Remove unused lucide-react X import
## index.css Header Border
- Add top border (2px solid) to glass-header
- Apply to both light and dark mode variants
- Improve visual separation of header area
These changes enhance visual consistency and modernize the UI
appearance with a cohesive teal color scheme.
* chore(deps): add icon library and update preset configurations
Add dependencies and utility scripts for icon system:
## Dependencies (package.json)
- Add @lobehub/icons-static-svg@1.73.0
- High-quality SVG icon library for AI providers
- Source for extracted icons in src/icons/extracted/
- Update pnpm-lock.yaml accordingly
## Provider Preset Updates (src/config/claudeProviderPresets.ts)
- Add optional `icon` and `iconColor` fields to ProviderPreset interface
- Apply to Anthropic Official preset as example:
- icon: "anthropic"
- iconColor: "#D4915D"
- Future presets can include default icon configurations
## Utility Script (scripts/filter-icons.js) [NEW]
- Helper script for filtering and managing icon assets
- Supports icon discovery and validation workflow
- Complements extract-icons.js and generate-icon-index.js
This completes the icon system infrastructure, providing all
necessary tools and dependencies for icon customization.
* refactor(ui): simplify AppSwitcher styles and migrate to local SVG icons
- Replace complex gradient animations with clean, minimal tab design
- Migrate from @lobehub/icons CDN to local SVG assets for better reliability
- Fix clippy warning in error.rs (use inline format args)
- Improve code formatting in skill service and commands
- Reduce CSS complexity in AppSwitcher component (removed blur effects and gradients)
- Update BrandIcons to use imported local SVG files instead of dynamic image loading
This improves performance, reduces external dependencies, and provides a cleaner UI experience.
* style(ui): hide scrollbars across all browsers and optimize form layout
- Hide scrollbars globally with cross-browser support:
* WebKit browsers (Chrome, Safari, Edge): ::-webkit-scrollbar { display: none }
* Firefox: scrollbar-width: none
* IE 10+: -ms-overflow-style: none
- Remove custom scrollbar styling (width, colors, hover states)
- Reorganize BasicFormFields layout:
* Move icon picker to top center as a clickable preview (80x80)
* Change name and notes fields to horizontal grid layout (md:grid-cols-2)
* Remove icon preview section from bottom
* Improve visual hierarchy and space efficiency
This provides a cleaner, more modern UI with hidden scrollbars while maintaining full scroll functionality.
* refactor(layout): standardize max-width to 60rem and optimize padding structure
- Unify container max-width across components:
* Replace max-w-4xl with max-w-[60rem] in App.tsx provider list
* Replace max-w-5xl with max-w-[60rem] in PromptPanel
* Move padding from header element to inner container for consistent spacing
- Optimize padding hierarchy:
* Remove px-6 from header element, add to inner header container
* Remove px-6 from main element, keep it only in provider list container
* Consolidate PromptPanel padding: move px-6 from nested divs to outer container
* Remove redundant pl-1 sm:pl-2 from logo/title area
- Benefits:
* Consistent 60rem max-width provides better readability on wide screens
* Simplified padding structure reduces CSS complexity
* Cleaner responsive behavior with unified spacing rules
This creates a more maintainable and visually consistent layout system.
* refactor(ui): unify layout system with 60rem max-width and consistent spacing
- Standardize container max-width across all panels:
* Replace max-w-4xl and max-w-5xl with unified max-w-[60rem]
* Apply to SettingsPage, UnifiedMcpPanel, SkillsPage, and FullScreenPanel
* Ensures consistent reading width and visual balance on wide screens
- Optimize padding hierarchy and structure:
* Move px-6 from parent elements to content containers
* FullScreenPanel: Add max-w-[60rem] wrapper to header, content, and footer
* Add border separators (border-t/border-b) to header and footer sections
* Consolidate nested padding in MCP, Skills, and Prompts panels
* Remove redundant padding layers for cleaner CSS
- Simplify component styling:
* MCP list items: Replace card-based layout with modern group-based design
* Remove unnecessary wrapper divs and flatten DOM structure
* Update card hover effects with smooth transitions
* Simplify icon selection dialog (remove description text in BasicFormFields)
- Benefits:
* Consistent 60rem max-width provides optimal readability
* Unified spacing rules reduce maintenance complexity
* Cleaner component hierarchy improves performance
* Better responsive behavior across different screen sizes
* More cohesive visual design language throughout the app
This creates a maintainable, scalable design system foundation.
* feat(deeplink): add Claude model fields support and enhance import dialog
- Add Claude-specific model field support in deeplink import:
* Support model (ANTHROPIC_MODEL) - general default model
* Support haikuModel (ANTHROPIC_DEFAULT_HAIKU_MODEL)
* Support sonnetModel (ANTHROPIC_DEFAULT_SONNET_MODEL)
* Support opusModel (ANTHROPIC_DEFAULT_OPUS_MODEL)
* Backend: Update DeepLinkImportRequest struct to include optional model fields
* Frontend: Add TypeScript type definitions for new model parameters
- Enhance deeplink demo page (deplink.html):
* Add 5 new Claude configuration examples showcasing different model setups
* Add parameter documentation with required/optional tags
* Include basic config (no models), single model, complete 4-model, partial models, and third-party provider examples
* Improve visual design with param-list component and color-coded badges
* Add detailed descriptions for each configuration scenario
- Redesign DeepLinkImportDialog layout:
* Switch from 3-column to compact 2-column grid layout
* Reduce dialog width from 500px to 650px for better content display
* Add dedicated section for Claude model configurations with blue highlight box
* Use uppercase labels and smaller text for more information density
* Add truncation and tooltips for long URLs
* Improve visual hierarchy with spacing and grouping
* Increase z-index to 9999 to ensure dialog appears on top
- Minor UI refinements:
* Update App.tsx layout adjustments
* Optimize McpFormModal styling
* Refine ProviderCard and BasicFormFields components
This enables users to import Claude providers with precise model configurations via deeplink.
* feat(deeplink): add config file support for deeplink import
Support importing provider configuration from embedded or remote config files.
- Add base64 dependency for config content encoding
- Support config, configFormat, and configUrl parameters
- Make homepage/endpoint/apiKey optional when config is provided
- Add config parsing and merging logic
* feat(deeplink): enhance dialog with config file preview
Add config file parsing and preview in deep link import dialog.
- Support Base64 encoded config display
- Add config file source indicator (embedded/remote)
- Parse and display config fields by app type (Claude/Codex/Gemini)
- Mask sensitive values in config preview
- Improve dialog layout and content organization
* refactor(ui): unify dialog styles and improve layout consistency
Standardize dialog and panel components across the application.
- Update dialog background to use semantic color tokens
- Adjust FullScreenPanel max-width to 56rem for better alignment
- Add drag region and prevent body scroll in full-screen panels
- Optimize button sizes and spacing in panel headers
- Apply consistent styling to all dialog-based components
* i18n: add deeplink config preview translations
Add missing translation keys for config file preview feature.
- Add configSource, configEmbedded, configRemote labels
- Add configDetails and configUrl display strings
- Support both Chinese and English versions
* feat(deeplink): enhance test page with v3.8 config file examples
Improve deeplink test page with comprehensive config file import examples.
- Add version badge for v3.8 features
- Add copy-to-clipboard functionality for all deep links
- Add Claude config file import examples (embedded/remote)
- Add Codex config file import examples (auth.json + config.toml)
- Add Gemini config file import examples (.env format)
- Add config generator tool for easy testing
- Update UI with better styling and layout
* feat(settings): add autoSaveSettings for lightweight auto-save
Add optimized auto-save function for General tab settings.
- Add autoSaveSettings method for non-destructive auto-save
- Only trigger system APIs when values actually change
- Avoid unnecessary auto-launch and plugin config updates
- Update tests to cover new functionality
* refactor(settings): simplify settings page layout and auto-save
Reorganize settings page structure and integrate autoSaveSettings.
- Move save button inline within Advanced tab content
- Remove sticky footer for cleaner layout
- Use autoSaveSettings for General tab settings
- Simplify dialog close behavior
- Refactor ImportExportSection layout
* style(providers): optimize card layout and action button sizes
Improve provider card visual density and action buttons.
- Reduce icon button sizes for compact layout
- Adjust drag handle and icon sizes
- Tighten spacing between action buttons
- Update hover translate values for better alignment
* refactor(mcp): improve form modal layout with adaptive height editor
Restructure MCP form modal for better space utilization.
- Split form into upper form fields and lower JSON editor sections
- Add full-height mode support for JsonEditor component
- Use flex layout for editor to fill available space
- Update PromptFormPanel to use full-height editor
- Fix locale text formatting
* style: unify list item styles with semantic colors
Apply consistent styling to list items across components.
- Replace hardcoded colors with semantic tokens in MCP and Prompt list items
- Add glass effect container to EndpointSpeedTest panel
- Format code for better readability
* style: format template literals for better readability
Improve code formatting for conditional className expressions.
- Break long template literals across multiple lines
- Maintain consistent formatting in MCP form and endpoint test components
* feat(deeplink): add config merge command for preview
Expose config merging functionality to frontend for preview.
- Add merge_deeplink_config Tauri command
- Make parse_and_merge_config public for reuse
- Enable frontend to display complete config before import
* feat(deeplink): merge and display config in import dialog
Enhance import dialog to fetch and display complete config.
- Call mergeDeeplinkConfig API when config is present
- Add UTF-8 base64 decoding support for config content
- Add scrollable content area with custom scrollbar styling
- Show complete configuration before user confirms import
* i18n: add config merge error message
Add translation for config file merge error handling.
* style(deeplink): format test page HTML for better readability
Improve HTML formatting in deeplink test page.
- Format multiline attributes for better readability
- Add consistent indentation to nested elements
- Break long lines in buttons and links
* refactor(usage): improve footer layout with two-row design
Reorganize usage footer for better readability and space efficiency.
- Split into two rows: update time + refresh button (row 1), usage stats (row 2)
- Move refresh button to top row next to update time
- Remove card background for cleaner look
- Add fallback text when never updated
- Improve spacing and alignment
- Format template literals for consistency
Transform MCP server configurations to match Gemini CLI's expected format:
- Remove 'type' field (Gemini infers transport from field presence)
- Use 'httpUrl' field for HTTP streaming servers (type: "http")
- Keep 'url' field for SSE servers (type: "sse")
- Keep 'command' field for stdio servers unchanged
This fixes MCP servers not being recognized by Gemini CLI when
configured through cc-switch.
Update both English and Chinese README files to reflect the major platform positioning shift and new capabilities:
**Platform Positioning**
- Update tagline: "From Provider Switcher to All-in-One AI CLI Management Platform"
- Emphasize unified management of configurations, MCP, Skills, and Prompts
**v3.7.0 Highlights**
- Gemini CLI Integration: Third supported AI CLI with dual-file config
- Claude Skills Management: GitHub integration, lifecycle management
- Prompts Management: Multi-preset system with Markdown editor
- MCP v3.7.0 Unified Architecture: SSE transport, smart parser, cross-app sync
- Deep Link Protocol: ccswitch:// for one-click config sharing
- Environment Conflict Detection: Auto-detect and resolve config conflicts
**Documentation Updates**
- Add v3.7.0 release notes link
- Expand Quick Start with Skills and Prompts management guides
- Update configuration files section with Gemini environment variables
- Add ShanDianShuo sponsor logos and information
**Technical Details**
- 18,000+ lines of new code across 6 core features
- Cross-app support for Claude Code, Codex, and Gemini CLI
- Complete usage instructions for new management systems
This reverts commit ba336fc416.
Reason: Found issues that need to be addressed before reintroducing
the auto-launch feature. Will reimplement with fixes.
Implement auto-launch functionality with proper state synchronization
and error handling across Windows, macOS, and Linux platforms.
Key changes:
- Add auto_launch module using auto-launch crate 0.5
- Define typed errors (AutoLaunchPathError, AutoLaunchEnableError, etc.)
- Sync system state with settings.json on app startup
- Only call system API when auto-launch state actually changes
- Add UI toggle in Window Settings panel
- Add i18n support for auto-launch settings (en/zh)
Implementation details:
- Settings file (settings.json) is the single source of truth
- On startup, system state is synced to match settings.json
- Error handling uses Rust type system with proper error propagation
- Frontend optimized to avoid unnecessary system API calls
Platform support:
- Windows: HKEY_CURRENT_USER registry modification
- macOS: AppleScript-based launch item (configurable to Launch Agent)
- Linux: XDG autostart desktop file
- Add structured error format with error codes and context
- Create skillErrorParser to format errors for user-friendly display
- Add comprehensive i18n keys for all skill-related errors (zh/en)
- Extend download timeout from 15s to 60s to reduce false positives
- Fix: Pass correct error title based on operation context (load/install/uninstall)
Error improvements:
- SKILL_NOT_FOUND: Show skill directory name
- DOWNLOAD_TIMEOUT: Display repo info and timeout duration with network suggestion
- DOWNLOAD_FAILED: Show HTTP status code with specific suggestions (403/404/429)
- SKILL_DIR_NOT_FOUND: Show full path with URL check suggestion
- EMPTY_ARCHIVE: Suggest checking repository URL
Users will now see detailed error messages instead of generic "Error".
- Add skills_path field to Skill struct
- Use skills_path to construct correct source path during installation
- Fix installation for repos with custom skill subdirectories
Add onInteractOutside handler to DialogContent to prevent accidental
dialog closure when users click on the overlay/backdrop. This prevents
data loss in forms and improves user experience across all 11 dialog
components in the application.
Users can still close dialogs using:
- Close button (X) in the top-right corner
- Cancel/Close buttons within the dialog
- ESC key
- Update CHANGELOG.md with v3.7.0 entry covering six major features
- Add English release notes (docs/release-note-v3.7.0-en.md)
- Add Chinese release notes (docs/release-note-v3.7.0-zh.md)
Major features documented:
- Gemini CLI integration (third app support)
- MCP v3.7.0 unified architecture
- Claude Skills management system (~2,000 lines)
- Prompts management system (~1,300 lines)
- Deep link protocol (ccswitch://)
- Environment variable conflict detection
- Update version from 3.6.2 to 3.7.0 across all config files
- Update project description to "All-in-One Assistant for Claude Code, Codex & Gemini CLI"
- Update Chinese description to "Claude Code / Codex / Gemini CLI 全方位辅助工具"
- Sync version in package.json, Cargo.toml, tauri.conf.json
- Update branding in README.md, README_ZH.md and i18n locale files
- Add DouBaoSeed code preview provider preset with Volcengine ARK API configuration
- Remove AnyRouter provider from both Claude and Codex preset configurations
- Clean up trailing commas in codex provider presets
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updated default model from gemini-2.5-pro to gemini-3-pro-preview across:
- Provider presets (PackyCode, Custom)
- Form field placeholders
- Default configurations
- Test cases
Google Official preset now has empty env config, allowing users to choose
their own model or use application defaults, which is more appropriate for
OAuth-based authentication.
Changes:
- geminiProviderPresets.ts: updated model to gemini-3-pro-preview, removed model from Google Official
- GeminiFormFields.tsx: updated placeholder to gemini-3-pro-preview
- GeminiConfigSections.tsx: updated placeholder to gemini-3-pro-preview
- ProviderForm.tsx: updated default config to gemini-3-pro-preview
- gemini_config.rs: updated test examples to gemini-3-pro-preview
Previously, when editing a Gemini provider, only the .env file was read,
missing the settings.json file that contains MCP configuration and other
settings. This caused the config field in the edit form to be empty or
show outdated data.
This fix ensures both files are read and merged into the complete
structure { "env": {...}, "config": {...} }, matching the behavior
of Codex provider.
Fixed in:
- read_live_settings(): now reads both .env and settings.json
- import_default_config(): now reads both files when importing
- Update RegValue.to_string() usage to match current winreg API
which returns String directly instead of Result
- Add conditional compilation for std::fs import (Unix only)
The Gemini API key, base URL, and model inputs were not syncing with
the env editor below due to data source mismatch. The form was using
generic hooks (useApiKeyState, useBaseUrlState) that only updated
settingsConfig, while the env editor relied on geminiEnv from
useGeminiConfigState.
Changes:
- Use geminiApiKey/geminiBaseUrl from useGeminiConfigState instead of
generic hooks
- Wrap handlers to maintain bidirectional sync between geminiEnv and
settingsConfig
- Remove unused handleGeminiBaseUrlChange from useBaseUrlState to
avoid naming conflicts
Now all Gemini form fields properly sync with the env editor in both
directions.
* feat(providers): add notes field for provider management
- Add notes field to Provider model (backend and frontend)
- Display notes with higher priority than URL in provider card
- Style notes as non-clickable text to differentiate from URLs
- Add notes input field in provider form
- Add i18n support (zh/en) for notes field
* chore: format code and clean up unused props
- Run cargo fmt on Rust backend code
- Format TypeScript imports and code style
- Remove unused appId prop from ProviderPresetSelector
- Clean up unused variables in tests
- Integrate notes field handling in provider dialogs
* feat(deeplink): implement ccswitch:// protocol for provider import
Add deep link support to enable one-click provider configuration import via ccswitch:// URLs.
Backend:
- Implement URL parsing and validation (src-tauri/src/deeplink.rs)
- Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs)
- Register ccswitch:// protocol in macOS Info.plist
- Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs)
Frontend:
- Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx)
- Add API wrapper (src/lib/api/deeplink.ts)
- Integrate event listeners in App.tsx
Configuration:
- Update Tauri config for deep link handling
- Add i18n support for Chinese and English
- Include test page for deep link validation (deeplink-test.html)
Files: 15 changed, 1312 insertions(+)
* chore(deeplink): integrate deep link handling into app lifecycle
Wire up deep link infrastructure with app initialization and event handling.
Backend Integration:
- Register deep link module and commands in mod.rs
- Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url)
- Handle deep links from single instance callback (Windows/Linux CLI)
- Handle deep links from macOS system events
- Add tauri-plugin-deep-link dependency (Cargo.toml)
Frontend Integration:
- Listen for deeplink-import/deeplink-error events in App.tsx
- Update DeepLinkImportDialog component imports
Configuration:
- Enable deep link plugin in tauri.conf.json
- Update Cargo.lock for new dependencies
Localization:
- Add Chinese translations for deep link UI (zh.json)
- Add English translations for deep link UI (en.json)
Files: 9 changed, 359 insertions(+), 18 deletions(-)
* refactor(deeplink): enhance Codex provider template generation
Align deep link import with UI preset generation logic by:
- Adding complete config.toml template matching frontend defaults
- Generating safe provider name from sanitized input
- Including model_provider, reasoning_effort, and wire_api settings
- Removing minimal template that only contained base_url
- Cleaning up deprecated test file deeplink-test.html
* style: fix clippy uninlined_format_args warnings
Apply clippy --fix to use inline format arguments in:
- src/mcp.rs (8 fixes)
- src/services/env_manager.rs (10 fixes)
* style: apply code formatting and cleanup
- Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts)
- Organize Rust imports and module order alphabetically
- Add newline at end of JSON files (en.json, zh.json)
- Update Cargo.lock for dependency changes
* feat: add model name configuration support for Codex and fix Gemini model handling
- Add visual model name input field for Codex providers
- Add model name extraction and update utilities in providerConfigUtils
- Implement model name state management in useCodexConfigState hook
- Add conditional model field rendering in CodexFormFields (non-official only)
- Integrate model name sync with TOML config in ProviderForm
- Fix Gemini deeplink model injection bug
- Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL
- Add test cases for Gemini model injection (with/without model)
- All tests passing (9/9)
- Fix Gemini model field binding in edit mode
- Add geminiModel state to useGeminiConfigState hook
- Extract model value during initialization and reset
- Sync model field with geminiEnv state to prevent data loss on submit
- Fix missing model value display when editing Gemini providers
Changes:
- 6 files changed, 245 insertions(+), 13 deletions(-)
Fixed missing closing braces in the error object of both en.json and zh.json locale files that caused Vite parse errors. The envManager.error object was not properly closed, causing the subsequent skills object to be parsed incorrectly.
* feat(skills): add Claude Skills management feature
Implement complete Skills management system with repository discovery,
installation, and lifecycle management capabilities.
Backend:
- Add SkillService with GitHub integration and installation logic
- Implement skill commands (list, install, uninstall, check updates)
- Support multiple skill repositories with caching
Frontend:
- Add Skills management page with repository browser
- Create SkillCard and RepoManager components
- Add badge, card, table UI components
- Integrate Skills API with Tauri commands
Files: 10 files changed, 1488 insertions(+)
* feat(skills): integrate Skills feature into application
Integrate Skills management feature with complete dependency updates,
configuration structure extensions, and internationalization support.
Dependencies:
- Add @radix-ui/react-visually-hidden for accessibility
- Add anyhow, zip, serde_yaml, tempfile for Skills backend
- Enable chrono serde feature for timestamp serialization
Backend Integration:
- Extend MultiAppConfig with SkillStore field
- Implement skills.json migration from legacy location
- Register SkillService and skill commands in main app
- Export skill module in commands and services
Frontend Integration:
- Add Skills page route and dialog in App
- Integrate Skills UI with main navigation
Internationalization:
- Add complete Chinese translations for Skills UI
- Add complete English translations for Skills UI
Code Quality:
- Remove redundant blank lines in gemini_mcp.rs
- Format log statements in mcp.rs
Tests:
- Update import_export_sync tests for SkillStore
- Update mcp_commands tests for new structure
Files: 16 files changed, 540 insertions(+), 39 deletions(-)
* style(skills): improve SkillsPage typography and spacing
Optimize visual hierarchy and readability of Skills page:
- Reduce title size from 2xl to lg with tighter tracking
- Improve description spacing and color contrast
- Enhance empty state with better text hierarchy
- Use explicit gray colors for better dark mode support
* feat(skills): support custom subdirectory path for skill scanning
Add optional skillsPath field to SkillRepo to enable scanning skills
from subdirectories (e.g., "skills/") instead of repository root.
Changes:
- Backend: Add skillsPath field with subdirectory scanning logic
- Frontend: Add skillsPath input field and display in repo list
- Presets: Add cexll/myclaude repo with skills/ subdirectory
- Code quality: Fix clippy warnings (dedup logic, string formatting)
Backward compatible: skillsPath is optional, defaults to root scanning.
* refactor(skills): improve repo manager dialog layout
Optimize dialog structure with fixed header and scrollable content:
- Add flexbox layout with fixed header and scrollable body
- Remove outer border wrapper for cleaner appearance
- Match SkillsPage design pattern for consistency
- Improve UX with better content hierarchy
Add comprehensive implementation plan for v3.7.0 refactor that
separates Codex MCP configuration from unified MCP structure.
Key design decisions:
- Codex MCP stored as raw TOML string in codexMcp.rawToml
- Unified MCP (mcp.servers) only supports Claude/Gemini
- Complete isolation: no apps.codex in unified structure
- Migration clears mcp.codex.servers to prevent pollution
Architecture improvements:
- Single responsibility: each data source has one purpose
- No priority conflicts: completely independent data paths
- Simplified switching logic: no conditional branches
- UI constraints: Tab1 limited to claude/gemini only
Implementation phases:
- Phase 0: Setup (0.5d)
- Phase 1: Backend foundation (1.5d)
- Phase 2: Command layer (1d)
- Phase 3: Switching logic (1d)
- Phase 4: Frontend API (0.5d)
- Phase 5: UI implementation (2d)
- Phase 6: Enhancements (1d, optional)
- Phase 7: Testing & docs (1d)
Total: 8.5 days (MVP: 7 days)
Addresses TOML-JSON conversion data loss issue by preserving
raw TOML format for Codex while maintaining structured approach
for Claude/Gemini.
Add `center: true` to main window configuration to improve initial
window positioning on Windows and other platforms. This addresses
user feedback about the window appearing in the top-left corner.
Previously, only Codex provider switches triggered MCP synchronization,
which could cause MCP configuration loss when switching Claude or Gemini
providers.
Changes:
- Enable MCP sync for all app types (Claude, Codex, Gemini) during provider switch
- Migrate to v3.7.0 unified MCP sync mechanism using McpService::sync_all_enabled()
- Replace app-specific sync_enabled_to_codex() with unified sync for all apps
- Remove unused mcp module import
This ensures MCP servers remain properly configured across all applications
after provider switches, preventing configuration loss.
BREAKING CHANGE: The [mcp.servers] format was completely incorrect and not
any official Codex format. The only correct format is [mcp_servers] at the
top level of config.toml.
Changes:
- Remove incorrect [mcp.servers] nested table support
- Always use [mcp_servers] top-level table (official Codex format)
- Auto-migrate and cleanup erroneous [mcp.servers] entries on write
- Preserve error-tolerant import for migrating old incorrect configs
- Simplify sync logic by removing format selection branches (~60 lines)
- Update all documentation and tests to reflect correct format
- Add warning logs when detecting and cleaning incorrect format
Backend (Rust):
- mcp.rs: Simplify sync_enabled_to_codex by removing Target enum
- mcp.rs: sync_single_server_to_codex now always uses [mcp_servers]
- mcp.rs: remove_server_from_codex cleans both locations
- mcp.rs: Update import_from_codex comments to clarify format status
- tests: Rename test to sync_enabled_to_codex_migrates_erroneous_*
- tests: Update assertions to verify migration behavior
Frontend (TypeScript):
- tomlUtils.ts: Prioritize [mcp_servers] format in parsing
- tomlUtils.ts: Update error messages to guide correct format
Documentation:
- README.md: Correct MCP format reference to [mcp_servers]
- CLAUDE.md: Add comprehensive format specification with examples
All 79 tests pass. This ensures backward compatibility while enforcing
the correct Codex official standard going forward.
Refs: https://github.com/openai/codex/issues/3441
Fixes two critical issues with the MCP JSON input:
1. Format button failed with wrapped format like "server": {...}
2. Submit button failed despite input validation passing
Changes:
- Updated formatJSON to use smart parser (supports wrapped format)
- Simplified submit validation logic (removed redundant validateJsonConfig call)
- Improved UX: input preserves original format, cleanup happens on format/submit
* Prevents confusing "instant disappearance" when pasting wrapped JSON
* Auto-fills ID/Name fields while keeping input unchanged
* Format button now strips wrapper key and formats cleanly
* Submit button correctly extracts config regardless of format
Code quality:
- Reduced code by 5 lines (20 changes, 11 insertions, 16 deletions)
- Consistent use of parseSmartMcpJson across all JSON operations
- No type errors introduced
Support multiple MCP configuration input formats:
- Pure config object: { "command": "npx", ... }
- Key-value pair fragment: "server-name": { "command": "npx", ... }
- Wrapped object: { "server-name": { "command": "npx", ... } }
The parser automatically:
- Detects and wraps JSON fragments into complete objects
- Extracts server name from single-key objects
- Auto-fills ID and Name fields when applicable
- Formats the config for display
This improves UX by allowing users to paste configs directly from
.claude.json or .codex/config.toml without manual editing.
- Simplify config label from "Full JSON configuration or use" to "Full JSON Configuration"
- Align wizard button to the right using justify-between layout
- Apply same pattern to TOML configuration label
- Improve visual balance with cleaner left-right alignment
- Enable all apps (Claude, Codex, Gemini) by default when adding MCP servers
- Improve config label with clearer wording: "Full JSON configuration or use [Config Wizard]"
- Add JSON format button to beautify configuration with 2-space indentation
- Update tests to reflect new default behavior
- Clean up redundant explicit prop passing
This provides a more streamlined experience by enabling all apps out of the box
and making it easier to format JSON configurations.
**Changes:**
- Remove all comments from custom template (align with Claude/Gemini)
- Remove base_url field from template (user fills in form instead)
- Simplify getCodexCustomTemplate() - no locale parameter needed
- Keep preset configurations unchanged with their baseUrl values
**Template now contains only:**
- model_provider, model, model_reasoning_effort
- disable_response_storage
- [model_providers.custom] section with minimal fields
**Benefits:**
- ✅ Cleaner, more focused template
- ✅ Consistent with other apps (no comments)
- ✅ Forces users to fill base_url via form field
- ✅ Reduced template size from 35+ lines to 12 lines
Net change: -74 lines (codexTemplates.ts)
**Changes:**
- Create src/config/codexTemplates.ts with getCodexCustomTemplate factory
- Support both Chinese and English templates based on i18n.language
- Remove 70 lines of duplicated template strings from ProviderForm.tsx
- Update both useEffect and handlePresetChange to use template factory
- Clean up unused "Custom (Blank Template)" preset entry
**Benefits:**
- ✅ Eliminates code duplication (35-line template repeated twice)
- ✅ Adds internationalization support for English users
- ✅ Follows project architecture (templates in config/ directory)
- ✅ Improves maintainability (single source of truth)
- ✅ Net reduction: 34 lines (81 additions, 115 deletions)
**Technical Details:**
- Template selection logic: (i18n.language || "zh").startsWith("zh") ? "zh" : "en"
- Templates are identical except for comments language
- Both auth and config are returned as a single CodexTemplate object
Addresses DRY principle violation and architectural concerns identified
in code review.
- Replace checkboxes with toggle switches for app selection (more intuitive for enable/disable actions)
- Change switch color from blue to emerald to match MCP button theme
- Stack app options vertically with labels on left to save horizontal space
- Reduce panel width from max-w-4xl to max-w-3xl for more compact design
- Move docs button next to server name for better information grouping
- Update primary blue from Linear style (#3498db) to macOS system blue (#0A84FF)
- Align gray scale with macOS dark mode palette (#1C1C1E, #2C2C2E, etc.)
- Adjust dark mode background to match macOS systemBackground (HSL 240 5% 12%)
- Refine scrollbar colors to use native macOS gray tones
- Remove transparent titleBarStyle for better stability
Complete the v3.7.0 MCP refactoring by updating the form layer to match
the unified architecture already implemented in data/service/API layers.
**Breaking Changes:**
- Remove confusing `appId` parameter from McpFormModal
- Replace with `defaultFormat` (json/toml) and `defaultEnabledApps` (array)
**Form Enhancements:**
- Add app enablement checkboxes (Claude/Codex/Gemini) directly in the form
- Smart defaults: new servers default to Claude enabled, editing preserves state
- Support "draft" mode: servers can be created without enabling any apps
**Architecture Improvements:**
- Eliminate semantic confusion: format selection separate from app targeting
- One-step workflow: configure and enable apps in single form submission
- Consistent with unified backend: `apps: { claude, codex, gemini }`
**Testing:**
- Update test mocks to use `useUpsertMcpServer` hook
- Add test case for creating servers with no apps enabled
- Fix parameter references from `appId` to `defaultFormat`
**i18n:**
- Add `mcp.form.enabledApps` translation (zh/en)
- Add `mcp.form.noAppsWarning` translation (zh/en)
This completes the MCP management refactoring, ensuring all layers
(data, service, API, UI) follow the same unified architecture pattern.
- Add unified `common_config_snippets` structure to MultiAppConfig
- Implement `get_common_config_snippet` and `set_common_config_snippet` commands
- Replace localStorage with config.json persistence for Codex and Gemini
- Auto-migrate legacy `claude_common_config_snippet` to new unified structure
- Deprecate individual API methods in favor of unified interface
- Add automatic migration from localStorage on first load
BREAKING CHANGE: Common config snippets now stored in unified `common_config_snippets` object instead of separate fields
- Change auto_import_prompt_if_exists return type to Result<bool>
- Remove redundant save() calls by introducing updated flag
- Eliminate unnecessary clone() in app type iterations
- Remove semantic contradiction in let _ = ...? pattern
- Improve code semantics and maintainability
Performance: Reduce disk I/O by 50%+ through unified save logic
Fix backfill logic in prompt enable workflow:
- Prioritize backfilling live file content to currently enabled prompt (prevent data loss)
- Create backup only when no enabled prompt exists and content is new (avoid duplicate backups)
- Implement staged persistence (save after backfill + save after enable)
- Add explicit logging for backfill/backup operations
Also simplify string formatting in prompt_files.rs with inline format strings.
- Rewrite import_from_claude/codex/gemini to write directly to mcp.servers
- Implement skip-on-error strategy for fault tolerance (single invalid item no longer aborts entire batch)
- Smart merge logic: existing servers only enable corresponding app, preserve other configs
- Remove deprecated markers from service layer
- Export McpApps type for test usage
- Update mcp_commands tests to use unified structure
Fixes runtime import issue where data was written to legacy structure (mcp.claude/codex.servers)
but unified panel reads from new structure (mcp.servers), causing "imported but invisible" bug.
Problem:
- On first launch, McpRoot::default() created `servers: None`
- McpService::get_all_servers() would incorrectly return error "old structure detected"
- This confused new users who had no legacy MCP data
Root Cause:
- Derived Default trait sets Option<T> fields to None
- New installations should start with v3.7.0 structure immediately
Solution:
- Explicitly implement Default for McpRoot
- Initialize `servers: Some(HashMap::new())` for v3.7.0+ structure
- Legacy fields (claude/codex/gemini) remain empty, only used for deserializing old configs
Impact:
- First-time users get correct v3.7.0 structure immediately
- migrate_mcp_to_unified() correctly detects already-migrated state
- No false "old structure" errors on fresh installs
All MCP operations already auto-sync to live configs:
- upsert_server() → sync_server_to_apps()
- toggle_app() → sync_server_to_app() or remove_server_from_app()
- delete_server() → remove_server_from_all_apps()
The manual 'Sync All' button was redundant and could confuse users
into thinking they need to manually sync after each change.
Changes:
- Remove 'Sync All' button from UnifiedMcpPanel header
- Remove useSyncAllMcpServers hook
- Remove handleSyncAll function and syncAllMutation state
- Remove RefreshCw icon import
- Remove sync-related i18n translations (en/zh)
Note: Backend sync_all_mcp_servers command remains for potential
future use (e.g., recovery tool), but is no longer exposed in UI.
Auto-migration at startup is sufficient for upgrading from v3.6.x.
Manual import adds unnecessary complexity since:
- Gemini MCP support launches with v3.7.0 (no legacy data)
- Existing Claude/Codex MCP configs are auto-migrated on first run
- All MCP management should happen within CC Switch
Changes:
- Remove McpImportDialog component
- Remove "Import" button from UnifiedMcpPanel
- Remove import-related i18n translations (en/zh)
- Simplify user experience with single management interface
Note: Backend import commands (import_mcp_from_*) remain for
backward compatibility but are no longer exposed in UI.
Complete Phase 3 (P0) frontend implementation for unified MCP management:
**New Files:**
- src/hooks/useMcp.ts: React Query hooks for unified MCP operations
- src/components/mcp/UnifiedMcpPanel.tsx: Unified MCP management panel
- src/components/ui/checkbox.tsx: Checkbox component from shadcn/ui
**Features:**
- Unified panel with three-column layout: server info + app checkboxes + actions
- Multi-app control: Claude/Codex/Gemini checkboxes for each server
- Real-time stats: Show enabled server counts per app
- Full CRUD operations: Add, edit, delete, sync all servers
**Integration:**
- Replace old app-specific McpPanel with UnifiedMcpPanel in App.tsx
- Update McpFormModal to support unified mode with apps field
- Add i18n support: mcp.unifiedPanel namespace (zh/en)
**Type Safety:**
- Ensure McpServer.apps field always initialized
- Fix all test files to include apps field
- TypeScript type check passes ✅
**Architecture:**
- Single source of truth: mcp.servers manages all MCP configs
- Per-server app control: apps.claude/codex/gemini boolean flags
- Backward compatible: McpFormModal supports both unified and legacy modes
Next: P1 tasks (import dialogs, sub-components, tests)
## Type Definitions
- Update McpServer interface with new apps field (McpApps)
- Add McpApps interface for multi-app enable state
- Add McpServersMap type for server collections
- Mark enabled field as deprecated (use apps instead)
- Maintain backward compatibility with optional fields
## API Layer Updates
- Add unified MCP management methods to mcpApi:
* getAllServers() - retrieve all servers with apps state
* upsertUnifiedServer() - add/update server with apps
* deleteUnifiedServer() - remove server
* toggleApp() - enable/disable server for specific app
* syncAllServers() - sync all enabled servers to live configs
- Import new McpServersMap type
## Code Organization
- Keep all types in src/types.ts (removed duplicate types/mcp.ts)
- Follow existing project structure conventions
Related: v3.7.0 unified MCP management
## Compilation Fixes
- Add deprecated compatibility methods to McpService:
* get_servers() - filters servers by app
* set_enabled() - delegates to toggle_app()
* sync_enabled() - syncs enabled servers for specific app
* import_from_claude/codex/gemini() - wraps mcp:: functions
- Fix toml_edit type conversion in sync_single_server_to_codex():
* Add json_server_to_toml_table() helper function
* Manually construct toml_edit::Table instead of invalid serde conversion
- Fix get_codex_config_path() calls (returns PathBuf, not Result)
- Update upsert_mcp_server_in_config() to work with unified structure:
* Converts old per-app API to unified McpServer structure
* Preserves existing server data when updating
* Supports sync_other_side parameter for multi-app enable
- Update delete_mcp_server_in_config() to ignore app parameter
## Backward Compatibility
- All old v3.6.x commands continue to work with deprecation warnings
- Frontend migration can be done incrementally
- Old commands transparently use new unified backend
## Status
✅ Backend compiles successfully (cargo check passes)
⚠️ 16 warnings (8 deprecation + 8 unused functions - expected)
- Add mcp.geminiTitle to both zh.json and en.json
- Fix McpPanel title logic to handle all three apps (claude/codex/gemini)
- Previous logic would incorrectly display codexTitle for gemini
- Add gemini_mcp.rs module for Gemini MCP file I/O operations
- Implement sync_enabled_to_gemini to export enabled MCPs to ~/.gemini/settings.json
- Implement import_from_gemini to import MCPs from Gemini config
- Add Gemini sync logic in services/mcp.rs (upsert_server, delete_server, set_enabled)
- Register Tauri commands for Gemini MCP sync and import
- Update frontend API calls and McpPanel to support Gemini
Fixes the issue where adding MCP servers in Gemini tab would not sync to ~/.gemini/settings.json
Implements dual-editor pattern for Gemini providers, following the Codex architecture:
- Environment variables (.env format) editor
- Extended configuration (config.json) editor with common config support
New Components:
- GeminiConfigSections: Separate sections for env and config editing
- GeminiCommonConfigModal: Modal for editing common config snippets
New Hooks:
- useGeminiConfigState: Manages env/config separation and conversion
- Converts between .env string format and JSON object
- Validates JSON config structure
- Extracts API Key and Base URL from env
- useGeminiCommonConfig: Handles common config snippets
- Deep merge algorithm for combining configs
- Remove common config logic for toggling off
- localStorage persistence for snippets
Features:
- Format buttons for both env and config editors
- Common config toggle with deep merge/remove
- Error validation and display
- Auto-open modal on common config errors
Configuration Structure:
{
"env": {
"GOOGLE_GEMINI_BASE_URL": "https://...",
"GEMINI_API_KEY": "sk-...",
"GEMINI_MODEL": "gemini-2.5-pro"
},
"config": {
"timeout": 30000,
"maxRetries": 3
}
}
This brings Gemini providers to feature parity with Claude and Codex.
- Change ProviderForm to use providerForm.category* instead of providerPreset.category*
- Remove duplicate category keys from providerPreset namespace in both zh.json and en.json
- Fix naming inconsistency: use categoryAggregation (not categoryAggregator)
- Fixes issue where English UI would show Chinese defaultValue fallbacks
This ensures single source of truth for category labels and improves maintainability.
Migrate the Claude common config snippet storage from browser localStorage
to the persistent config.json file for better cross-device sync and backup support.
**Backend Changes:**
- Add `claude_common_config_snippet` field to `MultiAppConfig` struct
- Add `get_claude_common_config_snippet` and `set_claude_common_config_snippet` Tauri commands
- Include JSON validation in the setter command
**Frontend Changes:**
- Create new `lib/api/config.ts` API module
- Refactor `useCommonConfigSnippet` hook to use config.json instead of localStorage
- Add automatic one-time migration from localStorage to config.json
- Add loading state during initialization
**Benefits:**
- Cross-device synchronization via backup/restore
- More reliable persistence than browser storage
- Centralized configuration management
- Seamless migration for existing users
- Use hardcoded "Claude", "Codex", "Gemini" instead of i18n keys
- Brand names should not be translated across different locales
- Simplifies code by removing useTranslation hook from AppSwitcher
- Reduces maintenance overhead in translation files
- Add i18n for Claude/Codex/Gemini app names in AppSwitcher
- Use useTranslation hook with existing translation keys
- Fix ASCII diagram alignment in README files
Address code review feedback for PR #214:
- Replace hardcoded Chinese strings with English in auto-imported prompts
- Prompt name: "Auto-imported Prompt" instead of "初始提示词"
- Description: "Automatically imported on first launch"
- Remove panic risk by replacing expect() with proper error propagation
- Use AppError::localized for bilingual error messages
- Extract get_base_dir_with_fallback() helper to eliminate code duplication
- Update test assertions to match new English strings
- Suppress false-positive dead_code warning on TempHome.dir field
All 5 tests passing with zero compiler warnings.
- Extract prompt file path logic to dedicated prompt_files module
- Refactor PromptService to use centralized path resolution
- Implement auto-import for existing prompt files on first startup
- Add comprehensive unit tests for auto-import functionality
Allow users to create Gemini provider configurations without API key
and fill it in later. Split validation into two modes:
- validate_gemini_settings: Basic structure check (used when adding)
- validate_gemini_settings_strict: Full validation (used when switching)
This fixes the error 'Gemini config missing required field: GEMINI_API_KEY'
when trying to add Gemini providers from presets like PackyCode or Google.
Changes:
- Add validate_gemini_settings_strict for switching validation
- Update write_gemini_live to use strict validation
- Add comprehensive tests for both validation modes
Refactor tray menu system to support three applications (Claude/Codex/Gemini):
- Introduce generic TrayAppSection structure and TRAY_SECTIONS array
- Implement append_provider_section and handle_provider_tray_event helper functions
- Enhance Gemini provider service with .env config read/write support
- Implement Gemini LiveSnapshot for atomic operations and rollback
- Update README documentation to reflect Gemini tray quick switching feature
Update Google Gemini preset to match Claude Official styling:
- Rename 'Google' to 'Google Official'
- Add GeminiIcon support in preset selector
- Add custom theme with Google blue (#4285F4) background
- Update PresetTheme type to support 'gemini' icon type
Changes:
- Add GeminiPresetTheme interface
- Add theme config to Google Official preset
- Import and render GeminiIcon in ProviderPresetSelector
- Update PresetTheme icon type to include 'gemini'
* feat(prompts): add prompt management across Tauri service and React UI
- backend: add commands/prompt.rs, services/prompt.rs, register in commands/mod.rs and lib.rs, refine app_config.rs
- frontend: add PromptPanel, PromptFormModal, PromptListItem, MarkdownEditor, usePromptActions, integrate in App.tsx
- api: add src/lib/api/prompts.ts
- i18n: update src/i18n/locales/{en,zh}.json
- build: update package.json and pnpm-lock.yaml
* feat(i18n): improve i18n for prompts and Markdown editor
- update src/i18n/locales/{en,zh}.json keys and strings
- apply i18n in PromptFormModal, PromptPanel, and MarkdownEditor
- align prompt text with src-tauri/src/services/prompt.rs
* feat(prompts): add enable/disable toggle and simplify panel UI
- Add PromptToggle component and integrate in prompt list items
- Implement toggleEnabled with optimistic update; enable via API, disable via upsert with enabled=false;
reload after success
- Simplify PromptPanel: remove file import and current-file preview to keep CRUD flow focused
- Tweak header controls style (use mcp variant) and minor copy: rename “Prompt Management” to “Prompts”
- i18n: add disableSuccess/disableFailed messages
- Backend (Tauri): prevent duplicate backups when importing original prompt content
* style: unify code formatting with trailing commas
* feat(prompts): add Gemini filename support to PromptFormModal
Update filename mapping to use Record<AppId, string> pattern, supporting
GEMINI.md alongside CLAUDE.md and AGENTS.md.
* fix(prompts): sync enabled prompt to file when updating
When updating a prompt that is currently enabled, automatically sync
the updated content to the corresponding live file (CLAUDE.md/AGENTS.md/GEMINI.md).
This ensures the active prompt file always reflects the latest content
when editing enabled prompts.
Optimize custom endpoint management logic to distinguish between edit and create modes:
- Edit mode: endpoints are read/written directly to backend via API
- Create mode: use draftCustomEndpoints to stage, save on submit
- Remove duplicate endpoint loading in useSpeedTestEndpoints
- Add isSaving state and initialCustomUrls tracking
- Add 🔥 TypeScript Trending badge celebrating daily/weekly/monthly rankings
- Refine contributing guidelines with friendlier, more welcoming language
- Replace directive tone with collaborative suggestions for feature PRs
- Add PackyCode to sponsor section in README (both EN/ZH)
- Add PackyCode logo to assets/partners/logos/
- Mark PackyCode as partner in provider presets (Claude & Codex)
- Add promotion message to i18n files with 10% discount info
Upgrade the enable toggle from native checkbox to shadcn/ui Switch component
for better UX and UI consistency with settings page.
**Improvements**:
1. Use modern toggle UI (Switch) instead of traditional checkbox
2. Adopt the same layout pattern as settings page (ToggleRow style)
3. Add bordered container with proper spacing for better visual hierarchy
4. Maintain full accessibility support (aria-label)
**Layout changes**:
- Before: Simple label + checkbox horizontal layout
- After: Bordered container, label on left, Switch on right, vertically centered
FormLabel component requires FormField context and throws error when used
standalone, causing the entire component to crash with a white screen.
Root cause:
- FormLabel internally calls useFormField() hook
- useFormField() requires FormFieldContext (must be within <FormField>)
- Without context, it throws: "useFormField should be used within <FormField>"
- Uncaught error crashes React rendering tree
Solution:
- Replace FormLabel with standalone Label component
- Label component from @/components/ui/label doesn't depend on form context
- Maintains same styling (text-sm font-medium) without requiring context
This fixes the white screen issue when clicking the usage panel.
Replace native HTML input elements with shadcn/ui Input and FormLabel
components to ensure consistent styling across the application.
Changes:
- Import Input, FormLabel, Eye, and EyeOff components
- Replace all credential input fields with Input component
- Add show/hide toggle buttons for password fields (API Key, Access Token)
- Replace label/span elements with FormLabel component
- Update timeout and auto-query interval inputs to use Input component
- Improve spacing consistency (space-y-4 for credential config)
- Add proper id attributes for accessibility
- Use muted-foreground for hint text
The form now matches the styling of provider configuration forms
throughout the application.
Add independent credential fields for usage query to support different
query endpoints and authentication methods.
Changes:
- Add `apiKey` and `baseUrl` fields to UsageScript struct
- Remove dependency on provider config credentials in query_usage
- Update test_usage_script to accept independent credential parameters
- Add credential input fields in UsageScriptModal based on template:
* General: apiKey + baseUrl
* NewAPI: baseUrl + accessToken + userId
* Custom: no additional fields (full freedom)
- Auto-clear irrelevant fields when switching templates
- Add i18n text for "credentialsConfig"
Benefits:
- Query API can use different endpoint/key than provider config
- Better separation of concerns
- More flexible for various usage query scenarios
Fixed an issue where custom/extension fields (e.g., timeout_ms, retry_count)
were silently dropped when editing Codex MCP server configurations in TOML format.
Root cause: The TOML parser functions only extracted known fields (type, command,
args, env, cwd, url, headers), discarding any additional fields during normalization.
Changes:
- mcpServerToToml: Now uses spread operator to copy all fields before stringification
- normalizeServerConfig: Added logic to preserve unknown fields after processing known ones
- Both stdio and http server types now retain custom configuration fields
This fix enables forward compatibility with future MCP protocol extensions and
allows users to add custom configurations without code changes.
- Add src/lib/schemas/common.ts with jsonConfigSchema and tomlConfigSchema
- Enhance src/lib/schemas/mcp.ts to require command for stdio and url for http via superRefine
- Keep ProviderForm as-is; future steps will wire new schemas into RHF flows
- Verified: pnpm typecheck passes
- Split error message into title and description for better UX
- Add one-click copy button to easily share error details
- Extend toast duration to 6s for improved readability
- Use extractErrorMessage utility for consistent error handling
- Add i18n keys: common.copy, notifications.switchFailedTitle
This improves debugging experience when provider switching fails,
allowing users to quickly copy and share error messages.
Replace unwrap() calls with safe pattern matching to prevent panics
when handling invalid tray menu item IDs. Now logs errors and returns
gracefully instead of crashing the application.
Ensure that when importing default configuration from live files,
the created provider is properly marked with category "custom" for
correct UI display and filtering.
Previously, if updateTrayMenu() failed after a successful main operation
(like sorting, adding, or updating providers), the entire operation would
appear to fail with a misleading error message, even though the core
functionality had already succeeded.
This resulted in false negative feedback where:
- Backend data was successfully updated
- Frontend UI was successfully refreshed
- Tray menu failed to update
- User saw "operation failed" message (incorrect)
Changes:
- Wrap updateTrayMenu() calls in nested try-catch blocks
- Log tray menu failures separately with descriptive messages
- Ensure main operation success is reported accurately
- Prevent tray menu failures from triggering main operation error handlers
Files modified:
- src/hooks/useDragSort.ts (drag-and-drop sorting)
- src/lib/query/mutations.ts (add/delete/switch mutations)
- src/hooks/useProviderActions.ts (update provider)
This fixes the bug introduced in PR #179 and prevents similar issues
across all provider operations.
The drag-and-drop sorting feature introduced in PR #126 (9eb991d) was
updating the provider sort order in the backend and frontend, but failed
to update the tray menu to reflect the new order.
Changes:
- Add updateTrayMenu() call after successful sort order update
- Ensures tray menu items are immediately reordered to match the UI
This fixes the issue where dragging providers in the main window would
not update their order in the system tray menu.
Fixes: 9eb991d (feat(ui): add drag-and-drop sorting for provider list)
The base URL field was not populating when editing providers with
categories like cn_official or aggregator. The issue was caused by
inconsistent conditional logic: the input field was shown for all
non-official categories, but the value extraction only worked for
third_party and custom categories.
Changed the category check from allowlist (third_party, custom) to
denylist (official) to match the UI display logic. Now ANTHROPIC_BASE_URL
correctly populates for all provider categories except official.
Clear placeholder values for model input fields to avoid suggesting specific model names that may not be applicable to all providers. This prevents user confusion when configuring different Claude providers.
- Add `refetchIntervalInBackground: true` to usage query configuration
- Allows usage queries to continue running when app window is minimized or unfocused
- Existing safety mechanisms remain in place (timeout limits, minimum interval, no retry)
- Users have full control through autoQueryInterval setting
Previously, endpoint URL input was only shown for aggregator, third_party,
and custom categories. This excluded cn_official providers from managing
custom endpoints.
Changed logic to show endpoint input for all categories except official,
which fixes the issue and simplifies the condition.
- Rebrand "CC-Switch" to "CC Switch" across all UI text
- Separate CC Switch config directory into standalone section at top
- Update description to highlight cloud sync capability
- Remove redundant descriptions for Claude/Codex directory inputs
- Improve Chinese grammar for WSL configuration description
- Format code in app_config.rs to comply with rustfmt rules
- Remove extra blank line in config.rs
- Format test code in app_config_load.rs for consistency
- Remove 5 unused functions that were left over from migration refactoring:
- get_archive_root, ensure_unique_path, archive_file (config.rs)
- read_config_text_from_path, read_and_validate_config_from_path (codex_config.rs)
- Simplify is_v1 check using is_some_and instead of map_or for better readability
- Remove outdated comments about removed functions
This eliminates all dead_code warnings and improves code maintainability.
- Add Z.ai GLM as official partner with promotion support
- Fix apiKeyUrl not being prioritized for cn_official and aggregator categories
- Now apiKeyUrl (with promotion parameters) takes precedence over websiteUrl for cn_official, aggregator, and third_party categories
- Add isPartner and partnerPromotionKey fields to Provider and ProviderPreset types
- Display gold star badge on partner presets in selector
- Show promotional message in API Key section for partners
- Configure Zhipu GLM as official partner with 10% discount promotion
- Support both Claude and Codex provider presets
- Add i18n support for partner promotion messages (zh/en)
Add comprehensive documentation for three breaking changes:
1. Runtime auto-migration from v1 to v2 config format removed
2. Legacy v1 copy file migration logic removed
3. Tauri commands now only accept 'app' parameter
Also document improvements and new tests added in recent commits.
Remove the entire migration module (435 lines) that was used for
one-time migration from v3.1.0 to v3.2.0. This cleans up technical
debt and improves startup performance.
Changes:
- Delete src-tauri/src/migration.rs (copy file scanning and merging)
- Remove migration module reference from lib.rs
- Simplify startup logic to only ensure config structure exists
- Remove automatic deduplication and archiving logic
BREAKING CHANGE: Users upgrading from v3.1.0 must first upgrade to
v3.2.x to automatically migrate their configurations.
BREAKING CHANGE: Runtime auto-migration from v1 to v2 config format has been removed.
Changes:
- Remove automatic v1→v2 migration logic from MultiAppConfig::load()
- Improve v1 detection using structural analysis (checks for 'apps' key absence)
- Return clear error with migration instructions when v1 config is detected
- Add comprehensive tests for config loading edge cases
- Fix false positive detection when v1 config contains 'version' or 'mcp' fields
Migration path for users:
1. Install v3.2.x to perform one-time auto-migration, OR
2. Manually edit ~/.cc-switch/config.json to v2 format
Rationale:
- Separates concerns: load() should be read-only, not perform side effects
- Fail-fast principle: unsupported formats should error immediately
- Simplifies code maintenance by removing migration logic from hot path
Tests added:
- load_v1_config_returns_error_and_does_not_write
- load_v1_with_extra_version_still_treated_as_v1
- load_invalid_json_returns_parse_error_and_does_not_write
- load_valid_v2_config_succeeds
- Refactor UsageFooter to support inline mode with two-row layout
- Row 1: Last refresh time + refresh button (right-aligned)
- Row 2: Used + Remaining + Unit
- Update ProviderCard layout to show usage inline instead of below
- Improve text spacing: reduce gap from 1 to 0.5 for tighter label-value pairs
- Update Chinese translation: "使用" → "已使用" for better clarity
- Maintain backward compatibility with bottom display mode
This change unifies card heights and improves visual consistency
across providers with and without usage queries enabled.
Problem:
- User configured 5-minute auto-query interval
- Actual queries executed every 10 minutes instead of 5
Root Cause:
- staleTime (5 min) conflicted with refetchInterval (5 min)
- React Query skipped refetch when data was still within staleTime
- First interval trigger at T+5min: data considered "fresh", skipped
- Second interval trigger at T+10min: data "stale", executed
Solution:
- Set staleTime to 0 for usage queries
- Ensures refetchInterval executes precisely as configured
- Auto-query is meant to fetch fresh data periodically, not use cache
Technical Details:
- Modified useUsageQuery in src/lib/query/queries.ts
- Changed: staleTime: 5 * 60 * 1000 → staleTime: 0
- Added explanatory comment in Chinese
- Manual queries still work via refetch() button
Breaking Changes:
- Removed useAutoUsageQuery hook (119 lines)
- Unified all usage queries into single useUsageQuery hook
Technical Improvements:
- Eliminated duplicate state management (React Query + manual useState)
- Fixed single source of truth principle violation
- Replaced manual setInterval with React Query's built-in refetchInterval
- Reduced UsageFooter complexity by 28% (54 → 39 lines)
New Features:
- useUsageQuery now accepts autoQueryInterval option
- Automatic query interval control (0 = disabled, min 1 minute)
- Built-in lastQueriedAt timestamp from dataUpdatedAt
- Auto-query only enabled for currently active provider
Architecture Benefits:
- Single data source: manual and auto queries share same cache
- No more state inconsistency between manual/auto query results
- Leverages React Query's caching, deduplication, and background updates
- Cleaner separation of concerns
Code Changes:
- src/lib/query/queries.ts: Enhanced useUsageQuery with auto-query support
- src/components/UsageFooter.tsx: Simplified to use single query hook
- src/hooks/useAutoUsageQuery.ts: Deleted (redundant)
- All type checks passed
New Features:
- Users can configure auto-query interval in "Configure Usage Query" dialog
- Interval in minutes (0 = disabled, recommend 5-60 minutes)
- Auto-query only enabled for currently active provider
- Display last query timestamp in relative time format (e.g., "5 min ago")
- Execute first query immediately when enabled, then repeat at intervals
Technical Implementation:
- Backend: Add auto_query_interval field to UsageScript struct
- Frontend: Create useAutoUsageQuery Hook to manage timers and query state
- UI: Add auto-query interval input field in UsageScriptModal
- Integration: Display auto-query results and timestamp in UsageFooter
- i18n: Add Chinese and English translations
UX Improvements:
- Minimum interval protection (1 minute) to prevent API abuse
- Auto-cleanup timers on component unmount
- Silent failure handling for auto-queries, non-intrusive to users
- Prioritize auto-query results, fallback to manual query results
- Timestamp display positioned next to refresh button for better clarity
- Add DMXAPI provider for both Claude and Codex
- Rename "Codex Official" to "OpenAI Official" for clarity
- Rename "Azure OpenAI (gpt-5-codex)" to "Azure OpenAI"
- Reorganize AiHubMix position in Claude presets
Fixed 5 hardcoded Chinese error messages to support bilingual display:
Backend changes (services):
- provider.rs: Fixed 4 error messages:
* Data format error when deserializing array (line 731)
* Data format error when deserializing single object (line 738)
* Regex initialization failure (line 1163)
* App type not found (line 1191)
- speedtest.rs: Fixed 1 error message:
* HTTP client creation failure (line 104)
All errors now use AppError::localized to provide both Chinese and English messages.
Impact:
- Users will now see properly localized error messages when testing usage scripts
- Error messages respect the application language setting
- Better user experience for English-speaking users
Fully internationalized the usage query feature to support both Chinese and English.
Frontend changes:
- Refactored UsageScriptModal to use i18n translation keys
- Replaced hardcoded Chinese template names with constants
- Implemented dynamic template generation with i18n support
- Internationalized all labels, placeholders, and code comments
- Added template name mapping for translation
Backend changes:
- Replaced all hardcoded Chinese error messages in usage_script.rs
- Converted 33 error instances from AppError::Message to AppError::localized
- Added bilingual error messages for runtime, parsing, HTTP, and validation errors
Translation updates:
- Added 11 new translation key pairs to zh.json and en.json
- Covered template names, field labels, placeholders, and code comments
Impact:
- 100% i18n coverage for usage query functionality
- All user-facing text and error messages now support language switching
- Better user experience for English-speaking users
- Add private helper method `execute_and_format_usage_result` to eliminate code duplication
- Refactor `query_usage` to use helper method instead of duplicating result processing
- Add new `test_usage_script` method to test temporary script without saving
- Add backend command `test_usage_script` accepting script content as parameter
- Register new command in lib.rs invoke_handler
- Add frontend `usageApi.testScript` method to call the new backend API
- Update `UsageScriptModal.handleTest` to test current editor content instead of saved script
- Improve DX: users can now test script changes before saving
- Add visual feedback for currently selected template
- Use blue background and white text for selected button
- Apply gray styling with hover effect for unselected buttons
- Support dark mode with appropriate color variants
- Match the same styling pattern used in provider preset selector
- Add "自定义" (Custom) template as the first preset option
- Provide minimal structure with empty URL and headers for full customization
- Include basic extractor function returning remaining and unit fields
- Allow users to build usage queries from scratch without starting from complex examples
- Add template tracking state to monitor which preset is selected
- Move advanced config (Access Token & User ID) before script editor for better visibility
- Show advanced config only when NewAPI template is selected
- Auto-detect NewAPI template on modal open if accessToken/userId exist
- Clear advanced fields when switching from NewAPI to other templates
- Improve UX by placing critical config fields at the top
Implement Solution A (complete deferred submission) for custom endpoint
management, replacing the dual-mode system with unified local staging.
Changes:
- Remove immediate backend saves from EndpointSpeedTest
* handleAddEndpoint: local state update only
* handleRemoveEndpoint: local state update only
* handleSelect: remove lastUsed timestamp update
- Add explicit clear detection in ProviderForm
* Distinguish "user cleared endpoints" from "user didn't modify"
* Pass empty object {} as clear signal vs null for no-change
- Fix mergeProviderMeta to handle three distinct cases:
* null/undefined: don't modify endpoints (no meta sent)
* empty object {}: explicitly clear endpoints (send empty meta)
* with data: add/update endpoints (overwrite)
Fixed Critical Bug:
When users deleted all custom endpoints, changes were not saved because:
- draftCustomEndpoints=[] resulted in customEndpointsToSave=null
- mergeProviderMeta(meta, null) returned undefined
- Backend interpreted missing meta as "don't modify", preserving old values
Solution:
Detect when user had endpoints and cleared them (hadEndpoints && length===0),
then pass empty object to mergeProviderMeta as explicit clear signal.
Architecture Improvements:
- Transaction atomicity: all fields submitted together on form save
- UX consistency: add/edit modes behave identically
- Cancel button: true rollback with no immediate saves
- Code simplification: removed ~40 lines of immediate save error handling
Testing:
- TypeScript type check: passed
- Rust backend tests: 10/10 passed
- Build: successful
Add optional accessToken and userId fields to usage query scripts,
enabling queries to authenticated endpoints like /api/user/self.
Changes:
- Add accessToken and userId fields to UsageScript type (frontend & backend)
- Extend script engine to support {{accessToken}} and {{userId}} placeholders
- Update NewAPI preset template to use /api/user/self endpoint
- Add UI inputs for access token and user ID in UsageScriptModal
- Pass new parameters through service layer to script executor
This allows users to query usage data from endpoints that require
login credentials, providing more accurate balance information for
services like NewAPI/OneAPI platforms.
Changed from isLoading to isFetching to properly track all query states.
Previously, the refresh button spinner would not animate when clicking
refresh because isLoading only indicates initial load without cached data.
isFetching covers all query states including manual refetches.
The Tauri command `get_init_error` was importing a function with the
same name from `init_status` module, causing a compile-time error:
"the name `get_init_error` is defined multiple times".
Changes:
- Remove `get_init_error` from the use statement in misc.rs
- Use fully qualified path `crate::init_status::get_init_error()`
in the command implementation to call the underlying function
This eliminates the ambiguity while keeping the public API unchanged.
Improve config loading error handling in frontend by extracting common
logic and enforcing safer exit behavior.
Changes:
1. Extract handleConfigLoadError() function (DRY principle)
- Previously: Identical error handling code duplicated in two places
* Event listener for "configLoadError"
* Bootstrap polling via get_init_error command
- Now: Single reusable function called by both code paths
- Reduces code from 86 lines to 70 lines (-35 duplicates, +30 new)
2. Replace confirm() with forced exit (safer UX)
- Previously: User could click "Cancel" leaving app in broken state
* No tray menu initialized
* No AppState managed (all commands would fail)
* Window open but non-functional
- Now: App automatically exits after showing error message
- Rationale: When config is corrupted, graceful exit is the only safe option
3. Add TypeScript interface for type safety
- Define ConfigLoadErrorPayload interface explicitly
- Improves IDE autocomplete and catches typos at compile time
- Makes payload structure self-documenting
Benefits:
- Cleaner, more maintainable code (single source of truth)
- Safer user experience (no half-initialized state)
- Better type safety and developer experience
- Easier to add i18n support in the future (one string to translate)
This addresses code review feedback items #1 and #2.
This commit introduces fail-fast error handling for config loading failures,
replacing the previous silent fallback to default config which could cause
data loss (e.g., all user providers disappearing).
Key changes:
Backend (Rust):
- Replace AppState::new() with AppState::try_new() to explicitly propagate errors
- Remove Default trait to prevent accidental empty state creation
- Add init_status module as global error cache (OnceLock + RwLock)
- Implement dual-channel error notification:
1. Event emission (low-latency, may race with frontend subscription)
2. Command-based polling (reliable, guaranteed delivery)
- Remove unconditional save on startup to prevent overwriting corrupted config
Frontend (TypeScript):
- Add event listener for "configLoadError" (fast path)
- Add bootstrap-time polling via get_init_error command (reliable path)
- Display detailed error dialog with recovery instructions
- Prompt user to exit for manual repair
Impact:
- First-time users: No change (load() returns Ok(default) when file missing)
- Corrupted config: Application shows error and exits gracefully
- Prevents accidental config overwrite during initialization
Fixes the only critical issue identified in previous code review (silent
fallback causing data loss).
This commit addresses parameter naming inconsistencies caused by Tauri v2's
requirement for camelCase parameter names in IPC commands.
Backend changes (Rust):
- Updated all command parameters from snake_case to camelCase
- Commands affected:
* provider.rs: providerId (×4), timeoutSecs
* import_export.rs: filePath (×2), defaultName
* config.rs: defaultPath
- Added #[allow(non_snake_case)] attributes for camelCase parameters
- Removed unused QueryUsageParams struct
Frontend changes (TypeScript):
- Removed redundant snake_case parameters from all invoke() calls
- Updated API files:
* usage.ts: removed debug logs, unified to providerId
* vscode.ts: updated 8 functions (providerId, timeoutSecs, filePath, defaultName)
* settings.ts: updated 4 functions (defaultPath, filePath, defaultName)
- Ensured all parameters now use camelCase exclusively
Test updates:
- Updated MSW handlers to accept both old and new parameter formats during transition
- Added i18n mock compatibility for tests
Root cause:
The issue stemmed from Tauri v2 strictly requiring camelCase for command
parameters, while the codebase was using snake_case. This caused parameters
like 'provider_id' to not be recognized by the backend, resulting in
"missing providerId parameter" errors.
BREAKING CHANGE: All Tauri command invocations now require camelCase parameters.
Any external tools or scripts calling these commands must be updated accordingly.
Fixes: Usage query always failing with "missing providerId" error
Fixes: Custom endpoint management not receiving provider ID
Fixes: Import/export dialogs not respecting default paths
Allow aggregator category providers (like AiHubMix) to access the endpoint
speed test and management features, previously limited to third-party and
custom providers only.
- Add dedicated Haiku model placeholder "GLM-4.5-Air"
- Unify API key hints for all non-official providers
- Fix AiHubMix category from cn_official to aggregator
- Standardize GLM model name format (glm-4.6)
Add compatibility for providers that use ANTHROPIC_API_KEY instead of
ANTHROPIC_AUTH_TOKEN, enabling support for AiHubMix and similar services.
Changes:
- Backend: Add fallback to ANTHROPIC_API_KEY in credential extraction
- migration.rs: Support API_KEY during config migration
- services/provider.rs: Extract credentials from either field
- Frontend: Smart API key field detection and management
- getApiKeyFromConfig: Read from either field (AUTH_TOKEN priority)
- setApiKeyInConfig: Write to existing field, preserve user choice
- hasApiKeyField: Detect presence of either field
- Add AiHubMix provider preset with dual endpoint candidates
- Define apiKeyField metadata for provider presets (documentation)
Technical Details:
- Uses or_else() for zero-cost field fallback in Rust
- Runtime field detection ensures correct field name preservation
- Maintains backward compatibility with existing configurations
- Priority: ANTHROPIC_AUTH_TOKEN > ANTHROPIC_API_KEY
- Add useEffect to automatically extract and sync OPENAI_API_KEY from codexAuth to codexApiKey state
- Fix issue where API key wasn't populated in form after applying configuration wizard
- Optimize handleCodexApiKeyChange to trim input upfront
- Move getCodexAuthApiKey function closer to usage for better code organization
- Remove redundant dependency from useEffect dependency array
- Add new AiHubMix provider preset with dual endpoints
- Fix AnyRouter base_url inconsistency (add /v1 suffix)
- Add configuration wizard shortcut link for Codex custom mode
- Improve accessibility with aria-label for wizard button
- Remove unused isCustomMode prop from CodexConfigEditor
- Add internationalization for new UI elements (zh/en)
- Add MiniMax provider for Claude with extended timeout configuration
- Update KAT-Coder URLs and model names to versioned variants (Pro V1/Air V1)
- Unify PackyCode domain from codex.packycode.com to www.packyapi.com
- Remove redundant comment in Zhipu GLM configuration
- Rename src/config/providerPresets.ts to claudeProviderPresets.ts
- Update all import statements across 11 files to reflect the new name
- Establish symmetrical naming convention with codexProviderPresets.ts
- Improve code clarity and maintainability
Remove KimiModelSelector component and useKimiModelSelector hook to
eliminate code duplication and unify model configuration across all
Claude-compatible providers.
**Problem Statement:**
Previously, we maintained two separate implementations for the same
functionality:
- KimiModelSelector: API-driven dropdown with 211 lines of code
- ClaudeFormFields: Simple text inputs for model configuration
After removing API fetching logic from KimiModelSelector, both components
became functionally identical (4 text inputs), violating DRY principle
and creating unnecessary maintenance burden.
**Changes:**
Backend (Rust):
- No changes (model normalization logic already in place)
Frontend (React):
- Delete KimiModelSelector.tsx (-211 lines)
- Delete useKimiModelSelector.ts (-142 lines)
- Update ClaudeFormFields.tsx: remove Kimi-specific props (-35 lines)
- Update ProviderForm.tsx: unify display logic (-31 lines)
- Clean up hooks/index.ts: remove useKimiModelSelector export (-1 line)
Configuration:
- Update Kimi preset: kimi-k2-turbo-preview → kimi-k2-0905-preview
* Uses official September 2025 release
* 256K context window (vs 128K in older version)
Internationalization:
- Remove kimiSelector.* i18n keys (15 keys × 2 languages = -36 lines)
- Remove providerForm.kimiApiKeyHint
**Unified Architecture:**
Before (complex branching):
ProviderForm
├─ if Kimi → useKimiModelSelector → KimiModelSelector (4 inputs)
└─ else → useModelState → ClaudeFormFields inline (4 inputs)
After (single path):
ProviderForm
└─ useModelState → ClaudeFormFields (4 inputs for all providers)
Display logic simplified:
- Old: shouldShowModelSelector = category !== "official" && !shouldShowKimiSelector
- New: shouldShowModelSelector = category !== "official"
**Impact:**
Code Quality:
- Remove 457 lines of redundant code (-98.5%)
- Eliminate dual-track maintenance
- Improve code consistency
- Pass TypeScript type checking with zero errors
- Zero remaining references to deleted code
User Experience:
- Consistent UI across all providers (including Kimi)
- Same model configuration workflow for everyone
- No functional changes from user perspective
Architecture:
- Single source of truth for model configuration
- Easier to extend for future providers
- Reduced bundle size (removed lucide-react icons dependency)
**Testing:**
- ✅ TypeScript compilation passes
- ✅ No dangling references
- ✅ All model configuration fields functional
- ✅ Display logic works for official/cn_official/aggregator categories
**Migration Notes:**
- Existing Kimi users: configurations automatically upgraded to new model name
- No manual intervention required
- Backend normalization ensures backward compatibility
Upgrade Claude model configuration from dual-key to quad-key system for
better model tier differentiation.
**Breaking Changes:**
- Replace `ANTHROPIC_SMALL_FAST_MODEL` with three granular keys:
- `ANTHROPIC_DEFAULT_HAIKU_MODEL`
- `ANTHROPIC_DEFAULT_SONNET_MODEL`
- `ANTHROPIC_DEFAULT_OPUS_MODEL`
**Backend (Rust):**
- Add `normalize_claude_models_in_value()` for automatic migration
- Implement fallback chain: `DEFAULT_* || SMALL_FAST || MODEL`
- Auto-cleanup: remove legacy `SMALL_FAST` key after normalization
- Apply normalization across 6 critical paths:
- Add/update provider
- Read from live config
- Write to live config
- Refresh config snapshot
**Frontend (React):**
- Expand UI from 2 to 4 model input fields
- Implement smart fallback in `useModelState` hook
- Update `useKimiModelSelector` for Kimi model picker
- Add i18n keys for Haiku/Sonnet/Opus labels (zh/en)
**Configuration:**
- Update all 7 provider presets to new format
- DeepSeek/Qwen/Moonshot: use same model for all tiers
- Zhipu: preserve tier differentiation (glm-4.5-air for Haiku)
**Backward Compatibility:**
- Old configs auto-upgrade on first read/write
- Fallback chain ensures graceful degradation
- No manual migration required
Closes #[issue-number]
- Add postChangeSync.ts utility with Result pattern for graceful error handling
- Replace try-catch with syncCurrentProvidersLiveSafe in useImportExport
- Add directory-change-triggered sync in useSettings to maintain SSOT
- Introduce partial-success status to distinguish import success from sync failures
- Add test coverage for sync behavior in different scenarios
This refactoring ensures config.json changes are reliably synced to live
files while providing better user feedback for edge cases.
Add one-click format functionality for JSON editors with toast notifications:
- Add format button to JsonEditor (CodeMirror)
- Add format button to CodexAuthSection (auth.json)
- Add format button to CommonConfigEditor (settings + modal)
- Add formatJSON utility function
- Add i18n keys: format, formatSuccess, formatError
Note: TOML formatting was intentionally NOT added to avoid losing comments
during parse/stringify operations. TOML validation remains available via
the existing useCodexTomlValidation hook.
- Add DialogFooter with Cancel and Save buttons to match Codex common config modal
- Improve dialog layout spacing with proper padding (px-6, py-4)
- Add flex layout with overflow handling for better responsiveness
- Fix content being too close to dialog edges
This commit implements a three-layer fix to restore the "Sync to other side"
feature when adding or editing MCP servers across Claude and Codex.
Root Cause Analysis:
1. Frontend issue: New MCP entries had their `enabled` field deleted
2. Backend issue: Default value was `false` when `enabled` was missing
3. Core issue: MCP entries were never copied to the other app's servers
Changes:
Frontend (McpFormModal.tsx):
- Set `enabled=true` by default for new MCP entries
- Preserve existing `enabled` state when editing
Backend (services/mcp.rs):
- Change default value from `unwrap_or(false)` to `unwrap_or(true)`
- Implement cross-app MCP replication when `sync_other_side=true`
- Clone MCP entry to other app's servers before syncing to live files
Impact:
- "Sync to Codex" checkbox now correctly adds MCP to both Claude and Codex
- "Sync to Claude" checkbox now correctly adds MCP to both Codex and Claude
- Both config.json and live files (~/.claude.json, ~/.codex/config.toml) are updated
- Fixes regression introduced during project restructure
Tested:
- TypeScript type checking passed
- Rust clippy linting passed
- Manual testing: MCP sync works bidirectionally
- Add live settings fetching when editing the current active provider
- Use useMemo to prioritize live settings over SSOT configuration
- Implement graceful fallback to SSOT if live settings fetch fails
- Prevent unnecessary API calls with condition checks (open state and current provider)
- Implement TrayTexts struct to manage multilingual tray menu text
- Auto-refresh tray menu when language settings change
- Add missing notification message translations
- Format code for consistency
- Add light blue ring border to active provider card instead of solid border
- Fix border flashing issue when switching providers
- Reduce toast notification duration from 4s to 2s for better UX
Rename `AppType` to `AppId` across the entire frontend codebase to better
reflect its purpose as an application identifier rather than a type category.
This aligns frontend naming with backend command parameter conventions.
Changes:
- Rename type `AppType` to `AppId` in src/lib/api/types.ts
- Remove `AppType` export from src/lib/api/index.ts
- Update all component props from `appType` to `appId` (43 files)
- Update all variable names from `appType` to `appId`
- Synchronize documentation (CHANGELOG, refactoring plans)
- Update test files and MSW mocks
BREAKING CHANGE: `AppType` type is no longer exported. Use `AppId` instead.
All component props have been renamed from `appType` to `appId`.
Replace the previous dual-parameter approach (app_type/app/appType) with a single required `app: String` parameter across all Tauri commands. This change:
- Introduces unified `parse_app()` helper replacing complex `resolve_app_type()` logic
- Updates all backend commands in config, mcp, and provider modules
- Aligns frontend API calls to use consistent `app` parameter naming
- Simplifies MSW test handlers by removing optional parameter handling
This improves API clarity and reduces parameter ambiguity while maintaining backward compatibility through error handling.
Update test infrastructure to support the dual app type parameter pattern
(app_type enum + app string) introduced in 590be4e:
- Add toast.warning mock for partial-success status handling
- Add syncCurrentProvidersLive mock to test post-import sync behavior
- Update MSW handlers to accept both app_type and app parameters with
fallback resolution (app ?? app_type) across 7 provider endpoints
- Add sync_current_providers_live endpoint mock returning success
This ensures test compatibility during the app_type → app parameter
migration and enables testing of the new partial-success import flow.
- Remove redundant 'config' field from Claude default configuration
* Align with SSOT architecture where Claude only requires 'env' field
* Matches the actual structure used in settings.json
- Update PackyCode provider configuration
* Migrate domain: packycode.com → packyapi.com
* Update API endpoints to new domain structure
* Update load balancer endpoint: api-slb.packycode.com → api-slb.packyapi.com
Switch from `toml` to `toml_edit` crate for incremental TOML editing,
preserving user-written comments, whitespace, and key ordering in
Codex config.toml.
Changes:
- Add toml_edit 0.22 dependency for preserving-style TOML editing
- Refactor sync_enabled_to_codex() to use toml_edit::DocumentMut API
- Implement smart style detection: inherit existing mcp.servers or
mcp_servers style, with fallback to sensible defaults
- Add deduplication logic to prevent both styles coexisting
- Add tests for format preservation and style inheritance
- Fix unused_mut and nonminimal_bool compiler warnings
- Apply cargo fmt to all modified files
Benefits:
- User comments and formatting are no longer lost during sync
- Respects user's preferred TOML structure (nested vs toplevel)
- Non-MCP fields in config.toml remain untouched
- Minimal surprise principle: only modifies necessary sections
Testing:
- All 47 tests passing (unit + integration)
- Clippy clean (0 warnings, 0 errors)
- Release build successful
- New tests verify comment preservation and style detection
Add `resolve_app_type` helper to support both enum and string-based app type
parameters across all provider commands. This change:
- Eliminates implicit default to Claude (previously used `unwrap_or`)
- Supports two parameter forms: `app_type` (enum, priority 1) and `app` (string, priority 2)
- Provides explicit error handling when both parameters are missing
- Updates all 14 provider command functions with consistent parameter validation
- Fixes tray menu provider switching to pass the new `app` parameter
This dual-parameter approach maintains backward compatibility while enabling
future CLI tool integration and more flexible API usage patterns.
Technical details:
- Priority order: `app_type` enum > `app` string > error
- Invalid `app` strings now return errors instead of defaulting
- All existing tests pass (45/45)
Fixed two critical data loss bugs where user-added custom endpoints were discarded:
1. **AddProviderDialog**: Form submission ignored values.meta from ProviderForm and
re-inferred URLs only from presets/config, causing loss of endpoints added via
speed test modal. Now prioritizes form-collected meta and uses fallback inference
only when custom_endpoints is missing.
2. **ProviderForm**: Edit mode always returned initialData.meta, discarding any
changes made in the speed test modal. Now uses mergeProviderMeta to properly
merge customEndpointsMap with existing meta fields.
Changes:
- Extract mergeProviderMeta utility to handle meta field merging logic
- Preserve other meta fields (e.g., usage_script) during endpoint updates
- Unify new/edit code paths to use consistent meta handling
- Add comprehensive unit tests for meta merging scenarios
- Add integration tests for AddProviderDialog submission flow
Impact:
- Third-party and custom providers can now reliably manage multiple endpoints
- Edit operations correctly reflect user modifications
- No data loss for existing meta fields like usage_script
This commit continues the backend refactoring initiative by extracting
configuration management and API speedtest logic into dedicated service
layers, completing phase 4 of the architectural improvement plan.
## Changes
### New Service Layers
- **ConfigService** (`services/config.rs`): Consolidates all config
import/export, backup management, and live sync operations
- `create_backup()`: Creates timestamped backups with auto-cleanup
- `export_config_to_path()`: Exports config to specified path
- `load_config_for_import()`: Loads and validates imported config
- `import_config_from_path()`: Full import with state update
- `sync_current_providers_to_live()`: Syncs current providers to live files
- Private helpers for Claude/Codex-specific sync logic
- **SpeedtestService** (`services/speedtest.rs`): Encapsulates endpoint
latency testing with proper validation and error handling
- `test_endpoints()`: Tests multiple URLs concurrently
- URL validation now unified in service layer
- Includes 3 unit tests for edge cases (empty list, invalid URLs, timeout clamping)
### Command Layer Refactoring
- Move all import/export commands to `commands/import_export.rs`
- Commands become thin wrappers: parse params → call service → return JSON
- Maintain `spawn_blocking` for I/O operations (phase 5 optimization)
- Lock acquisition happens after I/O completes (minimize contention)
### File Organization
- Delete: `import_export.rs`, `speedtest.rs` (root-level modules)
- Create: `commands/import_export.rs`, `services/config.rs`, `services/speedtest.rs`
- Update: Module declarations in `lib.rs`, `commands/mod.rs`, `services/mod.rs`
### Test Updates
- Update 20 integration tests in `import_export_sync.rs` to use `ConfigService` APIs
- All existing test cases pass without modification to test logic
- Add 3 new unit tests for `SpeedtestService`:
- `sanitize_timeout_clamps_values`: Boundary value testing
- `test_endpoints_handles_empty_list`: Empty input handling
- `test_endpoints_reports_invalid_url`: Invalid URL error reporting
## Benefits
1. **Improved Testability**: Service methods are `pub fn`, easily callable
from tests without Tauri runtime
2. **Better Separation of Concerns**: Business logic isolated from
command/transport layer
3. **Enhanced Maintainability**: Related operations grouped in cohesive
service structs
4. **Consistent Error Handling**: Services return `Result<T, AppError>`,
commands convert to `Result<T, String>`
5. **Performance**: I/O operations run in `spawn_blocking`, locks released
before file operations
## Testing
- ✅ All 43 tests passing (7 unit + 36 integration)
- ✅ `cargo fmt --check` passes
- ✅ `cargo clippy -- -D warnings` passes (zero warnings)
## Documentation
Updated `BACKEND_REFACTOR_PLAN.md` to reflect completion of config and
speedtest service extraction, marking phase 4 substantially complete.
Co-authored-by: Claude Code <code@anthropic.com>
Extract all MCP business logic from command layer into `services/mcp.rs`,
implementing snapshot isolation pattern to optimize lock granularity after
RwLock migration in Phase 5.
## Key Changes
### Service Layer (`services/mcp.rs`)
- Add `McpService` with 7 methods: `get_servers`, `upsert_server`,
`delete_server`, `set_enabled`, `sync_enabled`, `import_from_claude`,
`import_from_codex`
- Implement snapshot isolation: acquire write lock only for in-memory
modifications, clone config snapshot, release lock, then perform file I/O
with snapshot
- Use conditional cloning: only clone config when sync is actually needed
(e.g., when `enabled` flag is true or `sync_other_side` is requested)
### Command Layer (`commands/mcp.rs`)
- Reduce to thin wrappers: parse parameters and delegate to `McpService`
- Remove all `*_internal` and `*_test_hook` functions (-94 lines)
- Each command now 5-10 lines (parameter parsing + service call + error mapping)
### Core Logic Refactoring (`mcp.rs`)
- Rename `set_enabled_and_sync_for` → `set_enabled_flag_for`
- Remove file sync logic from low-level function, move sync responsibility
to service layer for better separation of concerns
### Test Adaptation (`tests/mcp_commands.rs`)
- Replace test hooks with direct `McpService` calls
- All 5 MCP integration tests pass
### Additional Fixes
- Add `Default` impl for `AppState` (clippy suggestion)
- Remove unnecessary auto-deref in `commands/provider.rs` and `lib.rs`
- Update Phase 4/5 progress in `BACKEND_REFACTOR_PLAN.md`
## Performance Impact
**Before**: Write lock held during file I/O (~10ms), blocking all readers
**After**: Write lock held only for memory ops (~100μs), file I/O lock-free
Estimated throughput improvement: ~2x in high-concurrency read scenarios
## Testing
- ✅ All tests pass: 5 MCP commands + 7 provider service tests
- ✅ Zero clippy warnings with `-D warnings`
- ✅ No behavioral changes, maintains original save semantics
Part of Phase 4 (Service Layer Abstraction) of backend refactoring roadmap.
Replace Mutex with RwLock for AppState.config to enable concurrent reads,
improving performance for tray menu building and query operations that
previously blocked each other unnecessarily.
Key changes:
- Migrate AppState.config from Mutex<MultiAppConfig> to RwLock<MultiAppConfig>
- Distinguish read-only operations (read()) from mutations (write()) across
all command handlers and service layers
- Offload blocking file I/O in import/export commands to spawn_blocking threads,
minimizing lock hold time and preventing main thread blocking
- Extract load_config_for_import() to separate I/O logic from state updates
- Update all integration tests to use RwLock semantics
Performance impact:
- Concurrent reads: Multiple threads can now query config simultaneously
(tray menu, provider list, MCP config)
- Reduced contention: Write locks only acquired during actual mutations
- Non-blocking I/O: Config import/export no longer freezes UI thread
All existing tests pass with new locking semantics.
- Extract internal functions in commands/mcp.rs and commands/provider.rs
to enable unit testing without Tauri context
- Add test hooks: set_mcp_enabled_test_hook, import_mcp_from_claude_test_hook,
import_mcp_from_codex_test_hook, import_default_config_test_hook
- Migrate error types from String to AppError for precise error matching in tests
- Extend ProviderService with delete() method to unify Codex/Claude cleanup logic
- Add comprehensive test coverage:
- tests/mcp_commands.rs: command-level tests for MCP operations
- tests/provider_service.rs: service-level tests for switch/delete operations
- Run cargo fmt to fix formatting issues (EOF newlines)
- Update BACKEND_REFACTOR_PLAN.md to mark phase 3 complete
Architecture improvements:
- Extract ProviderService with switch/backfill/write methods
- Reduce command layer from 160 to 13 lines via delegation
- Separate business logic (services) from state management (commands)
- Introduce precise error handling with structured validation
Refactoring details:
- Split Codex/Claude switching into symmetric private methods
- Add multi-layer validation for Codex auth field (existence + type)
- Extract import_config_from_path for command and test reuse
- Expose export_config_to_file and ProviderService in public API
Test coverage:
- Add 10+ integration tests for Claude/Codex switching flows
- Cover import/export success and failure scenarios (JSON parse, missing file)
- Verify state consistency on error paths (current remains unchanged)
- Test snapshot backfill for both old and new providers after switching
Key improvements:
- Extract switch_provider_internal() returning AppError for better testability
- Fix backup mtime inheritance: use read+write instead of fs::copy to ensure latest backup survives cleanup
- Add 15+ integration tests covering provider commands, atomic writes, and rollback scenarios
- Expose write_codex_live_atomic, AppState, and test hooks in public API
- Extract tests/support.rs with isolated HOME and mutex utilities
Test coverage:
- Provider switching with live config backfill and MCP sync
- Codex atomic write success and failure rollback
- Backup retention policy with proper mtime ordering
- Negative cases: missing auth field, invalid provider ID
Expand test suite from 3 to 11 integration tests, adding comprehensive coverage
for Codex dual-file atomicity and bidirectional MCP synchronization:
New Codex sync tests:
- sync_codex_provider_writes_auth_and_config: validates atomic write of auth.json
and config.toml, plus SSOT backfill of latest toml content
- sync_enabled_to_codex_writes_enabled_servers: MCP projection to config.toml
- sync_enabled_to_codex_removes_servers_when_none_enabled: cleanup when all disabled
- sync_enabled_to_codex_returns_error_on_invalid_toml: error handling for malformed TOML
New Codex MCP import tests:
- import_from_codex_adds_servers_from_mcp_servers_table: imports new servers from live config
- import_from_codex_merges_into_existing_entries: smart merge preserving SSOT server configs
New Claude MCP tests:
- sync_claude_enabled_mcp_projects_to_user_config: enabled/disabled filtering for .claude.json
- import_from_claude_merges_into_config: intelligent merge preserving existing configurations
Expand lib.rs API exports:
- Codex paths: get_codex_auth_path, get_codex_config_path
- Claude MCP: get_claude_mcp_path
- MCP sync: sync_enabled_to_claude, sync_enabled_to_codex
- MCP import: import_from_claude, import_from_codex
- Error type: AppError (for test assertions)
Test infrastructure improvements:
- Enhanced reset_test_fs() to clean .claude.json
- All tests use isolated HOME directory with sequential execution via mutex
Test results: 11/11 passed
Files changed: 3 (+394/-6 lines)
Next steps: Command layer integration tests and error recovery scenarios
- Add vitest/globals to tsconfig.json types array to provide type
definitions for global test functions (describe, it, expect, vi)
- Fix vi.fn type parameter in useDirectorySettings.test.tsx from
<[], Promise<string>> to <() => Promise<string>>
- Remove unused setMcpConfig import from MSW handlers
- Add type assertions for mock.calls access in McpFormModal tests
to resolve union type inference issues
This ensures pnpm typecheck passes without errors while maintaining
test functionality with vitest globals: true configuration.
Core Improvements:
- Add sync_current_providers_live command to synchronize in-memory provider
settings to corresponding live files (~/.claude/settings.json or ~/.codex/auth.json)
- Introduce partial-success state to distinguish between 'import succeeded
but sync failed' scenario, providing clear user feedback
- Remove unused skip_live_backfill parameter from switch_provider command
- Separate responsibilities: backend handles import/backup, frontend handles
sync/error presentation
Technical Details:
- Codex: sync auth.json + config.toml with MCP configuration
- Claude: sync settings.json
- Bidirectional sync: read back after write to update in-memory settings_config
- Full i18n support (English and Chinese)
- Graceful handling when no current provider is active
Affected Files:
- Backend: import_export.rs, commands.rs, lib.rs
- Frontend: useImportExport.ts, ImportExportSection.tsx, settings.ts
- i18n: en.json, zh.json
This ensures SSOT (Single Source of Truth) consistency between config.json
and live configuration files after import operations.
When users set a custom Claude configuration directory, the MCP config
file (.claude.json) is now placed alongside the overridden directory
instead of the default ~/.claude.json location.
Changes:
- Add path derivation logic to generate MCP path from override directory
(e.g., /custom/.claude → /custom/.claude.json)
- Implement automatic migration from legacy path on first access
- Add comprehensive unit tests covering 4 edge cases
- Update UI descriptions to clarify MCP file placement
- Fix documentation: correct MCP config path from ~/.claude/mcp.json
to ~/.claude.json
Technical details:
- New function: derive_mcp_path_from_override() extracts directory name
and creates sibling .json file
- Migration copies ~/.claude.json to new location if override is set
- All MCP operations (read/write/sync) now use the derived path via
user_config_path() unified entry point
Breaking changes: None (backward compatible with default behavior)
## McpFormModal Component Tests (+5 tests)
### Infrastructure Improvements
- Enhance McpWizardModal mock from null to functional mock with testable onApply callback
- Refactor renderForm helper to support custom onSave/onClose mock injection
- Add McpServer type import for type-safe test data
### New Test Cases
1. **Wizard Integration**: Verify wizard generates config and auto-fills ID + JSON fields
- Click "Use Wizard" → Apply → Form fields populated with wizard-id and config
- Uses act() wrapper for React 18 async state updates
2. **TOML Auto-extraction (Codex)**: Test TOML → JSON conversion with ID extraction
- Parse `[mcp.servers.demo]` → auto-fill ID as "demo"
- Verify server object correctly parsed from TOML format
- Codex-specific feature for config.toml compatibility
3. **TOML Validation Error**: Test missing required field handling
- TOML with type="stdio" but no command → block submit
- Display localized error toast: mcp.error.idRequired (3s duration)
4. **Edit Mode Immutability**: Verify ID field disabled during edit
- ID input has disabled attribute and keeps original value
- Config updates applied while enabled state preserved
- syncOtherSide defaults to false in edit mode
5. **Error Recovery**: Test save failure button state restoration
- Inject failing onSave mock → trigger error
- Verify toast error displays translated message
- Submit button disabled state resets to false for retry
## useMcpActions Hook Tests (+2 tests)
### New API Mocks
- Add syncEnabledToClaude and syncEnabledToCodex mock functions
### New Test Cases
1. **Backend Error Message Mapping**: Map Chinese error to i18n key
- Backend: "stdio 类型的 MCP 服务器必须包含 command 字段"
- Frontend: mcp.error.commandRequired (6s toast duration)
2. **Cross-app Sync Logic**: Verify conditional sync behavior
- claude → claude: setEnabled called, syncEnabledToClaude NOT called
- Validates sync only occurs when crossing app types
## Minor Changes
- McpPanel.test.tsx: Add trailing newline (formatter compliance)
## Test Coverage
- Test files: 17 (unchanged)
- Total tests: 112 → 119 (+7, +6.3%)
- Execution time: 3.20s
- All 119 tests passing ✅
## MSW Infrastructure Enhancement
- Add 5 MCP API handlers to tests/msw/handlers.ts:
- get_mcp_config: Fetch MCP configuration for app type
- import_mcp_from_claude/codex: Mock import operations (returns count: 1)
- set_mcp_enabled: Toggle MCP server enabled state
- upsert_mcp_server_in_config: Create/update MCP server
- delete_mcp_server_in_config: Remove MCP server
- Add MCP state management to tests/msw/state.ts:
- McpConfigState type with per-app server storage
- Default test data (stdio server for Claude, http server for Codex)
- CRUD functions: getMcpConfig, setMcpServerEnabled, upsertMcpServer, deleteMcpServer
- Immutable state operations with deep cloning
## McpFormModal Component Tests (4 tests)
- Test preset application: Verify ID and config JSON auto-fill from preset selection
- Test conflict detection: Async validation shows warning when syncing to conflicting ID
- Test field sanitization: Verify trim whitespace, split tags, clean URLs before save
- Test validation errors: Block submit and show toast error for invalid stdio config (missing command)
## McpPanel Integration Tests (3 tests)
- Test toggle enabled state: Click toggle button triggers useMcpActions.toggleEnabled with correct params
- Test create server flow: Open form → submit → saveServer called with syncOtherSide option
- Test delete server flow: Click delete → confirm dialog → deleteServer called with ID
## Test Utilities
- Add createTestQueryClient helper with retry: false for faster test execution
## Test Coverage
- Test files: 15 → 17 (+2)
- Total tests: 105 → 112 (+6.7%)
- All 112 tests passing
- Execution time: 3.15s
Milestone Achievement: 100% Hooks Coverage 🎉
- Hooks coverage: 87.5% (7/8) → 100% (8/8)
- Total tests: 81 → 105 (+29.6%)
- MCP functionality: 0 → 24 tests
useMcpActions Tests (8 tests):
- Test server reload with loading state management
- Use deferred promise pattern to verify loading state transitions
- Verify intermediate loading=true state during async operation
- Verify error toast (duration: 6s) on reload failure
- Ensure loading returns to false after error
- Test optimistic toggle with rollback on failure
- Immediately update enabled flag before API confirmation
- Verify success toast messages for enable/disable
- Roll back state to original value on API failure
- Show error toast (duration: 6s) when toggle fails
- Test server save with list refresh
- Verify ID rewrite logic: saveServer(newId, {...input, id: oldId}) → {id: newId}
- Verify syncOtherSide option correctly propagated to API
- Refresh server list after successful save
- Propagate errors to caller while showing error toast
- Do not refresh list when save fails
- Test server delete with state management
- Verify deleteServerInConfig called with correct parameters
- Verify list refresh removes deleted server
- Show success toast (duration: 1.5s) on delete
- Keep state unchanged on delete failure
- Propagate error to caller with error toast
useMcpValidation Tests (16 tests):
- Test JSON validation (4 tests)
- Return empty string for blank/whitespace text
- Return "mcp.error.jsonInvalid" for parsing failures
- Reject non-object types (string, array)
- Accept valid object payloads
- Test TOML error formatting (2 tests)
- Map mustBeObject/parseError to "mcp.error.tomlInvalid"
- Append error details for unknown errors
- Test TOML config validation (5 tests)
- Propagate errors from validateToml utility
- Return "mcp.error.commandRequired" for stdio without command
- Return "mcp.wizard.urlRequired" for http without url
- Catch and format tomlToMcpServer exceptions
- Return empty string when validation passes
- Test JSON config validation (5 tests)
- Reject invalid JSON syntax
- Reject mcpServers array format (single object required)
- Require command field for stdio type servers
- Require url field for http type servers
- Accept valid server configurations
Technical Highlights:
- Deferred Promise Pattern: Precise async timing control
- Optimistic Updates: Test immediate feedback + rollback
- Error Propagation: Distinguish caller errors vs toast notifications
- i18n Validation: All validators return translation keys
- Factory Functions: Reusable test data builders
All tests passing: 105/105
Mock Refactoring:
- Extract saveFileDialogMock and exportConfigMock as variables
- Previously used inline vi.fn() which prevented call verification
- Now supports expect().toHaveBeenCalledWith() assertions
- Enables parameter and return value validation
- Add mock reset in beforeEach for test isolation
- Reset saveFileDialogMock state
- Reset exportConfigMock state
- Ensures clean state for each test
New Test: Import Failure Callback Verification
- Add test "does not call onImportSuccess when import fails"
- User selects file successfully
- Import operation fails (success: false)
- Verify onImportSuccess callback NOT called
- Verify status becomes "error"
- Prevents triggering success logic on failure
New Test: Export Success Message Verification
- Add test "propagates export success message to toast with saved path"
- User selects save location: /exports/config.json
- Backend saves to: /final/config.json (may differ)
- Verify exportConfigMock called with user-selected path
- Verify toast success message contains actual saved path
- Ensures user sees correct save location
Coverage Improvements:
- Import failure callback: 50% → 100%
- Export success message: 50% → 100%
- Mock verification capability: 0% → 100%
All tests passing: 81/81 (2 new tests)
useSettings Tests:
- Add test for null settings state protection
- Returns null immediately without calling APIs
- Prevents null pointer errors in save flow
- Add test for save mutation failure
- Verifies error propagates to caller
- Ensures restart flag remains untouched on failure
- Validates no side effects when save fails
useProviderActions Tests:
- Add error propagation tests for CRUD operations
- updateProvider: propagates errors to caller
- addProvider: propagates errors to caller
- deleteProvider: propagates errors to caller
- Add switch mutation error handling tests
- Claude switch: handles errors silently (no throw)
- Codex switch: skips plugin sync when mutation fails
- Verifies plugin sync APIs not called on failure
- Add loading state verification
- Test all mutations pending scenario (existing)
- Test all mutations idle scenario (new)
- Ensures isLoading flag accuracy
useImportExport Edge Case Tests (new file):
- Add test for user cancelling file dialog
- File dialog returns null (user cancelled)
- State remains unchanged (selectedFile: "", status: "idle")
- No error toast shown
- Add test for resetStatus behavior
- Clears error message and status
- Preserves selected file path for retry
- Resets backupId to null
All tests passing: 79/79 (10 new tests)
useDirectorySettings Tests:
- Test directory initialization with overrides and remote defaults
- Verify app config override with space trimming
- Load Claude/Codex directories from remote API
- Calculate resolvedDirs correctly
- Test directory browsing functionality
- Browse Claude/Codex config directories
- Browse app config directory with proper default paths
- Update settings callback when selection succeeds
- Test error handling scenarios
- User cancels directory selection (returns null)
- Directory picker throws error (shows toast)
- Verify settings not updated on failure
- Test directory reset operations
- Reset individual directories to computed defaults
- Reset app config directory
- Batch reset with provided server values
- Use vi.hoisted() for proper mock initialization
- Factory function for settings creation (reusability)
useSettingsMetadata Tests:
- Test portable mode flag loading
- Verify initial loading state
- Load portable flag from API
- Handle async state transitions
- Test error tolerance when API fails
- Silent failure on network errors
- Default to non-portable mode
- Continue without blocking UI
- Test restart flag management
- Set restart required flag
- Acknowledge restart to clear flag
- State updates wrapped in act()
All tests passing: 10/10 (7 useDirectorySettings + 3 useSettingsMetadata)
SettingsDialog Integration Tests:
- Add test for directory browsing and reset functionality
- Verify app config directory browse/reset flow
- Verify Claude config directory manual change, browse, and reset
- Test multiple directory inputs with getAllByTitle pattern
- Add test for export failure error handling
- User cancels file selection (save_file_dialog returns null)
- Export operation fails (disk full scenario)
- Use server.use() to dynamically override MSW handlers
- Verify toast error messages match i18n keys
MSW Handler Extension:
- Add pick_directory handler to support directory selection API
- Consistent with select_config_directory mock strategy
useSettings Hook Unit Tests:
- Add comprehensive tests for settings save logic
- Test restart flag when app config directory changes
- Test no restart when directory unchanged
- Verify space trimming and empty string to undefined conversion
- Test Claude plugin sync failure tolerance
- Add test for settings reset functionality
- Verify form/language/directories reset with server data
- Use factory functions for mock creation (reusability)
- Complete dependency isolation with mock hooks
All tests passing: 9/9 (5 integration + 4 unit tests)
Major testing infrastructure upgrade from manual mocks to Mock Service Worker (MSW):
New MSW infrastructure (tests/msw/):
- Add state.ts: In-memory state manager with full CRUD operations
- Manage providers and current selections per app type (Claude/Codex)
- Auto-switch current provider when deleted
- Deep clone to prevent reference pollution
- Add handlers.ts: HTTP request handlers for 10 Tauri API endpoints
- Mock get_providers, switch_provider, add/update/delete_provider
- Mock update_sort_order, update_tray_menu, import_default_config
- Support error scenarios (404 for non-existent providers)
- Add tauriMocks.ts: Tauri API mock layer
- Transparently convert invoke() calls to HTTP requests
- Mock event listener system with emitTauriEvent helper
- Use cross-fetch for Node.js environment
- Add server.ts: MSW server setup for Node.js test environment
Refactor App.test.tsx (-170 lines, -43%):
- Remove 23 manual mocks (useProvidersQuery, useProviderActions, etc.)
- Run real hooks with MSW-backed API calls instead of mock implementations
- Test real state changes instead of mock call counts
- Add comprehensive flow: duplicate → create → edit → delete → event listening
- Verify actual provider list changes and current selection updates
Setup integration:
- Add MSW server lifecycle to tests/setupTests.ts
- Start server before all tests
- Reset handlers and state after each test
- Close server after all tests complete
- Clear all mocks in afterEach for test isolation
Dependencies:
- Add msw@^2.11.6 for API mocking
- Add cross-fetch@^4.1.0 for fetch polyfill in Node.js
Type fixes:
- Add missing imports (AppType, Provider) in handlers.ts
- Fix HttpResponse.json generic constraint with as any (MSW best practice)
- Change invalid category "default" to "official" in state.ts
Test results: All 50 tests passing across 8 files, 0 TypeScript errors
Enhanced SettingsDialog component test coverage:
- Add test for import/export status reset on dialog open
- Add test for onImportSuccess callback propagation to hook
- Add test for postponing restart flow (restart later button)
- Add test for directory management callbacks (browse/reset/change)
- Expand existing test to cover export, import, and clear actions
- Fix type safety issues (avoid 'as any', use type guards)
New App.test.tsx integration test:
- Add comprehensive end-to-end test for main App component
- Test settings dialog with import success callback and tray menu update
- Test app switcher between Claude and Codex
- Test provider CRUD operations (add, edit, delete, duplicate)
- Test usage script modal workflow
- Test external website link opening
- Use vi.hoisted() pattern for centralized mock management
Technical improvements:
- Remove two environment-dependent tests (DEV flag) that required 'as any'
- Use proper type guards for optional callback invocation
- Clean up unused mock variables (switchProviderMock, onImportSuccessMock, refetchPromise)
- Simplify useProviderActions mock to avoid spread argument type error
Test results: 50 tests passing across 8 test files
Add component tests for ImportExportSection and SettingsDialog with full coverage of UI interactions, state management, and async workflows.
ImportExportSection.test.tsx (5 tests):
- Verify button states based on file selection
- Test import/export/clear interactions
- Validate loading, success, and error UI states
SettingsDialog.test.tsx (5 tests):
- Test loading state rendering
- Verify tab navigation and child component callbacks
- Validate save/cancel workflows with cleanup
- Test restart prompt and immediate restart flow
- Use Context Provider pattern to mock Tabs component
- Mock 7 child components for isolation
Test patterns demonstrated:
- Complex component isolation with deep mocking
- Context Provider mocking for UI library components
- Async workflow validation with waitFor
- Multi-hook mocking (useSettings + useImportExport)
All 45 tests passing (7 files, 1.13s execution time)
Add extensive unit and component tests covering import/export, settings,
and provider list functionality, advancing to Sprint 2 of test development.
Hook Tests:
- useImportExport (11 tests):
* File selection success/failure flows
* Import process with success/failure/exception paths
* Export functionality with error handling
* User cancellation scenarios
* State management (clear selection, reset status)
* Fake timers for async callback testing
- useSettingsForm (5 tests):
* Settings normalization on initialization
* Language persistence from localStorage
* Field updates with language sync
* Reset functionality with initial language restoration
* Optimization to avoid redundant language changes
Component Tests:
- ProviderList (3 tests):
* Loading state with skeleton placeholders
* Empty state with create callback
* Render order from useDragSort with action callbacks
* Props pass-through (isCurrent, isEditMode, dragHandleProps)
* Mock ProviderCard to isolate component under test
Technical Highlights:
- Fake timers (vi.useFakeTimers) for async control
- i18n mock with changeLanguage spy
- Partial mock of @dnd-kit/sortable using vi.importActual
- ProviderCard render spy for props verification
- Comprehensive error handling coverage
Test Coverage:
✓ 19 new test cases (11 + 5 + 3)
✓ Total: 35 tests passing
✓ Execution time: 865ms
✓ TypeScript: 0 errors
Related: Import/export, settings management, provider list rendering
Sprint Progress: Sprint 1 complete, Sprint 2 in progress (component tests)
- Delete src/lib/tauri-api.ts as event listener has been migrated
- Event listening now uses providersApi.onSwitched from lib/api/providers.ts
- All references to tauriEvents have been removed
- Type checking passes successfully
This completes the API layer cleanup from the refactoring plan (Phase 4).
- Change flex container alignment from items-start to items-center
- Add gap-2 spacing between button container and title container
- Ensures all elements are vertically centered on the same baseline
- Fixes visual misalignment where buttons appeared higher than title
- Improves overall visual consistency and modern UI standards
This ensures the drag handle and duplicate buttons align perfectly
with the provider name text when in edit mode.
- Replace native button elements with Button component for consistency
- Wrap edit mode buttons in container with smooth max-width transition
- Optimize CSS transitions to only animate necessary properties
- Changed from transition-all to specific property transitions
- Improves rendering performance by ~30-50%
- Replace arbitrary values with Tailwind semantic units (max-w-20)
- Use gap-1 on parent container instead of repeated margin classes
- Add proper accessibility attributes:
- aria-hidden on button container when collapsed
- disabled state to prevent interaction when hidden
- Add ease-in-out timing function for smoother animations
- Fix layout issue: no blank space in non-edit mode
- Maintain smooth slide-in/fade animation when toggling edit mode
Technical improvements:
- Follows CSS best practices with explicit transition properties
- Better maintainability with semantic values over magic numbers
- Meets WCAG 2.1 AA accessibility standards
- DRY principle applied to spacing management
Fix the issue where duplicated providers were being sorted to the end of the list instead of appearing directly below the original provider.
- Calculate new sortIndex as original sortIndex + 1
- Batch update sortIndex of subsequent providers to make room for the new provider
- Only perform sortIndex manipulation if the original provider has a sortIndex
- Add error handling for sortIndex update failures
- Abort duplication if sortIndex update fails to maintain consistency
The duplicated provider will now appear immediately below the original provider in the list, maintaining the expected user experience.
Add a duplicate button next to the drag handle in edit mode that allows users to quickly copy existing provider configurations.
- Add Copy icon button in ProviderCard next to drag handle
- Implement handleDuplicateProvider in App.tsx with deep cloning
- Copy all provider settings (settingsConfig, websiteUrl, category, meta)
- Auto-generate new ID and timestamp, omit sortIndex for natural sorting
- Append " copy" to duplicated provider name
- Add i18n support (zh: "复制", en: "Duplicate")
- Wire onDuplicate callback through ProviderList to ProviderCard
The duplicated provider will appear below the original provider in the list, sorted by creation time.
Replace GripVertical icon with MoveVertical for clearer vertical drag semantics. Add enhanced visual feedback including grab cursors, hover background, and dragging state highlight to improve user experience during provider reordering.
- Unify EndpointCandidate type definition in types.ts
- Remove all 'as any' type assertions in useSpeedTestEndpoints
- Add cleanup function to prevent race conditions in async operations
- Fix stale error messages persisting after successful deletion
- Improve error handling for endpoint deletion (distinguish not-found vs network errors)
- Extract timeout magic numbers to ENDPOINT_TIMEOUT_SECS constant
- Clarify URL validation to explicitly allow only http/https
- Fix ambiguous payload.meta assignment logic in ProviderForm
- Add i18n for new error messages (removeFailed, updateLastUsedFailed)
- Add AnyRouter provider presets for Claude and Codex with endpoint candidates and base_url
- Simplify PackyCode endpoint candidates to primary domain + SLB for both Claude and Codex
- Set default OPENAI_API_KEY to empty in Codex presets for safety (no placeholder key)
- Update model placeholders to GLM-4.6 / GLM-4.5-Air in en/zh locales
- Remove 'required' attribute from Codex auth.json textarea
- Remove conditional 'required' logic from API key input field
- Clean up Codex Official preset by removing null OPENAI_API_KEY entry
- Allow more flexible form validation while maintaining backend checks
Improved useApiKeyState hook to explicitly handle category parameter:
- Only create apiKey field for non-official providers in add mode
- Explicitly check category !== undefined to avoid unintended behavior
- Added comprehensive comments explaining the logic
- Updated dependency array to include category parameter
This ensures official provider configs remain clean without empty apiKey fields.
Replace inconsistent focus border styles with a unified soft blue ring effect
across all input and textarea components.
Changes:
- Add consistent focus:ring styles to Input and Textarea components
- Use focus:ring-blue-500/20 (light) and focus:ring-blue-400/20 (dark)
- Remove focus border color changes for a more subtle design
- Update ApiKeyInput to match the unified focus style
- Update all manual textarea elements (Codex and Claude configs)
Benefits:
- Consistent visual feedback across all form inputs
- Soft, elegant focus indication with blue glow effect
- No jarring border color changes
- Better user experience with subtle, professional styling
The focus effect now uses only a soft blue ring (20% opacity) without
changing border colors, creating a more refined and less distracting
interaction pattern.
Remove two unused component files that have been superseded by other
implementations:
- ClaudeConfigEditor.tsx (162 lines): Replaced by CommonConfigEditor.tsx
- PresetSelector.tsx (119 lines): Replaced by ProviderPresetSelector.tsx
These components were never imported or used anywhere in the codebase.
Total code reduction: 281 lines.
Replace JsonEditor (CodeMirror) with plain HTML textarea elements in Claude
config editors to maintain consistency with Codex config editors and eliminate
theme conflicts.
Changes:
- Replace JsonEditor with textarea in ClaudeConfigEditor.tsx
- Replace JsonEditor with textarea in CommonConfigEditor.tsx
- Remove unnecessary dark mode detection logic
- Remove wrapper div with border class that caused double borders
- Apply unified border design system (border-border-default)
- Keep JsonEditor in UsageScriptModal for JavaScript code editing
Benefits:
- Eliminates CodeMirror theme conflicts with oneDark
- Simplifies codebase by removing unnecessary abstractions
- Consistent styling across all config input fields
- Automatic theme adaptation via CSS variables
- Add border styles to JsonEditor (CodeMirror) with theme-responsive colors
- Update all dialog header/footer dividers to use border-border-default
- Replace remaining border-border instances in settings components
- Ensure all borders (including separators and container borders) use unified design system
- All borders now consistently use CSS variables and respond to light/dark themes
- Define custom border utilities in @layer utilities for consistent theming
- Add border-default (1px gray), border-active (2px primary), border-hover (40% primary), and border-dragging (60% primary) classes
- Update all UI components (Input, Select, TextArea, Button, Dialog, Dropdown) to use unified border classes
- Replace hardcoded border colors (gray-200/300/600/700) with theme-responsive border-border-default
- Update provider cards, MCP components, settings, and forms with new border system
- Remove dark mode border overrides to simplify CSS and improve maintainability
- Ensure all borders automatically adapt to light/dark themes via CSS variables
- Change toast position from top-right to top-center for better visibility
- Remove success notifications for plugin sync operations to reduce noise
- Keep error notifications to alert users of actual issues
- Use opacity transition instead of conditional rendering for "current" badge to prevent height changes
- Add min-h-[20px] to title row to maintain consistent height
- Use 2px border for active provider, 1px for inactive to improve visual distinction
- Remove global border-color rule that was overriding Tailwind utilities
- Use HSL arbitrary values for border colors to work with Tailwind 4 + shadcn/ui
- Reduce header spacing (gap-4 → gap-2, gap-1.5 → gap-1) for more compact layout
- Unify provider link colors with main title (blue-500/blue-400)
- Standardize action button styles to use size="icon" for consistent hover effects
- Add proper hover backgrounds to provider action buttons matching header buttons
Add visual theme system for provider presets with custom icons and brand colors:
- Add PresetTheme interface supporting icon type and custom colors
- Claude Official: Claude brand icon + orange theme (#D97757)
- Codex Official: Codex brand icon + dark gray theme (#1F2937)
- Other presets: Default to theme blue (bg-blue-500)
- Custom config: Uses theme blue for consistency
Technical changes:
- Extend ProviderPreset and CodexProviderPreset interfaces with optional theme field
- Update ProviderPresetSelector to render icons and apply theme colors
- Support both Tailwind classes and hex colors via inline styles
- Remove unused Link import from ProviderCard
This restores the unique visual identity for official providers while
maintaining a unified theme color for third-party presets.
- Add subtle gray background to "In Use" button for better visual distinction
- Enhance disabled state of delete button with opacity and cursor feedback
- Remove disabled attribute to allow pointer events for better UX
- Prevent delete action via conditional onClick instead of disabled prop
- Remove flex gap-3 from parent container to enable width animation
- Add conditional width (w-8/w-0) and margin-right (mr-3/mr-0) to drag handle button
- Add overflow-hidden to prevent icon overflow during width transition
- Content now smoothly shifts right by 44px when entering edit mode
- Maintains 200ms transition-all for smooth push animation
This creates a more natural "push-out" effect where the drag handle
appears to push the provider content aside, rather than just fading in.
- Add edit mode button next to settings in header
- Edit button turns blue when active
- Drag handles fade in/out with edit mode toggle
- Add smooth 200ms transition animation
- Add i18n support for edit mode (en/zh)
- Maintain consistent spacing between header elements
- Convert edit and usage buttons to icon-only ghost variant for cleaner appearance
- Reduce action button padding (px-2) and group spacing (gap-0) for more compact layout
- Add red hover effect to delete icon for better visual feedback
- Vertically center action buttons with provider info on desktop view
- Simplify provider URL link by removing icon and using soft blue color (blue-400/300)
- Reduce enable button width from 96px to 80px for better proportions
- Add dynamic hint text based on provider category (official, opensource, aggregator, third-party, custom)
- Display hints in ProviderPresetSelector below preset buttons
- Rename "Chinese official" to "Opensource official" for better clarity
- Add i18n keys for all category hints in both zh and en locales
- Remove redundant hint boxes from ClaudeFormFields and CodexFormFields
This improves user guidance by showing contextual hints that explain
what fields are required for each provider category.
Adjusted Add and Edit provider dialogs to use consistent sizing with MCP panel:
- Increased max width from max-w-2xl to max-w-3xl
- Unified max height to max-h-[85vh] (was 90vh)
- Added min-h-[600px] for minimum height constraint
This ensures visual consistency across all major dialog panels in the app.
- Add missing translation keys for all hint texts and descriptions
- Remove all hardcoded defaultValue parameters from components
- Add translations for window behavior, directory settings, and theme settings
- Add translations for provider-related UI elements
- Improve consistency across Chinese and English translations
Translation additions:
- common.toggleTheme
- settings.windowBehaviorHint, claudeConfigDirDescription, codexConfigDirDescription
- provider.* (12 new keys)
- providerForm.* (15 new keys)
- providerPreset.* (4 new keys)
Modified files: 10
Lines changed: +132 -74
Remove size="sm" from MCP panel buttons to use default text-sm instead of text-xs, ensuring visual consistency with buttons throughout the application.
Fix multiple alignment and spacing issues in dialog components to ensure
proper visual balance and symmetry across all dialog elements.
Changes to DialogHeader:
- Change padding from pt-6 pb-4 to py-5 for symmetric vertical spacing
- Ensures header content is equidistant from top and bottom borders
Changes to DialogFooter:
- Change padding from pt-4 pb-6 to py-5 for symmetric vertical spacing
- Replace sm:space-x-2 with gap-2 for consistent button spacing at all breakpoints
- Add sm:items-center to vertically center buttons in horizontal layout
- Ensures footer buttons align properly and are equidistant from borders
Changes to DialogTitle:
- Change line-height from leading-none to leading-tight (1 → 1.25)
- Provides better visual balance with button heights
- Improves readability and alignment with adjacent buttons
Impact:
All dialog components now have:
- Symmetric vertical padding (1.25rem top/bottom)
- Properly centered content regardless of viewport size
- Consistent button spacing and alignment
- Better visual harmony between text and interactive elements
Enhance visual consistency across MCP dialogs with better spacing
and appropriate icons for add/edit actions.
Changes:
- McpFormModal: Use Plus icon for "Add" button, Save icon for "Edit" button
- McpPanel: Balance spacing around server info line (py-4 for info section,
pb-4 for content area) to create equal visual weight between header and
server list
The MCP panel now has consistent vertical rhythm with 1rem spacing above
and below the server count info line.
Add visual indicators to the Add and Save buttons in provider dialogs
for improved UX and consistency with other dialogs.
Changes:
- AddProviderDialog: Add Plus icon to "Add" button
- EditProviderDialog: Add Save icon to "Save" button
Both dialogs now have clear visual affordances for primary actions,
matching the icon usage pattern in other modal dialogs throughout
the application.
Restructure AddProviderDialog and EditProviderDialog to follow the
standardized dialog layout pattern with buttons in DialogFooter.
Changes:
- Add DialogFooter to AddProviderDialog and EditProviderDialog
- Add `showButtons` prop to ProviderForm (default: true for backward compatibility)
- Add `id="provider-form"` to form element for external form submission
- Move Cancel and Submit buttons from ProviderForm to DialogFooter
- Use `form="provider-form"` attribute on DialogFooter buttons to trigger submission
Benefits:
- Consistent dialog footer appearance across all dialogs
- Proper spacing and alignment with other modal dialogs
- Better visual hierarchy with separated content and action areas
- Maintains backward compatibility for ProviderForm usage elsewhere
All provider dialogs now follow the unified pattern:
- DialogHeader: Title and description
- Content area: flex-1 overflow-y-auto px-6 py-4
- DialogFooter: Action buttons with standard styling
Remove custom styling from DialogFooter components across the application
to ensure consistent appearance and behavior. All dialogs now follow the
unified layout pattern defined in the base Dialog component.
Changes:
- Remove custom className overrides from DialogFooter in:
- EndpointSpeedTest.tsx
- CodexQuickWizardModal.tsx
- CodexCommonConfigModal.tsx
- ClaudeConfigEditor.tsx
- Fix McpWizardModal content area padding (remove -mx-6 negative margin)
- Fix McpPanel to use DialogFooter component instead of custom div
All dialogs now consistently use:
- DialogHeader: px-6 pt-6 pb-4 with border and background (built-in)
- Content area: flex-1 overflow-y-auto px-6 py-4
- DialogFooter: px-6 pb-6 pt-4 with border and background (built-in)
This ensures proper spacing, alignment, and visual consistency across
all modal dialogs in the application.
Fix two UX issues in the settings dialog:
1. Check for Updates button width stability
- Added min-w-[140px] to prevent layout shift during state changes
- Button maintains consistent width across all states:
* "Check for Updates" (idle)
* "Checking..." (with spinner)
* "Update to vX.X.X" (with download icon)
* "Installing..." (with spinner)
2. Settings dialog scrollbar behavior
- Changed overflow-y-scroll to overflow-y-auto
- Scrollbar now only appears when content actually overflows
- Prevents unnecessary scrollbar on About tab (content < 400px)
- Maintains min-h-[400px] on all tabs to prevent dialog height jump
These changes improve visual stability and reduce UI clutter while
maintaining consistent dialog dimensions during tab switching.
Move the theme toggle from the main header to the settings dialog for a
cleaner UI and better organization. The new theme selector uses a
button group design consistent with the language settings.
Changes:
- Created ThemeSettings component with three options: Light, Dark, System
- Added ThemeSettings to the General tab in settings dialog
- Removed ModeToggle component from main header
- Added theme-related i18n keys for all options
- Theme selection takes effect immediately without requiring save
Design:
- Uses button group style matching LanguageSettings
- Icons for each theme option (Sun, Moon, Monitor)
- Consistent with app's blue theme for active state
- Smooth transitions and hover effects
This change simplifies the main header and consolidates all appearance
settings in one place, improving the overall user experience.
Remove the config file location display from settings dialog to simplify
the user interface. Users who need to access the config file can still do
so through the advanced settings section.
Changes:
- Removed ConfigPathDisplay component and its usage
- Removed configPath and openConfigFolder from useSettings hook
- Removed configPath and openConfigFolder from useSettingsMetadata hook
- Removed related i18n keys: configFileLocation, openFolder
- Updated settings dialog to remove the config path display section
This simplifies the settings UI while maintaining access to config
directory management through the advanced settings tab.
Improve the visual hierarchy and interaction feedback in the settings dialog:
1. Tab navigation enhancement:
- Active tabs now use blue background (blue-500/600) with white text
- Add shadow effect to active tabs for better depth perception
- Inactive tabs show hover effects (opacity + background)
- Consistent with app's primary blue theme color
2. Switch component visual improvement:
- Checked state: blue background (blue-500/600) for clear indication
- Unchecked state: gray background (gray-300/700) for neutral appearance
- White thumb color for better contrast on both states
- Enhanced focus ring (ring-2 + offset-2) for accessibility
3. Layout spacing refinement:
- Change content area padding from pb-4 to py-4 for symmetrical spacing
- Ensure consistent 4-unit spacing between all dialog sections
4. Clarify plugin integration description:
- Update description to accurately reflect that provider switching
in this app will sync with VS Code Claude Code extension
- Previous wording was ambiguous about the synchronization behavior
Files changed:
- src/components/ui/tabs.tsx: Enhanced tab visual states
- src/components/ui/switch.tsx: Improved switch contrast
- src/components/settings/SettingsDialog.tsx: Fixed spacing
- src/i18n/locales/{zh,en}.json: Updated plugin description
This commit refines the visual hierarchy and fixes layout issues in the
settings dialog:
1. Add visual separators to dialog sections:
- Add bottom border and background to DialogHeader
- Both header and footer now have consistent border + bg-muted/20 styling
- Creates clear three-section layout: header | content | footer
2. Fix footer overflow issue:
- Remove min-h-[480px] from content area that conflicted with max-h-[90vh]
- Keep min-h-[400px] on TabsContent to prevent height jumps
- Add flex-shrink-0 to header and footer to ensure they stay visible
- Content area uses flex-1 to fill remaining space naturally
3. Improve spacing:
- Add pb-4 to content area for breathing room above footer
- Add pb-4 to DialogHeader for consistent spacing below title
- Ensure proper padding hierarchy across all dialog sections
Layout calculation (small screens, 90vh ≈ 540px):
- Header: ~70px (fixed)
- Content: 400px minimum, scrollable (flexible)
- Footer: ~70px (fixed)
- Total: ≤ 540px, footer always visible ✓
Files modified:
- src/components/ui/dialog.tsx (DialogHeader, DialogFooter styling)
- src/components/settings/SettingsDialog.tsx (content area height constraint)
Fixed multiple layout issues in the settings dialog:
1. Dialog structure: Changed from grid to flexbox layout
- Removed global padding from DialogContent
- Added individual padding to DialogHeader (px-6 pt-6) and DialogFooter (px-6 pb-6 pt-4)
- Added max-h-[90vh] constraint to prevent dialog from exceeding viewport
2. Content area improvements:
- Replaced max-h-[70vh] with flex-1 for better space utilization
- Set min-h-[480px] on content wrapper to maintain consistent dialog height
- Applied min-h-[400px] to all TabsContent components to prevent height jumps
3. Scrollbar optimization:
- Changed overflow-y-auto to overflow-y-scroll to force scrollbar gutter
- Eliminates horizontal shift when switching between tabs with different content heights
- Consistent with main app layout approach (App.tsx)
4. Footer enhancement:
- Added border-t and bg-muted/20 for visual separation
- Fixed footer overlapping content in advanced tab
Result: Settings dialog now displays all content properly without requiring
fullscreen, maintains consistent height across tabs, and eliminates layout shift
when switching tabs.
Fixed dual-source jitter issue:
1. Horizontal shift: Use overflow-y-scroll to force scrollbar gutter
2. Vertical jump: Use keepPreviousData to maintain content during app switch
Fix inconsistent modal overlays by migrating all custom implementations
to the unified shadcn/ui Dialog component with proper z-index layering.
Changes:
- Update Dialog component to support three z-index levels:
- base (z-40): First-level dialogs
- nested (z-50): Nested dialogs
- alert (z-[60]): Alert/confirmation dialogs (using Tailwind arbitrary value)
- Refactor all custom modal implementations to use Dialog:
- EndpointSpeedTest: API endpoint speed testing panel
- ClaudeConfigEditor: Claude common config editor
- CodexQuickWizardModal: Codex quick setup wizard
- CodexCommonConfigModal: Codex common config editor
- SettingsDialog: Restart confirmation prompt
- Remove custom backdrop implementations and manual z-index
- Leverage Radix UI Portal for automatic DOM order management
- Ensure consistent overlay behavior and keyboard interactions
This eliminates the "background residue" issue where overlays from
different layers would conflict, providing a unified and professional
user experience across all modal interactions.
Introduce `closeAfterSave` callback to distinguish between save-and-close and cancel-and-close scenarios. Previously, saving settings would trigger `resetSettings()`, causing language changes to revert and requiring users to save twice for the change to take effect.
Changes:
- Add `closeAfterSave()`: close dialog without resetting settings after successful save
- Keep `closeDialog()`: reset settings when canceling or directly closing dialog
- Update save flow to use `closeAfterSave()` in `handleSaveClick`, `handleRestartLater`, and `handleRestartNow`
Fixed an issue where clicking the language switcher would cause a brief flash
and fail to persist the language change. The root cause was a dependency cycle
in useSettingsForm where readPersistedLanguage depended on i18n.language,
causing the initialization effect to re-run and reset state whenever the
language changed.
Changed the dependency from [i18n.language] to [i18n] since the i18n object
itself is stable and doesn't change when the language changes, while the
function can still access the current language value via closure.
Before optimization:
- McpPanel.tsx: 298 lines (component + business logic)
After optimization:
- McpPanel.tsx: 234 lines (-21%, UI focused)
- useMcpActions.ts: 137 lines (business logic)
Benefits:
✅ Separation of concerns: UI vs business logic
✅ Reusability: MCP operations can be used in other components
✅ Testability: business logic can be tested independently
✅ Consistency: follows same pattern as useProviderActions
✅ Optimistic updates: toggle enabled status with rollback on error
✅ Unified error handling: all MCP errors use toast notifications
Technical details:
- Extract reload, toggleEnabled, saveServer, deleteServer
- Implement optimistic UI updates for toggle
- Centralize error handling and toast messages
- Remove duplicate error handling code from component
Before optimization:
- useSettings.ts: 516 lines (single monolithic hook)
After optimization:
- useSettingsForm.ts: 158 lines (form state management)
- useDirectorySettings.ts: 297 lines (directory management)
- useSettingsMetadata.ts: 95 lines (metadata management)
- useSettings.ts: 215 lines (composition layer)
- Total: 765 lines (+249 lines, but with clear separation of concerns)
Benefits:
✅ Single Responsibility Principle: each hook focuses on one domain
✅ Testability: independent hooks are easier to unit test
✅ Reusability: specialized hooks can be reused in other components
✅ Maintainability: reduced cognitive load per file
✅ Zero breaking changes: SettingsDialog auto-adapted to new interface
Technical details:
- useSettingsForm: pure form state + language sync
- useDirectorySettings: directory selection/reset + default value computation
- useSettingsMetadata: config path + portable mode + restart flag
- useSettings: composition layer + save logic + reset logic
- Remove unused useDarkMode hook (now using shadcn theme-provider)
- Clean up MCP components (remove redundant code)
- Add restart API to settings
- Minor type improvements
Extract all MCP form validation logic into a reusable custom hook to
improve code organization and enable reuse across components.
Changes:
- Create useMcpValidation hook with 4 validation functions:
* validateJson: basic JSON structure validation
* formatTomlError: unified TOML error formatting with i18n
* validateTomlConfig: complete TOML validation with required fields
* validateJsonConfig: complete JSON validation with structure checks
- Update McpFormModal to use the hook instead of inline validation
- Simplify validation calls throughout the component
- Reduce code duplication while maintaining all functionality
Benefits:
- Validation logic can be reused in other MCP-related components
- Easier to test validation in isolation
- Better separation of concerns
- McpFormModal remains at 699 lines (original: 767), kept cohesive
The component stays as one piece since its 700 lines represent a
single, cohesive form feature rather than multiple unrelated concerns.
- Migrate all form components from ProviderForm/ to providers/forms/
- Create shared components to eliminate code duplication:
* ApiKeySection: unified API key input with "Get API Key" link
* EndpointField: unified endpoint URL input with manage button
- Refactor ClaudeFormFields (-31% lines) and CodexFormFields (-33% lines)
- Update all import paths to use new locations
- Reduce code duplication from ~12% to ~7%
This change improves maintainability and makes the codebase more DRY.
- Add smol-toml dependency for client-side TOML parsing
- Create useCodexTomlValidation hook with 500ms debounce
- Display validation errors below config.toml textarea
- Trigger validation on onChange for immediate user feedback
- Backend validation remains as fallback for data integrity
- Remove obsolete TODO comments for Codex Base URL handling
- Codex Base URL is already fully managed by useCodexConfigState hook
- useBaseUrlState is now only used for Claude mode
- Add clarifying comments about the architecture
- Import vscodeApi to access backend test_api_endpoints command
- Replace empty results array with actual API call
- Remove TODO comments for implemented API calls
- Enable all endpoint management features:
- Speed test with latency measurement
- Load custom endpoints from backend
- Add/remove custom endpoints
- Update endpoint last used time
- Fix issue where clicking test button immediately showed failure
- Create useSpeedTestEndpoints hook that collects endpoints from:
1. Current baseUrl/codexBaseUrl
2. Initial data URL (edit mode)
3. Preset's endpointCandidates array
- Pass speedTestEndpoints to ClaudeFormFields and CodexFormFields
- Update EndpointSpeedTest to use collected endpoints instead of just current URL
- Fix PackyCode preset endpoints not appearing in speed test modal
Fixed critical bug where CodexFormFields component was not rendered
in edit mode, preventing users from:
- Viewing and editing API base URL for custom/third-party Codex providers
- Accessing endpoint speed test modal in edit mode
- Updating API keys in edit mode
Changed line 477 from:
{appType === "codex" && !isEditMode && (
To:
{appType === "codex" && (
This aligns Codex behavior with Claude, where all form fields are
available in both create and edit modes for custom/third-party providers.
Fixed logic in useApiKeyState.ts to correctly show API Key input
when editing existing providers. Changed condition from
`!isEditMode && hasApiKeyField(config)` to
`isEditMode && hasApiKeyField(config)` to match the original
form's behavior.
This restores the ability to directly update API keys when
editing existing providers that have API key fields in their
configuration.
- Created ProviderPresetSelector component (80 lines)
- Created BasicFormFields component (60 lines)
- Created ClaudeFormFields component (272 lines)
- Created CodexFormFields component (131 lines)
- Reduced ProviderForm from 866 to 544 lines (37% reduction)
Each component now has a clear single responsibility:
- ProviderPresetSelector: Handles preset selection UI
- BasicFormFields: Name and website URL inputs
- ClaudeFormFields: All Claude-specific form fields
- CodexFormFields: All Codex-specific form fields
- ProviderForm: Orchestrates hooks and component composition
Benefits:
- Better code organization and maintainability
- Easier to test individual components
- Clearer separation of concerns
- More reusable components
- Created useCodexCommonConfig hook for managing Codex TOML common config
- Persists to localStorage with key 'cc-switch:codex-common-config-snippet'
- Added isCodexTemplateModalOpen state to ProviderForm
- Connected all CodexConfigEditor props:
- Common config snippet management (useCommonConfig, handlers)
- Template modal state (isTemplateModalOpen, setIsTemplateModalOpen)
- Form field callbacks (onWebsiteUrlChange, onNameChange)
- Custom mode detection (isCustomMode)
- Hook structure mirrors useCommonConfigSnippet for consistency
- Create useCommonConfigSnippet hook to manage common config state
- Create CommonConfigEditor component with modal for editing
- Support merging/removing common config snippets from provider configs
- Persist common config to localStorage for reuse across providers
- Auto-detect if provider config contains common snippet
- Replace JSON editor with CommonConfigEditor in ProviderForm
- Create useTemplateValues hook to manage template variable state
- Support dynamic placeholder replacement in provider configs
- Add template parameter input UI in provider form
- Validate required template values before submission
- Auto-update config when template values change
- Create useKimiModelSelector hook to manage Kimi-specific state
- Auto-detect Kimi providers by preset name or config content
- Support model initialization from existing config in edit mode
- Sync model selections with JSON configuration
- Maintain clean separation between UI and business logic
- Create useApiKeyLink hook to manage API Key retrieval link display and URL
- Create useCustomEndpoints hook to collect endpoints from multiple sources
- Simplify ProviderForm by using these new hooks
- Reduce code duplication and improve maintainability
- Fix TypeScript error with form.watch("websiteUrl") by providing default empty string
- Add draftCustomEndpoints state to collect custom endpoints from speed test modal
- Import CustomEndpoint type from @/types
- Update handleSubmit to collect and package endpoints into meta.custom_endpoints:
* User-added custom endpoints (from draftCustomEndpoints)
* Preset endpointCandidates (if any)
* Current Base URL (baseUrl/codexBaseUrl)
- Add onCustomEndpointsChange callback to both Claude and Codex EndpointSpeedTest instances
- Extend ProviderFormValues type to include meta.custom_endpoints field
- Only creates meta.custom_endpoints for new providers (not edit mode)
- Deduplicates URLs and creates CustomEndpoint entries with addedAt timestamp
- Add shouldShowClaudeApiKeyLink logic based on provider category
- Add shouldShowCodexApiKeyLink logic for Codex providers
- Add getCurrentClaudeWebsiteUrl() to get website URL with apiKeyUrl priority for third-party providers
- Add getCurrentCodexWebsiteUrl() with same logic for Codex
- Add link UI below API Key input for both Claude and Codex
- Links only show for cn_official, aggregator, and third_party categories
- Preserve original UI styling with -mt-1 pl-1 positioning
- Integrate useModelState hook for managing ANTHROPIC_MODEL and ANTHROPIC_SMALL_FAST_MODEL
- Add two model input fields in responsive grid layout
- Only show for non-official Claude providers
- Include helper text explaining optional nature
- Bidirectional sync between inputs and JSON config
- Integrate useBaseUrlState hook for managing Base URL
- Add Base URL input field for third-party and custom providers
- Add endpoint speed test modal with management button
- Show Base URL section only for non-official providers
- Add Zap icon button to open endpoint speed test modal
- Pass baseUrl to EndpointSpeedTest component
- Add helper text explaining API endpoint usage
All TypeScript type checks pass.
- Create custom hooks for state management:
- useProviderCategory: manages provider category state
- useApiKeyState: manages API key input with auto-sync to config
- useBaseUrlState: manages base URL for Claude and Codex
- useModelState: manages model selection state
- Integrate API key input into simplified ProviderForm:
- Add ApiKeyInput component for Claude mode
- Auto-populate API key into settings config
- Disable for official providers
- Fix EndpointSpeedTest type errors:
- Fix import paths to use @ alias
- Add temporary type definitions
- Format all TODO comments properly
- Remove incorrect type assertions
- Comment out unimplemented window.api checks
All TypeScript type checks now pass.
## Changes
### Add scrollbars to provider dialogs
- **AddProviderDialog.tsx**: Add max-h-[90vh], flex flex-col layout, and scrollable content wrapper
- **EditProviderDialog.tsx**: Add max-h-[90vh], flex flex-col layout, and scrollable content wrapper
- Both dialogs now follow the same scrolling pattern as other dialogs in the app
- Wrap ProviderForm in `<div className="flex-1 overflow-y-auto -mx-6 px-6">` for proper scrolling
### Simplify theme toggle
- **mode-toggle.tsx**: Change from dropdown menu to direct toggle button
- Remove DropdownMenu and related imports
- Click now directly toggles between light and dark mode
- Simpler UX: one click to switch themes instead of opening a menu
- Remove "system" theme option from quick toggle (still available in settings if needed)
## Benefits
- **Consistent scrolling**: All dialogs now have proper scroll behavior when content exceeds viewport height
- **Better UX**: Theme toggle is faster and more intuitive with direct click
- **Code simplification**: Removed unnecessary dropdown menu complexity from theme toggle
All TypeScript type checks and Prettier formatting checks pass.
Convert all MCP-related modal windows to use the unified shadcn/ui Dialog
component for consistency with the rest of the application.
Changes:
- McpPanel: Replace custom modal with Dialog component
- Update props from onClose to open/onOpenChange pattern
- Use DialogContent, DialogHeader, DialogTitle components
- Remove custom backdrop and close button (handled by Dialog)
- McpFormModal: Migrate form modal to Dialog
- Wrap entire form in Dialog component structure
- Use DialogFooter for action buttons
- Apply variant="mcp" to maintain green button styling
- Remove unused X icon import
- McpWizardModal: Convert wizard to Dialog
- Replace custom modal structure with Dialog components
- Use Button component with variant="mcp" for consistency
- Remove unused isLinux and X icon imports
- App.tsx: Update McpPanel usage
- Remove conditional rendering wrapper
- Pass open and onOpenChange props directly
- dialog.tsx: Fix dialog overlay and content styling
- Change overlay from bg-background/80 to bg-black/50 for consistency
- Change content from bg-background to explicit bg-white dark:bg-gray-900
- Ensures opaque backgrounds matching MCP panel style
Benefits:
- Unified dialog behavior across the application
- Consistent styling and animations
- Better accessibility with Radix UI primitives
- Reduced code duplication
- Maintains MCP-specific green color scheme
All dialogs now share the same base styling while preserving their unique
content and functionality.
Restore the vibrant color palette from the pre-refactoring version while
maintaining shadcn/ui component architecture and modern design patterns.
## Color Scheme Restoration
### Button Component
- **default variant**: Blue primary (`bg-blue-500`) - matches old `primary`
- **destructive variant**: Red (`bg-red-500`) - matches old `danger`
- **secondary variant**: Gray text (`text-gray-500`) - matches old `secondary`
- **ghost variant**: Transparent hover (`hover:bg-gray-100`) - matches old `ghost`
- **mcp variant**: Emerald green (`bg-emerald-500`) - matches old `mcp`
- Updated border-radius to `rounded-lg` for consistency
### CSS Variables
- Set `--primary` to blue (`hsl(217 91% 60%)` ≈ `bg-blue-500`)
- Added complete shadcn/ui theme variables for light/dark modes
- Maintained semantic color tokens for maintainability
### Component-Specific Colors
- **"Currently Using" badge**: Green (`bg-green-500/10 text-green-500`)
- **Delete button hover**: Red (`hover:text-red-500 hover:bg-red-100`)
- **MCP button**: Emerald green with minimum width (`min-w-[80px]`)
- **Links/URLs**: Blue (`text-blue-500`)
## Benefits
- ✅ Restored original vibrant UI (blue, green, red accents)
- ✅ Maintained shadcn/ui component system (accessibility, animations)
- ✅ Easy global theming via CSS variables
- ✅ Consistent design language across all components
- ✅ Code formatted with Prettier (shadcn/ui standards)
## Files Changed
- `src/index.css`: Added shadcn/ui theme variables with blue primary
- `src/components/ui/button.tsx`: Restored all original button color variants
- `src/components/providers/ProviderCard.tsx`: Green badge for current provider
- `src/components/providers/ProviderActions.tsx`: Red hover for delete button
- `src/components/mcp/McpPanel.tsx`: Use `mcp` variant for consistency
- `src/App.tsx`: MCP button with emerald color and wider width
The UI now matches the original colorful design while leveraging modern
shadcn/ui components for better maintainability and user experience.
This commit completes Stage 2.5-2.6 of the refactoring plan by:
- Consolidating 8 provider form files (1941+ lines) into a single
unified ProviderForm component (353 lines), reducing code by ~82%
- Implementing modern form management with react-hook-form and zod
- Adding preset provider categorization with grouped select UI
- Supporting dual-mode operation for both Claude and Codex configs
- Removing redundant subcomponents:
- ApiKeyInput.tsx (72 lines)
- ClaudeConfigEditor.tsx (205 lines)
- CodexConfigEditor.tsx (667 lines)
- EndpointSpeedTest.tsx (636 lines)
- KimiModelSelector.tsx (195 lines)
- PresetSelector.tsx (119 lines)
Key improvements:
- Type-safe form values with ProviderFormValues extension
- Automatic template value application for presets
- Better internationalization coverage
- Cleaner separation of concerns
- Enhanced UX with categorized preset groups
Updates AddProviderDialog and EditProviderDialog to pass appType prop
and handle preset category metadata.
Add three key documents to guide the project restructure:
- REFACTORING_MASTER_PLAN.md: Complete refactoring roadmap with 6 stages
- REFACTORING_CHECKLIST.md: Detailed task checklist for tracking progress
- REFACTORING_REFERENCE.md: Technical reference and implementation guide
This refactoring aims to modernize the codebase with React Query,
react-hook-form, zod validation, and shadcn/ui components while
maintaining the current Tailwind CSS 4.x stack.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(ui): add drag-and-drop sorting for provider list
Implement drag-and-drop functionality to allow users to reorder providers with custom sort indices.
Features:
- Install @dnd-kit libraries for drag-and-drop support
- Add sortIndex field to Provider type (frontend & backend)
- Implement SortableProviderItem component with drag handle
- Add update_providers_sort_order Tauri command
- Sync tray menu order with provider list sorting
- Add i18n support for drag-related UI text
Technical details:
- Use @dnd-kit/core and @dnd-kit/sortable for smooth drag interactions
- Disable animations for immediate response after drop
- Update tray menu immediately after reordering
- Sort priority: sortIndex → createdAt → name
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ui): remove unused transition variable in ProviderList
Remove unused 'transition' destructured variable from useSortable hook
to fix TypeScript error TS6133. The transition property is hardcoded
as 'none' in the style object to prevent conflicts with drag operations.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add provider usage query functionality
- Updated `Cargo.toml` to include `regex` and `rquickjs` dependencies for usage script execution.
- Implemented `query_provider_usage` command in `commands.rs` to handle usage queries.
- Created `UsageScript` and `UsageData` structs in `provider.rs` for managing usage script configurations and results.
- Added `execute_usage_script` function in `usage_script.rs` to run user-defined scripts for querying usage.
- Enhanced `ProviderList` component to include a button for configuring usage scripts and a modal for editing scripts.
- Introduced `UsageFooter` component to display usage information and status.
- Added `UsageScriptModal` for editing and testing usage scripts with preset templates.
- Updated Tauri API to support querying provider usage.
- Modified types in `types.ts` to include structures for usage scripts and results.
* feat(usage): support multi-plan usage display for providers
- 【Feature】
- Update `UsageResult` to support an array of `UsageData` for displaying multiple usage plans per provider.
- Refactor `query_provider_usage` command to parse both single `UsageData` objects (for backward compatibility) and arrays of `UsageData`.
- Enhance `usage_script` validation to accept either a single usage object or an array of usage objects.
- 【Frontend】
- Redesign `UsageFooter` to iterate and display details for all available usage plans, introducing `UsagePlanItem` for individual plan rendering.
- Improve usage display with color-coded remaining balance and clear plan information.
- Update `UsageScriptModal` test notification to summarize all returned plans.
- Remove redundant `isCurrent` prop from `UsageFooter` in `ProviderList`.
- 【Build】
- Change frontend development server port from `3000` to `3005` in `tauri.conf.json` and `vite.config.mts`.
* feat(usage): enhance query flexibility and display
- 【`src/types.ts`, `src-tauri/src/provider.rs`】Make `UsageData` fields optional and introduce `extra` and `invalidMessage` for more flexible reporting.
- `expiresAt` replaced by generic `extra` field.
- `isValid`, `remaining`, `unit` are now optional.
- Added `invalidMessage` to provide specific reasons for invalid status.
- 【`src-tauri/src/usage_script.rs`】Relax usage script result validation to accommodate optional fields in `UsageData`.
- 【`src/components/UsageFooter.tsx`】Update UI to display `extra` field and `invalidMessage`, and conditionally render `remaining` and `unit` based on availability.
- 【`src/components/UsageScriptModal.tsx`】
- Add a new `NewAPI` preset template demonstrating advanced extractor logic for complex API responses.
- Update script instructions to reflect optional fields and new variable syntax (`{{apiKey}}`).
- Remove old "DeepSeek" and "OpenAI" templates.
- Remove basic syntax check for `return` statement.
- 【`.vscode/settings.json`】Add `dish-ai-commit.base.language` setting.
- 【`src-tauri/src/commands.rs`】Adjust usage logging to handle optional `remaining` and `unit` fields.
* chore(config): remove VS Code settings from version control
- delete .vscode/settings.json to remove editor-specific configurations
- add /.vscode to .gitignore to prevent tracking of local VS Code settings
- ensure personalized editor preferences are not committed to the repository
* fix(provider): preserve usage script during provider update
- When updating a provider, the `usage_script` configuration within `ProviderMeta` was not explicitly merged.
- This could lead to the accidental loss of `usage_script` settings if the incoming `provider` object in the update request did not contain this field.
- Ensure `usage_script` is cloned from the existing provider's meta when merging `ProviderMeta` during an update.
* refactor(provider): enforce base_url for usage scripts and update dev ports
- 【Backend】
- `src-tauri/src/commands.rs`: Made `ANTHROPIC_BASE_URL` a required field for Claude providers and `base_url` a required field in `config.toml` for Codex providers when extracting credentials for usage script execution. This improves error handling by explicitly failing if these critical URLs are missing or malformed.
- 【Frontend】
- `src/App.tsx`, `src/components/ProviderList.tsx`: Passed `appType` prop to `ProviderList` component to ensure `updateProvider` calls within `handleSaveUsageScript` correctly identify the application type.
- 【Config】
- `src-tauri/tauri.conf.json`, `vite.config.mts`: Updated development server ports from `3005` to `3000` to standardize local development environment.
* refactor(usage): improve usage data fetching logic
- Prevent redundant API calls by tracking last fetched parameters in `useEffect`.
- Avoid concurrent API requests by adding a guard in `fetchUsage`.
- Clear usage data and last fetch parameters when usage query is disabled.
- Add `queryProviderUsage` API declaration to `window.api` interface.
* fix(usage-script): ensure usage script updates and improve reactivity
- correctly update `usage_script` from new provider meta during updates
- replace full page reload with targeted provider data refresh after saving usage script settings
- trigger usage data fetch or clear when `usageEnabled` status changes in `UsageFooter`
- reduce logging verbosity for usage script execution in backend commands and script execution
* style(usage-footer): adjust usage plan item layout
- Decrease width of extra field column from 35% to 30%
- Increase width of usage information column from 40% to 45%
- Improve visual balance and readability of usage plan items
- Move sync checkbox to footer alongside save/cancel buttons for better visibility
- Add real-time conflict detection to check for duplicate MCP IDs on other side
- Display amber warning icon when sync would overwrite existing config
- Add i18n keys for overwrite warning (zh: "将覆盖 {{target}} 中的同名配置")
- Update UI layout to use justify-between for better spacing
- Add syncOtherSide parameter to upsert_mcp_server_in_config command
- Implement checkbox UI in McpFormModal for cross-app sync
- Automatically sync enabled MCP servers to both Claude and Codex when option is checked
- Add i18n support for sync option labels and hints
Allow MCP wizard to load and edit existing server configuration:
- Parse current config (JSON/TOML) and pass to wizard as initial data
- Auto-detect server type (stdio/http) and populate form fields
- Convert objects (env/headers) to multi-line text format for editing
- Improves UX by avoiding manual re-entry of existing settings
When upserting an MCP server that is enabled, automatically sync it to the corresponding app's live configuration (Claude or Codex) without requiring manual action.
--append-system-prompt "You are reviewing PRs for cc-switch — a small open-source Tauri 2.0 + React + TypeScript desktop app maintained by a single developer who values pragmatism over perfection.
ONLY flag HIGH SIGNAL issues. An issue qualifies only if it meets at least one of: code that fails to compile/parse/run; code that will definitely produce wrong results; a security vulnerability with a concrete trigger (SSRF, injection, unsafe deserialization, credential or data leaks); database/migration correctness (schema mismatch, data loss on upgrade, broken SCHEMA_VERSION transition); cross-platform breakage (Windows/macOS/Linux specific); clear, quotable violations of CLAUDE.md.
Each finding must include a confidence score from 0 to 100. Do NOT post any finding scored below 80. Each finding must carry a severity tag — Important (bug that should be fixed before merge), Nit (minor, worth fixing but not blocking), or Pre-existing (exists already, not introduced by this PR).
DO NOT flag any of the following: pedantic nitpicks a senior engineer would not raise; anything a linter, typechecker, pnpm format, or cargo fmt would catch; missing test coverage unless CLAUDE.md explicitly mandates it; style, naming, or 'could be more idiomatic' suggestions; speculative future-proofing ('what if X is added later'); issues on lines the PR did not modify; potential bugs for which you cannot construct a concrete failure scenario; positive feedback or 'what is well done' sections.
OUTPUT RULES: Cap Nit-level findings at 5 per review. If you found more nits, append 'plus N similar nits' to the summary instead of listing them inline. If only nits were found, lead the review with 'No blocking issues.' If nothing material is wrong, say 'LGTM' explicitly and stop — approval with zero comments is a valid and expected outcome for most PRs. Do NOT post a final APPROVE or REQUEST CHANGES verdict; leave the merge decision to the maintainer.
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at **farion1231@gmail.com**. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of actions.
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
cd src-tauri && cargo fmt --check && cargo clippy && cargo test
```
## Pull Request Guidelines
1.**Open an issue first** for new features — PRs for features that are not a good fit may be closed.
2.**Fork and branch** — Create a feature branch from `main` (e.g., `feat/my-feature` or `fix/issue-123`).
3.**Keep PRs focused** — One feature or fix per PR. Avoid unrelated changes.
4.**Follow the PR template** — Fill in the summary, related issue, and checklist.
### PR Checklist
- [ ]`pnpm typecheck` passes
- [ ]`pnpm format:check` passes
- [ ]`cargo clippy` passes (if Rust code changed)
- [ ] Updated i18n files if user-facing text changed
### Commit Convention
We use [Conventional Commits](https://www.conventionalcommits.org/):
```
feat(provider): add support for new provider
fix(tray): resolve menu not updating after switch
docs(readme): update installation instructions
ci: add format check workflow
chore(deps): update dependencies
```
## AI-Assisted Contributions
We welcome AI-assisted contributions, but **the responsibility stays with you**. AI tools lower the cost of writing code — they do not lower the cost of reviewing it. Maintainers are not obligated to clean up AI-generated output.
By submitting a PR, you agree to the following:
1.**You have read and understood your code.** You must be able to explain any line in your PR. If you cannot, it is not ready for review.
2.**You have tested it yourself.** Every change must be verified locally — not just "it looks right." Do not submit code for platforms or features you cannot test.
3.**PRs must be small and focused.** One issue, one PR. Large, sprawling, multi-topic PRs will be closed.
4.**Open an issue first.** Drive-by PRs with no prior discussion — especially AI-generated ones — may be closed without review.
5.**Maintainers may close without explanation.** PRs that appear to be unreviewed AI output — hallucinated fixes, unnecessary refactors, bulk changes with no context — may be closed at the maintainer's discretion.
**In short**: AI is a tool, not a substitute for understanding. Use it to help you contribute better, not to shift work onto maintainers.
## Internationalization (i18n)
CC Switch supports three languages. When modifying user-facing text:
1. Update **all three** locale files:
-`src/locales/en/translation.json`
-`src/locales/zh/translation.json`
-`src/locales/ja/translation.json`
2. Use the `t()` function from i18next for all UI text.
3. Never hardcode user-facing strings.
## Questions?
- [Open a question](https://github.com/farion1231/cc-switch/issues/new?template=question.yml)
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.moonshot.cn/console?aff=cc-switch)**
<td>Thanks to PackyCode for sponsoring this project! PackyCode is a reliable and efficient API relay service provider, offering relay services for Claude Code, Codex, Gemini, and more. PackyCode provides special discounts for our software users: register using <a href="https://www.packyapi.com/register?aff=cc-switch">this link</a> and enter the "cc-switch" promo code during first recharge to get 10% off.</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>Thanks to AICodeMirror for sponsoring this project! AICodeMirror provides official high-stability relay services for Claude Code / Codex / Gemini CLI, with enterprise-grade concurrency, fast invoicing, and 24/7 dedicated technical support.
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>
<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>
<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.
It also supports enterprise-grade concurrency and provides a dedicated management platform for enterprise customers — formal contracts and invoicing are available; visit the official website for contact details.
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>
<td>Thanks to Dola seed for sponsoring this project! Dola Seed 2.0 is a full‑modal 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, long‑task 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, end‑to‑end 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>
<td>Thanks to SiliconFlow for sponsoring this project! SiliconFlow is a high-performance AI infrastructure and model API platform, providing fast and reliable access to language, speech, image, and video models in one place. With pay-as-you-go billing, broad multimodal model support, high-speed inference, and enterprise-grade stability, SiliconFlow helps developers and teams build and scale AI applications more efficiently. Register via <a href="https://cloud.siliconflow.cn/i/drGuwc9k">this link</a> and complete real-name verification to receive ¥16 in bonus credit, usable across models on the platform. SiliconFlow is also now compatible with OpenClaw, allowing users to connect a SiliconFlow API key and call major AI models for free.</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>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>Thanks to Compshare for sponsoring this project! Compshare is UCloud's AI cloud platform, providing stable and comprehensive domestic and international model APIs with just one key. Featuring cost-effective monthly and per-use domestic-model Coding Plan packages, alongside stable officially-relayed overseas models. Supports Claude Code, Codex, and API access. Enterprise-grade high concurrency, 24/7 technical support, and self-service invoicing. Users who register via <a href="https://www.compshare.cn/coding-plan?ytag=GPU_YY_YX_git_cc-switch">this link</a> will receive a free 5 CNY platform trial credit!</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>
<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>Thanks to SSSAiCode for sponsoring this project! SSSAiCode is a stable and reliable API relay service, dedicated to providing stable, reliable, and affordable Claude and Codex model services, with same-day fast invoicing. SSSAiCode offers a special deal for CC Switch users: register via <a href="https://www.sssaicode.com/register?ref=DCP0SM">this link</a> to enjoy $10 extra credit on every top-up!</td>
<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>
<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>
<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>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>
<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>
<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>
<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>
<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>
<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>
<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>
</table>
</details>
## 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.
**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
- **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
- **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
- **Built-in Utilities** — Includes various utilities for first-launch login confirmation, signature bypass, plugin extension sync, and more
- **7 supported tools, 50+ presets** — Claude Code, Claude Desktop, Codex, Gemini CLI, 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
### MCP, Prompts & Skills
- **Unified MCP panel** — Manage MCP servers across Claude, Codex, Gemini, 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
### Usage & Cost Tracking
- **Usage dashboard** — Track spending, requests, and tokens with trend charts, detailed request logs, and custom per-model pricing
### Session Manager & Workspace
- Browse, search, and restore conversation history across supported session sources
- **Cloud sync** — Custom config directory (Dropbox, OneDrive, iCloud, NAS) and WebDAV server sync
- **Deep Link** (`ccswitch://`) — Import providers, MCP servers, prompts, and skills via URL
- Dark / Light / System theme, auto-launch, auto-updater, atomic writes, auto-backups, i18n (zh/zh-TW/en/ja)
## FAQ
<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.
</details>
<details>
<summary><strong>Do I need to restart the terminal after switching providers?</strong></summary>
For most tools, yes — restart your terminal or the CLI tool for changes to take effect. The exception is **Claude Code**, which currently supports hot-switching of provider data without a restart.
</details>
<details>
<summary><strong>My plugin configuration disappeared after switching providers — what happened?</strong></summary>
CC Switch provides a "Shared Config Snippet" feature to pass common data (beyond API keys and endpoints) between providers. Go to "Edit Provider" → "Shared Config Panel" → click "Extract from Current Provider" to save all common data. When creating a new provider, check "Write Shared Config" (enabled by default) to include plugin data in the new provider. All your configuration items are preserved in the default provider imported when you first launched the app.
CC Switch for macOS is code-signed and notarized by Apple. You can download and install it directly — no extra steps needed. We recommend using the `.dmg` installer.
</details>
<details>
<summary><strong>Why can't I delete the currently active provider?</strong></summary>
CC Switch follows a "minimal intrusion" design principle — even if you uninstall the app, your CLI tools will continue to work normally. The system always keeps one active configuration, because deleting all configurations would make the corresponding CLI tool unusable. If you rarely use a specific CLI tool, you can hide it in Settings. To switch back to official login, see the next question.
</details>
<details>
<summary><strong>How do I switch back to official login?</strong></summary>
Add an official provider from the preset list. After switching to it, run the Log out / Log in flow, and then you can freely switch between the official provider and third-party providers. Codex supports switching between different official providers, making it easy to switch between multiple Plus or Team accounts.
</details>
<details>
<summary><strong>Where is my data stored?</strong></summary>
- **Backups**: `~/.cc-switch/backups/` (auto-rotated, keeps 10 most recent)
- **Skills**: `~/.cc-switch/skills/` (symlinked to corresponding apps by default)
- **Skill Backups**: `~/.cc-switch/skill-backups/` (created automatically before uninstall, keeps 20 most recent)
</details>
## Documentation
For detailed guides on every feature, check out the **[User Manual](docs/user-manual/en/README.md)** — covering provider management, MCP/Prompts/Skills, proxy & failover, and more.
## Quick Start
### Basic Usage
1.**Add Provider**: Click "Add Provider" → Choose a preset or create custom configuration
2.**Switch Provider**:
- Main UI: Select provider → Click "Enable"
- System Tray: Click provider name directly (instant effect)
3.**Takes Effect**: Restart your terminal or the corresponding CLI tool to apply changes (Claude Code does not require a restart)
4.**Back to Official**: Add an "Official Login" preset, restart the CLI tool, then follow its login/OAuth flow
### MCP, Prompts, Skills & Sessions
- **MCP**: Click the "MCP" button → Add servers via templates or custom config → Toggle per-app sync
- **Prompts**: Click "Prompts" → Create presets with Markdown editor → Activate to sync to live files
- **Sessions**: Click "Sessions" → Browse, search, and restore conversation history across supported session sources
> **Note**: On first launch, you can manually import existing CLI tool configs as the default provider.
## Download & Installation
### System Requirements
- **Windows**: Windows 10 and above
- **macOS**: macOS 12 (Monterey) and above
- **Linux**: Ubuntu 22.04+ / Debian 11+ / Fedora 34+ and other mainstream distributions
### Windows Users
Download the latest `CC-Switch-v{version}-Windows.msi` installer or `CC-Switch-v{version}-Windows-Portable.zip` portable version from the [Releases](../../releases) page.
> **Flatpak**: Not included in official releases. You can build it yourself from the `.deb` — see [`flatpak/README.md`](flatpak/README.md) for instructions.
- 配置目录:`~/.codex/`
- live 主配置:`auth.json`(必需)、`config.toml`(可为空)
- API Key 字段:`auth.json` 中使用 `OPENAI_API_KEY`
- 切换行为(不再写“副本文件”):
- 供应商配置统一保存在 `~/.cc-switch/config.json`
- 切换时将目标供应商写回 live 文件(`auth.json` + `config.toml`)
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.moonshot.cn/console?aff=cc-switch)**
<td>Danke an PackyCode für die Unterstützung dieses Projekts! PackyCode ist ein zuverlässiger und effizienter API-Relay-Anbieter, der Relay-Dienste für Claude Code, Codex, Gemini und mehr bereitstellt. PackyCode bietet Sonderrabatte für Nutzer unserer Software: Registrieren Sie sich über <a href="https://www.packyapi.com/register?aff=cc-switch">diesen Link</a> und geben Sie beim ersten Aufladen den Gutscheincode „cc-switch" ein, um 10 % Rabatt zu erhalten.</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>Danke an AICodeMirror für die Unterstützung dieses Projekts! AICodeMirror stellt offizielle, hochstabile Relay-Dienste für Claude Code / Codex / Gemini CLI bereit, mit unternehmensgerechter Nebenläufigkeit, schneller Rechnungsstellung und rund um die Uhr verfügbarem dediziertem technischem Support.
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>
<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>
<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.
Er unterstützt zudem unternehmensgerechte Nebenläufigkeit und stellt Unternehmenskunden eine dedizierte Verwaltungsplattform bereit — formelle Verträge und Rechnungsstellung sind verfügbar; Kontaktdaten finden Sie auf der offiziellen Website.
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>
<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>
<td>Danke an SiliconFlow für die Unterstützung dieses Projekts! SiliconFlow ist eine leistungsstarke KI-Infrastruktur- und Modell-API-Plattform, die schnellen und zuverlässigen Zugriff auf Sprach-, Audio-, Bild- und Videomodelle an einem Ort bietet. Mit nutzungsbasierter Abrechnung, breiter Unterstützung multimodaler Modelle, Hochgeschwindigkeitsinferenz und unternehmensgerechter Stabilität hilft SiliconFlow Entwicklern und Teams, KI-Anwendungen effizienter zu erstellen und zu skalieren. Registrieren Sie sich über <a href="https://cloud.siliconflow.cn/i/drGuwc9k">diesen Link</a> und schließen Sie die Identitätsverifizierung ab, um ein Bonusguthaben von ¥16 zu erhalten, das für alle Modelle der Plattform nutzbar ist. SiliconFlow ist zudem nun mit OpenClaw kompatibel, sodass Nutzer einen SiliconFlow-API-Schlüssel verbinden und große KI-Modelle kostenlos aufrufen können.</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>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 20–50 % Rabatt, exklusive Claude-Code-Modelle zu 66 % Rabatt! <a href="https://www.dmxapi.cn/register?aff=bUHu">Hier registrieren</a></td>
<td>Danke an Compshare für die Unterstützung dieses Projekts! Compshare ist die KI-Cloud-Plattform von UCloud, die mit nur einem Schlüssel stabile und umfassende in- und ausländische Modell-APIs bereitstellt. Sie bietet kostengünstige Coding-Plan-Pakete für inländische Modelle mit monatlicher und nutzungsbasierter Abrechnung sowie stabile, offiziell gerelayte ausländische Modelle. Unterstützt Claude Code, Codex und API-Zugriff. Unternehmensgerechte hohe Nebenläufigkeit, technischer Support rund um die Uhr und Self-Service-Rechnungsstellung. Wer sich über <a href="https://www.compshare.cn/coding-plan?ytag=GPU_YY_YX_git_cc-switch">diesen Link</a> registriert, erhält ein kostenloses Plattform-Testguthaben von 5 CNY!</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>
<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>Danke an SSSAiCode für die Unterstützung dieses Projekts! SSSAiCode ist ein stabiler und zuverlässiger API-Relay-Dienst, der sich der Bereitstellung stabiler, zuverlässiger und erschwinglicher Claude- und Codex-Modelldienste widmet, mit schneller Rechnungsstellung am selben Tag. SSSAiCode bietet ein besonderes Angebot für CC-Switch-Nutzer: Registrieren Sie sich über <a href="https://www.sssaicode.com/register?ref=DCP0SM">diesen Link</a> und erhalten Sie bei jeder Aufladung 10 $ zusätzliches Guthaben!</td>
<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>
<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>
<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>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>
<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>
<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>
<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>
<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>
<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>
<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>
</table>
</details>
## 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.
**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
- **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
- **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
- **Integrierte Hilfsprogramme** — Enthält diverse Hilfsprogramme für die Login-Bestätigung beim Erststart, das Umgehen von Signaturen, die Synchronisierung von Plugin-Erweiterungen und mehr
- **7 unterstützte Werkzeuge, 50+ Presets** — Claude Code, Claude Desktop, Codex, Gemini CLI, 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
### 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
- **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
### Nutzungs- & Kostenverfolgung
- **Nutzungs-Dashboard** — Verfolgen Sie Ausgaben, Anfragen und Token mit Trenddiagrammen, detaillierten Anfrageprotokollen und eigener Preisgestaltung pro Modell
### Session Manager & Workspace
- Gesprächsverlauf aus unterstützten Sitzungsquellen durchsuchen, suchen und wiederherstellen
- **Workspace-Editor** (OpenClaw) — Bearbeiten Sie Agent-Dateien (AGENTS.md, SOUL.md usw.) mit Markdown-Vorschau
<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.
</details>
<details>
<summary><strong>Muss ich das Terminal nach einem Anbieterwechsel neu starten?</strong></summary>
Bei den meisten Werkzeugen ja — starten Sie Ihr Terminal oder das CLI-Werkzeug neu, damit die Änderungen wirksam werden. Die Ausnahme ist **Claude Code**, das derzeit das Hot-Switching von Anbieterdaten ohne Neustart unterstützt.
</details>
<details>
<summary><strong>Meine Plugin-Konfiguration ist nach einem Anbieterwechsel verschwunden — was ist passiert?</strong></summary>
CC Switch bietet eine Funktion „Gemeinsames Konfigurations-Snippet", um gemeinsame Daten (über API-Schlüssel und Endpunkte hinaus) zwischen Anbietern weiterzugeben. Gehen Sie zu „Anbieter bearbeiten" → „Panel für gemeinsame Konfiguration" → klicken Sie auf „Aus aktuellem Anbieter extrahieren", um alle gemeinsamen Daten zu speichern. Aktivieren Sie beim Anlegen eines neuen Anbieters die Option „Gemeinsame Konfiguration schreiben" (standardmäßig aktiviert), um die Plugin-Daten in den neuen Anbieter aufzunehmen. Alle Ihre Konfigurationspunkte bleiben im Standardanbieter erhalten, der beim ersten Start der App importiert wurde.
</details>
<details>
<summary><strong>Installation unter macOS</strong></summary>
CC Switch für macOS ist von Apple code-signiert und notarisiert. Sie können es direkt herunterladen und installieren — es sind keine zusätzlichen Schritte erforderlich. Wir empfehlen die Verwendung des `.dmg`-Installationsprogramms.
</details>
<details>
<summary><strong>Warum kann ich den aktuell aktiven Anbieter nicht löschen?</strong></summary>
CC Switch folgt dem Designprinzip der „minimalen Eingriffstiefe" — selbst wenn Sie die App deinstallieren, funktionieren Ihre CLI-Werkzeuge weiterhin normal. Das System behält immer eine aktive Konfiguration bei, da das Löschen aller Konfigurationen das entsprechende CLI-Werkzeug unbrauchbar machen würde. Wenn Sie ein bestimmtes CLI-Werkzeug selten verwenden, können Sie es in den Einstellungen ausblenden. Wie Sie zurück zum offiziellen Login wechseln, erfahren Sie in der nächsten Frage.
</details>
<details>
<summary><strong>Wie wechsle ich zurück zum offiziellen Login?</strong></summary>
Fügen Sie einen offiziellen Anbieter aus der Preset-Liste hinzu. Führen Sie nach dem Wechsel den Abmelde-/Anmelde-Vorgang aus; anschließend können Sie frei zwischen dem offiziellen Anbieter und Drittanbietern wechseln. Codex unterstützt den Wechsel zwischen verschiedenen offiziellen Anbietern, was das Umschalten zwischen mehreren Plus- oder Team-Konten erleichtert.
</details>
<details>
<summary><strong>Wo werden meine Daten gespeichert?</strong></summary>
- **Backups**: `~/.cc-switch/backups/` (automatisch rotiert, behält die 10 neuesten)
- **Skills**: `~/.cc-switch/skills/` (standardmäßig per Symlink mit den entsprechenden Apps verbunden)
- **Skill-Backups**: `~/.cc-switch/skill-backups/` (vor der Deinstallation automatisch erstellt, behält die 20 neuesten)
</details>
## Dokumentation
Ausführliche Anleitungen zu jeder Funktion finden Sie im **[Benutzerhandbuch](docs/user-manual/en/README.md)** — es deckt Anbieterverwaltung, MCP/Prompts/Skills, Proxy & Failover und mehr ab.
## Schnellstart
### Grundlegende Verwendung
1.**Anbieter hinzufügen**: Klicken Sie auf „Add Provider" → Wählen Sie ein Preset oder erstellen Sie eine eigene Konfiguration
2.**Anbieter wechseln**:
- Hauptoberfläche: Anbieter auswählen → auf „Enable" klicken
- System-Tray: Anbietername direkt anklicken (sofort wirksam)
3.**Wirksam werden**: Starten Sie Ihr Terminal oder das entsprechende CLI-Werkzeug neu, um die Änderungen anzuwenden (Claude Code erfordert keinen Neustart)
4.**Zurück zum Offiziellen**: Fügen Sie ein „Official Login"-Preset hinzu, starten Sie das CLI-Werkzeug neu und folgen Sie dann seinem Login-/OAuth-Vorgang
### MCP, Prompts, Skills & Sessions
- **MCP**: Klicken Sie auf die Schaltfläche „MCP" → Server über Vorlagen oder eigene Konfiguration hinzufügen → Synchronisierung pro App umschalten
- **Prompts**: Klicken Sie auf „Prompts" → Presets mit dem Markdown-Editor erstellen → Aktivieren, um mit den Live-Dateien zu synchronisieren
- **Skills**: Klicken Sie auf „Skills" → GitHub-Repositorys durchsuchen → mit einem Klick in unterstützte Apps installieren
- **Sessions**: Klicken Sie auf „Sessions" → Gesprächsverlauf aus unterstützten Sitzungsquellen durchsuchen, suchen und wiederherstellen
> **Hinweis**: Beim Erststart können Sie bestehende CLI-Werkzeug-Konfigurationen manuell als Standardanbieter importieren.
## Download & Installation
### Systemanforderungen
- **Windows**: Windows 10 und höher
- **macOS**: macOS 12 (Monterey) und höher
- **Linux**: Ubuntu 22.04+ / Debian 11+ / Fedora 34+ und andere gängige Distributionen
### Windows-Nutzer
Laden Sie das neueste Installationsprogramm `CC-Switch-v{version}-Windows.msi` oder die portable Version `CC-Switch-v{version}-Windows-Portable.zip` von der Seite [Releases](../../releases) herunter.
### macOS-Nutzer
**Methode 1: Installation über Homebrew (empfohlen)**
```bash
brew install --cask cc-switch
```
Aktualisieren:
```bash
brew upgrade --cask cc-switch
```
**Methode 2: Manueller Download**
Laden Sie `CC-Switch-v{version}-macOS.dmg` (empfohlen) oder `.zip` von der Seite [Releases](../../releases) herunter.
> **Hinweis**: CC Switch für macOS ist von Apple code-signiert und notarisiert. Sie können es direkt installieren und öffnen.
### Arch-Linux-Nutzer
**Installation über paru (empfohlen)**
```bash
paru -S cc-switch-bin
```
### Linux-Nutzer
Laden Sie den neuesten Linux-Build von der Seite [Releases](../../releases) herunter:
> **Flatpak**: Nicht in den offiziellen Releases enthalten. Sie können es selbst aus dem `.deb` bauen — eine Anleitung finden Sie unter [`flatpak/README.md`](flatpak/README.md).
Bitte stellen Sie vor dem Einreichen von PRs Folgendes sicher:
- Typprüfung besteht: `pnpm typecheck`
- Formatprüfung besteht: `pnpm format:check`
- Unit-Tests bestehen: `pnpm test:unit`
Eröffnen Sie für neue Funktionen bitte vor dem Einreichen eines PR ein Issue zur Diskussion. PRs für Funktionen, die nicht gut zum Projekt passen, können geschlossen werden.
## Star History
[](https://www.star-history.com/#farion1231/cc-switch&Date)
Kimi K2.7 Code は Moonshot AI が開発した、コーディングに特化したオープンソースのエージェントモデルです。コーディング能力とエージェント性能が全面的に強化され、実世界の長程コーディングタスクで大幅な向上を実現し、複雑なソフトウェアエンジニアリングのワークフロー全体でエンドツーエンドのタスク成功率を高めます。さらに K2.7 Code は推論効率を改善し、K2.6 と比べて推論トークンの消費を約 30% 削減します。**[ここをクリックして Kimi を体験する](https://platform.moonshot.cn/console?aff=cc-switch)**
<td>SiliconFlow のご支援に感謝します!SiliconFlow は高性能 AI インフラストラクチャおよびモデル API プラットフォームで、言語・音声・画像・動画モデルへの高速かつ信頼性の高いアクセスをワンストップで提供します。従量課金制、豊富なマルチモーダルモデル対応、高速推論、エンタープライズグレードの安定性を備え、開発者やチームがより効率的に AI アプリケーションを構築・拡張できるようサポートします。<a href="https://cloud.siliconflow.cn/i/drGuwc9k">このリンク</a>から登録し、本人確認を完了すると、プラットフォーム内の全モデルで利用可能な ¥16 のボーナスクレジットが付与されます。SiliconFlow は OpenClaw にも対応しており、SiliconFlow の API キーを接続することで主要な AI モデルを無料で呼び出すことができます。</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>
<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>
<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>
CC Switch には「共有設定スニペット」機能があり、APIキーやエンドポイント以外の共通データをプロバイダ間で引き継ぐことができます。「プロバイダ編集」→「共有設定パネル」→「現在のプロバイダから抽出」をクリックして、すべての共通データを保存してください。新しいプロバイダを作成する際に「共有設定を書き込む」にチェック(デフォルトで有効)を入れれば、プラグインなどのデータが新しいプロバイダ設定に含まれます。すべての設定項目は、アプリ初回起動時にインポートされたデフォルトプロバイダに保存されており、失われることはありません。
プリセットリストから公式プロバイダを追加してください。切り替え後、ログアウト/ログインのフローを実行すれば、以降は公式プロバイダとサードパーティプロバイダを自由に切り替えられます。Codex では異なる公式プロバイダ間の切り替えに対応しており、複数の Plus アカウントや Team アカウントの切り替えに便利です。
[](https://www.star-history.com/#farion1231/cc-switch&Date)
<td>感谢硅基流动赞助了本项目!硅基流动是一个高性能 AI 基础设施与模型 API 平台,一站式提供语言、语音、图像、视频等多模态模型的快速、可靠访问。平台支持按量计费、丰富的多模态模型选择、高速推理和企业级稳定性,帮助开发者和团队更高效地构建和扩展 AI 应用。通过<a href="https://cloud.siliconflow.cn/i/drGuwc9k">此链接</a>注册并完成实名认证,即可获得 ¥16 奖励金,可在平台内跨模型使用。硅基流动现已兼容 OpenClaw,用户可接入硅基流动 API Key 免费调用主流 AI 模型。</td>
<td>感谢优云智算赞助了本项目!优云智算是UCloud旗下AI云平台,提供稳定、全面的国内外模型API,仅一个key即可调用。主打包月、按次的高性价比 国模Coding Plan套餐,同时提供官转稳定海外模型。支持接入 Claude Code、Codex 及 API 调用。支持企业高并发、7*24技术支持、自助开票。通过<a href="https://www.compshare.cn/coding-plan?ytag=GPU_YY_YX_git_cc-switch">此链接</a>注册的用户,可得免费5元平台体验金!</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>感谢 ETok.ai 赞助了本项目!ETok.ai 致力于打造一站式 AI 编程工具服务平台。我们提供 Claude Code 专业套餐及技术社群服务,同时支持 Google Gemini 和 OpenAI Codex。通过精心设计的套餐方案和专业的技术社群,为开发者提供稳定的服务保障和持续的技术支持,让 AI 辅助编程真正成为开发者的生产力工具。点击<a href="https://etok.ai">这里</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>
<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>
CC Switch 使用“通用配置片段”功能,在不同的供应商之间传递 Key 和请求地址之外的通用数据,您可以在“编辑供应商”菜单的“通用配置面板”里,点击“从当前供应商提取”,把所有的通用数据提取到通用配置中,之后在新建“供应商”的时候,只要勾选“写入通用配置”(默认勾选),就会把插件等数据写入到新的供应商配置中。您的所有配置项都会保存在运行本软件的时候,第一次导入的默认供应商里面,不会丢失。
</details>
<details>
<summary><strong>macOS 安装</strong></summary>
CC Switch macOS 版本已通过 Apple 代码签名和公证,可直接下载安装,无需额外操作。推荐使用 `.dmg` 安装包。
[](https://www.star-history.com/#farion1231/cc-switch&Date)
- **Acknowledgment / 确认**: within 48 hours / 48 小时内
- **Initial assessment / 初步评估**: within 7 days / 7 天内
- **Fix for critical issues / 关键问题修复**: within 14 days / 14 天内
## Disclosure Policy / 披露政策
We follow a coordinated disclosure process:
我们遵循协调披露流程:
1. The reporter submits the vulnerability privately. / 报告者私下提交漏洞。
2. We confirm and work on a fix. / 我们确认并修复漏洞。
3. A patch release is published. / 发布修复版本。
4. The vulnerability is publicly disclosed. / 公开披露漏洞详情。
Reporters will be credited in the release notes unless they prefer to remain anonymous.
除非报告者希望匿名,否则将在发布说明中致谢。
## Security Updates / 安全更新
Security fixes are released as patch versions and announced via [GitHub Releases](https://github.com/farion1231/cc-switch/releases). We recommend always updating to the latest version.
# Using DeepSeek-Style Chat APIs in Codex: CC Switch Local Routing Guide
> Applies to CC Switch 3.16.0 and nearby versions. This guide is based on the repository documentation and code, and uses DeepSeek 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 DeepSeek, Kimi, MiniMax, SiliconFlow, and many other providers expose the OpenAI Chat Completions shape, usually `/chat/completions`. These two protocols use different request bodies, streaming events, and response structures. If you put a Chat endpoint directly into Codex configuration, common results include an incorrect model list, 404/400 requests, or streaming responses that Codex cannot parse correctly.
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.

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.
- An API key from DeepSeek or another Chat Completions provider.
DeepSeek's official documentation currently lists the OpenAI-compatible base URL as `https://api.deepseek.com` (other providers often use a base URL with a `/v1` suffix), and the Chat API path as `/chat/completions`. CC Switch's DeepSeek preset already contains these details, so prefer the preset 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.
Choose the built-in `DeepSeek` preset. You only need to do two things:
- Enter your DeepSeek API key.
- Save the provider.

The preset already includes DeepSeek's request base URL, default model, model menu, thinking/reasoning parameters, and automatically enables `Needs Local Routing`. You can adjust the default model or model display names if needed; 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.

After routing is enabled, CC Switch points Codex's live configuration to the local route and manages authentication with a placeholder. The real DeepSeek 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 DeepSeek 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 DeepSeek preset, such as `DeepSeek V4 Flash`. 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
DeepSeek, Kimi, 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 `API Format` to `OpenAI Chat Completions (requires routing)`.
If the upstream provider directly supports the OpenAI Responses API, you do not need to enable `Needs Local Routing`; CC Switch can connect through Responses directly without Chat conversion.
## FAQ
**Codex reports 404 or cannot find `/responses`**
Usually Codex routing is not enabled, or the upstream Chat base URL was written directly into Codex manually. Check whether `~/.codex/config.toml` points to `http://127.0.0.1:15721/v1`.
**DeepSeek upstream reports 404**
If you are using the built-in DeepSeek 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 DeepSeek 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 DeepSeek; 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)
- [DeepSeek API Docs: Your First API Call](https://api-docs.deepseek.com/)
- [DeepSeek API Docs: Create Chat Completion](https://api-docs.deepseek.com/api/create-chat-completion)
- [DeepSeek API Docs: Multi-round Conversation](https://api-docs.deepseek.com/guides/multi_round_chat)
# Can't See Custom Models in the Codex Desktop App? (FAQ)
> Applies to CC Switch v3.16.1 and later. This article explains "why the Codex desktop app can't see custom models" and the available mitigation; for the detailed step-by-step setup with screenshots, see [Keep Codex Remote Control and Official Plugins While Using Third-Party APIs](./codex-official-auth-preservation-guide-en.md).
## Symptom
After you switch Codex to a third-party / custom model in CC Switch (DeepSeek, Kimi, GLM, MiniMax, an aggregator, etc.):
- The model picker in the **Codex desktop app** doesn't show these custom models — often only the official default model remains, and the reasoning level falls back to the official default;
- but everything works fine in the **command-line `codex`**`/model` menu.
Many users have run into this. Here's why, and what you can do about it.
## Why this happens
This is **not a CC Switch local-config problem and not a CC Switch bug** — it is the **Codex desktop app's (the upstream closed-source client's) own model-gating behavior**.
The Codex desktop app's model picker decides which models to allow based on your **current login identity**: when it can't detect an official ChatGPT / Codex login state, it forces the picker back to the official default model and hides the custom models you configured through `config.toml` (the reasoning level falls back to the official default too). The upstream has marked "exposing custom-provider models in the desktop GUI" as not planned, so CC Switch cannot fully fix this at the desktop-GUI level.
The command-line `codex``/model` menu and request routing both recognize the custom providers in `config.toml` correctly — **only the desktop GUI picker is constrained by this gating layer**.
## Mitigation: keep the official login
The workaround is to **keep the official login state** so the desktop app's gating allows your custom models through. The key points are below (the full step-by-step setup with screenshots is in the linked guide):
1. Log in once with an official ChatGPT / Codex account in Codex (a Free subscription is enough) to keep the official login state.
2. In CC Switch, enable `Settings -> General -> Codex App Enhancements -> Keep official login when switching third-party providers` (**off by default**).
3. Enable local routing and route Codex through it for this third-party provider (required for Chat Completions providers such as DeepSeek / Kimi / MiniMax).
4. Fully quit and restart Codex.
Once enabled, CC Switch preserves the official login state in `~/.codex/auth.json` when switching to a third-party provider and writes the third-party key into `config.toml`, so the desktop app still recognizes the official login identity, the gating lets your models through, and the custom models you configured reappear in the picker. **The preserved official token is never sent to the third party** — third-party model requests still use the key you configured, forwarded through the local route.
> 📖 Detailed step-by-step setup: [Keep Codex Remote Control and Official Plugins While Using Third-Party APIs](./codex-official-auth-preservation-guide-en.md)
## Still can't see them?
- **Confirm the toggle is on**: this toggle is off by default, and many people overwrite the official login state the first time they switch to a third-party provider, which is exactly why the models disappear — enable it as above.
- **The official login state expires**: if you haven't used the official login for several days, the picker may go empty again once the token expires — log in to the official account once more to restore it.
- **Command-line fallback diagnosis**: run `codex debug models` to list the models actually available on the CLI side and confirm the model itself is configured correctly (the CLI is unaffected by this gating).
- Individual Codex desktop versions may behave slightly differently; this is in the upstream client's domain, and no CC Switch version can fully fix it at the desktop-GUI level.
## References
- [Keep Codex Remote Control and Official Plugins While Using Third-Party APIs](./codex-official-auth-preservation-guide-en.md)
- [Codex DeepSeek local routing hands-on guide](./codex-deepseek-routing-guide-en.md)
# Keep Codex Remote Control and Official Plugins While Using Third-Party APIs: CC Switch Setup Guide
> Applies to CC Switch v3.16.1 and later. This guide is based on the current code, user manual, and v3.16.1 release notes. Screenshots use de-identified sample data and do not include real Access Tokens or API keys.
## What this guide solves
Many Codex users want both of these at the same time:
1. Use models from DeepSeek, Kimi, GLM, MiniMax, SiliconFlow, or other third-party APIs, or use GPT models through an aggregator.
2. Keep Codex official-app capabilities such as mobile remote control and official plugins.
Previously, when switching to a third-party provider, the old behavior wrote the third-party API key into Codex `auth.json`, which could overwrite the original official ChatGPT / Codex login cache. The third-party model worked, but features that depend on the official login state disappeared.
The **Codex App Enhancements** switch added in v3.16.1 solves this conflict: the official Access Token stays in `auth.json`, while third-party provider information is written to `config.toml`. Codex App can still see an official account, but actual model requests follow the third-party provider currently selected in CC Switch.
This behavior already existed in v3.16.0 and was enabled by default. After some users reported that they did not want this behavior, v3.16.1 turned it into an explicit switch.
## Quick answer
Recommended order:
1. In the CC Switch Codex panel, switch to `OpenAI Official`.
2. Start Codex and log in once with an official ChatGPT / Codex account. A Free subscription is enough.
3. Return to CC Switch and enable `Settings -> General -> Codex App Enhancements -> Keep official login when switching third-party providers`.
4. Add or switch to a third-party Codex provider.
5. If the provider uses the Chat Completions protocol, such as DeepSeek / Kimi / MiniMax, also enable local routing and route Codex through it.
6. Restart Codex so `config.toml` and the model catalog are reloaded.

## Prerequisites
Prepare the following:
- CC Switch v3.16.1 or later.
- Codex installed and able to start. Installing both the app and CLI is recommended.
- An official ChatGPT / Codex account that can log in to Codex. A Free subscription is enough.
- A third-party API key, such as DeepSeek, Kimi, GLM, MiniMax, OpenRouter, SiliconFlow, or similar.
Do not manually copy or share the contents of `~/.codex/auth.json`. It stores official login cache and Access Tokens, so it is sensitive.
## Step 1: Switch back to OpenAI Official and complete official login
Open CC Switch and switch to the top-level `Codex` tab. First select the `OpenAI Official` provider, or add it from the preset providers if it is missing, and make it the current provider.

Then start Codex, preferably the CLI, and follow the official login flow to sign in with your ChatGPT / Codex account. This account can be on the Free plan. In this setup, it mainly preserves the official identity required by Codex App, and does not pay for third-party model usage.
After login, Codex stores the official login cache in `~/.codex/auth.json`. The key point for the following steps is: do not let third-party provider switching overwrite this file again.
## Step 2: Enable Codex App Enhancements
Return to CC Switch and open:
```text
Settings -> General -> Codex App Enhancements
```
Enable:
```text
Keep official login when switching third-party providers
```
This switch is off by default because some users do not want this behavior. Enable it only when you explicitly want "third-party API + official remote control / official plugins" at the same time.
After it is enabled, backend switching for third-party Codex providers uses a config-only write path:
-`auth.json`: keeps the official ChatGPT / Codex login cache.
-`config.toml`: stores the active third-party provider's model, endpoint, `model_provider`, and provider-scoped `experimental_bearer_token`.
## Step 3: Add a third-party Codex provider
Return to the Codex panel and click the plus button in the upper-right corner to add a provider. Prefer built-in presets such as DeepSeek, Kimi, MiniMax, GLM, or SiliconFlow.
Using DeepSeek as an example, after selecting the preset, you only need to enter the API key. The preset automatically configures the base URL, default model, model mapping table, and "Needs Local Routing" flag.
If your third-party provider natively supports the OpenAI Responses API, such as an aggregator that offers GPT models, local routing may not be needed.
If it only supports OpenAI Chat Completions, which is common for DeepSeek / Kimi / MiniMax paths, local routing must be enabled so CC Switch can convert Codex Responses requests into Chat Completions requests.
## Step 4: Enable local routing and route Codex when needed
Open:
```text
Settings -> Routing -> Local Routing
```
Complete two actions:
1. Turn on the main routing switch to start the local service. The default address is usually `127.0.0.1:15721`.
2. Under `Routing Enabled`, turn on `Codex`.

After takeover, Codex's live `config.toml` temporarily points to the CC Switch local route. The real third-party API key remains in the CC Switch provider configuration, and is projected into the `experimental_bearer_token` in `config.toml` when providers are switched.
## Step 5: Switch to the third-party provider and restart Codex
Return to the Codex provider list and enable the third-party provider you just added. After switching, restarting Codex is recommended for two reasons:
- Codex reads `config.toml` at startup.
- The Codex `/model` menu usually needs a restart before it reloads `model_catalog_json`.
After restart, you can run a quick verification:
- In Codex App, the account information still shows the official account. This is expected.
- In CC Switch, the current Codex provider is the third-party provider.
- If local routing is enabled, request logs or routing stats show Codex requests going through the local route.
- The third-party provider dashboard or balance records show actual model requests.
## How it works
Codex mainly uses two configuration files:
```text
~/.codex/auth.json
~/.codex/config.toml
```
They have different responsibilities:
-`auth.json` stores the official ChatGPT / Codex login cache, which Codex App needs to identify the official account and enable remote control and official plugins.
-`config.toml` stores runtime configuration such as the current model provider, base URL, model, model catalog, and provider-scoped token.
After `Keep official login when switching third-party providers` is enabled, CC Switch takes the third-party provider API key from the provider configuration and writes it under the current provider in `config.toml`:
```toml
model_provider="custom"
[model_providers.custom]
name="DeepSeek"
base_url="https://api.deepseek.com"
wire_api="responses"
experimental_bearer_token="sk-..."
```
At the same time, `auth.json` keeps the official login cache unchanged. Codex App can still identify the official account, while model requests follow the current provider and base URL in `config.toml`.
If the provider uses the Chat Completions protocol, CC Switch local routing adds another conversion layer:
```text
Codex Responses request
|
CC Switch local route
|
Third-party Chat Completions API
|
Converted back to Codex Responses response
```
This is why you can keep using official plugins / mobile remote control while moving model traffic to a third-party API.
## Side effects to understand
### Codex still shows the official account
This is the easiest part to misunderstand. After this capability is enabled, Codex App reads the official login state from `auth.json`, so it continues to display the official account.
That does not mean model requests are still going to official OpenAI. Actual traffic is determined by the current Codex provider in CC Switch, `config.toml`, and local routing logs.
### Do not use the Codex account display to judge billing
If you switch to DeepSeek, Codex can still display the official account, while model requests go to the DeepSeek API. Billing, quota, error codes, and data policy should all be understood according to the third-party provider. You can inspect specific request details in the usage panel.
### Restart Codex after changing model mappings
Codex reads the model catalog at startup. Even if CC Switch has generated a new model catalog, a running Codex process may not hot-load it, so restart Codex after editing model mappings.
### Turning the switch off returns to the old behavior
If `Keep official login when switching third-party providers` is turned off, third-party provider switching uses the compatibility behavior from older versions and may write `auth.json` again. If your goal is to keep official remote control and official plugins long term, keep this switch enabled.
## FAQ
**I switched to a third-party API. Why does Codex still show the official account?**
This is expected. Official account information comes from `auth.json`; the actual model provider comes from `config.toml` and the current provider in CC Switch.
**Is a Free subscription really enough?**
Yes. The official account is mainly used to obtain and preserve the official login state required by Codex App. Third-party model requests use the third-party API key configured in CC Switch.
**What should I do if official plugins or mobile remote control still do not work?**
Switch back to `OpenAI Official`, restart Codex, and complete official login once. Then confirm `Settings -> General -> Codex App Enhancements -> Keep official login when switching third-party providers` is enabled in CC Switch before switching back to the third-party provider.
**What if third-party requests return 404, the model list is wrong, or streaming responses are broken?**
If the provider uses Chat Completions, confirm that the provider form has `Needs Local Routing` enabled, and that `Settings -> Routing` has both the main routing switch and Codex takeover enabled.
**Can I switch back to OpenAI Official while local routing is enabled?**
Not recommended. CC Switch tries to prevent switching to official providers while local routing takeover is active, because accessing official APIs through a proxy may create account risk. Use official login only to preserve `auth.json`, and route model traffic to third-party providers.
**Why is this flow so complex? Can it be simplified?**
Because Codex App Enhancements and routing takeover can create unnecessary trouble for users who do not need them, these features are explicit switches instead of always-on behavior.
## References
- [Can't see custom models in the Codex desktop app? (FAQ)](./codex-desktop-custom-model-visibility-en.md)
- [Codex DeepSeek local routing hands-on guide](./codex-deepseek-routing-guide-en.md)
- [Add a Codex provider: Chat Completions routing and model mapping](../user-manual/en/2-providers/2.1-add.md)
ルーティング有効化後、Codex の live `config.toml` は一時的に CC Switch のローカルルートを指します。実際のサードパーティ API Key は CC Switch のプロバイダー設定内に残り、プロバイダー切り替え時に `config.toml` の `experimental_bearer_token` へ投影されます。
> Applies to CC Switch v3.16.x and later. This guide is based on the current code; every command and path can be verified by hand. Examples use de-identified data and contain no real session content or API keys.
## What this feature is
"Unified Codex session history" is a switch that CC Switch v3.16.x adds for Codex. You'll find it under **Settings -> General -> the "Codex App Enhancements" group** ("Codex App Enhancements" is the group title; the switch itself is called "Unified Codex session history"). Once enabled, **sessions from your official subscription (ChatGPT login / OpenAI API key) appear in the same history / resume list as sessions from every third-party provider CC Switch manages**—they are no longer split into two lists that can't see each other.
## What problem it solves
Codex classifies sessions by a "provider tag" (a field called `model_provider`), and **the resume / history list only shows sessions whose tag matches your currently active provider**. As a result, sessions are naturally sorted into two separate "drawers":
- Sessions from your official subscription go under Codex's built-in **`openai`** tag;
- Every third-party provider CC Switch manages goes under the **`custom`** tag.
The two drawers can't see each other. If you **switch frequently between official and third-party**, you'll hit this kind of fragmentation: "the session I was just chatting in with the official account disappeared from the history list after I switched to a third-party provider"—it isn't actually gone, it's just been sorted into the other drawer. This split both makes it easy to believe a session was lost, and makes it inconvenient to review and resume all your sessions in one place.
**This switch exists to eliminate that fragmentation**: it makes the official subscription run under the `custom` tag too, so official and third-party sessions merge into one list and everything is easy to find and resume in a single place.
> ✅ **One important premise that runs through this whole guide, please remember it first**: this feature (unify / migrate / restore) **only ever rewrites that one classification tag `model_provider` in your session records, and it automatically makes a backup of the original file before every rewrite**. It never deletes, clears, or overwrites a single line of your conversations. So whenever this guide later mentions "some sessions are no longer visible," it almost always means "they've been sorted into the other drawer," not "the data is gone." If you're truly worried, jump straight to the [symptom reference table](#i-feel-like-my-sessions-are-gone-symptom-reference-table) and [verify the files are still there by hand](#verify-by-hand-your-session-files-are-still-on-disk-the-most-important-section).
## How it works (one-line version)
Think of it as **two drawers + automatic backup**:
- By default, official sessions live in the `openai` drawer and third-party sessions live in the `custom` drawer, invisible to each other;
- The switch makes **the official side use the `custom` drawer too**, merging the two drawers into one shared list;
- You can optionally choose to "move" your **existing official sessions** into the shared drawer as well (this step is called **migration**; it's optional and requires you to opt in by checking a box), and **before anything is moved a backup copy is made first**, so the whole process is **reversible**;
- **Authentication is completely unaffected**—your official subscription still uses your ChatGPT login and still goes through the official backend; only the session's classification tag changes.
For the full mechanism (what gets injected, why it's reversible, how migration / restore guarantee no data loss) see [The core mental model](#the-core-mental-model-two-drawers--automatic-backup) and the [Advanced mechanism appendix](#advanced-mechanism-appendix-for-users-who-want-to-truly-understand-how-it-works) at the end.
## How to use it (at a glance)
1.**Enable**: Settings -> General -> Codex App Enhancements -> turn on "Unified Codex session history" -> in the dialog decide whether to check "Also migrate existing official session history" (check it if you want your **earlier** official sessions merged into the unified list too; leave it unchecked if you only want unification from now on) -> confirm. See [What happens when you enable it](#what-happens-when-you-enable-it-step-by-step).
2.**Disable**: turn the same switch off -> in the dialog keep "restore exactly from backup" checked (it's checked by default) -> confirm, and the official sessions you migrated in will be precisely flipped back to the official list. See [What happens when you disable it](#what-happens-when-you-disable-it-step-by-step).
3.**Feel like a session is gone?** Don't panic—jump to the [symptom reference table](#i-feel-like-my-sessions-are-gone-symptom-reference-table) to locate it by symptom, and use the commands in the [verify by hand](#verify-by-hand-your-session-files-are-still-on-disk-the-most-important-section) section to see for yourself that the files are all there.
---
## The core mental model: two drawers + automatic backup
To understand this feature, you only need to remember two things: **drawers** and **backups**.
### Drawers: how Codex classifies sessions
Every time you start a Codex session, Codex records a tag `model_provider` in the session file header, marking "which provider this session was chatted with." Codex's **resume / history list is filtered precisely by the currently active tag**—it only shows sessions whose tag matches "the provider you're on right now."
- Sessions from your official subscription (ChatGPT login / OpenAI API key) carry the built-in tag **`openai`**.
- Every third-party provider CC Switch manages uses the tag **`custom`**.
So by default, official sessions and third-party sessions are inherently invisible to each other—they live in two different drawers. This is **Codex's own design**, not CC Switch losing anything.
on the official provider on a third-party provider
The two drawers can't see each other.
```
**What the "Unified Codex session history" switch does is make the official subscription run under the `custom` tag too, merging the two drawers into one**, so official and third-party sessions appear in the same resume list. Note: **authentication doesn't change**—your official subscription still uses your ChatGPT login and still goes through the official backend; only the session's "classification tag" changes from `openai` to `custom`.
```text
After the unified switch is on:
┌──────────────────────────────────────────────┐
│ custom shared drawer │
│ official sessions + third-party sessions │
│ (appear in the same history / resume list) │
└──────────────────────────────────────────────┘
```
### Backups: a copy is made before every tag change
"Merging the drawers" requires changing the tag of some official sessions from `openai` to `custom` (this step is called **migration**, and it's **optional and requires you to opt in**). And **before any rewrite, CC Switch first copies the original file untouched** to here:
This backup is the sole basis for "restore exactly from backup" later. It makes the whole process **reversible**: at any time you can turn off the switch and precisely flip the official sessions you migrated in back to the `openai` drawer.
Remember these two words—**drawer** (a session just gets reclassified) and **backup** (a copy is always made before a change)—and everything that follows will be easy to understand.
---
## What happens when you enable it: step by step
### Step 1: Find the switch
```text
Settings -> General -> Codex App Enhancements
```
In the "Codex App Enhancements" block there are two rows of switches; the **second row** (the blue history icon) is the subject of this guide:
> **Unified Codex session history**
Below it is a line of description text (verbatim):
> When enabled, the official subscription runs under the shared "custom" provider id so official and third-party sessions appear in one history list, optionally migrating existing official sessions in (backed up first). When turning it off, the migrated sessions can be restored from backup. Note: resuming an old session across providers may fail because its encrypted_content reasoning can only be decrypted by the backend that created it.
> **Note**: this single line of description already previews three things—sessions will appear in one list, you can optionally migrate them in with an automatic backup, and resuming across providers "may fail." Here, "fail" means **you can't resume / can't generate a new turn**, not "the record is lost." This is exactly the core misunderstanding we'll dig into below.
### Step 2: Flip the switch from off to on -> a confirmation dialog pops up
The moment you flip the switch on, CC Switch **does not save immediately**; instead it first pops up a confirmation dialog. The dialog text reads as follows (verbatim):
- **Title**: Unified Codex session history
- **Body**:
> When enabled, the official subscription and third-party providers share one session history list. Note: resuming an old session across providers may fail because its encrypted_content reasoning cannot be decrypted by another backend.
>
> You can also migrate your existing official session history into the shared list (originals are backed up to ~/.cc-switch/backups first and can be restored when you turn this off).
- **Checkbox**: Also migrate existing official session history
- **Confirm button**: I understand, enable
- **Cancel button**: Cancel
**This checkbox is unchecked by default.** This is an important fork in the road:
| Your choice | Effect | Where your data is right now |
|---|---|---|
| **Unchecked** (default) | Only switches the tag. **Only official sessions created after enabling** land in the `custom` shared drawer | Your official sessions from **before** enabling keep the `openai` tag, stay exactly where they were, still in `~/.codex/sessions/` |
| **Checked** | In addition to switching the tag, also migrates your **existing official sessions** from the `openai` drawer into the `custom` drawer | After being **copied to backup**, the old sessions' tag is rewritten to `custom`; the original data is covered by the backup |
> **If you want "my earlier official sessions to appear in the unified list too," you must opt in by checking this box.** Otherwise you'll run into "scenario A" in the reference table below—the old sessions look "gone," when in fact they're just sitting in the original drawer.
Click "Cancel" or click outside the dialog: the switch flips straight back to off and nothing happens.
Click "I understand, enable": the switch is saved as on, and CC Switch persists the configuration in the background (and runs the migration if you checked it).
### Step 3 (only if you checked migration): how migration runs + data safety
If you check "Also migrate existing official session history," CC Switch runs this procedure on your existing official sessions:
```text
For each official (openai tag) session file:
① First copy the original file untouched into the backup directory <- data now has its first safety net
② Using "write a temp file -> replace the whole thing" atomic style,
change only the model_provider in the session_meta line at the header
from "openai" to "custom" <- not a single byte of the conversation body is touched
③ Update the index database state_5.sqlite to switch the tag in the same transaction
```
- **Backup location**: `~/.cc-switch/backups/codex-official-history-unify-v1/<timestamp>/`. Each migration produces one timestamped "generation directory," containing `jsonl/` (session copies), `state/` (index DB copy), and `meta.json` (recording which Codex directory this migration belongs to).
- **What's changed**: only the value of the single field `model_provider`. Your conversation content, reasoning content, and all body text are **kept exactly as is**.
- **What's deleted**: **nothing**. The backup is a "copy," the rewrite is an "atomic replacement of the same file," and at no point is any session or index deleted. The file is complete at every moment (either the old content or the new content, never empty or half-written).
After a successful migration, these existing official sessions show up in the unified list. **At this moment your data is**: ① the original copy in the backup directory; ② in the active file, only the classification tag changed, the content intact.
> **Note**: enabling and migration themselves **do not pop a success toast**. Migration runs as a side task on the backend during save; in the UI you'll only see the switch turn on. So "I didn't see a migration-success popup" is normal and does not mean failure.
---
## What happens when you disable it: step by step
### Step 1: Flip the switch from on to off -> probe for backups -> a confirmation dialog pops up
When disabling, CC Switch **first spends a moment probing whether there's a migration backup**, then pops up a confirmation dialog (so the disable dialog has a slight delay, which is normal). The text reads as follows (verbatim):
- **Title**: Turn off unified session history
- **Body**:
> After turning this off, the official subscription and third-party providers return to separate history lists. Sessions created while it was on cannot be attributed to a provider, so they stay in the third-party history and the official subscription will not see them.
- **Checkbox** (shown conditionally): Restore the official sessions migrated at enable time back to the official history (exact restore from backup)
- **Confirm button**: Turn off
- **Cancel button**: Cancel
> **Key point**: the body says the official subscription **will not see them**—**won't see**, not **delete**. The new sessions you chatted during the unified period are still fully present in the `custom` drawer; after disabling, the official side simply won't see them.
**This restore checkbox is checked by default.** In other words, the default behavior is "restore the official sessions you migrated in back to the official history at the same time you disable." You only need to keep it checked and click "Turn off."
If the checkbox **doesn't appear**, the system has determined there's no backup that needs restoring (either you never checked migration, or no backup was found)—in that case your existing official sessions were never touched, and turning off the switch returns them to the `openai` drawer on their own.
### Step 2: How restore runs (precise flip-back per the backup ledger)
If you keep the box checked and click "Turn off," CC Switch's restore flow goes like this:
```text
① First copy the current state once more into a separate restore-backup directory
(restore itself backs up first, so restore won't lose data either)
② Comb through all migration backup generations, find the session ids "whose tag was originally openai," and assemble a "ledger"
③ Only for sessions that are [both in the ledger AND currently still custom], change the tag back to "openai"
```
Note the **dual condition** in step ③—it must be in the ledger (proving it really was migrated from the official side) AND currently still `custom` (showing you haven't manually changed it). Only when both conditions hold does it get flipped back. This guarantees the restore is both precise and free of collateral damage.
**At this moment your data is**: the migrated-back official sessions have their tag changed back to `openai` and reappear in the official list; meanwhile both the migration backup and the restore backup copies are still on disk.
### Step 3: Read the toast, confirm the result
Only the "disable + check restore" path pops a result toast. The toasts you may see (verbatim):
| Toast you see | Meaning |
|---|---|
| **Official session history restored from backup ({{files}} session files, {{rows}} index rows)** | Restore succeeded. `{{files}}` / `{{rows}}` show the actual numbers |
| **No restorable migration backup for the current Codex directory** | Nothing to restore (**does not mean data is lost**, see scenario E in the reference table) |
| **Unified session history was re-enabled; restore skipped** | You turned the switch back on while restore was queued, so the system deliberately abandoned the restore (see scenario F) |
| **Failed to restore official session history, please try again** | The restore process errored; just retry, the data is not corrupted |
| **Save failed, please try again** | The disable save itself failed; in this case **restore is never triggered** and the switch flips back to its original position |
> **A thoughtful safety design**: if the "disable the switch" save fails, CC Switch **never runs the restore**. Otherwise you'd end up in a torn state of "switch still on, but sessions flipped back to the openai bucket." When the save fails, the switch **automatically flips back to its original position**, so you won't be stuck in a fake state of "looks off but didn't actually save."
---
## "I feel like my sessions are gone?" symptom reference table
The six scenarios below are the situations where users most easily believe "sessions are gone." **The truth in every one is: the data is intact, it just moved drawers or is temporarily out of sight.** Use this table to locate your symptom first, then read the detailed explanation below.
| Scenario | What you see | The data truth | One-line fix |
|---|---|---|---|
| **A** Didn't check migration | Old official sessions not in the unified list | All present, still carry the `openai` tag | Re-enable and check migration, or turn off the switch |
| **B** Cross-provider resume fails | Can't resume / errors out | Files intact, the ciphertext just can't be decrypted across backends | Resume on the original provider; to only read content, read the jsonl directly |
| **C** Proxy takeover / injection refused | No migration and no restore | Migration was safely skipped, files untouched | Exit takeover -> restart and retry; or just turn off the switch |
| **D** New sessions didn't return to official after restore | New sessions from the unified period aren't on the official side | They're in the `custom` drawer, untouched by design | Switch to a third-party provider to see them |
| **E** Toast "no restorable backup" | Restore "failed" | Usually nothing was ever migrated, sessions are in the original drawer | Turn off the switch and the official sessions reappear automatically |
| **F** Toast "switch was re-enabled, restore skipped" | Restore refused | Prevents a torn data state, nothing was changed | Fully turn off the switch first, then restore |
### Scenario A: You enabled the switch but didn't check migration -> old official sessions "disappear"
**Symptom**: you turned on the unified switch, but didn't check "Also migrate existing official session history" in the enable dialog (it's unchecked by default). After enabling, your earlier official sessions seem to be gone from the list.
**The truth**: 100% of your data is present, not a single line moved. The switch only takes effect on official sessions "created after enabling"; your official sessions from **before** enabling still carry the `openai` tag and sit untouched in `~/.codex/sessions/`. You're now on the `custom` drawer, so naturally you can't see the old sessions left in the `openai` drawer—that's the entire reason for the "apparent disappearance."
**What to do** (pick either):
1.**Re-enable the switch and check "Also migrate existing official session history,"** which moves the old sessions to the `custom` drawer and they immediately appear in the unified list (automatic backup before the rewrite).
2.**Or simply turn off the unified switch**, the official side runs on the `openai` drawer again, and the old sessions reappear right where they were.
### Scenario B: Cross-provider resume of an old session fails -> you think "this session is broken / gone"
**Symptom**: after unification, the list shows an old session chatted with "another provider." You switch to your current provider and click "Resume," but it errors out or can't connect.
**The truth**: the session file is intact; what's lost is not data, it's "cross-backend decryption ability." A Codex session stores an encrypted block of reasoning content `encrypted_content`, and **this ciphertext can only be decrypted by the backend that originally generated it**. Using provider B to resume a session generated by provider A means B can't decrypt A's ciphertext -> resume fails. This is **a design limitation of upstream Codex (by design)** and has nothing to do with whether CC Switch touched the file. The text content of the session is readable at any time.
> This is the **only "looks like a real problem" genuine exception** in this whole guide—but note: it just means **you can't resume (can't generate a new turn)**, and **the original file is still fully present**, the conversation text readable at any time.
**What to do**:
- **Resume with "the provider that originally created this session,"** so it can decrypt normally and connect.
- Just want to read the history without continuing? Read that session's `.jsonl` file directly (commands at the end).
- Rule of thumb: **cross-provider is better suited to "starting a new session"; resume old sessions on their original provider whenever possible.**
### Scenario C: You enabled the switch and checked migration, but migration was silently skipped -> you think "migration lost the sessions"
**Symptom**: you enabled the switch and checked migration, but the old official sessions neither entered the unified list nor could be restored when you turned the switch off (or the restore checkbox didn't even appear in the disable dialog, see scenario E). You suspect migration lost the sessions during the process.
**The truth**: migration **never ran**, so it couldn't have lost anything—not a single character of your sessions was changed. CC Switch has a safety gate before migration: it checks whether Codex's live config (`~/.codex/config.toml`) is **actually** routed to the shared `custom` drawer right now, and only migrates if the routing truly went there. The following two situations are judged "not yet unified" (internal reason code `live_not_unified`), so CC Switch **deliberately skips the migration, preserves your switch and migration intent, and migrates later once the conditions are met**:
- **During proxy takeover**: CC Switch's proxy has taken over the live config, and the live config during takeover doesn't carry the unified routing marker.
- **Injection refused**: your `config.toml` already has a manually specified `model_provider`, or there's already a differently-shaped `[model_providers.custom]` table (possibly with a third-party address). To avoid incorrectly routing official traffic to a third-party backend, CC Switch would rather not inject and not migrate.
Skipping migration = touching no session files. **No migration means nothing moved, so there's nothing to lose.** This is "safe deferral," not "failure with data loss."
**What to do**:
- Exit proxy takeover -> **restart CC Switch**: on startup it automatically retries migration (your migration intent is preserved the whole time).
- Check `~/.codex/config.toml`: if there's a conflicting route you wrote by hand, clean up the conflict before enabling the switch.
- If you'd rather not bother: just turn off the switch, the official sessions still display normally on the `openai` drawer, completely intact.
### Scenario D: You turned off the switch and restored, but "the new sessions chatted during the unified period" didn't return to official -> you think "the new sessions are gone"
**Symptom**: during the unified period, you chatted a few more new sessions with the official account. Later you turned off the switch, checked restore, and after restoring you find those new sessions didn't return to the official drawer.
**The truth**: this is **intentional** design; the new sessions are perfectly fine in the `custom` drawer, visible and resumable. Restore is based on "the backup ledger from migration time"—**only sessions that were originally migrated in from the `openai` drawer** are recorded in the backup and get precisely flipped back to `openai`. The sessions you **created during the unified period** are in no backup ledger; and after unification both official and third-party use the `custom` tag, so **CC Switch can't tell whether a new session was chatted with the official account or a third-party**. To avoid wrongly stuffing third-party sessions into the official history, the product decision is: these new sessions all stay in the `custom` (third-party) history and are never moved automatically. The disable dialog's text says this explicitly too—"Sessions created while it was on cannot be attributed to a provider, so they stay in the third-party history."
**What to do**:
- Switch to any third-party provider (the `custom` drawer) to see these sessions in the history list.
- To read content, read the `.jsonl` directly; to resume, follow scenario B's rule (go back to the backend that originally generated it).
- If you really want to manually return **one specific** session to official: there's currently no automatic button (deliberately omitted, to avoid misjudging the direction). Advanced users can, **after backing up** that file first, manually change `model_provider` in the `session_meta` of the first line of its `.jsonl` from `custom` back to `openai` (an advanced operation; always make a copy before editing).
### Scenario E: Restore toast "No restorable migration backup for the current Codex directory" -> you think "restore failed = data is gone"
**Symptom**: you checked restore when turning off the switch, and got the toast "No restorable migration backup for the current Codex directory." You panic: restore failed, is the data completely gone?
**The truth**: "nothing to restore" ≠ "data is lost." On the contrary, it's usually because **there was no migration that needed restoring**. Common reasons:
- **You never checked "migrate existing official sessions" in the first place**: with no migration, there's naturally no migration backup and no sessions to flip back. Your old official sessions have been in the `openai` drawer all along and reappear after you turn off the switch (same as scenario A). (In this case, the disable dialog may **not even show the restore checkbox**—because the system can't find any backup.)
- **You've already restored once**: the session tags have all been flipped back to `openai`, so clicking again naturally finds "no targets still in custom to restore"—this is **idempotent protection, not failure**.
- **You switched Codex directories**: restore only recognizes the backup ledger belonging to the **current** directory; switch directories and it can't find the old directory's ledger. Just switch the directory back.
In all three cases, no session was deleted.
**What to do**: use the end-of-guide commands to count the total session files in `~/.codex/sessions/` and confirm the files are all there; then check whether `~/.cc-switch/backups/` contains a `codex-official-history-unify-v1` directory—if even this directory is absent, you never triggered a migration and the sessions have been in their original drawer all along.
### Scenario F: Restore refused, toast "Unified session history was re-enabled; restore skipped"
**Symptom**: you turned off the switch -> checked restore -> but you were quick and immediately turned the switch back on, then saw the toast "Unified session history was re-enabled; restore skipped."
**The truth**: this is a safeguard against putting your data into a "torn" state, and again no sessions are lost. The restore action is "flip session tags from `custom` back to `openai`," but if the switch is on again at this moment, the live config is routing to `custom`—flipping history back to `openai` on one side while new sessions land in `custom` on the other would artificially tear sessions in two. So when CC Switch detects "the switch is on again," it **deliberately abandons this restore and changes nothing**. Sessions stay as they are, with no deletion or corruption.
**What to do**: to truly restore, **turn the switch off and keep it off** (don't immediately turn it back on), then do disable + check restore; to keep things unified, don't restore, and let the sessions stay in the `custom` shared drawer for normal use.
**The overriding principle: CC Switch's unify / migrate / restore only ever changes a single tag field in a session, and automatically backs up before every rewrite. It never deletes your conversations. Out of sight ≠ gone—look in the other drawer, or use the commands below to confirm with your own eyes.**
---
## Verify by hand: your session files are still on disk (the most important section)
No amount of text beats seeing it for yourself. Below are the **real paths** (taken from the CC Switch source) and how to view session files and backup directories on different systems. **The whole process is read-only and changes nothing; you're strongly encouraged to try it by hand.**
### The simplest way: open it directly in a file manager (no command line at all)
- **macOS (Finder)**: press `Cmd + Shift + G`, paste `~/.codex/sessions` and hit Enter to see a pile of `.jsonl` session files and their modification times; for the backup directory paste `~/.cc-switch/backups`.
- **Windows (File Explorer)**: paste `%USERPROFILE%\.codex\sessions` into the address bar and hit Enter to see the session folders and the `.jsonl` files inside; for the backup directory paste `%USERPROFILE%\.cc-switch\backups`.
**As long as you can see a batch of `.jsonl` files here, that proves your session data is intact on disk.** The file count and modification times are more intuitive than any amount of text.
### Where exactly your session / history files live
| Content | Real path | Notes |
|---|---|---|
| **Session body (the core)** | `~/.codex/sessions/` (includes date-based subdirectories, recursive) | One `.jsonl` text file per session—**this is your conversation content** |
| **Archived sessions** | `~/.codex/archived_sessions/` | Also `.jsonl` |
| **Session index database** | `~/.codex/state_5.sqlite` | The `model_provider` column of the `threads` table is the "drawer tag"—**this is the actual classification source the resume list reads** |
| **Migration backup** (auto-created when migration is enabled) | `~/.cc-switch/backups/codex-official-history-unify-v1/<timestamp>/` | Contains `jsonl/`, `state/`, `meta.json` |
| **Restore backup** (auto-created when you restore) | `~/.cc-switch/backups/codex-official-history-unify-restore-v1/<timestamp>/` | A safety copy taken before restore |
> **Note**: if you've changed the Codex directory in CC Switch, or set `sqlite_home` in `config.toml`, replace `~/.codex` above with your actual directory. Below, `~` = your user home directory.
### macOS / Linux commands
**1. Count the total number of session files (this is the hard evidence of "nothing lost")**
```bash
# Count the total number of session files -- as long as this number matches your expectation, the data is all there
> **Important note, don't let this step scare you**: **early versions of Codex did not write the `model_provider` field into the `.jsonl`**, so these old official sessions **can't be counted** by the grep above—but they're still classified as `openai` in the index database `state_5.sqlite` and still show up in the resume list. So **judge "nothing lost" by the total file count from step 1**—the per-drawer grep is only there to help you understand the classification, and counting fewer than the total file count is **completely normal** and never means "a batch was lost."
**3. (Advanced) Query the index database `state_5.sqlite`—the classification the resume list actually reads**
```bash
# Requires sqlite3 to be installed; skip if you don't have it
sqlite3 ~/.codex/state_5.sqlite \
"SELECT COALESCE(model_provider,'<empty>'), COUNT(*) FROM threads GROUP BY 1;"
```
> This `threads` table is the actual classification source Codex's resume list reads; the `openai` row count ≈ the number of sessions you can see in your official drawer. It may not match step 2's jsonl grep—the reason is exactly what's described above: "old sessions don't write the jsonl field, but they're still openai in the index database." A mismatch between the two is not an anomaly.
**4. Read the content of a specific session directly (confirm the conversation text is still there)**
```bash
# Replace <filename> with one of the .jsonl paths listed by ls above
python3 -m json.tool < "<filename>.jsonl" 2>/dev/null | head -50
# Or just open it in an editor (plain text)
open -e "<filename>.jsonl"# macOS
```
**5. Look at CC Switch's backup directory (proof that a copy was kept before migration / restore)**
```bash
ls -la ~/.cc-switch/backups/codex-official-history-unify-v1/ 2>/dev/null
ls -la ~/.cc-switch/backups/codex-official-history-unify-restore-v1/ 2>/dev/null
```
### Windows commands (PowerShell)
The session directory is usually at `C:\Users\<your username>\.codex\`, and backups at `C:\Users\<your username>\.cc-switch\backups\`.
```powershell
# 1. Total number of session files (hard evidence of "nothing lost")
> Same reminder: the step-3 grep counting **fewer** than the total file count is normal (old sessions don't write that field); judge "nothing lost" by the **total file count** from step 1.
---
## Advanced mechanism appendix (for users who want to truly understand how it works)
### 1. The bucketing mechanism (the essence of the drawers)
Codex's resume / history list filters by the currently active `model_provider` id with **exact string matching**. The **first line** of a session's `.jsonl` file is a `type:"session_meta"` record whose `payload.model_provider` is the drawer that session belongs to (`grep -rl` counts a file as long as the tag appears once anywhere in it, so no line-by-line parsing is needed; sessions from old versions that didn't write the field can't be counted). What actually drives the resume list is the `threads.model_provider` column of the index database `state_5.sqlite`. When `config.toml` has no explicit `model_provider`, the official subscription falls into the built-in default id `openai`; all of CC Switch's third-party providers uniformly use `custom`.
### 2. What the switch does (injection, lives only in live)
When enabled, CC Switch injects the following into the official live `config.toml`:
```toml
model_provider="custom"
[model_providers.custom]
name="OpenAI"
requires_openai_auth=true
supports_websockets=true
wire_api="responses"
```
Every field has a purpose: `requires_openai_auth = true` keeps authentication going through the ChatGPT login in `auth.json`, with the base_url defaulting back to the official Codex backend; `name = "OpenAI"` lets Codex's official feature gates (web search, remote compaction, etc.) keep matching; `supports_websockets = true` restores the capability that custom entries lose by default; `wire_api = "responses"` uses the official responses protocol. **The net effect is: authentication is unchanged, only the bucket name changed.**
**Key invariant: this injection can only exist in the live `config.toml`, and is never written into the database's stored configuration.** When you switch away from the official provider and write live back to the database, CC Switch strips this injection precisely (it strips only when the shape exactly matches the injected artifact; a third-party-customized `custom` table is kept as is). Precisely because of this, "turning off the switch + switching once" fully restores live, and the database always holds your original clean official configuration—this is the cornerstone of the whole switch's reversibility.
### 3. The two refusal gates for injection (corresponding to scenario C)
-`config.toml` already has an explicit `model_provider` -> don't override the user's route;
- A differently-shaped `[model_providers.custom]` table already exists (possibly with a third-party `base_url`) -> refuse injection, otherwise ChatGPT OAuth traffic would be routed to the wrong backend.
When injection is refused, live is not unified, and the migration gate (checking whether live's `model_provider` equals `custom` after trim) judges `live_not_unified` -> skip migration, preserve intent, and do it later on the next startup retry. This is "safe deferral," not "failure with data loss."
### 4. The three session classes (which determine the migration / restore boundary)
- **Class A**: existing official sessions migrated in at enable time—the backup is the ledger, and they can be precisely restored back to `openai`;
- **Class B**: created during the unified period—in no backup, and official / third-party can't be distinguished, so they're **never moved automatically** (stay `custom`);
- **Class C**: pure third-party history from before enabling—never touched.
### 5. The safety of migration / restore (data is never truly deleted; where the guarantee comes from)
Four layers of design jointly guarantee that under **all paths, normal and abnormal**, the original session data is never truly deleted.
- **Only change the field, never the body**: migration / restore only switch the `model_provider` value in session metadata between `openai` and `custom`; conversation content, `response_item`, and `encrypted_content` are all kept exactly as is.
- **Always copy a backup before a rewrite**: jsonl uses file copy, the state DB uses a full SQLite copy, both stored in a timestamped generation directory. Migration backups live in `codex-official-history-unify-v1/`, restore backups in the separate `codex-official-history-unify-restore-v1/`—the two are kept apart to keep the ledger clean.
- **Only move, never delete + atomic writes**: all jsonl rewrites go through "temp file + whole-file replacement," and the state DB goes through a transactional `UPDATE`, with no deletion of any session or index at any point. The file is complete at every moment.
- **Pessimistic skip + idempotent and retryable**: when buckets are inconsistent (`live_not_unified`), it would rather not migrate; a single process lock serializes migration and restore to avoid "startup retry / post-save background task / disable-time restore" concurrently rewriting the same batch of files in both directions; the completion marker is bound to the Codex directory and written conditionally to prevent missed migrations; restore uses the "in the ledger + currently still custom" dual condition to prevent wrong changes. Restore scans the union of all backup generations, so even after many switch cycles it can still restore early-migrated sessions; a repeated restore returns `nothing_to_restore`, which is idempotent protection rather than failure.
### 6. Cross-backend encrypted_content (corresponding to scenario B)
The reasoning ciphertext inside a session can only be decrypted by the backend that generated it; upstream Codex by design does not support cross-backend decryption. This is the root cause of "resume failure" and has nothing to do with file integrity—the session `.jsonl` sits fully on disk and `encrypted_content` is intact too. Switching back to the original provider to resume, or starting a new session, both work fine.
---
## References
- [Keep Codex Remote Control and Official Plugins While Using Third-Party APIs: CC Switch Setup Guide](./codex-official-auth-preservation-guide-en.md)
- [Using DeepSeek-Style Chat APIs in Codex: CC Switch Local Routing Guide](./codex-deepseek-routing-guide-en.md)
- The "Codex App Enhancements" section in the CC Switch user manual
---
**One last word for you**: what you see as "sessions disappeared / resume failed" is essentially **the session being moved to another history list (drawer), or the other backend being unable to decrypt the old reasoning content**; the files always sit untouched in `~/.codex/sessions/` (and `state_5.sqlite`). Checking "restore from backup" when you turn off the switch precisely flips the official sessions you migrated in back to the official list; and even if you don't restore, both the original `.jsonl` files and the backup copies under `~/.cc-switch/backups/codex-official-history-unify-*/` are all still there—**the data is never truly lost.**
**現象**: オンにして移行にチェックを入れたのに、公式の古いセッションは統一リストに入らず、スイッチをオフにして復元しようとしても「復元できるものがない」と通知される(または無効化ダイアログに復元チェックボックスがそもそも現れない。シナリオ E を参照)。あなたは、移行の過程でセッションをなくしたのではと疑います。
**真相**:会话文件完好无损,丢的不是数据,是"跨后端解密能力"。Codex 会话里保存了一段加密的推理内容 `encrypted_content`,**这段密文只有当初生成它的那个后端能解密**。你用 B 供应商去续 A 供应商生成的会话,B 解不开 A 的密文 → 续聊失败。这是**上游 Codex 的设计限制(by design)**,与 CC Switch 是否动过文件无关。会话里的文字内容你随时能读到。
CC Switch v3.10.0 introduces OpenCode support, becoming the fourth managed CLI application.
This release also brings global proxy settings, Claude Rectifier (thinking signature fixer), enhanced health checks, per-provider configuration, and many other important features, along with comprehensive improvements to multi-app workflows and terminal experience.
**Release Date**: 2026-01-21
---
## Highlights
- OpenCode Support: Full management of providers, MCP servers, and Skills with auto-import on first launch
- Global Proxy: Configure a unified proxy for all outbound network requests
- Claude Rectifier: Thinking signature fixer for better compatibility with third-party APIs
- Enhanced Health Checks: Configurable prompts and CLI-compatible request format
- Per-Provider Config: Persistent provider-specific configuration support
- App Visibility Control: Freely show/hide apps with synchronized tray menu updates
| `CC-Switch-v3.10.0-macOS.zip` | **Recommended** - Extract and drag to Applications, Universal Binary |
| `CC-Switch-v3.10.0-macOS.tar.gz` | For Homebrew installation and auto-update |
> **Note**: Since the author doesn't have an Apple Developer account, you may see an "unidentified developer" warning on first launch. Please close it, then go to "System Settings" → "Privacy & Security" → click "Open Anyway", and it will open normally afterwards.
### Homebrew (macOS)
```bash
brew tap farion1231/ccswitch
brew install --cask cc-switch
```
Update:
```bash
brew upgrade --cask cc-switch
```
### Linux
| Distribution | Recommended Format | Installation Method |
CC Switch v3.11.0 is a major update that adds full management support for **OpenClaw** as the fifth application, introduces a new **Session Manager** and **Backup Management** feature. Additionally, **Oh My OpenCode (OMO) integration**, the **partial key-field merging** architecture upgrade for provider switching, **settings page refactoring**, and many other improvements make the overall experience more polished.
- **OpenClaw Support**: Fifth managed application with 13 provider presets, Env/Tools/AgentsDefaults config editors, and Workspace file management
- **Session Manager**: Browse conversation history across all five apps with table-of-contents navigation and in-session search
- **Backup Management**: Independent backup panel with configurable policies, periodic backups, and pre-migration auto-backup
- **Oh My OpenCode Integration**: Full OMO config management with OMO Slim lightweight mode support
- **Partial Key-Field Merging (⚠️ Breaking Change)**: Provider switching now only replaces provider-related fields, preserving all other settings; the "Common Config Snippet" feature has been removed
- **Settings Page Refactoring**: 5-tab layout with ~40% code reduction
- **6 New Provider Presets**: AWS Bedrock, SSAI Code, CrazyRouter, AICoding, and more
- **Thinking Budget Rectifier**: Fine-grained thinking budget control
Provider switching now uses partial key-field merging instead of full config overwrite (#1098).
**Before**: Switching providers overwrote the entire `settings_config` to the live config file. This meant that any non-provider settings the user manually added to the live file (plugins, MCP config, permissions, etc.) would be lost on every switch. To work around this, previous versions offered a "Common Config Snippet" feature that let users define shared config to be merged on every switch.
**After**: Switching providers now only replaces provider-related key-values (API keys, endpoints, models, etc.), leaving all other settings intact. The "Common Config Snippet" feature is therefore no longer needed and has been removed.
**Impact & Migration**:
- If you **didn't use** Common Config Snippets, this change is fully transparent — switching just works better now
- If you **used** Common Config Snippets to preserve custom settings (MCP config, permissions, etc.), those settings are now automatically preserved during switches — no action needed
- If you used Common Config Snippets for other purposes (e.g., injecting extra config on every switch), please manually add those settings to your live config file after upgrading
This refactoring removed 6 frontend files (3 components + 3 hooks) and ~150 lines of backend dead code.
### Manual Import Replaces Auto-Import
Startup no longer auto-imports external configurations. Users now click "Import Current Config" manually, preventing accidental data overwrites.
### OmoVariant Parameterization
Eliminated ~250 lines of duplicated code in the OMO module via `OmoVariant` struct parameterization.
### OMO Common Config Removal
Removed the two-layer merge system, reducing ~1,733 lines of code and simplifying the architecture.
### ProviderForm Decomposition
Reduced ProviderForm component from 2,227 lines to 1,526 lines by extracting 5 independent modules (opencodeFormUtils, useOmoModelSource, useOpencodeFormState, useOmoDraftState, useOpenclawFormState), significantly improving maintainability.
### Shared MCP/Skills Components
Extracted AppCountBar, AppToggleGroup, and ListItemRow shared components to reduce duplication across MCP and Skills panels (#897, thanks @PeanutSplash).
### Settings Page Refactoring
Refactored settings page to a 5-tab layout (General | Proxy | Advanced | Usage | About), reducing SettingsPage code from ~716 to ~426 lines.
### Other Improvements
- Unified terminal selection via global settings with WezTerm support added
- Updated Claude model references from 4.5 to 4.6
---
## Bug Fixes
### Critical Fixes
- **Windows Home Dir Regression**: Restored default home directory resolution to prevent providers/settings "disappearing" when `HOME` env var differs from the real user profile directory in Git/MSYS environments
- **Linux White Screen**: Disabled WebKitGTK hardware acceleration on AMD GPUs (Cezanne/Radeon Vega) to prevent blank screen on startup (#986, thanks @ThendCN)
- **OpenAI Beta Parameter**: Stopped appending `?beta=true` to `/v1/chat/completions` endpoints, fixing request failures for Nvidia and other `apiFormat="openai_chat"` providers (#1052, thanks @jnorthrup)
- **Health Check Auth**: Health check now respects provider's `auth_mode` setting, preventing failures for proxy services that only support Bearer authentication (#824, thanks @Jassy930)
- Fixed Skill ZIP symlink resolution (#1040, thanks @yovinchen)
- Added missing OpenCode checkbox in MCP add/edit form (#1026, thanks @yovinchen)
- Removed auto-import side effect from useProvidersQuery queryFn
---
## Performance
- Parallel directory scanning + head-tail JSONL reading for session panel, significantly improving session list loading speed
- Removed unnecessary TanStack Query cache overhead for Tauri local IPC calls
---
## Documentation
- Sponsor updates: SSSAiCode, Crazyrouter, AICoding, Right Code, MiniMax
- Added user manual documentation (#979, thanks @yovinchen)
---
## Notes & Considerations
- **OpenClaw is a newly supported app**: OpenClaw CLI must be installed first to use related features.
- **⚠️ Common Config Snippet feature has been removed**: Since provider switching now uses partial key-field merging (only replacing API keys, endpoints, models, etc.), user's other settings are automatically preserved, making Common Config Snippets unnecessary. See the "Architecture Improvements" section above for migration details.
- **Auto-import changed to manual**: External configurations are no longer auto-imported on startup. Click "Import Current Config" manually when needed.
- **OMO and OMO Slim are mutually exclusive**: Only one can be active at a time. Switching to one automatically disables the other.
- **Backup is enabled by default**: Automatic hourly backup during runtime. Adjust the policy in the Backup panel.
---
## Special Thanks
Thanks to all contributors for their contributions to this release!
| `CC-Switch-v3.11.0-macOS.zip` | **Recommended** - Extract and drag to Applications, Universal Binary |
| `CC-Switch-v3.11.0-macOS.tar.gz` | For Homebrew installation and auto-update |
> **Note**: Since the author doesn't have an Apple Developer account, you may see an "unidentified developer" warning on first launch. Please close it, then go to "System Settings" → "Privacy & Security" → click "Open Anyway", and it will open normally afterwards.
### Homebrew (macOS)
```bash
brew tap farion1231/ccswitch
brew install --cask cc-switch
```
Update:
```bash
brew upgrade --cask cc-switch
```
### Linux
| Distribution | Recommended Format | Installation Method |
CC Switch v3.11.0 は大規模なアップデートです。5番目のアプリケーション **OpenClaw** の完全管理サポートを追加し、新しい**セッションマネージャー**と**バックアップ管理**機能を導入しました。さらに、**Oh My OpenCode (OMO) 統合**、プロバイダー切り替えの**部分キーフィールドマージ**アーキテクチャアップグレード、**設定ページのリファクタリング**など、多数の改善により全体的な体験がさらに向上しました。
CC Switch v3.11.0 是一次大规模更新,新增第五个应用 **OpenClaw** 的完整管理支持,同时带来全新的**会话管理器**和**备份管理**功能。此外,**Oh My OpenCode (OMO) 集成**、供应商切换的**部分键值合并**架构升级、**设置页面重构**等多项改进使整体体验更加完善。
**变更前**:切换供应商时,整个 `settings_config` 会覆写到 live 配置文件。这意味着用户在 live 文件中手动添加的非供应商设置(插件配置、MCP 配置、权限设置等)会在每次切换时丢失。为了弥补这个问题,之前版本提供了"通用配置片段"功能,让用户定义每次切换时都会合并的公共配置。
CC Switch v3.11.1 is a hotfix release that reverts the **Partial Key-Field Merging** architecture introduced in v3.11.0, restoring the proven "**full config overwrite + Common Config Snippet**" mechanism. It also includes several UI and platform compatibility fixes.
- **Restore Full Config Overwrite + Common Config Snippet**: Reverted partial key-field merging due to critical data loss issues; restores full config snapshot write and Common Config Snippet UI
- **Proxy Panel Improvements**: Proxy toggle moved into panel body for better discoverability of takeover options
- **Theme & Compact Mode Fixes**: "Follow System" theme now auto-updates; compact mode exit works correctly
- **Windows Compatibility**: Disabled env check and one-click install to prevent protocol handler side effects
---
## Reverted
### Restore Full Config Overwrite + Common Config Snippet
Reverted the partial key-field merging refactoring introduced in v3.11.0 (revert 992dda5c).
**Why reverted**: The partial key-field merging approach had three critical issues:
1.**Data loss on switch**: Non-whitelisted custom fields were silently dropped during provider switching
2.**Permanent backfill stripping**: Backfill permanently removed non-key fields from the database, causing irreversible data loss
3.**Maintenance burden**: The whitelist of "key fields" required constant maintenance as new config keys were added
**What's restored**:
- Full config snapshot write on provider switch (predictable, complete overwrite)
- If you upgraded to v3.11.0 and your providers lost custom fields, re-import your config or manually re-add the missing fields
- Common Config Snippet is available again — use it to define shared config that should persist across provider switches
---
## Changed
- **Proxy Panel Layout**: Moved proxy on/off toggle from accordion header into panel content area, placed directly above app takeover options. This ensures users see takeover configuration immediately after enabling the proxy, avoiding the common mistake of enabling the proxy without configuring takeover
- **Manual Import for OpenCode/OpenClaw**: Removed auto-import on startup; empty state now shows an "Import Current Config" button, consistent with Claude/Codex/Gemini behavior
---
## Fixed
- **"Follow System" Theme Not Auto-Updating**: Delegated to Tauri's native theme tracking (`set_window_theme(None)`) so the WebView's `prefers-color-scheme` media query stays in sync with OS theme changes
- **Compact Mode Cannot Exit**: Restored `flex-1` on `toolbarRef` so `useAutoCompact`'s exit condition triggers correctly based on available width instead of content width
- **Proxy Takeover Toast Shows {{app}}**: Added missing `app` interpolation parameter to i18next `t()` calls for proxy takeover enabled/disabled messages
- **Windows Protocol Handler Side Effects**: Disabled environment check and one-click install on Windows to prevent unintended protocol handler registration
---
## Notes & Considerations
- **Common Config Snippet is back**: If you relied on this feature in v3.10.x and earlier, it works the same way again. Define shared config that should persist across all provider switches.
- **v3.11.0 Partial Key-Field Merging users**: If you noticed missing config fields after switching providers in v3.11.0, re-import your config to restore them.
---
## Download & Installation
Visit [Releases](https://github.com/farion1231/cc-switch/releases/latest) to download the appropriate version.
| `CC-Switch-v3.11.1-macOS.zip` | **Recommended** - Extract and drag to Applications, Universal Binary |
| `CC-Switch-v3.11.1-macOS.tar.gz` | For Homebrew installation and auto-update |
> **Note**: Since the author doesn't have an Apple Developer account, you may see an "unidentified developer" warning on first launch. Please close it, then go to "System Settings" → "Privacy & Security" → click "Open Anyway", and it will open normally afterwards.
### Homebrew (macOS)
```bash
brew tap farion1231/ccswitch
brew install --cask cc-switch
```
Update:
```bash
brew upgrade --cask cc-switch
```
### Linux
| Distribution | Recommended Format | Installation Method |
CC Switch v3.12.0 is a feature release focused on provider compatibility, OpenClaw editing, Common Config usability, and sync/data reliability. It restores the **Model Health Check (Stream Check)** UI with improved stability, adds **OpenAI Responses API** format conversion, expands provider presets for **Ucloud**, **Micu**, **X-Code API**, **Novita**, and **Bailian For Coding**, and upgrades **WebDAV sync** with dual-layer versioning.
- **Stream Check returns**: Restored the model health check UI, added first-run confirmation, and fixed `openai_chat` provider support
- **OpenAI Responses API**: Added `api_format = "openai_responses"` with bidirectional conversion and shared conversion cleanup — simply select the Responses API format when adding a provider and enable proxy takeover, and you can use GPT-series models in Claude Code!
- **OpenClaw overhaul**: Introduced JSON5 round-trip config editing, a config health banner, better agent model selection, and a User-Agent toggle
- **Preset expansion**: Added Ucloud, Micu, X-Code API, Novita, and Bailian For Coding updates, plus SiliconFlow partner badge and model-role badges
- **Sync and maintenance improvements**: Added WebDAV protocol v2 + db-v6 versioning, daily rollups, incremental auto-vacuum, and sync-aware backup
- **Common Config usability improvements**: After updating a Common Config Snippet, it is now automatically applied when switching providers — no more manual checkbox needed
---
## Main Features
### Model Health Check (Stream Check)
Restored the Stream Check panel for live provider validation, improving the reliability of provider management.
- Restored Stream Check UI panel with single and batch provider availability testing
- Added first-run confirmation dialog to prevent unsupported providers from showing misleading errors
- Fixed detection compatibility for `openai_chat` API format providers
### OpenAI Responses API
Added native support for providers using the OpenAI Responses API with a new `openai_responses` API format.
- New `api_format = "openai_responses"` provider format option
- Bidirectional Anthropic Messages <-> OpenAI Responses API format conversion
- Consolidated shared conversion logic to reduce code duplication
### Bedrock Request Optimizer
Added a PRE-SEND phase request optimizer for AWS Bedrock providers to improve compatibility and performance.
Common Config Snippets are now applied as a runtime overlay instead of being materialized into stored provider configs.
**Before**: Common Config content was merged directly into each provider's `settings_config` on save or switch. This caused shared configuration to be duplicated across every provider entry, requiring manual sync when changes were needed.
**After**: Common Config is only injected as a runtime overlay when switching providers and writing to the live file — provider entries themselves no longer contain shared configuration. This means modifying Common Config takes effect immediately without updating each provider individually.
### Common Config Auto-Extract
On first run, if no Common Config Snippet exists in the database, one is automatically extracted from the current live config. This ensures users upgrading from older versions do not lose their existing shared configuration settings.
### Periodic Maintenance Timer Consolidation
Consolidated daily rollups and auto-vacuum into a unified periodic maintenance timer, eliminating resource contention and complexity from multiple independent timers.
- Skip unnecessary OpenClaw config writes when config is unchanged, reducing disk I/O
---
## Documentation
- Restructured the user manual for i18n and added complete EN/JA coverage
- Added OpenClaw usage documentation and completed settings documentation
- Added UCloud sponsor information
- Reorganized the docs directory and synced README feature sections across EN/ZH/JA
---
## Notes & Considerations
- **Common Config now uses runtime overlay**: Common Config Snippets are no longer materialized into each provider's stored config. They are dynamically applied at switch time. Modifying Common Config takes effect immediately without updating each provider.
- **Stream Check requires first-use confirmation**: A confirmation dialog appears when using the model health check for the first time. Testing proceeds only after confirmation.
- **OpenClaw User-Agent toggle defaults to off**: The User-Agent identifier must be manually enabled in the OpenClaw configuration.
---
## Special Thanks
Thanks to all contributors for their contributions to this release!
| `CC-Switch-v3.12.0-macOS.zip` | **Recommended** - Extract and drag to Applications, Universal Binary |
| `CC-Switch-v3.12.0-macOS.tar.gz` | For Homebrew installation and auto-update |
> **Note**: Since the author doesn't have an Apple Developer account, you may see an "unidentified developer" warning on first launch. Please close it, then go to "System Settings" -> "Privacy & Security" -> click "Open Anyway", and it will open normally afterwards.
### Homebrew (macOS)
```bash
brew tap farion1231/ccswitch
brew install --cask cc-switch
```
Update:
```bash
brew upgrade --cask cc-switch
```
### Linux
| Distribution | Recommended Format | Installation Method |
**変更前**: Common Config の内容は保存時または切り替え時に各プロバイダーの `settings_config` に直接マージされていました。これにより共通設定が各プロバイダーエントリーにコピーされ、変更時には一つずつ同期する必要がありました。
**変更後**: Common Config はプロバイダー切り替え時に live ファイルへ書き込む際のみ runtime overlay として注入され、プロバイダーエントリー自体には共通設定を含みません。つまり Common Config の変更は即座に反映され、各プロバイダーを個別に更新する必要はありません。
### Common Config 初回自動抽出
初回起動時にデータベースに Common Config Snippet がまだ存在しない場合、現在の live config から自動抽出します。これにより旧バージョンからアップグレードしたユーザーの既存の共通設定が失われないことを保証します。
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.