Pages (phase 4):
- Usage: v7 layout (filter row, four metrics with more on demand,
switchable trend, request log / provider / model / pricing tabs, data
sources sheet). Latency becomes output speed: output / (duration -
first token), shown only for requests with at least 100 output tokens;
provider totals sum output and generation time instead of averaging.
- Sessions: single-column list grouped by project or time, drilling into
a reader with resume, find, question jump and copy actions.
- MCP and Skills share one app matrix (neutral checks, retry on failed
writes, column bulk actions with undo); MCP add/edit moves into a
drawer with form/JSON modes; Skills merges installed and discover and
always shows a Pi column.
- Prompts: one page for eight apps including Hermes SOUL.md, enable and
disable as buttons, delete with undo instead of a confirm, copy to
other apps, import existing files; app pages show the active prompt.
- Add provider is two steps: pick a preset (always-on search including
Chinese names and domains, categories inferred for display, sorted by
name with no promoted entries), then fill the form. Edit pages name
the provider and warn when it is the one in use.
- App page menu links to that app's usage.
Visual pass (phase 6):
- Secondary pages cover only the content area so the sidebar stays.
- Cmd/Ctrl+K jumps to apps, pages and settings sections.
- Legacy button variants, primary color, dialogs, toasts and sheets
follow v7 tokens; hard-coded palette classes and 10px text across
the remaining components are replaced with tokens.
Quota on provider cards now reads as remaining ("5-hour 69% left", "Balance 82.10") in the secondary text color, keeping the two tiers with the least left. A tier under 10% turns warning, used up / expired / failed turns danger, and a failed query shows the reason on a second gray line. Reset times and the last update move into the hover text; clicking the quota checks again. Expanded views use one bar style where a shorter bar means less left. Green is gone from quota everywhere.
Switch-style apps (Claude Code, Codex, Gemini CLI, Grok Build) get a mode
row (Direct / Routing / Stack). Tabs only change what is viewed; taking
effect goes through buttons that state the consequence, with a confirm
dialog for entering a proxy mode and a one-step, undoable return to
direct. Provider cards are drawn from a per-mode presentation model
(status, buttons, chips, queue order, disabled reasons) instead of each
card inspecting proxy state, and additive apps get Added / Available
sections.
Backend: entering a mode can pick the route target in the same step,
`get_app_mode` reports the effective mode per app, and startup attach
failures are kept for the UI to surface with a retry.
Settings gains a Local Routing section (service, listen address, apps
using routing, failover tuning) replacing the old proxy tab and toggles.
Claude Desktop gets an access bar for model mapping and service state.
Replace the header toolbar and icon-only app switcher with the v7 shell:
- Sidebar (200px, collapses to a 72px icon rail under 960px, Cmd/Ctrl+\
toggles and remembers the choice): the app list with mode tags and
"needs attention" dots, then Usage (today's cost), Accounts (re-login
dot), MCP, Skills, Prompts and Sessions pinned to the bottom, and an
Apps / Settings footer. Settings swaps the sidebar for its own
directory.
- Every screen gets a 52px page header; Linux in-app window controls
sit at its far right. macOS traffic lights move down to centre in the
44px sidebar top bar.
- Usage, Accounts, MCP, Skills, Prompts (with an app picker) and
Sessions become global pages instead of settings tabs or per-app
toolbar buttons. OpenClaw and Hermes switch to their own pages with a
segmented control; OpenClaw's env, tools and agents merge into one
Config page.
- New Apps page: per-app install / upgrade with path and install
source, batch upgrade, conflict details, manual commands, and the
"show in sidebar" switch that used to live in settings. Tool
management moves out of About into a shared hook; a read-only
list_tool_installations command feeds the page so it does not share
calls with the upgrade preflight.
- Settings regroup into General, App config, Local routing, Network,
Data and About, using row/card layout. Path settings save per
section; switches still save immediately. Skills storage and sync
move to a sheet on the Skills page.
- New opt-in "check app updates on startup" setting (off by default):
when on, a background check puts a neutral dot on Apps.
- Switch adopts the v7 track and action colour; env-conflict banner
joins the page flow instead of overlaying the header.
The old route / Stack / failover toggles stay in the provider page
header until the mode tabs land.
Foundation for the v7 redesign; no layout changes yet.
- Add the v7 tokens (surfaces, text, action, inverse, direct / route / stack,
status, chart, shadows) to index.css and expose them in Tailwind together
with v7 radius, shadow and type-role scales; teach tailwind-merge the new
names so text-body no longer cancels text-fg-1.
- Align the shadcn HSL tokens (background, card, muted, border, ring) with the
v7 palette and use the neutral ring for focus outlines.
- Rename light / dark to Light / Dark Side of the Moon with System first, and
pause transitions while switching so colors don't fade in piece by piece.
- Add v7 button variants (solid, neutral, quiet, direct, route, stack) and
sizes, plus HelpTip / DisabledReason (the "?" card), SegmentedControl,
Notice / NoticeSlot and Sheet.
- Respect the OS reduced-motion setting for framer-motion.
Claude Desktop model-mapping providers send every request through the local
routing service, but the service lifecycle only counted the four routing apps:
when Claude Code left routing, stop_server_if_unused stopped the service Desktop
was still using, and nothing started it on launch.
- stop_server_if_unused now also treats a current Desktop model-mapping
provider as a user of the service.
- ensure_desktop_mapping_service starts the service when that provider is in
use; it runs on startup, after switching a Desktop provider and after
applying a project that covers Claude Desktop.
- The frontend no longer warns that routing must be started when switching to
a Desktop model-mapping provider, since the backend starts it; drop the now
unused isProxyRunning hook argument and proxyReasonClaudeDesktop string.
- Always show the usage button in the header and add an Accounts button; the
usage dashboard has data from session logs even without routing.
- Cmd+, now always opens the General settings tab.
- Remove the dead agents and universal views, which had no entry point but
could still be restored from localStorage.
- Restore thin always-visible scrollbars; keep a .no-scrollbar opt-out.
- Keep the provider card's main action visible; only the secondary icon
buttons still reveal on hover.
- Show the needs-routing badge for every app providerNeedsRouting covers,
which adds the missing Grok Build badge.
- Replace leftover "proxy takeover" wording with Routing mode and refresh the
outdated Accounts description in all four locales.
Opening any Hermes session in the Session Manager failed with "no such
column: created_at": the messages table stores `timestamp` (REAL epoch
seconds). Rework the SQLite reader to follow Hermes' own display
projection:
- Select columns by what the store actually has, so older state.db files
without active/compacted/display_metadata still load.
- Show live rows plus compaction-archived rows (active = 1 OR
compacted = 1); rewound/regenerated rows stay hidden. Collapse the
copies compression re-inserts with Hermes' display identity (role,
content, timestamp, tool_call_id, tool_calls, tool_name), keeping the
first position and the most live copy, and skip model_only rows.
- Order by id: timestamps regress across clock skew and compaction.
- Decode "\0json:" structured content (text + image parts) to text.
- Render assistant tool calls as [Tool: name], matching Codex.
The session list now uses the first user message as summary (and as the
title when Hermes has none) and prefers last_activity_at for recency. The
provider filter dropdown gains a Hermes entry.
Supersedes #6694.
Co-authored-by: h6rime <127593600+h6rime@users.noreply.github.com>
- add Command Code presets for Claude Code and Codex
- map the Claude and Codex provider endpoints and model catalogs
- detect both base URLs and query credits, rolling limits, and subscription periods
- document the preset and usage coverage in English, Chinese, and Japanese
- add preset routing and quota parsing tests
Chat-compatible upstreams (DeepSeek-style models, MiniMax M3) can inline
reasoning at the start of `content` as `<think>…</think>` or
`<thinking>…</thinking>`. The Claude path passed the whole block through
as plain text, tags included; the Codex path only knew `<think>`.
Split the leading block into reasoning with one shared splitter
(proxy/providers/inline_think.rs), used by the Claude and Codex paths,
streaming and non-streaming:
- Both tag pairs are recognized, mismatched pairs are tolerated, and tags
split across SSE chunks are handled.
- Reasoning is emitted per delta; only a short tail that may be the start
of a close tag is held back. The idle timeout measures the converted
stream, so buffering the whole block until the close tag let it fire
during long reasoning while the upstream was still sending. The Codex
path had this problem since it first shipped.
- Held content is flushed at every boundary: tool calls, finish_reason,
[DONE], truncated EOF and stream errors. At a tool call it is released
before the tool block opens, so blocks never overlap.
- Newlines adjacent to a tag are separators and are not emitted, tracked
across deltas so the output does not depend on SSE chunking. Spaces and
tabs are kept, they may be indentation.
Fixes#7722Fixes#7271
Co-authored-by: Jason <farion1231@gmail.com>
Projecting an app's MCP servers stopped at the first server that could
not be written or removed, so one bad entry left every server after it
out of sync: newly enabled ones were never written and disabled ones
stayed in the client's config.
Sync each server independently, log the failures, and report them
together at the end, grouping server ids that failed for the same
reason.
Co-authored-by: AnxForever <130662349+AnxForever@users.noreply.github.com>
A spec without `type` was always written as stdio. The panel's JSON mode
and deeplinks both accept a bare `{"url": …}`, so such a server ended up
as `command = ""` plus `url`. Codex rejects that entry with "url is not
supported for stdio" and refuses to load the whole config.toml, taking
every other MCP server down with it.
Infer the transport on the write side the same way the import side does:
no `type`, or an explicit stdio with no command, is HTTP when a `url` is
present.
Codex applies the same all-or-nothing check to every field that belongs
to the other transport, so write only the chosen transport's fields.
Stdio entries no longer carry `url`, `http_headers`, `env_http_headers`,
`http_headers_helper`, `bearer_token_env_var` or `oauth_resource`; url
entries no longer carry `command`, `args`, `env`, `env_vars` or `cwd`.
Each field is still written where it is valid. The lists were checked
field by field against Codex 0.160.
Refs #6719
Co-authored-by: ChrisPan <39005916+szupzj18@users.noreply.github.com>
Codex has no `type` key under `[mcp_servers.*]`; it infers the transport
from `command` (stdio) or `url` (streamable HTTP). Since Codex 0.158 the
`type` line CC Switch wrote on every MCP sync is reported as an
unrecognized setting, and `--strict-config` refuses to start.
Stop emitting the key from the shared Codex serializer while still using
the stored transport type to pick which fields to write. On import, infer
the transport from `url` when `type` is absent so url-only entries are no
longer treated as stdio and skipped. Grok Build output is unchanged.
Fixes#7734Fixes#7554Fixes#5820
Drop the ETok.ai entry from the four-language README sponsor table and
remove its presets from all nine apps along with the partnerPromotion.etok
copy. The etok icon stays in the icon index so existing user providers
that reference it keep rendering.
first_token_ms was stamped when the usage collector accepted its first
event, so the usage filter decided what "first token" meant. When an
upstream's opening event carried no usage key (Responses passthrough
without usage on response.created, Chat passthrough with usage only on
the final chunk), the first accepted event was the terminal one, the
generation window shrank to a few milliseconds and TPS jumped into the
thousands.
Mark the first output on every SSE data line instead, independent of the
usage filter, skipping only metadata events (response.created,
response.in_progress, ping, keepalive), the same semantic TTFT rule
Sub2API uses.
On the frontend, hide TPS when the generation window is under 100ms:
relays that buffer and flush at once, or short replies landing in one
packet, measure a transport burst rather than generation speed. This
also cleans up rows recorded before the backend fix.
CodexKeychainLogin::Found and ::Missing are only constructed by the macOS keychain reader, so non-macOS lib builds never construct them and clippy -D warnings fails on Linux and Windows. Allow dead_code on those two variants outside macOS only, keeping the lint active on macOS.
Requests for gpt-6.1-sol through the Claude-to-Codex OAuth route advertised Codex 0.155.0, below the ChatGPT-account catalog gate for that model, so the backend rejected them with HTTP 400 "model is not supported when using Codex with a ChatGPT account" and model discovery omitted it. The gate sits above the model's advertised minimal_client_version (0.153.0): the catalog lists gpt-6.1-sol only from client_version 0.159.0. Bump the shared client identity to 0.159.0, the exact observed floor rather than the latest release.
The regression tests for the generation headers and the model-list request now assert a version at or above the GPT-6.1 Sol floor.
Fixes#7797
Stack ids are only resolved while the app is in proxy mode with Stack
mode on. In routing mode the list and keys are kept but not published,
so a client still holding an old Stack id gets an error instead of being
forwarded to a list member. The Stack bit is left as is when leaving
proxy mode, so direct mode is gated too.
Rename the Stack mode to 叠加 (zh-TW 疊加) in the Chinese UI so it reads
alongside 路由 and 映射. Tags and tabs say 叠加; sentences say 叠加模式
or 叠加的模型.
- zh / zh-TW locale strings, the form defaultValues and the matching
test assertions
- the Chinese half of user-facing backend errors in mode/controller.rs,
mode/stack.rs and codex_direct.rs; the English halves are unchanged
English and Japanese keep "Stack". Code identifiers, persisted keys
(enableStackMode, the live-state stack mode), commands and log text are
unchanged.
Once `model_catalog_json` is set, Codex only knows the models in that file.
Third-party GPT rows were built from the neutral native template (a one-line
"You are Codex" prompt, `model_messages` stripped) or, on ProxyChat, from a
gpt-5.5 clone regardless of the model name, so GPT models lost their own
harness prompt, freeform apply_patch and reasoning levels.
Rows now look up Codex's bundled official list with Codex's own matching rule
(longest prefix, then one simple namespace segment, case-sensitive) and mirror
the matched entry verbatim; row overrides do not apply, and a narrower relay
window goes in `model_context_window` in config.toml. Fields owned by the
official account or backend are reset: Responses Lite off, speed tiers,
upgrade and NUX cleared, always listed, and ProxyChat keeps
`supports_image_detail_original` off. Anthropic rows and the DeepSeek vendor
catalog are unchanged. Stack members get the same entries under their
prefixed ids.
The reverse parser only collapses a row to its bare model name when it
regenerates identically, so older ProxyChat gpt-5.5 clones keep the user's
display name and window.
A native ChatGPT login has no managed-account marker, which is the normal
state, but the ownership check logged "Failed to read Codex managed OAuth
auth marker ... file does not exist" on every switch and token lookup.
Check that the marker exists first, as the other marker readers in the
same file do. A marker that exists but cannot be read still warns, and
the result is unchanged: no marker never establishes ownership.
Opening the Codex add dialog projects the custom template onto the live
config to show what config.toml will look like. The template has an empty
API key and `requires_openai_auth = true`, and the projection ran the same
check a switch does, so the dialog failed with "this Codex config has no
usable API key" before the user could type one. Third-party presets with
the same defaults failed the same way. The editor base then stayed empty,
so saving fell back to storing the row only and global edits made in the
dialog never reached the live config.
The editor now projects such a draft as if a key were present, using an
in-memory placeholder that is removed before display and before the row
is stored. Only the missing-key error is retried; other errors surface as
before. A keyless row can be saved, as the non-editor add path already
allowed once a current provider exists. Every write to the live config
still projects the real row and refuses it: switching or routing to it,
editing the current provider in direct mode, and adding it as the first
provider, with the row rolled back.
Codex groups sessions by the model_provider id in config.toml: the CLI's
resume picker and `resume --last` only list sessions recorded under the
current id, and resuming a thread needs its recorded id to still resolve.
With the official provider as the proxy (or Stack) route, CC Switch wrote
`model_provider = "cc-switch-official"`, so those sessions went into a
third bucket, separate from both the official (`openai`) and third-party
(`custom`) history, and the table was deleted as soon as the route moved
away.
The official route now uses the same id as direct official use:
- Unified session history off: no model_provider; a top-level
`openai_base_url` points the built-in openai provider at the local proxy,
so sessions stay under `openai`. A leftover custom table is kept in its
dormant form, as before. The key is updated in place, not moved.
- Unified session history on: the custom official mirror table, pointed at
the proxy with websockets off.
The two shapes produce different contracts, so toggling the setting while
routed to the official provider rewrites the client. `cc-switch-official`
is no longer written; an existing table is only removed.
`openai_base_url` is written only for this route and only with the local
proxy address. The official route already sent the client's own login to
the proxy, so nothing new is exposed. Direct third-party routes still never
use it.
Supporting changes:
- The proxy answers GET on the Responses paths with 426. The built-in
openai provider tries a WebSocket first and falls back to HTTP for the
session right away on 426; other errors used up its retries first.
- The built-in provider's `version` header is stripped from requests sent
to third parties, along with the other Codex client headers.
- The new shapes carry no placeholder key, so `live_has_proxy_placeholder`
also recognizes a live config that routes the official provider to the
configured proxy address. The port must match; with port 0 only the host
and path are checked. Other local gateways that point openai_base_url at
a different port are not treated as leftover takeover state.
Claude Code fetches the model list from the proxy once at startup, so a
change to the published Stack models only shows up after a restart.
Adding or removing a member already said so in its saved toast, but
editing a member's models or deleting a member did not.
In Claude Code Stack mode the provider list now compares the published
model ids each time the Stack list is refetched and shows "Stack models
changed. Restart Claude Code to apply" when the set differs. The refetch
right after adding or removing a member is skipped, since that toast
already asked for a restart; the skip is used up by that refetch even
when nothing changed, so it cannot swallow a later edit. Reordering
models or editing fields that are not published (keys, addresses) does
not trigger it. Codex is left to its process-based banner.
Codex's app-server reads the model catalog once at startup and keeps it,
while config.toml is re-read per request. Since 0.159 the codex CLI talks
to a managed daemon that only restarts on upgrade, so reopening codex after
a Stack change still shows the old model list; the desktop app and editor
extensions have the same problem with their own app-servers.
In Codex Stack mode the provider list now shows a banner when a running
Codex client started before the current catalog was written:
- CLI daemon: a button restarts it through Codex's own
`codex app-server daemon restart`, after a confirmation that running
tasks will be interrupted. Nothing runs when the daemon is not up.
- Desktop app / editor extensions: the banner explains how to fully quit
and reopen them; CC Switch does not restart them.
Staleness is judged against a device-local history of catalog generations
(codex-catalog-history.json) rather than file times: an unchanged catalog
is not rewritten, and quitting CC Switch removes the catalog pointer and
restores it on the next start, so a daemon that survived a CC Switch
restart still has the current catalog. Every Codex write and startup
records the catalog a newly started Codex would read; processes that
started before the oldest recorded generation count as stale.
Processes are read with ps on macOS and Linux only; Windows shows no
banner for now.
In Stack mode every added provider now stays highlighted like an added
provider in additive apps. The card and the Stack badge use violet,
matching the Stack icon in the header, so Stack mode is easy to tell apart
from routing and failover, which stay green.
In Stack mode the default provider no longer hides behind the routing
aliases. Claude Code's four role aliases (startup default, background
tasks, subagent aliases) all point at the first model of the default
provider's list, as its Stack id. Haiku drops the [1M] marker like the
routing contract does, and CLAUDE_CODE_SUBAGENT_MODEL is not written so
subagents follow the model picked in /model. The default provider's
whole list is published like any other member; Codex keeps publishing
the default's catalog as the route's own rows.
Claude providers get an explicit Stack model list (meta.stackModels).
Unset means the list follows the model mapping (ANTHROPIC_MODEL first,
then the tiers); an empty list is a deliberate choice and publishes
nothing.
In Stack mode the provider form opens a simplified layout: the model
list with a star for the default, plus the fields needed to reach the
provider, with a link to the full form. Both layouts share one form
state, so the default is synced when the user acts, not derived from
the layout at save time:
- Claude: when the first model of the list changes, ANTHROPIC_MODEL
follows it; an unchanged first model leaves ANTHROPIC_MODEL alone.
- Codex: the star follows config.toml's model. Starring, renaming or
removing the default row updates model on the spot; save only fills
model from the first row when it is empty. A default outside the list
is flagged with an action to add it.
The mode that publishes several providers' models in one client model list
is now called Stack in both the UI and the code. The feature is unreleased,
so persisted names change without aliases: the live-state entry, the pending
op, the settings flag and the two commands now use "stack".
The proxy's own "attached" state (attached_route, mode.attached, op::ATTACH)
keeps its name; it means the proxy contract is in place, not this mode.
When the official ChatGPT route is the default, Codex treats every model
under its "OpenAI" provider table as capable of remote compaction (V2): a
normal /responses stream ending in a compaction_trigger, which must return
exactly one compaction item or Codex fails the turn. Attached third-party
models could not do that, and history mixed across providers carried state
that the other side rejects.
Compaction bridge:
- Chat and Anthropic conversions and native Responses passthrough turn the
trigger into a tool-free summary turn and return the summary as a single
compaction item wrapped as ccswitch-compaction-v1.
- Only a normal stop counts as a finished summary. Truncated or filtered
turns report response.incomplete; empty summaries report response.failed.
- On replay, CC Switch summaries become a user message for every upstream.
Foreign blobs become a readable note for Chat/Anthropic conversions and go
to native Responses upstreams untouched, since they may have issued them.
Official passthrough: before the ChatGPT backend, drop reasoning items that
carry CC Switch envelopes or no encrypted content, and remove item ids that
do not match their type. Pure official history stays byte-identical.
Opaque state rectifier: when a native Responses upstream rejects encrypted
state it cannot verify (invalid_encrypted_content, "could not be verified",
"not issued to this caller", store:false lookups, xAI blob errors), strip
reasoning items and encrypted parts, plus compaction where warranted, and
retry the same provider once. A second failure goes through the normal
error classification. Governed by the rectifier master switch.
Also strip x-oai-*, x-openai-* and x-responsesapi-* client fingerprint
headers from third-party requests, and drop stale entity headers when a
streamed SSE body is rewritten (this also fixes the xAI namespace restore
stream, which kept the upstream Content-Length).
Attached models were a per-card toggle inside routing mode. Turn them
into an attached mode that sits beside routing: settings show either
the routing switch or the attached-mode switch on the main page, for
Claude Code and Codex only (Gemini CLI and Grok Build keep routing).
Turning one on first sends Claude Code and Codex apps that are in the
other mode back to direct.
In attached mode the provider list works like the additive apps: Add
and Remove put a provider's models into the client's model picker, and
"Set as default" picks the provider for unprefixed requests. The
default is always on the list and cannot be removed; a new default set
from the card, the tray or a project joins it. Official accounts can
only be the default. There is no failover: default requests go to the
default only, enabling failover is refused under the switch lock, and a
late failover result from a request sent before entering attached mode
is dropped. The queue is kept for routing mode.
Internally attached mode is proxy mode plus PoolState.enabled, written
on every proxy entry and committed with the client files. With the bit
off nothing is published and the list is kept for next time.
Also fix issues found reviewing the first two commits:
- strip ChatGPT fingerprint headers only from Codex attached requests
- recompute the Codex catalog when an attached member's row is edited
- skip a member whose row cannot be projected instead of failing every
write, and refuse to attach such rows
- keep the gateway discovery switch as an exclusive field so a user's
global value survives direct switches
- tell the user when the route or config.toml owns the model catalog
- read the Codex keychain entry by account key with a three-state
result, outside the app lock and off the async runtime
- retry the Codex rewrite on the next check when a refreshed official
list could not be written
Merge attached providers' models into the Codex catalog as
ccs-<key>/<model> rows after the route's own rows. Each row keeps its
provider's tool profile, a fixed comp_hash and its own window; the
global window keys move into the rows while models are attached.
Attached requests keep their model, never carry the ChatGPT identity
headers to a third party, and drop hosted web_search for native
Responses gateways that reject it.
With the official account as the route, the catalog lists every
official model: fetched with the login Codex will use after the
operation, cached per workspace, user and Codex version, refreshed in
the background, with Codex's bundled list as the fallback. Logins CC
Switch does not manage are only read, never refreshed.
Let proxy mode publish extra providers' models to Claude Code's /model
picker as ccs-claude-<key>--<model> ids. A request that selects one goes
straight to that provider and never touches routing state: no breaker
permit or result, no status counters, no in-use update, no failover
switch, no model mapping, single-provider timeouts.
The list and a permanent key registry live in live-state.json and are
committed with the client file in one operation; a failed change reports
whether it was dropped or will be rolled forward. Unresolvable prefixed
ids are rejected instead of falling back to the route. /v1/models serves
the Anthropic-shaped list for gateway model discovery.
Saving any disabled prompt emptied the app's prompt file whenever no prompt
was enabled, so adding or importing a prompt wiped a hand-written AGENTS.md
or Hermes SOUL.md without a backup. A deeplink import also lost the file
before enable_prompt could back it up.
Follow the MiniMax Code branch: read the previous row before saving and
clear the file only when this prompt was enabled, is now disabled, and no
other prompt is enabled.
A vendor that also hosts other vendors' models (NVIDIA lists
minimaxai/minimax-m2.7 without a canonical id) was counted as a second
vendor next to the entry relays point at, so the fallback gave up and
models like MiniMax-M2.7, gpt-5.4 and kimi-k2.6 got no metadata through
unrecognized relays. Use the entries relays point at first and only fall
back to a vendor's own entry when nothing points anywhere.
- Export modelsDevQueryOptions so the metadata fill hook and both usage
pricing dialogs use one query definition, including the one-hour
gcTime the fill hook needs for its prefetch-only cache.
- Add useCommittableRef for the "latest value + synchronous commit"
pattern repeated in the Codex, Hermes, OpenClaw, and OpenCode forms.
- Drop an unused field from the models.dev index.
Without a thinking map, Pi shows off..high for a custom model and hides
xhigh and max, even when the upstream supports them. When a fetched
model is picked and has no map yet, fill one:
- Use the reviewed map of the Pi preset at the same endpoint, but only
under the same API and when the compat it relies on does not conflict
with what the user wrote. Missing compat keys are added to the model,
and for openai-completions the thinking format and reasoning_effort
support that Pi detects must match the preset's.
- Otherwise, when Pi sends levels verbatim as reasoning_effort
(openai-responses, or openai-completions detected as the OpenAI
thinking format), derive the map from models.dev effort levels using
Pi's own getEffortThinkingLevelMap rule.
A model with its own baseUrl is now looked up by that address. Update
the Pi design docs accordingly.
Apply the same lookup to Pi custom providers: picking a fetched model
fills contextWindow, maxTokens, reasoning, and image input when they are
still empty. thinkingLevelMap is left untouched.
Update the Pi design docs, which previously forbade inferring model
capabilities. That rule came from an earlier implementation that voted
across providers by model name and produced wrong values; the docs now
keep the lesson as constraints (match the provider by endpoint first,
fill only empty fields, never generate a thinking map).
A provider's /models endpoint only returns model IDs, so a model picked
from the fetched list left its context window, output limit, reasoning
levels, and modalities empty, while the same model in a preset had them.
When a model is picked in the Codex, OpenClaw, Hermes, OpenCode, or
MiniMax Code form, look up its metadata field by field:
1. the preset model with the same endpoint (hand-verified values);
2. the models.dev provider whose API endpoint matches;
3. the vendor's own models.dev entry, without its price.
Endpoints match only when host and path are equal after dropping a
trailing version segment or request path, so plans that share a host
(z.ai standard vs Coding Plan, OpenCode Zen vs Go) never borrow each
other's values. Models are never matched by name across providers: the
same model ID often has very different context windows per deployment.
Only empty fields are filled, and reasoning or image input is only ever
turned on. models.dev is prefetched once the model list is fetched and
shared with the usage pricing import; when it arrives late the row is
filled again, located by its stable row key. A single toast asks the
user to review what was filled.
Move the models.dev download, response types, and model ID normalization
out of the pricing module into src/lib/modelsDev.ts so other features can
reuse the same data. The query key becomes ["models-dev"] because the
cache now holds the raw catalog rather than pricing-only data.
Switch the Kimi Open Platform invite links to the new campaign trackers:
mainland platform.kimi.com links now use track-7cf2b91dcde043eda6ef9a95951a042c
and global platform.kimi.ai links use track-674ed6e2af924a5682a87421f7cf753a.
In the four READMEs this replaces the previous track IDs in the sponsor banner
and API call to action. The Kimi and Kimi Global presets for Claude Code,
Claude Desktop, Codex, OpenCode, OpenClaw, Hermes and Pi now carry the same
tracked links in websiteUrl, so sign-ups from inside the app are attributed as
well. The aff=cc-switch tag is kept everywhere; the console API-key deep links
and the Kimi Code subscription links are unchanged.
Success, provider failure, neutral failure and terminal failure each
had copies of the breaker/status/in-use/failover-switch updates inside
forward_with_retry_inner. Move them into note_attempt, finish_success,
record_provider_failure, finish_neutral_failure and
record_failed_request so the loop no longer writes routing state
directly.
Add characterization tests that drive forward_with_retry against a
scripted upstream and assert ProxyStatus, current_providers and
circuit breaker counts on every exit path. No behavior change.
* fix(tool): improve tool action handling and user feedback for ongoing installations
* fix(about): enhance tool upgrade handling to preserve WSL parameters across remounts
* refactor: update OpenCode configuration handling and enhance JSONC support
- Refactored OpenCode configuration reading to use `read_opencode_config_from_path` for improved error handling.
- Added tests to ensure that OpenCode JSONC provider and MCP workflows preserve comments during import and export.
- Updated localization files to reflect changes in OpenCode configuration directory description, now supporting both JSON and JSONC formats.
- Introduced a new `jsonc_document.rs` module for format-preserving JSONC/JSON5 editing, including functionality for parsing, applying changes, and validating output.
- Enhanced existing tests to cover various edge cases in JSONC handling, ensuring robustness against invalid configurations and preserving comments.
* test(opencode): 添加对 Unicode 行注释的处理测试
* fix(opencode): 添加供应商和 MCP 服务配置格式错误的警告日志
Run each Windows tool lifecycle script from its own temp directory so concurrent calls can no longer overwrite or delete each other's .bat file.
Add a backend ToolLifecycleCoordinator: a global execution lock serializes all install/update writes (pnpm/bun share one global manifest, and concurrent 'add -g' silently drops one side's entry), and a per-tool try-lock rejects duplicate submissions even after the About page remounts. Locks move into the blocking task so a cancelled caller cannot release them while the installer is still running.
The About page now locks per tool instead of globally, queues multi-install confirmation plans, ignores double clicks on the confirm dialog, and shows the full error text in a tooltip.
OpenCode 2.x moves sessions into session_v2/session_message and stops writing the V1 tables, so the session manager missed every new session.
- Detect the V2 layout when both session_v2 and session_message exist (same rule as the usage collector)
- Scan, load and delete V2 sessions, including their child tables
- Reuse opencode_config::get_opencode_db_path() for the database path
When a Responses SSE stream delivers its output only in the terminal response.completed / response.incomplete event, route it through the established Responses JSON conversion so text before a web search stays ahead of the search result. A missing terminal status falls back by event name, matching the incremental path's stop reason mapping. Also fixes terminal-only function_call items that previously produced stop_reason tool_use without a tool_use block.
Follow-up to #5681.
* feat: Enhance OpenCode provider configuration and import functionality
- Added support for built-in provider defaults in OpenCode configuration.
- Updated OpenCode provider options to allow omitting npm and model definitions.
- Improved import logic for OpenCode providers from live config, ensuring existing credentials are respected.
- Introduced tests for credential handling and built-in provider editing.
- Enhanced UI components to reflect changes in provider configuration and selection.
- Added localization for new built-in defaults feature.
* refactor(tests): enhance test environment isolation and settings management
* Refactor OpenCode provider handling and validation
- Removed TestSettingsGuard to simplify settings management during tests.
- Enhanced validation in handleDuplicateProvider to ensure npm and model definitions are provided for non-OMO providers.
- Updated OpenCodeFormFields to conditionally allow built-in defaults based on props.
- Improved ProviderForm to manage existing OpenCode overrides and prevent incomplete copies from being added.
- Added comprehensive tests for OpenCode provider duplication and validation scenarios.
- Introduced new error messages for better user feedback on provider requirements.
- Created opencode_tests.rs to cover various scenarios for OpenCode provider imports and updates.
---------
Co-authored-by: Jason <farion1231@gmail.com>
Import and live writes now persist the original provider JSON and use OpenCodeProviderConfig only for validation and display-name extraction. The typed round trip dropped fields the type does not model, such as api, env, whitelist and models.<id>.limit.input. Removes the lossy get_typed_providers/set_typed_provider helpers.
Refs #7382