Managed-OAuth providers (github_copilot / codex_oauth / xai_oauth) need
proxy takeover to inject credentials, but the "needs routing" badge and
the switch-time warning only keyed off apiFormat — missing OAuth providers
whose upstream format is native (e.g. the new Codex xAI Grok OAuth preset
on openai_responses).
- Add isOAuthProviderType / OAUTH_PROVIDER_TYPES as the SSOT for OAuth types
- Extract providerNeedsRouting() as the authoritative predicate: managed
OAuth always needs routing (the backend rejects these accounts' direct
connection) regardless of apiFormat or Claude Desktop mode
- ProviderCard badges (claude / codex / claude-desktop) and the switch
warning consume the shared predicate instead of raw apiFormat
- Gate the warning on per-app routing readiness: claude-desktop uses
isProxyRunning (backend takeover status has no such field), other apps
use isProxyTakeover — fixes false warnings on Desktop and missing
warnings when the proxy runs but the app isn't taken over
- Force proxy mode for all managed-OAuth providers in the Claude Desktop
form (lock the mode toggle), not only xai_oauth
- Add unit tests for providerNeedsRouting, the switch routing gate, and
Desktop OAuth preset enforcement
- i18n(zh/en/ja/zh-TW): add notifications.proxyReasonManagedOAuth
Add a managed "xAI (Grok) OAuth" Codex provider that routes through the
local proxy to api.x.ai via the shared Grok CLI OAuth identity, plus the
native-Responses compatibility layer that makes Codex 0.142+ traffic work
against xAI's strict upstream serde parser.
Provider:
- codex.rs: recognize the xai_oauth placeholder in extract_auth, hard-pin
the base URL to api.x.ai and the tool profile to native Responses
- forwarder.rs: treat xAI OAuth auth failures as non-retryable
- presets + ProviderForm/CodexFormFields: managed OAuth preset that hides
the api key/endpoint fields and derives the provider type across apps
Native Responses compatibility (gated on is_xai_oauth, so no other
provider is affected):
- transform_codex_responses_namespace: flatten Codex's private
namespace/plugin tool declarations into top-level function tools on the
request; restore the flat function_call names back to {name, namespace}
on the response (streaming and non-streaming) so the client matches its
own namespaced tool registry
- transform_codex_responses_xai_sanitize: strip the OpenAI-backend-private
fields xAI rejects (external_web_access, prompt_cache_retention,
safety_identifier, the additional_tools carrier, tool_search, ...) with
deterministic removals that keep the prompt-cache prefix stable
- wire both into the native passthrough after the request transform;
response restore runs in a dedicated handler so the generic passthrough
hot path is untouched
Ports the proven approach of sub2api's Grok Responses gateway. Verified
with a 4-round codex -> xAI OAuth workload: all tasks green, zero upstream
errors.
API-key preset pointing at https://api.x.ai/v1 with wire_api =
"responses": xAI serves the Responses API natively, so no route
takeover conversion is needed. Ships a grok-4.5 catalog entry
(500K context, parallel tools, text+image) so the model shows up
under Codex's strict catalog parsing. This is deliberately not a
managed-account preset: providerType stays absent, pinned by a
regression test alongside the Claude-side xAI OAuth presets.
Add XaiOAuthSection (device-code login, account list, default selection)
backed by the generalized useManagedAuth hook, wire it into the Auth
Center and both Claude provider forms, and expose model fetching for
signed-in accounts.
- Accounts requiring re-auth stay visible in the account selector as
disabled items with an expired badge instead of silently vanishing.
- Auth status refetches periodically so a revoked refresh token surfaces
without reloading the panel.
- All strings ship in zh/en/ja/zh-TW; a locale coverage test pins every
required key in all four locales.
Register the xai_oauth provider type constant and add xAI (Grok) presets
targeting grok-4.5. The Desktop preset routes branded claude-* model IDs
to grok-4.5 upstream so it passes Desktop's third-party model validation.
Add kimi-k3 (1M context, 131K max output) to the Kimi provider preset
model lists for Codex, Hermes, OpenClaw, and OpenCode, appended after
kimi-k2.7-code so the catalog-first-row default backfill is unchanged.
The Kimi For Coding plan presets keep the kimi-for-coding endpoint
alias and are intentionally untouched.
Frontend half of the logging overhaul.
Capture:
- Add a global error boundary plus window error/unhandledrejection
handlers that persist renderer errors via tauri-plugin-log (new
@tauri-apps/plugin-log dependency + log:default capability), so a
white-screen crash leaves a trace.
Redaction (two layers over every logged value):
- Structured serializer: redact by sensitive property name (arrays and
nested values included) and by opaque value shape.
- Text layer, the universal final pass: redact URL credentials/query,
auth schemes, known secret shapes, and named-key array/object values.
The container rule closes prefix+JSON, double-encoded, and POJO-error
bypasses regardless of which entrypoint the structured data came from.
- Render Errors as redacted message + native stack across V8 and WebKit
(macOS/Linux) without depending on a single stack format.
- Bound all inputs and drop oversized JSON to keep the UI responsive.
i18n:
- zh / en / ja / zh-TW for the new log settings and error text.
Drop the Max Context Tokens / Auto Compact Window template inputs from
the Codex and Kimi For Coding presets and hardcode the values in the
preset env (372000 / 262144). The rare user who wants different numbers
can edit them directly in the JSON editor.
Both keys stay on purpose: the compact window resolves as
min(model window, value), so matching the window is behavior-neutral
today, but the explicit env pins compaction locally against
remote-config experiments dialing it down.
Claude Code caps unknown model ids at 200K, so the preset's standalone
CLAUDE_CODE_AUTO_COMPACT_WINDOW=262144 was clamped by min(window, value)
and never took effect. Pair it with CLAUDE_CODE_MAX_CONTEXT_TOKENS and
route the endpoint's kimi-for-coding alias on every tier, since both
context envs are ignored for claude-* prefixed model ids.
Saved providers get the same context defaults injected into effective
live settings at switch time, with a mirror-inverse strip on backfill so
injected values never harden into provider config. Explicit user values
always win over the injected defaults.
- Update Codex OAuth presets to the gpt-5.6 family (haiku -> gpt-5.6-luna)
and bump the custom Codex template default model
- Inject CLAUDE_CODE_MAX_CONTEXT_TOKENS / CLAUDE_CODE_AUTO_COMPACT_WINDOW
(372000, the ChatGPT Codex catalog window with a ~353K effective budget,
openai/codex#31860) into effective live settings so Claude Code stops
assuming a 200K window and compacts before the upstream rejects the prompt
- Gate the injected defaults on every configured model being gpt-5.6*:
gpt-5.5's upstream catalog oscillates between 272K and 372K and must not
inherit them; explicit user values always win
- Strip the injected values on switch-away backfill (mirror-inverse of the
injection conditions) so program defaults never harden into per-provider
explicit values, and keep both keys out of the shared common-config snippet
Let users switch between the built-in OpenAI provider and third-party Codex providers directly from the provider panel while takeover mode remains active.
Centralize the frontend capability predicate so only the fixed codex-official seed receives native-login routing support, and keep copied UUID-based official entries clearly marked as unsupported.
Add an idempotent backend command that recreates the deleted official seed, wire it into the add-provider flow, refresh localized guidance, and add mutation and provider-action regression coverage.
Codex always sends prompt_cache_key in its Responses requests, but the
Responses -> Chat Completions conversion dropped it, breaking session
cache affinity on upstreams that route by key (e.g. Kimi Coding).
- Re-inject prompt_cache_key after conversion in the forwarder: an
explicit client key wins, otherwise a client-provided session ID;
generated per-request UUIDs are never sent upstream.
- Provider-aware gating: "auto" enables only known-compatible upstreams
(api.openai.com, api.kimi.com/coding) because strict gateways reject
unknown fields with HTTP 400 (e.g. Fireworks); an advanced
Auto/Enabled/Disabled override is available on the Codex form in all
four locales.
- Kimi For Coding preset opts in explicitly.
* feat(codex): support native Anthropic Messages protocol as upstream
Allow gateways that only expose the native Anthropic Messages protocol
(/v1/messages) to be used by Codex: the local proxy performs bidirectional
request/response/streaming conversion between Responses and Anthropic.
Backend:
- Add two conversion modules: transform_codex_anthropic / streaming_codex_anthropic
- codex.rs: add routing detection and auth: ANTHROPIC_AUTH_TOKEN→Bearer (default),
ANTHROPIC_API_KEY→x-api-key, mutually exclusive
- handlers.rs: add handle_codex_anthropic_to_responses_transform
- forwarder.rs: support optional Claude Code client fingerprint impersonation
(User-Agent / anthropic-beta / x-app / system prompt first-line injection)
and /responses→/v1/messages rewriting
- codex_config: the anthropic format reuses the NativeResponses profile to strip
custom tools
- ProviderMeta: add impersonateClaudeCode
Frontend:
- CodexApiFormat: add "anthropic"; the form adds auth field selection and an
impersonation toggle
- Add en/ja/zh/zh-TW copy
Robustness:
- Downgrade when tool history / forced tool_choice conflicts with extended
thinking, avoiding upstream 400s
- Emit cache_creation_input_tokens in usage and use saturating_add to guard
against overflow
- Append a unique suffix to non-streaming/streaming output-item ids to avoid
multi-segment text/thinking overwriting each other
* fix(codex): harden Anthropic bridge against empty text blocks and truncated streams
- Drop empty/whitespace-only text content blocks when rebuilding Anthropic
messages from Responses history; Anthropic 400s on empty text blocks (e.g. an
empty assistant text emitted alongside a tool_use), which broke follow-up and
tool-result requests. Also drop messages left without content.
- Do not report a truncated Anthropic stream as completed: when the SSE
connection ends before message_stop with no stop_reason, emit an incomplete
response if partial output exists, or a failed (stream_truncated) response
otherwise, mirroring the chat converter's EOF handling.
* fix(codex): resolve Anthropic-bridge review findings
Blocking:
1. Defer stripping the [1m] long-context marker until after catalog matching and model write-back, re-stripping it on the final Codex→Anthropic body and setting a flag to emit the context-1m-2025-08-07 beta header, so the marker is no longer lost or overridden by the default model.
2. Gate Anthropic thinking on the trailing turn only (via trailing_turn_allows_thinking) instead of scanning full history, so a Codex session that resends history each round no longer permanently loses thinking after the first tool call.
3. Inject 5m ephemeral cache_control on the Codex→Anthropic body by reusing cache_injector (handling the system string→array conversion), so system/tools/history are cached instead of re-sent at full price every round.
4. Add a shared base_url_is_full_endpoint helper (normalizing whitespace/query/fragment/trailing slash) used by both the Anthropic and Chat paths, so a base URL already ending in /v1/messages is treated as a full endpoint instead of double-appending to /v1/messages/v1/messages.
5. Align the catalog tool-profile predicate with the routing predicate so resolve_codex_catalog_tool_profile returns the Anthropic profile whenever the request converts to Anthropic, preventing freeform tools like apply_patch from being silently filtered by a ProxyChat catalog.
6. Explicitly disable native web_search for the Anthropic profile (including the no-catalog branch) via set_codex_native_web_search_field, so Codex no longer treats it as available while the transform silently drops it.
7. Only forward tool_choice when tools survive filtering, dropping it otherwise, to avoid a non-retryable 400 from upstream when tool_choice is sent with no tools.
8. Lower the fallback default to max_tokens=8192 (only when max_output_tokens is omitted) and clamp the thinking budget to max_tokens/2, disabling thinking below the 1024 floor, to avoid hard 400s on low-output-ceiling models/gateways.
Minor:
9. Centralize the Codex/OpenAI fingerprint-header denylist in is_codex_client_fingerprint_header so impersonating Claude Code uniformly drops originator/session_id/conversation_id/chatgpt-account-id/x-client-request-id/openai-* and the x-stainless-*/x-codex-* prefixes.
10. Retain content_block_start.input as start_input and fall back to it at block close when no input_json_delta arrived, so a gateway that carries the full tool input on the start event no longer yields empty tool arguments.
11. Extract a shared codex_responses_sse module as the single Responses SSE envelope builder that both the chat and anthropic streaming emitters delegate to, with byte-for-byte-unchanged wire output, so future event-format fixes touch one place instead of two.
* fix(codex): add per-provider max_output_tokens override for Codex→Anthropic path
Codex does not forward model_max_output_tokens in the request body,
causing the proxy to fall back to a conservative 8192 default. This
truncates long or thinking-heavy responses (stop_reason=max_tokens).
- Add maxOutputTokens field to ProviderMeta (Rust + TypeScript)
- Inject the value into the Anthropic request body before transform,
taking precedence over request-supplied and default values
- Add numeric input in Codex form fields (Anthropic format only)
- Add i18n entries for label, placeholder, and hint (en/zh/zh-TW/ja)
- Include roundtrip and omission unit tests for the new field
* fix(codex): harden Anthropic protocol bridge
* fix(codex): address Anthropic bridge review
* fix(codex): preserve flattened Anthropic inputs
* fix(codex): harden Anthropic recovery paths
* fix(ci): satisfy Rust clippy
---------
Co-authored-by: Jason <farion1231@gmail.com>
updateTomlCommonConfigSnippet re-serialized the whole document through
smol-toml (parse -> deepMerge -> stringify): comments dropped, keys
reordered, and empty parent table headers synthesized -- the
long-standing "config.toml keeps getting reordered" symptom (audit C5,
introduced in 083e48bf).
Replace it with a backend command backed by the same
merge_toml_table_like / remove_toml_table_like used when writing live
configs, so the form preview and the live write share one merge
semantic and user formatting survives edit-time merges. The frontend
sync helper is deleted outright to keep the pattern from coming back.
Making the form operations async exposes them to interleaving, so a
result is discarded unless it is still current when it lands:
- a per-hook sequence number (last operation wins) covers rapid
toggle/save races where an earlier merge resolves after a later
removal and would flip the switch back;
- a config-baseline check covers the user hand-editing the TOML while
a merge is in flight -- the stale result must not clobber their edit.
The checkbox state self-heals via the existing inference effect.
Regression tests pin both by resolving a suspended merge after a newer
operation / an external edit, plus backend tests locking comment and
key-order preservation, scalar override, and value-matched removal.
Usage/quota queries frequently showed spurious "query failed" states that
manual refresh could not clear (#3820). Root cause: all transport-level
failures were folded into Ok(success:false), so react-query's retry never
fired and the failure body poisoned the cache as regular data.
Backend:
- balance/coding_plan/subscription services now return Err for send
failures and body-read failures (read body via bytes() before
serde_json::from_slice; reqwest's json() wraps read errors as Decode,
making error-kind checks on it dead code). Auth/4xx/parse errors stay
Ok(success:false) and surface immediately.
- Script path maps transient AppError keys (request_failed /
read_response_failed) to Err; Volcengine adds a Transient call variant.
- Command layer skips snapshot persistence, usage-cache-updated emit and
tray refresh on Err so the cache bridge cannot overwrite retained data.
- Expired-credential retry propagates transient errors instead of
rewriting them as "OAuth token has expired".
Frontend:
- resolveDisplayUsage generalized to subscription quotas; 5th param is now
an options object with a `rejected` flag: stale success data retained by
react-query across rejections is re-anchored to dataUpdatedAt and expires
through the same 10-minute keep-last-good window instead of being shown
indefinitely (a total outage used to mask longer than a single 5xx).
- useSubscriptionQuota / useCodexOauthQuota gain keep-last-good with
per-scope snapshot reset; rejected queries with no displayable value
synthesize a failure placeholder carrying the real error message so
footers keep rendering a retry entry point.
- HTTP 429 is classified transient (retry-later) alongside 5xx.
- UsageScriptModal surfaces string rejections via extractErrorMessage.
Tests: 6 backend behavior tests drive real HTTP against a local listener
to pin the Err/Ok channel semantics; frontend suite extends
keepLastGoodUsage coverage (405 passing).
* feat: add Claude subagent takeover config
* feat: add Claude subagent model field
* i18n: add Claude subagent model labels
* fix(proxy): preserve configured subagent model mapping
* fix(providers): exclude subagent model from Claude common config
* style: format rust code
* Update Longcat presets to LongCat-2.0
* fix(proxy): classify LongCat-2.0 as text-only for media sanitizer
The Longcat presets now use LongCat-2.0, but the known_text_only_model
allowlist still only matched the retired longcat-flash-chat tail. Without
this, images pasted into a text-only LongCat-2.0 session are forwarded
upstream instead of being replaced with the unsupported-image marker,
causing a hard rejection. Add longcat-2.0 (keeping the retired name for
saved configs) and a regression test.
---------
Co-authored-by: chengzifeng <chengzifeng@meituan.com>
Co-authored-by: Jason <farion1231@gmail.com>
The default Sonnet tier was bumped to claude-sonnet-5, but the TheRouter preset tests and ClaudeDesktopProviderForm tests still asserted claude-sonnet-4.6/4-6. Update the expected values to match the presets; input fixtures are left as-is to keep exercising legacy-route migration.
The OpenClaw DouBaoSeed preset hard-coded contextWindow 128000 while the
Codex preset/catalog uses 262144 for the same model, giving OpenClaw users
a too-small window that could compress or truncate long context early.
Align to 262144 and add a cross-preset consistency test asserting the
OpenClaw and Codex Doubao context windows stay equal, so neither side can
drift again.
Volcengine Ark rejects the bare model name doubao-seed-2-1-pro with a 404
("model does not exist or you do not have access to it") even after the
model is activated; the API requires the full dated id
doubao-seed-2-1-pro-260628. Update the Doubao preset model id across all
apps (config default, generated catalog, and OpenClaw namespaced refs).
Pricing lookup only stripped 8-digit (YYYYMMDD) and ISO date suffixes, not
the 6-digit YYMMDD format Volcengine uses (-260628, -250615), so real
Doubao usage never matched the bare-name pricing seed and showed $0 cost.
Extend strip_model_date_suffix to also strip 6-digit YYMMDD (with
month/day validation to avoid eating non-date version suffixes), keeping
the bare-name pricing seed as the canonical identity. This also fixes
pricing for every other Volcengine Doubao model. Add unit and end-to-end
regression tests.
Align the Codex provider form with Claude Code. Model mapping (the
catalog) is independent from route takeover: native Responses providers
(MiMo, Doubao, MiniMax) need it for direct connect with no proxy, while
Chat providers use the proxy regardless of any per-provider flag.
- Remove the "Needs Local Routing" toggle. It had no backend field and
only gated catalog/reasoning persistence, which is equivalent to
"is the mapping filled".
- Always show model mapping for non-official providers; persist when the
list is non-empty (backend already keys off modelCatalog.models).
- Gate reasoning visibility/persistence on Chat format, not the toggle.
- Mark the Chat upstream-format option "routing required" and refresh the
advanced-section hint (zh/en/ja/zh-TW); drop dead localRouting* keys.
- Move the model-mapping block above the custom User-Agent block.
fix(codex): preserve native-profile hidden catalog fields on edit
useCodexConfigState dropped supportsParallelToolCalls / inputModalities /
baseInstructions when loading a saved provider, so editing and saving a
native Responses provider lost parallel tools, image input and the
official base instructions from the generated catalog. Preserve them on
load (camelCase + snake_case) and compare them in the row sync. Add a
regression test.
- Add `qwen3-coder` to the web_search reject model-prefix blacklist so the
native qwen3-coder-plus direct-connect preset suppresses the built-in tool
(百炼 rejects it for the coder series), while general Qwen models sharing the
DashScope host keep web_search enabled. Matched on the model axis, not host.
- Correct LongCat-2.0-Preview context window from 128K to its real 1M
(1048576), aligning with the MiMo/Qwen 2^20 convention.
- Tighten native Responses preset tests to assert exact model→contextWindow
catalogs instead of only checking catalog presence.
Codex providers can now run in two modes per provider:
- Proxy-Chat (route takeover): apiFormat=openai_chat, existing Responses<->Chat conversion. Unchanged.
- Native-Responses (direct): apiFormat=openai_responses, no proxy. cc-switch generates ~/.codex/cc-switch-model-catalog.json so Codex shows the custom models and tools work without the freeform apply_patch (type=custom) tool that native gateways like MiMo reject; editing falls back to shell_command.
Catalog generation is keyed on apiFormat (CodexCatalogToolProfile), decoupled from the takeover toggle, so native providers persist a catalog without enabling local route mapping.
Codex's catalog parser requires base_instructions on every entry; the native template carries a neutral default and per-vendor official text overrides it (MiMo, MiniMax). Synthesized catalogs for Qwen/Doubao/LongCat use the neutral default.
Existing native providers must be re-saved once to regenerate a valid catalog (no DB migration).
Swap the SubRouter apiKeyUrl across all 7 client presets from the
neutral console/token page to the affiliate registration link
(?aff=l3ri), matching the project's referral-link convention.
Sync the preset test constant accordingly.
Commit 273cc48c migrated 7 CN providers from openai_chat to native
openai_responses and refreshed model ids on the remaining chat-only
providers, but the codexChatProviderPresets snapshot was not updated,
breaking the unit tests on main.
Drop the migrated providers (DouBaoSeed, Bailian, Longcat, MiniMax,
MiniMax en, Xiaomi MiMo x2) from the chat list, sync GLM/StepFun/Ling
model ids, and add a test locking the migrated presets to
openai_responses with no modelCatalog (so the local route-mapping
toggle stays unchecked).
The Codex provider form tied Chat-format conversion and route takeover (model
mapping) to one toggle, so a provider serving a native Responses API could not
use model mapping without forcing Chat Completions conversion.
- Promote the upstream format (Chat Completions / Responses) to an independent,
always-visible selector that triggers no sub-menus on its own.
- The local-routing toggle now solely gates the advanced sub-sections: model
mapping catalog, plus reasoning capability when the format is Chat.
- Persist modelCatalog / codexChatReasoning based on the takeover toggle and
derive its initial state from saved catalog presence (no new persisted field).
- Refresh codexConfig i18n (zh/en/ja/zh-TW): add upstreamFormat* keys and reword
the routing/advanced hints to reflect the decoupling.
- Fix codexChatProviderPresets test expectation for the Doubao Seed 2.1 Pro rename.
Set the default Claude Code auto-compact window to 262144 for the
Kimi For Coding provider preset, matching the official Kimi docs:
https://www.kimi.com/code/docs/en/third-party-tools/other-coding-agents.html
Use templateValues so users can customize the value (e.g. for future
models or performance tuning) while keeping 262144 as the default.
The official-first/prime-partner sort changed Original mode order, but the
selector tests still asserted the old raw array order. Update the three
stale assertions and add a dedicated case covering the prime-partner group
and the exclusivity rule (an official preset also flagged primePartner
stays only in the official group).
* feat(usage): support importing model pricing from models.dev
Add an "Import from models.dev" button to the Add Pricing panel that
fetches https://models.dev/api.json, lists priced models sorted by
release date (newest 50 by default, full-text search across ~4800),
and bulk-imports the selected entries through the same
update_model_pricing command as manual entry.
- Normalize imported model IDs to match the backend's
clean_model_id_for_pricing rules (strip vendor prefix, lowercase,
truncate ':' suffix, map '@' to '-', drop the [1m] marker) so the
stored rows actually match cost-attribution lookups
- Dedupe selections that collapse to the same model_id and report
skipped duplicates in the success toast
- Invalidate usage queries on settled (not just success) so partial
import failures still refresh the pricing list
- Keep ESC inside the picker's search input from closing the dialog
and discarding the selection
- Add i18n keys for zh/en/zh-TW/ja and unit tests for the
normalization, price formatting and flattening logic
Fixes#4017
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(usage): match scoped cost backfill against raw model aliases
The scoped backfill selected zero-cost rows via exact SQL string match,
but log columns store raw model strings (route prefixes, :free variants,
date suffixes), so alias rows were skipped until the next full backfill
on startup. Filter rows in Rust with the same model_pricing_candidates
normalization used by the pricing lookup; pricing decision logic is
untouched. Pre-existing gap from schema v11, surfaced by bulk import.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(usage): restrict models.dev pricing import to a single model
Each update_model_pricing call triggers a backfill pass that loads
every zero-cost usage row before filtering by model, so bulk-importing
N entries scaled as selectedModels x allZeroCostLogs full scans.
Re-importing pricing is rare, so drop the batch path instead of
optimizing it: the picker is now single-select, one import runs exactly
one update_model_pricing call and one backfill pass. This also removes
the normalized-ID dedup logic and the useImportModelPricing hook in
favor of the existing useUpdateModelPricing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(provider-preset-selector): after searching presets, it is impossible to select search results.
feat(provider-preset-selector): add keyboard shortcut for search input and improve focus handling.
* fix(provider-list): prevent keydown event from triggering when default is prevented
* fix(provider-preset): keep preset clickable after search and restore keyboard UX
- ProviderList: scope the typing guard to the Ctrl/Cmd+F branch so Escape
still closes the search panel (the top-level early return swallowed it);
reuse isTextEditableTarget instead of re-implementing the check.
- ProviderPresetSelector: drop the rAF select() that raced with typing and
ate the first character (gateway -> ateway), and restore the input
autoFocus for the open-by-click path; refocus via rAF when Ctrl/Cmd+F is
pressed while the box is already open so focus returns to the input.
- Add a regression test for the re-focus-on-shortcut behavior.
---------
Co-authored-by: Jason <farion1231@gmail.com>
- useUsageQuery: retry once + keep-last-good — show the last successful
result for up to 10min when a query fails transiently (network/timeout/
HTTP 5xx), so a single blip no longer flips the card to red. Deterministic
failures (auth, empty key, unknown provider, 4xx) surface immediately and
clear the snapshot so a stale quota can't resurface after credentials change.
- bump native balance/coding-plan/subscription request timeouts 10s -> 15s
for slow cross-border endpoints.
- coding_plan: return explicit errors ("API key is empty" / "Unknown coding
plan provider") instead of a blank failure, mirroring balance.
- add unit tests for keep-last-good and transient/deterministic classification.
* 调整预设供应商按钮外观与搜索框位置
1. 调整预设供应商按钮外观,显示默认图标,大小统一;
2. 调整预设供应商搜索框位置。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test(provider): 新增预设按钮外观与 inline 搜索的单元测试
覆盖:
1. 所有预设按钮固定 200px 宽度,视觉对齐一致
2. preset.icon 存在时按钮内渲染 ProviderIcon
3. preset 无 icon 且无 theme.icon 时渲染占位元素保持文字对齐
4. 点击放大镜 inline 切换搜索输入框可见性,ESC 收起并清空
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(provider-preset): responsive grid layout and search polish
- Replace fixed-width preset buttons with a responsive CSS grid (auto-fill, 150px min column)
- Add a leading placeholder to the custom button so its label aligns with iconed presets
- Close the inline search box on outside click, restoring the old Popover behavior
- Span the empty-state hint across the full grid row
- Update component tests for the new layout and behaviors
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
Update the Codex chat preset test's Kimi expectation (kimi-k2.6 -> kimi-k2.7-code) after the Kimi K2.7 upgrade, update the Claude Desktop form test for the four-tier (Sonnet/Opus/Fable/Haiku) routes, and reformat UsageDateRangePicker imports (prettier).
The preset search text also included websiteUrl and the shared category label, producing imprecise matches: a single category term matched the whole group, and URL fragments like "com"/"api" matched nearly everything. Restrict the search text to the display name and raw name; category labels are still used for rendering.
- Wire claude-fable-5 as a fourth tier on both proxy paths, with a
fable -> opus -> default fallback mirroring the official downgrade.
- Whitelist the fable- prefix for the Desktop 1.12603.1+ validator.
- Clarify fallbackModelHint (zh/en/ja/zh-TW): a blank tier on
third-party endpoints forwards the literal model name and 404s.
Refs #3980, #4026, #4049.
Polish the provider-level User-Agent override UI on the Claude and Codex forms.
- Add a shared CustomUserAgentField (label + input + preset dropdown + live
validation) so both forms stay in sync.
- Provide curated UA presets (Claude Code / Kilo Code families that pass
coding-plan UA whitelists per #3671); the first is Claude Code's real
`claude-cli/x (external, cli)` format. Whitelists gate on the name prefix,
not the version, so static values stay valid across upgrades.
- Expose presets via a dropdown to the right of the input (z-[200] so it
renders above the dialog layers) instead of inline chips.
- Move the field into the existing advanced/reasoning collapsibles.
- userAgent.ts mirrors the backend byte rule (reject only control chars;
non-ASCII is allowed) for a non-blocking inline hint.
- i18n for all four locales (zh/en/ja/zh-TW).
Move the CCSub preset to sit right after DouBaoSeed, at the end of the
partner block and before the first non-partner provider, so its position
is consistent across all six apps:
- Codex / OpenCode: moved up from the 2nd slot (between Shengsuanyun and
the next partner) to the block tail
- OpenClaw / Hermes: moved up from the aggregator section to the block tail
- Claude / Claude Desktop: already at the block tail
Also add the missing CHANGELOG entry for the CCSub preset, and drop the
provider preset order test that enforced a now-unneeded ordering invariant.