Commit Graph

1654 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)".