Commit Graph

976 Commits

  • Merge branch 'main' into feat/gemini-proxy-integration
    # Conflicts:
    #	src-tauri/src/proxy/providers/claude.rs
    #	src/i18n/locales/en.json
    #	src/i18n/locales/ja.json
    #	src/i18n/locales/zh.json
  • feat(stream-check): refresh default models and detect model-not-found errors (#2099)
    * chore(stream-check): update default health check models to latest
    
    Replaces deprecated gpt-5.1-codex@low with gpt-5.4@low and switches
    the Gemini default from gemini-3-pro-preview to gemini-3-flash-preview
    to pick the lightest variant of the latest series for fast, low-cost
    health checks.
    
    https://claude.ai/code/session_01NGWLchcTP76rJHjiP5Ehte
    
    * feat(stream-check): detect model-not-found errors with dedicated toast
    
    Health check previously classified failures purely by HTTP status code,
    which meant deprecated/invalid models showed up as a generic "Not found
    (404)" error pointing users to check the Base URL — misleading when the
    URL is fine and only the test model is wrong (e.g. gpt-5.1-codex after
    it was retired).
    
    Backend: add detect_error_category() that inspects 4xx response bodies
    for model-not-found indicators (model_not_found, does not exist,
    invalid model, not_found_error, etc.) and returns a "modelNotFound"
    category. Thread the resolved test model through build_stream_check_result
    so the failed result carries it in model_used. Add StreamCheckResult
    .error_category field (serde-skipped when None).
    
    Frontend: useStreamCheck branches on errorCategory === "modelNotFound"
    before the HTTP-status fallback and renders a toast.error with the model
    name and a description pointing to Model Test Config. Add i18n keys
    (modelNotFound / modelNotFoundHint) for zh/en/ja.
    
    Tests: unit-test detect_error_category against real OpenAI/Anthropic
    error shapes, 5xx false-positive avoidance, and plain 401 auth errors.
    
    https://claude.ai/code/session_01NGWLchcTP76rJHjiP5Ehte
    
    * fix(stream-check): add missing error_category field in fallback
    
    The error_category field was added to StreamCheckResult in this branch
    but the fallback constructor in stream_check_all_providers was not
    updated, which broke cargo build.
    
    ---------
    
    Co-authored-by: Claude <noreply@anthropic.com>
  • feat: classify stream check errors with color-coded toasts
    Distinguish between "provider rejects probe" (yellow warning) and
    "genuinely broken" (red error) in health check results.
    
    Backend: add AppError::HttpStatus variant to carry structured HTTP
    status codes, populate http_status on error results, classify codes
    into short labels (e.g. "Auth rejected (401)"), and truncate overly
    long response bodies.
    
    Frontend: route 401/403/400/429/5xx to toast.warning with localized
    hints explaining the error may not indicate actual unusability; route
    404/402/connection errors to toast.error. Add i18n keys for all three
    locales (zh/en/ja).
    
    Also deduplicate check_once by reusing build_stream_check_result.
  • fix: auto-expand collapsed messages when search matches hidden content
    When a search query matches text beyond the collapse point, the message
    automatically expands to show the highlighted match. Also adds
    aria-expanded for accessibility.
  • perf: collapse long session messages to reduce text layout cost
    Messages over 3000 characters are now truncated to 1500 characters by
    default, with an expand/collapse toggle. This avoids expensive browser
    text layout for large AI responses containing code or tool output.
  • perf: virtualize session message list for long conversations
    Replace full DOM rendering with @tanstack/react-virtual to only render
    visible messages (~25 DOM nodes instead of N). Wrap SessionMessageItem
    in React.memo to prevent unnecessary re-renders on state changes.
  • fix: remove duplicate usage summary from app filter bar
    The request count and total cost were displayed both in the app filter
    bar and in the UsageSummaryCards below it. Remove the redundant inline
    summary and its unused imports.
  • refactor: remove per-provider proxy config feature
    Replace all remaining "代理服务/Proxy Service/プロキシサービス" references
    in the local routing feature context with "路由服务/Routing Service/
    ルーティングサービス". This covers service settings, status messages,
    tooltips, field descriptions, and tab labels.
    
    Global Proxy, HTTP proxy hints, and AI Agent references are unchanged.
  • rename: rebrand "Local Proxy Takeover" to "Local Routing" in all locales
    Replace all user-facing references to "本地代理接管/Local Proxy/Takeover"
    with "本地路由/Local Routing/ローカルルーティング" across zh/en/ja to
    eliminate naming confusion with the separate "Global Proxy" feature.
    
    Only i18n string values are changed; keys, code identifiers, and
    database schema remain untouched.
  • refactor: remove per-provider proxy config feature
    The per-provider proxy configuration (meta.proxyConfig) is removed
    because its scope is too narrow and covered by global proxy settings
    and proxy takeover mode. Users can achieve the same result via the
    global proxy panel.
    
    Changes:
    - Remove ProviderProxyConfig type (frontend TS + backend Rust)
    - Remove ProviderAdvancedConfig proxy UI block, keep testConfig/pricingConfig
    - Simplify http_client: delete build_proxy_url_from_config,
      build_client_for_provider, get_for_provider
    - Simplify forwarder/stream_check/model_fetch to use global client
    - Remove i18n keys (en/zh/ja)
    - Fix pre-existing test bug in transform.rs (extra None arg)
  • fix(usage): sync request log time range with dashboard 1d/7d/30d selector
    The RequestLogTable had a hardcoded 24-hour rolling window, ignoring the
    dashboard's time range selector. Now it accepts a timeRange prop and
    dynamically adjusts the query window, so users can view logs beyond just
    the last day.
  • feat(usage): improve pagination with first/last 3 pages and page jump input
    Show first 3 and last 3 page buttons instead of just first/last, with
    Set-based deduplication for clean edge merging. Add a page number input
    field with Go button for direct page navigation.
  • fix(sessions): strip OpenClaw message_id suffix and allow 2-line titles
    OpenClaw gateway injects `[message_id: UUID]` metadata at the end of
    every message, wasting display space. Strip this suffix from both title
    and summary fields.
    
    Also change session title display from single-line truncate to
    line-clamp-2, so longer titles (e.g. OpenClaw's timestamp-prefixed
    messages) can show more meaningful content across two lines.
  • feat: block official provider switching during proxy takeover
    Prevent users from switching to official providers (Anthropic/OpenAI/Google)
    when proxy takeover is active, as using a proxy with official APIs may cause
    account bans.
    
    Defense-in-depth across 4 layers:
    - Backend: ProviderService::switch(), hot_switch_provider(), switch_proxy_provider command
    - Frontend: useProviderActions soft guard with error toast
    - UI: ProviderActions button disabled with ShieldAlert icon
    - Tray menu: official provider items disabled with  indicator
    
    Also warns when enabling proxy takeover while current provider is official.
  • 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
  • 添加应用级别窗口按钮,以改善linux wayland下系统窗口按钮失效的问题 (#1119)
    * feat(window): add app-level window controls with settings toggle
    
    Add a persistent settings toggle to enable app-level minimize/maximize/close controls and hide system decorations when enabled, providing a Wayland-friendly fallback for broken native titlebar interactions.
    
    Co-authored-by: Cursor <cursoragent@cursor.com>
    
    * fix(window): restrict app-level window controls to Linux only and fix startup flicker
    
    - Guard useAppWindowControls with isLinux() in App.tsx so it's always
      false on macOS/Windows even if persisted as true
    - Wrap set_decorations call in lib.rs with #[cfg(target_os = "linux")]
    - Only show the toggle in WindowSettings on Linux
    - Skip setDecorations effect while settingsData is still loading to
      prevent the Rust-side decoration state from being overridden by the
      undefined->false fallback, which caused a brief title bar flicker
    
    ---------
    
    Co-authored-by: wzk <wx13571681304@outlook.com>
    Co-authored-by: Cursor <cursoragent@cursor.com>
    Co-authored-by: Jason <farion1231@gmail.com>
  • Merge branch 'main' into feat/gemini-proxy-integration
    Resolve conflict in claudeProviderPresets.ts: keep both Gemini Native
    and Shengsuanyun presets.
  • feat: update PIPELLM preset with full config, add Codex support and icon
    Update base URL to cc-api.pipellm.ai, add complete model definitions
    (Opus/Sonnet/Haiku), add Codex preset with custom TOML config, add
    pipellm PNG icon, and set apiKeyUrl to referral link.
  • feat: add E-FlowCode provider preset across all five apps
    Multi-protocol provider: Anthropic for Claude, OpenAI Responses for
    Codex/OpenClaw, OpenAI for OpenCode, custom Gemini config. Includes
    PNG icon via URL import and provider-specific settings like effortLevel,
    enabledPlugins, and custom TOML config for Codex.
  • feat: add Shengsuanyun provider preset with partner promotion
    Add Shengsuanyun (胜算云) as an aggregator partner across all five apps,
    positioned right after official providers. Uses anthropic-messages
    protocol for OpenCode/OpenClaw. Includes URL-based icon import (217KB
    SVG), partner promotion i18n for zh/en/ja, and localized display name.
  • feat: add LionCCAPI provider preset with partner promotion
    Add LionCCAPI as a third-party partner provider across all five apps
    (Claude, Codex, Gemini, OpenCode, OpenClaw) with anthropic-messages
    protocol for OpenCode and OpenClaw. Include partner promotion i18n
    entries for zh/en/ja locales and lioncc icon.
  • feat: replace x-code icon with high-res xcode icon via URL import
    Replace the old low-res x-code inline SVG (7.6KB) with a new high-res
    xcode.svg (286KB) loaded via Vite URL import. Update all three provider
    preset files to reference the new icon name.
  • feat: support URL-based icons for large SVGs and raster images
    Add dual rendering mode to the icon system: small optimized SVGs
    continue to be inlined via dangerouslySetInnerHTML, while large SVGs
    and raster images (png/jpg/webp/etc) use Vite URL imports rendered
    as <img> tags. Added dds.svg (1.4MB) as the first URL-based icon.
    
    Updated generate-icon-index.js to support multi-format icons with
    a manual URL_ICONS control list. Updated ProviderIcon to handle
    both inline SVG and URL-based rendering paths.
  • feat: add ddshub provider preset with partner promotion
    Add ddshub as a third-party partner provider for Claude, including
    SVG icon and i18n promotion text in zh/en/ja locales.
  • Restore first-class OMO Slim council support (#1981) (#1982)
    cc-switch could already persist arbitrary OMO Slim agent keys and top-level fields, but the built-in metadata and UI copy still reflected the pre-council agent set. This made the upstream council feature look unsupported and pushed users toward manual JSON-only setup.
    
    Promote council to a built-in OMO Slim agent, add copy that points top-level plugin settings at Other Fields, and lock the behavior with regression tests.
    
    Constraint: oh-my-opencode-slim exposes council through both agents.council and top-level council config
    Rejected: Add a dedicated council editor UI now | too much surface area for issue #1981
    Confidence: high
    Scope-risk: narrow
    Reversibility: clean
    Directive: Keep OMO Slim built-in agent metadata aligned with upstream agent additions before shipping UI support
    Tested: pnpm exec vitest run tests/utils/omoConfig.test.ts tests/components/OmoFormFields.mergeCustomModelsIntoStore.test.ts
    Tested: pnpm typecheck
    Not-tested: End-to-end validation against a live oh-my-opencode-slim installation
    Related: farion1231/cc-switch#1981
  • Let Kaku users launch sessions from their chosen terminal (#1954) (#1983)
    Kaku is a WezTerm-derived macOS terminal, so reusing the existing WezTerm-compatible launch path keeps the change small while making it selectable in settings and session resume flows.
    
    Constraint: Kaku support should stay macOS-only and avoid introducing a separate launcher model
    Rejected: Treat Kaku as a silent WezTerm fallback | users could not explicitly choose it in settings
    Confidence: high
    Scope-risk: narrow
    Reversibility: clean
    Directive: Keep Kaku on the shared WezTerm-compatible launch path unless upstream drops the start-compatible CLI
    Tested: pnpm typecheck; pnpm format:check; cargo check --manifest-path src-tauri/Cargo.toml; cargo fmt --manifest-path src-tauri/Cargo.toml --check; cargo test --manifest-path src-tauri/Cargo.toml --lib session_manager::terminal::tests
    Not-tested: End-to-end launch against a locally installed Kaku.app
    Related: #1954
  • Align Thinking fallback with main-model-only Claude mappings (#1984)
    The Claude provider form reopened with an empty Thinking model after users saved only a main model. This updates model-state hydration to mirror the existing Haiku-style fallback semantics: read ANTHROPIC_REASONING_MODEL when present, otherwise display ANTHROPIC_MODEL, without writing a synthetic reasoning field back into config.
    
    Constraint: Existing Haiku, Sonnet, and Opus selectors already rely on read-time fallback behavior
    Rejected: Persist ANTHROPIC_REASONING_MODEL from ANTHROPIC_MODEL automatically | would diverge from Haiku behavior and silently rewrite saved config
    Confidence: high
    Scope-risk: narrow
    Reversibility: clean
    Directive: Keep Thinking fallback read-only unless all model-mapping fields are intentionally migrated to write-through semantics
    Tested: pnpm typecheck
    Tested: pnpm test:unit (1 unrelated pre-existing failure in tests/components/UnifiedSkillsPanel.test.tsx mock setup)
    Not-tested: Manual add-provider reopen flow in the desktop UI
  • Restore auth tab localization in settings (#1985)
    The settings page already routes the auth tab label through the shared i18n key, but the locale bundles never defined that key. Adding the missing entries fixes the label with the same simple pattern used by the other tabs.
    
    Constraint: Keep the fix aligned with the existing settings-tab i18n flow
    Rejected: Add component-level bilingual rendering | unnecessary for a missing translation key
    Confidence: high
    Scope-risk: narrow
    Reversibility: clean
    Directive: When adding settings tabs, define locale keys in every bundled language before relying on fallback text
    Tested: pnpm format:check; pnpm typecheck
    Not-tested: Manual verification in the desktop UI
  • feat(provider): add TheRouter presets for Claude, Codex, and Gemini (#1891)
    * feat(provider): add TheRouter presets for Claude and Codex
    
    * feat(provider): add TheRouter Gemini preset
    
    ---------
    
    Co-authored-by: max <me19@qq.com>
  • Merge branch 'main' into feat/gemini-proxy-integration
    # Conflicts:
    #	src-tauri/src/proxy/providers/claude.rs
    #	src-tauri/src/proxy/sse.rs
    #	src-tauri/src/services/stream_check.rs
  • fix(session-manager): improve session search accuracy and Chinese support
    - Pre-filter sessions by provider before indexing to prevent result
      truncation when FlexSearch limit cuts across providers
    - Switch tokenizer from "forward" to "full" for Chinese substring matching
    - Preserve FlexSearch relevance ranking when search query is present
  • feat(common-config): show first-run notice dialog when editing providers
    Display a one-time informational dialog explaining the Common Config
    Snippet feature when users first open the add/edit provider form.
    Uses a derived isOpen state from settings to avoid race conditions.
    Adds commonConfigConfirmed flag to both TS and Rust settings types.
  • feat(common-config): add guide info and empty state to common config editor
    Add an informational alert block at the top of the common config snippet
    editor modal (Claude/Codex/Gemini) explaining what the feature is, why
    it exists, and how to use it. Also add an empty state prompt when no
    snippet has been extracted yet, guiding users to click "Extract from
    Editor". Includes i18n support for zh/en/ja.
  • fix(usage): only show CLI subscription quota for active provider
    CLI-credential-based subscriptions (Claude/Codex/Gemini) read from a
    single global credential file, so the quota always reflects the last
    CLI login rather than a specific provider. Showing it on non-current
    cards is misleading when multiple official subscriptions exist.
    
    Apply the same isCurrent + autoQuery pattern already used by Copilot
    and Codex OAuth: only query and render the quota footer when the
    provider is the currently active one.
  • fix(notifications): remove duplicate toast when switching to proxy providers
    When switching to Copilot/ChatGPT/OpenAI-format providers with the proxy
    not running, two toasts appeared: a "proxy required" warning followed by
    a "switch success" toast. Unify the post-switch toast logic so that all
    provider types show a single success toast, and skip it entirely when
    a proxy-required warning was already shown.
  • feat(welcome): show first-run welcome dialog on fresh install
    Introduce a one-time welcome dialog that explains CC Switch's workflow
    to new users: how their existing config is preserved as a "default"
    provider and how the bundled "Official" preset enables one-click revert.
    Upgrade users are excluded by checking is_providers_empty() at startup
    and never see the dialog.
    
    Persistence follows the existing *_confirmed convention in AppSettings
    (proxy/usage/stream_check/failover), stored in settings.json. The field
    is only written when the user explicitly clicks the confirm button,
    keeping its semantics strictly about user acknowledgement.
    
    Also adds two reusable DAO helpers:
    - Database::is_providers_empty for fresh-install detection, using
      EXISTS(SELECT 1) for a short-circuit query.
    - Database::get_bool_flag accepting "true" | "1", with
      init_default_official_providers migrated to use it.
    
    Dialog copy in zh/en/ja uses conditional phrasing so it stays
    accurate whether or not existing live config was found.
  • fix(usage): only auto-poll Copilot/ChatGPT quota for current provider
    CopilotQuotaFooter and CodexOauthQuotaFooter called their hooks with a
    hardcoded `enabled: true` plus an unconditional 5-minute refetch and
    refetchOnWindowFocus, so non-current reverse-proxy cards kept polling
    in violation of the project's "only the active provider auto-queries
    on cooldown" rule. With multiple Copilot or ChatGPT accounts bound to
    different cards, every card kept hitting its own usage endpoint.
    
    Adopt the same pattern as useUsageQuery: keep `enabled` independent of
    isCurrent so first-fetch and manual refresh still work, but gate
    refetchInterval / refetchIntervalInBackground / refetchOnWindowFocus on
    a new `autoQuery` option, and thread `isCurrent` from ProviderCard
    through the footers into the hooks.
  • feat(stream-check): support OpenCode via npm package mapping
    Phase 3: implement stream check for OpenCode providers by mapping the
    `settings_config.npm` (AI SDK package name) to the corresponding API
    protocol and delegating to the existing stream checkers.
    
    Package mapping:
    - @ai-sdk/openai-compatible → openai_chat
    - @ai-sdk/openai            → openai_responses
    - @ai-sdk/anthropic         → anthropic (ClaudeAuth strategy)
    - @ai-sdk/google            → gemini (Google strategy)
    - @ai-sdk/amazon-bedrock    → not supported (phase 4 message polish)
    
    Note: OpenCode nests baseURL/apiKey under `settings_config.options`
    (different from OpenClaw's root-level fields) and uses `baseURL` with
    a capital L. Three new extractors (base_url / api_key / npm) encode
    these shape differences so check_opencode_stream stays symmetric with
    check_openclaw_stream.
    
    Frontend: drop the remaining `appId !== "opencode"` filter in
    ProviderList.tsx — both apps can now test providers.
  • feat(stream-check): support OpenClaw openai-completions protocol
    Phase 1 of extending stream health check to OpenCode/OpenClaw apps.
    
    - Add early-dispatch path for OpenCode/OpenClaw in check_once so they
      bypass the adapter layer (which only knows Claude/Codex/Gemini
      settings_config shapes).
    - Introduce check_openclaw_stream dispatcher that reads the `api` field
      from settings_config and routes to the existing check_claude_stream
      with api_format="openai_chat" for "openai-completions". Other
      protocols return localized errors to be lit up in phases 2 and 4.
    - Extract build_stream_check_result helper to avoid duplicating the
      StreamCheckResult construction logic between the two code paths.
    - Unblock the test button for OpenClaw providers in ProviderList.tsx.
    
    OpenCode still returns the "not yet supported" error; it will be
    enabled in phase 3.