Use lazy initialization in useState to parse model values from config
on first render, matching the pattern used in useApiKeyState. This
fixes an issue where model fields were not populated in edit mode.
- Format Rust code with rustfmt (misc.rs, types.rs)
- Format TypeScript/React code with Prettier (4 files)
- Fix ProviderList test by wrapping with QueryClientProvider
- Add `enabled` parameter to useCommonConfigSnippet hook
- Skip all loading and auto-merge logic when enabled=false
- Replace CommonConfigEditor with simplified JsonEditor for OpenCode
- Prevent Claude's common config snippet from being injected into OpenCode
Fix state synchronization in useBaseUrlState and useApiKeyState hooks
to properly clear values when config is reset. Previously, when switching
from a preset to "custom", the baseUrl and apiKey states would retain
their old values because the sync logic only updated when new values
existed, not when they were cleared.
Changes:
- useBaseUrlState: Always sync baseUrl to config value (empty if undefined)
- useApiKeyState: Remove hasApiKeyField check that prevented clearing
The common config checkbox was only auto-enabled for the first preset
(custom) because hasInitializedNewMode ref was permanently locked to
true after first initialization.
Add selectedPresetId parameter to all three common config hooks and
reset the initialization flag when preset changes, allowing the
initialization logic to re-run for each preset switch.
- Change extraction source from current active provider to editor's live content
- Add i18n support for JSON parse error messages via invalid_json_format_error()
- Simplify API by removing unused providerId parameter
- Update button labels and error messages in zh/en/ja locales
- Add backend command to extract common config snippet from current provider
- Automatically extract common config on first run after importing default provider
- Auto-enable common config checkbox in new provider mode when snippet exists
- Refactor Gemini common config to operate on .env instead of config.json
- Add "Extract from Current" button to all three common config modals
- Update i18n translations for new extraction feature
* feat(proxy): extract model name from API response for accurate usage tracking
- Add model field extraction in TokenUsage parsing for Claude, OpenAI, and Codex
- Prioritize response model over request model in usage logging
- Update model extractors to use parsed usage.model first
- Add tests for model extraction in stream and non-stream responses
* feat(proxy): implement streaming timeout control with validation
- Add first byte timeout (0 or 1-180s) for streaming requests
- Add idle timeout (0 or 60-600s) for streaming data gaps
- Add non-streaming timeout (0 or 60-1800s) for total request
- Implement timeout logic in response processor
- Add 1800s global timeout fallback when disabled
- Add database schema migration for timeout fields
- Add i18n translations for timeout settings
* feat(proxy): add model mapping module for provider-based model substitution
- Add model_mapper.rs with ModelMapping struct to extract model configs from Provider
- Support ANTHROPIC_MODEL, ANTHROPIC_REASONING_MODEL, and default models for haiku/sonnet/opus
- Implement thinking mode detection for reasoning model priority
- Include comprehensive unit tests for all mapping scenarios
* fix(proxy): bypass circuit breaker for single provider scenario
When failover is disabled (single provider), circuit breaker open state
would block all requests causing poor UX. Now bypasses circuit breaker
check in this scenario. Also integrates model mapping into request flow.
* feat(ui): add reasoning model field to Claude provider form
Add ANTHROPIC_REASONING_MODEL configuration field for Claude providers,
allowing users to specify a dedicated model for thinking/reasoning tasks.
* feat(proxy): add openrouter_compat_mode for optional format conversion
Add configurable OpenRouter compatibility mode that enables Anthropic to
OpenAI format conversion. When enabled, rewrites endpoint to /v1/chat/completions
and transforms request/response formats. Defaults to enabled for OpenRouter.
* feat(ui): add OpenRouter compatibility mode toggle
Add UI toggle for OpenRouter providers to enable/disable compatibility
mode which uses OpenAI Chat Completions format with SSE conversion.
* feat(stream-check): use provider-configured model for health checks
Extract model from provider's settings_config (ANTHROPIC_MODEL, GEMINI_MODEL,
or Codex config.toml) instead of always using default test models.
* refactor(ui): remove timeout settings from AutoFailoverConfigPanel
Remove streaming/non-streaming timeout configuration from failover panel
as these settings have been moved to a dedicated location.
* refactor(database): migrate proxy_config to per-app three-row structure
Replace singleton proxy_config table with app_type primary key structure,
allowing independent proxy settings for Claude, Codex, and Gemini.
Add GlobalProxyConfig queries and per-app config management in DAO layer.
* feat(proxy): add GlobalProxyConfig and AppProxyConfig types
Add new type definitions for the refactored proxy configuration:
- GlobalProxyConfig: shared settings (enabled, address, port, logging)
- AppProxyConfig: per-app settings (failover, timeouts, circuit breaker)
* refactor(proxy): update service layer for per-app config structure
Adapt proxy service, handler context, and provider router to use
the new per-app configuration model. Read enabled/timeout settings
from proxy_config table instead of settings table.
* feat(commands): add global and per-app proxy config commands
Add new Tauri commands for the refactored proxy configuration:
- get_global_proxy_config / update_global_proxy_config
- get_proxy_config_for_app / update_proxy_config_for_app
Update startup restore logic to read from proxy_config table.
* feat(api): add frontend API and Query hooks for proxy config
Add TypeScript wrappers and TanStack Query hooks for:
- Global proxy config (address, port, logging)
- Per-app proxy config (failover, timeouts, circuit breaker)
- Proxy takeover status management
* refactor(ui): redesign proxy panel with inline config controls
Replace ProxySettingsDialog with inline controls in ProxyPanel.
Add per-app takeover switches and global address/port settings.
Simplify AutoFailoverConfigPanel by removing timeout settings.
* feat(i18n): add proxy takeover translations and update types
Add i18n strings for proxy takeover status in zh/en/ja.
Update TypeScript types for GlobalProxyConfig and AppProxyConfig.
* refactor(proxy): load circuit breaker config per-app instead of globally
Extract app_type from router key and read circuit breaker settings
from the corresponding proxy_config row for each application.
- Skip live writes when takeover is active and proxy is running
- Refresh live backups from provider edits during takeover
- Sync live tokens to DB without clobbering real keys with placeholders
- Avoid injecting extra placeholder keys into Claude live env
- Reapply takeover after proxy listen address/port changes
- In takeover mode, edit dialog uses DB config and keeps API key state in sync
- Remove `.replace(/\/+$/, "")` from all base URL handlers in useBaseUrlState.ts
- Remove trailing slash stripping in useCodexConfigState.ts
- Remove trailing slash normalization in providerConfigUtils.ts setCodexBaseUrl()
- Update i18n hints (en/ja/zh) to instruct users to avoid trailing slashes
This gives users explicit control over URL format rather than silently modifying input.
* feat(providers): add notes field for provider management
- Add notes field to Provider model (backend and frontend)
- Display notes with higher priority than URL in provider card
- Style notes as non-clickable text to differentiate from URLs
- Add notes input field in provider form
- Add i18n support (zh/en) for notes field
* chore: format code and clean up unused props
- Run cargo fmt on Rust backend code
- Format TypeScript imports and code style
- Remove unused appId prop from ProviderPresetSelector
- Clean up unused variables in tests
- Integrate notes field handling in provider dialogs
* feat(deeplink): implement ccswitch:// protocol for provider import
Add deep link support to enable one-click provider configuration import via ccswitch:// URLs.
Backend:
- Implement URL parsing and validation (src-tauri/src/deeplink.rs)
- Add Tauri commands for parse and import (src-tauri/src/commands/deeplink.rs)
- Register ccswitch:// protocol in macOS Info.plist
- Add comprehensive unit tests (src-tauri/tests/deeplink_import.rs)
Frontend:
- Create confirmation dialog with security review UI (src/components/DeepLinkImportDialog.tsx)
- Add API wrapper (src/lib/api/deeplink.ts)
- Integrate event listeners in App.tsx
Configuration:
- Update Tauri config for deep link handling
- Add i18n support for Chinese and English
- Include test page for deep link validation (deeplink-test.html)
Files: 15 changed, 1312 insertions(+)
* chore(deeplink): integrate deep link handling into app lifecycle
Wire up deep link infrastructure with app initialization and event handling.
Backend Integration:
- Register deep link module and commands in mod.rs
- Add URL handling in app setup (src-tauri/src/lib.rs:handle_deeplink_url)
- Handle deep links from single instance callback (Windows/Linux CLI)
- Handle deep links from macOS system events
- Add tauri-plugin-deep-link dependency (Cargo.toml)
Frontend Integration:
- Listen for deeplink-import/deeplink-error events in App.tsx
- Update DeepLinkImportDialog component imports
Configuration:
- Enable deep link plugin in tauri.conf.json
- Update Cargo.lock for new dependencies
Localization:
- Add Chinese translations for deep link UI (zh.json)
- Add English translations for deep link UI (en.json)
Files: 9 changed, 359 insertions(+), 18 deletions(-)
* refactor(deeplink): enhance Codex provider template generation
Align deep link import with UI preset generation logic by:
- Adding complete config.toml template matching frontend defaults
- Generating safe provider name from sanitized input
- Including model_provider, reasoning_effort, and wire_api settings
- Removing minimal template that only contained base_url
- Cleaning up deprecated test file deeplink-test.html
* style: fix clippy uninlined_format_args warnings
Apply clippy --fix to use inline format arguments in:
- src/mcp.rs (8 fixes)
- src/services/env_manager.rs (10 fixes)
* style: apply code formatting and cleanup
- Format TypeScript files with Prettier (App.tsx, EnvWarningBanner.tsx, formatters.ts)
- Organize Rust imports and module order alphabetically
- Add newline at end of JSON files (en.json, zh.json)
- Update Cargo.lock for dependency changes
* feat: add model name configuration support for Codex and fix Gemini model handling
- Add visual model name input field for Codex providers
- Add model name extraction and update utilities in providerConfigUtils
- Implement model name state management in useCodexConfigState hook
- Add conditional model field rendering in CodexFormFields (non-official only)
- Integrate model name sync with TOML config in ProviderForm
- Fix Gemini deeplink model injection bug
- Correct environment variable name from GOOGLE_GEMINI_MODEL to GEMINI_MODEL
- Add test cases for Gemini model injection (with/without model)
- All tests passing (9/9)
- Fix Gemini model field binding in edit mode
- Add geminiModel state to useGeminiConfigState hook
- Extract model value during initialization and reset
- Sync model field with geminiEnv state to prevent data loss on submit
- Fix missing model value display when editing Gemini providers
Changes:
- 6 files changed, 245 insertions(+), 13 deletions(-)
- Add unified `common_config_snippets` structure to MultiAppConfig
- Implement `get_common_config_snippet` and `set_common_config_snippet` commands
- Replace localStorage with config.json persistence for Codex and Gemini
- Auto-migrate legacy `claude_common_config_snippet` to new unified structure
- Deprecate individual API methods in favor of unified interface
- Add automatic migration from localStorage on first load
BREAKING CHANGE: Common config snippets now stored in unified `common_config_snippets` object instead of separate fields
Implements dual-editor pattern for Gemini providers, following the Codex architecture:
- Environment variables (.env format) editor
- Extended configuration (config.json) editor with common config support
New Components:
- GeminiConfigSections: Separate sections for env and config editing
- GeminiCommonConfigModal: Modal for editing common config snippets
New Hooks:
- useGeminiConfigState: Manages env/config separation and conversion
- Converts between .env string format and JSON object
- Validates JSON config structure
- Extracts API Key and Base URL from env
- useGeminiCommonConfig: Handles common config snippets
- Deep merge algorithm for combining configs
- Remove common config logic for toggling off
- localStorage persistence for snippets
Features:
- Format buttons for both env and config editors
- Common config toggle with deep merge/remove
- Error validation and display
- Auto-open modal on common config errors
Configuration Structure:
{
"env": {
"GOOGLE_GEMINI_BASE_URL": "https://...",
"GEMINI_API_KEY": "sk-...",
"GEMINI_MODEL": "gemini-2.5-pro"
},
"config": {
"timeout": 30000,
"maxRetries": 3
}
}
This brings Gemini providers to feature parity with Claude and Codex.
Migrate the Claude common config snippet storage from browser localStorage
to the persistent config.json file for better cross-device sync and backup support.
**Backend Changes:**
- Add `claude_common_config_snippet` field to `MultiAppConfig` struct
- Add `get_claude_common_config_snippet` and `set_claude_common_config_snippet` Tauri commands
- Include JSON validation in the setter command
**Frontend Changes:**
- Create new `lib/api/config.ts` API module
- Refactor `useCommonConfigSnippet` hook to use config.json instead of localStorage
- Add automatic one-time migration from localStorage to config.json
- Add loading state during initialization
**Benefits:**
- Cross-device synchronization via backup/restore
- More reliable persistence than browser storage
- Centralized configuration management
- Seamless migration for existing users
Optimize custom endpoint management logic to distinguish between edit and create modes:
- Edit mode: endpoints are read/written directly to backend via API
- Create mode: use draftCustomEndpoints to stage, save on submit
- Remove duplicate endpoint loading in useSpeedTestEndpoints
- Add isSaving state and initialCustomUrls tracking
The base URL field was not populating when editing providers with
categories like cn_official or aggregator. The issue was caused by
inconsistent conditional logic: the input field was shown for all
non-official categories, but the value extraction only worked for
third_party and custom categories.
Changed the category check from allowlist (third_party, custom) to
denylist (official) to match the UI display logic. Now ANTHROPIC_BASE_URL
correctly populates for all provider categories except official.
- Add Z.ai GLM as official partner with promotion support
- Fix apiKeyUrl not being prioritized for cn_official and aggregator categories
- Now apiKeyUrl (with promotion parameters) takes precedence over websiteUrl for cn_official, aggregator, and third_party categories
- Add isPartner and partnerPromotionKey fields to Provider and ProviderPreset types
- Display gold star badge on partner presets in selector
- Show promotional message in API Key section for partners
- Configure Zhipu GLM as official partner with 10% discount promotion
- Support both Claude and Codex provider presets
- Add i18n support for partner promotion messages (zh/en)
- Add useEffect to automatically extract and sync OPENAI_API_KEY from codexAuth to codexApiKey state
- Fix issue where API key wasn't populated in form after applying configuration wizard
- Optimize handleCodexApiKeyChange to trim input upfront
- Move getCodexAuthApiKey function closer to usage for better code organization
- Remove redundant dependency from useEffect dependency array
- Rename src/config/providerPresets.ts to claudeProviderPresets.ts
- Update all import statements across 11 files to reflect the new name
- Establish symmetrical naming convention with codexProviderPresets.ts
- Improve code clarity and maintainability
Remove KimiModelSelector component and useKimiModelSelector hook to
eliminate code duplication and unify model configuration across all
Claude-compatible providers.
**Problem Statement:**
Previously, we maintained two separate implementations for the same
functionality:
- KimiModelSelector: API-driven dropdown with 211 lines of code
- ClaudeFormFields: Simple text inputs for model configuration
After removing API fetching logic from KimiModelSelector, both components
became functionally identical (4 text inputs), violating DRY principle
and creating unnecessary maintenance burden.
**Changes:**
Backend (Rust):
- No changes (model normalization logic already in place)
Frontend (React):
- Delete KimiModelSelector.tsx (-211 lines)
- Delete useKimiModelSelector.ts (-142 lines)
- Update ClaudeFormFields.tsx: remove Kimi-specific props (-35 lines)
- Update ProviderForm.tsx: unify display logic (-31 lines)
- Clean up hooks/index.ts: remove useKimiModelSelector export (-1 line)
Configuration:
- Update Kimi preset: kimi-k2-turbo-preview → kimi-k2-0905-preview
* Uses official September 2025 release
* 256K context window (vs 128K in older version)
Internationalization:
- Remove kimiSelector.* i18n keys (15 keys × 2 languages = -36 lines)
- Remove providerForm.kimiApiKeyHint
**Unified Architecture:**
Before (complex branching):
ProviderForm
├─ if Kimi → useKimiModelSelector → KimiModelSelector (4 inputs)
└─ else → useModelState → ClaudeFormFields inline (4 inputs)
After (single path):
ProviderForm
└─ useModelState → ClaudeFormFields (4 inputs for all providers)
Display logic simplified:
- Old: shouldShowModelSelector = category !== "official" && !shouldShowKimiSelector
- New: shouldShowModelSelector = category !== "official"
**Impact:**
Code Quality:
- Remove 457 lines of redundant code (-98.5%)
- Eliminate dual-track maintenance
- Improve code consistency
- Pass TypeScript type checking with zero errors
- Zero remaining references to deleted code
User Experience:
- Consistent UI across all providers (including Kimi)
- Same model configuration workflow for everyone
- No functional changes from user perspective
Architecture:
- Single source of truth for model configuration
- Easier to extend for future providers
- Reduced bundle size (removed lucide-react icons dependency)
**Testing:**
- ✅ TypeScript compilation passes
- ✅ No dangling references
- ✅ All model configuration fields functional
- ✅ Display logic works for official/cn_official/aggregator categories
**Migration Notes:**
- Existing Kimi users: configurations automatically upgraded to new model name
- No manual intervention required
- Backend normalization ensures backward compatibility
Upgrade Claude model configuration from dual-key to quad-key system for
better model tier differentiation.
**Breaking Changes:**
- Replace `ANTHROPIC_SMALL_FAST_MODEL` with three granular keys:
- `ANTHROPIC_DEFAULT_HAIKU_MODEL`
- `ANTHROPIC_DEFAULT_SONNET_MODEL`
- `ANTHROPIC_DEFAULT_OPUS_MODEL`
**Backend (Rust):**
- Add `normalize_claude_models_in_value()` for automatic migration
- Implement fallback chain: `DEFAULT_* || SMALL_FAST || MODEL`
- Auto-cleanup: remove legacy `SMALL_FAST` key after normalization
- Apply normalization across 6 critical paths:
- Add/update provider
- Read from live config
- Write to live config
- Refresh config snapshot
**Frontend (React):**
- Expand UI from 2 to 4 model input fields
- Implement smart fallback in `useModelState` hook
- Update `useKimiModelSelector` for Kimi model picker
- Add i18n keys for Haiku/Sonnet/Opus labels (zh/en)
**Configuration:**
- Update all 7 provider presets to new format
- DeepSeek/Qwen/Moonshot: use same model for all tiers
- Zhipu: preserve tier differentiation (glm-4.5-air for Haiku)
**Backward Compatibility:**
- Old configs auto-upgrade on first read/write
- Fallback chain ensures graceful degradation
- No manual migration required
Closes #[issue-number]
Rename `AppType` to `AppId` across the entire frontend codebase to better
reflect its purpose as an application identifier rather than a type category.
This aligns frontend naming with backend command parameter conventions.
Changes:
- Rename type `AppType` to `AppId` in src/lib/api/types.ts
- Remove `AppType` export from src/lib/api/index.ts
- Update all component props from `appType` to `appId` (43 files)
- Update all variable names from `appType` to `appId`
- Synchronize documentation (CHANGELOG, refactoring plans)
- Update test files and MSW mocks
BREAKING CHANGE: `AppType` type is no longer exported. Use `AppId` instead.
All component props have been renamed from `appType` to `appId`.
- Unify EndpointCandidate type definition in types.ts
- Remove all 'as any' type assertions in useSpeedTestEndpoints
- Add cleanup function to prevent race conditions in async operations
- Fix stale error messages persisting after successful deletion
- Improve error handling for endpoint deletion (distinguish not-found vs network errors)
- Extract timeout magic numbers to ENDPOINT_TIMEOUT_SECS constant
- Clarify URL validation to explicitly allow only http/https
- Fix ambiguous payload.meta assignment logic in ProviderForm
- Add i18n for new error messages (removeFailed, updateLastUsedFailed)
Improved useApiKeyState hook to explicitly handle category parameter:
- Only create apiKey field for non-official providers in add mode
- Explicitly check category !== undefined to avoid unintended behavior
- Added comprehensive comments explaining the logic
- Updated dependency array to include category parameter
This ensures official provider configs remain clean without empty apiKey fields.
- Add smol-toml dependency for client-side TOML parsing
- Create useCodexTomlValidation hook with 500ms debounce
- Display validation errors below config.toml textarea
- Trigger validation on onChange for immediate user feedback
- Backend validation remains as fallback for data integrity
- Create useSpeedTestEndpoints hook that collects endpoints from:
1. Current baseUrl/codexBaseUrl
2. Initial data URL (edit mode)
3. Preset's endpointCandidates array
- Pass speedTestEndpoints to ClaudeFormFields and CodexFormFields
- Update EndpointSpeedTest to use collected endpoints instead of just current URL
- Fix PackyCode preset endpoints not appearing in speed test modal
Fixed logic in useApiKeyState.ts to correctly show API Key input
when editing existing providers. Changed condition from
`!isEditMode && hasApiKeyField(config)` to
`isEditMode && hasApiKeyField(config)` to match the original
form's behavior.
This restores the ability to directly update API keys when
editing existing providers that have API key fields in their
configuration.
- Created useCodexCommonConfig hook for managing Codex TOML common config
- Persists to localStorage with key 'cc-switch:codex-common-config-snippet'
- Added isCodexTemplateModalOpen state to ProviderForm
- Connected all CodexConfigEditor props:
- Common config snippet management (useCommonConfig, handlers)
- Template modal state (isTemplateModalOpen, setIsTemplateModalOpen)
- Form field callbacks (onWebsiteUrlChange, onNameChange)
- Custom mode detection (isCustomMode)
- Hook structure mirrors useCommonConfigSnippet for consistency
- Create useCommonConfigSnippet hook to manage common config state
- Create CommonConfigEditor component with modal for editing
- Support merging/removing common config snippets from provider configs
- Persist common config to localStorage for reuse across providers
- Auto-detect if provider config contains common snippet
- Replace JSON editor with CommonConfigEditor in ProviderForm
- Create useTemplateValues hook to manage template variable state
- Support dynamic placeholder replacement in provider configs
- Add template parameter input UI in provider form
- Validate required template values before submission
- Auto-update config when template values change
- Create useKimiModelSelector hook to manage Kimi-specific state
- Auto-detect Kimi providers by preset name or config content
- Support model initialization from existing config in edit mode
- Sync model selections with JSON configuration
- Maintain clean separation between UI and business logic
- Create useApiKeyLink hook to manage API Key retrieval link display and URL
- Create useCustomEndpoints hook to collect endpoints from multiple sources
- Simplify ProviderForm by using these new hooks
- Reduce code duplication and improve maintainability
- Fix TypeScript error with form.watch("websiteUrl") by providing default empty string
- Create custom hooks for state management:
- useProviderCategory: manages provider category state
- useApiKeyState: manages API key input with auto-sync to config
- useBaseUrlState: manages base URL for Claude and Codex
- useModelState: manages model selection state
- Integrate API key input into simplified ProviderForm:
- Add ApiKeyInput component for Claude mode
- Auto-populate API key into settings config
- Disable for official providers
- Fix EndpointSpeedTest type errors:
- Fix import paths to use @ alias
- Add temporary type definitions
- Format all TODO comments properly
- Remove incorrect type assertions
- Comment out unimplemented window.api checks
All TypeScript type checks now pass.