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.
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
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.
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): 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>
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(provider): show persistent config highlight for additive-mode providers
Closes https://github.com/farion1231/cc-switch/issues/1692
* fix(provider): limit persistent config highlight to opencode only
* Add directory picker before launching Claude terminal
* fix(terminal): preserve cwd path and strip Windows verbatim prefix
- Stop trimming non-empty paths so directories with leading/trailing
spaces on Unix are handled correctly
- Strip \\?\ extended-length prefix from canonicalized paths on Windows
to prevent batch script cd failures
* fix(terminal): restore UNC paths when stripping Windows verbatim prefix
Handle \\?\UNC\server\share form separately from regular \\?\ prefix,
converting it back to \\server\share so network/WSL directory paths
remain valid in batch cd commands.
* fix(terminal): use pushd for UNC paths in Windows batch launcher
`cmd.exe` cannot set a UNC path (e.g. `\\wsl$\...`) as the current
directory via `cd /d`; it errors with "CMD does not support UNC paths
as current directories". Switch to `pushd` which temporarily maps the
UNC share to a drive letter.
Rename `build_windows_cd_command` → `build_windows_cwd_command` to
reflect the broader semantics. Extract `build_windows_cwd_command_str`
and `is_windows_unc_path` helpers for testability, and add unit tests
covering drive paths, UNC paths, and batch metacharacter escaping.
Also fix minor style issues: sort mod declarations alphabetically,
add missing EOF newline in lightweight.rs, add explicit type annotation
in streaming_responses test, and reformat tray menu builder chain.
* style(frontend): reformat provider forms, constants and hooks
Apply prettier formatting across 5 frontend files. No logic changes.
Changed files:
- AddProviderDialog.tsx: reformat generic type annotation and callback
- ClaudeFormFields.tsx: consolidate multi-line useState and Collapsible props
- CodexConfigSections.tsx: expand single-line React imports to multi-line,
collapse removeCodexTopLevelField() call
- constants.ts: merge TemplateType into single line
- useSkills.ts: expand single-line TanStack Query imports to multi-line,
reformat uninstallSkill mutationFn chain
* deps(proxy): add hyper ecosystem crates and manual decompression libs
reqwest internally normalizes all header names to lowercase and does not
preserve insertion order, causing proxied requests to differ from the
original client requests. To achieve transparent header forwarding with
original casing and order, introduce lower-level hyper HTTP client libs.
New dependencies:
- hyper-util 0.1: TokioExecutor + legacy Client with
preserve_header_case support for HTTP/1.1
- hyper-rustls 0.27: rustls-based TLS connector for hyper
- http 1 / http-body 1 / http-body-util 0.1: HTTP type crates for
hyper 1.x request/response construction
- flate2 1: manual gzip/deflate decompression (replaces reqwest auto)
- brotli 7: manual brotli decompression
Changed dependencies:
- serde_json: enable preserve_order feature to keep JSON field order
- reqwest: drop gzip feature to prevent reqwest from overriding the
client's original accept-encoding header
* refactor(proxy): use hyper client for header-case preserving forwarding
Previously the proxy used reqwest for all upstream requests. reqwest
normalizes header names to lowercase and reorders them internally,
making proxied requests distinguishable from direct CLI requests.
Some upstream providers are sensitive to these differences.
This commit replaces reqwest with a hyper-based HTTP client on the
default (non-proxy) path, achieving wire-level header fidelity:
Server layer (server.rs):
- Replace axum::serve with a manual hyper HTTP/1.1 accept loop
- Enable preserve_header_case(true) so incoming header casing is
captured in a HeaderCaseMap extension on each request
- Bridge hyper requests to axum Router via tower::Service
New hyper client module (hyper_client.rs):
- Lazy-initialized hyper-util Client with preserve_header_case
- ProxyResponse enum wrapping both hyper::Response and reqwest::Response
behind a unified interface (status, headers, bytes, bytes_stream)
- send_request() builds requests with ordered HeaderMap + case map
Request handlers (handlers.rs):
- Switch from (HeaderMap, Json<Value>) extractors to raw
axum::extract::Request to preserve Extensions (containing the
HeaderCaseMap from the accept loop)
- Pass extensions through the forwarding chain
Forwarder (forwarder.rs):
- Remove HEADER_BLACKLIST array; replace with ordered header iteration
that preserves original header sequence and casing
- Build ordered_headers by iterating client headers, skipping only
auth/host/content-length, and inserting auth headers at the original
authorization position to maintain order
- Handle anthropic-beta (ensure claude-code-20250219 tag) and
anthropic-version (passthrough or default) inline during iteration
- Remove should_force_identity_encoding() — accept-encoding is now
transparently forwarded to upstream
- Use hyper client by default; fall back to reqwest only when an
HTTP/SOCKS5 proxy tunnel is configured
Provider adapters (adapter.rs, claude.rs, codex.rs, gemini.rs):
- Replace add_auth_headers(RequestBuilder) -> RequestBuilder with
get_auth_headers(AuthInfo) -> Vec<(HeaderName, HeaderValue)>
- Adapters now return header pairs instead of mutating a reqwest builder
- Claude adapter: merge Anthropic/ClaudeAuth/Bearer into single branch;
move Copilot fingerprint headers into get_auth_headers
Response processing (response_processor.rs):
- Add manual decompression (gzip/deflate/brotli via flate2 + brotli)
for non-streaming responses, since reqwest auto-decompression is now
disabled to allow accept-encoding passthrough
- Add compressed-SSE warning log for streaming responses
- Accept ProxyResponse instead of reqwest::Response
HTTP client (http_client.rs):
- Disable reqwest auto-decompression (.no_gzip/.no_brotli/.no_deflate)
on both global and per-provider clients
Streaming adapters (streaming.rs, streaming_responses.rs):
- Generalize stream error type from reqwest::Error to generic E: Error
Misc:
- log_codes.rs: add SRV-005 (ACCEPT_ERR) and SRV-006 (CONN_ERR)
- stream_check.rs: reformat copilot header lines
- transform.rs: fix trailing whitespace alignment
* fix(lint): resolve 35 clippy warnings across Rust codebase
Fix all clippy warnings reported by `cargo clippy --lib`:
- codex_config.rs: fix doc_overindented_list_items (3 spaces -> 2)
- commands/copilot.rs: inline format args in 2 log::error! calls
- commands/provider.rs: inline format args in 3 map_err closures
- proxy/hyper_client.rs: inline format arg in log::debug! call
- proxy/providers/copilot_auth.rs: inline format args in 16 locations
(log macros, format! in headers, error constructors)
- proxy/thinking_optimizer.rs: inline format args in 2 log::info! calls
- services/skill.rs: inline format args in log::debug! call
- services/webdav_sync.rs: inline format args in 6 format! calls
(version compat messages, download limit messages)
- services/webdav_sync/archive.rs: inline format args in 2 format! calls
- session_manager/providers/opencode.rs: inline format args in
source_path format!
All fixes use the clippy::uninlined_format_args suggestion pattern:
format!("msg: {}", var) -> format!("msg: {var}")
* deps(proxy): add raw HTTP write and native TLS cert dependencies
Add crates required for the raw TCP/TLS write path that bypasses
hyper's header encoder to preserve original header name casing:
- httparse: parse raw TCP peek bytes to capture header casings
- tokio-rustls + rustls: direct TLS connections for raw write path
- webpki-roots: Mozilla CA bundle baseline
- rustls-native-certs: load system keychain CAs (trusts proxy MITM
certificates from Clash, mitmproxy, etc.)
* fix(proxy): address code review feedback on response handling
Fixes from PR #1714 code review:
- Extract `read_decoded_body()` and `strip_entity_headers_for_rebuilt_body()`
in response_processor to properly clean content-encoding/content-length
headers after decompression
- Reuse `read_decoded_body()` in handlers.rs for Claude transform path,
ensuring compressed responses are decoded before format conversion
- Make `build_proxy_url_from_config()` public so forwarder can pass proxy
URL to the hyper raw write path
- Add `has_system_proxy_env()` utility with test coverage
- Add 50ms backoff after accept() failures in server.rs to prevent
tight-loop CPU spin on transient socket errors
* feat(proxy): implement raw TCP/TLS write with HTTP CONNECT tunnel
Rewrite hyper_client with a two-tier strategy for header case preservation:
Primary path (raw write):
- Peek raw TCP bytes in server.rs to capture OriginalHeaderCases before
hyper lowercases them
- Build raw HTTP/1.1 request bytes with exact original header name casing
- Write directly to TLS stream, then use WriteFilter to let hyper parse
the response while discarding its duplicate request writes
- Support HTTP CONNECT tunneling through upstream proxies, so header case
is preserved even when a proxy (Clash, V2Ray) is configured
Fallback path (hyper-util Client):
- Used when OriginalHeaderCases is empty or raw write fails
- Configured with title_case_headers(true) for best-effort casing
TLS improvements:
- Load native system certificates alongside webpki roots so proxy MITM
CAs (installed in system keychain) are trusted through CONNECT tunnels
Key types added:
- OriginalHeaderCases: maps lowercase name → original wire-casing bytes
- WriteFilter<S>: AsyncRead+AsyncWrite wrapper that discards writes
- connect_via_proxy(): HTTP CONNECT tunnel establishment
- ExtensionDebugMarker: diagnostic marker for extension chain debugging
* refactor(proxy): route requests through hyper with proxy-aware forwarding
Rework forwarder request dispatch to always prefer the hyper raw write
path (header case preservation) over reqwest:
Request routing:
- HTTP/HTTPS proxy: hyper raw write through CONNECT tunnel (case preserved)
- SOCKS5 proxy: reqwest fallback (CONNECT not supported for SOCKS5)
- No proxy: hyper raw write direct connection
Header handling improvements:
- Replace host header in-place at original position instead of
skip-and-append, preserving client's header ordering
- Preserve client's original accept-encoding for transparent passthrough;
only force identity encoding when transform path needs decompression
- Add should_force_identity_encoding() to centralize the decision
- Remove hardcoded 'br, gzip, deflate' override that masked client values
Proxy URL resolution (priority order):
1. Provider-specific proxy config (if enabled)
2. Global proxy URL configured in CC Switch
3. Direct connection (no proxy)
* chore(proxy): remove dead code, redundant tests and debug scaffolding
- Inline should_force_identity_encoding() (was just `needs_transform`)
and delete its 5 test cases
- Remove ExtensionDebugMarker diagnostic type
- Remove unused has_system_proxy_env() and its test
- Remove strip_entity_headers test
- Simplify hyper path: remove redundant is_socks_proxy ternary
- Update hyper_client module doc to reflect CONNECT tunnel support
* fix(proxy): block direct-connect fallback and complete CONNECT tunnel support
* feat(hooks): improve proxy requirement warnings with specific reasons
- Remove redundant OpenAI format hint toast messages
- Add detailed reason detection for proxy requirements (OpenAI Chat, OpenAI Responses, full URL mode)
- Update i18n files with new reason-specific keys
* style(*): format code with prettier
- Remove extra whitespace in http_client.rs
- Fix formatting issues in useProviderActions.ts
* fix(proxy): post-merge fixes for forward return type and clippy warnings
- Restore forward() return type to (ProxyResponse, Option<String>)
to pass claude_api_format through to callers
- Inline format args in log::warn! macro (clippy::uninlined_format_args)
- Suppress too_many_arguments for check_claude_stream
* refactor(proxy): preserve original header wire order and add non-streaming body timeout
- Rewrite build_raw_request to emit headers in original
client-sent sequence instead of hash-map order
- Remove unused OriginalHeaderCases::get_all method
- Add body_timeout to read_decoded_body to prevent
requests hanging when upstream stalls after headers
* fix: route copilot claude openai models to responses
* fix(i18n): add copilotProxyHint translation key for all locales
The copilotProxyHint message was using inline defaultValue with Chinese
text, which would show Chinese to English and Japanese users. Added
proper translation keys in zh/en/ja locale files and removed the
hardcoded defaultValue fallback.
---------
Co-authored-by: Jason <farion1231@gmail.com>
* feat(proxy): add full URL mode and refactor endpoint rewriting
- Add `isFullUrl` provider meta to treat base_url as complete API endpoint
- Remove hardcoded `?beta=true` from Claude adapter, pass through from client
- Refactor forwarder endpoint rewriting with proper query string handling
- Block provider switching when proxy is required but not running
- Add full URL toggle UI in endpoint field with i18n (zh/en/ja)
* refactor(proxy): remove beta query handling
* fix(proxy): strip beta query when rewriting Claude endpoints
* feat(codex): complete full URL support
* refactor(ui): refine full URL endpoint hint
Add StepFun Step Plan (阶跃星辰编程计划) as an OpenCode provider preset.
StepFun Step Plan is a subscription-based coding AI service that uses a
dedicated API endpoint separate from the standard StepFun provider.
- Base URL: https://api.stepfun.com/step_plan/v1
- Model: step-3.5-flash (196B MoE, optimized for agent and coding tasks)
- Category: cn_official
- Supports tool_call, reasoning, temperature control
Ref: https://platform.stepfun.com/docs/zh/step-plan/overview
Made-with: Cursor
Co-authored-by: sky-wang-salvation <sky-wang-salvation@users.noreply.github.com>
Add risk disclaimer section in all three languages (EN/ZH/JA) warning
users about potential GitHub ToS violations, account suspension risks,
and no long-term availability guarantee for the Copilot reverse proxy.
Tauri's built-in DMG styling relies on AppleScript/Finder access which
silently fails on CI (tauri-apps/tauri#1731). Switch to create-dmg tool
which works on GitHub Actions macOS runners.
- Replace Tauri DMG with create-dmg: background image, icon positions,
app-drop-link, codesign, hide-extension
- Regenerate background image at 2x Retina resolution (1320x800)
- Revert tauri.conf.json dmg config (ineffective on CI)
- Reorder steps: Prepare → Notarize DMG → Verify
- Notarize and Verify now use release-assets/ path for DMG
- Add DMG background image with drag-to-install arrow guide
- Configure window size (660x400), app and Applications icon positions
- Center icons horizontally with visual arrow between them
Tauri only notarizes the .app bundle, not the DMG container. This caused
stapler staple to fail with "Record not found" for the DMG.
- Add "Notarize macOS DMG" step using xcrun notarytool with retry logic
- Add retry logic (3 attempts) to macOS build step for transient network failures
- Add hdiutil verify before DMG notarization submission
- Import Developer ID Application certificate into temporary keychain
- Inject APPLE_SIGNING_IDENTITY/APPLE_ID/APPLE_PASSWORD/APPLE_TEAM_ID
into Tauri build step for automatic signing and notarization
- Staple notarization tickets to both .app and .dmg (hard-fail)
- Add verification step: codesign --verify + spctl -a + stapler validate
for both .app and .dmg, gating the release on success
- Collect .dmg alongside .tar.gz and .zip in release assets
- Clean up temporary keychain with original default restored
- Update release notes to recommend .dmg and note Apple notarization
- Remove all xattr workarounds and "unidentified developer" warnings
from README, README_ZH, installation guides, and FAQ (EN/ZH/JA)
Two components (ProviderList, UsageScriptModal) directly spread the full
settings object from the query into settingsApi.save(), which includes
webdavSync with an empty password (cleared by get_settings_for_frontend
for security). The backend merge_settings_for_save only preserved
existing WebDAV config when the incoming field was None, not when it was
present with an empty password.
Frontend fix: strip webdavSync before saving in both components, matching
the pattern already used by useSettings hook.
Backend defense-in-depth: merge_settings_for_save now backfills the
existing password when the incoming one is empty, preventing future
regressions from similar oversights.
Replace map_thinking_to_reasoning_effort() with resolve_reasoning_effort()
that uses a two-tier priority system:
1. Explicit output_config.effort: low/medium/high map 1:1, max → xhigh
2. Fallback: thinking.type + budget_tokens thresholds (<4k → low,
4k-16k → medium, ≥16k → high, adaptive → high)
Both Chat Completions and Responses API paths share the same helper,
ensuring consistent mapping across all OpenAI-compatible endpoints.
Claude Opus 4.6 and Sonnet 4.6 1M context window is now GA and no
longer requires a beta header. Update contextWindow from 200k to 1M
for all OpenClaw/OpenCode presets (27 entries in OpenClaw, 1 in
OpenCode Bedrock). Also add claude-sonnet-4-6 model pricing seed.
- Remove AuthCenterPanel import and OAuth TabsContent
- Narrow activeTab type from three values to "app-specific" | "universal"
- Simplify footer by removing oauth branch, reducing to two-way conditional
- Change TabsList from grid-cols-3 to grid-cols-2
- OAuth authentication remains available in settings page and CopilotAuthSection
- Make header constants in copilot_auth.rs public, add COPILOT_INTEGRATION_ID
- Unify User-Agent across 4 internal methods (eliminate "CC-Switch" leakage) and version string
- claude.rs add_auth_headers now references shared constants, adds user-agent / api-version / openai-intent
- forwarder.rs filters all 6 fixed fingerprint headers for Copilot requests to prevent reqwest append duplication
- stream_check.rs health check pipeline aligned with new fingerprint