Repository navigation
fix(proxy): support Codex Alpha Search and Claude hosted WebSearch - #5681
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5ddff6efcd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
To use Codex here, create a Codex account and connect to github. |
|
Maintainer action needed for the updated head
Locally verified with |
|
To use Codex here, create a Codex account and connect to github. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cf3920a7e4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6acf43cf30
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-authored-by: Ethan <wzy1350720763@gmail.com>
6acf43c to
3c2de8a
Compare
|
@codex review |
|
Codex Review: Something went wrong. Try again later by commenting “@codex review”. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review |
|
Codex Review: Something went wrong. Try again later by commenting “@codex review”. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review |
|
Codex Review: Something went wrong. Try again later by commenting “@codex review”. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@allenxu09 您好,打扰了。近期仓库 PR 较多,想请您方便时帮忙看一下这个代理兼容修复。 当前状态:
这个 PR 主要修复两处兼容问题:Codex 如果维护侧希望进一步拆分提交或调整实现范围,我可以继续配合。谢谢! |
|
我基于当前 head [P1] 未支持的 WebSearch caller 约束被静默改成
|
|
@allenxu09 谢谢您这次详细复查。您指出的两个协议兼容问题已在 P1:caller / response inclusion 约束
P2:最终回答 URL citation
提交前我按本 PR 审核视角反复执行了本地 当前本地验证:
PR 描述也已同步更新影响范围、fail-closed 边界和最新自测结果。新一轮 GitHub CI 当前显示 @codex review |
|
Codex Review: Didn't find any major issues. 👍 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
啥时候合进去啊? |
|
@farion1231 您好,打扰了。想请您方便时帮忙审核一下这个 PR。 此前复查提出的 caller / response inclusion 安全约束和最终 URL citation 两项问题,已在最新提交
如果实现范围、提交拆分或合入方式需要调整,我会继续配合。谢谢! |
|
@farion1231 不好意思再次打扰。这个 PR 目前仍在等待维护者审核,最新提交 您方便时能否帮忙看一下?如果近期没有时间,也烦请推荐一位合适的 reviewer,或告知是否需要拆分/缩小 PR 范围。谢谢! |
|
@allenxu09 您好,想再跟进确认一下。您此前指出的 caller / response inclusion 和 URL citation 两项问题已在 方便时能否帮忙确认这两项修复是否已经符合预期?如果技术审核已通过,也烦请协助 approve 或推进后续合入;如果 #5681 当前仍不适合合并,也希望能告知具体还需要调整的范围或合入方案,我会继续配合。谢谢! |
我觉得还行,这个急需 |
|
确实 这应该算是个大bug了 影响功能使用 |
|
已合并,感谢 @Program120 扎实的工作!这个 PR 补上了代理链路上联网搜索的两块硬缺口:Claude Code 内置 WebSearch 经 Responses / Codex OAuth 路由终于可用,Codex 独立 Alpha Search 也不再 404。协议边界一律 fail-closed 而不是静默削弱、97 个既有测试零改动全过、连同 4.7k 行的回归覆盖,工程质量令人印象深刻。也感谢 @AndrewZhuCC 在 #5379 中的原始 Alpha Search 实现,squash 提交已通过 Co-authored-by 保留归属。 另有一个欢迎 follow-up 的小问题(不影响 Codex OAuth 主链路):当上游只发 |
我来处理吧! |
* fix(codex): respect user-owned model_catalog_json when generating catalog (#6087)
The Some arm of set_codex_model_catalog_json_field unconditionally overwrote model_catalog_json with the cc-switch-owned filename, discarding any user-provided custom catalog path or filename. This mirrors the ownership check already present in the None arm: only claim the pointer when it is absent or already cc-switch-owned, leaving user-managed external catalog files untouched.
Add tests covering both full-path and relative-filename user-owned catalogs in the Some arm.
* fix(windows): fall back when WSL rejects atomic replace (#6232)
* fix(windows): fall back when WSL rejects atomic replace
* fix(windows): preserve config during WSL fallback
* fix(windows): use rename fallback for WSL UNC
* ci: run Windows backend tests against WSL2 filesystems (#6233)
* ci: add WSL2 backend checks
* ci: fix WSL2 backend job by keeping MSVC toolchain on native temp
- compile test binaries before pointing TEMP/TMP at the WSL2 UNC path
(mt.exe fails with c1010070/LNK1327 when TEMP is a UNC path)
- inject WSL2 TEMP/TMP per test step instead of via GITHUB_ENV
- use --tests so doctests cannot trigger link steps under the UNC temp
- run the filesystem contract gate before the full serial suite
- drop fmt/clippy steps duplicated from the existing Windows job
* ci: move full WSL2 suite to nightly, keep per-PR contract gate
- the full serial suite needs 50+ minutes on the 9P filesystem
(2026/2343 lib tests finished in 39.5min before the 45min timeout)
- per-PR job now runs only the atomic-write contract (~6min total)
and builds just the lib test binary via --lib
- new nightly workflow (schedule + workflow_dispatch, 90min timeout)
runs the contract plus the full serial suite on the WSL2 home
---------
Co-authored-by: Jason <farion1231@gmail.com>
* ci: fix i18n labeler glob to match actual locales path
The i18n label never fired because the glob pointed at src/locales/**
while the locale files live under src/i18n/locales/.
* ci: skip frontend/backend checks when a PR touches neither area
Add a changes-detection job (dorny/paths-filter, SHA-pinned) and gate
the frontend job, the 3-OS backend matrix, and the WSL2 contract gate
on whether the PR actually touches their inputs. About two thirds of
merged PRs only touch one side, and docs-only PRs now skip CI entirely.
Pushes to main still run everything unconditionally so the caches that
PRs restore from stay warm. Path groups mirror .github/labeler.yml,
plus rust-toolchain.toml which pins the compiler used by the backend
jobs.
* fix(codex): fill DeepSeek contextWindow in OpenCode Go catalog
Both deepseek-v4-pro/flash lacked contextWindow; codex_config.rs falls back to 128k for catalog entries without it, forcing premature compaction.
* feat(claude): route OpenCode Go preset directly to /messages
/v1/messages only accepts x-api-key: switch env from ANTHROPIC_AUTH_TOKEN to ANTHROPIC_API_KEY and add top-level apiKeyField.
Drop apiFormat so Claude Code connects directly (providerNeedsRouting no longer triggers) instead of through the proxy.
Desktop keeps mode: proxy (it cannot rename models itself) but switches to anthropic passthrough.
* chore(grokBuild): correct OpenCode Go exclusion rationale
Upstream has had grok-4.5 since 2026-08, so 'no Grok models upstream' is no longer accurate.
Exclusion is now a deliberate product decision; enrollment requires separate review.
* fix(windows): escape backslashes before Handlebars vars in WiX registry key
The single backslash in Software\{{manufacturer}}\{{product_name}} was
consumed by Handlebars as a mustache escape, so the MSI wrote a literal
key named Software{{manufacturer}}{{product_name}} directly under HKCU.
Double the backslashes to match every other registry key in the template.
Fixes #6283
* fix(skills): report missing SSOT directory as update in check_updates
check_updates trusted the DB-cached content_hash before ever looking at
disk, so a skill whose SSOT directory is gone (e.g. after restoring a DB
backup on another machine) showed "no update" and the missing files were
permanently masked; the only recovery was uninstall + reinstall.
Extract the local-hash decision into local_hash_for_update_check with
the order inverted: validate the untrusted directory name, check that
the SSOT directory actually exists, and only then trust the cached
hash (computing and backfilling it when absent). A missing directory
now yields None, which never equals the remote hash, so the skill
surfaces as "update available" and the existing update_skill flow
rebuilds it — no new download logic.
Invalid directory values keep the pre-fix behavior (fall back to the
cached hash) instead of being reported as updatable, because
update_skill would hard-fail on the same validation gate.
Covered by four unit tests on the extracted helper.
* fix(grokbuild): add glass form container to provider form (#6198)
Apply the shared glass card styles (background, border, radius, padding) used by the generic provider form to the Grok Build provider form.
* fix(ui): reduce provider editor empty space (#6199)
* fix(claude): align advanced options with Codex (#6200)
* feat(provider): add PPIO support (#6239)
* feat(provider): add PPIO support
Refs #6226
* test(provider): avoid pinning PPIO preset order
* feat(provider): add fuzzy search to model mapping dropdown (#6285)
The model mapping dropdown (used by Claude Desktop, Codex, and OpenCode
provider forms) lists every fetched model grouped by vendor with no way
to filter. Providers with hundreds of models make the right model hard
to find, as requested in #6275.
Convert ModelDropdown from a plain DropdownMenu to a Popover + Command
combobox (the existing OmoFormFields pattern), adding a fuzzy search
input powered by cmdk:
- non-matching groups auto-hide; an empty state shows when nothing matches
- vendor name is indexed as a keyword, so models can be matched by vendor
as well as by model id
- the { models, onSelect } API is unchanged, so all 5 call sites work as-is
Consolidate two near-identical inline copies of the same grouped
dropdown (ModelInputWithFetch and the Copilot branch in ClaudeFormFields)
onto ModelDropdown so every model picker gains the search and the
duplication is removed.
Add searchModelPlaceholder / searchModelEmpty / searchModelAriaLabel i18n
keys for en, zh, zh-TW, ja.
* fix(provider): finish model dropdown consolidation (#6353)
* refactor(openclaw): align provider model editor (#6201)
Co-authored-by: Jason <farion1231@gmail.com>
* fix(claude-desktop): align provider form frame (#6205)
* style(openclaw): clarify User-Agent section hierarchy (#6204)
* refactor(openclaw): align provider model editor
* style(openclaw): clarify user agent section hierarchy
* fix(ui): unify checkbox styles (#6203)
* feat(provider): add JieKou AI presets (#6356)
* feat(provider): add JieKou AI presets
* feat(provider): use Claude Fable 5 for JieKou AI
---------
Co-authored-by: jiekouai <231459779+jiekouai@users.noreply.github.com>
* refactor(opencode): clarify provider form hierarchy (#6206)
* feat(proxy): animate routing activation (#6209)
* feat(proxy): animate routing activation
* fix(proxy): gate routing activation animation
* fix(proxy): wait for initial status before takeover
---------
Co-authored-by: allenxu09 <s23372.xu@stu.scie.com.cn>
* refactor(hermes): align provider form hierarchy (#6207)
* refactor(hermes): align provider form hierarchy
* fix(hermes): match model detail label typography
* fix(hermes): mute context length label
* fix(hermes): align model detail field width
* fix(hermes): expose model detail state
* fix(claude-desktop): clarify model configuration modes (#6208)
* fix(ui): unify checkbox styles
* fix(claude-desktop): align provider form frame
* fix(claude-desktop): clarify model configuration modes
* fix(claude-desktop): preserve direct preset models
---------
Co-authored-by: Jason <farion1231@gmail.com>
* fix(zhipu): accept CREDIT_LIMIT quota entries (#6160)
Zhipu renamed the quota entry type field from TOKENS_LIMIT to CREDIT_LIMIT on the mainland endpoint, causing all quota tiers to be skipped and the usage panel to show empty. Accept both values (case-insensitive) so old plans and the international endpoint keep working.
Fixes #6153
* fix(backup): preserve SQL fidelity and recovery safety (#6146)
* fix(backup): preserve incremental auto-vacuum on import
* fix(backup): preserve SQLite scalar values in SQL dumps
* fix(backup): preserve autoincrement high-water marks
* fix(backup): restore valid empty SQL exports
* fix(backup): make restores atomic under retention
* fix(backup): publish safety snapshots atomically
* fix(backup): serialize backup file operations
* fix(sync): harden sync and restore consistency (#6147)
Closes #6129
* fix(proxy): never clobber Codex official ChatGPT login on takeover restore
The restore backup is a snapshot taken when takeover starts and is never
refreshed from live afterwards. If the user logged into official ChatGPT
while takeover was active, exit/crash-recovery restored the stale
third-party API-key snapshot verbatim over auth.json, wiping the login on
every restart (and the auto re-takeover on startup looped the clobber).
Restore now arbitrates before writing: whenever live auth.json holds real
login credentials (codex_auth_has_credential_login_material, and not the
proxy placeholder), live wins — the backup's API key is demoted into
config.toml as experimental_bearer_token and the backup auth slot is
dropped, so auth.json is left untouched. Live credentials are always newer
than the snapshot because only Codex itself (login / token self-refresh)
advances them, so even an official-shape backup must not roll live tokens
back. Metadata residue (last_refresh / tokens.account_id) counts neither
as a live login to protect nor as backup material to restore. Both restore
entry points — the with-fallback path (exit / crash recovery / takeover
disable) and the simple path (takeover rebuild and failure rollback) —
share the same protection.
Fixes #6277
* fix(proxy): stop thinking/reasoning_content injection for Kimi upstreams
Moonshot/Kimi asked to exit the reasoning-vendor workaround: their
endpoints no longer require thinking replay on tool-call turns, and the
injected placeholders disrupt the model's chain of thought.
- Remove moonshot/kimi from REASONING_VENDOR_HINTS; Kimi now gets clean
passthrough on the anthropic path (no placeholder thinking blocks, no
signature stripping) and no reasoning_content on the openai_chat path.
- DeepSeek/MiMo keep the behavior (documented server-side requirement).
- Flip the two Kimi gating tests into negative regressions and update
stale comments/test model names that still referenced Kimi.
* fix(pricing): correct Grok 4.5 cached rate, seed Grok 4.6 and a DeepSeek alias
Findings from a models.dev audit of the seeded pricing table:
- Correct grok-4.5 cached input 0.50 -> 0.30 (seed + repair with an
old-value guard). The current xAI list table, models.dev and the billing
observed for grok-4.5-build all state 0.30; 0.50 is grok-4.6's rate.
- Seed grok-4.6 (2 / 6 / 0.50, released 2026-08-12). Both 4.5 and 4.6 double
their rates above a 200K prompt, but the table has no tier column, so the
base tier is stored, consistent with other tiered vendors.
- Seed a deepseek-v4-flash-0731 alias at DeepSeek's official price.
strip_model_date_suffix only strips ISO, 8-digit YYYYMMDD and 6-digit
YYMMDD suffixes, and the prefix fallback only matches longer rows, so this
id resolved to no pricing row and was silently billed at zero.
The GrokBuild backfill test expectations follow the corrected cache-read
price; its cache-inclusive input assertion (450 fresh tokens) is unchanged.
* fix(codex): restore angle-bracket text lost in DeepSeek catalog mirror extraction
The official catalog mirror template was extracted from DeepSeek's docs
page with HTML unescaping applied before tag stripping, so literal
angle-bracket text inside the JSON (e.g. "<this good thing>" and the
spaced-path markdown link example) was swallowed as if it were HTML
tags — 8 spots across base_instructions and model_messages for both
models, 86 bytes missing per harness copy. Restore them; the template
is now structurally identical to the current official models.json.
Also refresh the preset comment: deepseek-v4-pro's Codex integration
is live as of 2026-08 (its catalog entry differs from flash only by
priority), so the "switching early errors upstream" warning is gone.
* fix(ui): remove partner star badge from main panel provider cards
The star was legacy display logic gated on meta.isPartner, which the
generic provider form almost never persisted. The standalone Grok Build
form unconditionally saves meta.isPartner, resurrecting the dead code
path. Partner stars belong only in the preset selector, so drop the
card-level badge and the now-unused officialPartner i18n key from all
four locales.
* chore(presets): migrate RunAPI domain to runapi.host, keep runapi.co as fallback endpoint
Switch RunAPI website, registration and request URLs to runapi.host
across all seven app presets and the four README partner sections.
The previous runapi.co endpoints are retained as endpointCandidates
fallbacks where the preset type supports the field (claude,
claude-desktop, codex, grok-build).
* docs(partner): update FennoAI offer to $1.99 trial plan with $50 credits
FennoAI changed its CC Switch benefit: the previous ¥9.9/month Coding
Plan worth $150 in credits is replaced by a $1.99 Trial Plan worth
$50 in credits, valid for 7 days. Update the in-app promo line in all
four locales and the partner sections of the four READMEs. The 20%
referral reward and the registration link stay unchanged.
* feat(presets): split Volcengine into Agent Plan and Coding Plan presets
The former 火山Agentplan preset actually pointed at Coding Plan
endpoints (/api/coding[/v3]). Split it into two presets across all six
app preset files:
- 火山 Agent Plan: /api/plan (Anthropic) and /api/plan/v3 (OpenAI),
with its own activity/agentplan invite link
- 火山 Coding Plan: keeps /api/coding[/v3] and the original
activity/codingplan invite link
Both plans' OpenAI endpoints are covered by the official Codex docs
(wire_api=responses), so the Coding Plan Codex preset also uses native
Responses. Widen the Volcengine usage detection to
volces.com/api/(plan|coding) in both the frontend pattern and the
backend detect_provider mirror; the quota query already auto-probes
both plans (GetAFPUsage then GetCodingPlanUsage). Split the partner
promo copy into volcengine_agentplan / volcengine_codingplan i18n keys
in all four locales, and pin both presets in the codex native
Responses preset test.
* feat(presets): add XycAi partner preset across all seven apps
- Gateway: apicdn.xycai.us primary, apicdn.xyc.ai fallback
- Claude auth uses ANTHROPIC_API_KEY per official docs
- Claude Desktop connects direct with native Anthropic format
- In-app partner promotion only (no README entry), four locales
- Register xycai icon (PNG via iconUrls) with metadata
* fix(grokbuild): include required input token details (#6423)
* fix(provider): prevent macOS IME input corruption (#6308) (#6333)
Fixes #6308
* refactor(grokbuild): align provider form with Codex (#6427)
* feat(pi): add native coding agent support (#6064)
* feat(pi): add native coding agent support
* refactor(pi): align native provider management
* fix(pi): allow removing global default provider
* feat(pi): refine provider creation experience
* refactor(pi): align prompt management UI
* feat(pi): refine native prompt and provider UX
* fix(pi): size config preview to content
* feat(pi): refine native provider management
* refactor(pi): remove unused integration paths
* fix(pi): address review feedback
* test(pi): align model picker assertions
* fix(pi): preserve AGENTS.md during restore sync
---------
Co-authored-by: Jason <farion1231@gmail.com>
* feat(pi): add session usage statistics (#6463)
* feat(codex): support per-model reasoning levels in model catalog (#6228)
* feat(codex): support per-model reasoning levels in model catalog
The generated Codex model catalog hardcodes supported_reasoning_levels to
none/high from the native template, so users of gateways that accept more
effort levels (e.g. LiteLLM serving deepseek-v4-flash with
low/medium/high/xhigh/max) cannot select anything above high in the Codex
UI.
Add optional per-model reasoningLevels / defaultReasoningLevel fields to the
model mapping. The backend applies them on top of the native template and the
official vendor catalog (unknown efforts are dropped, and the default falls
back to the template/vendor default when still in the list, otherwise the
highest declared level). The model mapping rows now expose a reasoning-level
multi-select plus a default-level picker.
Fixes #6181
* fix(codex): validate reasoning default against canonical supported set
Address bot review feedback:
- Validate every default candidate (explicit value, template default,
fallback) against the canonical filtered efforts so a typo'd level can
never become default_reasoning_level while missing from
supported_reasoning_levels.
- The fallback now picks the highest supported level in canonical order
instead of the last declared entry, and supported_reasoning_levels
always emits canonical order regardless of declaration order.
- Drop the misleading "Auto (highest selected)" placeholder: the backend
prefers the template default while it stays supported, so the label is
now just "Auto" in all four locales.
* fix(proxy): map ultra reasoning effort instead of dropping it in codex transforms
The catalog UI now offers all eight canonical levels, but the proxy
transform layer predates ultra and silently dropped it:
- Chat transform: passthrough mode now forwards ultra verbatim (the
per-model reasoningLevels declaration vouches for upstream support,
same as the existing max/xhigh handling); deepseek and openrouter
modes clamp it to their highest legal tier (max / xhigh) instead of
dropping the effort parameter entirely.
- Anthropic transform: ultra now maps to the max thinking budget;
previously it fell through to None, so picking the deepest level
disabled extended thinking altogether.
* fix(ui): keep codex reasoning levels in canonical order and allow resetting default to Auto
- The reasoning-levels editor now stores selections in canonical
ascending-depth order instead of click order, matching the order the
generated catalog and the Codex picker use.
- The default-level select gains an explicit Auto item (Radix Select
forbids empty item values, so a sentinel maps back to undefined);
previously a chosen default could not be reverted without unchecking
and rechecking the level.
- Tests: cover duplicate level declarations in the canonical
normalization test.
* fix(ui): raise nested default-level menu above the reasoning popover
The enclosing reasoning-levels popover uses z-[1000] while SelectContent
defaults to z-[100], so the default-level menu rendered behind the panel
when it flipped upward near the viewport bottom. Raise it to z-[1100],
matching the existing nested-layer convention (usage panels use z-[120]
above z-[110] dialogs).
---------
Co-authored-by: Kaihang An <ztmdsbt@gmail.com>
Co-authored-by: Jason <farion1231@gmail.com>
* Add managed OAuth account selection for providers (#3879)
* Add managed ChatGPT account binding for Codex
* Consolidate managed account entry points
* Clarify unmanaged Codex login option
* fix(provider-form): show Codex FAST mode toggle in account-select mode
The FAST mode switch was gated on mode === "manage", but the Claude and
Claude Desktop Codex OAuth forms render CodexOAuthSection in select mode
while still passing onFastModeChange. The toggle was therefore hidden:
new providers couldn't enable FAST mode and existing ones couldn't turn
it off, even though the backend still honors meta.codexFastMode. Gate the
toggle on onFastModeChange presence instead, restoring the pre-refactor
behavior without affecting the manage panel or native Codex form (which
do not pass the handler).
* fix(i18n): localize Codex managed-account none option label
ProviderForm passed a hardcoded Chinese string as codexOauthNoneOptionLabel,
so en/ja/zh-TW users saw Chinese text in the Codex account selector's
"none" option. Add codexOauth.noneOptionLabel to all four locales and
resolve it via t().
* feat(codex): write managed ChatGPT id_token into live auth + reauth prompt
Bind the selected ChatGPT account's id_token into the Codex official
managed auth.json so it matches a native browser login for the fields
that drive behavior (auth_mode, account/plan/email via id_token claims).
- Persist id_token on CodexAccountData; capture at login and on refresh
(empty string treated as missing). Add get_valid_token_and_id_token_for_account.
- codex_managed_oauth_live_auth now writes tokens.id_token when available.
- Keep the managed-vs-native safety marker intact: extract_codex_managed_oauth_auth
tolerates id_token but still rejects native logins (which carry refresh_token /
top-level last_refresh), so cc-switch never clears a real browser login.
refresh_token and last_refresh are intentionally NOT written for this reason.
- Surface reauth_required (account has no stored id_token) through
GitHubAccount -> ManagedAuthAccount -> TS. Legacy accounts get a styled
amber prompt + one-click re-login (device flow) in CodexOAuthSection,
flagged in both the selector dropdown and the select-mode hint.
- i18n: reauth strings added for en / ja / zh / zh-TW.
Verified: pnpm typecheck + build:renderer green; cross-model review of Rust
correctness, the managed-vs-native safety invariant, and the id_token lifecycle.
* style(codex): satisfy prettier format check for reauth UI
* fix(i18n): add missing managed-account selector keys
The managed OAuth account selector referenced codexOauth.chatgptAccount,
codexOauth.manageAccounts, copilot.githubAccount, copilot.manageAccounts and
providerForm.providerKeyStatusLoading with Chinese defaultValue fallbacks, so
the en/ja/zh-TW UIs rendered Chinese text. Add the translations across all four
locales.
Addresses review feedback on #3879.
* fix(providers): don't clear managed OAuth binding on status-query failure
useManagedAuth now exposes isStatusSuccess. The account-clear effect in the
Codex/Copilot selector sections only unbinds when the status query has
*successfully* loaded and the bound account is genuinely gone. Previously a
failed or still-pending query yielded an empty accounts array, which silently
cleared the selection; the subsequent save then dropped providerType/authBinding
and turned a managed provider into a broken unbound config.
Addresses review feedback on #3879.
* fix(codex-oauth): make managed accounts safe & usable with the bare CLI
Addresses review feedback on #3879. These backend changes are deeply
intertwined across shared files, so they are committed together.
Refreshable managed auth (bare `codex` support):
- Write a full, native-shaped auth.json bundle (tokens.refresh_token + top-level
last_refresh) instead of an access-only token, so the Codex CLI can self-refresh
and the managed account keeps working past ~1h without a proxy.
- last_refresh reflects the access token's real obtained-at time (not write time),
so the CLI doesn't treat a cached token as freshly refreshed.
- Read back the CLI-rotated refresh_token from ~/.codex/auth.json before writing
(account_id-matched, chatgpt-mode guarded) so re-switching doesn't clobber the
CLI's valid login with a stale refresh_token.
Never persist managed tokens at rest:
- Backfill of a managed provider always replaces live auth with the stored
placeholder (even on marker mismatch), so native tokens can't leak into the
provider's DB config.
- Sanitize managed auth out of Live backups on the initial/strict snapshot paths
and in update_live_backup_from_provider_inner before serialization.
Concurrency / blocking:
- Drop the redundant outer Arc<RwLock<CodexOAuthManager>> (all methods are &self)
so token refresh no longer holds a coarse lock across the network; give OAuth
token/device requests a 30s timeout instead of the shared 600s client default.
- Add a persistence lock and linearize the access-token cache under a consistent
accounts -> access_tokens order (existence-checked reads/writes; remove/clear
clear the cache atomically) to prevent stale or resurrected cache entries.
Switch ordering:
- Preflight the managed token before committing the current provider on
switch/add/update, so a failed token fetch can't leave DB/UI pointing at a
provider whose live config was never written.
Cleanup:
- Remove the now-dead content-fingerprint marker machinery; managed writes are
now shape-identical to a native login, so ownership is judged by account_id.
Known, documented limitations (B-scheme, narrow & recoverable): a remove+re-login
of the same account within an in-flight refresh window (ABA), a login authorized
after logout, and switch-away cleanup being unable to distinguish our write from a
user's native login of the same account.
* fix(codex-oauth): keep managed token in restore backup; fix failing tests
CI caught three test failures from the previous commit. Root cause: the Live
backup (proxy_live_backup) is local restore state that is replayed to
~/.codex/auth.json when proxy takeover ends, so it must contain the real auth to
restore a working login. Stripping it (the earlier "don't leak into backup"
change) broke restore and over-stripped a user's native login.
- Revert the backup auth stripping: remove sanitize_codex_backup_auth from the
initial/strict snapshot paths and the forced auth={} in
update_live_backup_from_provider_inner. The managed token belongs in the
restore backup (the refresh_token is already persisted by CodexOAuthManager, so
this is not a new exposure).
- Restore the ownership marker (account_id + access_token fingerprint) so backup
cleanup can tell our managed write from a user's native `codex login` of the
same account. extract now also tolerates the full-bundle shape (refresh_token +
last_refresh) so it fingerprints ①'s refreshable writes. clear_codex_auth_in_backup
and clear_codex_live_auth_for_managed_account use the marker again; the
account_id-only helper is dropped.
- Fix the adopt unit test: adopt now invalidates the cached access token, so the
test asserts the stored refresh_token/id_token were updated and the cache was
cleared instead of reading it back through get_valid_token_bundle (which would
trigger a network refresh).
Token stays out of the exported provider settings_config (backfill strip) — only
the local restore backup keeps it, matching pre-existing behavior.
* fix(codex): harden managed OAuth credential lifecycle
* fix: preserve Codex restore auth state
* feat(codex): support multiple official accounts
* feat(codex): refine official account login selection
Keep native Codex login separate from managed ChatGPT accounts, migrate provider identity safely when switching modes, and cover the account-selection states with regression tests.
* style(codex): satisfy clippy for login guard
---------
Co-authored-by: Jason <farion1231@gmail.com>
Co-authored-by: YUZHEthefool <2804776511@qq.com>
* feat(presets): pre-fill vendor-documented Codex reasoning levels
Follow-up to #6228 per-model reasoning levels: pass reasoningLevels /
defaultReasoningLevel through the modelCatalog() preset helper and
pre-fill the enums documented by each vendor for native Responses
presets, so known models ship with their real effort tiers out of the
box while remaining user-editable per provider.
Filled (official-doc evidence cited inline):
- Volcengine Agent/Coding Plan ark-code-latest: low/medium/high
- DouBaoSeed doubao-seed-2-1-pro-260628: minimal/low/medium/high
- Tencent Hunyuan hy3 & hy3-preview: low/high
- Longcat LongCat-2.0: high (single tier)
- xAI Grok (API & OAuth) grok-4.5: low/medium/high
Deliberately left unfilled (comments guard against "completing" them):
DeepSeek direct (official catalog mirror already declares low/high/max),
MiniMax & Xiaomi MiMo (official catalog equals template default,
zero-effect fill), Bailian qwen3-coder-plus (no per-model evidence).
defaultReasoningLevel is never declared: the backend fallback lands on
high for every filled entry. Chat-route presets are out of scope since
effort only toggles thinking there (supportsEffort: false).
Adds tests/config/codexReasoningLevelPresets.test.ts locking the
expected enums, the unfilled list, and a canonical-effort whitelist
over all presets (unknown values are silently dropped downstream).
* feat(presets): fill remaining Codex reasoning levels for form visibility
Second round of the #6228 preset pre-fill, reversing the round-1
"deliberately unfilled" list per product ruling: an "unset" reasoning
levels field in the provider form is more misleading than a redundant
or snapshot-style declaration.
- DeepSeek direct (flash + pro): low/high/max, copied from the official
catalog mirror. Per-row values override the mirror, so the preset must
be kept in sync if DeepSeek's catalog changes (noted inline).
- MiniMax x2 and Xiaomi MiMo x2 (both models): none/high, equal to the
template default - zero behavior change, declared only so the form
shows the real tiers.
- Zhipu GLM x2 (Chat route): none/high two-state. Also fixes a real
gap: the gpt5_5 template default tiers (low/medium/high/xhigh) had no
none, so users could not disable thinking from Codex's picker even
though GLM's thinking:{type:"disabled"} switch is real; the other
template tiers were fake differentiation (supportsEffort: false).
Still unfilled: Bailian qwen3-coder-plus (no per-model evidence) and
OpenCode Go (multi-vendor Chat gateway without a codexChatReasoning
declaration - the thinking toggle is inert there, so a none tier would
be a fake switch).
Test file extended to 20 assertions covering the new fills and the
updated unfilled list.
* fix(presets): replace unavailable SiliconFlow/ModelScope catalog models
2026-08-15 vendor audit (official catalogs, cross-checked live):
- SiliconFlow: MiniMax-M2.7 was never listed on either site - users got
400 "Model does not exist" out of the box. The two sites carry
different MiniMax lineups and must not mirror each other:
- .cn -> Pro/MiniMaxAI/MiniMax-M2.5 (catalog contextLen 196608);
reasoning levels left unset (no official evidence M2.5 thinking can
be disabled via enable_thinking)
- .com -> MiniMaxAI/MiniMax-M3 (official 1M window = 1048576);
reasoningLevels none/high (platform enable_thinking switch is real
and M3 thinking is documented as disableable)
- ModelScope: bare ZhipuAI/GLM-5.1 id is not in the free API-Inference
service list (only the BYOK :DashScope variant exists) - replaced
with ZhipuAI/GLM-5.2 which is on the free community route. Levels
left unset until field passthrough to the upstream is verified.
Applied across all seven apps carrying these presets (claude,
claude-desktop, codex, hermes, openclaw, opencode, pi), plus the
hardcoded model id in the SiliconFlow platform-inference Rust test and
the context-window test assertions. Bare MiniMax-M2.7 ids (MiniMax's
own official endpoint) are a different platform and intentionally
untouched. Pi catalog key moved to the existing zai/glm-5.2 entry.
Known follow-up: openclaw SiliconFlow cost figures still reflect the
old M2.7 pricing and need a separate re-audit.
* feat(proxy): extend StepFun effort inference to step-3.7-flash
The StepFun inference block predated step-3.7-flash and only granted
reasoning_effort to models containing "2603". Per the official
reasoning guide and model pages (both .com and .ai sites, 2026-08-15
audit), step-3.7-flash supports low/medium/high (default medium) while
step-3.5-flash-2603 supports low/high; the suffix-less step-3.5-flash
exposes no effort control.
- supports_effort now also matches step-3.7-flash
- effort_value_mode is split per model: 2603 keeps the low_high
collapsing map; 3.7-flash uses passthrough - reusing low_high would
collapse medium into high, creating tiers with no wire difference
- the fix stays in the inference table rather than a preset-level
codexChatReasoning declaration, which is provider-wide and would lose
the per-model gating (suffix-less step-3.5-flash must not receive
reasoning_effort - its behavior is undocumented)
- StepFun preset catalogs pre-fill the vendor-documented tiers
(3.7-flash low/medium/high, 2603 low/high, suffix-less unset); no
none tier anywhere - StepFun documents no way to disable thinking
- new unit test locks the three per-model inference outcomes
* fix(presets): correct thinking-switch dialects for aggregator platforms
2026-08-15 vendor audit, aggregator group. All three presets declared
the Zhipu-style top-level thinking:{type} object, which is a model
vendor dialect their platforms never documented:
- ModelScope: platform switch is the top-level enable_thinking bool
(official model-page extra_body samples + Bailian first-party GLM
docs). thinkingParam fixed; levels stay unset until field passthrough
to the upstream is verified.
- Novita: same fix - the Create-chat-completion parameter enum has no
thinking object; enable_thinking is documented platform-wide. GLM-5.1
catalog row gains none/high (two-hop inference, noted inline).
- Nvidia NIM: fake switch withdrawn. The per-model official OpenAPI
(additionalProperties:false) has no top-level thinking field; the
real control chat_template_kwargs:{thinking:bool} is outside our
thinkingParam value range, so the declaration becomes an explicit
no-injection block (supportsThinking:false, thinkingParam:none).
All three keep their codexChatReasoning block on purpose: deleting it
would fall through to the backend name/model inference, which matches
"glm"/"kimi" in the model id and would resurrect the wrong dialect.
A new modelscope branch in infer_aggregator_platform_config covers
hand-created ModelScope providers the same way the siliconflow branch
does (platform identity beats model-vendor hints), with a unit test.
* feat(presets): real thinking switches for Qianfan and BytePlus Codex
2026-08-15 vendor audit, official-switch group:
- Baidu Qianfan Coding Plan: the v2 Chat API documents top-level
thinking:{type:enabled/disabled} (same shape as Zhipu) across the
plan's six main models, and Baidu's own OpenCode integration guide
sends it on the /v2/coding endpoint. Declaration added (with the
supportsThinking:true flag that gates the whole injection path) plus
none/high levels. Known alias caveat noted inline: if the console
resolves qianfan-code-latest to a non-thinking model the field is
ignored, not rejected (platform documents unsupported params as
ignore-not-error).
- BytePlus (ModelArk intl): the "not verified for the intl site"
comment is obsolete - BytePlus' own Codex guide configures this exact
coding/v3 endpoint with wire_api="responses". Switched apiFormat to
openai_responses (aligned with the domestic Volcengine Plan presets,
one less conversion layer) and pre-filled low/medium/high per the
official Codex doc, with the glm-5-2 tier-collapse caveat noted.
Test moves BytePlus from the Chat-route list to the native Responses
list; reasoning-level expectations extended for both presets.
* feat(presets): enable reasoning effort for Kimi per vendor's own guide
2026-08-15 audit, Kimi group. Both Kimi presets declared
supportsEffort:false, yet Moonshot's two official Codex integration
guides - which use CC Switch as their worked example - explicitly
instruct users to enable both "支持思考模式" and "支持推理强度/思考
等级". The missing piece was the top-level reasoning_effort string
(low/high/max), the only reasoning control the K3 family supports.
Open Platform preset (api.moonshot.cn/v1):
- supportsEffort:true + effortParam reasoning_effort; effortValueMode
stays unset (passthrough - the deepseek map would crush low to high)
- kimi-k3: low/high/max. No explicit default: the template default
medium is outside the subset so the backend falls back to the highest
tier, max, which is exactly the vendor default
- kimi-k2.7-code: single-tier ["high"] (always-thinking, effort
documented as unsupported - single tier avoids the template's fake
differentiated tiers, LongCat precedent)
Kimi For Coding preset (api.kimi.com/coding/v1):
- same declaration change; catalog extended with the full official
model table: kimi-for-coding-highspeed (262144), k3 (1048576),
k3-256k (262144)
- k3/k3-256k: low/high/max with explicit defaultReasoningLevel high -
first use of an explicit default: the vendor default (high) differs
from the fallback (max), and max would silently burn more tokens
- no none tier anywhere: k2.7/k3 cannot disable thinking, and on the
Kimi Code gateway disabling reroutes requests to K2.6 (different
model, different billing). Gateway effort whitelist rejects unknown
values with 400 (minimal is not in it); the declared subsets keep the
picker inside the whitelist
Known doc conflict noted inline: the parameter reference says K3 should
not receive the thinking param while the integration guide enables
thinking mode; production shows no failures, so we follow the guide -
first thing to check if users report Kimi 400s.
Test tuple gains an optional expected-default column.
* feat(presets): add Baidu Qianfan Token Plan preset across six apps
Token Plan (personal) replaced Coding Plan for new purchases on
2026-07-13; existing Coding Plan presets stay untouched since active
subscriptions remain valid until expiry.
- claude/claude-desktop: /anthropic/tokenplan/personal, all roles on
deepseek-v4-pro per the official Claude Code integration guide
- codex: /v2/tokenplan/personal via openai_chat local routing with a
six-model catalog; first Qianfan preset with supportsEffort enabled
(no alias indirection anymore, and the default model is on the
official reasoning_effort support list) using effortValueMode
"deepseek", which matches Qianfan's own low/medium->high,
xhigh->max compatibility mapping verbatim; deepseek models omit
defaultReasoningLevel so the fallback (max) matches the platform's
automatic max for agent requests
- opencode/hermes: full six-model lineup on the OpenAI-compatible
endpoint per the official integration guides
- openclaw: model entry mirrors the official OpenClaw guide verbatim
(context 98304 is the vendor's OpenClaw-specific figure, not the
platform model list's 1M)
ernie-5.1 is excluded (official retirement on 8/20). New test locks
endpoints, lineups and OpenClaw figures across five apps; codex is
covered by the existing chat preset and reasoning level tests.
* fix(usage): count DeepSeek cache-hit tokens in chat-format usage (#6126)
DeepSeek Chat defines cache hits through the documented fields
prompt_cache_hit_tokens / prompt_cache_miss_tokens. CC Switch previously
only read the standard/OpenAI-compatible cache fields, so responses that
omit prompt_tokens_details.cached_tokens lost cache-hit accounting. This
adds the DeepSeek-documented field as a last-resort fallback in both the
usage parser (proxy_request_logs / usage page) and the Codex
chat-to-Responses usage synthesis (shared by streaming and non-streaming
paths).
The official endpoint currently also mirrors the value into the
undocumented prompt_tokens_details.cached_tokens (verified 2026-08-05
against api.deepseek.com), which is why existing accounting was
unaffected there. The fallback aligns with the documented contract,
defends against the undocumented mirror disappearing, and covers relays
that only pass through the documented fields. Behavior is unchanged
whenever the upstream sends any standard cache field — the DeepSeek
field sits last in the or_else chain.
Refs #6073
* fix(codex): route OpenCode Zen reasoning effort through the platform config (#6123)
* fix(codex): route OpenCode Zen reasoning effort through the platform config
Zen 网关的合法 reasoning_effort 档位是逐模型的(models.dev
reasoning_options:glm-5.2 仅 high|max、deepseek-v4-flash 为
low|high|max、kimi-k3 仅 max,glm-5.1 等为 toggle 型无 effort),
opencode 客户端也严格按模型声明发值。此前统一并集映射会把 Codex
默认的 medium 发给只声明 high|max 的 glm-5.2(默认路径必现),
也会把 low 发给不支持 low 的 deepseek-v4-pro(回归)。
- infer_aggregator_platform_config 新增 opencode.ai 平台条目(仅按
name+base_url 判定,不看模型名),zen 模式:顶层 reasoning_effort
+ reasoning_content,不发厂商原生 thinking 形状
- CodexChatReasoningConfig 新增运行时字段 effort_levels(#[serde(skip)]
不持久化);resolve 末端对 zen 模式按请求模型从供应商
settings_config.modelCatalog 的 reasoningLevels(复用 #6228 引入的
逐模型声明,camelCase/snake_case 双格式兼容)查表附上,查不到则
None —— 转换层完全不发 reasoning_effort
- transform_codex_chat 的 zen 映射改为逐模型钳制:取「不小于请求的
最近合法档」,超出最高档取最高档;请求值无法识别则丢弃字段
- OpenCode Go 预设按 models.dev 补上逐模型 reasoningLevels 表;
表单加载侧提取纯函数 mapCodexCatalogModelForForm 并保住
reasoningLevels 的 load→save 回环(含 snake_case 旧数据),保存侧
trim 档位值
存量供应商不会自动获得档位表(其 settings_config 无 reasoningLevels),
行为退化为不发 effort 字段(与改动前一致的安全默认);重新添加一次
OpenCode Go 预设即可获得逐模型表。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(codex): move OpenCode Go to filled entries in reasoning-level expectations
上游 #6228 的 deliberately-unfilled 清单把 OpenCode Go 整体列为不填,
依据是「无 codexChatReasoning 声明、逐模型语义未经网关验证」——两个
前提均被本 PR 改变:预设已补 codexChatReasoning(effortValueMode:"zen"),
且逐模型档位有据(opencode 客户端 variants() 严格按 models.dev
reasoning_options 发值)。glm-5.2 / deepseek-v4-pro / deepseek-v4-flash
移入 EXPECTED(不填 none:effort 档位非思考开关);glm-5.1(toggle 型)、
kimi-k2.7-code(官方标注不支持 effort)、mimo-v2.5-pro(models.dev 未
收录)保持不填并更新注释依据。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(proxy): support Codex Alpha Search and Claude hosted WebSearch (#5681)
Two independent web-search paths behind the CC Switch local proxy:
- Register POST /alpha/search and its local aliases as a semantic passthrough
to the selected Codex provider, reusing provider selection, model mapping,
auth, retry/failover, and logging. Full-URL providers derive the sibling
/alpha/search endpoint only from unambiguous /responses URLs and fail
closed otherwise.
- Bridge Claude Code's hosted WebSearch tool onto OpenAI Responses and the
Codex OAuth backend: request translation with fail-closed semantics for
unrepresentable constraints (blocked_domains, non-direct callers,
response_inclusion, unverified versions), paired server_tool_use /
web_search_tool_result blocks, citation preservation, max_uses enforcement,
multi-turn replay, and usage.server_tool_use.web_search_requests.
Fixes #5363
Fixes #5378
Co-authored-by: zhuanzhi <zaz92537@126.com>
* fix(detect): resolve Windows CLI detection from registry PATH and standalone installer dirs (#6284)
* fix(detect): resolve Windows CLI detection from registry PATH and standalone installer dirs
On Windows, CLI version detection relied solely on the inherited process
PATH plus a set of hardcoded directories. This broke detection in two common
cases, both reported as open issues:
1. After an in-app self-update, the MSI/WiX-auto-launched process inherits
only the machine-level PATH and drops the user-level PATH (#6061), so any
CLI installed in a user-PATH location reads as "not installed" until the
user fully exits and relaunches from the Start menu.
2. Non-npm installs (winget Claude, the standalone Codex installer, a custom
npm prefix) live in directories that were never scanned, so CC Switch never
saw them (#6278, #6047, #4366).
Detection also probed hardcoded fallback dirs before the PATH default, letting
a stale shim in `%APPDATA%\npm` override the newer install the user actually
runs — "updated but still shows the old version" (#4701).
Fix, all in `src-tauri/src/commands/misc.rs`:
- `effective_path_string()` / `effective_path_os()` reconstruct the effective
PATH by merging the process PATH with the HKLM + HKCU registry `Path`
values (REG_EXPAND_SZ expanded via `expand_env_chars`). Used by
`build_tool_search_paths`, `scan_cli_version`, `enumerate_tool_installations`
and `resolve_path_default`, so detection sees the same set of installations
a freshly logged-in shell would, regardless of how the process was launched.
Mirrors the registry read already done in `env_checker::check_system_env`.
- `build_tool_search_paths` now explicitly scans the standalone installer
dirs `%LOCALAPPDATA%\Programs\OpenAI\Codex\bin` (codex) and
`%LOCALAPPDATA%\Programs\claude` (claude) ahead of the npm dir, as a
belt-and-suspenders fallback for installs that never touch PATH.
- `get_single_tool_version_impl` on Windows now probes the PATH-default entry
first (new `probe_path_default_version`) and falls back to the directory
scan only when it is genuinely absent — mirroring the non-Windows
`try_get_version` -> `scan_cli_version` structure, so the displayed
"current version" tracks what `tool` resolves to in a terminal.
- `resolve_path_default` runs `where` against the merged effective PATH and
skips App Execution Aliases (`Microsoft\WindowsApps`) so a reparse point that
launches the Store / a protocol handler is never treated as the PATH
default.
Regression tests added for the new Windows helpers and the standalone-dir
inclusion.
Closes #6278, #6061, #6047
Refs #4366, #4701
* fix(detect): support canonicalized Windows command scripts
* fix(detect): harden PATH lookup handling
* test(detect): normalize Windows path assertion
* fix(windows): eliminate startup white-black flash (FOUC) (#6252)
* fix(windows): eliminate startup white-black flash (FOUC)
Set window backgroundColor to match the dark theme surface so WebView2's
native window layer is dark before any web content paints. Add an inline
anti-FOUC script in index.html to apply the theme class synchronously
before first paint, reading from localStorage with system fallback.
Update CSP script-src to allow the inline script via SHA-256 base64 hash
alongside the existing 'self' source.
Fixes #6182
* fix(windows): show window after themed page loads
* fix(grokbuild): stop showing Codex-specific copy in the Grok Build form (#6511)
#6427 aligned the Grok Build provider form with Codex by reusing
CodexFormFields, but kept Codex-facing copy that does not apply to
Grok Build:
- the default-model hint named Codex and referenced the model-mapping
fallback, which Grok Build does not have (no catalog section)
- the model placeholder suggested a GPT model
- the advanced-section hint listed model mapping as contained
- the reasoning-effort hint referenced Codex's reasoning.effort
- the max-output-tokens hint referenced Codex's request body
Branch those five strings on appId and add grokBuild.* locale entries
(en/zh/ja/zh-TW) describing the Grok Build semantics; Codex rendering
is unchanged. Add regression tests asserting the Grok form shows the
Grok copy and never the Codex text.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(usage): keep multi-year trend tooltip and dots on the same point (#6337)
The trend chart used the localized MM/DD label as the Recharts category key, so a range spanning multiple years made the same MM/DD collapse into one category and the active dot snapped to the earlier year's point while the tooltip followed the cursor.
Use the backend RFC3339 bucket timestamp as the category key, resolve tick text by that key, and read the tooltip heading from the point payload. Axis ticks gain a 2-digit year only for multi-year ranges.
Fixes #6302
* fix: remove stray > char in UnifiedSkillsPanel JSX
* fix: resolve 2 compilation errors from upstream merge (temp_guard naming + Pi match arm)
* feat: add quota display mode setting (compact/expanded) to control subscription quota rendering globally
* refactor: move quota display mode setting from Proxy tab to General tab
* refactor: quota display settings match other settings style, terminal always at bottom
* fix: add icon to quota display settings
* fix: expanded mode always shows full expanded layout, not inline format
* fix: change display text from '套餐' to 'plan套餐'
* fix: expanded mode always shows 'plan套餐', compact mode hides expand button
* fix: compact mode uses tier format for providers with tier info
* fix: recognize OpenCode Go as plan provider for compact display
* fix: remove hardcoded label, let UsageFooter handle display for all providers
* Revert "fix: remove hardcoded label, let UsageFooter handle display for all providers"
This reverts commit afc438c853fc407bf901aa8659efbe3241c952c9.
* fix: reduce spacing between expanded panel and provider card
* fix: further reduce spacing between expanded panel and provider card
* fix: add icon to quota display toggle row
* fix: use different icon for toggle row vs header
* fix: update quota display description to option B
* fix: reverse toggle logic - default compact mode, toggle for expanded
* fix: adjust header spacing to match other settings sections
* fix: add space-y-1 to header for consistent spacing
* fix: format reset time in floating panel instead of showing raw date
* fix: hide reset time in floating panel
* fix: used highlighted green, hide ISO date, proper alignment
* fix: show reset time with label in expanded mode
* fix: unify reset time font size with other text
* fix: unify time display font style with usage display
* fix: show reset countdown + date in expanded mode
* fix: simplify countdown format to 4h57m with aligned time
* fix: add '重置于' label with countdown and aligned time
* fix: use Chinese countdown format with aligned time
* fix: use short countdown format with clock icon
* fix: hide minutes for weekly/monthly tiers
* fix: align numbers with fixed widths and add percent signs
* fix: use inline-flex for proper alignment in expanded mode
* fix: left-align remaining display and remove duplicate percent sign
* fix: remove leading zero from hours in countdown
* fix: add i18n for tier names in expanded mode
* feat: add Pi provider remote support (SSOT read/write, switch, remove)
* fix: unify percent sign color with label color
* fix: unify percent sign color with label color
* fix: move percent sign inside colored span for used display
---------
Co-authored-by: misaka_myu <misaka_myu@sina.com>
Co-authored-by: Allen Xu <s23372.xu@stu.scie.com.cn>
Co-authored-by: Jason <farion1231@gmail.com>
Co-authored-by: SaladDay <1203511142@qq.com>
Co-authored-by: Hu Miao <viktor@ppio.com>
Co-authored-by: Thefool <114680394+YUZHEthefool@users.noreply.github.com>
Co-authored-by: jiekouai <super@jiekou.ai>
Co-authored-by: jiekouai <231459779+jiekouai@users.noreply.github.com>
Co-authored-by: Thefool <2804776511@qq.com>
Co-authored-by: yovinchen <gzh298255@gmail.com>
Co-authored-by: Kaihang An <43318667+stofancy@users.noreply.github.com>
Co-authored-by: Kaihang An <ztmdsbt@gmail.com>
Co-authored-by: zayoka <141497128+zayokami@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: wuzhenyu <38416772+Program120@users.noreply.github.com>
Co-authored-by: zhuanzhi <zaz92537@126.com>
Co-authored-by: 0x400 <76235076+Hexc01@users.noreply.github.com>
…arion1231#5681) Two independent web-search paths behind the CC Switch local proxy: - Register POST /alpha/search and its local aliases as a semantic passthrough to the selected Codex provider, reusing provider selection, model mapping, auth, retry/failover, and logging. Full-URL providers derive the sibling /alpha/search endpoint only from unambiguous /responses URLs and fail closed otherwise. - Bridge Claude Code's hosted WebSearch tool onto OpenAI Responses and the Codex OAuth backend: request translation with fail-closed semantics for unrepresentable constraints (blocked_domains, non-direct callers, response_inclusion, unverified versions), paired server_tool_use / web_search_tool_result blocks, citation preservation, max_uses enforcement, multi-turn replay, and usage.server_tool_use.web_search_requests. Fixes farion1231#5363 Fixes farion1231#5378 Co-authored-by: zhuanzhi <zaz92537@126.com>
When a Responses SSE stream delivers its output only in the terminal response.completed / response.incomplete event, route it through the established Responses JSON conversion so text before a web search stays ahead of the search result. A missing terminal status falls back by event name, matching the incremental path's stop reason mapping. Also fixes terminal-only function_call items that previously produced stop_reason tool_use without a tool_use block. Follow-up to #5681.
When a Responses SSE stream delivers its output only in the terminal response.completed / response.incomplete event, route it through the established Responses JSON conversion so text before a web search stays ahead of the search result. A missing terminal status falls back by event name, matching the incremental path's stop reason mapping. Also fixes terminal-only function_call items that previously produced stop_reason tool_use without a tool_use block. Follow-up to farion1231#5681. (cherry picked from commit 2cdfc6a)
When a Responses SSE stream delivers its output only in the terminal response.completed / response.incomplete event, route it through the established Responses JSON conversion so text before a web search stays ahead of the search result. A missing terminal status falls back by event name, matching the incremental path's stop reason mapping. Also fixes terminal-only function_call items that previously produced stop_reason tool_use without a tool_use block. Follow-up to farion1231#5681.
* fix(codex): raise OAuth client identity to 0.155.0 for GPT-6 Sol/Luna (#7597) Requests for gpt-6-sol and gpt-6-luna through the Claude-to-Codex OAuth route advertised Codex 0.153.4, below the models' 0.155.0 minimal_client_version (official catalog since rust-v0.156.1), so ChatGPT's backend rejected them with HTTP 400 "model is not supported when using Codex with a ChatGPT account", and model discovery omitted both models. Bump the shared client version to 0.155.0, the exact floor rather than the latest release, to avoid opting into newer version-gated backend behavior. The regression tests for the generation headers and the model-list request now assert a version at or above the Sol/Luna floor. Fixes #7651 * fix(proxy): preserve max effort for GPT-6 Sol and Luna (#7662) Claude Code's explicit /effort max was downgraded to xhigh for gpt-6-sol and gpt-6-luna because the max-capable model whitelist added in #7531 predates them. Both models list max as a supported reasoning effort in OpenAI's model docs and the Codex model catalog, so add them to the whitelist; other models and effort levels are unchanged. The existing resolver and Anthropic-to-Responses regression tests now cover both models. * fix(detect): upgrade Homebrew Casks via brew --cask instead of npm (#6572) Recognize Homebrew Caskroom installs so cask-installed CLIs (codex, claude-code, grok-build) upgrade with `<brew> upgrade --cask <token>` instead of being anchored to Homebrew's sibling npm, which failed with EEXIST on /opt/homebrew/bin/<tool> or would sideload a second npm global package. - Share one Cellar/Caskroom token parser between formula and cask detection - Skip the Codex npm self-heal for broken cask installs, same as formulae - Classify install source from the resolved Cellar/Caskroom target so Intel /usr/local/bin launchers are tagged homebrew, while nvm/volta/fnm shims keep launcher-based classification Fixes #6562 * feat(about): add MiniMax Code install/update detection Add MiniMax Code (mcode) to the About page tool list: version probing, latest version from the npm registry (@minimax-ai/code), and silent install/update actions. - Install runs the official installer (install.sh / install.ps1) with an npm fallback. npm installs pass --ignore-scripts=false, --include=optional and --allow-scripts for @minimax-ai/code and better-sqlite3, since npm 12 blocks dependency install scripts by default and SQLite breaks without them. - `mcode update` is deliberately not used: without a TTY it only prints the command and exits 0 without installing, which would mask failures in silent lifecycle runs. - Script installs are updated in place by re-running the official installer with MCODE_INSTALL_DIR set to the detected root. Custom install dirs are claimed via the installer's install.json receipt (UTF-8 BOM tolerated, as Windows PowerShell 5.1 writes one), falling back to the default .minimax-code directory name. The Windows layout keeps mcode.cmd at the install root, so only the POSIX layout strips a bin segment. - npm global installs anchor to the sibling npm with the same flags. - Search the installer launcher dirs ($MCODE_INSTALL_DIR, ~/.minimax-code) ahead of generic Node locations. - Update the one-click install hint in all four locales, also adding the previously missing Grok Build. * fix(mcode): replace misleading empty-state copy that points to a missing import button MiniMax Code intentionally has no "Import Current Config" button (same as Pi): custom providers in config.yaml are merged into the list on every load, and the official MiniMax account provider plus its sign-in state are owned by MiniMax Code, which CC Switch refuses to write. The empty state still showed the generic copy asking users to click that button. Give mcode its own empty-state title and description in all four locales, route Pi and mcode through their own namespaces in ProviderEmptyState, and add a regression test. * feat(mcode): pass Pi model compat through to MiniMax Code presets MiniMax Code 0.5.x reads a whitelist of `models.<id>.compat` keys from config.yaml and layers them over the compat it synthesizes, so Pi's per-model compat can be reused instead of dropping every preset that carries one. - Keep a model when every compat key is one MiniMax Code reads, and write the compat verbatim. kimi-k3 stays out: `deferredToolsMode` is unknown to MiniMax Code's embedded pi-ai. - Map adaptive-thinking Claude models to `thinking.effortOptions`, since MiniMax Code ignores Pi's thinkingLevelMap and compat-blind versions would otherwise fall back to budget_tokens. - Presets grow from 40 to 77; only AWS Bedrock remains excluded. * fix(updater): upgrade Codex standalone installs in place instead of via npm Updating a CLI installed outside npm fell back to a bare `npm i -g`, which silently installed a second, npm-managed copy instead of upgrading the one the command line actually runs. - Codex standalone installer (install.sh / install.ps1): rerun the official installer with CODEX_INSTALL_DIR / CODEX_HOME pinned to the detected install. `codex update` is not used: it pipes `curl | sh` without pipefail and reports success even when the download fails. - Keep the npm self-heal for broken Codex installs away from standalone installs whose entry lives in a Homebrew/nvm bin directory. - When the default install is a native binary outside node_modules and its channel is unknown (winget, Scoop, Nix, manual download), stop falling back to npm: the probe reports it as unmanaged, the About page skips it with a localized hint, and the backend refuses to run it. - Add a Claude Code cask regression test on top of the Homebrew cask support from #6572. Fixes #7650 * docs(readme): refresh stale facts across all four READMEs and add a tagline and quick nav - Add Pi (10 managed apps), scope tray/MCP/Skills per app, document additive apps - Fix "signature bypass" wording (Hide AI Attribution), common-config FAQ labels, official-login/restart FAQs, and first-launch auto-import note - Correct Linux requirements (glibc 2.35 + WebKitGTK 4.1) and release asset names - Update dev env (Node 20.19+/22.12+, pnpm 10, Rust 1.95), drop broken coverage cmd - Point release notes to v3.20.4 and SSSAiCode to sssaicodeapi.com - JA: replace the stale unsigned-macOS instructions; fix Chinese FAQ anchors in SUPPORT.md and issue templates * docs(readme): restructure all four READMEs and move dev docs to CONTRIBUTING - Reorder sections (download and quick start before features), add a per-tool capability table, and document local routing, projects, the OAuth auth center (with a ToS risk note), quota display, usage without routing, S3 sync, and the CLI tool manager - Rewrite the data-storage FAQ; add FAQs for routing addresses, OpenAI/Gemini upstreams, connectivity checks, WSL, and the community CLI - Fix first-launch import (Claude Desktop is not imported), Pi's Enable button, and the non-existent Markdown preview; unify on "local routing" - Move architecture/dev guide/project structure into CONTRIBUTING.md and refresh it (Node/pnpm/Rust versions, CI-aligned checks, test isolation, four locales, no ESLint/pnpm lint) * docs(readme): use support@ccswitch.io as the sponsorship contact * fix(codex): add vision-capable glm-5.3-flash to Zhipu GLM presets (#7689) Add a glm-5.3-flash row to both the Zhipu GLM and Zhipu GLM en Codex presets, declaring ["text", "image"] input, a 1M context and the same low/high/max reasoning levels (default max) as glm-5.3. Without this row, users renamed the seeded glm-5.3 row to flash and inherited its hidden text-only declaration, so Codex rejected image input. The official Codex models.json on both sites does not list flash yet; the rows follow the model pages instead (natively multimodal, 1M context, fully available on the GLM Coding Plan, text parameters matching GLM-5.3). Fixes #7688 * docs(guides): refresh Codex guides for native Responses presets and the renamed auth toggle - Backfill website-only edits: Kimi direct-connection section, desktop FAQ cross-links, stray </content> - Retitle the DeepSeek guide around Chat-format providers; v4-pro, Kimi, GLM and MiniMax now connect directly - Rename the toggle to "Keep official login for direct switches"; document auth.json removal and takeover preservation - Prefill gpt-5.6-sol, mention the Kimi Global presets, and use routing takeover terminology - Remove the outdated, unreferenced proxy-guide-zh.md * docs(user-manual): refresh the zh/en/ja manual for v3.20.4 - Cover Grok Build, Pi and MiniMax Code across install, interface, settings, presets, MCP, Skills, prompts, sessions, local routing, config files and env conflict pages - Rename proxy/takeover wording to local routing and update settings paths to the Routing / Auth / Usage Statistics tabs - Rewrite 4.5 as the connectivity check that replaced model test in v3.16.3 (no real model request, no test model to configure) - Fix stale facts: official subscription quota is opt-in via the Official Subscription template since v3.16.2, Codex keys live only in config.toml experimental_bearer_token, deep link content/config must be Base64, SQL export includes usage logs, onboarding skip writes hasCompletedOnboarding in ~/.claude.json - Backfill the website-only Codex preset section (native Responses vs Chat Completions) and align UI labels with the v3.20.4 locales - Retranslate en/ja from the approved zh master, keeping all three languages line-aligned with matching anchors * fix(grokbuild): keep provider model table selected when backfilling live config Grok Build's /settings "Default model" rewrites models.default in ~/.grok/config.toml. Selecting a built-in model while a third-party provider was active left the provider's own [model.*] table unreferenced; the switch backfill stored that config verbatim, and switching back then failed validation with "missing [model.\"...\"]". When models.default is missing or dangling during backfill and the provider's original default table is still present, restore it. * fix: stop shared config and live files from leaking provider settings Common config snippet (Claude Code): - Treat custom request headers (`*_CUSTOM_HEADERS`, `HEADERS`) and any key containing COOKIE or AUTHORIZATION as credentials. Header names in general stay shareable (CLAUDE_CODE_ATTRIBUTION_HEADER, OTEL_EXPORTER_OTLP_HEADERS). - Never share key fields: all ANTHROPIC_* settings, the protocol selectors (CLAUDE_CODE_USE_BEDROCK/VERTEX/...), AWS_* and Vertex regions, and top-level model/apiKeyHelper/fallbackModel. Switching from Bedrock to the official provider no longer keeps Claude Code on Bedrock, and a /model choice no longer follows every switch. - Entries an existing snippet already shared are stripped from the current provider's row on its next switch-away, so they cannot get stuck in the wrong provider. A provider carrying AWS or Google credentials keeps that family's selectors and regions. Live files holding keys are now written owner-only (0600): Codex auth.json and config.toml, Claude Code settings.json and Grok Build config.toml. New files used to follow the umask (0644). Presets: - E-FlowCode and PIPELLM no longer ship personal preferences; Codex E-FlowCode drops `personality`. - AWS Bedrock (API Key) stores the key in env.AWS_BEARER_TOKEN_BEDROCK, which Claude Code reads. The key field and the proxy read it too; existing rows with a top-level apiKey keep working and are edited in place. - A preset scan keeps preference keys out of Claude and Codex presets. Remove dead code only reached from tests: ProxyService::start_with_takeover and its helpers, and the ConfigService sync_current_providers_to_live family. Behavior changes: ANTHROPIC_* settings such as ANTHROPIC_LOG are no longer shared; a Bedrock or Vertex provider whose selectors only lived in the shared snippet may need them re-entered once. * test: add golden tests that lock live-config behavior before the switching refactor The upcoming refactor replaces whole-file live writes with key-field replacement and splits direct and proxy modes. These tests pin what must survive it, and pass on the current code. Backend (`cargo test --test golden`, snapshots under src-tauri/tests/golden/snapshots, refresh with CC_SWITCH_UPDATE_GOLDEN=1): - mcp_bytes: MCP projection bytes in ~/.claude.json, Codex config.toml and Gemini settings.json, and that switching providers leaves the MCP section untouched. - import_rows: raw DB rows written by deep-link import and first-run import (including seeded official rows and the generated snippet). - file_modes (Unix): which client files a switch writes and their permission bits; existing 0600 files are never loosened. - claude_key_fields: after a switch, live key fields are exactly the target provider's, across third-party, Bedrock, Vertex, apiKeyHelper and official providers, with or without common config. - codex_red_lines: 25 Codex invariants (index at the top of the file), asserted as outcomes: the config loads on Codex 0.149, the official login never reaches a third-party URL, refused switches leave files, current provider and rows untouched. - downgrade_contract: the previous release detects takeover by the PROXY_MANAGED literal, rebuilds live from current rows without backup rows, and replays leftover backup rows. Frontend: ProviderForm submit payloads for ten representative Claude and Codex presets. Three spots are sorted before comparing because their output order is not deterministic today (MCP maps written from a HashMap, Gemini .env parsed into a HashMap). One ignored test records a known gap: exiting the proxy restores a login the user logged out of while on a third-party Codex route. * refactor(usage): drop per-provider and global cost multipliers Relays already price in "official price x their own multiplier", and the real spend is visible from the relay's own balance or usage script. A second copy of that multiplier in CC Switch only drifts out of sync, and it only ever applied to requests going through local routing (session log imports always recorded 1.0), so users setting it in direct mode saw no effect at all. - Remove the pricing section from the provider editor (it was also shown for Pi and MiniMax Code, which never go through local routing). - Remove the default multiplier column from the usage pricing defaults; keep the global pricing model source (request vs response model) and say it only applies to routed requests. - Remove the get/set_default_cost_multiplier commands and DAO methods. - Routed requests are now always recorded at multiplier 1. Existing log rows keep the multiplier they were recorded with. - Keep costMultiplier / pricingModelSource in ProviderMeta, unread, so older devices syncing the same rows do not lose their values; the proxy_config column stays as well, so no schema migration is needed. * fix(database): reclaim the whole freelist on incremental vacuum PRAGMA incremental_vacuum frees one page per result row, and the pages are only released while the statement is being stepped. execute_batch steps it once, so startup cleanup and periodic maintenance reclaimed a single page each time and the database file never shrank after pruning. Add Database::incremental_vacuum_on_conn, which drains every result row, and use it in both places. * feat: add a key-field write engine for client config files Client config files are now changed through one engine instead of being rebuilt from provider snapshots: - Key-field definitions live in live/floor.rs and are shared with the common-config extraction. - JSON, TOML and dotenv patchers change only the keys they name. Other keys, values, order, comments, indentation, line endings and BOM stay as they are, and writing the same target twice is byte-stable. - A file that cannot be parsed is refused and left untouched; it is never rebuilt from an empty document. - Each write is staged next to the target, re-read and compared before the rename, and replanned on top of the new content when another writer got there first. The first engine write of every file keeps a byte-for-byte backup under ~/.cc-switch/backups/live-first-write/. - Multi-file operations record their intent in ~/.cc-switch/live-state.json (0600) before publishing. After a crash the operation is rolled forward from the staged files, discarded if nothing was published, or abandoned if the files were changed externally. Recovery runs at startup and before every operation. Claude Desktop is the first client moved onto the engine. Its four files are written in one operation; the gateway profile keeps the user's own settings, seeds disableDeploymentModeChooser and coworkEgressAllowedHosts only when missing, and is written owner-only. Restoring the official app now clears only the CC Switch key fields instead of deleting the profile, and a meta file that cannot be parsed is refused instead of replaced. * feat(claude): switch Claude Code by replacing only key fields Switching, adding the first provider, editing the current provider, sync, universal providers and restoring after the local router all write settings.json through the write engine now. Only endpoint, credential, model and protocol fields plus the provider's own switches are replaced; hooks, plugins, permissions and everything else stay byte-for-byte. The file write and the current-provider pointer commit as one operation, so a crash in between rolls forward on startup. - No more backfill, no common-config snippet and no context-window injection for Claude. Window values left behind by the old injection are cleaned up from a frozen list. - The provider editor shows what settings.json will look like after switching. On save, provider fields go back to the row and other edits go to live as global settings, with a three-way check against external changes and a dialog to resolve conflicts. Row fields that never reach live are listed with a one-click add. - New Claude rows keep commonConfigEnabled: true so older versions that sync the row still merge their snippet. - Presets drop timeout and telemetry keys; a test now requires Claude presets to contain only key or provider-owned fields. * feat(proxy): keep a per-app direct/proxy mode instead of backing up live files Local routing no longer takes over a client by backing up its live config and restoring it later. Each routable app (Claude Code, Codex, Gemini CLI, Grok Build) now has a device-local mode in live-state.json: direct or proxy, whether it is attached, the proxy route, and the contract written into the client. proxy_config.enabled is kept only as a mirror for older versions. - Entering proxy mode writes the route's contract (local proxy address, placeholder credential, model aliases, provider-exclusive fields) into the client; leaving re-projects the direct provider. No backups. - The proxy route is independent of the direct provider. Switching while routing changes only the route; if the contract is unchanged the client file is neither read nor written. Failover records the new route and never touches client files. - Current-provider reads go through mode::current::provider_for with an explicit purpose (direct pointer vs. the provider in use); a unit test rejects direct calls to the old getters. - Quitting detaches (writes the direct config back, keeps the mode); startup re-attaches. Legacy takeover state is migrated on startup: backup rows are saved to ~/.cc-switch/backups/proxy-live-backup/ and deleted so an older build cannot replay them. - "Stop routing" returns every app to direct and then stops the server. Applying a project no longer disables routing; it moves the direct provider or the route depending on the mode. A provider used as the route cannot be deleted. - Codex and Grok Build still use their whole-file writers for proxy writes, and all three non-Claude apps for leaving; the direct row is backfilled from live before entering (never for the official Codex card), and leaving to an unbound official Codex card writes config.toml only. - The provider list marks the direct provider while routing. - Removes the backup/restore/hot-switch/crash-recovery paths from the proxy service, the unused tray Auto item, and the downgrade golden test. * feat(codex): switch Codex by replacing only key fields Switching, adding, editing, sync, universal providers and entering or leaving local routing now write Codex through the write engine. Only routing, credential, model, reasoning, context-window and compatibility fields are replaced; MCP servers, [projects], comments and the user's own provider tables stay byte-for-byte. auth.json, config.toml, the model catalog, the managed-account marker and a device-local login stash commit as one operation, so a crash at any point rolls forward on startup. - Third-party routes are always written as [model_providers.custom] with the key in experimental_bearer_token. Rows that use another id, a top-level openai_base_url or a legacy reserved table are normalized on the way out. Switching to an official card leaves a dormant custom table (local proxy address plus placeholder credential) so older third-party sessions can still resume. - requires_openai_auth follows the route's auth and whether a login is on disk; keyring and ephemeral credential stores are handled separately. - A switch is refused before anything is written when the active profile overrides the route, the selected table is missing or built in, a keyless row would fall back to the official login, or the row has no auth. - Provider tables an older version wrote are removed only when they can be proven to be CC Switch's. Tables a profile references are kept, and reserved ids that cannot be proven are renamed. - Provider-exclusive fields, including web_search and a row's own model_catalog_json, are removed on the way out only when live still holds the previous provider's value. - No more backfill. A native ChatGPT login that CC Switch deletes or overwrites is kept in ~/.cc-switch/codex-login-stash.json (0600, not synced) and restored when switching back to an official card; logging out on an official card discards it. Login snapshots already in official rows seed the stash once. A leftover API key in auth.json is deleted only when it matches a third-party row's key. - Managed accounts fetch their token before taking the lock. If the CLI refreshes the login during a switch, the switch stops before writing anything. Once files are published, a later failure no longer rolls the row back; startup finishes the switch. - Local routing: third-party routes get a fixed local-proxy table and the official route uses cc-switch-official plus the client's own login. The contract covers every field written, so a route switch with the same contract touches no files. - The Codex editor shows what config.toml will look like after switching. On save, key fields go to the row and other edits go to live as global settings with a three-way check. Row fields that never reach live are listed read-only and can be copied. - The Codex common-config snippet is frozen like Claude's; its UI and the TOML snippet command are removed. New Codex rows keep commonConfigEnabled: true for older versions that sync them. - The write engine can delete a file as part of an operation. - Integration-test mutexes tolerate poisoning so one failure no longer cascades into unrelated tests. * feat(gemini,grok): switch Gemini CLI and Grok Build by replacing only key fields Switching, adding, editing, sync and entering or leaving local routing now write Gemini CLI and Grok Build through the write engine. Whole-file rewrites, backfill before switching and snippet merging are gone. Gemini CLI: - Only the key-field lines in .env change; comments, order and the user's own variables stay. In settings.json only security.auth.selectedType and model.name change. Both files commit as one operation. - selectedType follows the provider type (Google login for official cards, API key otherwise) instead of the row, so a /auth choice an older version stored in a row can no longer send a third-party card through Google login. - Local routing writes the route's model name along with the local address and placeholder key, so switching to a route with another model rewrites the client, as Claude Code does. - Vertex rows pass validation with GOOGLE_API_KEY or GOOGLE_GENAI_USE_VERTEXAI instead of GEMINI_API_KEY. - The common-config snippet is frozen like Claude's and Codex's; its UI and the explainer dialog are removed. New rows keep commonConfigEnabled: true for older versions that sync them. Grok Build: - Only models.default and the [model."<name>"] table CC Switch wrote change; MCP servers and the user's other tables stay. - The written table name is recorded in live-state.json in the same operation, and switching away removes the recorded table rather than whatever models.default points to now, so a default changed in Grok's /settings no longer leaves the old provider's table behind. Without a record (just upgraded), the table is inferred from the current row. Tables holding the proxy placeholder key are always removed. - Local routing writes the route's table pointing at the local /grokbuild/v1 endpoint with the placeholder key through the engine. An official card cannot be a route. Editors: - Both editors show what the client files will look like after switching. On save, key fields go to the row and other edits go to live as global settings with a three-way check. Gemini row fields that never reach live can be added to the global settings; Grok's can be copied, as in Codex. The TOML diffing shared with Codex moves to editor_toml.rs. Also: - First-run import no longer moves key fields (GEMINI_MODEL, Codex model_reasoning_effort and the like) from the default row into the snippet, where a frozen snippet would lose them. - The .env patch can remove keys unconditionally and parse entries. * feat(providers): add Codex, Gemini CLI and Grok Build providers through the key-field editor The add dialog for these apps now follows the same rules as the editor and Claude Code's add dialog. Picking a preset (or the custom template) projects it onto the current client files, so the editor shows what the files will look like after switching to the new provider. On save, key fields and provider-exclusive fields go to the row and other edits go to live as global settings with a three-way check. Before, anything typed there besides key fields was stored in the row and never took effect. - Adding and editing share one backend path per app. When the new provider is the first one, its key fields and the direct pointer are committed in the same write; the Gemini and Grok editor writers take a set_pointer flag for this, as Codex's already did. - The form reports each projection to the dialog as the three-way base. Only the latest request counts; while a projection is pending or after it fails, the add saves the row only. Every reset of the shown content re-projects or clears the base, otherwise global settings missing from the editor would be written to live as deletions. Grok's official card has no editor and keeps its seed flow. - Remove six unused common-config strings and rewrite the empty-state hint that still described the snippet; it now explains key-field switching and also shows for Grok Build. * docs(zh): describe key-field switching in the README FAQ and user manual Chinese master text for the new switching behavior; translations follow once it is final. - README FAQ: switching no longer touches plugins, hooks and other shared settings (replaces the common-config snippet answer); a model changed in the client resets on switching back; routing keeps the direct provider and writes it back on exit; the device-local files (live-state.json, the Codex login stash, first-write backups) and the restart note now covering Gemini CLI. - Manual: the editor section (what it shows, global vs per-provider fields, edit conflicts, inactive fields) replaces backfill; the add dialog follows the same rules; quick toggles are marked global or per-provider; the keys each app's switch changes; the config ownership and malformed-file sections; routing writes back the direct provider; the Codex login is restored when switching back to an official card; Gemini auth follows the provider type. * docs(zh): fix leftover restart and routing backup wording - The routing feature bullet and the quick start still said only Codex and Grok Build may need a restart when a switch changes the model; Gemini CLI does too, as the FAQ already says. - The routing page still said routing backs up the original config and restores it; disabling routing now writes back the direct provider, and the only backup is the one taken before the first write. * docs: translate key-field switching docs to English, Japanese and German Brings the README FAQ (English, Japanese, German) and the English and Japanese user manuals in line with the Chinese master text. - README FAQ: switching no longer touches plugins, hooks and other shared settings; a model changed in the client resets on switching back; routing keeps the direct provider and writes it back on exit; the device-local files; the restart note now covers Gemini CLI. - Manual: the config editor section replaces backfill; the add dialog follows the same rules; quick toggles are marked global or per provider; the keys each app's switch changes; config ownership, malformed files and the first-write backup; routing writes back the direct provider; the Codex login is restored when switching back to an official card; Gemini auth follows the provider type. * fix(gemini): ask for a restart after switching Gemini CLI Gemini CLI loads ~/.gemini/.env only at startup (and when validating the auth method), and never overwrites a variable already in its environment, so a running session keeps the old key, endpoint and model after a switch. The switch toast now asks for a restart, as it already does for Codex and Grok Build. The user manual said the opposite in three places (the quick start table, the switch page and the FAQ), claiming Gemini CLI re-reads .env on every request; all three languages now say a restart is needed. The README and the routing page now list Gemini CLI among the apps that show a restart notice. * fix(live): keep staged bytes and roll forward interrupted writes around external edits - commit_staged keeps the temp file when the rename fails, so a pending operation can still be finished later; one-shot StagedWrite::commit removes it itself. - Recovery rolls forward once any file carries its planned bytes: files still at their old bytes are published, files changed by someone else are left alone, and the pointer/mode still land (RolledForwardExcept). Only an operation that never published anything is discarded. - Entry points take the switch lock and settle the previous operation before reading mode, pointer, auth or stash (controller::lock_settled); each app writer refuses to continue after finishing a leftover operation so the caller retries with fresh state. - Codex proxy contract includes the official login identity, so two official routes for different accounts swap auth.json. - An unreadable Codex login stash is never overwritten by a write that would store a login into it. * fix(editor): keep live-owned exclusive fields and route tables out of provider rows - Exclusive fields shown in the editor only because live already had them are no longer adopted into the row when left untouched; deleting one now removes it from live instead of being a no-op. Modified values still belong to the row. The Codex add dialog compares against a projection of live with an empty card, since its base already carries the preset. - Codex editor diffs skip the route tables selected by both the base and the edited document, so a table renamed away from `custom` no longer leaks into config.toml as a global setting. - The Claude editor takes the proxy switch lock, saves the row before writing live and restores the row when the write fails without leaving a pending operation, matching the other editors. Row rollback is shared through ProviderService::restore_row. * fix(live): settle before acting, mark publishing before the first rename and keep live-owned fields out of new rows - A multi-file operation records that it has started publishing in live-state.json before the first file is replaced; if that cannot be recorded nothing is published. Recovery rolls such an operation forward even when a published file was changed again afterwards, so an unpublished Codex login stash is no longer discarded. - controller::lock_settled fails when the previous operation cannot be finished, and entry points refuse to save or switch instead of writing rows the leftover operation cannot restore. Startup, detach and exit log the failure per app and continue. - Saving, adding and syncing the current provider take the proxy switch lock before reading mode and pointer, so entering proxy mode can no longer be overwritten by direct key fields. A new proxy address is written to every attached app even if one of them fails. - Codex: the row-level model catalog pointer leaves with its provider; a profile selecting the built-in openai provider counts as the official route; exclusive fields the editor shows only because live has them are compared by value, so an untouched field changed by the client stays with the client. The add dialog sends the preset draft it projected, and the backend uses it as the field origin like an existing row. - Claude editor keeps a legacy Bedrock apiKey at the top level where older versions read it; the Gemini editor recognizes a legacy Google card the same way switching does. - The request path passes the provider it already resolved to the router instead of reading live-state.json twice per request. * refactor: share editor, write and lookup helpers across apps and drop dead common-config code - Resolve three-way editor conflicts in one place (ConflictPolicy::resolve) and share JsonChange between the Claude and Gemini editors - Move the duplicated Codex/Grok editor helpers into editor_toml and chain TOML steps through TomlSteps instead of per-app wrapper patches - Start every engine write through AppWrite::begin so the lock and the pre-write recovery check cannot be skipped (the Codex edits-only path missed it) - Look up the direct provider, the in-use provider and the proxy origin through single helpers; read the mode file once per status poll, settle mode and written records in one state update, and query one row for the provider existence check - Drop the common-config merge chain in live.rs that no longer changed anything, the unused config API and snippet helpers on the frontend - Share the live-edit conflict retry flow, the inactive-fields panel and the editor-view app list across dialogs and editors * refactor(health-check): drop connectivity check settings and dead batch/log paths The reachability probe treats any HTTP response as reachable, so its timeout, retry and slow-threshold knobs barely change the outcome. Remove the Advanced settings panel and its get/save commands; the probe now always uses the built-in defaults (8s timeout, 1 retry, 6000ms slow threshold). Also remove dead code: the batch check command had no frontend caller, and stream_check_logs was written on every check but never read. Stop writing it and drop the always-empty model_used/error_category result fields. The table, its migrations and the periodic cleanup stay so existing rows age out. * feat(mcode): let users choose the provider key instead of a random UUID MiniMax Code shows custom_provider:<key>/<model> in its model picker, so the UUID CC Switch used as the key leaked into the UI. New MCode providers now take a user-entered key, prefilled from the preset, like OpenCode. Existing providers keep their UUID and the key is locked when editing, since MCode's default-model references embed it. - Duplicating an MCode provider generates an unused <key>-copy key. - Adding rejects a key already used by a database row or any live custom_provider node, including MCode's own account providers. The check and save run under the MCode switch lock, and the live write re-checks the key while holding config.yaml.lock so it never replaces a node MCode created in the meantime. * Fix/skills new icon (#7727) * fix: replace Wrench icon with Sparkles in skill management buttons * feat: add SkillsIcon component and replace Sparkles icon in skill management * fix(usage): support OpenCode V2 session tables (#7541) OpenCode 2.x moved sessions and messages into session_v2 / session_message and stopped writing the V1 tables, so the usage collector imported nothing on upgraded installs. Detect V2 only when both tables exist, and import assistant and completed compaction rows from session_message. V1 rows migrated into V2 keep their ids, so request_id dedup prevents double counting. Fixes #7529 * fix(opencode): preserve provider fields during import and sync (#7577) Import and live writes now persist the original provider JSON and use OpenCodeProviderConfig only for validation and display-name extraction. The typed round trip dropped fields the type does not model, such as api, env, whitelist and models.<id>.limit.input. Removes the lossy get_typed_providers/set_typed_provider helpers. Refs #7382 * fix(opencode): 支持导入和编辑内置供应商覆盖 (#7749) * feat: Enhance OpenCode provider configuration and import functionality - Added support for built-in provider defaults in OpenCode configuration. - Updated OpenCode provider options to allow omitting npm and model definitions. - Improved import logic for OpenCode providers from live config, ensuring existing credentials are respected. - Introduced tests for credential handling and built-in provider editing. - Enhanced UI components to reflect changes in provider configuration and selection. - Added localization for new built-in defaults feature. * refactor(tests): enhance test environment isolation and settings management * Refactor OpenCode provider handling and validation - Removed TestSettingsGuard to simplify settings management during tests. - Enhanced validation in handleDuplicateProvider to ensure npm and model definitions are provided for non-OMO providers. - Updated OpenCodeFormFields to conditionally allow built-in defaults based on props. - Improved ProviderForm to manage existing OpenCode overrides and prevent incomplete copies from being added. - Added comprehensive tests for OpenCode provider duplication and validation scenarios. - Introduced new error messages for better user feedback on provider requirements. - Created opencode_tests.rs to cover various scenarios for OpenCode provider imports and updates. --------- Co-authored-by: Jason <farion1231@gmail.com> * fix(proxy): preserve terminal web search output order (#6509) When a Responses SSE stream delivers its output only in the terminal response.completed / response.incomplete event, route it through the established Responses JSON conversion so text before a web search stays ahead of the search result. A missing terminal status falls back by event name, matching the incremental path's stop reason mapping. Also fixes terminal-only function_call items that previously produced stop_reason tool_use without a tool_use block. Follow-up to #5681. * fix(sessions): support OpenCode V2 SQLite schema in session manager (#7755) OpenCode 2.x moves sessions into session_v2/session_message and stops writing the V1 tables, so the session manager missed every new session. - Detect the V2 layout when both session_v2 and session_message exist (same rule as the usage collector) - Scan, load and delete V2 sessions, including their child tables - Reuse opencode_config::get_opencode_db_path() for the database path * fix(settings): serialize tool lifecycle writes and isolate Windows scripts (#7725) Run each Windows tool lifecycle script from its own temp directory so concurrent calls can no longer overwrite or delete each other's .bat file. Add a backend ToolLifecycleCoordinator: a global execution lock serializes all install/update writes (pnpm/bun share one global manifest, and concurrent 'add -g' silently drops one side's entry), and a per-tool try-lock rejects duplicate submissions even after the About page remounts. Locks move into the blocking task so a cancelled caller cannot release them while the installer is still running. The About page now locks per tool instead of globally, queues multi-install confirmation plans, ignores double clicks on the confirm dialog, and shows the full error text in a tooltip. * feat(opencode): 支持 JSONC 配置并保留注释与格式 (#7762) * refactor: update OpenCode configuration handling and enhance JSONC support - Refactored OpenCode configuration reading to use `read_opencode_config_from_path` for improved error handling. - Added tests to ensure that OpenCode JSONC provider and MCP workflows preserve comments during import and export. - Updated localization files to reflect changes in OpenCode configuration directory description, now supporting both JSON and JSONC formats. - Introduced a new `jsonc_document.rs` module for format-preserving JSONC/JSON5 editing, including functionality for parsing, applying changes, and validating output. - Enhanced existing tests to cover various edge cases in JSONC handling, ensuring robustness against invalid configurations and preserving comments. * test(opencode): 添加对 Unicode 行注释的处理测试 * fix(opencode): 添加供应商和 MCP 服务配置格式错误的警告日志 * fix(settings): 保留工具升级状态并优化错误提示 (#7764) * fix(tool): improve tool action handling and user feedback for ongoing installations * fix(about): enhance tool upgrade handling to preserve WSL parameters across remounts * fix(sessions): sort OpenCode V2 messages by seq and support mixed V1/V2 databases (#7765) * refactor(proxy): route retry-loop bookkeeping through outcome helpers Success, provider failure, neutral failure and terminal failure each had copies of the breaker/status/in-use/failover-switch updates inside forward_with_retry_inner. Move them into note_attempt, finish_success, record_provider_failure, finish_neutral_failure and record_failed_request so the loop no longer writes routing state directly. Add characterization tests that drive forward_with_retry against a scripted upstream and assert ProxyStatus, current_providers and circuit breaker counts on every exit path. No behavior change. * chore(sponsors): update Kimi Open Platform invite track IDs Switch the Kimi Open Platform invite links to the new campaign trackers: mainland platform.kimi.com links now use track-7cf2b91dcde043eda6ef9a95951a042c and global platform.kimi.ai links use track-674ed6e2af924a5682a87421f7cf753a. In the four READMEs this replaces the previous track IDs in the sponsor banner and API call to action. The Kimi and Kimi Global presets for Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi now carry the same tracked links in websiteUrl, so sign-ups from inside the app are attributed as well. The aff=cc-switch tag is kept everywhere; the console API-key deep links and the Kimi Code subscription links are unchanged. * fix(pricing): add GPT-6.1 Sol pricing (#7800) * refactor(usage): extract shared models.dev fetch module Move the models.dev download, response types, and model ID normalization out of the pricing module into src/lib/modelsDev.ts so other features can reuse the same data. The query key becomes ["models-dev"] because the cache now holds the raw catalog rather than pricing-only data. * feat(providers): fill model metadata when picking a fetched model A provider's /models endpoint only returns model IDs, so a model picked from the fetched list left its context window, output limit, reasoning levels, and modalities empty, while the same model in a preset had them. When a model is picked in the Codex, OpenClaw, Hermes, OpenCode, or MiniMax Code form, look up its metadata field by field: 1. the preset model with the same endpoint (hand-verified values); 2. the models.dev provider whose API endpoint matches; 3. the vendor's own models.dev entry, without its price. Endpoints match only when host and path are equal after dropping a trailing version segment or request path, so plans that share a host (z.ai standard vs Coding Plan, OpenCode Zen vs Go) never borrow each other's values. Models are never matched by name across providers: the same model ID often has very different context windows per deployment. Only empty fields are filled, and reasoning or image input is only ever turned on. models.dev is prefetched once the model list is fetched and shared with the usage pricing import; when it arrives late the row is filled again, located by its stable row key. A single toast asks the user to review what was filled. * feat(pi): fill capabilities of a fetched model from presets and models.dev Apply the same lookup to Pi custom providers: picking a fetched model fills contextWindow, maxTokens, reasoning, and image input when they are still empty. thinkingLevelMap is left untouched. Update the Pi design docs, which previously forbade inferring model capabilities. That rule came from an earlier implementation that voted across providers by model name and produced wrong values; the docs now keep the lesson as constraints (match the provider by endpoint first, fill only empty fields, never generate a thinking map). * feat(pi): fill thinkingLevelMap of a fetched model Without a thinking map, Pi shows off..high for a custom model and hides xhigh and max, even when the upstream supports them. When a fetched model is picked and has no map yet, fill one: - Use the reviewed map of the Pi preset at the same endpoint, but only under the same API and when the compat it relies on does not conflict with what the user wrote. Missing compat keys are added to the model, and for openai-completions the thinking format and reasoning_effort support that Pi detects must match the preset's. - Otherwise, when Pi sends levels verbatim as reasoning_effort (openai-responses, or openai-completions detected as the OpenAI thinking format), derive the map from models.dev effort levels using Pi's own getEffortThinkingLevelMap rule. A model with its own baseUrl is now looked up by that address. Update the Pi design docs accordingly. * refactor(providers): share models.dev query options and committable refs - Export modelsDevQueryOptions so the metadata fill hook and both usage pricing dialogs use one query definition, including the one-hour gcTime the fill hook needs for its prefetch-only cache. - Add useCommittableRef for the "latest value + synchronous commit" pattern repeated in the Codex, Hermes, OpenClaw, and OpenCode forms. - Drop an unused field from the models.dev index. * fix(providers): prefer canonical pointers in the vendor fallback A vendor that also hosts other vendors' models (NVIDIA lists minimaxai/minimax-m2.7 without a canonical id) was counted as a second vendor next to the entry relays point at, so the fallback gave up and models like MiniMax-M2.7, gpt-5.4 and kimi-k2.6 got no metadata through unrecognized relays. Use the entries relays point at first and only fall back to a vendor's own entry when nothing points anywhere. * fix(prompt): only clear the prompt file when the last enabled prompt is disabled Saving any disabled prompt emptied the app's prompt file whenever no prompt was enabled, so adding or importing a prompt wiped a hand-written AGENTS.md or Hermes SOUL.md without a backup. A deeplink import also lost the file before enable_prompt could back it up. Follow the MiniMax Code branch: read the previous row before saving and clear the file only when this prompt was enabled, is now disabled, and no other prompt is enabled. * feat(proxy): attach third-party models to Claude Code in proxy mode Let proxy mode publish extra providers' models to Claude Code's /model picker as ccs-claude-<key>--<model> ids. A request that selects one goes straight to that provider and never touches routing state: no breaker permit or result, no status counters, no in-use update, no failover switch, no model mapping, single-provider timeouts. The list and a permanent key registry live in live-state.json and are committed with the client file in one operation; a failed change reports whether it was dropped or will be rolled forward. Unresolvable prefixed ids are rejected instead of falling back to the route. /v1/models serves the Anthropic-shaped list for gateway model discovery. * feat(proxy): attach third-party models to Codex in proxy mode Merge attached providers' models into the Codex catalog as ccs-<key>/<model> rows after the route's own rows. Each row keeps its provider's tool profile, a fixed comp_hash and its own window; the global window keys move into the rows while models are attached. Attached requests keep their model, never carry the ChatGPT identity headers to a third party, and drop hosted web_search for native Responses gateways that reject it. With the official account as the route, the catalog lists every official model: fetched with the login Codex will use after the operation, cached per workspace, user and Codex version, refreshed in the background, with Codex's bundled list as the fallback. Logins CC Switch does not manage are only read, never refreshed. * feat(proxy): make attached models a mode of its own next to routing Attached models were a per-card toggle inside routing mode. Turn them into an attached mode that sits beside routing: settings show either the routing switch or the attached-mode switch on the main page, for Claude Code and Codex only (Gemini CLI and Grok Build keep routing). Turning one on first sends Claude Code and Codex apps that are in the other mode back to direct. In attached mode the provider list works like the additive apps: Add and Remove put a provider's models into the client's model picker, and "Set as default" picks the provider for unprefixed requests. The default is always on the list and cannot be removed; a new default set from the card, the tray or a project joins it. Official accounts can only be the default. There is no failover: default requests go to the default only, enabling failover is refused under the switch lock, and a late failover result from a request sent before entering attached mode is dropped. The queue is kept for routing mode. Internally attached mode is proxy mode plus PoolState.enabled, written on every proxy entry and committed with the client files. With the bit off nothing is published and the list is kept for next time. Also fix issues found reviewing the first two commits: - strip ChatGPT fingerprint headers only from Codex attached requests - recompute the Codex catalog when an attached member's row is edited - skip a member whose row cannot be projected instead of failing every write, and refuse to attach such rows - keep the gateway discovery switch as an exclusive field so a user's global value survives direct switches - tell the user when the route or config.toml owns the model catalog - read the Codex keychain entry by account key with a three-state result, outside the app lock and off the async runtime - retry the Codex rewrite on the next check when a refreshed official list could not be written * feat(proxy): carry Codex compaction and turn state across attached providers When the official ChatGPT route is the default, Codex treats every model under its "OpenAI" provider table as capable of remote compaction (V2): a normal /responses stream ending in a compaction_trigger, which must return exactly one compaction item or Codex fails the turn. Attached third-party models could not do that, and history mixed across providers carried state that the other side rejects. Compaction bridge: - Chat and Anthropic conversions and native Responses passthrough turn the trigger into a tool-free summary turn and return the summary as a single compaction item wrapped as ccswitch-compaction-v1. - Only a normal stop counts as a finished summary. Truncated or filtered turns report response.incomplete; empty summaries report response.failed. - On replay, CC Switch summaries become a user message for every upstream. Foreign blobs become a readable note for Chat/Anthropic conversions and go to native Responses upstreams untouched, since they may have issued them. Official passthrough: before the ChatGPT backend, drop reasoning items that carry CC Switch envelopes or no encrypted content, and remove item ids that do not match their type. Pure official history stays byte-identical. Opaque state rectifier: when a native Responses upstream rejects encrypted state it cannot verify (invalid_encrypted_content, "could not be verified", "not issued to this caller", store:false lookups, xAI blob errors), strip reasoning items and encrypted parts, plus compaction where warranted, and retry the same provider once. A second failure goes through the normal error classification. Governed by the rectifier master switch. Also strip x-oai-*, x-openai-* and x-responsesapi-* client fingerprint headers from third-party requests, and drop stale entity headers when a streamed SSE body is rewritten (this also fixes the xAI namespace restore stream, which kept the upstream Content-Length). * refactor(proxy): rename attached models to Stack mode The mode that publishes several providers' models in one client model list is now called Stack in both the UI and the code. The feature is unreleased, so persisted names change without aliases: the live-state entry, the pending op, the settings flag and the two commands now use "stack". The proxy's own "attached" state (attached_route, mode.attached, op::ATTACH) keeps its name; it means the proxy contract is in place, not this mode. * feat(proxy): point Claude Code aliases at the default's first Stack model In Stack mode the default provider no longer hides behind the routing aliases. Claude Code's four role aliases (startup default, background tasks, subagent aliases) all point at the first model of the default provider's list, as its Stack id. Haiku drops the [1M] marker like the routing contract does, and CLAUDE_CODE_SUBAGENT_MODEL is not written so subagents follow the model picked in /model. The default provider's whole list is published like any other member; Codex keeps publishing the default's catalog as the route's own rows. Claude providers get an explicit Stack model list (meta.stackModels). Unset means the list follows the model mapping (ANTHROPIC_MODEL first, then the tiers); an empty list is a deliberate choice and publishes nothing. In Stack mode the provider form opens a simplified layout: the model list with a star for the default, plus the fields needed to reach the provider, with a link to the full form. Both layouts share one form state, so the default is synced when the user acts, not derived from the layout at save time: - Claude: when the first model of the list changes, ANTHROPIC_MODEL follows it; an unchanged first model leaves ANTHROPIC_MODEL alone. - Codex: the star follows config.toml's model. Starring, renaming or removing the default row updates model on the spot; save only fills model from the first row when it is empty. A default outside the list is flagged with an action to add it. * feat(proxy): highlight Stack members in violet In Stack mode every added provider now stays highlighted like an added provider in additive apps. The card and the Stack badge use violet, matching the Stack icon in the header, so Stack mode is easy to tell apart from routing and failover, which stay green. * feat(proxy): warn when Codex clients hold an old Stack model list Codex's app-server reads the model catalog once at startup and keeps it, while config.toml is re-read per request. Since 0.159 the codex CLI talks to a managed daemon that only restarts on upgrade, so reopening codex after a Stack change still shows the old model list; the desktop app and editor extensions have the same problem with their own app-servers. In Codex Stack mode the provider list now shows a banner when a running Codex client started before the current catalog was written: - CLI daemon: a button restarts it through Codex's own `codex app-server daemon restart`, after a confirmation that running tasks will be interrupted. Nothing runs when the daemon is not up. - Desktop app / editor extensions: the banner explains how to fully quit and reopen them; CC Switch does not restart them. Staleness is judged against a device-local history of catalog generations (codex-catalog-history.json) rather than file times: an unchanged catalog is not rewritten, and quitting CC Switch removes the catalog pointer and restores it on the next start, so a daemon that survived a CC Switch restart still has the current catalog. Every Codex write and startup records the catalog a newly started Codex would read; processes that started before the oldest recorded generation count as stale. Processes are read with ps on macOS and Linux only; Windows shows no banner for now. * feat(proxy): remind to restart Claude Code when Stack models change Claude Code fetches the model list from the proxy once at startup, so a change to the published Stack models only shows up after a restart. Adding or removing a member already said so in its saved toast, but editing a member's models or deleting a member did not. In Claude Code Stack mode the provider list now compares the published model ids each time the Stack list is refetched and shows "Stack models changed. Restart Claude Code to apply" when the set differs. The refetch right after adding or removing a member is skipped, since that toast already asked for a restart; the skip is used up by that refetch even when nothing changed, so it cannot swallow a later edit. Reordering models or editing fields that are not published (keys, addresses) does not trigger it. Codex is left to its process-based banner. * fix(codex): keep proxy sessions in the same history bucket as direct Codex groups sessions by the model_provider id in config.toml: the CLI's resume picker and `resume --last` only list sessions recorded under the current id, and resuming a thread needs its recorded id to still resolve. With the official provider as the proxy (or Stack) route, CC Switch wrote `model_provider = "cc-switch-official"`, so those sessions went into a third bucket, separate from both the official (`openai`) and third-party (`custom`) history, and the table was deleted as soon as the route moved away. The official route now uses the same id as direct official use: - Unified session history off: no model_provider; a top-level `openai_base_url` points the built-in openai provider at the local proxy, so sessions stay under `openai`. A leftover custom table is kept in its dormant form, as before. The key is updated in place, not moved. - Unified session history on: the custom official mirror table, pointed at the proxy with websockets off. The two shapes produce different contracts, so toggling the setting while routed to the official provider rewrites the client. `cc-switch-official` is no longer written; an existing table is only removed. `openai_base_url` is written only for this route and only with the local proxy address. The official route already sent the client's own login to the proxy, so nothing new is exposed. Direct third-party routes still never use it. Supporting changes: - The proxy answers GET on the Responses paths with 426. The built-in openai provider tries a WebSocket first and falls back to HTTP for the session right away on 426; other errors used up its retries first. - The built-in provider's `version` header is stripped from requests sent to third parties, along with the other Codex client headers. - The new shapes carry no placeholder key, so `live_has_proxy_placeholder` also recognizes a live config that routes the official provider to the configured proxy address. The port must match; with port 0 only the host and path are checked. Other local gateways that point openai_base_url at a different port are not treated as leftover takeover state. * fix(codex): show and save keyless drafts in the provider editor Opening the Codex add dialog projects the custom template onto the live config to show what config.toml will look like. The template has an empty API key and `requires_openai_auth = true`, and the projection ran the same check a switch does, so the dialog failed with "this Codex config has no usable API key" before the user could type one. Third-party presets with the same defaults failed the same way. The editor base then stayed empty, so saving fell back to storing the row only and global edits made in the dialog never reached the live config. The editor now projects such a draft as if a key were present, using an in-memory placeholder that is removed before display and before the row is stored. Only the missing-key error is retried; other errors surface as before. A keyless row can be saved, as the non-editor add path already allowed once a current provider exists. Every write to the live config still projects the real row and refuses it: switching or routing to it, editing the current provider in direct mode, and adding it as the first provider, with the row rolled back. * fix(codex): stop warning when the managed OAuth marker is absent A native ChatGPT login has no managed-account marker, which is the normal state, but the ownership check logged "Failed to read Codex managed OAuth auth marker ... file does not exist" on every switch and token lookup. Check that the marker exists first, as the other marker readers in the same file do. A marker that exists but cannot be read still warns, and the result is unchanged: no marker never establishes ownership. * fix(codex): mirror official entries for GPT models in the model catalog Once `model_catalog_json` is set, Codex only knows the models in that file. Third-party GPT rows were built from the neutral native template (a one-line "You are Codex" prompt, `model_messages` stripped) or, on ProxyChat, from a gpt-5.5 clone regardless of the model name, so GPT models lost their own harness prompt, freeform apply_patch and reasoning levels. Rows now look up Codex's bundled official list with Codex's own matching rule (longest prefix, then one simple namespace segment, case-sensitive) and mirror the matched entry verbatim; row overrides do not apply, and a narrower relay window goes in `model_context_window` in config.toml. Fields owned by the official account or backend are reset: Responses Lite off, speed tiers, upgrade and NUX cleared, always listed, and ProxyChat keeps `supports_image_detail_original` off. Anthropic rows and the DeepSeek vendor catalog are unchanged. Stack members get the same entries under their prefixed ids. The reverse parser only collapses a row to its bare model name when it regenerates identically, so older ProxyChat gpt-5.5 clones keep the user's display name and window. * feat(i18n): call Stack mode 叠加 in Chinese copy Rename the Stack mode to 叠加 (zh-TW 疊加) in the Chinese UI so it reads alongside 路由 and 映射. Tags and tabs say 叠加; sentences say 叠加模式 or 叠加的模型. - zh / zh-TW locale strings, the form defaultValues and the matching test assertions - the Chinese half of user-facing backend errors in mode/controller.rs, mode/stack.rs and codex_direct.rs; the English halves are unchanged English and Japanese keep "Stack". Code identifiers, persisted keys (enableStackMode, the live-state stack mode), commands and log text are unchanged. * fix(proxy): reject Stack model ids outside Stack mode Stack ids are only resolved while the app is in proxy mode with Stack mode on. In routing mode the list and keys are kept but not published, so a client still holding an old Stack id gets an error instead of being forwarded to a list member. The Stack bit is left as is when leaving proxy mode, so direct mode is gated too. * fix(codex): raise OAuth client identity to 0.159.0 for GPT-6.1 Sol (#7806) Requests for gpt-6.1-sol through the Claude-to-Codex OAuth route advertised Codex 0.155.0, below the ChatGPT-account catalog gate for that model, so the backend rejected them with HTTP 400 "model is not supported when using Codex with a ChatGPT account" and model discovery omitted it. The gate sits above the model's advertised minimal_client_version (0.153.0): the catalog lists gpt-6.1-sol only from client_version 0.159.0. Bump the shared client identity to 0.159.0, the exact observed floor rather than the latest release. The regression tests for the generation headers and the model-list…
Summary / 概述
This PR fixes two independent web-search paths that fail behind the CC Switch local proxy:
本 PR 修复 CC Switch 本地代理后的两条独立联网搜索链路:
POST /alpha/searchand its local aliases instead of returning a router-level 404.Codex 独立 Alpha Search —— 注册并安全转发
POST /alpha/search及其本地别名,避免请求在路由层直接返回 404。Claude Code 托管 WebSearch 经 OpenAI Responses / Codex OAuth —— 双向转换 Anthropic 托管 WebSearch 的请求、调用、结果、失败、用量和多轮上下文,覆盖流式与非流式响应。
The two features look similar to users but use different wire protocols. Fixing only one leaves the other client path broken.
两者在用户界面上都表现为“联网搜索”,但底层协议不同;只修其中一条,另一类客户端仍会失败。
No release-version change, package artifact, installer, database migration, or Linux low-memory build script is included.
本 PR 不包含版本号修改、安装包、构建产物、数据库迁移或 Linux 低内存构建脚本。
Root cause / 根因
1. Standalone Alpha Search / 独立 Alpha Search
Current Codex clients can send search commands to a dedicated
POST /alpha/searchprotocol rather than embedding them in/responses. CC Switch did not register this endpoint, so local aliases stopped at Axum with HTTP 404 and never reached provider selection.当前 Codex 客户端可能把搜索命令发送到独立的
POST /alpha/search协议,而不是嵌入/responses。CC Switch 未注册该端点,因此本地别名会在 Axum 路由层返回 HTTP 404,完全不会进入供应商选择与转发流程。For providers configured with
isFullUrl, blindly reusing a configured/responsesURL would also send an Alpha Search payload to the wrong endpoint.对于配置了
isFullUrl的供应商,直接复用/responses完整地址还会把 Alpha Search 请求体误投到错误端点。2. Claude Code hosted WebSearch / Claude Code 托管 WebSearch
Claude Code 2.1.218 currently sends a hosted tool shaped like:
Claude Code 2.1.218 当前会发送类似下面的托管工具:
{ "type": "web_search_20250305", "name": "web_search", "allowed_domains": [], "blocked_domains": [], "max_uses": 8 }The old converter treated every Anthropic tool as a normal function. The request therefore became a function named
web_search, not a hosted Responses{ "type": "web_search" }tool, so the upstream did not execute hosted search. On the return path,web_search_callitems and URL citations were ignored, leaving Claude Code with noserver_tool_use/web_search_tool_resultpair and an empty result envelope (searchCount: 0).旧转换器把所有 Anthropic 工具都当作普通函数,因此请求会变成名为
web_search的 function,而不是 Responses 托管工具{ "type": "web_search" },上游自然不会执行托管搜索。返回路径又忽略了web_search_call与 URL citation,Claude Code 收不到成对的server_tool_use/web_search_tool_result,最终只看到空结果(searchCount: 0)。This is why
WebFetchremained usable while built-inWebSearchfailed: WebFetch is a separate client-side path.这也解释了为什么
WebFetch一直可用,而内置WebSearch失败:WebFetch 是独立的客户端执行路径。Implementation / 实现
Alpha Search passthrough / Alpha Search 透传
Register
/alpha/search,/v1/alpha/search,/v1/v1/alpha/search, and/codex/v1/alpha/search.Normalize all aliases to the selected provider's canonical
/alpha/searchsibling.Preserve query parameters and JSON payload without Responses/Chat/Anthropic conversion.
Reuse Codex provider selection, model mapping, authentication, retry/failover, logging, error mapping, and response handling.
For
isFullUrl, derive the sibling route only when the configured URL unambiguously ends in/responsesor/responses/compact; merge configured and incoming query parameters.Reject opaque full URLs instead of sending a search payload to an unrelated endpoint.
注册
/alpha/search、/v1/alpha/search、/v1/v1/alpha/search和/codex/v1/alpha/search。将所有本地别名规范化为所选供应商同级的标准
/alpha/search。原样保留 query 与 JSON 请求体,不进入 Responses/Chat/Anthropic 协议转换。
复用 Codex 供应商选择、模型映射、认证、重试/故障转移、日志、错误映射和响应处理。
对
isFullUrl,仅当地址明确以/responses或/responses/compact结尾时推导同级路由,并合并配置侧与请求侧 query。对不透明完整 URL 明确报错,避免把搜索请求体发送到无关端点。
Hosted WebSearch request bridge / 托管 WebSearch 请求桥接
Recognize the semantic hosted-tool family by
type, while converting only explicitly verified protocol versions:web_search,web_search_20250305,web_search_20260209, andweb_search_20260318.Preserve the actual inbound tool
namein all return blocks. Unknown date-suffixedweb_search_*declarations are still identified as hosted tools for routing, but fail closed before conversion until their caller/result semantics are verified.Keep a user-defined function merely named
web_searchas a normal function; detection is based on hosted-tooltype, not name alone.Convert the hosted declaration to Responses
{ "type": "web_search" }.Add private
external_web_access: trueonly for the ChatGPT Codex backend; omit it for strict API-key Responses gateways.Preserve
allowed_domainsasfilters.allowed_domainsand object-shapeduser_location.Fail closed on non-empty
blocked_domains, because Responses has no equivalent deny-list.Validate caller semantics before conversion: basic/20250305 may use their verified direct default; 20260209/20260318 require exactly
allowed_callers: ["direct"]; code-execution or mixed callers are rejected.Reject any
response_inclusionvalue and unknown WebSearch protocol version until the bridge can represent their result-hiding/caller semantics exactly.Preserve exact forced-tool semantics on Codex OAuth by filtering the outbound tool list to the dynamically named hosted tool before using string
"required"; API-key Responses uses{ "type": "web_search" }.按
type识别托管工具族,但只转换已经明确验证的协议版本:web_search、web_search_20250305、web_search_20260209和web_search_20260318。在所有返回块中保留请求里的实际工具
name;未知日期后缀的web_search_*仍会按托管工具进入路由判断,但在 caller/结果语义完成验证前会明确拒绝转换。用户自定义的同名
web_search函数仍保持普通 function;识别依据是托管工具type,不会只看名称误判。将托管声明转换为 Responses
{ "type": "web_search" }。私有字段
external_web_access: true仅发送给 ChatGPT Codex 后端;严格 API-key Responses 网关不会收到该字段。把
allowed_domains保留为filters.allowed_domains,并透传对象形式的user_location。对非空
blocked_domains采取安全失败,因为 Responses 没有等价拒绝列表。转换前验证 caller 语义:基础版/20250305 可使用已验证的 direct 默认值;20260209/20260318 必须显式且仅设置
allowed_callers: ["direct"];code-execution 或混合 caller 会被拒绝。在桥接层能够精确表达隐藏结果/caller 语义前,任何
response_inclusion值及未知 WebSearch 协议版本都会明确拒绝。Codex OAuth 强制选择时,先把出站工具过滤为动态命名的目标托管工具,再使用字符串
"required",从而保持“必须调用这个工具”的精确语义;API-key Responses 使用{ "type": "web_search" }。max_usescompatibility /max_uses兼容The two Responses backends have different accepted schemas, verified with safe schema probes:
两类 Responses 后端接受的协议不同,已通过安全的结构探测验证:
API-key Responses routes map
max_usesto the upstream hard limitmax_tool_calls.ChatGPT Codex rejects top-level
max_tool_callsand per-toolmax_uses.For a forced Codex hosted-search request, the bridge adds the cap to model instructions and enforces it locally while consuming SSE. When an extra search begins, it cancels upstream consumption and returns a paired
max_uses_exceededresult.For non-forced Codex requests with
max_uses, the bridge fails closed because an aggregate or per-tool budget cannot be represented safely.The non-streaming Codex path enforces the cap while reading upstream SSE, so it can stop polling immediately instead of buffering the entire upstream response first.
API-key Responses 路由把
max_uses映射为上游硬限制max_tool_calls。ChatGPT Codex 会拒绝顶层
max_tool_calls和工具内max_uses。对强制使用托管搜索的 Codex 请求,桥接层把上限写入模型指令,并在消费 SSE 时本地强制执行;额外搜索一开始就停止继续读取上游,并返回成对的
max_uses_exceeded结果。对带
max_uses的非强制 Codex 请求,由于无法安全表示聚合或逐工具预算,桥接层明确失败。Codex 非流式路径会在读取上游 SSE 时执行限制,因此可以立即停止轮询,而不是先把整个上游响应缓冲完。
Hosted WebSearch response bridge / 托管 WebSearch 响应桥接
Convert every Responses
web_search_callinto one Anthropicserver_tool_useplus exactly one adjacentweb_search_tool_result.Request and preserve per-call
action.sources; merge final-message citations as metadata/fallback without mixing results across calls.Preserve final
url_citationannotations in non-streaming, buffered SSE, and terminal-only recovery paths. Valid ranges become deterministic Markdown links; unsafe, overlapping, code-contained, or incomplete ranges fall back to escapedSources:links without dropping the original text.Reconcile keyed/unkeyed delta aliases, cumulative snapshots, late annotations, repeated deltas, Unicode offsets, and pre-/post-search text without duplication or loss.
Deduplicate URLs and enrich placeholder titles/page ages when better metadata arrives later.
Preserve failed/incomplete calls as
web_search_tool_result_errorinstead of manufacturing an empty success; supported signals map to Anthropic-compatible error codes withunavailableas fallback.Preserve stable upstream or synthesized IDs across added/done/terminal events.
Preserve terminal-only assistant text, missing-delta suffixes, reasoning, and function calls without duplicates.
Queue post-search semantic content until the matching result is ready so server-tool/result blocks stay adjacent and in original order; emit Anthropic
pingevents while waiting so downstream idle timeouts remain active.On EOF with an unpaired hosted call, surface explicit truncation rather than a normal successful turn.
Reconstruct hosted call/result context on later turns for stateless Codex OAuth conversations.
Add
usage.server_tool_use.web_search_requests; preserve a completed client function-call stop reason astool_usewhen a local hosted-search limit terminates the stream.把每个 Responses
web_search_call转换为一个 Anthropicserver_tool_use,并严格配对一个相邻的web_search_tool_result。请求并保留逐调用
action.sources;最终消息 citation 仅作为元数据补充或兜底,不会把多个调用的结果混在一起。在非流式、缓冲 SSE 和仅终态恢复三条路径保留最终
url_citationannotation。有效区间生成确定性的 Markdown 链接;不安全、重叠、位于代码块或不完整的区间改用经过转义的Sources:链接兜底,且不修改或丢弃原始文本。正确合并有键/无键 delta 别名、累积快照、迟到 annotation、重复 delta、Unicode 偏移,以及搜索前后的文本,避免重复或丢失。
URL 去重,并在后续拿到更完整信息时补充占位标题与 page age。
失败或未完成的调用转换为
web_search_tool_result_error,不会伪造成空成功;已知信号映射为 Anthropic 兼容错误码,其余安全回退为unavailable。在 added/done/terminal 事件间保持上游 ID 或合成 ID 稳定。
保留只出现在终态的助手文本、缺失 delta 的后缀、reasoning 和 function call,且不重复输出。
搜索结果就绪前暂存后续语义内容,保证 server-tool/result 相邻且维持原始顺序;等待期间发送 Anthropic
ping,避免下游静默超时。若 EOF 时仍有未配对搜索,明确返回截断错误,而不是正常成功终止。
在后续轮次重建托管调用/结果上下文,支持无状态 Codex OAuth 多轮对话。
补充
usage.server_tool_use.web_search_requests;若本地限流终止时已有完整客户端函数调用,停止原因仍保持tool_use。Compatibility and impact / 兼容性与影响范围
POST /alpha/searchaliasesweb_search*→ Responsesopenai_responsesand Codex OAuth only / 仅openai_responses与 Codex OAuthweb_searchWebFetchKnown boundaries / 已知边界:
A compatible Responses gateway must implement hosted
web_search; otherwise its upstream error is returned normally.Dynamic tool names are preserved, but a new
web_search_*protocol version needs explicit semantic validation before conversion.blocked_domains, non-direct/mixedallowed_callers,response_inclusion, unknown WebSearch versions, and non-forced Codexmax_usesfail closed rather than silently weakening caller restrictions.兼容 Responses 网关本身必须实现托管
web_search,否则会正常返回其上游错误。动态工具名称会被保留,但新的
web_search_*协议版本必须先完成明确的语义验证,才允许转换。blocked_domains、非 direct/混合allowed_callers、response_inclusion、未知 WebSearch 版本,以及非强制 Codexmax_uses都会安全失败,不会静默削弱调用方限制。Review feedback addressed / 已处理的审核意见
Maintainer feedback and all prior Codex review threads are addressed:
管理员反馈与此前 Codex 审核线程均已处理:
pair a result with every emitted search call;
emit terminal-only assistant text;
replay hosted-search context on later turns;
preserve exact forced WebSearch selection;
propagate failed hosted-search results;
fail closed for unsupported caller/result-inclusion semantics;
preserve final URL citations across non-streaming, buffered streaming, and terminal-only paths.
每个搜索调用都配对一个结果;
输出仅存在于终态的助手文本;
后续轮次回放托管搜索上下文;
保持强制 WebSearch 的精确选择语义;
传播失败的托管搜索结果;
对无法表示的 caller/结果隐藏语义安全失败;
在非流式、缓冲流式和仅终态路径完整保留最终 URL citation。
Validation / 自测
Current base:
mainat878c26f3; reviewed head:c07b902e; macOS arm64; Rust toolchain 1.95.当前基线:
main的878c26f3;审核提交:c07b902e;macOS arm64;Rust 1.95。Local Codex review gates / 本地 Codex 审查门禁
After implementing the maintainer feedback, the complete incremental diff was repeatedly reviewed locally with Codex at high reasoning effort. Every reported edge case was first reproduced by a regression test, then fixed and re-reviewed. The final pass over the exact bytes committed as
c07b902ereturned: “No discrete, actionable regressions were identified.”落实管理员反馈后,使用高推理强度的本地 Codex 对完整增量进行了多轮审核。每个发现都先补回归测试复现,再修复并重新审核。对最终提交
c07b902e相同代码的最后一轮结论为:未发现独立、可执行的回归问题。Targeted regression tests / 针对性回归
Coverage includes all Alpha Search aliases and full-URL safety; verified/unknown WebSearch versions and caller constraints; exact forced selection; custom same-name function isolation;
allowed_domains/blocked_domains; API-key and Codexmax_uses; multi-call pairing; final URL-citation rendering and safe fallback; keyed/unkeyed SSE aliases; failure/incomplete results; terminal text and stable IDs; output ordering and timeout pings; truncation; multi-turn replay; non-streaming early cancellation; usage and stop reasons.覆盖全部 Alpha Search 别名与完整 URL 安全性;已验证/未知 WebSearch 版本及 caller 约束;精确强制选择;普通同名函数隔离;
allowed_domains/blocked_domains;API-key 与 Codexmax_uses;多调用配对;最终 URL citation 渲染与安全兜底;有键/无键 SSE 别名;失败/未完成结果;终态文本与稳定 ID;输出排序与保活 ping;截断;多轮回放;非流式提前取消;usage 与停止原因。Full Rust checks / Rust 全量检查
cargo fmt --manifest-path src-tauri/Cargo.toml --all -- --check git diff --check origin/main cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets --all-features -- -D warnings cargo test --manifest-path src-tauri/Cargo.toml -- \ --skip update_current_claude_desktop_provider_syncs_profile_when_proxy_takeover_is_activeLibrary tests: 2,301 passed, 0 failed, 2 repository-marked ignored, 1 explicitly skipped because it conflicts with the live CC Switch/Claude Desktop takeover test environment.
Integration tests: 118 passed, 0 failed.
Formatting and Clippy warnings-as-errors: passed.
库测试:2,301 通过、0 失败、2 项仓库自带 ignore;另明确跳过 1 项会与本机正在运行的 CC Switch / Claude Desktop 接管测试环境冲突的测试。
集成测试:118 通过、0 失败。
格式检查与 Clippy 零警告:通过。
Frontend regression checks / 前端回归检查
Executed via the already-installed project-local binaries, without changing dependency approval policy:
使用项目内已安装工具执行,未修改依赖构建批准策略:
tsc --noEmit: passed;Manual end-to-end WebSearch / 手工端到端 WebSearch
Environment: Claude Code 2.1.218, CC Switch local proxy with this bridge, Codex OAuth, and only built-in
WebSearchallowed.环境:Claude Code 2.1.218、包含本桥接的 CC Switch 本地代理、Codex OAuth,并且只允许内置
WebSearch。Observed result for
Python official documentation python.org:对
Python official documentation python.org的实测结果:one built-in WebSearch execution (
searchCount: 1);three
python.orgsources returned, includinghttps://docs.python.org/3/index.html;final response cited the first source;
no WebFetch fallback was used.
内置 WebSearch 实际执行 1 次(
searchCount: 1);返回 3 个
python.org来源,其中包括https://docs.python.org/3/index.html;最终回答正确引用第一个来源;
未使用 WebFetch 兜底。
Attribution and related issues / 贡献归属与关联问题
Fixes Claude Code built-in WebSearch returns 0 results and can stall via Codex OAuth proxy #5363
Fixes bug(codex): standalone Web Search returns 404 because Alpha Search route is missing #5378
The standalone Alpha Search route and original mock-upstream test are based on fix(proxy): support Codex alpha search passthrough #5379 by @AndrewZhuCC; commit authorship is preserved. This PR adds full-URL safety and combines that route with the distinct Claude hosted-WebSearch bridge requested in Claude Code built-in WebSearch returns 0 results and can stall via Codex OAuth proxy #5363.
[Codex] Xiaomi MiMo 官方支持联网搜索,但直连禁用且本地路由丢弃 web_search #5233 is related to Xiaomi MiMo protocol behavior but is not changed here.
独立 Alpha Search 路由和原始 mock 上游测试基于 @AndrewZhuCC 的 fix(proxy): support Codex alpha search passthrough #5379,并保留提交作者归属;本 PR 增加完整 URL 安全处理,并合入 Claude Code built-in WebSearch returns 0 results and can stall via Codex OAuth proxy #5363 所需的另一条 Claude 托管 WebSearch 桥接。
[Codex] Xiaomi MiMo 官方支持联网搜索,但直连禁用且本地路由丢弃 web_search #5233 与小米 MiMo 协议行为相关,但不属于本 PR 修改范围。
Checklist / 检查清单
mainintegrated and re-reviewed / 已同步最新main并重新审核