Commit Graph

147 Commits

Author SHA1 Message Date
SaladDay ac764b4818 refactor(provider): establish typed write ownership
Replace the generic provider upsert surface with strict typed create, update, rename, endpoint, and compensation operations. This app-independent prerequisite owns provider writes without depending on Pi runtime or canonical restore.

The v17 migration also reserves dormant device-local ledger tables and the Pi skill bit alongside endpoint normalization. Keeping that reservation here prevents a later stacked feature from rewriting a published migration; no Pi runtime behavior is activated by this commit.

Old save_provider callsite classification
==========================================

Inventory authority: abandoned 5a385fc8 tree. The old definition at
src-tauri/src/database/dao/providers.rs:180 is deleted and is not a callsite.

Production callsites:

- src-tauri/src/commands/provider.rs:253 [create] Claude Desktop import creates
  one absent aggregate; it now strict-inserts the row and initial endpoints in
  one transaction.
- src-tauri/src/database/dao/providers.rs:638 [create] official seed first
  proves absence, then strict-creates; a racing insert is a conflict.
- src-tauri/src/database/dao/providers.rs:704 [create] on-demand seed first
  proves absence, then strict-creates; it cannot overwrite an existing row.
- src-tauri/src/services/omo.rs:291 [create] OMO import constructs a new
  aggregate and strict-creates it; OMO is not eligible for rename.
- src-tauri/src/services/provider/endpoints.rs:85 [update] endpoint last-used
  is not a Provider-row save; it now calls the exact touch endpoint operation.
- src-tauri/src/services/provider/live.rs:1567 [create/update] default live
  import is reconciliation: read first, then strict create or strict update.
- src-tauri/src/services/provider/live.rs:1743 [update] an existing OpenCode
  live provider follows the strict row-update branch.
- src-tauri/src/services/provider/live.rs:1770 [create] a new OpenCode live
  provider follows the strict aggregate-create branch.
- src-tauri/src/services/provider/live.rs:1825 [update] an existing OpenClaw
  live provider follows the strict row-update branch.
- src-tauri/src/services/provider/live.rs:1858 [create] a new OpenClaw live
  provider follows the strict aggregate-create branch.
- src-tauri/src/services/provider/live.rs:1900 [update] an existing Hermes live
  provider follows the strict row-update branch.
- src-tauri/src/services/provider/live.rs:1926 [create] a new Hermes live
  provider follows the strict aggregate-create branch.
- src-tauri/src/services/provider/mod.rs:2568 [create] ProviderService::add owns
  strict aggregate creation and all initial endpoints.
- src-tauri/src/services/provider/mod.rs:2680 [rename] an additive DB-only key
  change now uses the dedicated transactional rename after eligibility checks.
- src-tauri/src/services/provider/mod.rs:2711 [update] OMO edit updates exactly
  the existing main row after its live-file coordination.
- src-tauri/src/services/provider/mod.rs:2740 [update] additive-provider edit
  updates exactly the existing main row after resolving live ownership.
- src-tauri/src/services/provider/mod.rs:2750 [update] switch-mode edit updates
  exactly the existing main row and never inserts.
- src-tauri/src/services/provider/mod.rs:2948 [update] remove-from-live changes
  only the existing provider's live-managed marker.
- src-tauri/src/services/provider/mod.rs:3120 [update] switch backfill updates
  only the existing current provider row.
- src-tauri/src/services/provider/mod.rs:3174 [update] successful additive
  switch changes only the existing live-managed marker.
- src-tauri/src/services/provider/mod.rs:3315 [update] common-config migration
  updates only each already-read existing row.
- src-tauri/src/services/provider/mod.rs:3895 [update] Gemini credential scrub
  updates only each already-read existing row.
- src-tauri/src/services/provider/mod.rs:4082 [update] sort ordering is routed
  to the dedicated sort-index state operation, not row replacement.
- src-tauri/src/services/provider/mod.rs:4636 [create/update] universal-to-
  Claude reconciliation reads the target and selects strict create or update.
- src-tauri/src/services/provider/mod.rs:4651 [create/update] universal-to-
  Codex reconciliation reads the target and selects strict create or update.
- src-tauri/src/services/provider/mod.rs:4665 [create/update] universal-to-
  Gemini reconciliation reads the target and selects strict create or update.

Required indirect ownership paths:

- src-tauri/src/deeplink/provider.rs [create] the old indirect flow called
  ProviderService::add and then appended endpoints one by one. It now supplies
  every non-primary endpoint to one strict aggregate create, so hydration is
  complete atomically and a duplicate is zero-side-effect.
- [restore] no old generic-save callsite is reclassified as restore. Exact
  aggregate replacement exists only as the sealed
  restore_provider_aggregate_on_tx compensation primitive.

Test-only callsites:

Every item below is classified [test]. Each is fixture setup, not a production
write authority, and is migrated to a real ProviderService entry where the
behavior is under test or to the cfg(test)-only typed fixture reconciler where
the test merely needs pre-existing rows.

- src-tauri/src/codex_history_migration.rs:1442 [test] migration fixture setup.
- src-tauri/src/codex_history_migration.rs:1452 [test] migration fixture setup.
- src-tauri/src/codex_history_migration.rs:2174 [test] migration fixture setup.
- src-tauri/src/codex_history_migration.rs:2176 [test] migration fixture setup.
- src-tauri/src/codex_history_migration.rs:2199 [test] migration fixture setup.
- src-tauri/src/codex_history_migration.rs:2219 [test] migration fixture setup.
- src-tauri/src/codex_history_migration.rs:2247 [test] migration fixture setup.
- src-tauri/src/codex_history_migration.rs:2267 [test] migration fixture setup.
- src-tauri/src/codex_history_migration.rs:2288 [test] migration fixture setup.
- src-tauri/src/codex_history_migration.rs:2320 [test] migration fixture setup.
- src-tauri/src/codex_history_migration.rs:2393 [test] migration fixture setup.
- src-tauri/src/codex_history_migration.rs:2449 [test] migration fixture setup.
- src-tauri/src/codex_history_migration.rs:2498 [test] migration fixture setup.
- src-tauri/src/codex_history_migration.rs:2555 [test] migration fixture setup.
- src-tauri/src/codex_history_migration.rs:2604 [test] migration fixture setup.
- src-tauri/src/codex_history_migration.rs:2625 [test] migration fixture setup.
- src-tauri/src/database/dao/providers.rs:754 [test] DAO fixture setup.
- src-tauri/src/proxy/provider_router.rs:351 [test] router fixture setup.
- src-tauri/src/proxy/provider_router.rs:352 [test] router fixture setup.
- src-tauri/src/proxy/provider_router.rs:377 [test] router fixture setup.
- src-tauri/src/proxy/provider_router.rs:378 [test] router fixture setup.
- src-tauri/src/proxy/provider_router.rs:410 [test] router fixture setup.
- src-tauri/src/proxy/provider_router.rs:411 [test] router fixture setup.
- src-tauri/src/proxy/provider_router.rs:447 [test] router fixture setup.
- src-tauri/src/proxy/provider_router.rs:448 [test] router fixture setup.
- src-tauri/src/proxy/provider_router.rs:488 [test] router fixture setup.
- src-tauri/src/services/provider/mod.rs:485 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:586 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:813 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:825 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:1472 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:1607 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:1737 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:1945 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:1952 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:1978 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:2006 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:2056 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:2130 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:2167 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:2207 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:2235 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:2270 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:2320 [test] service fixture setup.
- src-tauri/src/services/provider/mod.rs:2362 [test] service fixture setup.
- src-tauri/src/services/proxy.rs:3762 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:3948 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:4034 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:4095 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:4114 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:4263 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:4341 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:4421 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:4533 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:4651 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:4787 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:5264 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:5320 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:5385 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:5387 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:5460 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:5462 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:5611 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:5613 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:5615 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:5698 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:5700 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:5998 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:6000 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:6173 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:6175 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:6417 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:6419 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:6553 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:6555 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:6635 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:6637 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:6919 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:7173 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:7175 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:7240 [test] proxy fixture setup.
- src-tauri/src/services/proxy.rs:7242 [test] proxy fixture setup.
- src-tauri/tests/profile_roundtrip.rs:112 [test] profile fixture create.
- src-tauri/tests/profile_roundtrip.rs:116 [test] profile fixture create.
- src-tauri/tests/profile_roundtrip.rs:126 [test] profile fixture create.
- src-tauri/tests/profile_roundtrip.rs:133 [test] profile fixture create.
- src-tauri/tests/profile_roundtrip.rs:292 [test] profile fixture create.
- src-tauri/tests/profile_roundtrip.rs:501 [test] profile fixture create.
- src-tauri/tests/profile_roundtrip.rs:505 [test] profile fixture create.
- src-tauri/tests/profile_roundtrip.rs:670 [test] profile fixture create.
- src-tauri/tests/profile_roundtrip.rs:677 [test] profile fixture create.
- src-tauri/tests/profile_roundtrip.rs:762 [test] Linux Desktop fixture create.
- src-tauri/tests/profile_roundtrip.rs:769 [test] Linux Desktop fixture create.
- src-tauri/tests/provider_commands.rs:69 [test] command fixture create.
- src-tauri/tests/provider_service.rs:2927 [test] service fixture create.
2026-08-03 10:01:45 +00:00
Jason 200aa14295 style(providers): fix prettier formatting in ProviderForm 2026-07-21 17:20:45 +08:00
Jason dbb5bd1537 feat(codex): xAI (Grok) OAuth managed provider with native Responses compat
Add a managed "xAI (Grok) OAuth" Codex provider that routes through the
local proxy to api.x.ai via the shared Grok CLI OAuth identity, plus the
native-Responses compatibility layer that makes Codex 0.142+ traffic work
against xAI's strict upstream serde parser.

Provider:
- codex.rs: recognize the xai_oauth placeholder in extract_auth, hard-pin
  the base URL to api.x.ai and the tool profile to native Responses
- forwarder.rs: treat xAI OAuth auth failures as non-retryable
- presets + ProviderForm/CodexFormFields: managed OAuth preset that hides
  the api key/endpoint fields and derives the provider type across apps

Native Responses compatibility (gated on is_xai_oauth, so no other
provider is affected):
- transform_codex_responses_namespace: flatten Codex's private
  namespace/plugin tool declarations into top-level function tools on the
  request; restore the flat function_call names back to {name, namespace}
  on the response (streaming and non-streaming) so the client matches its
  own namespaced tool registry
- transform_codex_responses_xai_sanitize: strip the OpenAI-backend-private
  fields xAI rejects (external_web_access, prompt_cache_retention,
  safety_identifier, the additional_tools carrier, tool_search, ...) with
  deterministic removals that keep the prompt-cache prefix stable
- wire both into the native passthrough after the request transform;
  response restore runs in a dedicated handler so the generic passthrough
  hot path is untouched

Ports the proven approach of sub2api's Grok Responses gateway. Verified
with a 4-round codex -> xAI OAuth workload: all tasks green, zero upstream
errors.
2026-07-21 16:39:34 +08:00
Jason e9317f476e feat(xai): add Grok account management UI with four-locale strings
Add XaiOAuthSection (device-code login, account list, default selection)
backed by the generalized useManagedAuth hook, wire it into the Auth
Center and both Claude provider forms, and expose model fetching for
signed-in accounts.

- Accounts requiring re-auth stay visible in the account selector as
  disabled items with an expired badge instead of silently vanishing.
- Auth status refetches periodically so a revoked refresh token surfaces
  without reloading the panel.
- All strings ship in zh/en/ja/zh-TW; a locale coverage test pins every
  required key in all four locales.
2026-07-21 16:39:34 +08:00
Thefool 1c0ee0c58a feat(grokbuild): add first-class Grok Build support (#5453)
* feat(grokbuild): add backend integration

* feat(grokbuild): add provider and management UI

* test(grokbuild): cover configuration and integrations

* fix(grokbuild): address backend review feedback

* fix(grokbuild): complete UI review feedback

* feat(grokbuild): add CLI lifecycle management

* fix(grokbuild): align provider icon fallback

* test(grokbuild): cover provider icon persistence
2026-07-17 15:50:50 +08:00
Jason a078b4b207 feat(proxy): session-based prompt_cache_key routing for Codex Chat bridge
Codex always sends prompt_cache_key in its Responses requests, but the
Responses -> Chat Completions conversion dropped it, breaking session
cache affinity on upstreams that route by key (e.g. Kimi Coding).

- Re-inject prompt_cache_key after conversion in the forwarder: an
  explicit client key wins, otherwise a client-provided session ID;
  generated per-request UUIDs are never sent upstream.
- Provider-aware gating: "auto" enables only known-compatible upstreams
  (api.openai.com, api.kimi.com/coding) because strict gateways reject
  unknown fields with HTTP 400 (e.g. Fireworks); an advanced
  Auto/Enabled/Disabled override is available on the Codex form in all
  four locales.
- Kimi For Coding preset opts in explicitly.
2026-07-13 16:48:21 +08:00
Jason 06039540ff refactor(health-check): remove per-provider test config 2026-07-13 16:48:21 +08:00
Jason 7479d10db7 feat(codex): add default model field to provider form
Expose the top-level `model` key of config.toml as an editable field in
the Codex provider form, so users can switch to newly released models
(e.g. gpt-5.6) without waiting for a preset update — preset updates only
affect newly added providers, existing ones keep their saved TOML.

The field syncs bidirectionally with the TOML editor (mirroring the
base-URL pattern), suggests models from the mapping catalog plus the
provider's /models endpoint, and offers a one-click "add to mapping"
action when the value is outside a non-empty catalog. Hidden for
official providers.

Details:

- Save-time catalog sync now only backfills the first mapping row into
  the top-level model when the field was left empty, so the explicit
  field wins over the implicit row-0 sync
- Escape model names (and base_url) with TOML basic-string escaping and
  strip control characters from field input: /models ids are remote
  data and unescaped interpolation could inject config.toml lines
  (e.g. a forged [mcp_servers.*] command)
- The strict model-line matcher now recognizes escaped output, empty
  strings and single-quoted literals, keeping extract/set round-trips
  stable; deliberately no key-only loose matching (it would misfire on
  assignment-looking text inside multiline strings)
- Invalidate the fetched model list whenever the request identity
  changes (base URL, full-URL toggle, API key, custom UA) and discard
  in-flight responses via a sequence guard, so the dropdown never shows
  a previous provider's models
- i18n for zh/en/ja/zh-TW
2026-07-13 16:48:21 +08:00
v2v 6245caa6c5 Fix/opencode known field editors (#2907)
* fix: add opencode model limit editor

* fix: add opencode headers editor

* style: refine opencode advanced field layout

* fix: preserve valid opencode headers

* fix: preserve opencode extra options and sync translations

---------

Co-authored-by: wzk <wx13571681304@outlook.com>
Co-authored-by: Jason Young <44939412+farion1231@users.noreply.github.com>
Co-authored-by: Jason <farion1231@gmail.com>
2026-07-11 16:38:00 +08:00
Yeeyzy 99e11e0851 feat(codex): support native Anthropic Messages protocol as upstream (#5071)
* feat(codex): support native Anthropic Messages protocol as upstream

Allow gateways that only expose the native Anthropic Messages protocol
(/v1/messages) to be used by Codex: the local proxy performs bidirectional
request/response/streaming conversion between Responses and Anthropic.

Backend:
- Add two conversion modules: transform_codex_anthropic / streaming_codex_anthropic
- codex.rs: add routing detection and auth: ANTHROPIC_AUTH_TOKEN→Bearer (default),
  ANTHROPIC_API_KEY→x-api-key, mutually exclusive
- handlers.rs: add handle_codex_anthropic_to_responses_transform
- forwarder.rs: support optional Claude Code client fingerprint impersonation
  (User-Agent / anthropic-beta / x-app / system prompt first-line injection)
  and /responses→/v1/messages rewriting
- codex_config: the anthropic format reuses the NativeResponses profile to strip
  custom tools
- ProviderMeta: add impersonateClaudeCode

Frontend:
- CodexApiFormat: add "anthropic"; the form adds auth field selection and an
  impersonation toggle
- Add en/ja/zh/zh-TW copy

Robustness:
- Downgrade when tool history / forced tool_choice conflicts with extended
  thinking, avoiding upstream 400s
- Emit cache_creation_input_tokens in usage and use saturating_add to guard
  against overflow
- Append a unique suffix to non-streaming/streaming output-item ids to avoid
  multi-segment text/thinking overwriting each other

* fix(codex): harden Anthropic bridge against empty text blocks and truncated streams

- Drop empty/whitespace-only text content blocks when rebuilding Anthropic
  messages from Responses history; Anthropic 400s on empty text blocks (e.g. an
  empty assistant text emitted alongside a tool_use), which broke follow-up and
  tool-result requests. Also drop messages left without content.
- Do not report a truncated Anthropic stream as completed: when the SSE
  connection ends before message_stop with no stop_reason, emit an incomplete
  response if partial output exists, or a failed (stream_truncated) response
  otherwise, mirroring the chat converter's EOF handling.

* fix(codex): resolve Anthropic-bridge review findings

Blocking:
1. Defer stripping the [1m] long-context marker until after catalog matching and model write-back, re-stripping it on the final Codex→Anthropic body and setting a flag to emit the context-1m-2025-08-07 beta header, so the marker is no longer lost or overridden by the default model.
2. Gate Anthropic thinking on the trailing turn only (via trailing_turn_allows_thinking) instead of scanning full history, so a Codex session that resends history each round no longer permanently loses thinking after the first tool call.
3. Inject 5m ephemeral cache_control on the Codex→Anthropic body by reusing cache_injector (handling the system string→array conversion), so system/tools/history are cached instead of re-sent at full price every round.
4. Add a shared base_url_is_full_endpoint helper (normalizing whitespace/query/fragment/trailing slash) used by both the Anthropic and Chat paths, so a base URL already ending in /v1/messages is treated as a full endpoint instead of double-appending to /v1/messages/v1/messages.
5. Align the catalog tool-profile predicate with the routing predicate so resolve_codex_catalog_tool_profile returns the Anthropic profile whenever the request converts to Anthropic, preventing freeform tools like apply_patch from being silently filtered by a ProxyChat catalog.
6. Explicitly disable native web_search for the Anthropic profile (including the no-catalog branch) via set_codex_native_web_search_field, so Codex no longer treats it as available while the transform silently drops it.
7. Only forward tool_choice when tools survive filtering, dropping it otherwise, to avoid a non-retryable 400 from upstream when tool_choice is sent with no tools.
8. Lower the fallback default to max_tokens=8192 (only when max_output_tokens is omitted) and clamp the thinking budget to max_tokens/2, disabling thinking below the 1024 floor, to avoid hard 400s on low-output-ceiling models/gateways.

Minor:
9. Centralize the Codex/OpenAI fingerprint-header denylist in is_codex_client_fingerprint_header so impersonating Claude Code uniformly drops originator/session_id/conversation_id/chatgpt-account-id/x-client-request-id/openai-* and the x-stainless-*/x-codex-* prefixes.
10. Retain content_block_start.input as start_input and fall back to it at block close when no input_json_delta arrived, so a gateway that carries the full tool input on the start event no longer yields empty tool arguments.
11. Extract a shared codex_responses_sse module as the single Responses SSE envelope builder that both the chat and anthropic streaming emitters delegate to, with byte-for-byte-unchanged wire output, so future event-format fixes touch one place instead of two.

* fix(codex): add per-provider max_output_tokens override for Codex→Anthropic path

Codex does not forward model_max_output_tokens in the request body,
causing the proxy to fall back to a conservative 8192 default. This
truncates long or thinking-heavy responses (stop_reason=max_tokens).

- Add maxOutputTokens field to ProviderMeta (Rust + TypeScript)
- Inject the value into the Anthropic request body before transform,
  taking precedence over request-supplied and default values
- Add numeric input in Codex form fields (Anthropic format only)
- Add i18n entries for label, placeholder, and hint (en/zh/zh-TW/ja)
- Include roundtrip and omission unit tests for the new field

* fix(codex): harden Anthropic protocol bridge

* fix(codex): address Anthropic bridge review

* fix(codex): preserve flattened Anthropic inputs

* fix(codex): harden Anthropic recovery paths

* fix(ci): satisfy Rust clippy

---------

Co-authored-by: Jason <farion1231@gmail.com>
2026-07-10 23:06:30 +08:00
秋澪Akimio b3e5e32c89 feat: add Claude subagent model config (#4830)
* feat: add Claude subagent takeover config

* feat: add Claude subagent model field

* i18n: add Claude subagent model labels

* fix(proxy): preserve configured subagent model mapping

* fix(providers): exclude subagent model from Claude common config

* style: format rust code
2026-07-07 10:28:36 +08:00
Jason 85b38c1bed refactor(codex): decouple model mapping from local-routing toggle
Align the Codex provider form with Claude Code. Model mapping (the
catalog) is independent from route takeover: native Responses providers
(MiMo, Doubao, MiniMax) need it for direct connect with no proxy, while
Chat providers use the proxy regardless of any per-provider flag.

- Remove the "Needs Local Routing" toggle. It had no backend field and
  only gated catalog/reasoning persistence, which is equivalent to
  "is the mapping filled".
- Always show model mapping for non-official providers; persist when the
  list is non-empty (backend already keys off modelCatalog.models).
- Gate reasoning visibility/persistence on Chat format, not the toggle.
- Mark the Chat upstream-format option "routing required" and refresh the
  advanced-section hint (zh/en/ja/zh-TW); drop dead localRouting* keys.
- Move the model-mapping block above the custom User-Agent block.

fix(codex): preserve native-profile hidden catalog fields on edit

useCodexConfigState dropped supportsParallelToolCalls / inputModalities /
baseInstructions when loading a saved provider, so editing and saving a
native Responses provider lost parallel tools, image input and the
official base instructions from the generated catalog. Preserve them on
load (camelCase + snake_case) and compare them in the row sync. Add a
regression test.
2026-06-29 23:30:58 +08:00
Jason 15e712e779 feat(codex): support native Responses direct-connect with generated model catalog
Codex providers can now run in two modes per provider:

- Proxy-Chat (route takeover): apiFormat=openai_chat, existing Responses<->Chat conversion. Unchanged.

- Native-Responses (direct): apiFormat=openai_responses, no proxy. cc-switch generates ~/.codex/cc-switch-model-catalog.json so Codex shows the custom models and tools work without the freeform apply_patch (type=custom) tool that native gateways like MiMo reject; editing falls back to shell_command.

Catalog generation is keyed on apiFormat (CodexCatalogToolProfile), decoupled from the takeover toggle, so native providers persist a catalog without enabling local route mapping.

Codex's catalog parser requires base_instructions on every entry; the native template carries a neutral default and per-vendor official text overrides it (MiMo, MiniMax). Synthesized catalogs for Qwen/Doubao/LongCat use the neutral default.

Existing native providers must be re-saved once to regenerate a valid catalog (no DB migration).
2026-06-29 23:30:58 +08:00
Jason a4eb5f3778 feat(codex): decouple upstream format selector from model-mapping toggle
The Codex provider form tied Chat-format conversion and route takeover (model
mapping) to one toggle, so a provider serving a native Responses API could not
use model mapping without forcing Chat Completions conversion.

- Promote the upstream format (Chat Completions / Responses) to an independent,
  always-visible selector that triggers no sub-menus on its own.
- The local-routing toggle now solely gates the advanced sub-sections: model
  mapping catalog, plus reasoning capability when the format is Chat.
- Persist modelCatalog / codexChatReasoning based on the takeover toggle and
  derive its initial state from saved catalog presence (no new persisted field).
- Refresh codexConfig i18n (zh/en/ja/zh-TW): add upstreamFormat* keys and reword
  the routing/advanced hints to reflect the decoupling.
- Fix codexChatProviderPresets test expectation for the Doubao Seed 2.1 Pro rename.
2026-06-26 09:15:51 +08:00
Jianfeng Mei 6fd4e6f462 feat(proxy): 添加本地代理请求覆盖功能,支持自定义请求头和请求体 (#4589)
* feat(proxy): 添加本地代理请求覆盖功能,支持自定义请求头和请求体

* fix(proxy): harden local request overrides validation

* feat(proxy): 添加受保护的本地代理请求头名称验证功能

* fix(i18n): 更新本地代理请求覆盖的错误提示信息格式

---------

Co-authored-by: jason.mei <jason.mei@ucloud.cn>
2026-06-24 22:19:01 +08:00
Tan Kimzeg 55abd1822c feat(ui): 为多处 JsonEditor 组件接入暗色模式 (#4556)
在为 UsageScriptModal、ProviderForm、UniversalProviderFormModal 中
的 JsonEditor CodeMirror 编辑器添加 darkMode 支持,使用 useDarkMode()
hook 监听 App 主题,自动切换 oneDark 编辑器主题。
2026-06-23 23:30:01 +08:00
Jason 4f8a79c273 feat: add Claude Fable 5 model mapping across Claude Code and Desktop
- Wire claude-fable-5 as a fourth tier on both proxy paths, with a
  fable -> opus -> default fallback mirroring the official downgrade.
- Whitelist the fable- prefix for the Desktop 1.12603.1+ validator.
- Clarify fallbackModelHint (zh/en/ja/zh-TW): a blank tier on
  third-party endpoints forwards the literal model name and 404s.

Refs #3980, #4026, #4049.
2026-06-13 19:12:34 +08:00
RoromoriYuzu 25983f3420 fix: omit customUserAgent when provider category is official
Stale custom UA values from non-official presets were persisted even
after switching to an official preset, silently altering request headers.
2026-06-11 08:29:29 +08:00
RoromoriYuzu ff706e9e96 feat: add provider user agent override 2026-06-11 08:29:29 +08:00
Jason a04e72a267 Fix Codex edit dialog masking live OAuth during proxy takeover
The reported "OAuth access token disappears when enabling Codex proxy
takeover" was a display artifact, not data loss: auth.json on disk kept
the OAuth login the whole time. During takeover the edit dialog falls
back to the stored provider config (so it does not surface the proxy
placeholder), which for a third-party provider shows that provider's own
key instead of the live auth.json, making the OAuth token look gone.

Thread an isProxyTakeover flag from App through ProviderForm into the
Codex editor and show an explicit notice plus storage-aware auth/config
hints clarifying that the form displays the stored provider config while
the live config is temporarily managed by the proxy. Drop the
proxy-running condition so the notice shows whenever takeover is active,
even with the proxy stopped.

Add a regression test asserting the dialog does not read live settings
during takeover and renders the database config. i18n synced across
en/zh/ja/zh-TW.
2026-06-01 11:41:05 +08:00
Jason af60c7ed2c feat(codex): add remote compaction toggle for third-party providers
Write model_providers name as "OpenAI" to let Codex attempt remote
compaction through compatible endpoints. Hidden for official providers.
2026-05-28 11:57:54 +08:00
Jason 44d9aabbf3 feat: adaptive reasoning detection for Codex Chat providers
Auto-detect each Chat-routed Codex provider's reasoning interface from
its name, base URL, and model, then inject the matching thinking
parameter without manual configuration:

- Platform-first inference (OpenRouter, SiliconFlow) overrides model
  rules, since the same model exposes different reasoning controls
  depending on the hosting platform.
- Effort tiers are forwarded only to providers that support them
  (DeepSeek, OpenRouter, and StepFun's step-3.5-flash-2603); on/off-only
  providers (Kimi, GLM, Qwen, MiniMax, MiMo, SiliconFlow) drop the level
  instead of sending a field the upstream rejects.
- OpenRouter uses the native reasoning:{effort} object, clamps max to
  xhigh (its enum has no max), and forwards an explicit effort:"none" so
  reasoning can be turned off.
- StepFun falls back to inference so per-model effort support is honored
  (the static preset would have forced effort on step-3.5-flash too).

Includes the Codex provider-form reasoning controls, i18n strings
(zh/en/ja), and response-side reasoning extraction.
2026-05-25 22:20:33 +08:00
Jason f2935a3db9 feat: add Chat Completions routing for 22 Codex third-party presets
Enable openai_chat routing with explicit model catalogs across the major
Chinese/Asian providers (DeepSeek, Zhipu GLM, Kimi, MiniMax, Baidu Qianfan,
Bailian, StepFun, Volcengine Agent Plan, BytePlus, DouBaoSeed, ModelScope,
Longcat, BaiLing, Xiaomi MiMo, SiliconFlow, Novita AI, Nvidia, etc.). Each
preset declares its context window so the UI can size catalog rows when the
preset is picked.

Also lands two consistency fixes uncovered along the way:
- Include setCodexCatalogModels in resetCodexConfig's useCallback deps to
  match the new third parameter it consumes.
- Realign TheRouter Codex test to the "custom" model_provider bucket
  established by the recent third-party unification; the previous assertion
  predated that refactor and had been failing on HEAD.
2026-05-25 22:20:33 +08:00
Jason 791ced0034 fix: Codex model catalog WYSIWYG and config consolidation
- Remove mergeCodexDefaultCatalogModelForSave implicit injection (P1)
  The model mapping table is now the single source of truth; no hidden
  entries are prepended on save.

- Sync first catalog row model into config.toml on save
  Ensures Codex default request model matches the table's first entry
  instead of retaining a stale template value.

- Remove API Format selector from CodexFormFields (P3)
  wire_api is always 'responses'; the selector confused users into
  thinking they were changing the upstream protocol. Only the 'Needs
  Local Routing' toggle remains.

- Add restart hint to model mapping i18n text (P2)
  model_catalog_json is loaded at Codex startup; users are now informed
  that a restart is needed after changes.

- Unify write_codex_live_with_catalog helper (P4)
  Replaces three scattered prepare+write call sites in config.rs,
  provider/live.rs, and proxy.rs with a single entry point.

- Clean up useCodexConfigState dead state (P3 follow-up)
  Remove codexModelName, codexContextWindow, codexAutoCompactLimit and
  their handlers/effects since no component consumes them after the UI
  consolidation.
2026-05-25 22:20:33 +08:00
Jason 1c82b8a3fa Add Chat Completions routing for Codex providers
- Add a Codex API format selector and routing badge for Chat Completions providers.
- Convert Codex Responses requests to upstream Chat Completions when routing is required.
- Convert Chat Completions JSON and SSE responses back to Responses format.
- Keep generated Codex wire_api values on Responses for Codex compatibility.
- Add i18n labels, provider metadata handling, and focused conversion tests.
2026-05-19 10:23:48 +08:00
BlueOcean 0fb7fd12e5 feat: add Xiaomi MiMo Token Plan presets (#2803)
* feat: add Xiaomi MiMo token plan presets

* fix: update Xiaomi MiMo provider presets

* fix: align MiMo V2.5 model specs with official documentation

- Update maxTokens from 32000 to 131072 (128K) for mimo-v2.5-pro and mimo-v2.5
- Update contextWindow from 262144 to 1048576 (1M) for mimo-v2.5
- Aligns with official specs from Xiaomi MiMo documentation
- Ensures consistency between OpenClaw and OpenCode presets

---------

Co-authored-by: Jason <farion1231@gmail.com>
2026-05-18 10:38:03 +08:00
Jason ec8afd63ea refactor(presets): render presets in array order and prioritize partners
Remove the category-based grouping logic from ProviderPresetSelector,
letting the array position in each preset config file be the single
source of truth for display order. Move partner presets (PatewayAI,
火山Agentplan, BytePlus, DouBaoSeed) right after Shengsuanyun across
all 6 config files so they appear earlier in the UI.
2026-05-15 23:28:59 +08:00
Jason 402570ce31 fix(usage): pricing routing, SSE lifecycle, and validation hardening
* model pricing routing: extend prefix-match families (gpt-/o1-o5/
  gemini-/deepseek-/qwen-/glm-/kimi-/minimax-) with per-family dash
  thresholds so short base IDs like gpt-5 no longer mis-match
  gpt-5-mini; strip ISO and 8-digit date suffixes via UTF-8-safe
  byte matching so claude-haiku-4-5-20251001 falls back to
  claude-haiku-4-5 pricing
* SSE collector: SseUsageFinishGuard (RAII) guarantees finish() on
  early return or panic; AtomicBool fast path lets push() skip the
  Mutex once first-event time is recorded
* validation: shared validate_cost_multiplier / validate_pricing_source
  helpers across DAO and service layers; PRICING_SOURCE_RESPONSE /
  PRICING_SOURCE_REQUEST constants replace string literals; price
  fields in update_model_pricing now reject empty / non-decimal /
  negative input before INSERT
* backfill: add backfill_missing_usage_costs_for_model so a single
  price edit only scans matching rows instead of the full log table;
  startup backfill remains full-scan
* session_usage{,_codex,_gemini}: share find_model_pricing helper from
  usage_stats; metadata_modified_nanos centralizes mtime precision
* frontend: NON_NEGATIVE_DECIMAL_REGEX + isNonNegativeDecimalString
  replace three copies of the same multiplier regex; isUnpricedUsage
  surfaces zero-cost rows that have usage tokens (cached per row to
  avoid double evaluation); invalidate usageKeys.all on pricing mutate
  so backfilled rows refresh
2026-05-14 11:53:51 +08:00
Jason 4b57f7e113 feat(claude-code): role-based model mapping with display names and 1M flag
- Replace the four flat env inputs with a Sonnet/Opus/Haiku role table.
  Each row exposes ANTHROPIC_DEFAULT_*_MODEL plus a new display name
  field ANTHROPIC_DEFAULT_*_MODEL_NAME, and Sonnet/Opus gain a
  "Declare 1M" checkbox that toggles the [1M] suffix.
- Strip the [1M] context-capability marker before forwarding non-Copilot
  requests upstream. Copilot keeps its existing [1m]->-1m normalization.
- Claude Desktop import now consumes ANTHROPIC_DEFAULT_*_MODEL_NAME as
  label_override, closing the Claude Code -> Claude Desktop displayName
  pipeline; add_route's merge logic is shared between hashmap branches.
- Unify the [1M] marker as ONE_M_CONTEXT_MARKER across
  claude_desktop_config and proxy::model_mapper; rename the strip
  helper to strip_one_m_suffix_for_upstream.
- Collapse useModelState's seven duplicated useState initializers and
  the useEffect parse block into a single parseModelsFromConfig call.
- Add tests/hooks/useModelState.test.tsx and a Claude Desktop import
  test covering Kimi K2 -> label_override. i18n (en/ja/zh) updated.
2026-05-13 17:06:05 +08:00
Jason 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.
2026-05-08 22:34:46 +08:00
allen_xln 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>
2026-04-25 09:28:28 +08:00
Jesus Díaz Rivas 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>
2026-04-23 12:05:17 +08:00
Jason 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
2026-04-21 11:57:07 +08:00
Jason 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).
2026-04-21 11:57:06 +08:00
Jason 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
2026-04-21 11:57:06 +08:00
Jason 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)
2026-04-21 11:57:06 +08:00
Jason 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
2026-04-21 11:57:06 +08:00
Jason 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)
2026-04-14 14:26:55 +08:00
Jason a5a5207f74 style: fix prettier formatting in ProviderCard and ProviderForm 2026-04-09 21:13:32 +08:00
Jason 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.
2026-04-09 16:49:14 +08:00
Jason 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.
2026-04-09 16:49:13 +08:00
Jason 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.
2026-04-02 17:15:11 +08:00
Dex Miller 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>
2026-04-01 21:16:41 +08:00
Dex Miller 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
2026-03-28 15:52:02 +08:00
Zhou Mengze 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>
2026-03-17 23:57:58 +08:00
Hexi 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.
2026-03-15 15:24:17 +08:00
Jason 239c6fb2d7 fix: prevent common config modal infinite reopen loop and add draft editing
The auto-open useEffect in CodexConfigEditor and GeminiConfigEditor
created an inescapable loop: commonConfigError triggered modal open,
closing the modal didn't clear the error, so the effect immediately
reopened it — locking the entire UI.

- Remove auto-open useEffect from both Codex and Gemini config editors
- Convert common config modals to draft editing (edit locally, validate
  before save) instead of persisting on every keystroke
- Add TOML/JSON pre-validation via parseCommonConfigSnippet before any
  merge operation to prevent invalid content from being persisted
- Expose clearCommonConfigError so editors can clear stale errors on
  modal close
2026-03-11 14:40:14 +08:00
Jason 0bcbffb8a0 feat: rename UCloud provider to Compshare/优云智算 with i18n support
Add optional `nameKey` field to all preset interfaces for localized
display names. The preset selector and form now resolve `nameKey` via
i18next, falling back to `name` when unset.

- zh: 优云智算 / en+ja: Compshare
- Update model ID prefix ucloud/ → compshare/ in OpenClaw presets
- Update partner promotion copy and README sponsor descriptions
2026-03-10 23:43:23 +08:00
Jason cc15d7b1e3 feat: add OpenClaw User-Agent toggle, default off
Add a switch in the OpenClaw provider form to optionally send a browser
User-Agent header. The toggle defaults to off — only providers that
explicitly include headers in their preset or config will have it enabled.

Remove the previous auto-injection logic that force-added User-Agent on
every preset load and new provider creation.
2026-03-09 10:36:11 +08:00
Jason dd971246be feat: restore Claude provider auth field selector (AUTH_TOKEN / API_KEY) 2026-03-09 08:56:58 +08:00