Commit Graph

937 Commits

  • Align Thinking fallback with main-model-only Claude mappings
    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
  • 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.
  • fix(providers): disable test/usage buttons for Copilot and Codex OAuth cards
    These OAuth providers ship with non-empty ANTHROPIC_BASE_URL, so the
    isOfficialProvider() heuristic (which checks for a missing base URL)
    returned false and left the health-check and usage-config buttons
    enabled — inconsistent with other official OAuth cards. Extend the
    button disabling logic at the call site with isCopilot / isCodexOauth,
    matching the pattern already used for the quota footer branch above.
  • chore(presets): bump Codex OAuth preset to GPT-5.4 family
    Update the "Codex (ChatGPT Plus/Pro)" entry in Claude Code presets to
    the new GPT-5.4 naming, which drops the legacy `-codex` suffix. Map the
    Haiku tier to `gpt-5.4-mini` for lower-cost lightweight calls while
    keeping Sonnet/Opus on the standard `gpt-5.4`.
  • fix(linux): repair unresponsive UI on startup and full-screen panels
    Linux users reported the window UI (including native title bar buttons)
    couldn't receive clicks until manually maximizing and restoring the
    window. Root causes: (1) Tauri webview did not acquire focus on startup
    so first clicks were consumed by X11/Wayland click-to-activate
    (Tauri #10746, wry #637); (2) GTK surface input region failed to
    renegotiate on the visible:false + show() path under some
    WebKitGTK/compositor combinations.
    
    - Add linux_fix::nudge_main_window helper that performs set_focus plus
      a ±1px no-op resize after window show, with a 500ms reconciliation
      readback to compensate for dropped resize requests on slow
      compositors.
    - Wire the helper into every window re-show path: normal startup,
      deeplink, single_instance, tray show_main, and lightweight exit.
    - Set WEBKIT_DISABLE_COMPOSITING_MODE=1 at startup to avoid resize
      crashes and Wayland surface negotiation issues.
    - Remove data-tauri-drag-region on Linux from App.tsx header and the
      shared FullScreenPanel (used by all provider/MCP/workspace forms)
      to avoid Tauri #13440 in Wayland sessions. Extract drag-region
      constants to src/lib/platform.ts for reuse.
    
    All Rust changes are gated by #[cfg(target_os = "linux")]; frontend
    changes preserve macOS/Windows behavior via runtime isLinux() checks.
    Known limitation: tiling Wayland compositors ignore set_size, so
    GDK_BACKEND=x11 remains the user-side workaround.
  • i18n(zh): unify Skills terminology in settings labels
    Use "Skills" consistently in skillStorage title/description and
    skillSync title to match the upstream Agent Skills wording and the
    existing English label style used elsewhere on the settings page.
  • refactor: tighten OAuth Auth Center copy, layout, and icon
    - Trim Auth Center section descriptions to focus on user intent
    - Remove duplicate outer heading on the auth settings tab
    - Swap Sparkles glyph for CodexIcon on the ChatGPT card
    - Generalize codexOauth.authStatus to a neutral "Auth status"
    - Register settings.authCenter.* keys across zh/en/ja locales
  • feat: display subscription quota for Codex OAuth provider cards
    Codex OAuth (ChatGPT Plus/Pro) providers previously fell through to the
    default UsageFooter branch and showed no quota at all, while Copilot and
    official Codex providers already had a wham/usage-backed quota footer.
    
    This wires up the same five-hour / seven-day tier badges for codex_oauth
    provider cards by reusing the existing query_codex_quota function and
    SubscriptionQuotaFooter rendering, parameterized to keep both the CLI
    credential path ("codex") and the cc-switch managed OAuth path
    ("codex_oauth") working from a single source of truth.
    
    - Parameterize services::subscription::query_codex_quota with tool_label
      and expired_message; promote SubscriptionQuota constructors to
      pub(crate). The CLI path keeps its existing "codex" label and the
      "re-login with Codex CLI" message; the new path passes "codex_oauth"
      and a cc-switch-specific re-login hint.
    - Add a new get_codex_oauth_quota Tauri command in commands/codex_oauth.rs
      that resolves the ChatGPT account (explicit binding > default account
      > not_found), pulls a valid access_token from CodexOAuthManager
      (auto-refresh handled), and delegates to query_codex_quota.
    - Extract SubscriptionQuotaFooter's render body into a pure
      SubscriptionQuotaView component (props: quota / loading / refetch /
      appIdForExpiredHint / inline). The existing SubscriptionQuotaFooter
      becomes a thin wrapper with identical props and behavior, so
      CopilotQuotaFooter and the official Claude/Codex/Gemini paths are
      untouched. This avoids duplicating ~280 lines of five-state rendering.
    - Add CodexOauthQuotaFooter, a 38-line wrapper that calls the new
      useCodexOauthQuota hook and forwards to SubscriptionQuotaView.
    - ProviderCard inserts an isCodexOauth branch between isCopilot and
      isOfficial, keyed off PROVIDER_TYPES.CODEX_OAUTH (newly added to
      config/constants.ts to centralize the previously scattered string).
    - Frontend hook caches per (codex_oauth, accountId) so multiple cards
      bound to the same ChatGPT account share one fetch via react-query
      dedup; cards bound to different accounts get independent fetches.
    - No new i18n keys: existing subscription.fiveHour / sevenDay / expired /
      refresh / queryFailed / expiredHint are reused.
  • 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: resolve rustfmt formatting and clippy warnings
    - Apply cargo fmt across schema.rs, session_usage*.rs, skill.rs, usage_stats.rs
    - Fix clippy::for_kv_map: use messages.values() instead of (_, msg) pattern
    - Suppress clippy::only_used_in_recursion for intentional recursive base path
    - Fix prettier formatting in UsageScriptModal.tsx
  • feat: display Copilot premium interactions quota on provider card
    Copilot usage query API was implemented but never surfaced on the main
    provider list. Add CopilotQuotaFooter component that auto-detects
    github_copilot providers and displays premium interaction utilization
    inline, reusing the existing TierBadge UI from SubscriptionQuotaFooter.
  • feat: add per-app usage filtering (Claude/Codex/Gemini)
    Add dashboard-level app type filter to usage statistics, replacing the
    DataSourceBar with a more useful segmented control. All components
    (summary cards, trend chart, provider stats, model stats, request logs)
    now respond to the selected app filter.
    
    Backend: add optional app_type parameter to get_usage_summary,
    get_daily_trends, get_provider_stats, and get_model_stats queries.
    Frontend: new AppTypeFilter type, updated query keys with appType
    dimension for proper cache separation, and RequestLogTable local
    filter auto-locks when dashboard filter is active.
  • feat: add Gemini CLI session log usage tracking
    Parse ~/.gemini/tmp/*/chats/session-*.json for precise per-message
    token data (input/output/cached/thoughts). Integrates with existing
    background sync and manual sync button alongside Claude and Codex.
  • feat: replace Codex estimated usage with precise JSONL session log parsing
    Replace the 70/30 input/output token estimation from state_5.sqlite
    with precise parsing of Codex CLI JSONL session logs (~/.codex/sessions/).
    
    - Parse event_msg (token_count), turn_context, and session_meta events
    - Compute exact input/output/cached token deltas from cumulative totals
    - Reuse session_log_sync table for incremental file scanning
    - Pre-filter lines with string contains() before JSON deserialization
    - Add codex_session data source to DataSourceBar with i18n (zh/en/ja)
  • feat: add session log usage tracking without proxy
    Parse Claude Code JSONL session files (~/.claude/projects/) and Codex
    SQLite database (~/.codex/state_5.sqlite) to track API usage without
    requiring proxy interception. This enables usage statistics for users
    who don't use the proxy feature.
    
    Key changes:
    - Add session_usage.rs: incremental JSONL parser with message.id dedup
    - Add session_usage_codex.rs: import thread-level token data from Codex
    - Add data_source column to proxy_request_logs (proxy/session_log/codex_db)
    - Add session_log_sync table for tracking parse offsets
    - Background sync every 60s + manual sync via DataSourceBar UI
    - Schema migration v7→v8
    - i18n support for zh/en/ja
  • fix: hide empty description and fix broken skill link for skills.sh results
    - Hide "暂无描述" text when skill has no description (skills.sh API
      doesn't return descriptions), show empty spacer instead
    - Change skills.sh result link from guessed subdirectory path to repo
      root URL, since skillId doesn't reflect the actual nested path
  • feat: integrate skills.sh search for discovering skills from public registry
    Add skills.sh API integration allowing users to search and install from
    a catalog of 91K+ agent skills directly within CC Switch. The search
    results are converted to DiscoverableSkill objects and reuse the existing
    install pipeline. Includes fallback directory search for repos where
    skills are nested in subdirectories, and filters out non-GitHub sources.
  • feat: add skill storage location toggle between CC Switch and ~/.agents/skills
    Allow users to choose between storing skills in CC Switch's managed
    directory (~/.cc-switch/skills/) or the Agent Skills open standard
    directory (~/.agents/skills/). Includes migration logic that safely
    moves files before updating settings, with confirmation dialog for
    non-empty installations.
  • fix: animate "Update All" button sliding in from the left of "Check Updates"
    Use max-width + opacity CSS transition so the button smoothly expands
    into view instead of popping in abruptly.
  • feat: add "Update All" button for batch skill updates
    Show an "Update All (N)" button next to "Check Updates" when updates
    are available. Sequentially updates each skill and reports results.
  • feat: add skill update detection via SHA-256 content hashing
    - Add content_hash and updated_at fields to skills table (DB migration v6→v7)
    - Compute directory content hash on install/import/restore for version tracking
    - Add check_updates command: downloads repos, compares hashes, returns update list
    - Add update_skill command: backs up old files, re-downloads and replaces SSOT
    - Backfill content_hash for existing skills on first update check
    - Add "Check Updates" button and per-skill update badge/button in UnifiedSkillsPanel
    - Add i18n keys for zh/en/ja
  • fix: set default auto-query interval to 5min and fix number input clearing
    - Change default autoQueryInterval from 0 (disabled) to 5 minutes for
      new usage scripts (Token Plan, Balance, and general templates)
    - Fix controlled number inputs (timeout & interval) that couldn't be
      cleared: defer validation to onBlur so users can delete and retype
  • fix: align usage display across provider cards by always rendering action buttons
    Test and ConfigureUsage buttons are now always rendered instead of
    conditionally, with a disabled style for providers that don't support
    them (e.g. official subscriptions). This ensures consistent button
    container width so the usage display aligns uniformly across all cards.
  • feat: add official balance query for DeepSeek, StepFun, SiliconFlow, OpenRouter, Novita AI
    Add a new "Official" (官方) template type in the usage query panel that
    queries account balance via each provider's native API endpoint.
    Follows the same zero-script pattern as Token Plan — Rust handles the
    HTTP call, frontend auto-detects the provider from base URL.
    
    Supported providers and endpoints:
    - DeepSeek: GET /user/balance
    - StepFun: GET /v1/accounts
    - SiliconFlow: GET /v1/user/info (cn + com)
    - OpenRouter: GET /api/v1/credits
    - Novita AI: GET /v3/user/balance
  • fix: add padding to toolbar container to prevent add button shadow clipping (#1951)
    The orange add button's circular shadow appeared square because the
    parent overflow-x-hidden container was clipping it at the edges.
  • 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.
  • 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.
  • 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>