Commit Graph

85 Commits

  • [codex] Preserve cache_control when merging system prompts (#1946)
    * Preserve cache hints when collapsing system prompts
    
    Strict OpenAI-compatible chat backends still need fragmented Claude\nsystem prompts collapsed into one leading system message, but that\nnormalization should not silently drop stable cache hints. Preserve\nmessage-level cache_control when the merged system fragments agree,\nand fall back to omitting it when the fragments conflict.\n\nConstraint: Must keep single-system normalization for Nvidia/Qwen-style chat backends\nRejected: Always copy the first cache_control | could misrepresent conflicting cache boundaries\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: If system prompt merging changes again, preserve cache_control whenever the merged metadata is unambiguous\nTested: cargo test proxy::providers::transform --manifest-path src-tauri/Cargo.toml\nNot-tested: End-to-end prompt caching behavior against cache-aware OpenAI-compatible upstreams\nRelated: #1881
    
    * Tighten cache hint inheritance for merged system prompts
    
    The follow-up cache hint fix still treated mixed present/absent\ncache_control across fragmented system prompts as inheritable, which\nexpanded the cache scope after prompt collapse. Treat that mix as\nambiguous and only preserve cache_control when every merged fragment\nexplicitly agrees on the same value.\n\nConstraint: Must preserve strict-backend system prompt normalization from #1942\nRejected: Inherit first present cache_control | widens cache scope when later fragments were intentionally uncached\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Any future merged-system cache hint logic should treat missing cache_control as semantically significant\nTested: cargo test proxy::providers::transform --manifest-path src-tauri/Cargo.toml\nNot-tested: End-to-end upstream caching behavior against cache-aware relays\nRelated: #1881\nRelated: #1946
    
    * Keep cache-control merge regressions easy to review
    
    Reflow the two long cache-control regression assertions in transform.rs so the neighboring merge cases stay rustfmt-aligned and easier to scan.
    
    This keeps the preserved code change separate from the untracked Markdown design notes the user did not want committed.
    
    Constraint: Exclude Markdown design files from the commit while preserving the local code change
    Rejected: Include docs in the same commit | user explicitly asked to leave Markdown files out
    Confidence: high
    Scope-risk: narrow
    Reversibility: clean
    Directive: Treat this as a readability-only test change; do not infer runtime behavior changes from it
    Tested: cargo test --manifest-path src-tauri/Cargo.toml test_anthropic_to_openai_drops_ --lib
    Tested: cargo check --manifest-path src-tauri/Cargo.toml --tests
    Tested: pnpm format:check
    Tested: pnpm typecheck
    Not-tested: Full application integration and manual flows
  • Stop sending prompt cache keys on Claude chat conversions (#2003)
    Responses conversions still use promptCacheKey, but chat completions now stay a pure shape transform. This keeps Claude -> chat requests aligned with providers that do not understand the field and keeps stream checks consistent with production behavior.
    
    Constraint: Issue #1919 requires removing prompt_cache_key from Claude -> OpenAI Chat requests
    Rejected: Add a runtime toggle for chat injection | requested behavior is unconditional removal
    Confidence: high
    Scope-risk: narrow
    Reversibility: clean
    Directive: Keep promptCacheKey limited to Claude -> Responses conversions unless a provider-specific contract is proven
    Tested: cargo test anthropic_to_openai
    Tested: cargo test anthropic_to_responses_with_cache_key
    Tested: cargo test transform_claude_request_for_api_format_responses
    Not-tested: Full src-tauri test suite
    Related: #1919
  • fix: map adaptive thinking to xhigh reasoning_effort instead of high
    When thinking.type is "adaptive" (Claude's maximum thinking mode) and
    output_config.effort is absent, resolve_reasoning_effort() incorrectly
    mapped it to "high" instead of "xhigh" in OpenAI format conversions.
  • feat: add Codex OAuth (ChatGPT Plus/Pro) reverse proxy support
    Adds a new managed OAuth provider that lets Claude Code route requests
    through a user's ChatGPT Plus/Pro subscription via the chatgpt.com
    backend-api/codex endpoint.
    
    - CodexOAuthManager: OpenAI Device Code flow with multi-account support,
      JWT-based account identification, and automatic access_token refresh.
    - Reuses the generic managed-auth command surface (auth_start_login,
      auth_poll_for_account, etc.) via provider dispatch in commands/auth.rs.
    - ClaudeAdapter detects codex_oauth providers, forces the base URL to
      the ChatGPT backend, pins api_format to openai_responses, and emits
      Authorization + originator headers; the forwarder injects the dynamic
      access_token and ChatGPT-Account-Id per request.
    - transform_responses gains an is_codex_oauth path that aligns the body
      with OpenAI's codex-rs ResponsesApiRequest contract: sets store:false,
      appends reasoning.encrypted_content to include, strips max_output_tokens
      / temperature / top_p, injects default instructions/tools/parallel_tool_calls,
      and forces stream:true. Covered by 9 new unit tests plus regression
      guards for the non-Codex path.
    - Stream check reuses the same transform flag so detection matches the
      production request shape.
    - Frontend adds CodexOAuthSection + useCodexOauth hook, integrates it
      into ClaudeFormFields / ProviderForm / AuthCenterPanel, ships a new
      "Codex (ChatGPT Plus/Pro)" preset, and adds zh/en/ja i18n strings.
  • fix: handle UTF-8 multi-byte characters split across stream chunk boundaries (#1923)
    * fix: handle UTF-8 multi-byte characters split across stream chunk boundaries
    
    Replace String::from_utf8_lossy with append_utf8_safe in all four SSE
    streaming paths. When a multi-byte UTF-8 character (e.g. Chinese, emoji)
    is split across TCP chunk boundaries, from_utf8_lossy silently replaces
    the incomplete halves with U+FFFD (�). This caused intermittent garbled
    output in Claude Code when using the Copilot reverse proxy, because the
    format conversion streams reconstruct SSE events from the corrupted buffer.
    
    The new append_utf8_safe function preserves incomplete trailing bytes in
    a remainder buffer and merges them with the next chunk before decoding,
    ensuring characters are never split during UTF-8 conversion.
    
    Fixes: intermittent U+FFFD replacement characters in Claude Code output
    via Copilot proxy (not reproducible with direct Copilot connections like
    opencode because they pass through raw bytes without format conversion).
    
    * style: fix cargo fmt formatting in UTF-8 boundary tests
    
    ---------
    
    Co-authored-by: Cod1ng <codingts@gmail.com>
    Co-authored-by: encodets <encodets@gmail.com>
  • Normalize fragmented system prompts for strict chat backends (#1942)
    Some OpenAI-compatible chat providers reject requests when Claude-side\nsystem fragments arrive as multiple system messages. Normalize the\nconverted OpenAI chat payload so system content becomes a single\nleading system message while leaving the rest of the message stream\nunchanged.\n\nConstraint: Nvidia/Qwen-style chat completions require a single leading system prompt\nRejected: Reorder system messages only | still leaves fragmented system prompts for strict backends\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep OpenAI chat system prompts normalized unless a provider explicitly requires fragmented system messages\nTested: cargo test proxy::providers::transform --manifest-path src-tauri/Cargo.toml\nNot-tested: Full end-to-end proxy capture against Nvidia upstream in this session\nRelated: #1881
  • feat: add Copilot optimizer to reduce premium interaction consumption
    Implement request classification, tool result merging, compact detection,
    deterministic request IDs, and warmup downgrade for Copilot proxy.
    
    The root cause was x-initiator being hardcoded to "user", making Copilot
    count every API request (including tool callbacks and agent continuations)
    as a separate premium interaction. The optimizer dynamically classifies
    requests as "user" or "agent" based on message content analysis.
    
    Closes #1813
  • fix(copilot): 修复 GitHub Copilot 认证和代理问题 (#1854)
    * fix(copilot): 修复 GitHub Copilot 400 认证错误
    
    问题:使用 GitHub Copilot provider 时报错 400 bad request
    
    根因:与 copilot-api 项目对比发现多处差异
    
    修复内容:
    - 更新版本号 0.26.7 到 0.38.2
    - 更新 API 版本 2025-04-01 到 2025-10-01
    - 添加缺失的关键 headers
    - 修正 openai-intent 值
    - 添加动态 API endpoint 支持
    - 同步更新 stream_check.rs headers
    
    Closes #1777
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * fix: flush stream after write_all in hyper_client proxy
    
    Add explicit flush() calls after write_all() for TLS stream, plain TCP
    stream, and CONNECT tunnel requests to ensure buffered data is sent
    immediately, preventing connection hangs in Copilot auth header flow.
    
    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
    
    * 修复登录时的剪切板在mac与linux端可能没复制验证码
    
    * fix: flush stream after write_all in hyper_client proxy
    
    Add explicit flush() calls after write_all() for TLS stream, plain TCP
    stream, and CONNECT tunnel requests to ensure buffered data is sent
    immediately, preventing connection hangs in Copilot auth header flow.
    
    * 修复登录时的剪切板在mac与linux端可能没复制验证码
    
    * 1、修复不同类型的个人商业等不同类型的copilot账号问题
    2、将验证码复制改为异步操作
    
    * fix: address PR review comments for Copilot auth                                                      │
    │                                                                                                                      │
    │ - Fix clipboard blocking by using spawn_blocking for arboard ops                                                     │
    │ - Implement dynamic endpoint routing for enterprise Copilot users                                                    │
    │ - Add api_endpoints cache cleanup in remove_account() and clear_auth()                                               │
    │ - Change API endpoint log level from info to debug                                                                   │
    │ - Fix clear_auth() to continue cleanup even if file deletion fails                                                   │
    │ - Add 9 unit tests for Copilot detection and api_endpoints cachin
    
    * style: fix cargo fmt formatting
    
    * Fix Copilot dynamic endpoint handling
    
    * fix: restore clear_auth() memory-first cleanup order and fix cache leaks
    
    - Restore clear_auth() to clean memory state before deleting the storage
      file. The previous order (file deletion first) caused a regression where
      users could get stuck in a "cannot log out" state if file removal failed.
    
    - Add missing copilot_models.clear() in clear_auth() — this cache was
      cleaned in remove_account() but never in the full clear path.
    
    - Add endpoint_locks cleanup in both remove_account() and clear_auth()
      to prevent minor in-process memory leaks.
    
    - Update test to assert the correct behavior: memory should be cleaned
      even when file deletion fails.
    
    ---------
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    Co-authored-by: 周梦泽 <mengze.zhou@dafeng-tech.com>
    Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
    Co-authored-by: Jason <farion1231@gmail.com>
  • fix(test): resolve HOME env race condition in parallel tests
    - Use get_home_dir() instead of dirs::home_dir() in get_opencode_dir()
      and get_openclaw_dir() to respect CC_SWITCH_TEST_HOME override
    - Add CC_SWITCH_TEST_HOME to all TempHome implementations
    - Add #[serial] to all with_test_home tests to share serialization
      with other env-mutating tests
    - Remove --test-threads=1 workaround from CI
  • fix(proxy): serialize per-app provider switches to prevent state corruption
    Concurrent failover switches for the same app could cause is_current,
    local settings, and Live backup to point at different providers.
    
    - Add SwitchLockManager with per-app mutexes (different apps still parallel)
    - Unify scattered switch logic into ProxyService::hot_switch_provider
    - Fix TOCTOU in set_current_provider via mutate_settings
    - Add logical_target_changed to skip redundant UI refreshes
    - Add tests for serialization and restore-waits-for-switch scenarios
  • feat(terminal): add directory picker before launching Claude terminal (#1752)
    * Add directory picker before launching Claude terminal
    
    * fix(terminal): preserve cwd path and strip Windows verbatim prefix
    
    - Stop trimming non-empty paths so directories with leading/trailing
      spaces on Unix are handled correctly
    - Strip \\?\ extended-length prefix from canonicalized paths on Windows
      to prevent batch script cd failures
    
    * fix(terminal): restore UNC paths when stripping Windows verbatim prefix
    
    Handle \\?\UNC\server\share form separately from regular \\?\ prefix,
    converting it back to \\server\share so network/WSL directory paths
    remain valid in batch cd commands.
    
    * fix(terminal): use pushd for UNC paths in Windows batch launcher
    
    `cmd.exe` cannot set a UNC path (e.g. `\\wsl$\...`) as the current
    directory via `cd /d`; it errors with "CMD does not support UNC paths
    as current directories". Switch to `pushd` which temporarily maps the
    UNC share to a drive letter.
    
    Rename `build_windows_cd_command` → `build_windows_cwd_command` to
    reflect the broader semantics. Extract `build_windows_cwd_command_str`
    and `is_windows_unc_path` helpers for testability, and add unit tests
    covering drive paths, UNC paths, and batch metacharacter escaping.
    
    Also fix minor style issues: sort mod declarations alphabetically,
    add missing EOF newline in lightweight.rs, add explicit type annotation
    in streaming_responses test, and reformat tray menu builder chain.
  • refactor(proxy): transparent header forwarding via hyper client (#1714)
    * style(frontend): reformat provider forms, constants and hooks
    
    Apply prettier formatting across 5 frontend files. No logic changes.
    
    Changed files:
    - AddProviderDialog.tsx: reformat generic type annotation and callback
    - ClaudeFormFields.tsx: consolidate multi-line useState and Collapsible props
    - CodexConfigSections.tsx: expand single-line React imports to multi-line,
      collapse removeCodexTopLevelField() call
    - constants.ts: merge TemplateType into single line
    - useSkills.ts: expand single-line TanStack Query imports to multi-line,
      reformat uninstallSkill mutationFn chain
    
    * deps(proxy): add hyper ecosystem crates and manual decompression libs
    
    reqwest internally normalizes all header names to lowercase and does not
    preserve insertion order, causing proxied requests to differ from the
    original client requests. To achieve transparent header forwarding with
    original casing and order, introduce lower-level hyper HTTP client libs.
    
    New dependencies:
    - hyper-util 0.1: TokioExecutor + legacy Client with
      preserve_header_case support for HTTP/1.1
    - hyper-rustls 0.27: rustls-based TLS connector for hyper
    - http 1 / http-body 1 / http-body-util 0.1: HTTP type crates for
      hyper 1.x request/response construction
    - flate2 1: manual gzip/deflate decompression (replaces reqwest auto)
    - brotli 7: manual brotli decompression
    
    Changed dependencies:
    - serde_json: enable preserve_order feature to keep JSON field order
    - reqwest: drop gzip feature to prevent reqwest from overriding the
      client's original accept-encoding header
    
    * refactor(proxy): use hyper client for header-case preserving forwarding
    
    Previously the proxy used reqwest for all upstream requests. reqwest
    normalizes header names to lowercase and reorders them internally,
    making proxied requests distinguishable from direct CLI requests.
    Some upstream providers are sensitive to these differences.
    
    This commit replaces reqwest with a hyper-based HTTP client on the
    default (non-proxy) path, achieving wire-level header fidelity:
    
    Server layer (server.rs):
    - Replace axum::serve with a manual hyper HTTP/1.1 accept loop
    - Enable preserve_header_case(true) so incoming header casing is
      captured in a HeaderCaseMap extension on each request
    - Bridge hyper requests to axum Router via tower::Service
    
    New hyper client module (hyper_client.rs):
    - Lazy-initialized hyper-util Client with preserve_header_case
    - ProxyResponse enum wrapping both hyper::Response and reqwest::Response
      behind a unified interface (status, headers, bytes, bytes_stream)
    - send_request() builds requests with ordered HeaderMap + case map
    
    Request handlers (handlers.rs):
    - Switch from (HeaderMap, Json<Value>) extractors to raw
      axum::extract::Request to preserve Extensions (containing the
      HeaderCaseMap from the accept loop)
    - Pass extensions through the forwarding chain
    
    Forwarder (forwarder.rs):
    - Remove HEADER_BLACKLIST array; replace with ordered header iteration
      that preserves original header sequence and casing
    - Build ordered_headers by iterating client headers, skipping only
      auth/host/content-length, and inserting auth headers at the original
      authorization position to maintain order
    - Handle anthropic-beta (ensure claude-code-20250219 tag) and
      anthropic-version (passthrough or default) inline during iteration
    - Remove should_force_identity_encoding() — accept-encoding is now
      transparently forwarded to upstream
    - Use hyper client by default; fall back to reqwest only when an
      HTTP/SOCKS5 proxy tunnel is configured
    
    Provider adapters (adapter.rs, claude.rs, codex.rs, gemini.rs):
    - Replace add_auth_headers(RequestBuilder) -> RequestBuilder with
      get_auth_headers(AuthInfo) -> Vec<(HeaderName, HeaderValue)>
    - Adapters now return header pairs instead of mutating a reqwest builder
    - Claude adapter: merge Anthropic/ClaudeAuth/Bearer into single branch;
      move Copilot fingerprint headers into get_auth_headers
    
    Response processing (response_processor.rs):
    - Add manual decompression (gzip/deflate/brotli via flate2 + brotli)
      for non-streaming responses, since reqwest auto-decompression is now
      disabled to allow accept-encoding passthrough
    - Add compressed-SSE warning log for streaming responses
    - Accept ProxyResponse instead of reqwest::Response
    
    HTTP client (http_client.rs):
    - Disable reqwest auto-decompression (.no_gzip/.no_brotli/.no_deflate)
      on both global and per-provider clients
    
    Streaming adapters (streaming.rs, streaming_responses.rs):
    - Generalize stream error type from reqwest::Error to generic E: Error
    
    Misc:
    - log_codes.rs: add SRV-005 (ACCEPT_ERR) and SRV-006 (CONN_ERR)
    - stream_check.rs: reformat copilot header lines
    - transform.rs: fix trailing whitespace alignment
    
    * fix(lint): resolve 35 clippy warnings across Rust codebase
    
    Fix all clippy warnings reported by `cargo clippy --lib`:
    
    - codex_config.rs: fix doc_overindented_list_items (3 spaces -> 2)
    - commands/copilot.rs: inline format args in 2 log::error! calls
    - commands/provider.rs: inline format args in 3 map_err closures
    - proxy/hyper_client.rs: inline format arg in log::debug! call
    - proxy/providers/copilot_auth.rs: inline format args in 16 locations
      (log macros, format! in headers, error constructors)
    - proxy/thinking_optimizer.rs: inline format args in 2 log::info! calls
    - services/skill.rs: inline format args in log::debug! call
    - services/webdav_sync.rs: inline format args in 6 format! calls
      (version compat messages, download limit messages)
    - services/webdav_sync/archive.rs: inline format args in 2 format! calls
    - session_manager/providers/opencode.rs: inline format args in
      source_path format!
    
    All fixes use the clippy::uninlined_format_args suggestion pattern:
      format!("msg: {}", var)  ->  format!("msg: {var}")
    
    * deps(proxy): add raw HTTP write and native TLS cert dependencies
    
    Add crates required for the raw TCP/TLS write path that bypasses
    hyper's header encoder to preserve original header name casing:
    
    - httparse: parse raw TCP peek bytes to capture header casings
    - tokio-rustls + rustls: direct TLS connections for raw write path
    - webpki-roots: Mozilla CA bundle baseline
    - rustls-native-certs: load system keychain CAs (trusts proxy MITM
      certificates from Clash, mitmproxy, etc.)
    
    * fix(proxy): address code review feedback on response handling
    
    Fixes from PR #1714 code review:
    
    - Extract `read_decoded_body()` and `strip_entity_headers_for_rebuilt_body()`
      in response_processor to properly clean content-encoding/content-length
      headers after decompression
    - Reuse `read_decoded_body()` in handlers.rs for Claude transform path,
      ensuring compressed responses are decoded before format conversion
    - Make `build_proxy_url_from_config()` public so forwarder can pass proxy
      URL to the hyper raw write path
    - Add `has_system_proxy_env()` utility with test coverage
    - Add 50ms backoff after accept() failures in server.rs to prevent
      tight-loop CPU spin on transient socket errors
    
    * feat(proxy): implement raw TCP/TLS write with HTTP CONNECT tunnel
    
    Rewrite hyper_client with a two-tier strategy for header case preservation:
    
    Primary path (raw write):
    - Peek raw TCP bytes in server.rs to capture OriginalHeaderCases before
      hyper lowercases them
    - Build raw HTTP/1.1 request bytes with exact original header name casing
    - Write directly to TLS stream, then use WriteFilter to let hyper parse
      the response while discarding its duplicate request writes
    - Support HTTP CONNECT tunneling through upstream proxies, so header case
      is preserved even when a proxy (Clash, V2Ray) is configured
    
    Fallback path (hyper-util Client):
    - Used when OriginalHeaderCases is empty or raw write fails
    - Configured with title_case_headers(true) for best-effort casing
    
    TLS improvements:
    - Load native system certificates alongside webpki roots so proxy MITM
      CAs (installed in system keychain) are trusted through CONNECT tunnels
    
    Key types added:
    - OriginalHeaderCases: maps lowercase name → original wire-casing bytes
    - WriteFilter<S>: AsyncRead+AsyncWrite wrapper that discards writes
    - connect_via_proxy(): HTTP CONNECT tunnel establishment
    - ExtensionDebugMarker: diagnostic marker for extension chain debugging
    
    * refactor(proxy): route requests through hyper with proxy-aware forwarding
    
    Rework forwarder request dispatch to always prefer the hyper raw write
    path (header case preservation) over reqwest:
    
    Request routing:
    - HTTP/HTTPS proxy: hyper raw write through CONNECT tunnel (case preserved)
    - SOCKS5 proxy: reqwest fallback (CONNECT not supported for SOCKS5)
    - No proxy: hyper raw write direct connection
    
    Header handling improvements:
    - Replace host header in-place at original position instead of
      skip-and-append, preserving client's header ordering
    - Preserve client's original accept-encoding for transparent passthrough;
      only force identity encoding when transform path needs decompression
    - Add should_force_identity_encoding() to centralize the decision
    - Remove hardcoded 'br, gzip, deflate' override that masked client values
    
    Proxy URL resolution (priority order):
    1. Provider-specific proxy config (if enabled)
    2. Global proxy URL configured in CC Switch
    3. Direct connection (no proxy)
    
    * chore(proxy): remove dead code, redundant tests and debug scaffolding
    
    - Inline should_force_identity_encoding() (was just `needs_transform`)
      and delete its 5 test cases
    - Remove ExtensionDebugMarker diagnostic type
    - Remove unused has_system_proxy_env() and its test
    - Remove strip_entity_headers test
    - Simplify hyper path: remove redundant is_socks_proxy ternary
    - Update hyper_client module doc to reflect CONNECT tunnel support
    
    * fix(proxy): block direct-connect fallback and complete CONNECT tunnel support
    
    * feat(hooks): improve proxy requirement warnings with specific reasons
    
    - Remove redundant OpenAI format hint toast messages
    - Add detailed reason detection for proxy requirements (OpenAI Chat, OpenAI Responses, full URL mode)
    - Update i18n files with new reason-specific keys
    
    * style(*): format code with prettier
    
    - Remove extra whitespace in http_client.rs
    - Fix formatting issues in useProviderActions.ts
    
    * fix(proxy): post-merge fixes for forward return type and clippy warnings
    
    - Restore forward() return type to (ProxyResponse, Option<String>)
      to pass claude_api_format through to callers
    - Inline format args in log::warn! macro (clippy::uninlined_format_args)
    - Suppress too_many_arguments for check_claude_stream
    
    * refactor(proxy): preserve original header wire order and add non-streaming body timeout
    
    - Rewrite build_raw_request to emit headers in original
      client-sent sequence instead of hash-map order
    - Remove unused OriginalHeaderCases::get_all method
    - Add body_timeout to read_decoded_body to prevent
      requests hanging when upstream stalls after headers
  • fix: 修复 Copilot 作为 Claude 时 OpenAI 模型的 Responses 分流 (#1735)
    * fix: route copilot claude openai models to responses
    
    * fix(i18n): add copilotProxyHint translation key for all locales
    
    The copilotProxyHint message was using inline defaultValue with Chinese
    text, which would show Chinese to English and Japanese users. Added
    proper translation keys in zh/en/ja locale files and removed the
    hardcoded defaultValue fallback.
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • feat(proxy): add full URL mode and refactor endpoint rewriting (#1561)
    * feat(proxy): add full URL mode and refactor endpoint rewriting
    
    - Add `isFullUrl` provider meta to treat base_url as complete API endpoint
    - Remove hardcoded `?beta=true` from Claude adapter, pass through from client
    - Refactor forwarder endpoint rewriting with proper query string handling
    - Block provider switching when proxy is required but not running
    - Add full URL toggle UI in endpoint field with i18n (zh/en/ja)
    
    * refactor(proxy): remove beta query handling
    
    * fix(proxy): strip beta query when rewriting Claude endpoints
    
    * feat(codex): complete full URL support
    
    * refactor(ui): refine full URL endpoint hint
  • fix(proxy): parse SSE fields with optional spaces in streaming handlers (#1664)
    * fix(proxy): handle SSE fields with or without spaces
    
    * refactor(proxy): deduplicate SSE field parsing
  • feat(proxy): resolve reasoning_effort from explicit effort with budget fallback
    Replace map_thinking_to_reasoning_effort() with resolve_reasoning_effort()
    that uses a two-tier priority system:
    
    1. Explicit output_config.effort: low/medium/high map 1:1, max → xhigh
    2. Fallback: thinking.type + budget_tokens thresholds (<4k → low,
       4k-16k → medium, ≥16k → high, adaptive → high)
    
    Both Chat Completions and Responses API paths share the same helper,
    ensuring consistent mapping across all OpenAI-compatible endpoints.
  • fix(copilot): unify request fingerprint across all Copilot API calls
    - Make header constants in copilot_auth.rs public, add COPILOT_INTEGRATION_ID
    - Unify User-Agent across 4 internal methods (eliminate "CC-Switch" leakage) and version string
    - claude.rs add_auth_headers now references shared constants, adds user-agent / api-version / openai-intent
    - forwarder.rs filters all 6 fixed fingerprint headers for Copilot requests to prevent reqwest append duplication
    - stream_check.rs health check pipeline aligned with new fingerprint
  • feat(copilot): add GitHub Copilot reverse proxy support (#930)
    * refactor(toolsearch): replace binary patch with ENABLE_TOOL_SEARCH env var toggle
    
    - Remove toolsearch_patch.rs binary patching mechanism (~590 lines)
      - Delete `toolsearch_patch.rs` and `commands/toolsearch.rs`
      - Remove auto-patch startup logic and command registration from lib.rs
      - Remove `tool_search_bypass` field from settings.rs
      - Remove frontend settings ToggleRow, useSettings hook sync logic, and API methods
      - Clean up zh/en/ja i18n keys (notifications + settings)
    
    - Add ENABLE_TOOL_SEARCH toggle to Claude provider form
      - Add checkbox in CommonConfigEditor.tsx (alongside teammates toggle)
      - When enabled, writes `"env": { "ENABLE_TOOL_SEARCH": "true" }`
      - When disabled, removes the key; takes effect on provider switch
      - Add zh/en/ja i18n key: `claudeConfig.enableToolSearch`
    
    Claude Code 2.1.76+ natively supports this env var, eliminating the need for binary patching.
    
    * feat(claude): add effortLevel high toggle to provider form
    
    - Add "high-effort thinking" checkbox to Claude provider config form
    - When checked, writes `"effortLevel": "high"`; when unchecked, removes the field
    - Add zh/en/ja i18n translations
    
    * refactor(claude): remove deprecated alwaysThinking toggle
    
    - Claude Code now enables extended thinking by default; alwaysThinkingEnabled is a no-op
    - Thinking control is now handled via effortLevel (added in prior commit)
    - Remove state, switch case, and checkbox UI from CommonConfigEditor
    - Clean up alwaysThinking i18n keys across zh/en/ja locales
    
    * feat(opencode): add setCacheKey: true to all provider presets
    
    - Add setCacheKey: true to options in all 33 regular presets
    - Add setCacheKey: true to OPENCODE_DEFAULT_CONFIG for custom providers
    - Exclude 2 OMO presets (Oh My OpenCode / Slim) which have their own config mechanism
    
    Closes #1523
    
    * fix(codex): resolve 1M context window toggle causing MCP editor flicker
    
    - Add localValueRef to short-circuit duplicate CodeMirror updateListener callbacks,
      breaking the React state → CodeMirror → stale onChange → React state feedback loop
    - Use localValueRef.current in handleContextWindowToggle and handleCompactLimitChange
      to avoid stale closure reads
    - Change compact limit input from type="number" to type="text" with inputMode="numeric"
      to remove unnecessary spinner buttons
    
    * feat(codex): add 1M context window toggle utilities and i18n keys
    
    - Add extractCodexTopLevelInt, setCodexTopLevelInt, removeCodexTopLevelField
      TOML helpers in providerConfigUtils.ts
    - Add i18n keys for contextWindow1M, autoCompactLimit in zh/en/ja locales
    
    * feat(claude): collapse model mapping fields by default
    
    - Wrap 5 model mapping inputs in a Collapsible, collapsed by default
    - Auto-expand when any model value is present (including preset-filled)
    - Show hint text when collapsed explaining most users need no config
    - Add zh/en/ja i18n keys for toggle label and collapsed hint
    - Use variant={null} to avoid ghost button hover style clash in dark mode
    
    * feat(claude): merge advanced fields into single collapsible section
    
    - Merge API format, auth field, and model mapping into a unified "Advanced Options" collapsible
    - Extend smart-expand logic to detect non-default values across all advanced fields
    - Preserve model mapping sub-header and hint with a separator line
    - Update zh/en/ja i18n keys (advancedOptionsToggle, advancedOptionsHint, modelMappingLabel, modelMappingHint)
    
    * feat(copilot): add GitHub Copilot reverse proxy support
    
    Add GitHub Copilot as a Claude provider variant with OAuth device code
    authentication and Anthropic ↔ OpenAI format transformation.
    
    Backend:
    - Add CopilotAuthManager for GitHub OAuth device code flow
    - Implement Copilot token auto-refresh (60s before expiry)
    - Persist GitHub token to ~/.cc-switch/copilot_auth.json
    - Add ProviderType::GitHubCopilot and AuthStrategy::GitHubCopilot
    - Modify forwarder to use /chat/completions for Copilot
    - Add Copilot-specific headers (Editor-Version, Editor-Plugin-Version)
    
    Frontend:
    - Add CopilotAuthSection component for OAuth UI
    - Add useCopilotAuth hook for OAuth state management
    - Auto-copy user code to clipboard and open browser
    - Use 8-second polling interval to avoid GitHub rate limits
    - Skip API Key validation for Copilot providers
    - Add GitHub Copilot preset with claude-sonnet-4 model
    
    Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
    
    * fix(copilot): remove is_expired() calls from tests
    
    Remove references to deleted is_expired() method in test code.
    Only is_expiring_soon() is needed for token refresh logic.
    
    Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
    
    * feat(copilot): add real-time model listing from Copilot API
    
    - Add fetch_models() to CopilotAuthManager calling GET /models endpoint
    - Add copilot_get_models Tauri command
    - Add copilotGetModels() frontend API wrapper
    - Modify ClaudeFormFields to show model dropdown for Copilot providers
      - Fetches available models on component mount when isCopilotPreset
      - Groups models by vendor (Anthropic, OpenAI, Google, etc.)
      - Input + dropdown button combo allows both manual entry and selection
      - Non-Copilot providers keep original plain Input behavior
    
    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
    
    * feat(copilot): add usage query integration
    
    - Add Copilot usage API integration (fetch_usage method)
    - Add copilot_get_usage Tauri command
    - Add GitHub Copilot template in usage query modal
    - Unify naming: copilot → github_copilot
    - Add constants management (TEMPLATE_TYPES, PROVIDER_TYPES)
    - Improve error handling with detailed error messages
    - Add database migration (v5 → v6) for template type update
    - Add i18n translations (zh, en, ja)
    - Improve type safety with TemplateType
    - Apply code formatting (cargo fmt, prettier)
    
    * 修复github 登录和注销问题 ,模型选择问题
    
    * feat(copilot): add multi-account support for GitHub Copilot
    
    - Add multi-account storage structure with v1 to v2 migration
    - Add per-account token caching and auto-refresh
    - Add new Tauri commands for account management
    - Integrate account selection in Proxy forwarder
    - Add account selection UI in CopilotAuthSection
    - Save githubAccountId to ProviderMeta
    - Add i18n translations for multi-account features (zh/en/ja)
    
    * 修复用量查询Reset字段出现多余字符
    
    * refactor(auth-binding): introduce generic provider auth binding primitives
    
    - add shared authBinding types in Rust and TypeScript while keeping githubAccountId as a compatibility field\n- resolve Copilot token, models, and usage through provider-bound account lookup instead of only the implicit default account\n- fix the Unix build regression in settings.rs by restoring std::io::Write for write_all()\n- remove the accidental .github ignore entry and drop leftover Copilot form debug logs\n- keep the first migration step non-breaking by writing both authBinding and the legacy githubAccountId field from the form
    
    * refactor(auth-service): add managed auth command surface and explicit default account state
    
    - introduce generic managed auth commands and frontend auth API wrappers for provider-scoped login, status, account listing, removal, logout, and default-account selection\n- store an explicit Copilot default_account_id instead of relying on HashMap iteration order, and use it consistently for fallback token/model/usage resolution\n- sort managed accounts deterministically and surface default-account state to the UI\n- refactor the Copilot form hook to wrap a generic useManagedAuth implementation while preserving the existing component contract\n- add default-account controls to the Copilot auth section and extend Copilot auth status serialization/tests for the new state
    
    * feat(auth-center): add a dedicated settings entrypoint for managed OAuth accounts
    
    - add an Auth Center tab to Settings so managed OAuth accounts are no longer hidden inside individual provider forms\n- introduce a first AuthCenterPanel that hosts GitHub Copilot account management as the initial managed auth provider\n- keep the provider form experience intact while establishing a global account-management surface for future providers such as OpenAI\n- validate that the new settings tab works cleanly with the generic managed auth hook and existing Copilot account controls
    
    * feat(add-provider): expose managed OAuth sources alongside universal providers
    
    - add an OAuth tab to the Add Provider flow so managed auth sources sit beside app-specific and universal providers\n- reuse the new Auth Center panel inside the dialog, keeping account management discoverable during provider creation\n- make the dialog footer adapt to the OAuth tab so account setup does not pretend to create a provider directly\n- align the add-provider UX with the new architecture where OAuth accounts are global assets and providers bind to them later
    
    * fix(auth-reliability): harden managed auth persistence and refresh behavior
    
    - replace direct Copilot auth store writes with private temp-file writes and atomic rename semantics, and document the local token storage limitation\n- add per-account refresh locks plus a double-check path so concurrent requests do not stampede GitHub token refresh\n- surface legacy migration failures through auth status, expose them in the UI, and add translated copy for the new account-state labels\n- stop writing the legacy githubAccountId field from the provider form while keeping compatibility reads in place\n- add logout error recovery and Copilot model-load toasts so auth failures are no longer silently swallowed
    
    * refactor(copilot-detection): prefer provider type before URL fallbacks
    
    - update forwarder endpoint rewriting to treat providerType as the primary GitHub Copilot signal\n- keep githubcopilot.com string matching only as a compatibility fallback for older provider records without providerType\n- reduce one more path where Copilot behavior depended purely on URL heuristics
    
    * fix(copilot-auth): add cancel button to error state in CopilotAuthSection
    
    - 错误状态下仅有"重试"按钮,用户无法退出(如不可恢复的 403 未订阅错误)
    - 新增"取消"按钮,复用已有的 cancelAuth 逻辑重置为 idle 状态
    
    * 修复打包后github账号头像显示异常
    
    * 修复github copilot 来源的模型测试报错
    
    * feat(copilot-preset): add default model presets for GitHub Copilot
    
    - 补充 Copilot 预设的默认模型配置,用户选完预设即可直接使用
    - ANTHROPIC_MODEL: claude-opus-4.6
    - ANTHROPIC_DEFAULT_HAIKU_MODEL: claude-haiku-4.5
    - ANTHROPIC_DEFAULT_SONNET_MODEL: claude-sonnet-4.6
    - ANTHROPIC_DEFAULT_OPUS_MODEL: claude-opus-4.6
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
    Co-authored-by: 周梦泽 <mengze.zhou@dafeng-tech.com>
    Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
  • fix(proxy): enable gzip compression for non-streaming proxy requests
    Non-streaming requests were forced to use `Accept-Encoding: identity`,
    preventing upstream response compression and increasing bandwidth usage.
    
    Now only streaming requests conservatively keep `identity` to avoid
    decompression errors on interrupted SSE streams. Non-streaming requests
    let reqwest auto-negotiate gzip and transparently decompress responses.
  • fix(proxy): use max_completion_tokens for o1/o3 series models (#1451)
    * fix(proxy): use max_completion_tokens for o1/o3 series models
    
    When converting Anthropic requests to OpenAI format for o1/o3 series
    models (like o1-mini, o3-mini), use max_completion_tokens instead of
    max_tokens to avoid unsupported_parameter errors.
    
    Fixes #1448
    
    * fix: revert incorrect o-series max_completion_tokens in Responses API path
    
    Responses API uses max_output_tokens for all models including o-series.
    The o-series max_completion_tokens fix should only apply to Chat Completions API.
    
    ---------
    
    Co-authored-by: Hajen Teowideo <hajen.teowideo@example.com>
    Co-authored-by: Jason Young <44939412+farion1231@users.noreply.github.com>
    Co-authored-by: Jason <farion1231@gmail.com>
  • feat: add usage daily rollups, incremental auto-vacuum, and sync-aware backup
    - Add usage_daily_rollups table (schema v6) to aggregate proxy request
      logs into daily summaries, reducing query overhead for statistics
    - Add rollup_and_prune DAO that aggregates old detail logs (>N days)
      into rollup rows and deletes the originals
    - Update all usage stats queries to UNION detail logs with rollup data
    - Introduce incremental auto-vacuum for SQLite, with startup and
      periodic cleanup of old stream_check_logs and request log rollups
    - Split backup export/import into full vs sync variants: WebDAV sync
      now skips local-only table data (proxy_request_logs,
      stream_check_logs, provider_health, proxy_live_backup,
      usage_daily_rollups) while preserving them on import
    - Add enable_logging guard to skip request log writes when disabled
    - Apply cargo fmt formatting fixes across multiple modules
  • fix: correct OpenAI ChatCompletion to Anthropic Messages streaming conversion
    Rewrite tool call handling in streaming format conversion to properly
    track multiple concurrent tool blocks with independent Anthropic content
    indices. Fix block interleaving (thinking/text/tool_use) with correct
    content_block_start/stop events, buffer tool arguments until both id and
    name are available, and add tool result message conversion in transform.
  • refactor: deduplicate and improve OpenAI Responses API conversion
    - Extract shared map_responses_stop_reason and build_anthropic_usage_from_responses into transform_responses.rs as pub(crate)
    - Align cache token extraction priority: OpenAI nested details as fallback, direct Anthropic fields as override
    - Extract resolve_content_index helper to eliminate 3x copy-paste in streaming_responses.rs
    - Add streaming reasoning/thinking event handlers (response.reasoning.delta/done)
    - Add explanatory comment to transform_response heuristic detection
    - Add openai_responses to api_format doc comment and needs_transform test
    - Add explicit no-op match arms for lifecycle events
    - Add promptCacheKey to TS ProviderMeta type
    - Update toast i18n key to be generic for both OpenAI formats (zh/en/ja)
  • feat: add OpenAI Responses API format conversion (api_format = "openai_responses")
    Support Anthropic ↔ OpenAI Responses API format conversion alongside existing
    Chat Completions conversion. The Responses API uses a flat input/output structure
    with lifted function_call/function_call_output items and named SSE lifecycle events.
  • feat: add Bedrock request optimizer (PRE-SEND thinking + cache injection) (#1301)
    * feat: add Bedrock request optimizer (PRE-SEND thinking + cache injection)
    
    Add a PRE-SEND request optimizer that enhances Bedrock API requests
    before forwarding, complementing the existing POST-ERROR rectifier system.
    
    New modules:
    - thinking_optimizer: 3-path model detection (adaptive/legacy/skip)
      - Opus 4.6/Sonnet 4.6: adaptive thinking + effort max + 1M context beta
      - Legacy models: inject extended thinking with max budget
      - Haiku: skip (no modification)
    - cache_injector: auto-inject cache_control breakpoints (max 4)
      - Injects at tools/system/assistant message positions
      - TTL upgrade for existing breakpoints (5m → 1h)
    
    Gate: only activates for Bedrock providers (CLAUDE_CODE_USE_BEDROCK=1)
    Config: stored in SQLite settings table, default OFF, user opt-in
    UI: new Optimizer section in RectifierConfigPanel with 3 toggles + TTL
    
    18 unit tests covering all paths. Verified against live Bedrock API.
    
    * chore: remove docs/plans directory
    
    * fix: address code review findings for Bedrock request optimizer
    
    P0 fixes:
    - Replace hardcoded Chinese with i18n t() calls in optimizer panel,
      add translation keys to zh/en/ja locale files
    - Fix u64 underflow: max_tokens - 1 → max_tokens.saturating_sub(1)
    - Move optimizer from before retry loop to per-provider with body
      cloning, preventing Bedrock fields leaking to non-Bedrock providers
    
    P1 fixes:
    - Replace .map() side-effect pattern with idiomatic if-let (clippy)
    - Fix module alphabetical ordering in mod.rs
    - Add cache_ttl whitelist validation in set_optimizer_config
    - Remove #[allow(unused_assignments)] and dead budget decrement
    
    ---------
    
    Co-authored-by: Keith (via OpenClaw) <keithyt06@users.noreply.github.com>
    Co-authored-by: Jason <farion1231@gmail.com>
  • fix: Don't add ?beta=true to OpenAI Chat Completions endpoints (#1052)
    Fixes Nvidia provider and other providers using apiFormat="openai_chat".
    
    The ClaudeAdapter::build_url() method was incorrectly adding ?beta=true
    to both /v1/messages and /v1/chat/completions endpoints. This caused
    the Nvidia provider to fail because:
    
    1. Nvidia uses apiFormat="openai_chat"
    2. Requests are transformed to OpenAI format and sent to /v1/chat/completions
    3. The URL gets ?beta=true appended (Anthropic-specific parameter)
    4. Nvidia's API rejects requests with this parameter
    
    Fix:
    - Only add ?beta=true to /v1/messages endpoint
    - Exclude /v1/chat/completions from getting this parameter
    
    Tested:
    - Anthropic /v1/messages still gets ?beta=true ✓
    - OpenAI Chat Completions /v1/chat/completions does NOT get ?beta=true ✓
    - All 13 Claude adapter tests pass ✓
    
    Co-authored-by: jnorthrup <jnorthrup@example.com>
  • feat(services): add OpenClaw branches to backend services
    - Add OpenClaw branches to proxy service (not supported)
    - Add OpenClaw to MCP service (skip sync, MCP still in development)
    - Add OpenClaw skills directory path
    - Update ProxyTakeoverStatus with openclaw field
    - Add OpenClaw to stream check (not supported)
  • feat(proxy): fix thinking rectifiers and resolve clippy warnings (#1005)
    * feat(proxy): align thinking rectifiers and resolve clippy warnings
    
    - add thinking budget rectifier flow with single retry on anthropic budget errors
    
    - align thinking signature rectification behavior with adaptive-safe handling
    
    - expose requestThinkingBudget in settings/ui/i18n and default rectifier config to disabled
    
    - fix clippy warnings in model_mapper format args and RectifierConfig default derive
    
    * fix(proxy): thinking rectifiers
  • fix(ci): add xdg-utils for ARM64 AppImage and suppress dead_code warnings
    - Add xdg-utils dependency for xdg-mime binary required by AppImage bundler
    - Remove unused McpStatus struct from gemini_mcp.rs (duplicate of claude_mcp.rs)
    - Add #![allow(dead_code)] to proxy models reserved for future type-safe API
  • refactor(proxy): remove DeepSeek max_tokens clamp from transform layer
    The max_tokens restriction was too aggressive and should be handled
    upstream or by the provider itself. Simplify anthropic_to_openai by
    removing provider parameter since model mapping is already done by
    proxy::model_mapper.
  • fix(proxy): improve URL building and remove redundant model mapping
    - Add model parameter to request logs for better debugging
    - Fix duplicate /v1/v1 in URL when both base_url and endpoint have version
    - Extend ?beta=true parameter to /v1/chat/completions endpoint
    - Remove model mapping from transform layer (now handled by model_mapper)
    - Add DeepSeek max_tokens clamping (1-8192 range)
  • refactor(claude): migrate api_format from settings_config to meta
    Move api_format storage from settings_config to ProviderMeta to prevent
    polluting ~/.claude/settings.json when switching providers.
    
    - Add api_format field to ProviderMeta (Rust + TypeScript)
    - Update ProviderForm to read/write apiFormat from meta
    - Maintain backward compatibility for legacy settings_config.api_format
      and openrouter_compat_mode fields (read-only fallback)
    - Strip api_format from settings_config before writing to live config
  • fix(claude): improve backward compatibility for openrouter_compat_mode
    Extend backward compatibility support for legacy openrouter_compat_mode field:
    - Support number type (1 = enabled, 0 = disabled)
    - Support string type ("true"/"1" = enabled)
    - Add corresponding test cases for number and string types
  • feat(claude): add API format selector for third-party providers
    Replace the OpenRouter-specific compatibility toggle with a generic
    API format selector that allows all Claude providers to choose between:
    
    - Anthropic Messages (native): Direct passthrough, no conversion
    - OpenAI Chat Completions: Enables Anthropic ↔ OpenAI format conversion
    
    Changes:
    - Add ClaudeApiFormat type ("anthropic" | "openai_chat") to types.ts
    - Replace openRouterCompatToggle with apiFormat dropdown in ClaudeFormFields
    - Update ProviderForm to manage apiFormat state via settingsConfig.api_format
    - Refactor claude.rs: add get_api_format() method, update needs_transform()
    - Maintain backward compatibility with legacy openrouter_compat_mode field
    - Update i18n translations (zh, en, ja)
  • fix(codex): fix 404 errors and connection timeout with custom base_url (#760)
    * fix(proxy): fix Codex 404 errors with custom base_url prefixes
    
    - handlers.rs:268: Remove hardcoded /v1 prefix in Codex forwarding
    - codex.rs:140: Only add /v1 for origin-only base_urls, dedupe /v1/v1
    - stream_check.rs:364: Try /responses first, fallback to /v1/responses
    - provider.rs:427: Don't force /v1 for custom prefix base_urls
    
    Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
    
    * fix(codex): always add /v1 for custom prefix base_urls
    
    Changed logic to always add /v1 prefix unless base_url already ends with /v1.
    This fixes 504 timeout errors with relay services that expect /v1 in the path.
    
    - Most relay services follow OpenAI standard format: /v1/responses
    - Users can opt-out by adding /v1 to their base_url configuration
    - Updated test case to reflect new behavior
    
    Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
    
    * fix(proxy): allow system proxy on localhost with different ports
    
    - Only bypass system proxy if it points to CC Switch's own port (15721)
    - Allow other localhost proxies (e.g., Clash on 7890) to be used
    - Add INFO level logging for request URLs to aid debugging
    
    This fixes connection timeout issues when using local proxy tools.
    
    Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
    
    * fix(codex): don't add /v1 for custom prefix base_urls
    
    Reverted logic to not add /v1 for base_urls with custom prefixes.
    Many relay services use custom paths without /v1.
    
    - Pure origin (e.g., https://api.openai.com) → adds /v1
    - With /v1 (e.g., https://api.openai.com/v1) → no change
    - Custom prefix (e.g., https://example.com/openai) → no /v1
    
    This fixes 404 errors with relay services that don't use /v1 in their paths.
    
    Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
    
    * fix(proxy): use dynamic port for system proxy detection
    
    Instead of hardcoding port 15721, now uses the actual configured
    listen_port from proxy settings.
    
    - Added set_proxy_port() to update the port when proxy server starts
    - Added get_proxy_port() to retrieve current port for detection
    - Updated server.rs to call set_proxy_port() on startup
    - Updated tests to reflect new behavior
    
    This allows users to change the proxy port in settings without
    breaking the system proxy detection logic.
    
    Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
    
    * fix(proxy): change default proxy port from 15721 to 5000
    
    Update the default fallback port in get_proxy_port() from 15721 to 5000
    to match the project's standard default port configuration.
    
    Also updated test cases to use port 5000 consistently.
    
    Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
    
    * fix(proxy): revert default port back to 15721
    
    Revert the default fallback port in get_proxy_port() from 5000 back to 15721
    to align with the project's updated default port configuration.
    
    Also updated test cases to use port 15721 consistently.
    
    Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: ozbombor <ozbombor@users.noreply.github.com>
    Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
  • Feat/pricing config enhancement (#781)
    * feat(db): add pricing config fields to proxy_config table
    
    - Add default_cost_multiplier field per app type
    - Add pricing_model_source field (request/response)
    - Add request_model field to proxy_request_logs table
    - Implement schema migration v5
    
    * feat(api): add pricing config commands and provider meta fields
    
    - Add get/set commands for default cost multiplier
    - Add get/set commands for pricing model source
    - Extend ProviderMeta with cost_multiplier and pricing_model_source
    - Register new commands in Tauri invoke handler
    
    * fix(proxy): apply cost multiplier to total cost only
    
    - Move multiplier calculation from per-item to total cost
    - Add resolve_pricing_config for provider-level override
    - Include request_model and cost_multiplier in usage logs
    - Return new fields in get_request_logs API
    
    * feat(ui): add pricing config UI and usage log enhancements
    
    - Add pricing config section to provider advanced settings
    - Refactor PricingConfigPanel to compact table layout
    - Display all three apps (Claude/Codex/Gemini) in one view
    - Add multiplier column and request model display to logs
    - Add frontend API wrappers for pricing config
    
    * feat(i18n): add pricing config translations
    
    - Add zh/en/ja translations for pricing defaults config
    - Add translations for multiplier, requestModel, responseModel
    - Add provider pricing config translations
    
    * fix(pricing): align backfill cost calculation with real-time logic
    
    - Fix backfill to deduct cache_read_tokens from input (avoid double billing)
    - Apply multiplier only to total cost, not to each item
    - Add multiplier display in request detail panel with i18n support
    - Use AppError::localized for backend error messages
    - Fix init_proxy_config_rows to use per-app default values
    - Fix silent failure in set_default_cost_multiplier/set_pricing_model_source
    - Add clippy allow annotation for test mutex across await
    
    * style: format code with cargo fmt and prettier
    
    * fix(tests): correct error type assertions in proxy DAO tests
    
    The tests expected AppError::InvalidInput but the DAO functions use
    AppError::localized() which returns AppError::Localized variant.
    Updated assertions to match the correct error type with key validation.
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • chore: release v3.10.1
    - Bump version to 3.10.1 across all config files
    - Update CHANGELOG with all fixes since v3.10.0
    - Fix Rust Clippy warning by using derive(Default)
    - Apply code formatting
  • fix(proxy): change rectifier default state to disabled
    Change the default state of the rectifier from enabled to disabled.
    This allows users to opt-in to the rectifier feature rather than having it enabled by default.
    
    Changes:
    - Set RectifierConfig::default() enabled and request_thinking_signature to false
    - Update serde default attributes from default_true to default
    - Update unit tests to reflect new default behavior
  • fix(failover): switch to P1 immediately when enabling auto failover
    Previously, enabling auto failover kept using the current provider until
    the first failure, causing inconsistency when the current provider was
    not in the failover queue. When stopping proxy, the restored config
    would not match user expectations.
    
    New behavior:
    - Enable auto failover = immediately switch to queue P1
    - Subsequent routing follows queue order (P1→P2→...)
    - Auto-add current provider to queue if queue is empty
    
    Changes:
    - Add switch_proxy_target() for hot-switching during proxy mode
    - Update provider_router to use queue order when failover enabled
    - Sync tray menu Auto click with the same logic
    - Update UI tooltips to reflect new semantics
    - Add tests for queue-only routing scenario
  • chore: fix code formatting and test setup
    - Format Rust code with rustfmt (misc.rs, types.rs)
    - Format TypeScript/React code with Prettier (4 files)
    - Fix ProviderList test by wrapping with QueryClientProvider
  • Feat/provider individual config (#663)
    * refactor(ui): simplify UpdateBadge to minimal dot indicator
    
    * feat(provider): add individual test and proxy config for providers
    
    Add support for provider-specific model test and proxy configurations:
    
    - Add ProviderTestConfig and ProviderProxyConfig types in Rust and TypeScript
    - Create ProviderAdvancedConfig component with collapsible panels
    - Update stream_check service to merge provider config with global config
    - Proxy config UI follows global proxy style (single URL input)
    
    Provider-level configs stored in meta field, no database schema changes needed.
    
    * feat(ui): add failover toggle and improve proxy controls
    
    - Add FailoverToggle component with slide animation
    - Simplify ProxyToggle style to match FailoverToggle
    - Add usage statistics button when proxy is active
    - Fix i18n parameter passing for failover messages
    - Add missing failover translation keys (inQueue, addQueue, priority)
    - Replace AboutSection icon with app logo
    
    * fix(proxy): support system proxy fallback and provider-level proxy config
    
    - Remove no_proxy() calls in http_client.rs to allow system proxy fallback
    - Add get_for_provider() to build HTTP client with provider-specific proxy
    - Update forwarder.rs and stream_check.rs to use provider proxy config
    - Fix EditProviderDialog.tsx to include provider.meta in useMemo deps
    - Add useEffect in ProviderAdvancedConfig.tsx to sync expand state
    
    Fixes #636
    Fixes #583
    
    * fix(ui): sync toast theme with app setting
    
    * feat(settings): add log config management
    
    Fixes #612
    Fixes #514
    
    * fix(proxy): increase request body size limit to 200MB
    
    Fixes #666
    
    * docs(proxy): update timeout config descriptions and defaults
    
    Fixes #612
    
    * fix(proxy): filter x-goog-api-key header to prevent duplication
    
    * fix(proxy): prevent proxy recursion when system proxy points to localhost
    
    Detect if HTTP_PROXY, HTTPS_PROXY, or ALL_PROXY environment variables
    point to loopback addresses (localhost, 127.0.0.1), and bypass system
    proxy in such cases to avoid infinite request loops.
    
    * fix(i18n): add providerAdvanced i18n keys and fix failover toast parameter
    
    - Add providerAdvanced.* i18n keys to en.json, zh.json, and ja.json
    - Fix failover toggleFailed toast to pass detail parameter
    - Remove Chinese fallback text from UI for English/Japanese users
    
    * fix(tray): restore tray-provider events and enable Auto failover properly
    
    - Emit provider-switched event on tray provider click (backward compatibility)
    - Auto button now: starts proxy, takes over live config, enables failover
    
    * fix(log): enable dynamic log level and single file mode
    
    - Initialize log at Trace level for dynamic adjustment
    - Change rotation strategy to KeepSome(1) for single file
    - Set max file size to 1GB
    - Delete old log file on startup for clean start
    
    * fix(tray): fix clippy uninlined format args warning
    
    Use inline format arguments: {app_type_str} instead of {}
    
    * fix(provider): allow typing :// in endpoint URL inputs
    
    Change input type from "url" to "text" to prevent browser
    URL validation from blocking :// input.
    
    Closes #681
    
    * fix(stream-check): use Gemini native streaming API format
    
    - Change endpoint from OpenAI-compatible to native streamGenerateContent
    - Add alt=sse parameter for SSE format response
    - Use x-goog-api-key header instead of Bearer token
    - Convert request body to Gemini contents/parts format
    
    * feat(proxy): add request logging for debugging
    
    Add debug logs for outgoing requests including URL and body content
    with byte size, matching the existing response logging format.
    
    * fix(log): prevent usize underflow in KeepSome rotation strategy
    
    KeepSome(n) internally computes n-2, so n=1 causes underflow.
    Use KeepSome(2) as the minimum safe value.
  • feat(opencode): Phase 1 - Backend data structure expansion for OpenCode support
    Add OpenCode as the 4th supported application with additive provider management:
    
    - Add OpenCode variant to AppType enum with all related match statements
    - Add enabled_opencode field to McpApps and SkillApps structures
    - Add opencode field to McpRoot and PromptRoot
    - Add database schema migration v3→v4 with enabled_opencode columns
    - Add settings.rs support for opencode_config_dir and current_provider_opencode
    - Create opencode_config.rs module for config file I/O operations
    - Update all services (proxy, mcp, skill, provider, stream_check) for OpenCode
    - Add OpenCode support to deeplink provider and MCP parsing
    - Update commands/config.rs for OpenCode config status and paths
    
    Key design decisions:
    - OpenCode uses additive mode (no is_current needed, no proxy support)
    - Config path: ~/.config/opencode/opencode.json
    - MCP format: stdio→local, sse/http→remote conversion planned
    - Stream check returns error (not yet implemented for OpenCode)
  • feat(proxy): add thinking signature rectifier for Claude API (#595)
    * feat(proxy): add thinking signature rectifier for Claude API
    
    Add automatic request rectification when Anthropic API returns signature
    validation errors. This improves compatibility when switching between
    different Claude providers or when historical messages contain incompatible
    thinking block signatures.
    
    - Add thinking_rectifier.rs module with trigger detection and rectification
    - Integrate rectifier into forwarder error handling flow
    - Remove thinking/redacted_thinking blocks and signature fields on retry
    - Delete top-level thinking field when assistant message lacks thinking prefix
    
    * fix(proxy): complete rectifier retry path with failover switch and chain continuation
    
    - Add failover switch trigger on rectifier retry success when provider differs from start
    - Replace direct error return with error categorization on rectifier retry failure
    - Continue failover chain for retryable errors instead of terminating early
    
    * feat(proxy): add rectifier config with master switch
    
    - Add RectifierConfig struct with enabled and requestThinkingSignature fields
    - Update should_rectify_thinking_signature to check master switch first
    - Add tests for master switch functionality
    
    * feat(db): add rectifier config storage in settings table
    
    Store rectifier config as JSON in single key for extensibility
    
    * feat(commands): add get/set rectifier config commands
    
    * feat(ui): add rectifier config panel in advanced settings
    
    - Add RectifierConfigPanel component with master switch and thinking signature toggle
    - Add API wrapper for rectifier config
    - Add i18n translations for zh/en/ja
    
    * feat(proxy): integrate rectifier config into request forwarding
    
    - Load rectifier config from database in RequestContext
    - Pass config to RequestForwarder for runtime checking
    - Use should_rectify_thinking_signature with config parameter
    
    * test(proxy): add nested JSON error detection test for thinking rectifier
    
    * fix(proxy): resolve HalfOpen permit leak and RectifierConfig default values
    
    - Fix RectifierConfig::default() to return enabled=true (was false due to derive)
    - Add release_permit_neutral() for releasing permits without affecting health stats
    - Fix 3 permit leak points in rectifier retry branches
    - Add unit tests for default values and permit release
    
    * style(ui): format ProviderCard style attribute
    
    * fix(rectifier): add detection for signature field required error
    
    Add support for detecting "signature: Field required" error pattern
    in the thinking signature rectifier. This enables automatic request
    rectification when upstream API returns this specific validation error.
  • Feature/global proxy (#596)
    * refactor(proxy): simplify logging for better readability
    
    - Delete 17 verbose debug logs from handlers, streaming, and response_processor
    - Convert excessive INFO logs to DEBUG level for internal processing details
    - Add 2 critical INFO logs in forwarder.rs for failover scenarios:
      - Log when switching to next provider after failure
      - Log when all providers have been exhausted
    - Fix clippy uninlined_format_args warning
    
    This reduces log noise while maintaining visibility into key user-facing decisions.
    
    * fix: replace unsafe unwrap() calls with proper error handling
    
    - database/dao/mcp.rs: Use map_err for serde_json serialization
    - database/dao/providers.rs: Use map_err for settings_config and meta serialization
    - commands/misc.rs: Use expect() for compile-time regex pattern
    - services/prompt.rs: Use unwrap_or_default() for SystemTime
    - deeplink/provider.rs: Replace unwrap() with is_none_or pattern for Option checks
    
    Reduces potential panic points from 26 to 1 (static regex init, safe).
    
    * refactor(proxy): simplify verbose logging output
    
    - Remove response JSON full output logging in response_processor
    - Remove per-request INFO logs in provider_router (failover status, provider selection)
    - Change model mapping log from INFO to DEBUG
    - Change usage logging failure from INFO to WARN
    - Remove redundant debug logs for circuit breaker operations
    
    Reduces log noise significantly while preserving important warnings and errors.
    
    * feat(proxy): add structured log codes for i18n support
    
    Add error code system to proxy module logs for multi-language support:
    
    - CB-001~006: Circuit breaker state transitions and triggers
    - SRV-001~004: Proxy server lifecycle events
    - FWD-001~002: Request forwarding and failover
    - FO-001~005: Failover switch operations
    - USG-001~002: Usage logging errors
    
    Log format: [CODE] Chinese message
    Frontend/log tools can map codes to any language.
    
    New file: src/proxy/log_codes.rs - centralized code definitions
    
    * chore: bump version to 3.9.1
    
    * style: format code with prettier and rustfmt
    
    * fix(ui): allow number inputs to be fully cleared before saving
    
    - Convert numeric state to string type for controlled inputs
    - Use isNaN() check instead of || fallback to allow 0 values
    - Apply fix to ProxyPanel, CircuitBreakerConfigPanel,
      AutoFailoverConfigPanel, and ModelTestConfigPanel
    
    * feat(pricing): support @ separator in model name matching
    
    - Refactor model name cleaning into chained method calls
    - Add @ to - replacement (e.g., gpt-5.2-codex@low → gpt-5.2-codex-low)
    - Add test case for @ separator matching
    
    * feat(proxy): add global proxy settings support
    
    Add ability to configure a global HTTP/HTTPS proxy for all outbound
    requests including provider API calls, speed tests, and stream checks.
    
    * fix(proxy): improve validation and error handling in proxy config panels
    
    - Add StopTimeout/StopFailed error types for proper stop() error reporting
    - Replace silent clamp with validation-and-block in config panels
    - Add listenAddress format validation in ProxyPanel
    - Use log_codes constants instead of hardcoded strings
    - Use once_cell::Lazy for regex precompilation
    
    * fix(proxy): harden error handling and input validation
    
    - Handle RwLock poisoning in settings.rs with unwrap_or_else
    - Add fallback for dirs::home_dir() in config modules
    - Normalize localhost to 127.0.0.1 in ProxyPanel
    - Format IPv6 addresses with brackets for valid URLs
    - Strict port validation with pure digit regex
    - Treat NaN as validation failure in config panels
    - Log warning on cost_multiplier parse failure
    - Align timeoutSeconds range to [0, 300] across all panels
    
    * feat(proxy): add local proxy auto-scan and fix hot-reload
    
    - Add scan_local_proxies command to detect common proxy ports
    - Fix SkillService not using updated proxy after hot-reload
    - Move global proxy settings to advanced tab
    - Add error handling for scan failures
    
    * fix(proxy): allow localhost input in proxy address field
    
    * fix(proxy): restore request timeout and fix proxy hot-reload issues
    
    - Add URL scheme validation in build_client (http/https/socks5/socks5h)
    - Restore per-request timeout for speedtest, stream_check, usage_script, forwarder
    - Fix set_global_proxy_url to validate before persisting to DB
    - Mask proxy credentials in all log outputs
    - Fix forwarder hot-reload by fetching client on each request
    
    * style: format code with prettier
    
    * fix(proxy): improve global proxy stability and error handling
    
    - Fix RwLock silent failures with explicit error propagation
    - Handle init() duplicate calls gracefully with warning log
    - Align fallback client config with build_client settings
    - Make scan_local_proxies async to avoid UI blocking
    - Add mixed mode support for Clash 7890 port (http+socks5)
    - Use multiple test targets for better proxy connectivity test
    - Clear invalid proxy config on init failure
    - Restore timeout constraints in usage_script
    - Fix mask_url output for URLs without port
    - Add structured error codes [GP-001 to GP-009]
    
    * feat(proxy): add username/password authentication support
    
    - Add separate username and password input fields
    - Implement password visibility toggle with eye icon
    - Add clear button to reset all proxy fields
    - Auto-extract auth info from saved URL and merge on save
    - Update i18n translations (zh/en/ja)
    
    * fix(proxy): fix double encoding issue in proxy auth and add debug logs
    
    - Remove encodeURIComponent in mergeAuth() since URL object's
      username/password setters already do percent-encoding automatically
    - Add GP-010 debug log for database read operations
    - Add GP-011 debug log to track incoming URL info (length, has_auth)
    - Fix username.trim() in fallback branch for consistent behavior