Commit Graph

411 Commits

Author SHA1 Message Date
Jason 85b38c1bed refactor(codex): decouple model mapping from local-routing toggle
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.
2026-06-29 23:30:58 +08:00
Jason e6d40d0a93 feat(presets): add OpenCode Go referral link and promo copy without partner badge
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.
2026-06-26 23:39:18 +08:00
abing|web3.club 430ddf92bd Add SubRouter provider presets (#4522)
Co-authored-by: abingyyds <abingyyds@gmail.com>
2026-06-26 23:30:52 +08:00
Jason a4eb5f3778 feat(codex): decouple upstream format selector from model-mapping toggle
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.
2026-06-26 09:15:51 +08:00
Jason 213f55a6d2 feat(usage): show direct Volcengine key console link in usage panel
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.
2026-06-26 09:14:10 +08:00
Jianfeng Mei 6fd4e6f462 feat(proxy): 添加本地代理请求覆盖功能,支持自定义请求头和请求体 (#4589)
* feat(proxy): 添加本地代理请求覆盖功能,支持自定义请求头和请求体

* fix(proxy): harden local request overrides validation

* feat(proxy): 添加受保护的本地代理请求头名称验证功能

* fix(i18n): 更新本地代理请求覆盖的错误提示信息格式

---------

Co-authored-by: jason.mei <jason.mei@ucloud.cn>
2026-06-24 22:19:01 +08:00
SaladDay edeee25fae feat(db): in-app recovery screen with upgrade button when DB version is too new (#4575)
* 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).
2026-06-24 15:07:42 +08:00
Arc 9171ad752c feat(usage): add live end time option for custom date range (#4438)
* 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.
2026-06-23 23:04:00 +08:00
makoMakoGo 2d478876fa fix: align Claude MCP path for custom config dirs (#3431)
* fix claude mcp path for custom config dir

* fix claude mcp custom profile isolation

* fix claude mcp override path edges

* fix ci test isolation
2026-06-23 09:00:56 +08:00
Jason c4630b5c26 feat(providers): add Volcengine Ark usage query with AK/SK signing
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
2026-06-22 10:28:10 +08:00
Jason a3b3a06f5e feat(providers): tighten Add Provider header spacing and add footer hint
- 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
2026-06-22 10:28:10 +08:00
Jason 142c8c1da7 feat(providers): rebrand CTok to ETok across presets, icons, and docs
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.
2026-06-22 10:28:10 +08:00
Xinyu Kuo 0bb3b7515a feat(usage): support importing model pricing from models.dev (#4079)
* 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>
2026-06-16 22:01:56 +08:00
Jason c678374c59 chore(presets): update Volcengine Ark Coding Plan invite link and promo copy
- 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
2026-06-14 23:03:31 +08:00
Jason a5903d8600 feat(health-check): replace real-LLM probe with HTTP reachability check
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.
2026-06-14 21:21:45 +08:00
Jason 4f8a79c273 feat: add Claude Fable 5 model mapping across Claude Code and Desktop
- 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.
2026-06-13 19:12:34 +08:00
Jason eab6bfd20c feat(codex): add opt-in migration and ledger-based restore for unified session history
- 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
2026-06-12 23:35:01 +08:00
Jason 948d762792 feat(codex): add unified session history toggle for official providers
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).
2026-06-12 10:40:51 +08:00
Jason 4f355970e1 chore(presets): remove LemonData provider and demote SudoCode to regular provider
- 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.
2026-06-11 23:15:11 +08:00
Jason 22ecd2d611 feat(usage): turn refresh interval into a select and align control widths
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.
2026-06-11 22:42:26 +08:00
Jason a75f479576 feat(usage): lift provider/model filters to dashboard-wide scope
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.
2026-06-11 22:42:26 +08:00
Jason c7efa77ad9 refactor(usage): fold claude-desktop into claude in the dashboard
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.
2026-06-11 22:42:26 +08:00
Peng Steam c1aa6c3917 feat(providers): add preset search and sorting (#3975) 2026-06-11 17:14:53 +08:00
Jason daa5595f36 feat(presets): add Unity2.ai partner provider across seven apps
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
2026-06-11 11:05:32 +08:00
Jason e776160912 feat(provider-form): consolidate codex form into advanced options section
- 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
2026-06-11 08:29:29 +08:00
Jason 596019505f feat(provider-form): custom User-Agent presets dropdown in advanced settings
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).
2026-06-11 08:29:29 +08:00
Jason e8b07cb2a5 feat(usage): claude-desktop filter and pricing-model audit display
- 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
2026-06-11 08:29:29 +08:00
Jason fa17194d84 feat(presets): add CCSub provider across six apps
Add CCSub, a multi-model aggregator partner, as a preset for Claude, Codex, OpenCode, OpenClaw, Claude Desktop, and Hermes. Each preset carries the referral signup link as apiKeyUrl.

- Register the ccsub icon via iconUrls (1.1MB SVG URL import) + metadata
- Add partnerPromotion copy in zh/en/ja
- List CCSub in the sponsor section of all README locales
- Use gpt-5.5 and gemini-3.1-pro as the OpenAI/Gemini model ids
2026-06-08 22:04:16 +08:00
Jason 5c36ae066b fix(providers): only block explicit official providers under proxy takeover
The proxy-takeover block previously fell back to the isOfficial heuristic
(empty base_url / missing key) when category was absent. That misjudged
custom providers whose endpoint lives in meta or whose fields are simply
unfilled: their switch button got disabled, making users think the config
was broken. That extra UI block was also "virtual" — the executor in
useProviderActions only ever honored category === "official", so the
front end blocked more than the backend would enforce.

Gate the block solely on explicit category === "official", matching the
executor and unifying both verdicts on a single source of truth.

Also rework the blocked-state UI:
- drop the red "blocked" badge for a plain disabled Enable button
- move title/cursor onto a wrapper span (disabled buttons set
  pointer-events:none, so an on-button title/cursor never fired)
- replace the account-ban warning tooltip with a lighter hint
  (provider.blockedByProxyHint), four locales kept in sync
2026-06-07 20:56:35 +08:00
Jason 473f21971d feat(usage): add official subscription quota template with unified tier rendering
Changes:
- Add official_subscription template type for Claude/Codex/Gemini
- Replace implicit 'category=official auto-query' with explicit opt-in template
- Default disabled; users enable via usage script modal with configurable interval
- Unify tier→label mapping across subscription and script paths via labeled_tier_parts()
- Fix tray rendering: week aliases (seven_day/opus/sonnet) now use highest utilization
- Add depth guard: official_subscription checks enabled flag in query_provider_usage_inner
- Add cache invalidation symmetry: invalidate_subscription() for disabled providers
- i18n: add templateOfficialSubscription + hint in zh/en/ja/zh-TW

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

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

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

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

* chore: remove unused token suffix translation

---------

Co-authored-by: Jason <farion1231@gmail.com>
2026-06-05 08:12:17 +08:00
@nothingness0db nothingness0db 0527002cca feat(usage): add OpenCode session usage sync (#3215)
* feat(usage): add OpenCode session usage sync

Add OpenCode as a fourth app type in the usage statistics system.
Reads per-message token data from opencode's local SQLite database
(~/.local/share/opencode/opencode.db) and imports into proxy_request_logs.

- New session_usage_opencode.rs module following Codex/Gemini pattern
- Parses assistant message.data JSON for tokens, cost, model
- Adds "opencode" to AppType union and filter tabs
- Updates dedup filters to include opencode_session data_source
- Adds i18n keys for all 4 locales

* fix(usage): add opencode to UsageHero title themes

* fix: respect XDG_DATA_HOME and platform defaults for OpenCode DB path

- Support OPENCODE_DB env var override (absolute and relative paths)
- Use ~/Library/Application Support/opencode/ on macOS
- Use XDG_DATA_HOME/opencode/ when set
- Fall back to ~/.local/share/opencode/ on Linux
- Rename misleading test to test_parse_message_data_ignores_role

* fix(usage): use ~/.local/share/opencode on all platforms

OpenCode relies on xdg-basedir, which ignores macOS/Windows conventions,
so its DB always lives at ~/.local/share/opencode. The previous macOS
default pointed at ~/Library/Application Support/opencode, which does not
exist, making the sync a silent no-op for macOS users without
XDG_DATA_HOME set.

* fix(usage): include opencode -wal mtime in freshness check

OpenCode runs its SQLite DB in WAL mode; new commits land in the -wal
file and the main DB file's mtime only advances on checkpoint. Keying
the freshness gate solely on opencode.db could skip newly written
sessions until a checkpoint occurred. Take the max of the db and -wal
mtimes instead.

* style(usage): apply cargo fmt to opencode session sync

Fixes Backend Checks cargo fmt --check failure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(usage): ignore empty OPENCODE_DB and XDG_DATA_HOME env vars

An empty OPENCODE_DB collapsed the path to the data dir (dropping the opencode.db filename); an empty XDG_DATA_HOME produced a relative "opencode" path. Treat empty strings as unset, per the XDG spec.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(usage): harden OpenCode session sync error handling and labeling

- Map '_opencode_session' provider_id to 'OpenCode (Session)' display name
- Return accurate inserted flag from insert_opencode_message (was always true)
- Do not advance file/session sync_state when a session errors, so failed
  inserts are retried next run instead of being permanently skipped
- Surface per-message insert failures into the sync result errors
- Add opencode_session data-source icon and i18n labels (zh/zh-TW/en/ja)
- Add provider-stats labeling test for opencode session rows

* fix(usage): only import finalized OpenCode messages

An in-progress assistant message holds partial tokens; OpenCode updates the same message_id with final values later. Since request_id is fixed and the insert uses INSERT OR IGNORE, a partial row could never be corrected. Skip messages without time.completed so each turn is imported once with final usage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(usage): keep OpenCode incomplete sessions retryable

---------

Co-authored-by: Eira Hazel <kip3vx9ma@mozmail.com>
Co-authored-by: Eria hazel <git config --global user.email your@email.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
2026-06-04 22:26:24 +08:00
Keith Yu 2a24da517f feat: 新增 S3 兼容云存储同步 (#1351)
* Add S3 Cloud Sync design document

Design for adding AWS S3 as a new Cloud Sync backend alongside WebDAV.
Hybrid approach: extract shared sync protocol, add independent S3 transport.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add S3 cloud sync implementation design (reqwest + Sig V4)

Updated design based on 2026-03-06 draft: switches from rust-s3 crate
to hand-rolled AWS Sig V4 on existing reqwest for broader S3-compatible
service support (AWS, MinIO, R2, Alibaba OSS, Tencent COS, Huawei OBS).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add S3 cloud sync implementation plan (11 tasks, TDD)

Detailed step-by-step plan covering: sync_protocol extraction, S3 Sig V4
transport, settings, sync/auto-sync modules, Tauri commands, frontend
presets/dynamic form, and i18n.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* deps: add hmac crate for S3 Sig V4 signing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract sync_protocol.rs from webdav_sync.rs for shared use

Move transport-agnostic sync protocol logic (constants, types, snapshot
building, manifest validation, artifact verification, snapshot application,
utilities) into a new shared sync_protocol module so both WebDAV and the
upcoming S3 transport can reuse it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use transport-neutral error keys in sync_protocol

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add S3 transport layer with AWS Sig V4 signing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add S3SyncSettings to AppSettings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add S3 sync module with upload/download/fetch

Implements the S3 sync protocol layer (s3_sync.rs) that combines the
shared sync_protocol with the S3 transport. Mirrors the WebDAV sync
module structure with independent sync mutex, connection check,
upload, download, fetch_remote_info, and sync status persistence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add S3 auto sync worker with debounce

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add S3 sync Tauri commands and auto sync worker startup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add S3 sync TypeScript types and API layer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add S3 sync i18n translations (en/zh/ja)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add S3 sync presets and dynamic form to sync settings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: preserve HTTP scheme for S3 custom endpoints (MinIO support)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add live S3 integration tests (env-var driven, --ignored)

Run with: S3_TEST_AK=... S3_TEST_SK=... S3_TEST_BUCKET=... cargo test --lib services::s3::integration_tests -- --ignored

Verifies test_connection, put_object, get_object, head_object, and 404
handling against a real S3 bucket using the project's own Sig V4 signing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: remove internal design docs before PR

* fix: wire S3 auto-sync to DB hook & sync UI state on async load

- P1: Add s3_auto_sync::notify_db_changed call in SQLite update_hook
  so S3 auto-sync worker receives DB change signals (was only wired
  for WebDAV, leaving S3 worker idle)

- P2: Add useEffect to update syncType selector when s3Config loads
  asynchronously, preventing stale "webdav" default for S3 users

* fix: satisfy clippy for s3 sync

* fix: address s3 sync review feedback

---------

Co-authored-by: Keith (via OpenClaw) <keithyt06@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
2026-06-04 22:18:51 +08:00
Jason 6692343d1e Add media fallback rectifier for text-only models
Replace unsupported Anthropic image blocks with an [Unsupported Image] marker when routed models are text-only or upstream rejects image input.

Add rectifier settings for media fallback and heuristic model detection, wire the controls into the settings UI, and cover the sanitizer and forwarder gates with regression tests.
2026-06-04 11:40:16 +08:00
Sleepwf 73073454cd fix(i18n): align Chinese VS Code wording with English and Japanese (#3228) 2026-06-03 15:10:06 +08:00
Jason aeaa016cae Simplify Codex takeover notice copy and match hint styling
Restyle the proxy-takeover notice in the Codex editor from the boxed
Alert to the amber inline-hint style used by the endpoint hints, and drop
implementation jargon (127.0.0.1 / PROXY_MANAGED) that users could not
interpret. The notice and the auth/config hints now simply state that the
form shows the stored provider config rather than the proxy-managed live
config. i18n synced across en/zh/ja/zh-TW.
2026-06-01 11:41:05 +08:00
Jason a04e72a267 Fix Codex edit dialog masking live OAuth during proxy takeover
The reported "OAuth access token disappears when enabling Codex proxy
takeover" was a display artifact, not data loss: auth.json on disk kept
the OAuth login the whole time. During takeover the edit dialog falls
back to the stored provider config (so it does not surface the proxy
placeholder), which for a third-party provider shows that provider's own
key instead of the live auth.json, making the OAuth token look gone.

Thread an isProxyTakeover flag from App through ProviderForm into the
Codex editor and show an explicit notice plus storage-aware auth/config
hints clarifying that the form displays the stored provider config while
the live config is temporarily managed by the proxy. Drop the
proxy-running condition so the notice shows whenever takeover is active,
even with the proxy stopped.

Add a regression test asserting the dialog does not read live settings
during takeover and renders the database config. i18n synced across
en/zh/ja/zh-TW.
2026-06-01 11:41:05 +08:00
Jason c9cadd6e09 Fix Codex OAuth auth being cleared during preserve-mode takeover
When "preserve official auth on switch" is enabled, proxy takeover routes
the PROXY_MANAGED placeholder into config.toml's experimental_bearer_token
and leaves auth.json (the ChatGPT OAuth login) untouched. Takeover detection
only inspected auth.json's OPENAI_API_KEY, so it never recognized this state
and returned a false negative, which led downstream paths to clobber the
preserved OAuth login.

- Detection: is_codex_live_taken_over now also matches a config.toml
  experimental_bearer_token equal to the placeholder, fixing detect/cleanup/
  restore/startup-recovery in one place.
- Cleanup: remove the config.toml bearer token only when it equals the
  placeholder (new remove_codex_experimental_bearer_token_if predicate), so a
  real third-party key is never stripped.
- Write: under preservation, the None-provider takeover path writes only
  config.toml and keeps auth.json intact, matching the provider path.
- Settings: rename the section to "Codex App Enhancements" and reword the
  description across all four locales.
- Add tests covering OAuth preservation on takeover and placeholder-only
  cleanup.
2026-05-30 22:56:36 +08:00
Jason 41433cfab2 Add Codex restart hint after provider switch 2026-05-30 21:19:30 +08:00
Jason 2683af57cb Add Codex auth preservation setting 2026-05-30 14:13:58 +08:00
Jason 94cc3d103a Align Claude Desktop model mapping with Claude Code three-role tiers
Claude Desktop's 3P validation only accepts claude-{sonnet,opus,haiku}-*
role IDs, so providers must map every tier. Bring the Desktop mapping flow
in line with Claude Code and fix the fallout that broke sub-agent Haiku calls.

- Proxy form: replace the dynamic route list with fixed Sonnet/Opus/Haiku
  tiers; blank tiers backfill from the first filled tier (Sonnet first) on
  submit and inherit its supports1m flag
- Backend: add a role-keyword fallback in map_proxy_request_model so dated
  official names (e.g. claude-haiku-4-5-20251001) resolve to the right tier,
  guarded by is_claude_safe_model_id so [1m]-suffixed IDs stay rejected
- Tighten is_claude_safe_model_id / isClaudeSafeRoute to reject degenerate
  role IDs like "claude-sonnet-"
- Fix the seed-effect race where normalizing empty routes to three blank
  tiers blocked the default-route backfill
- Sync switch hints, placeholders, and the zh/en/ja/zh-TW locales to the
  three-role-ID rule
- Update zh/en/ja user manual (2.1, 2.6), calling out legacy Claude IDs
  (claude-3-5-sonnet-...) as a rejected example

Tests: 282 frontend + 34 backend claude_desktop; typecheck, clippy, fmt clean.
2026-05-29 22:52:25 +08:00
Jason 3a15420770 Update default models and pricing across presets
Bump default model names project-wide: gpt-5.4 -> gpt-5.5,
gemini-3.x -> gemini-3.5-flash, glm-5 -> glm-5.1, and
grok-code-fast-1 -> grok-build-0.1 across all provider presets
(claude, codex, gemini, hermes, openclaw, opencode, universal),
Gemini config, and stream check defaults.

Pricing:
- Seed gemini-3.5-flash, gemini-3.1-flash-lite, step-3.5-flash-2603,
  doubao-seed-2.0-code, mimo-v2.5(/pro), qwen3-coder-480b, grok-build-0.1.
- Correct deepseek-v4-flash/pro, glm-5/5.1, grok pricing.
- Add repair_current_model_pricing: idempotent pass that fixes only
  rows still equal to the outdated built-in values, preserving any
  user-customized prices (seed uses INSERT OR IGNORE and cannot update
  existing rows).

Fixes from review:
- opencode: drop duplicate gemini-3.5-flash variant (unreachable via
  .find), keep the entry with the full minimal/low/medium/high set.
- Align stale display names/costs to gemini-3.5-flash (hermes, openclaw,
  opencode); openclaw cost -> {1.5, 9, 0.15} to match seed.
- i18n (zh/en/ja/zh-TW): refresh OMO category tooltips for new model
  names; fix writing tooltip to Kimi K2.5 to match its recommended.

Update tests accordingly and add a regression test asserting unique
model ids in the Google opencode preset variants.
2026-05-29 22:52:25 +08:00
Jason 0877b9e35d Upgrade default Claude Opus model to 4.8
Bump the default Opus route/model from claude-opus-4-7 to claude-opus-4-8
across provider presets (claude, claudeDesktop, hermes, openclaw, opencode,
universal), i18n locales (zh/en/ja/zh-TW), pricing seed data, and the
user-manual docs.

- Add claude-opus-4-8 pricing row ($5/$25/$0.50/$6.25); keep the 4-7 row
  for historical usage stats (seeded via INSERT OR IGNORE).
- Claude Desktop proxy: accept bidirectional opus 4-7 <-> 4-8 route alias
  during rollout so previously saved routes keep resolving.
- thinking_optimizer: route opus-4-8 through adaptive thinking and normalize
  dotted model ids (also fixes dotted 4-6/4-7 falling back to legacy).
- usage_stats: normalize Bedrock/Vertex/aggregator opus-4-8 ids to base
  pricing.

Also merge role:"system" messages into the Gemini systemInstruction in the
Anthropic->Gemini transform.
2026-05-29 22:52:25 +08:00
Jason e71b90916b Add SudoCode partner provider presets 2026-05-29 11:11:46 +08:00
Jason 32b30e43c0 Add AtlasCloud partner provider presets 2026-05-29 09:46:00 +08:00
Jason 9ef141902e Add APINebula partner provider presets 2026-05-29 09:18:59 +08:00
Jason 8302f1e36c Add APIKEY.FUN partner provider presets 2026-05-28 23:43:48 +08:00
Jason af60c7ed2c feat(codex): add remote compaction toggle for third-party providers
Write model_providers name as "OpenAI" to let Codex attempt remote
compaction through compatible endpoints. Hidden for official providers.
2026-05-28 11:57:54 +08:00
Jason 6b0dd3c4e9 fix(omo): sync recommended models with upstream and improve Fill Recommended feedback
The Fill Recommended button was misleading — it showed toast.success even
when most slots couldn't be filled due to model ID mismatches with the
user's configured providers.

Changes:
- Sync OMO_BUILTIN_AGENTS/CATEGORIES recommended fields with upstream
  oh-my-openagent model-requirements (gpt-5.4→5.5, kimi-k2.5→claude-sonnet-4-6, etc.)
- Add toast.warning tier when unmatched >= filled, showing slot:model pairs
  (e.g. "Sisyphus: claude-opus-4-7") so users know exactly what to configure
- Upgrade fillRecommendedNoMatch to also show examples
- Add fillRecommendedMostlyUnmatched i18n key (zh/en/ja/zh-TW)
2026-05-27 23:08:11 +08:00
Kairos Duan 3c3d417457 Enable Codex goals in provider templates (#3089)
* Enable Codex goals in provider templates

* feat: add Codex goal mode toggle

- Remove forced goals=true from Codex provider presets and custom templates.
- Add a Codex provider editor switch that updates [features].goals on demand.
- Update docs, i18n, and regression coverage for the optional Goal mode flow.

---------

Co-authored-by: Jason <farion1231@gmail.com>
2026-05-27 11:31:31 +08:00