Commit Graph

1395 Commits

  • 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
  • Normalize fragmented system prompts for strict chat backends
    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
  • fix: gate parse_gemini_keychain_json with cfg(target_os = "macos")
    The function is only called from read_gemini_credentials_from_keychain
    which is already macOS-only. Without the gate, non-macOS CI fails with
    dead-code error due to -D warnings.
  • fix: invert MiniMax usage_count to match 0%→100% convention
    usage_count is remaining quota (starts at total, decreases to 0),
    not used count. Invert calculation so all providers consistently
    show 0% when fresh and 100% when exhausted.
  • fix: correct MiniMax quota calculation and improve Token Plan display
    - Fix MiniMax usage_count being treated as remaining (was inverted)
    - Add MiniMax weekly quota tier extraction
    - Remove Zhipu TIME_LIMIT (tools usage), keep only TOKENS_LIMIT
    - Improve Kimi parsing with extract_reset_time and parse_f64 helpers
    - Reuse TierBadge for Token Plan inline rendering
    - Clean up unused i18n keys and debug println
  • feat: add Token Plan quota query for Kimi, Zhipu GLM, and MiniMax
    Add a new "Token Plan" template type in the usage query panel that
    natively queries quota/usage from Chinese coding plan providers
    (Kimi For Coding, Zhipu GLM, MiniMax) without requiring custom scripts.
    
    - Rust backend: new coding_plan service with provider-specific API
      queries (Kimi /v1/usages, Zhipu /api/monitor/usage/quota/limit,
      MiniMax /coding_plan/remains) normalized into UsageResult
    - Frontend: Token Plan template in UsageScriptModal with auto-detection
      of provider based on ANTHROPIC_BASE_URL pattern matching
    - Follows the same pattern as GitHub Copilot template (dedicated API
      path in queryProviderUsage, no JS script needed)
  • fix: allow provider switch without proxy, show warning instead of blocking
    Remove the hard block that prevented switching to providers requiring
    proxy (OpenAI format, Copilot, full URL mode) when the proxy is not
    running. Now the switch proceeds with a warning toast. Also deduplicate
    the proxy hint info toast so it doesn't appear alongside the warning.
  • feat: restore Copilot preset and auth center tab in settings
    Re-enable GitHub Copilot provider preset and the OAuth auth center tab
    that were temporarily hidden due to abnormal consumption rates. The
    Copilot optimizer introduced in the previous commit addresses the
    underlying issue.
  • 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: remove hover push animation on provider cards
    Keep usage display fixed in place and show action buttons with
    a simple opacity fade instead of sliding in and pushing content.
  • fix: hide usage config and health check buttons for official providers
    Official providers use built-in subscription quota display instead of
    custom usage scripts, and stream check is not applicable. Hide both
    action buttons when isOfficialProvider is true.
  • feat: add official subscription quota display for Gemini
    - Read Gemini OAuth credentials from macOS Keychain (gemini-cli-oauth)
      or legacy file (~/.gemini/oauth_creds.json)
    - Auto-refresh expired access tokens using refresh_token (Google OAuth
      tokens expire in ~1h, unlike Claude/Codex)
    - Two-step API: loadCodeAssist for project ID, then retrieveUserQuota
      for per-model quota buckets
    - Classify models into Pro/Flash/Flash Lite categories, show min
      remaining fraction as utilization percentage
    - Extend isOfficialProvider() for Gemini (no API key + no base URL)
    - Parameterize expiredHint i18n key with tool name for all three apps
  • feat: add official subscription quota display for Codex
    Read Codex OAuth credentials from ~/.codex/auth.json (with macOS
    Keychain fallback) and query chatgpt.com/backend-api/wham/usage to
    show rate limit utilization on official Codex provider cards. Reuses
    the same tier naming (five_hour, seven_day) for frontend i18n compat.
  • feat: display official subscription quota on Claude provider cards
    Read Claude OAuth credentials from macOS Keychain (with file fallback)
    and query the Anthropic usage API to show quota utilization inline on
    official provider cards. Includes compact countdown timer for reset
    windows and hides the rarely-used seven_day_sonnet tier in inline mode.
  • docs: update user manual to v3.12.3 with new features coverage (en/zh/ja)
    Add documentation for features introduced since v3.12.0:
    
    New docs:
    - 3.4 Session Manager: browse, search, resume, delete sessions
    - 3.5 Workspace & Daily Memory: OpenClaw workspace file editing
    
    Updated docs:
    - Add Lightweight Mode to interface overview and FAQ
    - Add tray submenu structure (providers grouped by app)
    - Add API Format selection (Anthropic/OpenAI Chat/OpenAI Responses)
    - Add Auto-Fetch Models button documentation
    - Add Claude Common Config quick toggles
    - Add Codex 1M Context Window toggle
    - Add Skill backup/restore lifecycle
    - Expand Backup Management panel documentation
    - Update WebDAV sync to v2 protocol with dual-layer versioning
    - Add OpenCode/OpenClaw to quickstart activation table
    - Update README version to v3.12.3
    
    All changes synced across en, zh, and ja locales.
  • feat(tray): collapse providers into submenus to prevent menu overflow
    Each app type (Claude/Codex/Gemini) now renders as a submenu instead
    of flat items, keeping the top-level tray menu compact regardless of
    provider count. The submenu label shows the current provider name
    (e.g. "Claude · OpenRouter") for at-a-glance visibility.
  • feat: differentiate fetch models error messages by failure type
    Distinguish between missing API key, missing endpoint, auth failure,
    unsupported provider (404/405), and timeout errors instead of showing
    a generic failure toast for all cases.
  • feat: add auto-fetch models from provider's /v1/models endpoint
    Add ability to fetch available models from third-party aggregation
    providers (SiliconFlow, OpenRouter, etc.) via OpenAI-compatible
    GET /v1/models endpoint. Users can click "Fetch Models" button in
    the provider form, then select models from a dropdown on each
    model input field.
    
    - Backend: new model_fetch service + Tauri command (Rust)
    - Frontend: ModelInputWithFetch shared component
    - Integrated into all 5 app forms (Claude/Codex/Gemini/OpenCode/OpenClaw)
    - i18n support for zh/en/ja
  • 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>
  • chore(deps): bump actions/cache from 4 to 5 in ci.yml (#1860)
    Sync ci.yml with release.yml after dependabot updates.
  • chore(deps): bump actions/download-artifact from 4 to 8
    Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 8.
    - [Release notes](https://github.com/actions/download-artifact/releases)
    - [Commits](https://github.com/actions/download-artifact/compare/v4...v8)
    
    ---
    updated-dependencies:
    - dependency-name: actions/download-artifact
      dependency-version: '8'
      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/upload-artifact from 4 to 7
    Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
    - [Release notes](https://github.com/actions/upload-artifact/releases)
    - [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)
    
    ---
    updated-dependencies:
    - dependency-name: actions/upload-artifact
      dependency-version: '7'
      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 2 to 5
    Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 2 to 5.
    - [Release notes](https://github.com/pnpm/action-setup/releases)
    - [Commits](https://github.com/pnpm/action-setup/compare/v2...v5)
    
    ---
    updated-dependencies:
    - dependency-name: pnpm/action-setup
      dependency-version: '5'
      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/setup-node from 4 to 6
    Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
    - [Release notes](https://github.com/actions/setup-node/releases)
    - [Commits](https://github.com/actions/setup-node/compare/v4...v6)
    
    ---
    updated-dependencies:
    - dependency-name: actions/setup-node
      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/cache from 4 to 5
    Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
    - [Release notes](https://github.com/actions/cache/releases)
    - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
    - [Commits](https://github.com/actions/cache/compare/v4...v5)
    
    ---
    updated-dependencies:
    - dependency-name: actions/cache
      dependency-version: '5'
      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>
  • ci: add stale bot to auto-close inactive issues after 60 days
    - Mark issues as stale after 60 days of inactivity
    - Auto-close after 14 more days without response
    - Bilingual (zh/en) stale and close messages
    - Exempt security and performance labeled issues
    - Runs daily at UTC 00:00, processes up to 100 issues per run
    - Only affects issues, not pull requests
  • docs: fix outdated README content and sync across languages
    - Fix JA README macOS FAQ still claiming app is unsigned (now code-signed & notarized)
    - Update SessionManager description from "Claude Code only" to "all supported apps" (EN/ZH/JA)
    - Replace non-existent Flatpak download entry with self-build reference (EN/ZH/JA)
    - Add OpenCode and OpenClaw filesystem paths to Flatpak minimal permissions
    - Bump user manual version to v3.12.3
  • 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
  • feat: hide GitHub Copilot provider preset and auth tab
    Users reported that Copilot support causes excessively fast token
    consumption. Temporarily hide the feature by adding a `hidden` field
    to ProviderPreset interface and commenting out the auth center tab
    in settings. Existing Copilot providers in DB still work via proxy.
  • docs: add CONTRIBUTING.md, SECURITY.md, and CODE_OF_CONDUCT.md
    Add three community health files with bilingual (EN/ZH) support:
    - CODE_OF_CONDUCT.md: Contributor Covenant v2.1 with official Chinese translation
    - SECURITY.md: security policy pointing to GitHub Security Advisories
    - CONTRIBUTING.md: contribution guide with dev setup, code style, PR guidelines,
      i18n rules, and AI-assisted contribution policy
  • ci: add issue and PR templates with bilingual support
    Add GitHub issue templates (bug report, feature request, documentation
    issue, question) and PR template. All templates use bilingual format
    (English + Chinese) to support the international community.
  • ci: add dependabot.yml config (#1829)
    * ci: add dependabot.yml config
    
    * chore: replace `rust` label with `backend` and fix missing newline
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • fix(claude): sync takeover live config during provider changes (#1828)
    Keep Claude's live settings aligned with the latest provider state while proxy takeover is active, without breaking takeover fields or restore behavior.
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • feat(provider): additive provider key lifecycle & fix openclaw serializer panic (#1724)
    * feat(provider): support additive provider key lifecycle management
    
    Add `addToLive` parameter to add_provider so callers can opt out of
    writing to the live config (e.g. when duplicating an inactive provider).
    Add `originalId` parameter to update_provider to support provider key
    renames — the old key is removed from live config before the new one
    is written.
    
    Frontend: ProviderForm now exposes provider-key input for openclaw app
    type, and EditProviderDialog forwards originalId on save. Deep-link
    import passes addToLive=true to preserve existing behavior.
    
    * test(provider): add integration tests for additive provider key flows
    
    Cover openclaw provider duplication scenario to verify that a generated
    provider key is assigned automatically. Add MSW handlers for
    get_openclaw_live_provider_ids, get_openclaw_default_model,
    scan_openclaw_config_health, and check_env_conflicts endpoints.
    Update EditProviderDialog mock to pass originalId alongside provider.
    
    * fix(openclaw): replace json-five serializer to prevent panic on empty collections
    
    json-five 0.3.1 panics when pretty-printing nested empty maps/arrays.
    Switch value_to_rt_value() to serde_json::to_string_pretty() which
    produces valid JSON5 output without the panic. Add regression test for
    removing the last provider (empty providers map).
    
    * style: apply rustfmt formatting to proxy and provider modules
    
    Reformat chained .header() calls in ClaudeAdapter and StreamCheckService
    for consistent alignment. Reorder imports alphabetically in stream_check.
    Fix trailing whitespace in transform.rs and merge import lines in
    provider/mod.rs.
    
    * style: fix clippy warnings in live.rs and tray.rs
    
    * refactor(provider): simplify live_config_managed and deduplicate tolerant live config checks
    
    - Change live_config_managed from Option<bool> to bool with #[serde(default)]
    - Extract repeated tolerant live config query into check_live_config_exists helper
    - Fix duplicate key generation to also check live-only provider IDs
    - Fix updateProvider test to match new { provider, originalId } call signature
    - Add streaming_responses test type annotation for compiler inference
    
    * fix(provider): distinguish legacy providers from db-only when tolerating live config errors
    
    Change `ProviderMeta.live_config_managed` from `bool` to `Option<bool>`
    to introduce a three-state semantic:
    - `Some(true)`: provider has been written to live config
    - `Some(false)`: explicitly db-only, never written to live config
    - `None`: legacy data or unknown state (pre-existing providers)
    
    Previously, legacy providers defaulted to `live_config_managed = false`
    via `#[serde(default)]`, which silently swallowed live config parse
    errors. This could mask genuine configuration issues for providers that
    had actually been synced to live config before the field was introduced.
    
    Now, only providers with an explicit `Some(false)` marker tolerate parse
    errors; legacy `None` providers surface errors as before, preserving
    safety for already-managed configurations.
    
    Also wrap the `ensureQueryData` call for live provider IDs during
    duplication in a try/catch so that a malformed config file shows a
    user-facing toast instead of silently failing.
    
    Add tests for both the legacy error propagation path and the frontend
    duplication failure scenario.
    
    * refactor(provider): unify OMO variant updates with atomic file-then-db writes and rollback
    
    Consolidate the duplicated omo/omo-slim update branches into a single
    match on the variant. Write the OMO config file from the in-memory
    provider state *before* persisting to the database, so a file-write or
    plugin-sync failure leaves the database unchanged. If `add_plugin`
    fails after the config file is already written, roll back to the
    previous on-disk contents via snapshot/restore.
    
    Also:
    - `sync_all_providers_to_live` now skips db-only providers
      (`live_config_managed == Some(false)`) instead of attempting to write
      them to live config.
    - `import_{opencode,openclaw}_providers_from_live` mark imported
      providers as `live_config_managed: Some(true)` so they are correctly
      recognized during subsequent syncs.
    - Extract OmoService helpers: `profile_data_from_provider`,
      `snapshot_config_file`, `restore_config_file`, `write_profile_config`,
      and the new public `write_provider_config_to_file`.
    - Add 9 new tests covering sync skip, legacy restore, import marking,
      OMO persistence, file-write failure, and plugin-sync rollback.
    
    * fix(provider): fix additive provider delete/switch regressions and redundancy
    
    - fix(delete): replace stale live_config_managed flag check with
      check_live_config_exists so providers written to live before the
      flag-flip logic was introduced are still cleaned up on delete
    - fix(switch): make write_live_with_common_config return Err instead of
      silently returning Ok when config structure is invalid, preventing
      live_config_managed from being incorrectly flipped to true
    - fix(update): block provider key rename for OMO/OMO Slim categories to
      prevent orphaned current-state markers breaking OMO file syncs
    - fix(switch): flip live_config_managed to true after successful live
      write for DB-only additive providers so sync_all_providers_to_live
      includes them on future syncs; roll back live write if DB update fails
    - refactor(delete): merge symmetric OMO/OMO-Slim blocks into single
      match-on-variant path; hoist DB read to top of additive branch
    - refactor(remove_from_live_config): merge OMO/OMO-Slim if/else-if
      into single match-on-variant path
    - refactor(switch_normal): merge two OMO/OMO-Slim if blocks into one
      OpenCode guard with (enable, disable) variant pair
    - fix(update): remove redundant duplicate return Ok(true) after OMO
      current-state write
    
    * fix(test): use preferred_filename after OMO field rename
    
    The merge from main brought in #1746 which renamed
    OmoVariant.filename → preferred_filename, but the test helper
    omo_config_path() was not updated, breaking compilation of all
    new provider tests.
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • docs: add ChefShop AI sponsor to all README versions
    Add ChefShop AI as a new sponsor with logo and descriptions in Chinese, English, and Japanese README files.
  • 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
  • fix(omo): adapt to oh-my-openagent rename with backward compatibility (#1746)
    * fix(omo): adapt to oh-my-openagent rename with backward compatibility
    
    Closes https://github.com/farion1231/cc-switch/issues/1733
    
    * fix(omo): prioritize oh-my-openagent config over legacy oh-my-opencode