Commit Graph

2026 Commits

Author SHA1 Message Date
Dearli666 3cd9a0dec5 fix(proxy): 规范化 Anthropic system 消息 (#3775) 2026-06-05 22:50:49 +08:00
Jason 8e0e9ac319 fix(usage): correct inflated input_tokens in Claude stream parsing
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
2026-06-05 21:45:34 +08:00
Jason bda625a4f1 fix(opencode): use OpenAI-compatible SDK for APINebula preset
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.
2026-06-05 20:13:33 +08:00
Jason 473f21971d feat(usage): add official subscription quota template with unified tier rendering
Changes:
- Add official_subscription template type for Claude/Codex/Gemini
- Replace implicit 'category=official auto-query' with explicit opt-in template
- Default disabled; users enable via usage script modal with configurable interval
- Unify tier→label mapping across subscription and script paths via labeled_tier_parts()
- Fix tray rendering: week aliases (seven_day/opus/sonnet) now use highest utilization
- Add depth guard: official_subscription checks enabled flag in query_provider_usage_inner
- Add cache invalidation symmetry: invalidate_subscription() for disabled providers
- i18n: add templateOfficialSubscription + hint in zh/en/ja/zh-TW

Backend (Rust):
- provider.rs: add TEMPLATE_TYPE_OFFICIAL_SUBSCRIPTION branch, flatten SubscriptionQuota→UsageData
- tray.rs: extract labeled_tier_parts() shared by both summary functions, use max_by for multi-alias groups
- usage_cache.rs: add invalidate_subscription() method
- Test coverage: add week-alias highest-utilization tests for both paths

Frontend (TypeScript):
- UsageScriptModal: add official_subscription to templates, auto-detect for official providers
- ProviderCard: gate useUsageQuery with !isOfficialSubscriptionUsage, pass autoQueryInterval to footer
- SubscriptionQuotaFooter: accept autoQueryInterval prop, default 0 (disabled)
- constants.ts: add TEMPLATE_TYPES.OFFICIAL_SUBSCRIPTION

Fixes tier rendering regression where:
- Claude/Codex: seven_day was missed (only weekly_limit matched) → lost 7-day window in tray
- Gemini: gemini_pro/flash/flash_lite fell through to fallback → leaked machine names
- Multi-window (opus+sonnet): find() took first, not worst → underestimated utilization and emoji color

All tests pass (cargo test + cargo clippy clean).
2026-06-05 19:03:40 +08:00
Allen Xu 03a9296c1f fix: polish usage statistics ui (#3426)
* fix: improve usage statistics ui

* chore: remove unused token suffix translation

---------

Co-authored-by: Jason <farion1231@gmail.com>
2026-06-05 08:12:17 +08:00
阿南 8e7d167ace 修复任务栏图标 (#3457) 2026-06-04 23:32:32 +08:00
ZHLH dadefdee77 fix: disable auto-capitalize on Input component for macOS (#3626)
Add autoComplete, autoCorrect, autoCapitalize, and spellCheck attributes
to prevent macOS from auto-capitalizing the first letter in input fields.
2026-06-04 23:11:23 +08:00
Yongmao Luo ad030da3b1 fix(coding-plan): route Zhipu quota query to the user's configured base URL (#3702)
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>
2026-06-04 23:10:45 +08:00
Yongmao Luo 8047f95416 fix(proxy): skip backup/restore when Live is already a proxy placeholder (#3689)
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>
2026-06-04 22:54:56 +08:00
@nothingness0db nothingness0db 0527002cca feat(usage): add OpenCode session usage sync (#3215)
* 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>
2026-06-04 22:26:24 +08:00
Keith Yu 2a24da517f feat: 新增 S3 兼容云存储同步 (#1351)
* 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>
2026-06-04 22:18:51 +08:00
oasis ea95f39adf fix(codex): drop tool_choice when tools is empty in Responses→Chat conversion (#3640)
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>
2026-06-04 15:20:04 +08:00
Jason f5acef32fd fix(proxy): clarify Codex 413 error as upstream limit, not local proxy
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
2026-06-04 11:40:16 +08:00
Jason 084857ce25 fix(claude-desktop): strip [1m] suffix before proxy route lookup
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
2026-06-04 11:40:16 +08:00
Jason 6692343d1e Add media fallback rectifier for text-only models
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.
2026-06-04 11:40:16 +08:00
Cherilyn Buren 33eafbad51 fix(copilot): 调高无限空白检测阈值 20 → 500 (#2647)
* 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>
2026-06-04 11:29:25 +08:00
Cao Xin c2337d6857 fix(codex): preserve custom tool metadata in chat routing (#3644)
* fix(codex): preserve custom tool metadata in chat routing

* fix(codex): reduce custom tool metadata description overhead

* fix(codex): stabilize custom tool metadata descriptions

* refactor(codex): remove unreachable custom tool description fallback
2026-06-04 08:33:53 +08:00
lzcndm ce538265cb fix: improve error message display in proxy panel (#3656) 2026-06-03 23:03:12 +08:00
zhibisora e458e77e30 feat: add CherryIN preset provider for Claude Code and Codex (#3643)
* 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.
2026-06-03 22:56:31 +08:00
makoMakoGo ae90b53454 fix(tests): isolate deeplink prompt import home (#3662) 2026-06-03 22:50:36 +08:00
makoMakoGo e891f5c876 [codex] fix Zhipu coding plan presets (#3524)
* 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>
2026-06-03 15:53:57 +08:00
Sleepwf 73073454cd fix(i18n): align Chinese VS Code wording with English and Japanese (#3228) 2026-06-03 15:10:06 +08:00
step 7811383b59 fix(codex): use relative filename for model_catalog_json (#3614)
* 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.

Closes farion1231/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>
2026-06-03 14:52:57 +08:00
Eter c1dff06625 feat: 新增 ZenMux Token Plan 供应商,支持手动凭证与 USD 额度富展示 (#2709)
* feat(Token plan): 增加 ZenMux 支持

* chore: format code with prettier

* chore: format code with cargo fmt

---------

Co-authored-by: 明桓 <jihaodong.jhd@oceanbase.com>
Co-authored-by: Jason <farion1231@gmail.com>
2026-06-03 14:45:49 +08:00
LaoYueHanNi 43ae1e5f2c fix(coding-plan): 适配 MiniMax 余额查询新接口 + 默认定价 (#3518)
* fix(coding-plan): 适配 MiniMax 余额查询新接口 + 默认定价

## 余额查询修复

/v1/api/openplatform/coding_plan/remains 新接口不再返回
current_*_total_count / current_*_usage_count(恒为 0),
改为返回 current_*_remaining_percent(剩余百分比)。
旧代码会因守卫失败导致 tiers 为空,tray 不再显示用量。

- 切换数据源到 *_remaining_percent,反转为已用百分比
- 过滤 model_name == "general",跳过 video 等非编程模型
- 提取 parse_minimax_tiers 纯函数,便于无 mock 单元测试
- 复用 TIER_FIVE_HOUR / TIER_WEEKLY_LIMIT 常量
- 新增 6 个单元测试覆盖主路径与边界

## 默认定价新增
按 2026-06-01 上线的人民币价格 (CNY/7.0 汇率,口径与 M2.7 反推一致)
换算为 USD,在 seed_model_pricing 数组中追加 minimax-m3:
| model_id     | display_name | input | output | cache_read | cache_creation |
|--------------|--------------|-------|--------|------------|----------------|
| minimax-m3   | MiniMax M3   | 0.60  | 2.40   | 0.12       | 0              |
不取512K上下文定价(参照当前模型标准)

* fix(coding-plan): MiniMax 兼容无周限额套餐 (current_weekly_status=3)
2026-06-03 14:28:05 +08:00
Yeeyzy b4f262c7bd fix(codex): always include output_tokens_details.reasoning_tokens in … (#3514)
* fix(codex): always include output_tokens_details.reasoning_tokens in chat→responses transform

Codex CLI strictly requires reasoning_tokens in the response.completed
usage object. Custom providers using /chat/completions often omit
completion_tokens_details, causing repeated parse failures and retries.

* fix(codex): guard non-object completion_tokens_details before insertion

---------

Co-authored-by: yeeyzy <yeeyzy@yangzhiying05@gmail.com>
2026-06-03 14:10:28 +08:00
makoMakoGo 693c3872f0 docs: refresh user manual for current app support (#3411)
* 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
2026-06-02 22:17:42 +08:00
Jason c67494bafc docs: clarify Codex guide usage in release notes 2026-06-01 22:11:33 +08:00
Jason 256b04999c docs: add Codex official auth preservation guide 2026-06-01 22:01:35 +08:00
Jason 25951d8132 chore(release): bump version to 3.16.1 and add release notes v3.16.1 2026-06-01 17:55:55 +08:00
Jason d66030bee6 Stream Codex custom tools with native input events
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.
2026-06-01 17:01:00 +08:00
Jason 5968336364 Restore Codex tool plugins over Chat Completions third-party proxy
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.
2026-06-01 16:29:23 +08:00
Jason b7499fc871 Refresh Codex provider label on proxy takeover hot-switch
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.
2026-06-01 15:16:13 +08:00
Jason aeaa016cae Simplify Codex takeover notice copy and match hint styling
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.
2026-06-01 11:41:05 +08:00
Jason 2a131a5572 Harden Codex takeover ownership signaling and serialize switch/takeover
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.
2026-06-01 11:41:05 +08:00
Jason a04e72a267 Fix Codex edit dialog masking live OAuth during proxy takeover
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.
2026-06-01 11:41:05 +08:00
Jason ce993baefa Fix Codex OAuth auth cleared on takeover when provider mis-categorized
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.
2026-06-01 11:41:05 +08:00
Jason d5328e5290 Fix Codex model catalog lost on proxy takeover-off restore
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.
2026-06-01 11:41:05 +08:00
Jason 0fbba4267c Fix Codex model catalog being wiped by live-config backfill
`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.
2026-06-01 11:39:22 +08:00
oasis 8bf1660237 fix(codex): always update model catalog JSON on provider switch (#3360)
* 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>
2026-06-01 11:38:07 +08:00
Siskon afa09e127e fix(usage): resolve per-app credentials for native balance/coding-plan queries (#3355)
* 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 #3158
Fixes #3100
Fixes #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.
2026-06-01 08:43:40 +08:00
Eunknight 0960fd7179 fix: Claude Desktop 官方供应商添加报错 #3402 (#3405)
* fix: Claude Desktop 官方供应商添加时缺少 ANTHROPIC_BASE_URL 报错

根因:前端 mutation 为 claude-desktop 生成随机 UUID 作为 provider id,
后端 is_official_provider 通过 id 匹配跳过校验,随机 UUID 不匹配导致
走入普通 direct 模式校验并要求 ANTHROPIC_BASE_URL。

修复:
- 前端:claude-desktop + category=official 时使用固定 id "claude-desktop-official"
- 后端:validate_provider / validate_direct_provider / validate_proxy_provider /
  apply_provider_to_paths 增加 category=="official" 兜底检查

Fixes #3402

* fix: restrict Claude Desktop official provider detection

* fix: add Claude Desktop official provider via seed

---------

Co-authored-by: 金恩光 <enguang.jin@gmail.com>
Co-authored-by: Jason <farion1231@gmail.com>
2026-05-31 23:37:31 +08:00
Zhiqi Yao 5ef72a2030 fix(codex): add multi-platform CLI discovery and static gpt-5.5 template fallback (#3382)
* 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>
2026-05-31 22:48:27 +08:00
Haoyu Wang e02a2763c2 fix: add kimi/moonshot to Anthropic tool thinking history normalizer (#3377)
* 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>
2026-05-31 20:10:02 +08:00
Jason c9cadd6e09 Fix Codex OAuth auth being cleared during preserve-mode takeover
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.
2026-05-30 22:56:36 +08:00
Jason 60a9b330e5 Refactor Codex live-write routing and cover default auth overwrite
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.
2026-05-30 22:09:28 +08:00
Jason f4e2c28a2b Enrich Codex proxy forwarding-error responses with context
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.
2026-05-30 21:42:16 +08:00
Jason 0e6f2b395f Swap Shengsuanyun and AICodeMirror sponsor ads 2026-05-30 21:23:50 +08:00
Jason 41433cfab2 Add Codex restart hint after provider switch 2026-05-30 21:19:30 +08:00
Jason 3f59ab3746 Default Codex auth preservation to off (opt-in)
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.
2026-05-30 21:04:23 +08:00