docs(upstream-ledger): L-4 models/list RPC — client-side static mirror is workaround

Adds L-4 note explaining models/list is currently served from a client-side static mirror as a temporary workaround, style aligned with L-1/L-2 entries.

Files: examples/desktop/docs/upstream-ledger.md +74/-0

Mirrors merge 7b6dfc6b on internal test-real branch. Test counts unchanged: 1820 (docs-only).
This commit is contained in:
ZiyaZhang
2026-07-19 08:39:59 -07:00
parent 95b8b2a1af
commit fcbf6ff813
+74
View File
@@ -200,3 +200,77 @@ becomes pure pass-through (roughly 20 lines) — a small doc-only PR at
that point removes the heuristic scan entirely. Until then, the tri-view
tabs read as "here's what the runtime is showing you" (wire signals) and
"here's what the shell inferred" (heuristic, tooltipped as such).
---
## L-4 Runtime should expose a `models/list` RPC (shell-side static mirror is workaround)
**Symptom.** The shell has no wire-level way to ask the runtime "which
(adapter × model) pairs are actually registered right now?". A researcher
who wires up a new adapter or ships a plugin that calls
`ctx.llm.registerAdapter(...)` at boot has no way to see that model in
the composer dropdown without also editing shell code. Picking a model
the runtime doesn't know about falls through to a `NO_ADAPTER` error,
which the shell recovers from with a "switch profile" affordance — but
the up-front list is a static mirror maintained by hand.
**Root cause.** No `models/list` (or equivalent) RPC on the wire. The
runtime knows its own adapter/model registry — that's what routes each
`llm/call` to the right adapter — but it never exposes that registry to
the shell. The shell therefore has to guess, and today "guess" means
"copy the yml into a JS map".
**Local workaround.**
- `src/main/profiles.js:230``PROFILE_MODELS` is a static map,
`profile name → array of supported model ids`, one entry per row in
`config/*.yml`.
- `src/main/profiles.js:246``modelsFor(name)` is the accessor exported
to `main.js`.
- `src/main/main.js:280` — the `profiles:models` IPC handler snapshots
the whole map to the renderer at handshake / profile-switch time.
- `src/renderer/renderer.js:4048``renderComposerModel(currentModel)`
uses the mirror to filter the composer dropdown; if the runtime later
reports `NO_ADAPTER` we fall back to the "switch profile" affordance.
- `test/model-profile-guard.test.js` — parses every `config/*.yml`
`models:` field and asserts the yml matches `PROFILE_MODELS` line by
line, so any yml drift trips the test.
Spot the workaround in code review by the `PROFILE_MODELS` literal at
`src/main/profiles.js:230` and by the guard test — both exist purely
because the runtime never told us.
The workaround is intentionally strict: hand-maintained maps drift, so
the guard test locks yml ↔ map. It does **not** cover runtime-time
`registerAdapter` calls; those still surface only as
`NO_ADAPTER`-then-recover.
**Upstream fix (needed).** Expose a `models/list` RPC on the wire:
```
Method: models/list
Params: none
Result: {
models: [
{ id: string, adapter: string, capabilities?: { streaming, thinking, ... } }
]
}
```
Consumer migration in this shell once the RPC lands:
- Add a `runtime:list-models` IPC and call it once at handshake plus on
every profile switch.
- Downgrade `PROFILE_MODELS` to a fallback used only when the RPC is
unavailable (e.g. older runtime).
- Keep `test/model-profile-guard.test.js` — its job becomes narrower
(yml ↔ fallback consistency), still worth having for older-runtime
users.
**Precedent in ledger.** Same shape as L-1 (`presentResult` wire),
L-2 (trace semantic signals), L-3 (artifact blob snapshot): the shell
paints over an information gap the runtime should own, and the fix is a
small wire addition that lets the workaround retire.
**Reference.** PR #374 review comment by @ZiyaZhang
https://github.com/deepseek-harness/deepseek-harness/pull/374#issuecomment-5016306211