mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-02 02:05:57 +08:00
c12364a940786e5a3626d20a1566706a50178825
222 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c12364a940 |
feat(claude-desktop): rework Claude Code import flow
- Derive route keys from the upstream model name (pass-through style) instead of fixed Claude aliases, and translate the legacy [1M] suffix into the supports1m field at the import boundary. Three Claude aliases mapped to the same upstream now collapse to a single route (e.g. MiniMax-M2 across SONNET/OPUS/HAIKU env produces one claude-MiniMax-M2 -> MiniMax-M2 row), with [1M] OR-aggregated. - Add an import-time safety net that rebuilds claude-desktop-official when missing, so users who deleted it can recover via the normal import button without losing customizations on other providers. - Hide API key and endpoint URL inputs in the official provider edit form to mirror Claude Code's behavior and prevent user confusion. - Reword the empty-state import button label for clarity. |
||
|
|
60a3628360 |
refactor(claude-desktop): replace [1M] suffix with supports1m field
inferenceModels entries now emit {name, supports1m: true} objects when
1M is enabled (plain strings otherwise), instead of appending a " [1M]"
suffix to model IDs. Route IDs and upstream model IDs are stored
verbatim; the suffix is rejected on input rather than silently stripped,
and proxy request mapping now requires an exact route_id match.
|
||
|
|
44d4ea81af |
- 修复 Claude Desktop 模型输入框失焦
- 为动态模型行添加稳定 rowId,避免编辑模型 ID 时重挂载 - 增加模型映射和直连模型列表焦点保持回归测试 |
||
|
|
6a3c2fe0ba |
- 支持 Claude Desktop 使用 Copilot/Codex OAuth 供应商
- 放开本地路由托管 OAuth 供应商校验,允许动态 Token - 新增 Claude Desktop Copilot/Codex 预设与账号选择 - 添加 OAuth proxy 回归测试 |
||
|
|
953b7cdcf9 |
refactor(claude-desktop): drop displayName from model route schema
Claude Desktop's new model menu reads model IDs directly and ignores the display_name field, so a separate displayName slot added UI noise without any product value. Collapse the routeId / model / displayName tuple down to routeId / model, and let the route ID carry the user-visible name through a non-editable claude- prefix rendered next to the input. Drop display_name from ClaudeDesktopModelRoute, ClaudeDesktopDefaultRoute, and ResolvedModelRoute on the Rust side plus the matching TS interfaces, stop emitting it in /v1/models responses, derive route IDs from upstream model IDs when picked via the model dropdown, and update zh/en/ja copy to describe the new two-field layout. |
||
|
|
5bbd83f7ca |
feat(claude-desktop): add 44 provider presets translated from Claude Code
- New src/config/claudeDesktopProviderPresets.ts with the Claude Code preset list re-shaped into Desktop's three-segment route format (routeId / upstreamModel / displayName); excludes OAuth providers, AWS Bedrock (no SigV4 support) and KAT-Coder (placeholder URL). - Non-Claude upstream presets show upstream model id as displayName (e.g. deepseek-v4-pro) so the Desktop model list reflects what is actually being requested. OpenRouter/TheRouter/PIPELLM keep Sonnet/Opus/Haiku since their upstream really is Anthropic Claude. - Wire ProviderPresetSelector into ClaudeDesktopProviderForm so selecting a preset back-fills baseUrl, mode, routes and apiFormat. - Drop the hard-coded ANTHROPIC_AUTH_TOKEN write in handleSubmit so ANTHROPIC_API_KEY presets (LemonData / AiHubMix / Gemini Native) save under the correct env key, and clear the opposite key on switch. - Hide the universal-providers tab for claude-desktop because its meta-driven routing has no analogue in the universal flat-env shape. - Add apps."claude-desktop" i18n key (zh/en/ja) so the dialog tab label resolves instead of showing the literal key. |
||
|
|
21b9eb0430 |
refactor(claude-desktop): simplify model mapping UX
- Remove "Import from Claude" button from main provider list (keep in empty state) - Remove "Desktop model" column from proxy mode mapping table; route names are now auto-generated - Rename "upstream model" label to "requested model" and equalize column widths - Rewrite model mapping hint and toggle description for end-user clarity - Update validation messages and remove dead i18n keys (routeModelLabel) |
||
|
|
2deee1097b |
refactor(claude-desktop): align provider form UI with Claude Code
- Rename field labels: "Gateway Base URL" → "API Endpoint", "Bearer Token" → "API Key" - Change layout from 2-column grid to vertical sections matching Claude Code - Reuse shared EndpointField component with format-aware amber hint box - Replace native <datalist> with vendor-grouped ModelDropdown (OpenCode pattern) - Move Fetch/Add buttons to section headers with compact sm styling - Extract ModelDropdown to shared module, deduplicate from OpenCodeFormFields - Extract renderActionButtons helper to eliminate proxy/direct button duplication - Remove dead i18n keys (gatewayBaseUrl, bearerToken) from all 3 locales |
||
|
|
8b3ad9caf9 |
feat(claude-desktop): add 3P provider switching with proxy gateway
Adds a new ClaudeDesktop AppType that writes Claude Desktop's third-party
inference profile under configLibrary/, sharing _meta.json with other
launchers (Ollama-compatible) so cc-switch can coexist with them.
Two switch modes:
- direct: provider already exposes claude-* / anthropic/claude-* model
ids on Anthropic Messages, Claude Desktop connects to it directly.
- proxy: cc-switch's local proxy acts as the inference gateway,
presenting only claude-* route names to Claude Desktop and mapping
them to real upstream models. Required after Anthropic restricted
Claude Desktop to claude-family ids.
Backend:
- New module claude_desktop_config with snapshot/rollback, official seed
bypass, /claude-desktop/v1/{models,messages} routes, and a single
source of truth for default proxy routes.
- Gateway token persisted in SQLite, validated on every proxied request.
- get_claude_desktop_status surfaces drift signals (stale models,
missing routes, proxy stopped, base URL mismatch, missing token).
Frontend:
- Slim ClaudeDesktopProviderForm independent from ProviderForm,
controlled by a top-level appId guard.
- ProviderList banner consumes the status query (5s polling) and
renders actionable diagnostics.
- ClaudeDesktopRouteToggle in the header to start/stop the local
gateway without touching takeover state.
- Three-locale i18n synchronised.
|
||
|
|
064b339bab | fix(claude): persist max effort via env (#2493) | ||
|
|
5b6339d78c |
chore(codex): hide 1M context window toggle in provider edit form
Codex models no longer accept model_context_window=1000000, so the toggle and its paired auto-compact-limit input are commented out in the provider edit form. State hooks, helper imports, and i18n keys are preserved so the UI can be restored in one batch if upstream support returns. The TOML editor remains visible, allowing manual edits if needed. |
||
|
|
85f0be9e1d |
feat(provider-form): soften validation with "save anyway" prompt (#2307)
* feat(provider-form): soften business-rule validation with "save anyway" prompt Refactor handleSubmit so empty-field / missing-item validations (provider name, endpoint, API key, opencode model, template variables, provider key required) no longer hard-reject with toast.error. Instead they are collected into an issues list and presented via a ConfirmDialog; the user can cancel or choose "Save anyway" to proceed. Integrity constraints stay as hard rejections: - providerKey regex / duplicate (would corrupt other providers) - Copilot / Codex OAuth not authenticated (no token, cannot establish) - omo Other Fields JSON not an object / parse failure This aligns the frontend with the backend's existing "relaxed save / strict switch" split (see gemini_config.rs: validate_gemini_settings vs validate_gemini_settings_strict) and unblocks legitimate configs such as AWS Bedrock, Vertex AI, and custom Gemini base URLs that the UI previously refused to save. Refs: #2196, #1204 * fix(provider-form): address review feedback on soft-validation P1: move empty providerKey back to hard rejection for OpenCode / OpenClaw / Hermes. Since providerKey is the primary identity for these apps and the mutations layer throws "Provider key is required" when absent, letting users click "save anyway" would surface a generic error toast instead of a precise, actionable one. Treat empty providerKey as an integrity constraint alongside regex / duplicate checks. P2: give the soft-confirm submit path its own submitting state. The confirm-dialog path bypassed react-hook-form's isSubmitting lifecycle, so slow or failing saves left the outer submit button responsive and could spawn unhandled rejections. Now the confirm handler awaits performSubmit inside try/catch/finally, uses an isConfirmSubmitting flag to gate both confirm and cancel clicks, and folds the flag into the outer disabled state and onSubmittingChange callback. Refs: #2307 review comments * chore(clippy): use push for single char '…' in truncate_body Clippy 1.95 added single_char_add_str which flagged the push_str("…") in truncate_body. Rebased onto latest upstream/main and applied the suggested fix so the Backend Checks clippy job passes. Unrelated to this PR's core changes; bundled in so the PR is mergeable without waiting for a separate upstream fix. --------- Co-authored-by: Allen <allen@AllenMacBook-M4-Pro.local> |
||
|
|
67dbfc0a8c |
fix(model-fetch): support /models for Anthropic-compat subpath providers
Providers like DeepSeek, Kimi, Zhipu GLM and MiniMax expose the Anthropic-compatible API on a subpath (e.g. /anthropic) while the OpenAI-style /models endpoint lives at the API root. The previous heuristic blindly appended /v1/models to the Base URL, so every such provider returned 404 and the UI mislabeled it as "provider does not support fetching models". Backend now generates a candidate list and tries them in order: preset override -> baseURL /v1/models -> stripped-subpath /v1/models -> stripped-subpath /models. Non-404/405 responses (auth, network) stop immediately so we never retry against hostile status codes. Known compat suffixes are kept in a length-descending constant so the longest match wins; response bodies are truncated to 512 chars to avoid HTML 404 pages bloating the error string. Preset type gains an optional modelsUrl (DeepSeek points at https://api.deepseek.com/models). Frontend threads the override through fetchModelsForConfig when the current Base URL still matches the preset default. A new fetchModelsEndpointNotFound i18n key replaces the misleading "not supported" toast for exhausted-candidate and 404/405 cases (zh/en/ja). |
||
|
|
010b163430 |
Fix/一键配置失效 (#2249)
* style(FailoverQueueManager): 显示供应商备注信息 * style(FailoverQueueItem): 添加供应商备注字段以支持备注信息显示 * style(FailoverQueueManager): 显示供应商备注信息 * style(FailoverQueueItem): 添加供应商备注字段以支持备注信息显示 * style(FailoverQueueManager): 更新供应商备注信息的显示样式 * style(FailoverQueueItem): 添加条件序列化以优化供应商备注字段 * fix: 优化模型状态管理,确保配置更新时正确引用最新设置 * fix(skill): improve error handling for skill source directory resolution Co-authored-by: Copilot <copilot@github.com> * fix(gemini): simplify project directory retrieval in scan_sessions function * fix(useModelState): optimize latestConfigRef assignment in useModelState hook * fix(useModelState): remove unnecessary blank line in useModelState hook --------- Co-authored-by: Copilot <copilot@github.com> |
||
|
|
10e0772d8c |
feat: Add Codex OAuth FAST mode toggle (#2210)
* Add Codex OAuth FAST mode toggle * fix(codex-oauth): default FAST mode to off to avoid surprise quota burn service_tier="priority" consumes ChatGPT subscription quota at a higher rate. Users must now opt in explicitly rather than inherit FAST mode silently when this feature ships. --------- Co-authored-by: Jason <farion1231@gmail.com> |
||
|
|
db92f2b3b0 |
style(providers): apply prettier to common-config hooks
Reformat three hooks brought in from upstream #2191 so that format:check passes in CI. |
||
|
|
0be75668cc |
feat(hermes): align provider schema with Hermes Agent 0.10.0
Hermes 0.10.0 tightened custom_providers validation (commit 2cdae233): invalid base_urls are rejected, unknown fields produce warnings, and new fields (rate_limit_delay, bedrock_converse, key_env) landed. - Add bedrock_converse to the api_mode selector (and i18n labels) - Expose rate_limit_delay in a provider-level advanced panel - Validate base_url client-side (URL shape, template-token friendly) - Drop per-model max_tokens — not in _VALID_CUSTOM_PROVIDER_FIELDS - Round-trip test asserts set_provider preserves rate_limit_delay / key_env / any unknown forward-compat field |
||
|
|
c8d91298c7 |
fix(providers): drop legacy ANTHROPIC_REASONING_MODEL from Claude quick-set
The model-mapping quick-set button referenced an undefined `reasoningModel` prop and wrote `ANTHROPIC_REASONING_MODEL`, which the backend explicitly marks as deprecated legacy (see services/provider/mod.rs and services/proxy.rs). Remove all three references so typecheck passes and the button matches the provider model schema. |
||
|
|
21a1518f9f |
refactor(hermes): drop "Auto" api_mode and require explicit protocol
Hermes' built-in api_mode detection only matches a handful of official endpoints (api.openai.com, api.anthropic.com, api.x.ai, AWS Bedrock); third-party / proxy endpoints silently fall back to chat_completions, which causes opaque 401/404s on Anthropic-protocol or Codex-Responses providers. The "Auto" option was misleading for the common third-party case. - Drop the "Auto" option from the API Mode dropdown; remove the HermesApiModeChoice sentinel type so writes always emit api_mode. - Default new providers and legacy entries lacking api_mode to chat_completions (only persisted on user save). - Deeplink imports now write api_mode: chat_completions explicitly instead of relying on URL heuristics; test renamed accordingly. - Rename the "Codex Responses (Copilot / OpenCode)" label to "OpenAI Responses" to match OpenAI's /v1/responses naming. |
||
|
|
497a543c1b |
feat(hermes): add API mode dropdown and per-provider model editor in form
The Hermes provider form previously only exposed Base URL and API Key, forcing users to drop into the Model panel to hand-edit model IDs after adding a provider. Following OpenClaw's shape, the form now carries: - An API Mode selector (auto-detect / chat_completions / anthropic_messages). "auto" is a UI-only sentinel — selecting it deletes api_mode from the config so the YAML doesn't leak a redundant field. - A model list editor where the first row is badged as the default and each row has a collapsible Advanced panel for context_length and max_tokens. Adding/removing rows uses a UUID-keyed ref so typing in one input doesn't drop focus when another row is added. - A Fetch Models button that pulls /v1/models from the configured endpoint and exposes the catalog in a per-row dropdown, identical to OpenClaw's flow. The vendor grouping is memoized so keystrokes don't trigger a reduce+sort per model row — Radix DropdownMenuContent does not lazy-mount, so the inner JSX evaluates on every render regardless of whether the menu is open. Three-locale i18n keys are added together (zh/en/ja). |
||
|
|
8b65a31c7c |
feat(claude): upgrade effort toggle from "high" to "max"
Per Anthropic's effort parameter docs, "high" is the API default and setting effortLevel="high" is equivalent to omitting the field entirely. The toggle previously produced no effect. Claude Opus 4.6, Sonnet 4.6, and Opus 4.7 now support a "max" level that enables unconstrained reasoning. Rename the checkbox (effortHigh -> effortMax) and write effortLevel="max" when toggled on. Existing "high" values in user configs are left untouched. Updates zh/en/ja locales and user-manual entries accordingly. |
||
|
|
0ca36b9d51 |
fix: address Hermes review findings (5 medium issues)
- Add missing Hermes MCP import on first launch (lib.rs) - Add Hermes branch in ProviderForm defaultValues fallback - Include Hermes in session manager subtitle (zh/en/ja) - Rename check_openclaw_stream to check_additive_app_stream - Cache parsed HERMES_DEFAULT_CONFIG to avoid repeated JSON.parse |
||
|
|
240969d8c7 |
feat: add Hermes UI components, presets, and config panels (Phase 8)
- Add 7 provider presets (OpenRouter, Anthropic, OpenAI, Google, DeepSeek, Together, Nous) - Create HermesFormFields + useHermesFormState for provider form integration - Create Model/Agent/Env config panels with save/load functionality - Create HermesHealthBanner for config warnings - Add hermes icon (violet winged H) to icon system - Integrate into App.tsx: 3 new view types (hermesModel/hermesAgent/hermesEnv), sidebar buttons (Brain/Bot/KeyRound), health banner, session support - Integrate into ProviderForm: presets, form state, key validation, rendering - Integrate into AddProviderDialog: universal tab exclusion, providerKey, base_url extraction - Add i18n keys for all Hermes UI (zh/en/ja) |
||
|
|
a0b585992a |
feat: add Hermes frontend types, API layer, and hooks (Phase 7)
- Add "hermes" to AppId union type and all exhaustive Record<AppId> - Add HermesModelConfig, HermesAgentConfig, HermesEnvConfig types - Add hermes field to VisibleApps, McpApps, ProxyTakeoverStatus - Create src/lib/api/hermes.ts with Tauri invoke wrappers - Create src/hooks/useHermes.ts with 5 query + 3 mutation hooks - Register hermes in APP_IDS, APP_ICON_MAP (violet color scheme) - Split MCP_SKILLS_APP_IDS into MCP_APP_IDS (includes hermes) and SKILLS_APP_IDS (excludes hermes, since Hermes has no Skills support) - Wire hermes additive-mode into App.tsx (remove/duplicate handlers), ProviderList.tsx (live provider ID query + In Config badge), mutations.ts (cache invalidation on switch/add/delete) - Add Hermes checkbox to McpFormModal - Add basic hermes i18n keys (en/zh/ja) |
||
|
|
e4c34b34e7 |
fix: remove ANTHROPIC_REASONING_MODEL to decouple thinking from model selection (#2081)
ANTHROPIC_REASONING_MODEL was a non-official env var that forced all requests with thinking params to use a single "reasoning model", overriding the user's /model selection. Since new Claude Code versions send adaptive thinking by default, this caused /model to silently fail. - Remove reasoning_model field and has_thinking_enabled() from model_mapper - Simplify map_model() to pure type-based matching (haiku/sonnet/opus) - Remove reasoning model UI field from provider form - Retain ANTHROPIC_REASONING_MODEL in ENV_EXCLUDES and override-key cleanup lists so legacy configs don't leak into common config |
||
|
|
cc77a040e2 |
fix: common config checkbox state not persisting for Codex/Claude/Gemini (#2191)
修复:Codex/Claude/Gemini 通用配置勾选状态无法正确保存的问题 Co-authored-by: 曾兴 <zengx@gantsoftware.com> |
||
|
|
8ba92b5470 |
feat(ui): add quick-set button for model mapping fields (#2179)
* feat(ui): add quick-set button for model mapping fields Add a "一键设置" (Quick Set) button in the model mapping section to simplify provider configuration. When users enter a model name in any of the five model fields, they can now click this button to populate all fields with that same value. This addresses the UX friction of manually filling all five model mapping fields (主模型, 推理模型, Haiku, Sonnet, Opus) when the provider uses the same model name across all request types. Implementation: - Add Wand2 icon import from lucide-react - Insert quick-set button alongside existing fetch-models button - Logic picks first non-empty model value and applies to all fields - Show success toast after applying - Disabled state when all model fields are empty - Add i18n strings for zh, en, ja locales Relates to user feedback about tedious model configuration workflow. * style(ui): format ClaudeFormFields component code Apply consistent code formatting to ClaudeFormFields.tsx following project linting rules. Includes multi-line import statements and improved readability for conditional expressions. |
||
|
|
87635e7fc6 |
feat(copilot): add GitHub Enterprise Server support (#2175)
* feat(copilot): add GitHub Enterprise Server support * fix(copilot): address GHES PR review findings (P1 + 2×P2) - P1: Use composite account ID (domain:user_id) for GHES to prevent cross-instance ID collisions; github.com keeps plain numeric ID for backward compatibilit - P2-a: Use get_api_endpoint() for model list URL with automatic fallback to static URL when dynamic endpoint resolution fails - P2-b: Add normalize_github_domain() as backend SSOT for domain normalization (lowercase, strip protocol/path/query, reject userinfo) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
03a0f9661b |
feat(proxy): Gemini Native API proxy integration (#1918)
* refactor(proxy): extract take_sse_block helper with CRLF delimiter support
Replace inline `buffer.find("\n\n")` SSE splitting logic across streaming,
streaming_responses, response_handler, and response_processor with a shared
`take_sse_block` function that handles both `\n\n` and `\r\n\r\n` delimiters.
* feat(proxy): add Gemini Native URL builder and full-URL resolver
Introduce gemini_url module that normalizes legacy Gemini/OpenAI-compatible
base URLs into canonical models/*:generateContent endpoints. Supports both
structured Gemini URLs (auto-normalized) and opaque relay URLs (pass-through
with query params only).
* feat(proxy): add Gemini Native schema, shadow store, transform, and streaming
- gemini_schema: Gemini generateContent request/response type definitions
- gemini_shadow: session-scoped shadow store for thinking signature and
tool-call state replay across streaming chunks
- transform_gemini: bidirectional Anthropic Messages ↔ Gemini Native
request/response conversion with thinking block and tool-use support
- streaming_gemini: Gemini SSE → Anthropic SSE streaming adapter with
incremental thinking/text/tool_use delta emission
* feat(proxy): wire Gemini Native format into proxy core and Claude adapter
Integrate gemini_native api_format throughout the proxy pipeline:
- ClaudeAdapter: detect Gemini provider type, Google/GoogleOAuth auth
strategies, and suppress Anthropic-specific headers for Gemini targets
- Forwarder: Gemini URL resolution, shadow store threading, endpoint
rewriting to models/*:generateContent with stream/non-stream variants
- Handlers: route Gemini streaming through streaming_gemini adapter and
non-streaming through transform_gemini converter
- Server/State: add GeminiShadowStore to shared ProxyState
- StreamCheck: support gemini_native health check with proper auth headers
* feat(ui): add Gemini Native provider preset and api format option
- Add gemini_native to ClaudeApiFormat type and ProviderMeta.apiFormat
- Add "Gemini Native" provider preset with default Google AI endpoints
- Show Gemini-specific endpoint hints and full-URL mode guidance
- Add gemini_native option to API format selector in ClaudeFormFields
- Add i18n strings for zh/en/ja
* feat(proxy): add Gemini Native tool argument rectification
* feat(proxy): update Gemini streaming and transformation logic
* fix(proxy): align shadow turns to tail on client history truncation
* fix: revert unrelated cache_key change in claude proxy transform
Restore .unwrap_or(&provider.id) fallback for cache_key to match main
branch behavior. Only gemini_native related changes should be in this branch.
* Prevent Gemini review regressions in streaming and tool rectification
PR #1918 review feedback exposed two correctness issues in the Gemini Native adapter path. Gemini SSE buffering was still using lossy UTF-8 decoding, which could corrupt split multibyte payloads and drop streamed output. Tool arg rectification also removed top-level parameters eagerly, which broke tools that legitimately define a parameters field.
This change moves Gemini SSE buffering onto the existing append_utf8_safe path and makes parameters flattening conditional on the schema actually expecting nested extraction. The old Skill rectification path stays intact, and new regression tests cover both the preserved parameters case and UTF-8-split JSON payloads.
Constraint: Existing PR #1918 review feedback must be fixed without staging unrelated local docs and artifact files
Rejected: Keep String::from_utf8_lossy in Gemini SSE buffering | corrupts split multibyte payloads and can drop JSON chunks
Rejected: Always preserve the parameters wrapper | regresses the existing nested-parameters rectification path for Skill-style tools
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep Gemini SSE buffering on the UTF-8-safe accumulator path and only unwrap parameters when the target schema does not declare it as a legitimate field
Tested: cargo fmt --manifest-path src-tauri/Cargo.toml --all; cargo test --manifest-path src-tauri/Cargo.toml preserves_utf8_boundaries_when_json_payload_spans_chunks; cargo test --manifest-path src-tauri/Cargo.toml gemini_to_anthropic_rectifies_tool_args_from_schema_hints; cargo test --manifest-path src-tauri/Cargo.toml rectifies_streamed_skill_args_from_nested_parameters; cargo test --manifest-path src-tauri/Cargo.toml gemini_to_anthropic_preserves_legitimate_parameters_arg
Not-tested: Full src-tauri test suite; live end-to-end Gemini relay traffic against upstream services
* Keep Gemini tool replay stable across Claude request boundaries
Claude Code follow-up requests were still falling back to locally reconstructed functionCall parts, which dropped Gemini thought signatures and triggered INVALID_ARGUMENT errors from the official Gemini API. The replay path needed to survive real Claude request boundaries, not just idealized in-process test flows.
This change makes Claude requests reuse X-Claude-Code-Session-Id as the shadow session key, records streamed Gemini tool turns before tool_use events are fully drained, and matches assistant tool_use turns to shadow state by tool_use id and normalized tool name before positional fallback. Together these fixes keep thoughtSignature-bearing Gemini tool calls available for the next request in the loop.
Constraint: Claude Code sends a stable X-Claude-Code-Session-Id header while metadata.session_id may be absent on follow-up requests
Rejected: Rely on metadata-only Claude session extraction | generated fresh session ids and broke cross-request shadow replay
Rejected: Record Gemini shadow only after streaming completes | loses the race when the client sends the next request immediately after tool_use
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Preserve Gemini shadow continuity across requests by keying Claude sessions from the header first and persisting tool-call shadow before yielding tool_use events downstream
Tested: cargo fmt --manifest-path src-tauri/Cargo.toml --all; cargo test --manifest-path src-tauri/Cargo.toml test_extract_session_from_claude_header; cargo test --manifest-path src-tauri/Cargo.toml test_extract_session_from_claude_header_precedes_metadata; cargo test --manifest-path src-tauri/Cargo.toml stores_tool_shadow_before_tool_use_events_are_fully_drained; cargo test --manifest-path src-tauri/Cargo.toml shadow_replay_matches_tool_use_turn_by_id_when_position_drifts; cargo test --manifest-path src-tauri/Cargo.toml shadow_replay_aligns_to_latest_turns_after_client_truncation
Not-tested: Full src-tauri test suite without test filters; live end-to-end Gemini relay after this exact commit hash
* style: apply cargo fmt to pass Backend Checks CI
Wrap prompt_cache_key chained call across lines per rustfmt default
formatting. Pure formatting change, no behavior difference.
* fix(proxy/gemini): synthesize unique ids for no-id tool calls + enforce object params schema
P1 — Parallel tool calls without Gemini-assigned ids no longer collapse.
Gemini 2.x native parallel `functionCall` entries may omit the `id` field.
The previous `merge_tool_call_snapshots` fell back to matching by `name`,
which silently merged two parallel calls to the same function into one
entry — dropping the first call's args. The non-streaming path and shadow
store further bottlenecked on empty-string ids: multiple `tool_use` blocks
shared the same id, and `tool_name_by_id.get("")` could only return one
mapping, causing later `tool_result` round-trips to fail with
`Unable to resolve Gemini functionResponse.name` or bind to the wrong tool.
Fix: introduce `synthesize_tool_call_id()` producing `gemini_synth_<uuid>`.
Both streaming and non-streaming response paths now guarantee every
Anthropic-visible tool_use carries a unique id. `merge_tool_call_snapshots`
matches by id first, falling back to the `parts` array position (for the
cumulative-streaming case) while preserving the synthesized id across
chunks. `convert_message_content_to_parts` detects the synthetic prefix
and strips the id from outbound `functionCall`/`functionResponse` so the
internal identifier never leaks upstream. `shadow_parts` performs the
same strip when replaying a recorded assistant turn.
P2 — Vertex AI rejects empty `parameters` schemas. When an Anthropic tool
arrives with missing or empty `input_schema`, the proxy used to emit
`"parameters": {}` (no `type`), which fails Vertex AI validation with
`functionDeclaration parameters schema should be of type OBJECT`.
Contrary to the automated-review suggestion, the fix is not to omit
`parameters` (that too is rejected) but to normalize to the canonical
empty-object form `{type: "object", properties: {}}`.
Refs: google-gemini/generative-ai-python#423, BerriAI/litellm#5055.
Fix: new `ensure_object_schema` helper in `gemini_schema` promotes
missing `type` to `"object"` and adds empty `properties` when absent,
while leaving atomic (non-object) schemas untouched.
Tests: seven new regressions covering parallel no-id calls, cumulative
chunk id reuse, synthetic-id round-trip both directions, shadow replay
id stripping, and the three Vertex-AI schema shapes.
The two existing wrapper functions (`gemini_to_anthropic` and
`gemini_to_anthropic_with_shadow`) gain `#[allow(dead_code)]` to clear
a pre-existing clippy -D warnings failure — they are part of the public
transform API surface and intentionally kept for future callers.
Addresses Codex review P1/P2 on #1918.
* fix(proxy/gemini): narrow URL normalization + guard empty OAuth access_token
P2a — Preserve opaque relay URLs that contain `/v1/models/` prefixes.
`should_normalize_gemini_full_url` previously flagged any full URL whose
path merely contained `/v1beta/models/` or `/v1/models/` as a structured
Gemini endpoint, forcing rewrite to `.../v1beta/models/{model}:method`.
This silently dropped legitimate relay route segments (e.g.
`https://relay.example/v1/models/invoke` → `.../v1beta/models/...:generateContent`,
losing `/invoke`) and sent traffic to the wrong upstream path.
Replace the bare `contains(...)` checks with
`matches_structured_gemini_models_path`, which requires the
`/models/` segment to be followed by a canonical Gemini method call
(`*:generateContent` or `*:streamGenerateContent`). The
`matches_bare_gemini_models_path` helper is generalized (and renamed) to
handle both `/v1beta/models/` and `/v1/models/` alongside the original
bare `/models/` shape.
P2b — Reject empty Gemini OAuth access_tokens before they reach the
bearer header.
`GeminiAdapter::parse_oauth_credentials` accepts refresh-token-only JSON
(and surfaces `{"access_token": "", ...}` for expired credentials) with
`access_token` defaulting to `""`. The Claude adapter's GeminiCli branch
then called `AuthInfo::with_access_token(key, creds.access_token)`
unconditionally, so the bearer-header builder at
`AuthStrategy::GoogleOAuth` resolved to `Authorization: Bearer ` — a
deterministic 401 from upstream.
CC Switch does not currently exchange the refresh_token for a fresh
access_token (`OAuthCredentials::needs_refresh` / `can_refresh` are
annotated `#[allow(dead_code)]`). Until that exists, only attach
`access_token` when it is non-empty; fall back to plain GoogleOAuth
strategy with the raw key and log a warn pointing users at
`~/.gemini/oauth_creds.json` so the failure mode is observable.
Tests:
- gemini_url.rs: three new regressions — opaque `/v1/models/invoke`,
opaque `/v1beta/models/route`, and the positive counter-case where a
structured `/v1/models/...:generateContent` path still normalizes.
- claude.rs: three new `test_extract_auth_gemini_cli_*` tests covering
refresh-only JSON, empty-string access_token JSON, and the valid-JSON
pass-through.
All 839 lib tests pass; cargo fmt + clippy -D warnings clean.
Addresses Codex review P2 findings on #1918.
* fix(proxy/gemini): treat empty-string functionCall id as missing in streaming path
Follow-up to the earlier P1 fix: some Gemini relays serialize an absent
functionCall id as `"id": ""` instead of omitting the field. The
non-streaming `extract_tool_call_meta` already filters these via
`.filter(|s| !s.is_empty())`, but the streaming counterpart
`extract_tool_calls` passed the empty string straight through
`function_call.get("id").and_then(|v| v.as_str())` into
`GeminiToolCallMeta::new`, producing a `Some("")` id.
Downstream, `merge_tool_call_snapshots` would then match two parallel
no-id calls against each other on their shared empty-string id,
collapsing them into a single snapshot (silent data loss for the first
call) and emitting an Anthropic `tool_use.id: ""` that breaks tool_result
correlation on the Claude Code client.
Fix:
- `extract_tool_calls`: apply the same `filter(|s| !s.is_empty())` guard
used in the non-streaming path so empty strings become `None` before
reaching the shadow meta.
- `merge_tool_call_snapshots`: defensively collapse any incoming
`Some("")` to `None` up front — keeps the "missing vs present" invariant
local to the merge step for future callers that might build
`GeminiToolCallMeta` by hand.
Tests (2 new, both in streaming_gemini):
- `parallel_empty_string_id_calls_are_treated_as_missing_and_preserved`
covers two parallel calls with explicit `"id": ""` — asserts both
surface, no empty tool_use id leaks, and each gets a unique
`gemini_synth_` id.
- `single_empty_string_id_tool_call_gets_synthesized_id` covers the
non-parallel degraded-relay case.
All 841 lib tests pass; cargo fmt + clippy -D warnings clean.
Addresses Codex follow-up P1 on #1918.
* fix(proxy/gemini): gate generic REST path suffixes behind Google host whitelist
`should_normalize_gemini_full_url` previously treated any full URL whose
path ends with `/v1`, `/v1/models`, `/models`, `/v1/openai`, or `/openai`
as a structured Gemini endpoint and rewrote it to
`/v1beta/models/{model}:generateContent`. These are ubiquitous REST
conventions — opaque relays such as `https://relay.example/custom/v1`
legitimately use them for fixed endpoints — so the rewrite silently
routed traffic to the wrong upstream path.
Split the predicate into two layers:
- **Unconditional**: `matches_structured_gemini_models_path` (i.e. a
`/models/...:generateContent` method call anywhere in the path), the
Google-specific `/v1beta*` family, and the deep OpenAI-compat paths
(`/v1beta/openai/chat/completions`, `/openai/chat/completions`, and
their `responses` siblings). These remain host-agnostic because the
path grammar itself is Gemini-specific.
- **Google-host gated**: `/v1`, `/v1/models`, `/models`, `/v1/openai`,
`/openai`. Only normalized when the host is one of
`generativelanguage.googleapis.com`, `aiplatform.googleapis.com`, or a
real `*-aiplatform.googleapis.com` Vertex regional endpoint. The match
is exact/suffix (not `contains`), so lookalike hosts like
`aiplatform.example.com` are correctly treated as opaque relays.
Tests (8 new in `gemini_url::tests`):
- Four opaque-relay cases: `/custom/v1`, `/custom/models`,
`/custom/v1/models`, `/custom/openai` — all preserved as-is.
- Three Google-host counter-cases: `/v1`, `/models`, and
`us-central1-aiplatform.googleapis.com/v1` still normalize.
- One lookalike safety case: `aiplatform.example.com/v1` is NOT
treated as Google.
All 849 lib tests pass; cargo fmt + clippy -D warnings clean.
Addresses Codex review P2 on #1918.
* fix(proxy/gemini): align shadow id with client-visible id in non-streaming path
When Gemini returns a `functionCall` without an id (common in 2.x
parallel calls), `gemini_to_anthropic_with_shadow_and_hints` previously
generated TWO independent synthesized UUIDs:
1. Line 186-197 — synthesized id `A` used for the Anthropic-visible
`content[tool_use].id` returned to the client.
2. Line 850-881 — `extract_tool_call_meta` independently synthesized
id `B ≠ A`, which populated `shadow_turn.tool_calls[i].id`.
`shadow_content` (line 225-228, cloned from `rectified_parts`) retained
the original missing/empty id. Result: the client sees id `A`, the
shadow store holds id `B`.
On the next turn, `convert_messages_to_contents` builds
`tool_name_by_id` from `build_tool_name_map_from_shadow_turns`, which
uses `tool_calls[i].id` — so the map contains `B → name` but not
`A → name`. When the client sends back `tool_result(tool_use_id=A)`,
resolution fails with:
Unable to resolve Gemini functionResponse.name for tool_use_id `A`
This affects both truncated histories (client sends only the
tool_result) and full histories (shadow-replay branch at line 342-354
skips `convert_message_content_to_parts`, so the assistant tool_use
block never registers id `A` itself).
Fix: make `rectified_parts` the single source of truth. After
`rectify_tool_call_parts`, run a pre-pass that writes
`synthesize_tool_call_id()` back into any `functionCall` that lacks a
non-empty id. All three readers — the content builder (186-197), the
shadow_content clone (225-228), and `extract_tool_call_meta` — then
observe the same id. `shadow_parts()` already strips synthesized ids on
replay (line 616-628), so the internal identifier never leaks to
Gemini upstream.
This mirrors the streaming path, which already has single-source-of-
truth semantics via `tool_call_snapshots` in `streaming_gemini.rs` —
no change needed there.
Tests (5 new in `transform_gemini::tests`):
- `non_stream_shadow_id_matches_client_visible_id`: asserts
`response.content[0].id == shadow.tool_calls[0].id ==
shadow.assistant_content.parts[0].functionCall.id`.
- `non_stream_missing_id_scenario_a_truncated_history_resolves`: turn 2
sends only `[tool_result(id=A)]`; resolution must succeed.
- `non_stream_missing_id_scenario_b_full_history_replay_resolves`: turn 2
sends `[assistant(tool_use=A), tool_result(A)]`; shadow-replay branch
strips the synth id from outgoing `functionCall` while still
resolving the subsequent `tool_result`.
- `non_stream_preserves_original_gemini_id_when_present`: regression —
genuine Gemini ids flow through unchanged.
- `non_stream_synthesized_id_not_leaked_to_gemini_via_shadow_replay`:
defensive — shadow-replay path must strip synth ids from both
`functionCall.id` and `functionResponse.id`.
All 854 lib tests pass; cargo fmt + clippy -D warnings clean.
Addresses Codex follow-up P1 on #1918.
* refactor(proxy/gemini): share build_anthropic_usage between stream and non-stream paths
`streaming_gemini::anthropic_usage_from_gemini` and
`transform_gemini::build_anthropic_usage` were byte-for-byte identical
(32 lines each) — both converting Gemini `usageMetadata` into the
Anthropic `usage` shape including `cache_read_input_tokens` mapping.
Promote the non-streaming version to `pub(crate)` and reuse it from the
streaming SSE converter. Removes ~30 lines of duplication and guarantees
the two paths cannot drift apart.
No behavioral change; all 854 lib tests pass; cargo fmt + clippy -D
warnings clean.
* fix(proxy/gemini): gate /v1beta behind Google host + normalize models/ model id prefix
Two related P2 corrections to the Gemini Native URL surface, both
folding into the existing Google-host-whitelist architecture.
## P2a — `/v1beta` suffix should not unconditionally trigger rewrite
`should_normalize_gemini_full_url` placed `/v1beta` and `/v1beta/models`
in the unconditional layer on the reasoning that `/v1beta` is
Google-specific. In practice an opaque relay fronting a non-Gemini
service at `https://relay.example/custom/v1beta` would still be
silently rewritten to `/v1beta/models/{model}:generateContent`,
breaking the deployment.
Move `/v1beta`, `/v1beta/models`, and `/v1beta/openai` into the
Google-host gated layer alongside `/v1`, `/models`, and friends. The
unconditional layer now only accepts paths whose grammar is
intrinsically Gemini — `/models/...:generateContent` method calls and
the deep OpenAI-compat endpoints like `/openai/chat/completions` and
`/openai/responses`. Pasted AI-Studio URLs such as
`https://generativelanguage.googleapis.com/v1beta` still normalize
because the host matches the whitelist.
## P2b — `model: "models/gemini-2.5-pro"` produced doubled path prefix
Gemini SDKs (and the official `list_models` response) commonly surface
model ids in resource-name form `models/gemini-2.5-pro`. Raw
interpolation into `format!("/v1beta/models/{model}:...")` produced
`/v1beta/models/models/gemini-2.5-pro:streamGenerateContent` which
upstream rejects — yielding false-negative health checks for otherwise
valid provider configs.
Introduce `normalize_gemini_model_id(&str) -> &str` in `gemini_url`
as the single source of truth: strips an optional leading `/` then an
optional `models/` prefix, leaving bare ids untouched. Apply in the
three call sites that build a Gemini method URL:
- `services/stream_check.rs::resolve_claude_stream_url` (unified path)
- `services/stream_check.rs::check_gemini_stream` (Gemini-only path)
- `proxy/forwarder.rs::rewrite_claude_transform_endpoint` (production)
Tests (9 new):
- `gemini_url`: 3 regressions for opaque vs Google-host `/v1beta*`
handling + 5 unit tests pinning `normalize_gemini_model_id` behavior
(strip prefix, leave bare id, preserve nested slashes past the one
stripped prefix, tolerate leading slash, pass through empty input).
- `stream_check`: one end-to-end regression confirming
`models/gemini-2.5-pro` collapses to the expected single-prefix URL.
- `forwarder`: one end-to-end regression on the production rewrite
path.
All 864 lib tests pass; cargo fmt + clippy -D warnings clean.
Addresses Codex P2 feedback on #1918.
* fix(proxy/gemini): trim API key before provider-type detection and OAuth parsing
Leading whitespace on a copied oauth_creds.json (e.g. trailing newline
when the user copies the file content as-is) would slip past the
`starts_with("ya29.") || starts_with('{')` prefix check in
`ClaudeAdapter::provider_type`, causing the provider to be misclassified
as raw-API-key Gemini and fall back to `x-goog-api-key` with the raw
JSON as the key — which upstream rejects with 401.
The frontend's `handleApiKeyChange` already trims on keystrokes but
deep-link imports, the JSON editor, and live-config backfill all bypass
that path. Trim at every backend extraction point so the coverage is
uniform:
- `ClaudeAdapter::extract_key` (5 env / fallback branches) gets
`.map(str::trim)` before `.filter(|s| !s.is_empty())` so that
whitespace-only values are also treated as missing.
- `GeminiAdapter::extract_key_raw` gets the same chain (including
the `.filter` it was missing before).
- `GeminiAdapter::parse_oauth_credentials` gets a defensive
`let key = key.trim();` at the entry as a belt-and-suspenders guard.
Adds two regression tests covering JSON and bare `ya29.` keys with
leading newline/space.
* fix(proxy/gemini): gate generic REST suffix stripping behind Google host in non-full-URL mode
`build_gemini_native_url` unconditionally stripped `/v1`, `/v1beta`,
`/models`, and `/openai` suffixes from the base path regardless of
host. This worked for Google's own endpoints but silently rewrote
third-party relay URLs like `https://relay.example/custom/v1` to
`.../custom/v1beta/models/...`, breaking any relay that mounts its
Gemini-compatible namespace under a versioned prefix.
The result was also asymmetric with the previously-fixed full-URL
branch: toggling the "full URL" switch changed the outbound URL for
the same base_url, which is exactly the kind of invisible behavior
that makes debugging proxy deployments painful.
Align `normalize_gemini_base_path` with
`should_normalize_gemini_full_url`'s layered model:
- Unconditional: `/models/...:method` structured paths and deep
OpenAI-compat endpoints (`/openai/chat/completions`,
`/openai/responses` and their versioned variants) — these are
unambiguous Gemini-specific grammar on any host.
- Google-host gated: generic `/v1`, `/v1beta`, `/models`, `/openai`
suffixes only get stripped on `generativelanguage.googleapis.com`,
`aiplatform.googleapis.com`, or `*-aiplatform.googleapis.com`.
Other hosts preserve the prefix verbatim so relays keep their
intended routing.
Adds seven regression tests for the non-full-URL flow: opaque relay
preservation (v1 / v1beta / models / openai suffix variants), Google
host normalization (counter-case), and boundary cases (structured
method path and deep OpenAI-compat endpoint stripped regardless of
host).
Test count: 864 -> 873.
* Revert "fix(proxy/gemini): gate generic REST suffix stripping behind Google host in non-full-URL mode"
This reverts commit
|
||
|
|
689ca08409 |
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. |
||
|
|
4a0b5c3dec |
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) |
||
|
|
3aef5217cb |
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 |
||
|
|
8c32610a7d |
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 |
||
|
|
a5a5207f74 | style: fix prettier formatting in ProviderCard and ProviderForm | ||
|
|
fa4297f4d3 |
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. |
||
|
|
ecb1fed5db |
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. |
||
|
|
5288238694 |
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 |
||
|
|
6a34253934 |
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. |
||
|
|
84998aa217 |
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. |
||
|
|
5017002938 |
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 |
||
|
|
de49f6fbbe |
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> |
||
|
|
fee8577032 |
feat: hide GitHub Copilot provider preset and auth tab
Users reported that Copilot support causes excessively fast token consumption. Temporarily hide the feature by adding a `hidden` field to ProviderPreset interface and commenting out the auth center tab in settings. Existing Copilot providers in DB still work via proxy. |
||
|
|
3553d05bf0 |
feat(provider): additive provider key lifecycle & fix openclaw serializer panic (#1724)
* feat(provider): support additive provider key lifecycle management
Add `addToLive` parameter to add_provider so callers can opt out of
writing to the live config (e.g. when duplicating an inactive provider).
Add `originalId` parameter to update_provider to support provider key
renames — the old key is removed from live config before the new one
is written.
Frontend: ProviderForm now exposes provider-key input for openclaw app
type, and EditProviderDialog forwards originalId on save. Deep-link
import passes addToLive=true to preserve existing behavior.
* test(provider): add integration tests for additive provider key flows
Cover openclaw provider duplication scenario to verify that a generated
provider key is assigned automatically. Add MSW handlers for
get_openclaw_live_provider_ids, get_openclaw_default_model,
scan_openclaw_config_health, and check_env_conflicts endpoints.
Update EditProviderDialog mock to pass originalId alongside provider.
* fix(openclaw): replace json-five serializer to prevent panic on empty collections
json-five 0.3.1 panics when pretty-printing nested empty maps/arrays.
Switch value_to_rt_value() to serde_json::to_string_pretty() which
produces valid JSON5 output without the panic. Add regression test for
removing the last provider (empty providers map).
* style: apply rustfmt formatting to proxy and provider modules
Reformat chained .header() calls in ClaudeAdapter and StreamCheckService
for consistent alignment. Reorder imports alphabetically in stream_check.
Fix trailing whitespace in transform.rs and merge import lines in
provider/mod.rs.
* style: fix clippy warnings in live.rs and tray.rs
* refactor(provider): simplify live_config_managed and deduplicate tolerant live config checks
- Change live_config_managed from Option<bool> to bool with #[serde(default)]
- Extract repeated tolerant live config query into check_live_config_exists helper
- Fix duplicate key generation to also check live-only provider IDs
- Fix updateProvider test to match new { provider, originalId } call signature
- Add streaming_responses test type annotation for compiler inference
* fix(provider): distinguish legacy providers from db-only when tolerating live config errors
Change `ProviderMeta.live_config_managed` from `bool` to `Option<bool>`
to introduce a three-state semantic:
- `Some(true)`: provider has been written to live config
- `Some(false)`: explicitly db-only, never written to live config
- `None`: legacy data or unknown state (pre-existing providers)
Previously, legacy providers defaulted to `live_config_managed = false`
via `#[serde(default)]`, which silently swallowed live config parse
errors. This could mask genuine configuration issues for providers that
had actually been synced to live config before the field was introduced.
Now, only providers with an explicit `Some(false)` marker tolerate parse
errors; legacy `None` providers surface errors as before, preserving
safety for already-managed configurations.
Also wrap the `ensureQueryData` call for live provider IDs during
duplication in a try/catch so that a malformed config file shows a
user-facing toast instead of silently failing.
Add tests for both the legacy error propagation path and the frontend
duplication failure scenario.
* refactor(provider): unify OMO variant updates with atomic file-then-db writes and rollback
Consolidate the duplicated omo/omo-slim update branches into a single
match on the variant. Write the OMO config file from the in-memory
provider state *before* persisting to the database, so a file-write or
plugin-sync failure leaves the database unchanged. If `add_plugin`
fails after the config file is already written, roll back to the
previous on-disk contents via snapshot/restore.
Also:
- `sync_all_providers_to_live` now skips db-only providers
(`live_config_managed == Some(false)`) instead of attempting to write
them to live config.
- `import_{opencode,openclaw}_providers_from_live` mark imported
providers as `live_config_managed: Some(true)` so they are correctly
recognized during subsequent syncs.
- Extract OmoService helpers: `profile_data_from_provider`,
`snapshot_config_file`, `restore_config_file`, `write_profile_config`,
and the new public `write_provider_config_to_file`.
- Add 9 new tests covering sync skip, legacy restore, import marking,
OMO persistence, file-write failure, and plugin-sync rollback.
* fix(provider): fix additive provider delete/switch regressions and redundancy
- fix(delete): replace stale live_config_managed flag check with
check_live_config_exists so providers written to live before the
flag-flip logic was introduced are still cleaned up on delete
- fix(switch): make write_live_with_common_config return Err instead of
silently returning Ok when config structure is invalid, preventing
live_config_managed from being incorrectly flipped to true
- fix(update): block provider key rename for OMO/OMO Slim categories to
prevent orphaned current-state markers breaking OMO file syncs
- fix(switch): flip live_config_managed to true after successful live
write for DB-only additive providers so sync_all_providers_to_live
includes them on future syncs; roll back live write if DB update fails
- refactor(delete): merge symmetric OMO/OMO-Slim blocks into single
match-on-variant path; hoist DB read to top of additive branch
- refactor(remove_from_live_config): merge OMO/OMO-Slim if/else-if
into single match-on-variant path
- refactor(switch_normal): merge two OMO/OMO-Slim if blocks into one
OpenCode guard with (enable, disable) variant pair
- fix(update): remove redundant duplicate return Ok(true) after OMO
current-state write
* fix(test): use preferred_filename after OMO field rename
The merge from main brought in #1746 which renamed
OmoVariant.filename → preferred_filename, but the test helper
omo_config_path() was not updated, breaking compilation of all
new provider tests.
---------
Co-authored-by: Jason <farion1231@gmail.com>
|
||
|
|
6a083cdd1c |
fix(omo): adapt to oh-my-openagent rename with backward compatibility (#1746)
* fix(omo): adapt to oh-my-openagent rename with backward compatibility Closes https://github.com/farion1231/cc-switch/issues/1733 * fix(omo): prioritize oh-my-openagent config over legacy oh-my-opencode |
||
|
|
67e074c0a7 |
refactor(proxy): transparent header forwarding via hyper client (#1714)
* 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
|
||
|
|
8cae7b7b73 |
feat(proxy): add full URL mode and refactor endpoint rewriting (#1561)
* 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 |
||
|
|
10be929f33 |
feat(claude): add "Disable Auto-Upgrade" checkbox to provider config editor
Add a toggle for DISABLE_AUTOUPDATER env var in CommonConfigEditor, following the same pattern as the existing Teammates mode toggle. |
||
|
|
8ccfbd36d6 |
feat(copilot): add GitHub Copilot reverse proxy support (#930)
* refactor(toolsearch): replace binary patch with ENABLE_TOOL_SEARCH env var toggle
- Remove toolsearch_patch.rs binary patching mechanism (~590 lines)
- Delete `toolsearch_patch.rs` and `commands/toolsearch.rs`
- Remove auto-patch startup logic and command registration from lib.rs
- Remove `tool_search_bypass` field from settings.rs
- Remove frontend settings ToggleRow, useSettings hook sync logic, and API methods
- Clean up zh/en/ja i18n keys (notifications + settings)
- Add ENABLE_TOOL_SEARCH toggle to Claude provider form
- Add checkbox in CommonConfigEditor.tsx (alongside teammates toggle)
- When enabled, writes `"env": { "ENABLE_TOOL_SEARCH": "true" }`
- When disabled, removes the key; takes effect on provider switch
- Add zh/en/ja i18n key: `claudeConfig.enableToolSearch`
Claude Code 2.1.76+ natively supports this env var, eliminating the need for binary patching.
* feat(claude): add effortLevel high toggle to provider form
- Add "high-effort thinking" checkbox to Claude provider config form
- When checked, writes `"effortLevel": "high"`; when unchecked, removes the field
- Add zh/en/ja i18n translations
* refactor(claude): remove deprecated alwaysThinking toggle
- Claude Code now enables extended thinking by default; alwaysThinkingEnabled is a no-op
- Thinking control is now handled via effortLevel (added in prior commit)
- Remove state, switch case, and checkbox UI from CommonConfigEditor
- Clean up alwaysThinking i18n keys across zh/en/ja locales
* feat(opencode): add setCacheKey: true to all provider presets
- Add setCacheKey: true to options in all 33 regular presets
- Add setCacheKey: true to OPENCODE_DEFAULT_CONFIG for custom providers
- Exclude 2 OMO presets (Oh My OpenCode / Slim) which have their own config mechanism
Closes #1523
* fix(codex): resolve 1M context window toggle causing MCP editor flicker
- Add localValueRef to short-circuit duplicate CodeMirror updateListener callbacks,
breaking the React state → CodeMirror → stale onChange → React state feedback loop
- Use localValueRef.current in handleContextWindowToggle and handleCompactLimitChange
to avoid stale closure reads
- Change compact limit input from type="number" to type="text" with inputMode="numeric"
to remove unnecessary spinner buttons
* feat(codex): add 1M context window toggle utilities and i18n keys
- Add extractCodexTopLevelInt, setCodexTopLevelInt, removeCodexTopLevelField
TOML helpers in providerConfigUtils.ts
- Add i18n keys for contextWindow1M, autoCompactLimit in zh/en/ja locales
* feat(claude): collapse model mapping fields by default
- Wrap 5 model mapping inputs in a Collapsible, collapsed by default
- Auto-expand when any model value is present (including preset-filled)
- Show hint text when collapsed explaining most users need no config
- Add zh/en/ja i18n keys for toggle label and collapsed hint
- Use variant={null} to avoid ghost button hover style clash in dark mode
* feat(claude): merge advanced fields into single collapsible section
- Merge API format, auth field, and model mapping into a unified "Advanced Options" collapsible
- Extend smart-expand logic to detect non-default values across all advanced fields
- Preserve model mapping sub-header and hint with a separator line
- Update zh/en/ja i18n keys (advancedOptionsToggle, advancedOptionsHint, modelMappingLabel, modelMappingHint)
* feat(copilot): add GitHub Copilot reverse proxy support
Add GitHub Copilot as a Claude provider variant with OAuth device code
authentication and Anthropic ↔ OpenAI format transformation.
Backend:
- Add CopilotAuthManager for GitHub OAuth device code flow
- Implement Copilot token auto-refresh (60s before expiry)
- Persist GitHub token to ~/.cc-switch/copilot_auth.json
- Add ProviderType::GitHubCopilot and AuthStrategy::GitHubCopilot
- Modify forwarder to use /chat/completions for Copilot
- Add Copilot-specific headers (Editor-Version, Editor-Plugin-Version)
Frontend:
- Add CopilotAuthSection component for OAuth UI
- Add useCopilotAuth hook for OAuth state management
- Auto-copy user code to clipboard and open browser
- Use 8-second polling interval to avoid GitHub rate limits
- Skip API Key validation for Copilot providers
- Add GitHub Copilot preset with claude-sonnet-4 model
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
* fix(copilot): remove is_expired() calls from tests
Remove references to deleted is_expired() method in test code.
Only is_expiring_soon() is needed for token refresh logic.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
* feat(copilot): add real-time model listing from Copilot API
- Add fetch_models() to CopilotAuthManager calling GET /models endpoint
- Add copilot_get_models Tauri command
- Add copilotGetModels() frontend API wrapper
- Modify ClaudeFormFields to show model dropdown for Copilot providers
- Fetches available models on component mount when isCopilotPreset
- Groups models by vendor (Anthropic, OpenAI, Google, etc.)
- Input + dropdown button combo allows both manual entry and selection
- Non-Copilot providers keep original plain Input behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(copilot): add usage query integration
- Add Copilot usage API integration (fetch_usage method)
- Add copilot_get_usage Tauri command
- Add GitHub Copilot template in usage query modal
- Unify naming: copilot → github_copilot
- Add constants management (TEMPLATE_TYPES, PROVIDER_TYPES)
- Improve error handling with detailed error messages
- Add database migration (v5 → v6) for template type update
- Add i18n translations (zh, en, ja)
- Improve type safety with TemplateType
- Apply code formatting (cargo fmt, prettier)
* 修复github 登录和注销问题 ,模型选择问题
* feat(copilot): add multi-account support for GitHub Copilot
- Add multi-account storage structure with v1 to v2 migration
- Add per-account token caching and auto-refresh
- Add new Tauri commands for account management
- Integrate account selection in Proxy forwarder
- Add account selection UI in CopilotAuthSection
- Save githubAccountId to ProviderMeta
- Add i18n translations for multi-account features (zh/en/ja)
* 修复用量查询Reset字段出现多余字符
* refactor(auth-binding): introduce generic provider auth binding primitives
- add shared authBinding types in Rust and TypeScript while keeping githubAccountId as a compatibility field\n- resolve Copilot token, models, and usage through provider-bound account lookup instead of only the implicit default account\n- fix the Unix build regression in settings.rs by restoring std::io::Write for write_all()\n- remove the accidental .github ignore entry and drop leftover Copilot form debug logs\n- keep the first migration step non-breaking by writing both authBinding and the legacy githubAccountId field from the form
* refactor(auth-service): add managed auth command surface and explicit default account state
- introduce generic managed auth commands and frontend auth API wrappers for provider-scoped login, status, account listing, removal, logout, and default-account selection\n- store an explicit Copilot default_account_id instead of relying on HashMap iteration order, and use it consistently for fallback token/model/usage resolution\n- sort managed accounts deterministically and surface default-account state to the UI\n- refactor the Copilot form hook to wrap a generic useManagedAuth implementation while preserving the existing component contract\n- add default-account controls to the Copilot auth section and extend Copilot auth status serialization/tests for the new state
* feat(auth-center): add a dedicated settings entrypoint for managed OAuth accounts
- add an Auth Center tab to Settings so managed OAuth accounts are no longer hidden inside individual provider forms\n- introduce a first AuthCenterPanel that hosts GitHub Copilot account management as the initial managed auth provider\n- keep the provider form experience intact while establishing a global account-management surface for future providers such as OpenAI\n- validate that the new settings tab works cleanly with the generic managed auth hook and existing Copilot account controls
* feat(add-provider): expose managed OAuth sources alongside universal providers
- add an OAuth tab to the Add Provider flow so managed auth sources sit beside app-specific and universal providers\n- reuse the new Auth Center panel inside the dialog, keeping account management discoverable during provider creation\n- make the dialog footer adapt to the OAuth tab so account setup does not pretend to create a provider directly\n- align the add-provider UX with the new architecture where OAuth accounts are global assets and providers bind to them later
* fix(auth-reliability): harden managed auth persistence and refresh behavior
- replace direct Copilot auth store writes with private temp-file writes and atomic rename semantics, and document the local token storage limitation\n- add per-account refresh locks plus a double-check path so concurrent requests do not stampede GitHub token refresh\n- surface legacy migration failures through auth status, expose them in the UI, and add translated copy for the new account-state labels\n- stop writing the legacy githubAccountId field from the provider form while keeping compatibility reads in place\n- add logout error recovery and Copilot model-load toasts so auth failures are no longer silently swallowed
* refactor(copilot-detection): prefer provider type before URL fallbacks
- update forwarder endpoint rewriting to treat providerType as the primary GitHub Copilot signal\n- keep githubcopilot.com string matching only as a compatibility fallback for older provider records without providerType\n- reduce one more path where Copilot behavior depended purely on URL heuristics
* fix(copilot-auth): add cancel button to error state in CopilotAuthSection
- 错误状态下仅有"重试"按钮,用户无法退出(如不可恢复的 403 未订阅错误)
- 新增"取消"按钮,复用已有的 cancelAuth 逻辑重置为 idle 状态
* 修复打包后github账号头像显示异常
* 修复github copilot 来源的模型测试报错
* feat(copilot-preset): add default model presets for GitHub Copilot
- 补充 Copilot 预设的默认模型配置,用户选完预设即可直接使用
- ANTHROPIC_MODEL: claude-opus-4.6
- ANTHROPIC_DEFAULT_HAIKU_MODEL: claude-haiku-4.5
- ANTHROPIC_DEFAULT_SONNET_MODEL: claude-sonnet-4.6
- ANTHROPIC_DEFAULT_OPUS_MODEL: claude-opus-4.6
---------
Co-authored-by: Jason <farion1231@gmail.com>
Co-authored-by: 周梦泽 <mengze.zhou@dafeng-tech.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
|
||
|
|
bb23ab918b |
fix: place OpenCode model variants at top level instead of inside options (#1317)
Split the expanded model panel into two editing areas: - "Model Properties" for top-level fields (variants, cost, etc.) - "SDK Options" for model.options fields (provider routing, etc.) Also guard against renaming extra fields to reserved keys (name, limit, options) or duplicate names, and fix ModelOptionKeyInput blur desync when a rename is rejected by the parent handler. |
||
|
|
5c03de53f7 |
fix(provider-form): prevent duplicate submissions on rapid button clicks (#1352)
Make ProviderForm.handleSubmit async and await onSubmit so react-hook-form's isSubmitting state is tracked. Disable submit buttons in AddProviderDialog and EditProviderDialog while submission is in flight via onSubmittingChange callback. |