Commit c750be3
authored
feat(webui): real cross-provider switching — sync providers into the engine registry (#39)
* feat(webui): cross-provider switching — sync providers to engine and surface configured models
Tickets 01-04 cataloged providers in the webui but never pushed them to
the engine's custom_provider registry. Selecting a provider model in the
dialog was UI-only — applyRecordedModel had nothing to match against and
the engine kept its default.
This commit closes ticket 05 by adding the engine-side projection:
* packages/webui/server/lib/engine-provider-sync.js: pure helpers
(providerKeyFromId, modelKeyFromId, toEngineCustomProvider) plus the
sync entry that writes the engine's custom_provider tree via an
atomic YAML rewrite. Only byok entries with both apiKey and baseURL
flow through; coding-plan goes through the engine's OAuth flows.
Reserved engine ids ('minimax', 'minimax_api', 'provider',
'custom_provider') get a deterministic '-byok' suffix to avoid
shadowing.
* packages/webui/server/routes/providers.js: PUT and preset enable
handlers call syncProvidersToEngine after the user-level write, then
shutdownMcodeAcpSingleton so the next operation spawns a fresh
subprocess that reads the new config. The sync result is included
in the response (engineSync.ok / engineSync.keys) so the UI can
surface a non-blocking warning if the engine write fails.
* packages/webui/server/lib/mapplyRecordedModel: bare-name option
match is now case-insensitive. The engine populates option.name from
the user-supplied label (e.g. 'GLM-5.3' for a custom provider whose
label differs in case from the model id 'glm-5.3'); a strict
comparison would skip the apply and leave the engine on its default.
* packages/webui/webapp/components/provider-management.tsx: provider
card now shows the configured models as chips directly, so the
operator sees what's wired up without expanding the editor.
* Test updates:
- packages/webui/test/lib/engine-provider-sync.test.js pins the v2 →
engine custom_provider mapping, the keep-key convention applied to
the PUT body, atomic write semantics, and operator-section
preservation.
- packages/webui/test/lib/mcode-acp-note.test.js updates the model
option fixtures to the actual engine wire format
('m:<provider>:<model>:u|v:<variant>') and pins the cross-provider
resolution path.
* release/public-source.json regenerated (new files + 3 modified).
Self-check evidence (18112 / 18113, isolated data dir under /tmp/dev-cp,
engine subprocess pointed at a fake HTTP server on :19999):
1. PUT /api/providers with byok-zhipu → engine config.yaml gains
custom_provider.byok-zhipu with the GLM-5.3 / GLM-4.6 entries;
response carries engineSync: { ok: true, keys: ['byok-zhipu'] }.
2. POST /api/send with model=byok-zhipu/glm-5.3 → engine subprocess
POSTs http://127.0.0.1:19999/v1/chat/completions with body.model
'glm-5.3' and Authorization 'Bearer sk-zhipu-fake-for-test'
(the BYOK provider's apiKey, NOT the builtin MiniMax apiKey).
Assistant returns 'OK from fake server (model=glm-5.3)'.
3. Hot-add: PUT with a third model (GLM-4-Flash) → /api/models and
the provider card reflect the new chip without restart.
4. Settings provider card: 'Zhipu BYOK 自定义 openai · byok · key set
GLM-5.3 GLM-4.6 GLM-4-Flash (HOT-ADDED)' visible without opening
the editor.
Gates: typecheck clean, test:webui 1602/1604 pass (2 unrelated skips),
test:webapp 264/264 pass, build:webui OK, check:source OK.
* fix(webui): preserve foreign engine custom_provider entries; write config.yaml 0600
Ticket 05 acceptance round: merge-over-replace, not replace-everything.
The previous sync algorithm replaced the engine's whole custom_provider
tree. A manually-added operator entry (e.g. via 'mcode provider add'
on the engine CLI) was silently DROPPED on the next webui PUT — same
destruction class as the absent-key wipe we hardened in ticket 03.
Ownership rule (on-disk fingerprint):
Every entry webui writes carries _webui_owned: true. The engine
ignores unknown fields (js-yaml parses the whole record and the
downstream consumers read named fields only), so the marker is
engine-safe.
Merge algorithm:
existing engine keys ∩ eligible webui keys → UPDATE in place
existing engine keys ∖ eligible webui keys:
_webui_owned === true → DELETE (webui-owned,
operator removed the
webui provider)
_webui_owned !== true (or missing) → PRESERVE (foreign;
operator-owned)
eligible webui keys ∖ existing engine keys → ADD
A foreign 'manual_only' provider now survives every webui PUT,
including the empty-eligible case (PUTting an ineligible-only catalogue
no longer wipes foreign entries). The sync response carries both
'keys' (the webui keys touched) and 'preserved' (the foreign keys
left untouched) so the route can surface the operator's manual
providers in the UI.
File mode 0600: config.yaml carries plaintext apiKeys. The engine's
own updateLocalByokConfig already pins 0600; the helper now matches.
Three-step atomic write (0600 tmp, rename, 0600 chmod) — same dance as
the engine's writer.
Tests added (engine-provider-sync.test.js):
- foreign entry survives a sync that has webui providers (whitelist
preserves it).
- empty eligible list does NOT wipe foreign (the destruction class
closed here).
- webui-managed entry whose provider is removed is dropped; foreign
sibling survives.
- webui-managed entry update replaces data, keeps the marker.
- no-op skip when nothing changed (no mtime churn, no needless chmod).
- config.yaml mode is 0600 (POSIX-only assertion, skipped on Windows).
Gates: typecheck clean, test:webui 1606/1608 pass (2 pre-existing skips),
engine-provider-sync tests 40/40 pass three consecutive runs.1 parent 67ad609 commit c750be3
7 files changed
Lines changed: 1420 additions & 59 deletions
File tree
- packages/webui
- server
- lib
- routes
- test/lib
- webapp/components
- release
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
188 | 188 | | |
189 | 189 | | |
190 | 190 | | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
191 | 201 | | |
192 | 202 | | |
193 | 203 | | |
| |||
199 | 209 | | |
200 | 210 | | |
201 | 211 | | |
202 | | - | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
203 | 215 | | |
204 | 216 | | |
205 | 217 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
62 | 66 | | |
63 | 67 | | |
64 | 68 | | |
| |||
67 | 71 | | |
68 | 72 | | |
69 | 73 | | |
| 74 | + | |
70 | 75 | | |
71 | 76 | | |
72 | 77 | | |
| |||
146 | 151 | | |
147 | 152 | | |
148 | 153 | | |
| 154 | + | |
149 | 155 | | |
150 | 156 | | |
151 | 157 | | |
152 | 158 | | |
153 | 159 | | |
154 | | - | |
| 160 | + | |
155 | 161 | | |
156 | 162 | | |
157 | 163 | | |
| |||
161 | 167 | | |
162 | 168 | | |
163 | 169 | | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
164 | 190 | | |
165 | 191 | | |
166 | 192 | | |
| |||
177 | 203 | | |
178 | 204 | | |
179 | 205 | | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
180 | 222 | | |
181 | 223 | | |
182 | 224 | | |
| |||
431 | 473 | | |
432 | 474 | | |
433 | 475 | | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
434 | 487 | | |
435 | 488 | | |
436 | 489 | | |
| |||
446 | 499 | | |
447 | 500 | | |
448 | 501 | | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
449 | 518 | | |
450 | 519 | | |
451 | 520 | | |
| |||
0 commit comments