Commit Graph

1653 Commits

  • docs: update sponsor logos and listings
    - Add ClaudeAPI as new sponsor (all 3 languages)
    - Remove ChefShop sponsor entry (all 3 languages)
    - Convert shengsuanyun logo from SVG to PNG
    - Standardize partner logos to 1920x798 canvas
    - Fix ClaudeAPI alt text typo and spacing
    - Sync sponsor order across zh/en/ja READMEs
  • refactor(claude-desktop): show badge only for providers requiring routing
    Direct-mode providers no longer display a badge since routing is
    optional for them. Proxy-mode providers now show "需要路由" / "Requires
    routing" to clarify that local routing must be active.
  • fix(claude-desktop): match proxy model route without [1M] suffix
    Claude Desktop strips the [1M] suffix from model IDs when sending
    requests, causing route lookup to fail with "model route is not
    configured". Fall back to base-name comparison when exact match misses.
  • refactor(claude-desktop): simplify model mapping UX
    - Remove "Import from Claude" button from main provider list (keep in empty state)
    - Remove "Desktop model" column from proxy mode mapping table; route names are now auto-generated
    - Rename "upstream model" label to "requested model" and equalize column widths
    - Rewrite model mapping hint and toggle description for end-user clarity
    - Update validation messages and remove dead i18n keys (routeModelLabel)
  • refactor(claude-desktop): align provider form UI with Claude Code
    - Rename field labels: "Gateway Base URL" → "API Endpoint", "Bearer Token" → "API Key"
    - Change layout from 2-column grid to vertical sections matching Claude Code
    - Reuse shared EndpointField component with format-aware amber hint box
    - Replace native <datalist> with vendor-grouped ModelDropdown (OpenCode pattern)
    - Move Fetch/Add buttons to section headers with compact sm styling
    - Extract ModelDropdown to shared module, deduplicate from OpenCodeFormFields
    - Extract renderActionButtons helper to eliminate proxy/direct button duplication
    - Remove dead i18n keys (gatewayBaseUrl, bearerToken) from all 3 locales
  • fix(claude-desktop): remove proxy-stopped status alert
    The route toggle in the top-right corner already communicates proxy
    state; the extra warning banner was redundant and inconsistent with
    other managed apps.
  • refactor(claude-desktop): trim duplication in proxy and switch flows
    - services/proxy.rs: collapse 10 repeated `OpenCode | OpenClaw | Hermes |
      ClaudeDesktop` match arms into `_` fallthroughs.
    - claude_desktop_config.rs: extract a `with_rollback` closure shared by
      apply_provider_to_paths and restore_official_at_paths.
    - useProviderActions.ts: replace the triple-nested ternary picking the
      switch-success toast message with a flat let/if/else block.
    
    Net -36 lines. No behavior change; cargo test and pnpm typecheck pass.
  • feat(claude-desktop): add 3P provider switching with proxy gateway
    Adds a new ClaudeDesktop AppType that writes Claude Desktop's third-party
    inference profile under configLibrary/, sharing _meta.json with other
    launchers (Ollama-compatible) so cc-switch can coexist with them.
    
    Two switch modes:
    - direct: provider already exposes claude-* / anthropic/claude-* model
      ids on Anthropic Messages, Claude Desktop connects to it directly.
    - proxy: cc-switch's local proxy acts as the inference gateway,
      presenting only claude-* route names to Claude Desktop and mapping
      them to real upstream models. Required after Anthropic restricted
      Claude Desktop to claude-family ids.
    
    Backend:
    - New module claude_desktop_config with snapshot/rollback, official seed
      bypass, /claude-desktop/v1/{models,messages} routes, and a single
      source of truth for default proxy routes.
    - Gateway token persisted in SQLite, validated on every proxied request.
    - get_claude_desktop_status surfaces drift signals (stale models,
      missing routes, proxy stopped, base URL mismatch, missing token).
    
    Frontend:
    - Slim ClaudeDesktopProviderForm independent from ProviderForm,
      controlled by a top-level appId guard.
    - ProviderList banner consumes the status query (5s polling) and
      renders actionable diagnostics.
    - ClaudeDesktopRouteToggle in the header to start/stop the local
      gateway without touching takeover state.
    - Three-locale i18n synchronised.
  • fix(proxy): reuse pooled HTTPS connections for non-Anthropic backends
    The hyper raw-write path preserves original header casing but rebuilds
    TCP+TLS on every request — there is no connection pool — which was the
    root cause of slow reverse-proxy throughput.
    
    Only Anthropic-native requests actually need exact header-case
    preservation. Route OpenAI/Copilot/Codex/Gemini/codex_oauth requests
    through the pooled reqwest client (pool_max_idle_per_host=10,
    tcp_keepalive=60s) instead, so warm connections get reused.
    
    Streaming requests get a precise first-byte timeout via
    tokio::time::timeout around reqwest's send() (which resolves on
    response headers), with the body phase handed off to response_processor.
    The streaming-detection helper now also covers Gemini SSE endpoints
    and Accept: text/event-stream, not just body.stream.
  • Fix Codex startup live import duplication (#2590)
    * Fix Codex startup live import duplication
    
    * Fix: Prevent duplicate Codex default provider on restart & add startup import tests
  • feat: return reasoning_content with tool_calls for DeepSeek models (#2543)
    * feat: return reasoning_content with tool_calls for DeepSeek models
    
    * fix: correct reasoning_content handling for DeepSeek tool_calls
    
    * test: cover DeepSeek reasoning content round trip
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • fix(ci): pin Claude review checkout to PR head sha
    Prevents `git fetch origin pull/<N>/head:main` failures on fork PRs
    whose head branch is also named `main` — using a SHA puts the runner
    in detached HEAD so the main ref is free for the action's internal fetch.
  • refactor(theme): drop unused MouseEvent param from setTheme
    Now that the view transition animation is gone, setTheme no longer
    needs click coordinates. Reduce the API surface to (theme: Theme) =>
    void and simplify the call sites in mode-toggle and ThemeSettings.
  • refactor(theme): remove circular reveal animation for theme switching
    The View Transitions API used here crashes WebKitGTK with SIGSEGV on
    Linux. Rather than gating document.startViewTransition per platform
    (see PR #2502), drop the animation entirely — it's a low-value visual
    flourish on a low-frequency action that doesn't justify a permanent
    platform branch.
    
    Removes the ::view-transition-* CSS block and the coordinate plumbing
    in setTheme. The optional event parameter is kept on the API surface
    to keep call sites compiling; they'll be cleaned up in a follow-up
    commit.
  • fix(ci): drop --max-turns 5 from Claude review args
    Cap was too tight — review tasks need 8-15 turns to read files, analyze,
    and post results. The first run after the previous prompt change failed
    with error_max_turns at turn 6. The disallowedTools list already keeps
    the agent in read-only mode, so an explicit turn cap is redundant.
  • chore(ci): tune Claude review prompt to reduce nitpick noise
    Previous prompt produced 5-10 findings per PR including dead-code/style
    nits mixed with real bugs. New prompt adds severity tiering, an 80
    confidence threshold, an explicit anti-pattern list, a 5-nit cap, and
    permits zero-comment LGTM as a valid outcome. Calibration follows
    Anthropic Code Review docs and the claude-plugins-official prompt.
  • chore(deps): bump actions/stale from 9 to 10 (#2520)
    Bumps [actions/stale](https://github.com/actions/stale) from 9 to 10.
    - [Release notes](https://github.com/actions/stale/releases)
    - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
    - [Commits](https://github.com/actions/stale/compare/v9...v10)
    
    ---
    updated-dependencies:
    - dependency-name: actions/stale
      dependency-version: '10'
      dependency-type: direct:production
      update-type: version-update:semver-major
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
  • chore(deps): bump softprops/action-gh-release from 2 to 3 (#2519)
    Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2 to 3.
    - [Release notes](https://github.com/softprops/action-gh-release/releases)
    - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
    - [Commits](https://github.com/softprops/action-gh-release/compare/v2...v3)
    
    ---
    updated-dependencies:
    - dependency-name: softprops/action-gh-release
      dependency-version: '3'
      dependency-type: direct:production
      update-type: version-update:semver-major
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
  • chore(deps): bump pnpm/action-setup from 5 to 6 (#2518)
    Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 5 to 6.
    - [Release notes](https://github.com/pnpm/action-setup/releases)
    - [Commits](https://github.com/pnpm/action-setup/compare/v5...v6)
    
    ---
    updated-dependencies:
    - dependency-name: pnpm/action-setup
      dependency-version: '6'
      dependency-type: direct:production
      update-type: version-update:semver-major
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
  • chore(deps): bump actions/checkout from 4 to 6 (#2517)
    Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
    - [Release notes](https://github.com/actions/checkout/releases)
    - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
    - [Commits](https://github.com/actions/checkout/compare/v4...v6)
    
    ---
    updated-dependencies:
    - dependency-name: actions/checkout
      dependency-version: '6'
      dependency-type: direct:production
      update-type: version-update:semver-major
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
  • chore(ci): add Claude Code Action for review-only @claude mentions
    - Restrict to repo collaborators via author_association check
    - Use OAuth token for subscription billing
    - Disable Edit/Write tools and set contents:read permission to enforce review-only
  • fix(proxy): derive Claude auth strategy from ANTHROPIC env var name
    Anthropic SDK assigns distinct semantics to the two env vars:
    
    - ANTHROPIC_API_KEY    -> x-api-key
    - ANTHROPIC_AUTH_TOKEN -> Authorization: Bearer
    
    The Claude adapter previously collapsed both into AuthStrategy::Anthropic
    and then emitted Authorization: Bearer regardless, breaking strict
    Anthropic-protocol endpoints (Anthropic official, Cloudflare AI Gateway,
    OpenCode Go, DashScope) and silently overriding the user's intended auth
    scheme.
    
    - claude::extract_auth: infer strategy from env var name
      (ANTHROPIC_AUTH_TOKEN -> ClaudeAuth, ANTHROPIC_API_KEY -> Anthropic),
      matching the precedence already used by extract_key.
    - claude::get_auth_headers: split the Anthropic arm so it emits
      x-api-key, while ClaudeAuth and Bearer continue to use Bearer.
    - stream_check: reuse ClaudeAdapter::get_auth_headers as the single
      source of truth, replacing the prior "always Bearer + maybe x-api-key"
      double injection that produced auth conflicts and false-negative
      health checks.
    - Cover each strategy -> header mapping and env-var precedence with
      new unit tests in claude.rs.
    
    Refs #2368, #2380
  • fix(proxy): strip leading billing header from system content (#2350)
    Claude Code injects a dynamic `x-anthropic-billing-header` line at the
    start of `system` content. Its rotating `cch=` token was forwarded into
    OpenAI Responses `instructions` and Chat system messages, which broke
    upstream prefix prompt cache reuse — a stable ~95k-token prefix was
    getting re-charged on every request.
    
    Strip only the leading occurrence in both anthropic_to_openai and
    anthropic_to_responses; later occurrences are preserved so user-authored
    prompt text containing the same string is not lost.
  • chore(usage): drop Hermes Agent tracking integration
    Hermes aggregates all in-process API calls into a single sessions row
    with the `model` field locked to the initial model, so the usage
    dashboard cannot cleanly surface per-call billing context. Two rounds
    of UI workarounds (raw mapping, then `<model> @ <host>` display) did
    not resolve the user-facing confusion, so the whole tracking
    integration is dropped for now.
    
    Removes session_usage_hermes service (and its 17 tests), sync wiring
    in commands/usage.rs and lib.rs, _hermes_session/hermes_session
    entries in usage_stats SQL (provider_name_coalesce CASE and
    effective_usage_log_filter IN clause), frontend Tab/banner/dropdown/
    icon entries, and four i18n keys per locale.
    
    Hermes app integration outside usage tracking (proxy routing,
    session manager, config) is preserved. Pre-existing hermes rows in
    proxy_request_logs are left as orphans — filtered out by the
    updated SQL and never surfaced in the UI.
  • feat: persist Tauri window state (#2377)
    Add the window-state plugin and explicitly save size and position across app exit, restart, and lightweight-mode transitions.
  • feat(providers): add Baidu Qianfan Coding Plan for Claude Code (#2322)
    * feat(providers): add baidu qianfan coding plan presets
    
    * refactor(providers): align qianfan presets with existing format
    
    * chore(providers): narrow qianfan coding plan scope
  • fix(coding-plan): correct zhipu weekly tier name by reset time (#2420)
    Zhipu's `data.limits[]` returns 1 entry for legacy plans (subscribed
    before 2026-02-12) and 2 entries for current plans. Previously every
    TOKENS_LIMIT entry was hardcoded as `five_hour`, so the weekly bucket
    was rendered with the 5-hour i18n label.
    
    Sort TOKENS_LIMIT entries by nextResetTime ascending and assign
    `five_hour` to index 0, `weekly_limit` to index 1. Legacy plans
    naturally degrade to a single five_hour tier.
    
    Also harden the parser: case-insensitive type match (defends against
    upstream casing changes), reuse TIER_FIVE_HOUR/TIER_WEEKLY_LIMIT
    constants, and add 8 unit tests covering both plan shapes plus
    defensive edge cases.
  • fix(dashscope): enhance usage parsing robustness to prevent VSCode cr… (#2425)
    * fix(dashscope): enhance usage parsing robustness to prevent VSCode crashes
    
    Enhanced build_anthropic_usage_from_responses() to handle null, missing, empty,
    and partial usage fields gracefully. This prevents VSCode Extension crashes with
    "Cannot read properties of null (reading 'output_tokens')" when connecting to
    DashScope (Alibaba Cloud Bailian) models.
    
    Changes:
    - Added defensive null checks and empty object detection
    - Implemented OpenAI field name fallbacks (prompt_tokens/completion_tokens)
    - Added comprehensive logging for malformed usage scenarios
    - Fixed streaming SSE event handlers with null-safe usage access
    - Preserved cache token fields even when input/output tokens are missing
    
    This ensures the proxy never crashes on malformed Responses API usage objects,
    returning valid Anthropic-compatible usage structures (input_tokens/output_tokens)
    in all cases.
    
    * fix(proxy): tighten Responses API usage fix per review
    
    - Drop redundant fallback in streaming.rs Chat Completions path; the
      existing if-let-Some guard already prevents usage:null, so the extra
      layer was dead code and caused a fmt-breaking indentation issue.
    - Demote partial-usage warn to debug. Streaming chunks legitimately
      arrive with partial token counts and the warn-level log was noisy.
    - Rewrite CHANGELOG entry: reference #2422, broaden scope from
      DashScope-only to all api_format=openai_responses users (Codex OAuth
      is the strongest signal; DashScope compatible-mode/v1/responses is
      the original report).
    - cargo fmt to clear 12 formatting differences vs main.
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • fix(config): sort JSON keys alphabetically for deterministic output (#2469)
    * fix(config): sort JSON keys alphabetically for deterministic output
    
    Ensures settings.json keys are written in sorted order, preventing
    non-deterministic git diffs when switching configs.
    
    * test(config): add unit tests for sort_json_keys and fix formatting
    
    Cover top-level sort, nested recursion, array order preservation,
    primitive pass-through, empty collections, and the core determinism
    guarantee (different insertion orders must yield identical output).
    
    Also fix line-length in write_json_file flagged by `cargo fmt --check`.
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • Fix log message for session usage codex (#2473)
    * Fix log message for session usage codex
    
    * Fix comments in session_usage_codex.rs
  • feat: support launch warp and execute session (#2466)
    * feat: support launch warp and execute session
    
    Signed-off-by: tison <wander4096@gmail.com>
    
    * other wires
    
    Signed-off-by: tison <wander4096@gmail.com>
    
    * for launch with provider
    
    Signed-off-by: tison <wander4096@gmail.com>
    
    * fixup indirection
    
    Signed-off-by: tison <wander4096@gmail.com>
    
    * clippy
    
    Signed-off-by: tison <wander4096@gmail.com>
    
    * address comments
    
    Signed-off-by: tison <wander4096@gmail.com>
    
    ---------
    
    Signed-off-by: tison <wander4096@gmail.com>
  • 修复 Codex 切换供应商后历史记录变化 (#2349)
    * Keep Codex history stable across provider switches
    
    * Restore template Codex provider id when backfilling live config
    
    Backfill writes the current Codex live config back to the previous
    provider's stored template after a switch. Because the live file now
    carries a normalized stable model_provider id, the previous provider's
    template would lose its own provider-specific id (and any matching
    [profiles.*] references) on every subsequent switch.
    
    Reverse the normalization at backfill time by rewriting model_provider,
    the active model_providers section, and matching profile references back
    to the template's original id.
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • docs(readme): add PatewayAI sponsor across zh/en/ja
    Also clean up a dangling <tr> tag in README_JA.md.
  • feat(usage): add Hermes Agent tracking + fix zero-cost bug + perf
    Hermes:
    - Parse ~/.hermes/state.db sessions (incl. profiles/*/state.db) into
      proxy_request_logs with data_source='hermes_session', WAL-aware
      incremental sync, Hermes-reported cost preferred over model_pricing
      fallback
    
    Zero-cost bug (dashboard showed \$0 totals):
    - GPT-5.5 family default pricing (~83% of affected rows used GPT-5.5)
    - find_model_pricing_row: ASCII-lowercase normalization so
      "OpenAI/GPT-5.5@HIGH" matches seeded "gpt-5.5"
    - Startup cost backfill in async task: scan rows where total_cost <= 0
      but tokens > 0, recompute via model_pricing in a single transaction
    
    Performance:
    - Add (app_type, created_at DESC) covering index for dashboard range
      queries
    - Add expression index on COALESCE(data_source, 'proxy') so dedup EXISTS
      subqueries use index lookup instead of full scan; drop superseded
      idx_request_logs_dedup_lookup
    
    Refactor:
    - row_to_request_log_detail helper (3-way de-dup; fixes cost_multiplier
      \"1\" vs \"1.0\" drift between callers)
    - Promote get_sync_state/update_sync_state to shared session_usage
      module (4 copies -> 1)
    - run_step helper in lib.rs replaces 9 if-let-Err blocks
    - maybe_backfill_log_costs returns bool to skip duplicate total_cost
      parsing in caller
  • fix(usage): prevent double-counting between proxy and session-log sources
    Proxy writes and session-log sync wrote to proxy_request_logs with
    mismatched request_ids: only Claude on a native Anthropic backend used the
    shared `session:{message_id}` key. Codex/Gemini and Claude-through-OpenAI
    providers always produced distinct ids, so primary-key dedup never fired
    and every real request was recorded twice.
    
    Adds a 7-dim fingerprint dedup (app_type, 4 token counts, 2xx status,
    model with case-insensitive match, ±10min window) wired into three layers:
    
    - Write path: should_skip_session_insert() blocks duplicate session rows
      before INSERT, unifying the previously-divergent Claude/Codex/Gemini
      paths through a single DedupKey-based helper.
    - Read path: effective_usage_log_filter() excludes already-covered session
      rows from every aggregation query.
    - Rollup path: same filter applied so usage_daily_rollups never absorbs
      duplicates.
    
    Also adds a covering index (idx_request_logs_dedup_lookup) so the EXISTS
    subquery stays index-only, and a transform.rs regression test that pins
    openai_to_anthropic id preservation - the missing piece that lets
    Claude+OpenAI-compatible providers reuse the session: id scheme.
  • chore(kimi): update Kimi For Coding website URL to /code/docs/
    Sync the preset's websiteUrl from the legacy /coding/docs/ path to
    the current /code/docs/ path across all four app presets (claude,
    hermes, openclaw, opencode).
  • fix(balance): show USD on SiliconFlow international site (was CNY)
    The query_siliconflow function received an is_cn flag that only switched
    the request domain (.cn vs .com) but the response builder hardcoded
    unit="CNY" for both sites. International users at api.siliconflow.com
    saw their USD balance labelled as CNY. Now unit and plan_name follow
    is_cn, so the EN site shows USD and "SiliconFlow (EN)".
  • chore(codex): hide 1M context window toggle in provider edit form
    Codex models no longer accept model_context_window=1000000, so the
    toggle and its paired auto-compact-limit input are commented out in
    the provider edit form. State hooks, helper imports, and i18n keys
    are preserved so the UI can be restored in one batch if upstream
    support returns. The TOML editor remains visible, allowing manual
    edits if needed.