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.
Add a show/hide toggle for the header project profile switcher under
the Homepage Display section in settings. Defaults to visible so
existing users keep the current behavior.
Expose the top-level `model` key of config.toml as an editable field in
the Codex provider form, so users can switch to newly released models
(e.g. gpt-5.6) without waiting for a preset update — preset updates only
affect newly added providers, existing ones keep their saved TOML.
The field syncs bidirectionally with the TOML editor (mirroring the
base-URL pattern), suggests models from the mapping catalog plus the
provider's /models endpoint, and offers a one-click "add to mapping"
action when the value is outside a non-empty catalog. Hidden for
official providers.
Details:
- Save-time catalog sync now only backfills the first mapping row into
the top-level model when the field was left empty, so the explicit
field wins over the implicit row-0 sync
- Escape model names (and base_url) with TOML basic-string escaping and
strip control characters from field input: /models ids are remote
data and unescaped interpolation could inject config.toml lines
(e.g. a forged [mcp_servers.*] command)
- The strict model-line matcher now recognizes escaped output, empty
strings and single-quoted literals, keeping extract/set round-trips
stable; deliberately no key-only loose matching (it would misfire on
assignment-looking text inside multiline strings)
- Invalidate the fetched model list whenever the request identity
changes (base URL, full-URL toggle, API key, custom UA) and discard
in-flight responses via a sequence guard, so the dropdown never shows
a previous provider's models
- i18n for zh/en/ja/zh-TW
* 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>
Since switching projects automatically saves the current configuration
back to the project being left, the per-project "update from current"
button is redundant and even harmful: it allowed overwriting another
project's snapshot with the current state, breaking the invariant that
a project holds the state you last left it in.
- Remove the resnapshot button and confirm dialog from the manage
dialog; drop the now-unused scope prop and PROFILE_SCOPE_LABELS
- Add a footer Close button to the manage dialog (overlay click is
disabled app-wide, so it previously could only be closed via Esc)
- Update manageDescription in all four locales to describe the
autosave behavior; remove the two dead i18n keys
- Fix a shadowed `warnings` vec in ProfileService::apply that silently
dropped autosave-failure warnings before they reached the frontend
- Mention auto-capture on next switch in the "no snapshot for this
scope yet" warning and doc comments
The backend update command keeps its resnapshot parameter; it is now
only exercised by the pre-switch autosave path.
Projects are now global shared entities; Claude and Codex groups switch
independently via scoped current pointers and scoped payload slots.
- Remove scope column from profiles; keep current_profile_id_<scope>
- Use Option<Vec<String>> for mcp/skills to distinguish 'never captured'
from 'captured empty', preventing cross-side accidental disable
- Update/apply operations scoped to the active group via merge_scope_from
- Tray menu nests same shared list under Claude Code/Codex groups
- Add i18n for per-scope tooltips and 'not saved for this side' hint
Refs: profile P1 shared-entity redesign
Claude Desktop's only dimension managed by cc-switch is its provider
(MCP/Skills are hardcoded unsupported and prompts have no live file),
so snapshots capture just the current desktop provider while the empty
MCP/Skills sets and None prompt make apply a natural no-op for the
other dimensions - no per-dimension special casing needed.
- Add claude-desktop slot to PROFILE_APPS and PerApp (serde key uses
the hyphenated app id); old payloads without the key deserialize to
None and leave Claude Desktop untouched on apply
- Gate the tray Projects submenu by iterating PROFILE_APPS instead of
hardcoding Claude/Codex visibility
- Show the profile switcher on the Claude Desktop tab, mirror the
PerApp type, invalidate the claude-desktop providers cache on apply,
and extend the switcher tooltip in all four locales
- Extend the roundtrip integration test with the desktop provider
dimension; the desktop switch is cfg-gated to macOS/Windows because
desktop live writes error on Linux where CI runs cargo test
Add a profile feature that captures the current provider, MCP,
skills and prompt state for Claude Code and Codex as a named
snapshot, and re-applies it in one click from the header switcher
or the tray Projects submenu.
- New profiles table (schema v12) with current marker in settings
- ProfileService orchestrates the four existing switch primitives
(provider first, then MCP diff, skills diff, prompt enable)
- Best-effort apply: dangling references become warnings, no rollback
- Header combobox switcher + snapshot-style manage dialog
- Tray Projects submenu shared with the UI apply/event pipeline
- i18n for zh/en/ja/zh-TW under the new profiles domain
- Integration tests covering roundtrip, dangling refs and clear
* 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
* fix(subscription): display Codex free-plan 30-day quota window (#3651)
Codex free accounts are metered on a rolling 30-day secondary window
(limit_window_seconds = 2,592,000) instead of the weekly window used by
paid plans. The backend already maps this correctly to the tier name
"30_day", but the frontend TIER_I18N_KEYS whitelist had no entry for it,
so SubscriptionQuotaView filtered the tier out. When that was the only
surviving tier (as happens for free accounts), the whole quota footer
rendered nothing — free accounts showed no remaining quota or reset time
while paid accounts worked.
- Add "30_day" -> subscription.thirtyDay to TIER_I18N_KEYS
- Add the thirtyDay label to all four locales (zh/en/ja/zh-TW)
- Add a unit test locking in window_seconds_to_tier_name mappings,
including the 30-day case
Fixes#3651
* fix(tray): render Codex free-plan 30-day window in tray menu
The tray keeps its own tier-name whitelist (TIER_LABEL_GROUPS) independent
of the frontend TIER_I18N_KEYS. Its month group only listed "monthly", so a
free Codex account whose only tier is "30_day" produced empty labeled parts
→ format_subscription_summary returned None → the tray showed no quota, even
though the footer now does. That breaks the invariant the existing
gemini_summary_lite_only_still_renders test guards: any tier visible in the
footer must not leave the tray blank.
- Add TIER_THIRTY_DAY ("30_day") constant so the string is single-sourced
across backend mapping, tray grouping, and the frontend whitelist
- Map 2_592_000s explicitly to it in window_seconds_to_tier_name
- Add TIER_THIRTY_DAY to the tray month ("m") group
- Add codex_summary_thirty_day_only_still_renders regression test
Follow-up to the review on #3651 / PR for the 30-day footer fix.
* feat(universal-provider): Auto-sync after adding and drop unused addSuccess i18n key
Signed-off-by: Hu Butui <hot123tea123@gmail.com>
* fix(i18n): sync zh-TW universal provider strings
---------
Signed-off-by: Hu Butui <hot123tea123@gmail.com>
Co-authored-by: Jason <farion1231@gmail.com>
Partner aggregator (New API gateway) across Claude Code, Claude Desktop,
Codex, OpenCode, OpenClaw, and Hermes -- no Gemini, since its /v1beta
surface falls through to the web app. Claude Code and Claude Desktop use
the bare host https://nekocode.ai; Codex, OpenCode, OpenClaw, and Hermes
use /v1 with gpt-5.5.
Marks isPartner with the ?aff=CCSWITCH referral on apiKeyUrl only (the
website URL and every API endpoint stay bare, so the attribution param
never rides on a real request). Adds the partnerPromotion blurb in all
four locales and a sponsor row appended to the end of all four READMEs,
surfacing the CC Switch offer: 10% off top-ups with promo code cc-switch.
Registers the logo-derived nekocode-icon.png via URL import; the README
banner is normalized to the standard 1920x798 aspect ratio.
Partner aggregator (New API gateway) across Claude Code, Claude Desktop,
Codex, Gemini, OpenCode, OpenClaw, and Hermes. Claude Code, Claude
Desktop, and Gemini use the bare host (gemini-3.1-pro-preview for
Gemini); Codex, OpenCode, OpenClaw, and Hermes use /v1 with gpt-5.5.
Marks isPartner with ?source=ccswitch attribution on apiKeyUrl only
(website URL stays clean). Adds the partnerPromotion blurb in all four
locales and a sponsor row in all four READMEs. Positioned after
ClaudeAPI in the Claude Code and Claude Desktop lists (the two apps
that carry a ClaudeAPI preset). Registers the favicon-derived code0.png
icon via URL import; the README banner is normalized to the standard
1920x798 aspect ratio.
* fix(i18n): rename "Write Common Config" checkbox to "Apply Common Config"
The original label "Write Common Config" (写入通用配置) was ambiguous
about data flow direction — it read as "write current config INTO the
common config" when the actual behavior is the reverse: merge the
saved common config snippet INTO this provider's config.
Rename to "Apply Common Config" across zh/zh-TW/en/ja, including all
hint/guide/notice references and component defaultValue fallbacks, to
make the action unambiguous. Also aligns the Gemini hint wording with
the claude/codex "when checked" phrasing.
* docs(ja): sync Japanese docs with renamed 'Apply Common Config' label
Codex review on #4829 pointed out the Japanese user manual and README
still referenced the old checkbox label after the rename.
- docs/user-manual/ja/2-providers/2.1-add.md: 共通設定を書き込み → 共通設定を適用
- README_JA.md: 共有設定を書き込む → 共有設定を適用
Add TeamoRouter (enterprise Agentic LLM gateway) as a sponsor partner
across Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw and Hermes.
- Claude Code uses bare host https://api.teamorouter.com; Codex uses
https://api.teamorouter.com/v1 (native Responses); OpenCode/OpenClaw/
Hermes use gpt-5.5 over the OpenAI-compatible /v1 endpoint. No Gemini.
- apiKeyUrl uses the English referral link with UTM params (in-app stays
English; README links are per-language).
- Add localized in-app promo blurb (zh/en/ja/zh-TW).
- Add README sponsor rows below PatewayAI in all four READMEs.
- Register the teamorouter provider icon and pad the README banner to the
standard 2.40 aspect ratio.
Add ZetaAPI (api.zetaapi.ai) as a partner aggregator preset across Claude
Code, Claude Desktop, Codex, OpenCode, OpenClaw, and Hermes (Gemini not
supported). ZetaAPI relays native Claude/GPT, so Claude Code/Codex pass
through native models; the OpenAI-compatible apps default to gpt-5.5.
- Claude / Claude Desktop: Anthropic-compatible bare host (api.zetaapi.ai)
- Codex: native Responses at api.zetaapi.ai/v1, gpt-5.5
- OpenCode / OpenClaw / Hermes: gpt-5.5 over api.zetaapi.ai/v1
- Partner referral link (zetaapi.ai/go/ccs) as apiKeyUrl + short
partner-promotion blurb in zh/en/ja/zh-TW
- Add ZetaAPI sponsor entry to all four READMEs (zetaapi-banner.png),
first-recharge 10% off with promo code CC-SWITCH
- Register zetaapi-icon.png icon (URL import + iconUrls + metadata)
Add FennoAI (api.fenno.ai) as a partner aggregator preset across Claude
Code, Claude Desktop, Codex, OpenCode, OpenClaw, and Hermes (Gemini not
supported). FennoAI relays native Claude/GPT, so Claude Code/Codex pass
through native models; the OpenAI-compatible apps default to gpt-5.5.
- Claude / Claude Desktop: Anthropic-compatible bare host (api.fenno.ai)
- Codex: native Responses at api.fenno.ai, gpt-5.5
- OpenCode / OpenClaw / Hermes: gpt-5.5 over api.fenno.ai/v1
- Partner referral link as apiKeyUrl + short partner-promotion blurb in
zh/en/ja/zh-TW
- Add FennoAI sponsor entry to all four READMEs (fenno-banner.png)
- Register fenno-icon.webp icon (URL import + iconUrls + metadata)
Add the Qiniu Cloud AI gateway (api.qnaigc.com primary, api.modelink.ai
overseas mirror) as a partner aggregator preset across Claude Code, Claude
Desktop, Codex, Gemini, OpenCode, OpenClaw, and Hermes.
Qiniu relays native Claude/GPT/Gemini, so Claude Code/Codex pass through
native models (no domestic-model pinning); the OpenAI-compatible apps
default to gpt-5.5.
- Claude / Claude Desktop: Anthropic-compatible bare host, native passthrough
- Codex: native Responses at /bypass/openai/v1, gpt-5.5
- Gemini: gemini-3.1-pro-preview via /bypass/vertex
- OpenCode / OpenClaw / Hermes: gpt-5.5 over OpenAI-compatible /v1
- Localized display name via nameKey + partner promotion blurb in zh/en/ja/zh-TW
- Register qiniu.png icon (URL import + iconUrls + metadata)
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.
Decouple the in-app promotion banner from isPartner so a preset can show a
promo phrase plus referral link without earning the paid-partner star.
- ApiKeySection: gate the banner on partnerPromotionKey alone; the star is
still driven solely by isPartner. This also un-suppresses the existing
MiniMax cn/en promos, which already carried copy without isPartner.
- OpenCode Go (claude/codex/opencode): point apiKeyUrl at the referral
link and add partnerPromotionKey "opencode_go" (no isPartner set).
- i18n: add the opencode_go promo string across zh/en/ja/zh-TW.
The Codex provider form tied Chat-format conversion and route takeover (model
mapping) to one toggle, so a provider serving a native Responses API could not
use model mapping without forcing Chat Completions conversion.
- Promote the upstream format (Chat Completions / Responses) to an independent,
always-visible selector that triggers no sub-menus on its own.
- The local-routing toggle now solely gates the advanced sub-sections: model
mapping catalog, plus reasoning capability when the format is Chat.
- Persist modelCatalog / codexChatReasoning based on the takeover toggle and
derive its initial state from saved catalog presence (no new persisted field).
- Refresh codexConfig i18n (zh/en/ja/zh-TW): add upstreamFormat* keys and reword
the routing/advanced hints to reflect the decoupling.
- Fix codexChatProviderPresets test expectation for the Doubao Seed 2.1 Pro rename.
The account-level AccessKey is buried in the Volcengine console menus,
so surface a clickable, visible link to the IAM key management page
(https://console.volcengine.com/iam/keymanage) right under the AK/SK
hint. Opens via settingsApi.openExternal; adds the volcengineKeyConsoleLink
label across all four locales.
* feat(db): in-app recovery screen with upgrade button when DB version is too new
When the SQLite user_version is newer than the app supports (SCHEMA_VERSION),
Database::init() fails and previously dead-ended in a native Retry/Exit dialog
(Retry just fails again). The app now boots a dedicated recovery screen instead.
- Detect the recoverable "version too new" case and surface it via init_status
(kind="db_version_too_new" + db_version/supported_version); force-show the main
window and skip normal AppState boot (recovery commands need only AppHandle).
- The recovery screen first checks for an available update:
- update available -> "Upgrade app" runs the updater (download + install +
restart) with a download progress bar.
- no update (already latest) -> warns that the DB is too new even for the latest
build (likely a third-party client), so upgrading cannot help.
- install_update_and_restart now emits `update-download-progress` events; new
`check_app_update_available` command.
- i18n: en / ja / zh / zh-TW.
Verified: pnpm typecheck + format:check + test:unit (351) green; cross-model
review of Rust correctness and recovery-mode safety (no AppState panic).
* fix(db): pre-check too-new DB before schema writes; exit on close in recovery mode
Addresses review feedback on the too-new-DB recovery flow.
- P1: stored_user_version_exceeds_supported() is now checked BEFORE
Database::init(), so create_tables()'s DDL (incl. the unconditional
DROP INDEX/DROP TABLE IF EXISTS failover_queue) never runs against a
database whose user_version we cannot understand. The earlier
post-init-failure recovery branch is removed; the pre-check is the
single authoritative guard, so "don't write to a DB we can't read"
now holds from the very first DB access.
- P2: the window CloseRequested handler now detects recovery mode
(init_error kind = db_version_too_new) and exits the app instead of
honoring minimize_to_tray_on_close. Recovery mode returns before the
tray is created, so hiding the window would leave the app running with
no tray to bring it back; native-close now quits cleanly.
Verified: cargo fmt --check clean; cross-model Rust review of both fixes
(compile-correctness + no normal-startup/close regression).
* add glm-5.2 pricing
* feat(usage): add live end time option for custom date range
Add a "End time follows current time" checkbox in the custom date range
picker. When enabled, the end time becomes read-only and automatically
tracks the current moment, so usage data always reflects up-to-the-second
consumption from the chosen start time.
This is especially useful under the Coding Plan 5-hour quota window —
users can set the start time to when their 5h window began and keep
the end time live to monitor real-time token consumption.
* style(usage): fix prettier formatting for UsageDashboard
* fix(usage): include liveEndTime in React Query cache keys
Without liveEndTime in the query key, a live custom range and a fixed
custom range with the same stored endpoints share the same cache entry.
After the live range refreshes (endDate = now), switching to the fixed
range shows stale data fetched through "now" instead of the original
end time.
Query coding-plan / agent-plan usage for Volcengine Ark via the
control-plane OpenAPI (open.volcengineapi.com), which requires
account-level AccessKey signing rather than the inference API key.
- Implement Volcengine Signature V4 (an AWS SigV4 variant: fixed
canonical header order, "HMAC-SHA256" algorithm without the AWS4
prefix, scope ending in "request", service "ark") in
services/coding_plan.rs
- Auto-detect the plan by probing GetAFPUsage (Agent Plan, AFP
five-hour/weekly/monthly quotas) first, falling back to
GetCodingPlanUsage (Coding Plan, session/weekly/monthly percentages);
a single credential covers whichever plan the account holds
- Parse the real response shape: the window label lives in the `Level`
field; guard ResetTimestamp <= 0 (session returns -1)
- Store account-level credentials on UsageScript (accessKeyId /
secretAccessKey), threaded through both the test command and the
TOKEN_PLAN auto-refresh path
- Add an independent AK/SK input block with a detailed hint pointing to
the Volcengine console -> API Access Keys
- Add the `monthly` tier label (frontend TIER_I18N_KEYS + tray
TIER_LABEL_GROUPS + subscription service)
- Sync zh / en / ja / zh-TW locales and the usage-query docs
- Reduce title->tabs and tabs->card vertical gaps from 24px to 12px
- Add an always-visible footer hint guiding users to fill the fields
below after choosing a preset (pinned footer, never scrolls off)
- FullScreenPanel: add optional contentClassName prop to scope content
padding overrides without affecting the other panels that share it
- i18n: add provider.addFooterHint in zh/en/ja/zh-TW
The vendor renamed its domain, API endpoints, and trademark from CTok to
ETok. Update all user-facing branding accordingly:
- Presets (claude, claude-desktop, codex, gemini, hermes, openclaw,
opencode): ctok.ai -> etok.ai, api.ctok.ai -> api.etok.ai, internal
provider id ctok -> etok, display name CTok.ai -> ETok.ai
- Icons: replace inline placeholder SVG with the new ETok brand PNG,
moved from `icons` to `iconUrls`; key ctok -> etok; banner logo
ctok.png -> etok.png
- README (en/zh/ja/de) partner banner: new logo, links, and copy
- i18n (en/ja/zh/zh-TW): partnerPromotion key ctok -> etok
Keep "ctok" in the Codex history-migration whitelist as a legacy id and
add "etok", so existing users' local session history stays correctly
bucketed after the rename.
* 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>
- Replace activity/agentplan links with the new codingplan invite URL
(ac=MMAP8JTTCAQ2, rc=6J6FV5N2) across all 6 app presets
- Refresh partnerPromotion copy in all 4 locales: two-month 75% off plus
invite code 6J6FV5N2; correct product name from Agent Plan to Coding Plan
- README: swap the CN-mainland redirect link in EN/DE, refresh the ZH promo
copy, and add the redirect link to the JA BytePlus entry
The provider panel health check sent a real streaming model request, which many third-party providers block (401/403/WAF), causing false negatives while only stable official endpoints passed. Replace it with a lightweight reachability probe: GET the provider base_url and treat any HTTP response (200/4xx/5xx) as reachable; only DNS/connect/TLS/timeout count as failure. Latency is the probe's TTFB.
Backend (services/stream_check.rs): rewrite ~2200 -> ~350 lines, dropping real-request building, format conversion, auth and API-path resolution while keeping per-app base_url extraction. Defaults: 8s timeout, 1 retry, 1500ms degraded threshold.
Failover invariant: the reachability check must never reset the circuit breaker (reachable != usable; a 403 host is reachable but broken for real traffic). Remove the resetCircuitBreaker call from useStreamCheck; failover failure detection stays driven solely by real proxy traffic (forwarder/circuit_breaker untouched). useResetCircuitBreaker is kept dormant for a future manual-recovery entry.
Open the check to all providers: drop the official/copilot/codex-oauth/third-party gating and the 'sends a real request' confirm dialog. For official providers whose base_url is intentionally empty, fall back to the endpoint the client actually uses (Claude -> api.anthropic.com, Codex -> chatgpt.com/backend-api/codex, Gemini -> generativelanguage). Non-official providers with a missing base_url still error to avoid a false green light. Claude Desktop Official is native 1P mode (talks to claude.ai, cc-switch not in the request path, no reliable endpoint) so its button stays hidden.
Slim StreamCheckConfig and per-provider testConfig to timeout/threshold/retries (drop test model + prompt); sync zh/en/ja/zh-TW. Retain the now-unused anthropic_to_openai/anthropic_to_gemini transform utilities and their test suites.
- 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.
- Enable dialog gains a checkbox (default off) to migrate existing
official sessions from the built-in "openai" bucket into the shared
"custom" bucket, with per-generation backups; failed migrations retry
at startup
- Disable dialog offers a precise restore driven by the backup ledger:
only sessions recorded as "openai" in backups are flipped back, and
sessions created while the toggle was on are never touched
- Completion marker and backup generations are bound to the canonical
Codex config dir; migrate/restore serialize on an op lock and the
marker is written conditionally inside the settings write lock
- save_settings rolls back the toggle and fails the save when the live
rewrite fails; migration additionally requires the live config to
actually route to the shared bucket (skips with live_not_unified so
refused injection or proxy takeover can't split history)
- Restore refuses to run while the toggle is (re-)enabled and reports
nothing_to_restore instead of a zero-count success; local migration
markers are now backend-owned in merge_settings_for_save so stale
frontend payloads can't resurrect them
- Settings autosave reverts optimistic form state on failure so a
failed toggle change can't be replayed by an unrelated save
- ConfirmDialog supports an optional checkbox; all four locales updated
Codex buckets resume history by the model_provider id recorded in each
session: official runs (no key, built-in "openai") and cc-switch
third-party runs (shared "custom") are mutually invisible in the resume
picker. Add an opt-in setting that runs official providers under the
shared "custom" id so future official sessions land in the same history
bucket as third-party ones. Forward-only by design: existing sessions
are not migrated.
When enabled, official live config.toml gets model_provider = "custom"
plus a [model_providers.custom] entry that mirrors the built-in openai
provider (requires_openai_auth routes auth to the ChatGPT login in
auth.json, name "OpenAI" keeps is_openai() feature gates, explicit
supports_websockets/wire_api restore built-in defaults). auth.json is
untouched.
Key invariants:
- Injection lives only in the live config: switch-away backfill strips
the exact injected shape, so stored provider configs stay clean and
turning the toggle off fully reverts on the next write.
- Toggle changes apply immediately via a takeover-aware reapply: when
the proxy owns the live config (backup/placeholder present), only the
live backup is updated, mirroring the provider-switch path.
- The takeover backup path runs the same injection so a takeover
release restores a config that still carries the unified routing.
- Injection refuses to activate a foreign [model_providers.custom]
table (e.g. stale entry with a third-party base_url) to avoid routing
ChatGPT OAuth traffic to an unknown backend.
The toggle lives under Settings → Codex App Enhancements; the
description warns that resuming old sessions across providers may fail
because encrypted_content reasoning only decrypts on the backend that
created it (upstream treats cross-provider resume as unsupported).
- LemonData: delete the provider preset from all apps (claude, claude-desktop,
codex, gemini, hermes, opencode, openclaw), remove partner promotion copy
(zh/en/ja/zh-TW), icon index/metadata entries, sponsor ads in all READMEs,
and the logo/icon PNG assets.
- SudoCode: drop the isPartner flag and partnerPromotionKey across all app
presets and remove the now-orphaned partner promotion copy; it stays as a
regular third_party provider, keeping its icon.
Replace the click-to-cycle refresh button with a Select matching the
source/model filters. The "off" option now shows a localized label
(zh/en/ja/zh-TW) instead of the cryptic "--", and changing the interval
still invalidates all usage queries for an immediate refresh.
Align the top-bar controls into two width groups: source and model
selects at 120px, refresh select and date range trigger at 150px. The
date range button moves from auto width to fixed, so its long custom
range label gets truncate + hover title, and the calendar icon is
shrink-proofed.
The provider/model filters only lived inside the request-log table, so
there was no way to see "how much did app X spend on source Y" across
the whole dashboard. Promote them to the top bar next to the app
filter, applying globally to the hero summary, trend chart, request
logs, and both stats tabs.
Backend: the five stats queries (summary, summary-by-app, trends,
provider stats, model stats) accept optional provider_name/model
filters, applied to both the detail and daily-rollup branches (the
rollup PK already carries provider_id/model/pricing_model). Sources
match by exact display name via provider_name_coalesce, so session
placeholder rows like "Claude (Session)" are selectable; models match
by effective pricing model (pricing_model falling back to model), the
same grouping key the model-stats tab uses. Request-log filtering
switches from LIKE to these exact semantics.
Frontend: two truncating dropdowns list only sources/models that have
data in the current range, with the model list cascading from the
selected source. Dynamic option values are prefix-encoded so a source
literally named "all" cannot collide with the sentinel, query keys
fall back to null instead of "all", and the option queries follow the
dashboard refresh interval (otherwise their default 30s polling drags
same-key stats queries along even with refresh disabled). The
request-log filter bar keeps only the log-specific status-code select.
Labels read "sources" rather than "providers" because direct-connect
session buckets sit alongside real providers. i18n updated across
zh/en/ja/zh-TW.
The Desktop gateway's proxy traffic is still recorded under its own
app_type for route-takeover billing audit (the request detail panel
shows the real value), but the dashboard now folds it into `claude`
for display. A standalone "Claude Desktop" bucket only ever showed a
partial number: Desktop chat usage never passes through the proxy and
has no scannable local source, while its Code-tab sessions are just the
embedded Claude Code runtime writing into the shared ~/.claude/projects
tree — so a separate bucket misled users into reading it as Desktop's
full usage.
Backend: new `folded_app_type_sql` helper wraps the app_type column in
every dashboard read path (10 filter sites + the by-app projection) so
`= 'claude'` also matches claude-desktop and GROUP BY merges the two,
without changing bound-param counts. Dedup matching and provider-limit
checks keep exact comparison; get_request_logs folds only the WHERE
filter and keeps the raw app_type in its row projection.
Frontend: drop claude-desktop from the dashboard AppType/KNOWN_APP_TYPES
filter list, the UsageHero theme, and the now-dead appFilter locale key
in all four languages (the managed-app apps.claude-desktop label stays).
Adds test_claude_desktop_folds_into_claude_for_display.
Add Unity2.ai, a high-performance AI API relay partner, as a preset for
Claude, Codex, Gemini, OpenCode, OpenClaw, Claude Desktop, and Hermes.
Each preset carries the referral signup link as apiKeyUrl.
- Register the unity2 icon via iconUrls (PNG URL import) + metadata
- Add partnerPromotion copy in zh/en/ja/zh-TW; backfill the missing
zh-TW ccsub entry
- List Unity2.ai in the sponsor section of all README locales
- Codex uses the bare base URL (gateway exposes /responses at root);
OpenCode/OpenClaw/Hermes use the /v1 chat-completions endpoint with
gpt-5.5 as the only preset model
- Trim CCSub OpenCode/OpenClaw/Hermes model lists to gpt-5.5 to match
- Normalize unity2/ccsub banners to the standard 2.41 aspect ratio
- Fold local routing toggle, model mapping, reasoning overrides and custom
User-Agent into a single collapsible advanced section, mirroring the
Claude form (auto-expands when UA is set or local routing is enabled)
- Custom User-Agent becomes configurable for native Responses providers;
it was previously reachable only when openai_chat routing was on
- Collapsed hint names local routing as the entry point for Chat
Completions / non-GPT providers
- Backfill all missing codexConfig keys in zh-TW locale
Polish the provider-level User-Agent override UI on the Claude and Codex forms.
- Add a shared CustomUserAgentField (label + input + preset dropdown + live
validation) so both forms stay in sync.
- Provide curated UA presets (Claude Code / Kilo Code families that pass
coding-plan UA whitelists per #3671); the first is Claude Code's real
`claude-cli/x (external, cli)` format. Whitelists gate on the name prefix,
not the version, so static values stay valid across upgrades.
- Expose presets via a dropdown to the right of the input (z-[200] so it
renders above the dialog layers) instead of inline chips.
- Move the field into the existing advanced/reasoning collapsibles.
- userAgent.ts mirrors the backend byte rule (reject only control chars;
non-ASCII is allowed) for a non-blocking inline hint.
- i18n for all four locales (zh/en/ja/zh-TW).
- add claude-desktop to AppType/KNOWN_APP_TYPES and the dashboard app
filter; it was hidden because its rows looked like pure failure
noise, which was the app_type attribution bug fixed on the backend
- request detail panel now shows the requested model and the pricing
model when they differ from the response model, making route-takeover
bills auditable from the UI
- locale keys added for zh/en/ja/zh-TW