@sagmans/dsh-tui 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/dsh-tui-dogfood/SKILL.md +5 -1
- package/.agents/skills/dsh-tui-dogfood/scripts/run-plugin-from-worktree.sh +26 -2
- package/.agents/skills/dsh-tui-update-models/SKILL.md +95 -17
- package/.agents/skills/dsh-tui-update-models/references/model-wiring.md +158 -28
- package/.agents/skills/dsh-tui-update-models/scripts/dump-model-catalog.mjs +152 -16
- package/README.md +117 -53
- package/lib/agent/history.d.ts +0 -2
- package/lib/agent/history.d.ts.map +1 -1
- package/lib/agent/history.js +1 -1
- package/lib/agent/history.js.map +1 -1
- package/lib/agent/host.d.ts +0 -25
- package/lib/agent/host.d.ts.map +1 -1
- package/lib/agent/host.js +2 -2
- package/lib/agent/host.js.map +1 -1
- package/lib/agent/model.d.ts +38 -6
- package/lib/agent/model.d.ts.map +1 -1
- package/lib/agent/model.js +38 -14
- package/lib/agent/model.js.map +1 -1
- package/lib/agent/prompt-history.d.ts +7 -12
- package/lib/agent/prompt-history.d.ts.map +1 -1
- package/lib/agent/prompt-history.js +10 -31
- package/lib/agent/prompt-history.js.map +1 -1
- package/lib/cards/composition.d.ts +1 -1
- package/lib/cards/composition.d.ts.map +1 -1
- package/lib/cards/composition.js +7 -0
- package/lib/cards/composition.js.map +1 -1
- package/lib/cards/presenter.d.ts.map +1 -1
- package/lib/cards/presenter.js +32 -2
- package/lib/cards/presenter.js.map +1 -1
- package/lib/cards.d.ts +27 -2
- package/lib/cards.d.ts.map +1 -1
- package/lib/cards.js +17 -3
- package/lib/cards.js.map +1 -1
- package/lib/compat/probe.js +1 -1
- package/lib/compat/probe.js.map +1 -1
- package/lib/config.d.ts +10 -0
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +18 -0
- package/lib/config.js.map +1 -1
- package/lib/contracts.d.ts +2 -0
- package/lib/contracts.d.ts.map +1 -1
- package/lib/export.d.ts +12 -0
- package/lib/export.d.ts.map +1 -1
- package/lib/export.js +34 -3
- package/lib/export.js.map +1 -1
- package/lib/herdr/client.d.ts +8 -9
- package/lib/herdr/client.d.ts.map +1 -1
- package/lib/herdr/client.js +14 -2
- package/lib/herdr/client.js.map +1 -1
- package/lib/herdr/constants.d.ts +18 -0
- package/lib/herdr/constants.d.ts.map +1 -1
- package/lib/herdr/constants.js +18 -0
- package/lib/herdr/constants.js.map +1 -1
- package/lib/herdr/reporter.d.ts +20 -4
- package/lib/herdr/reporter.d.ts.map +1 -1
- package/lib/herdr/reporter.js +70 -20
- package/lib/herdr/reporter.js.map +1 -1
- package/lib/herdr/state.d.ts +10 -10
- package/lib/herdr/state.d.ts.map +1 -1
- package/lib/herdr/state.js +22 -5
- package/lib/herdr/state.js.map +1 -1
- package/lib/identity.d.ts +0 -2
- package/lib/identity.d.ts.map +1 -1
- package/lib/identity.js +0 -1
- package/lib/identity.js.map +1 -1
- package/lib/index.d.ts +1 -4
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +15 -5
- package/lib/index.js.map +1 -1
- package/lib/input/keymap.d.ts +1 -7
- package/lib/input/keymap.d.ts.map +1 -1
- package/lib/input/keymap.js +3 -11
- package/lib/input/keymap.js.map +1 -1
- package/lib/input/submission.js +2 -2
- package/lib/input/submission.js.map +1 -1
- package/lib/install-skills.d.ts +20 -1
- package/lib/install-skills.d.ts.map +1 -1
- package/lib/install-skills.js +84 -9
- package/lib/install-skills.js.map +1 -1
- package/lib/model-list.d.ts.map +1 -1
- package/lib/model-list.js +17 -5
- package/lib/model-list.js.map +1 -1
- package/lib/queue.d.ts +0 -2
- package/lib/queue.d.ts.map +1 -1
- package/lib/queue.js +1 -1
- package/lib/queue.js.map +1 -1
- package/lib/settings-notice.d.ts +10 -1
- package/lib/settings-notice.d.ts.map +1 -1
- package/lib/settings-notice.js +17 -7
- package/lib/settings-notice.js.map +1 -1
- package/lib/spacing.d.ts +40 -0
- package/lib/spacing.d.ts.map +1 -0
- package/lib/spacing.js +54 -0
- package/lib/spacing.js.map +1 -0
- package/lib/stash/paths.d.ts +19 -10
- package/lib/stash/paths.d.ts.map +1 -1
- package/lib/stash/paths.js +52 -23
- package/lib/stash/paths.js.map +1 -1
- package/lib/stash/schema.d.ts +13 -12
- package/lib/stash/schema.d.ts.map +1 -1
- package/lib/stash/schema.js +11 -9
- package/lib/stash/schema.js.map +1 -1
- package/lib/stash/store.d.ts.map +1 -1
- package/lib/stash/store.js +9 -19
- package/lib/stash/store.js.map +1 -1
- package/lib/stash.d.ts +17 -10
- package/lib/stash.d.ts.map +1 -1
- package/lib/stash.js +31 -20
- package/lib/stash.js.map +1 -1
- package/lib/subagents.d.ts +0 -4
- package/lib/subagents.d.ts.map +1 -1
- package/lib/subagents.js +2 -2
- package/lib/subagents.js.map +1 -1
- package/lib/surface/appearance.d.ts +3 -0
- package/lib/surface/appearance.d.ts.map +1 -1
- package/lib/surface/appearance.js +5 -0
- package/lib/surface/appearance.js.map +1 -1
- package/lib/surface/background-work.d.ts +2 -0
- package/lib/surface/background-work.d.ts.map +1 -1
- package/lib/surface/background-work.js +45 -3
- package/lib/surface/background-work.js.map +1 -1
- package/lib/surface/commands.d.ts +2 -0
- package/lib/surface/commands.d.ts.map +1 -1
- package/lib/surface/commands.js +32 -6
- package/lib/surface/commands.js.map +1 -1
- package/lib/surface/modal-input.d.ts +3 -2
- package/lib/surface/modal-input.d.ts.map +1 -1
- package/lib/surface/modal-input.js +25 -8
- package/lib/surface/modal-input.js.map +1 -1
- package/lib/surface/model-choice.d.ts.map +1 -1
- package/lib/surface/model-choice.js +18 -18
- package/lib/surface/model-choice.js.map +1 -1
- package/lib/surface/preset-choice.d.ts.map +1 -1
- package/lib/surface/preset-choice.js +18 -10
- package/lib/surface/preset-choice.js.map +1 -1
- package/lib/surface/prompt-memory.d.ts +3 -1
- package/lib/surface/prompt-memory.d.ts.map +1 -1
- package/lib/surface/prompt-memory.js +10 -11
- package/lib/surface/prompt-memory.js.map +1 -1
- package/lib/surface/session-lifecycle.d.ts +6 -1
- package/lib/surface/session-lifecycle.d.ts.map +1 -1
- package/lib/surface/session-lifecycle.js +30 -8
- package/lib/surface/session-lifecycle.js.map +1 -1
- package/lib/surface/session-view.d.ts.map +1 -1
- package/lib/surface/session-view.js +1 -3
- package/lib/surface/session-view.js.map +1 -1
- package/lib/surface/staged-turns.d.ts +0 -2
- package/lib/surface/staged-turns.d.ts.map +1 -1
- package/lib/surface/staged-turns.js +37 -6
- package/lib/surface/staged-turns.js.map +1 -1
- package/lib/terminal/external-editor.d.ts +16 -0
- package/lib/terminal/external-editor.d.ts.map +1 -1
- package/lib/terminal/external-editor.js +38 -6
- package/lib/terminal/external-editor.js.map +1 -1
- package/lib/terminal/host-writes.d.ts +15 -2
- package/lib/terminal/host-writes.d.ts.map +1 -1
- package/lib/terminal/host-writes.js +10 -1
- package/lib/terminal/host-writes.js.map +1 -1
- package/lib/terminal/restore.d.ts +1 -2
- package/lib/terminal/restore.d.ts.map +1 -1
- package/lib/terminal/restore.js +1 -9
- package/lib/terminal/restore.js.map +1 -1
- package/lib/terminal/signals.d.ts +0 -1
- package/lib/terminal/signals.d.ts.map +1 -1
- package/lib/terminal/signals.js +1 -1
- package/lib/terminal/signals.js.map +1 -1
- package/lib/terminal/title.d.ts.map +1 -1
- package/lib/terminal/title.js +14 -3
- package/lib/terminal/title.js.map +1 -1
- package/lib/terminal/warning-screen.d.ts.map +1 -1
- package/lib/terminal/warning-screen.js +12 -1
- package/lib/terminal/warning-screen.js.map +1 -1
- package/lib/theme-capability.d.ts +0 -2
- package/lib/theme-capability.d.ts.map +1 -1
- package/lib/theme-capability.js +1 -1
- package/lib/theme-capability.js.map +1 -1
- package/lib/theme-defaults.d.ts.map +1 -1
- package/lib/theme-defaults.js +7 -0
- package/lib/theme-defaults.js.map +1 -1
- package/lib/theme-files.d.ts +1 -1
- package/lib/theme-files.d.ts.map +1 -1
- package/lib/theme-files.js +1 -1
- package/lib/theme-files.js.map +1 -1
- package/lib/theme-schema.d.ts +6 -6
- package/lib/theme-settings.d.ts +13 -8
- package/lib/theme-settings.d.ts.map +1 -1
- package/lib/theme-settings.js +17 -3
- package/lib/theme-settings.js.map +1 -1
- package/lib/theme-tokens.d.ts +1 -1
- package/lib/theme-tokens.d.ts.map +1 -1
- package/lib/theme-tokens.js +7 -2
- package/lib/theme-tokens.js.map +1 -1
- package/lib/transcript.d.ts +2 -2
- package/lib/transcript.d.ts.map +1 -1
- package/lib/transcript.js +9 -4
- package/lib/transcript.js.map +1 -1
- package/lib/ui/dock.d.ts +2 -0
- package/lib/ui/dock.d.ts.map +1 -1
- package/lib/ui/dock.js +18 -3
- package/lib/ui/dock.js.map +1 -1
- package/lib/ui/gap.d.ts +28 -0
- package/lib/ui/gap.d.ts.map +1 -0
- package/lib/ui/gap.js +47 -0
- package/lib/ui/gap.js.map +1 -0
- package/lib/ui/gutter.d.ts +25 -0
- package/lib/ui/gutter.d.ts.map +1 -0
- package/lib/ui/gutter.js +31 -0
- package/lib/ui/gutter.js.map +1 -0
- package/lib/ui/layout.d.ts +13 -1
- package/lib/ui/layout.d.ts.map +1 -1
- package/lib/ui/layout.js +32 -6
- package/lib/ui/layout.js.map +1 -1
- package/lib/ui/queue.d.ts +0 -4
- package/lib/ui/queue.d.ts.map +1 -1
- package/lib/ui/queue.js +2 -2
- package/lib/ui/queue.js.map +1 -1
- package/lib/ui/rows.d.ts +0 -8
- package/lib/ui/rows.d.ts.map +1 -1
- package/lib/ui/rows.js +1 -9
- package/lib/ui/rows.js.map +1 -1
- package/lib/ui/stash-picker.d.ts +2 -2
- package/lib/ui/stash-picker.d.ts.map +1 -1
- package/lib/ui/stash-picker.js +3 -3
- package/lib/ui/stash-picker.js.map +1 -1
- package/lib/ui/view/gate-card.d.ts.map +1 -1
- package/lib/ui/view/gate-card.js +4 -1
- package/lib/ui/view/gate-card.js.map +1 -1
- package/lib/ui/view/tool-card.d.ts +17 -2
- package/lib/ui/view/tool-card.d.ts.map +1 -1
- package/lib/ui/view/tool-card.js +40 -8
- package/lib/ui/view/tool-card.js.map +1 -1
- package/lib/ui/view/transcript-message.d.ts +3 -0
- package/lib/ui/view/transcript-message.d.ts.map +1 -1
- package/lib/ui/view/transcript-message.js +8 -1
- package/lib/ui/view/transcript-message.js.map +1 -1
- package/lib/ui/view.d.ts +36 -9
- package/lib/ui/view.d.ts.map +1 -1
- package/lib/ui/view.js +90 -32
- package/lib/ui/view.js.map +1 -1
- package/package.json +3 -1
- package/themes/deepseek-blue.yaml +3 -0
- package/themes/violet-orbit.yaml +16 -9
|
@@ -22,7 +22,11 @@ directory or branch tail. A path selects that checkout directly. The helper read
|
|
|
22
22
|
runs `pnpm run build` only when the checkout declares a build script. It adds
|
|
23
23
|
the bundle once if absent from the cloned profile, updates only its dependency,
|
|
24
24
|
restores local links from `link:` specifications, and launches
|
|
25
|
-
`dsh --profile tui` by default.
|
|
25
|
+
`dsh --profile tui` by default. For this TUI, the packaged helper checks
|
|
26
|
+
the launcher against compatible releases in the checkout’s `package.json` before
|
|
27
|
+
cloning. This checkout uses installed `dsh 0.1.5-rc.3` for PTY tests. Never
|
|
28
|
+
substitute `pnpm dsh` or a `--dsh` path from a Harness source checkout: that
|
|
29
|
+
host may migrate the cloned `settings.yaml`. The legacy
|
|
26
30
|
`scripts/dogfood/run-tui-from-worktree.sh` remains available in dsh-tui and
|
|
27
31
|
defaults to that script's checkout even when called elsewhere.
|
|
28
32
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
|
|
5
5
|
name="$(basename "${BASH_SOURCE[0]}")"
|
|
6
|
+
TUI_PACKAGE_NAME='@sagmans/dsh-tui'
|
|
6
7
|
# The link policy is shared with the validator, so both read one rule set.
|
|
7
8
|
helper_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
8
9
|
default_repo="${DSH_DOGFOOD_DEFAULT_REPO:-$PWD}"
|
|
@@ -268,11 +269,26 @@ if [[ "$dry_run" == 1 ]]; then
|
|
|
268
269
|
fi
|
|
269
270
|
|
|
270
271
|
[[ -n "$dsh_bin" ]] || die "no dsh launcher found; put dsh on PATH or pass --dsh"
|
|
272
|
+
# This plugin's daily host must not be replaced by a source checkout during setup or launch.
|
|
273
|
+
if [[ "$package_name" == "$TUI_PACKAGE_NAME" ]]; then
|
|
274
|
+
# Keep launcher startup writes out of both the live home and the clone.
|
|
275
|
+
version_probe_home="$(mktemp -d /tmp/dsh-dogfood-version.XXXXXXXX)" || die "cannot create isolated version probe home"
|
|
276
|
+
trap 'rm -rf -- "$version_probe_home"' EXIT
|
|
277
|
+
trap 'exit 1' INT TERM
|
|
278
|
+
reported_version="$(HOME="$version_probe_home" DSH_HOME="$version_probe_home" "$dsh_bin" --version 2>/dev/null)" || die "TUI dogfood requires compatible installed dsh"
|
|
279
|
+
rm -rf -- "$version_probe_home"
|
|
280
|
+
trap - EXIT INT TERM
|
|
281
|
+
node -e "const p=require(process.argv[1]);process.exit(p.dsh?.compatibility?.dshReleases?.[process.argv[2]] === 'compatible' ? 0 : 1)" "$target_path/package.json" "$reported_version" || die "TUI dogfood requires compatible installed dsh (got ${reported_version:-<empty>})"
|
|
282
|
+
fi
|
|
271
283
|
|
|
272
284
|
# --- seed the scratch home ---------------------------------------------------
|
|
273
285
|
|
|
274
286
|
if [[ -d "$home" && "$action" != "clean" && "$dry_run" != 1 ]]; then
|
|
275
287
|
validate_marker
|
|
288
|
+
# A marker that still reads 0 belongs to a run the kernel killed before it could
|
|
289
|
+
# clean up: the identity matches, so --reseed takes it, but nothing may reuse a
|
|
290
|
+
# home whose copy stopped halfway. A marker without the line predates this.
|
|
291
|
+
[[ "$reseed" == 1 ]] || [[ "$(marker_value ready)" != 0 ]] || die "$home is a half-seeded clone; pass --reseed to clone it again"
|
|
276
292
|
check_credentials
|
|
277
293
|
chmod 700 "$home"
|
|
278
294
|
if [[ -f "$home/.credentials.yaml" ]]; then chmod 600 "$home/.credentials.yaml"; fi
|
|
@@ -293,6 +309,11 @@ if [[ ! -d "$home" ]]; then
|
|
|
293
309
|
step "seeding $home from $source_home"
|
|
294
310
|
mkdir -p "$home"
|
|
295
311
|
chmod 700 "$home"
|
|
312
|
+
# Written before the copy, so whatever the copy does this directory is one that
|
|
313
|
+
# --clean and --reseed already own: an interrupted clone is recoverable rather
|
|
314
|
+
# than a husk neither action will touch. The copy marks it ready when it lands.
|
|
315
|
+
printf 'home=%s\nsource=%s\ntarget=%s\nprofile=%s\nready=0\n' "$home" "$source_home" "$target_path" "$profile" > "$marker"
|
|
316
|
+
chmod 600 "$marker"
|
|
296
317
|
if [[ "$fresh" == 1 ]]; then
|
|
297
318
|
for seed in .credentials.yaml settings.yaml; do
|
|
298
319
|
if [[ -f "$source_home/$seed" ]]; then cp -p "$source_home/$seed" "$home/$seed"; fi
|
|
@@ -316,8 +337,6 @@ if [[ ! -d "$home" ]]; then
|
|
|
316
337
|
fi
|
|
317
338
|
fi
|
|
318
339
|
|
|
319
|
-
printf 'home=%s\nsource=%s\ntarget=%s\nprofile=%s\n' "$home" "$source_home" "$target_path" "$profile" > "$marker"
|
|
320
|
-
chmod 600 "$marker"
|
|
321
340
|
materialize_cloned_links
|
|
322
341
|
check_clone_links
|
|
323
342
|
|
|
@@ -398,6 +417,11 @@ if ! node -e "process.exit(require(process.argv[1]).dsh.profile.bundles.includes
|
|
|
398
417
|
die "profile '$profile' does not list $package_name after relink (bundles: $bundles)"
|
|
399
418
|
fi
|
|
400
419
|
|
|
420
|
+
# The home is finished: it points at the target and its bundles answer, so it is
|
|
421
|
+
# ready to be reused rather than re-seeded.
|
|
422
|
+
printf 'home=%s\nsource=%s\ntarget=%s\nprofile=%s\nready=1\n' "$home" "$source_home" "$target_path" "$profile" > "$marker"
|
|
423
|
+
chmod 600 "$marker"
|
|
424
|
+
|
|
401
425
|
printf '\n' >&2
|
|
402
426
|
printf 'home: %s\n' "$home" >&2
|
|
403
427
|
printf 'target: %s\n' "$target_path" >&2
|
|
@@ -13,10 +13,11 @@ the ctx gauge. Find the owner first; then edit exactly one place.
|
|
|
13
13
|
| Where the wiring is | What it owns |
|
|
14
14
|
| --- | --- |
|
|
15
15
|
| `~/.dsh/profiles/<profile>/cordis.patch.yml` | The patch layer: applied after every bundle layer, last entry wins. Holds the catalog or `llm-pi-ai` config a developer actually edits. |
|
|
16
|
-
| `~/.dsh/settings.yaml` | Host settings.
|
|
16
|
+
| `~/.dsh/settings.yaml` | Host settings, hot-reloaded. A `llm-pi-ai:` or `llm-deepseek:` section overrides that adapter's patch entry without a restart - it is what the web Models page writes, and what an additive sign-in declares a route in. The legacy `dsh-provider-extra:` section carries `extraModels` and `codexExtraModels` only. **A catalog is never settings-owned**: managed mode installs no section, reads no overlay, and turns off settings projection for its own Config, so the profile patch is the only place a catalog exists. |
|
|
17
17
|
| `~/.dsh/profiles/<profile>/package.json` | Bundles and their dependency specs (`link:` for a checkout, a range for a release). |
|
|
18
18
|
| `node_modules/<bundle>/cordis.patch.yml` | The bundle's own defaults: disabled base rows, inserted rows, default config. |
|
|
19
|
-
| `node_modules/@earendil-works/pi-ai/dist/providers/data/<source>.json` | Installed per-provider catalog: ids, and the cost/context/effort data a template clone inherits. |
|
|
19
|
+
| `node_modules/@earendil-works/pi-ai/dist/providers/data/<source>.json` | Installed per-provider catalog: ids, and the cost/context/effort data a template clone or a `filter` match inherits. |
|
|
20
|
+
| `node_modules/@sagmans/dsh-provider-extra/docs/catalog.md` | The catalog's own field list and rejection rules, matching the plugin build the profile resolves. Read it before inventing a field. |
|
|
20
21
|
|
|
21
22
|
Entry shapes in the patch layer:
|
|
22
23
|
|
|
@@ -39,8 +40,13 @@ A config in a patch **replaces** the base config; it never merges field by field
|
|
|
39
40
|
| One new id that behaves like a catalog sibling | `extraModels: [{ id, template, name? }]` (clone the sibling's wire behavior) |
|
|
40
41
|
| Serve only some of a route's catalog | a `models:` whitelist of ids, in the order the picker should show |
|
|
41
42
|
| Own the whole model directory (gateways, subscriptions, logins) | `dsh-provider-extra` `catalog` v1, and disable the base rows |
|
|
43
|
+
| Serve most of an installed source, minus a few ids | catalog route `filter: { include?, exclude? }` - keeps every installed fact |
|
|
44
|
+
| Point a route at your own endpoint | catalog route with `api` + `baseURL` and no `source` |
|
|
42
45
|
| Retire a model or a provider | drop it from the list, or disable the row that offers it |
|
|
43
46
|
|
|
47
|
+
A catalog route declares **exactly one** of `models` and `filter`; `models: []`
|
|
48
|
+
is the explicit empty selection, and `filter` needs an installed `source`.
|
|
49
|
+
|
|
44
50
|
`dsh-provider-extra` catalog skeleton, with the two override layers a correct
|
|
45
51
|
entry needs as soon as a vendor number differs from the inherited one:
|
|
46
52
|
|
|
@@ -54,20 +60,61 @@ entry needs as soon as a vendor number differs from the inherited one:
|
|
|
54
60
|
- id: openai
|
|
55
61
|
name: OpenAI API
|
|
56
62
|
source: openai # pi-ai provider id; its data is the base
|
|
57
|
-
auth: { apiKeyRef: OPENAI_API_KEY }
|
|
63
|
+
auth: { apiKeyRef: OPENAI_API_KEY } # omit auth for a keyless local route
|
|
58
64
|
models:
|
|
59
65
|
- id: gpt-6-sol
|
|
60
66
|
name: GPT-6 Sol
|
|
67
|
+
aliases: [gpt-6-sol-preview] # extra selector inputs, never extra rows
|
|
61
68
|
template: gpt-5.6-sol # inherits wire behavior AND stale metadata
|
|
69
|
+
defaultMaxTokens: 128000 # request cap, only when the request gives none
|
|
62
70
|
metadata: # override what the vendor documented
|
|
63
71
|
contextWindow: 1050000
|
|
64
72
|
cost: { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 }
|
|
73
|
+
- id: local-llm
|
|
74
|
+
name: Local llama.cpp
|
|
75
|
+
api: openai-completions # own endpoint: api is required, source is absent
|
|
76
|
+
baseURL: http://127.0.0.1:8080/v1
|
|
77
|
+
models:
|
|
78
|
+
- id: qwen3-coder
|
|
79
|
+
name: Qwen3 Coder
|
|
80
|
+
metadata: # no installed sibling: every fact is declared
|
|
81
|
+
reasoning: true
|
|
82
|
+
input: [text]
|
|
83
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
|
|
84
|
+
contextWindow: 262144
|
|
85
|
+
maxTokens: 65536
|
|
86
|
+
- id: zai-all
|
|
87
|
+
name: Z.AI Coding Plan
|
|
88
|
+
source: zai
|
|
89
|
+
auth: { apiKeyRef: ZAI_API_KEY }
|
|
90
|
+
filter: { include: ['glm-*'], exclude: ['glm-4*'] } # '*' is the only wildcard
|
|
65
91
|
```
|
|
66
92
|
|
|
67
|
-
Rules the resolver enforces: `version` is
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
93
|
+
Rules the resolver enforces, each rejecting the **whole** candidate: `version` is
|
|
94
|
+
exactly `1`; an unknown field anywhere; duplicate provider ids, model ids, or
|
|
95
|
+
aliases; a `source` no installed pi-ai provider ships; `api` beside `source`, or
|
|
96
|
+
absent when `source` is absent; `baseURL` absent, non-HTTP(S), or carrying
|
|
97
|
+
credentials; a `transport` on a route whose source is not `openai-codex`; a
|
|
98
|
+
`fallbackSessionId` on a route whose source is not `opencode-go`; `auth` naming
|
|
99
|
+
both modes, or `apiKeyRef` on `openai-codex`, or a `credentialProvider` that is
|
|
100
|
+
not the `source`; both or neither of `models`/`filter`; an unknown `template`; a
|
|
101
|
+
model with no resolvable template and incomplete `metadata`; a
|
|
102
|
+
`defaultMaxTokens` above the model's `maxTokens`; a `metadata.api` that changes a
|
|
103
|
+
template-backed protocol; an empty catalog whose `default` is not `null`; a
|
|
104
|
+
`default` outside the served selection; a `reasoningEffort` the model does not
|
|
105
|
+
support. Omitting `catalog` is not `providers: []`: the first keeps the plugin's
|
|
106
|
+
legacy additive routes, the second serves nothing.
|
|
107
|
+
|
|
108
|
+
Ownership is exclusive and checked at boot. Disable **every** competing row in
|
|
109
|
+
the whole profile - on `dsh-base` those are `llm-pi-ai`, `llm-deepseek`, and
|
|
110
|
+
`agent-default-model` - and mount provider-extra after them. A surviving adapter,
|
|
111
|
+
provider directory, or default owner logs `CATALOG_OWNER_COLLISION` and fails
|
|
112
|
+
listing, selection, dispatch, and sign-in closed until it is gone.
|
|
113
|
+
|
|
114
|
+
The pinned host reads only `provider` and `model` from `catalog.default`: it
|
|
115
|
+
ignores `reasoningEffort`, so effort is chosen in the session surface. Saving a
|
|
116
|
+
default needs the host's `configEditor`; a host without one rejects with
|
|
117
|
+
`CONFIG_PERSISTENCE_UNAVAILABLE`, and the profile patch stays the place to edit.
|
|
71
118
|
|
|
72
119
|
## 3. Reasoning efforts
|
|
73
120
|
|
|
@@ -86,21 +133,47 @@ concept differently - map it, never rename the vendor value.
|
|
|
86
133
|
|
|
87
134
|
## 4. Costs
|
|
88
135
|
|
|
89
|
-
Per 1M tokens: `{ input, output, cacheRead, cacheWrite }
|
|
90
|
-
|
|
91
|
-
|
|
136
|
+
Per 1M tokens: `{ input, output, cacheRead, cacheWrite }`, plus optional
|
|
137
|
+
`tiers: [{ inputTokensAbove, input, output, cacheRead, cacheWrite }]`. `0` means
|
|
138
|
+
unknown pricing on a credit plan, not free inference. A vendor that charges more
|
|
139
|
+
above an input threshold needs its tier row, otherwise long prompts are
|
|
140
|
+
understated. `metadata.maxTokens` is model capacity and never a request cap;
|
|
141
|
+
`defaultMaxTokens` is the request default and must not exceed that capacity.
|
|
92
142
|
|
|
93
143
|
## 5. Verify before and after the edit
|
|
94
144
|
|
|
95
145
|
```sh
|
|
96
|
-
dsh --profile tui list-models
|
|
97
|
-
|
|
146
|
+
dsh --profile tui list-models # every reachable route, id<TAB>name
|
|
147
|
+
# resolved cost/ctx/efforts, naming the layer each number came from:
|
|
148
|
+
~/.agents/skills/dsh-tui-update-models/scripts/dump-model-catalog.mjs \
|
|
149
|
+
[--home "$DSH_HOME"] [--profile tui] [--data <pi-ai data dir>] [--json]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The dump marks each value `installed`, `template:<id>`, `route`, or `metadata`, so
|
|
153
|
+
an inherited number is visible instead of trusted, expands a `filter` route
|
|
154
|
+
against the installed data the same way the resolver does, and exits 1 on the
|
|
155
|
+
declarations the resolver refuses - incomplete metadata, a request default above
|
|
156
|
+
capacity, both membership styles, a default the served selection does not
|
|
157
|
+
contain.
|
|
158
|
+
|
|
159
|
+
Then check the vendor's own page for price, context window, max output, and
|
|
160
|
+
accepted effort values - the installed catalog lags vendors, and a template
|
|
161
|
+
clone carries a sibling's numbers.
|
|
162
|
+
|
|
163
|
+
To prove a catalog on a disposable home instead of the live one, provider-extra
|
|
164
|
+
ships a clone-only setup that composes the candidate and disables the competing
|
|
165
|
+
rows for you:
|
|
166
|
+
|
|
167
|
+
```sh
|
|
168
|
+
cd <dsh-provider-extra checkout> && pnpm catalog:setup \
|
|
169
|
+
--helper ~/.agents/skills/dsh-tui-dogfood/scripts/run-plugin-from-worktree.sh \
|
|
170
|
+
--source-home ~/.dsh --home /tmp/dsh-catalog-check --profile tui \
|
|
171
|
+
--catalog /tmp/catalog-config.json [--allow-row ROW_ID] [--dsh /abs/path/to/dsh/lib/bin.js]
|
|
98
172
|
```
|
|
99
173
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
catalog lags vendors, and a template clone carries a sibling's numbers.
|
|
174
|
+
`--catalog` is a private JSON file holding only `{"catalog": {...}}`. Success
|
|
175
|
+
means `composition-verified`, not authenticated runtime; launch only the clone
|
|
176
|
+
command it prints.
|
|
104
177
|
|
|
105
178
|
## 6. Pitfalls
|
|
106
179
|
|
|
@@ -110,8 +183,13 @@ catalog lags vendors, and a template clone carries a sibling's numbers.
|
|
|
110
183
|
| A window that is really a pricing threshold | vendors publish e.g. "above 272K input priced 2x" beside the real window |
|
|
111
184
|
| `off` missing on a reasoning model | `thinkingLevelMap.off` is `null` or absent where the vendor takes `none` |
|
|
112
185
|
| A stealth model with no vendor page | its numbers exist only in the routing catalog (pi.dev model page, models.dev) |
|
|
113
|
-
| Config ignored after an edit | a linked bundle loads `lib
|
|
186
|
+
| Config ignored after an edit | a linked bundle loads `lib/` or `dist/`; run the build in that checkout |
|
|
114
187
|
| ctx gauge and auto-compact trigger early/late | the declared `contextWindow` is wrong, not the gauge |
|
|
188
|
+
| Same model id, two context windows | each route inherits its own source's numbers; declare `metadata` on both |
|
|
189
|
+
| Everything fails closed, logs `CATALOG_OWNER_COLLISION` | a competing adapter, provider directory, or default owner row survived |
|
|
190
|
+
| A new field looks accepted but does nothing | the resolver rejects unknown fields; a stale plugin build silently predates it |
|
|
191
|
+
| `/model` saves a default that reverts | the host has no `configEditor`: `CONFIG_PERSISTENCE_UNAVAILABLE`; edit the patch |
|
|
192
|
+
| Route serves nothing after a `filter` edit | patterns match installed ids only, and `exclude` wins over `include` |
|
|
115
193
|
|
|
116
194
|
Never put a key in a profile or a patch: name it (`apiKeyEnv`, `apiKeyRef`,
|
|
117
195
|
`credentialProvider`) and keep the value in the environment or `$DSH_HOME/.env`.
|
|
@@ -12,6 +12,12 @@ Companion to [SKILL.md](../SKILL.md): exact fields, files, and checks.
|
|
|
12
12
|
4. The model directory a profile sees is owned by whichever row is enabled:
|
|
13
13
|
`llm-pi-ai` (additive provider config), `dsh-provider-extra` (owned catalog),
|
|
14
14
|
or both, if the profile is not using exclusive ownership.
|
|
15
|
+
5. `$DSH_HOME/settings.yaml` sits over the adapter rows, not over a catalog. A
|
|
16
|
+
`llm-pi-ai:` or `llm-deepseek:` section reshapes that adapter per request; a
|
|
17
|
+
`dsh-provider-extra:` section reshapes only the legacy routes'
|
|
18
|
+
`extraModels`/`codexExtraModels`, and only where no `models`/`codexModels`
|
|
19
|
+
selection is declared. A managed catalog reads no settings overlay at all, so
|
|
20
|
+
a catalog edit lands in the profile patch or nowhere.
|
|
15
21
|
|
|
16
22
|
Exclusive ownership is a set of disable rows plus the catalog itself:
|
|
17
23
|
|
|
@@ -23,6 +29,19 @@ Exclusive ownership is a set of disable rows plus the catalog itself:
|
|
|
23
29
|
config: { catalog: { version: 1, ... } }
|
|
24
30
|
```
|
|
25
31
|
|
|
32
|
+
Disable every competing row in the **whole** profile, not only the rows that come
|
|
33
|
+
before provider-extra: on `dsh-base` those are `llm-pi-ai`, `llm-deepseek`, and
|
|
34
|
+
`agent-default-model`, and another bundle may add `llm-openai` or
|
|
35
|
+
`llm-anthropic`. Inspect the composed profile rather than trusting this list.
|
|
36
|
+
Preflight refuses to register managed routes when an adapter, a provider
|
|
37
|
+
directory, or a default owner already exists; a later registration is caught by
|
|
38
|
+
the host's `llm/adapters-updated` notification. Either way the plugin logs
|
|
39
|
+
`CATALOG_OWNER_COLLISION` once per transition and fails closed: listing,
|
|
40
|
+
selection resolution, request preparation, default reads and saves, new
|
|
41
|
+
dispatch, and starting a sign-in all refuse while the conflict exists. Requests
|
|
42
|
+
already past their credential gates finish. Removing the competing rows restores
|
|
43
|
+
service without a reload of the catalog itself.
|
|
44
|
+
|
|
26
45
|
## `llm-pi-ai` provider config
|
|
27
46
|
|
|
28
47
|
```yaml
|
|
@@ -52,57 +71,131 @@ Exclusive ownership is a set of disable rows plus the catalog itself:
|
|
|
52
71
|
An id that the installed catalog already describes needs no restatement. An id it
|
|
53
72
|
does not describe needs the metadata (or a `template` sibling to clone).
|
|
54
73
|
|
|
74
|
+
Route fields (`providers` is a dict keyed by the route id; the key is the route):
|
|
75
|
+
`apiKeyEnv`, `displayName`, `api`, `baseURL`, `models`, `modelOverrides` (per-id
|
|
76
|
+
reshaping of an installed catalog, refused beside a `models` list), `compat`,
|
|
77
|
+
`defaultContextWindow` (262144), `defaultMaxTokens` (32768), `defaultInput`
|
|
78
|
+
(`[text]`), `headers`, `reasoning`, `thinkingBudgets`, `cacheRetention`,
|
|
79
|
+
`transport`, `timeoutMs`, `websocketConnectTimeoutMs`, `streamIdleTimeoutMs`,
|
|
80
|
+
`maxRequestImageBytes`, `requestImagePixelBudget`, `requestImageMaxBytes`,
|
|
81
|
+
`retryPolicy`. A route key that names an installed pi-ai provider inherits that
|
|
82
|
+
provider's endpoint, protocol, display name, and catalog; any other key is a
|
|
83
|
+
complete hand declaration and must name `api` and `baseURL`.
|
|
84
|
+
|
|
85
|
+
`reasoningEfforts` is this adapter's spelling of `thinkingLevelMap`: a dict of
|
|
86
|
+
selectable level to the wire value dispatch sends, `false` for a non-reasoning
|
|
87
|
+
model, and only `off` may carry an empty or null value. An empty dict, a level
|
|
88
|
+
mapping to an empty string, or a dict offering nothing beyond `off` is refused.
|
|
89
|
+
|
|
55
90
|
## `dsh-provider-extra`
|
|
56
91
|
|
|
57
|
-
Two config generations exist; a profile normally uses one
|
|
92
|
+
Two config generations exist; a profile normally uses one, and presence of
|
|
93
|
+
`catalog` is what opts a profile into managed ownership:
|
|
58
94
|
|
|
59
95
|
| Config | Use |
|
|
60
96
|
| --- | --- |
|
|
61
|
-
| Additive: `routeId`, `displayName`, `
|
|
62
|
-
| `catalog` v1 | own the entire directory: ids, aliases, metadata, and the default selection |
|
|
97
|
+
| Additive: `apiKeyEnv`, `routeId`, `displayName`, `baseURL`, `headers`, `fallbackSessionId`, `models`, `extraModels`, `codexEnabled`, `codexRouteId`, `codexDisplayName`, `codexModels`, `codexExtraModels`, `codexTransport`, `loginCommandEnabled`, `loginCommandName` | add a gateway or subscription route beside whatever else the profile serves |
|
|
98
|
+
| `catalog` v1 | own the entire directory: routes, ids, aliases, metadata, and the default selection |
|
|
63
99
|
|
|
64
|
-
|
|
100
|
+
The generations differ where a declaration cannot resolve. An additive
|
|
101
|
+
`extraModels` entry with a bad `template` lands in that route's model
|
|
102
|
+
diagnostics while the route keeps serving; an additive `models`/`codexModels`
|
|
103
|
+
whitelist naming an id nothing provides refuses the whole route. A **catalog**
|
|
104
|
+
rejects the whole candidate instead: there is no per-model diagnostic path.
|
|
105
|
+
|
|
106
|
+
Catalog route fields (an unknown field rejects the candidate):
|
|
65
107
|
|
|
66
108
|
```yaml
|
|
67
109
|
catalog:
|
|
68
110
|
version: 1 # exactly 1
|
|
69
|
-
default: # null only when
|
|
111
|
+
default: # null only when nothing is served
|
|
70
112
|
{ provider: opencode-go-session, model: deepseek-flash, reasoningEffort: max }
|
|
71
113
|
providers:
|
|
72
114
|
- id: opencode-go-session # the id every selection and alias check uses
|
|
73
115
|
name: OpenCode Go (session)
|
|
74
|
-
source: opencode-go # pi-ai provider id
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
116
|
+
source: opencode-go # installed pi-ai provider id; its data is the base
|
|
117
|
+
api: openai-completions # own endpoint only: required without source, refused beside it
|
|
118
|
+
baseURL: https://api.x.ai/v1 # required without source; http(s), no user/pass/fragment
|
|
119
|
+
headers: { X-Route: tenant-a } # static headers; secrets stay in a credential reference
|
|
120
|
+
transport: sse # openai-codex only: sse|websocket|websocket-cached|auto
|
|
121
|
+
fallbackSessionId: dsh-provider-extra # opencode-go only: session header for sessionless calls
|
|
122
|
+
auth: { apiKeyRef: OPENCODE_GO_API_KEY } # or { credentialProvider: openai-codex }; absent = keyless route
|
|
79
123
|
models:
|
|
80
124
|
- id: deepseek-flash
|
|
81
125
|
name: DeepSeek V4.1 Flash
|
|
82
|
-
aliases: [deepseek-v4.1-flash] # extra
|
|
83
|
-
template: deepseek-v4-flash #
|
|
84
|
-
defaultMaxTokens: 384000 # request
|
|
126
|
+
aliases: [deepseek-v4.1-flash] # extra selector inputs, unique in the route, never extra rows
|
|
127
|
+
template: deepseek-v4-flash # installed sibling of the SAME source to clone
|
|
128
|
+
defaultMaxTokens: 384000 # request default; must be <= resolved maxTokens
|
|
85
129
|
metadata:
|
|
86
130
|
api: openai-completions
|
|
87
131
|
reasoning: true
|
|
88
132
|
input: [text, image]
|
|
89
133
|
cost: { input: 0.22, output: 0.66, cacheRead: 0.007, cacheWrite: 0 }
|
|
134
|
+
# a threshold-priced vendor adds its tier rows beside those four rates:
|
|
135
|
+
# cost: { input: 0.22, output: 0.66, cacheRead: 0.007, cacheWrite: 0,
|
|
136
|
+
# tiers: [{ inputTokensAbove: 272000, input: 0.44, output: 1.32,
|
|
137
|
+
# cacheRead: 0.014, cacheWrite: 0 }] }
|
|
90
138
|
contextWindow: 1000000
|
|
91
|
-
maxTokens: 384000
|
|
139
|
+
maxTokens: 384000 # capacity, never a request cap
|
|
92
140
|
thinkingLevelMap: { off: null, minimal: null, low: low, medium: null, high: high, xhigh: null, max: max }
|
|
141
|
+
headers: { X-Model: flash }
|
|
93
142
|
compat: { supportsStore: false, supportsDeveloperRole: false, supportsReasoningEffort: true, supportsStrictMode: true, thinkingFormat: qwen }
|
|
143
|
+
- id: zai-catalog
|
|
144
|
+
name: Z.AI Coding Plan
|
|
145
|
+
source: zai
|
|
146
|
+
auth: { apiKeyRef: ZAI_API_KEY }
|
|
147
|
+
filter: { include: ['glm-*'], exclude: ['glm-4*'] } # instead of models; '*' is the only wildcard
|
|
94
148
|
```
|
|
95
149
|
|
|
150
|
+
`filter` needs an installed `source`: an absent `include` starts from every
|
|
151
|
+
installed model, matches keep installed order and every installed fact (id, name,
|
|
152
|
+
cost, capacity, protocol, endpoint), and the route serves them minus `exclude`.
|
|
153
|
+
A matched model that publishes no endpoint needs a route `baseURL`.
|
|
154
|
+
|
|
155
|
+
A model with no resolvable template must declare all of `api` (unless the route
|
|
156
|
+
declares `api`), `reasoning`, `input`, `cost`, `contextWindow`, and
|
|
157
|
+
`maxTokens`. `metadata.api` cannot change a template-backed protocol, must equal
|
|
158
|
+
a route's declared `api`, and must be an api the `source` describes.
|
|
159
|
+
|
|
160
|
+
`compat` accepts only the flags the model's protocol honors: completions takes the
|
|
161
|
+
`supports*`/`requires*` booleans plus `maxTokensField`, `thinkingFormat`,
|
|
162
|
+
`thinkingTokenBudgetField`, `cacheControlFormat`, `deferredToolsMode`, and
|
|
163
|
+
`sessionAffinityFormat`; responses takes its own flag set plus
|
|
164
|
+
`sessionAffinityFormat`; anthropic-messages takes its flag set and no enums.
|
|
165
|
+
Template-language and gateway-routing keys (`chatTemplateKwargs`,
|
|
166
|
+
`chatTemplateArgs`, `openRouterRouting`, `vercelGatewayRouting`,
|
|
167
|
+
`vllmPriority`, `allowedFallbackModels`) are refused. The plugin's own
|
|
168
|
+
`src/catalog-routes.ts` lists the accepted names per protocol.
|
|
169
|
+
|
|
96
170
|
Behaviour:
|
|
97
171
|
|
|
98
172
|
| Case | Result |
|
|
99
173
|
| --- | --- |
|
|
100
174
|
| `template` names a sibling the source ships | clone its wire behavior and metadata |
|
|
101
|
-
| `template` names nothing
|
|
102
|
-
|
|
|
103
|
-
|
|
|
104
|
-
|
|
|
105
|
-
| `
|
|
175
|
+
| `template` names nothing, or the route has no source | candidate rejected: `unknown template <id>` |
|
|
176
|
+
| unknown id, no template, incomplete metadata | candidate rejected: `unknown model requires complete metadata: missing <field>` |
|
|
177
|
+
| both `models` and `filter`, or neither | candidate rejected: `declare exactly one of models or filter` |
|
|
178
|
+
| duplicate provider id, model id, or alias | candidate rejected |
|
|
179
|
+
| `defaultMaxTokens` above the resolved `maxTokens` | candidate rejected: `exceeds model maxTokens` |
|
|
180
|
+
| `default` names a model outside the served selection | candidate rejected; an alias is canonicalized to its wire id |
|
|
181
|
+
| `reasoningEffort` unsupported by the model | candidate rejected |
|
|
182
|
+
| nothing served (empty providers, or `models: []`) | `default` must be `null`; `currentSelection()` throws `NO_DEFAULT_MODEL` |
|
|
183
|
+
|
|
184
|
+
### Default persistence
|
|
185
|
+
|
|
186
|
+
`catalog.default` reaches a new session as provider and model only: the pinned
|
|
187
|
+
host ignores its `reasoningEffort`, so effort is chosen in the surface.
|
|
188
|
+
`saveSelection(next)` validates first, then writes through the host's
|
|
189
|
+
`configEditor.edit(entry, updater)` so only `catalog.default` changes and
|
|
190
|
+
membership is re-checked inside the editor's lock. A host with no addressable
|
|
191
|
+
entry or no such editor rejects with `CONFIG_PERSISTENCE_UNAVAILABLE` - the
|
|
192
|
+
released `0.1.5-rc.2` host does - and nothing falls back to legacy
|
|
193
|
+
`agent-default-model` settings. Edit the profile patch there.
|
|
194
|
+
|
|
195
|
+
`compileCatalog(config)` and `buildCatalogProfile(provider)` are public: the
|
|
196
|
+
first returns a detached frozen snapshot or `undefined` for absence, the second
|
|
197
|
+
validates one route declaration on its own. Both are what the dump script and a
|
|
198
|
+
candidate check should read against.
|
|
106
199
|
|
|
107
200
|
## Where the base data comes from
|
|
108
201
|
|
|
@@ -113,8 +206,10 @@ PI_AI="${DSH%/lib/bin.js}/node_modules/@earendil-works/pi-ai"
|
|
|
113
206
|
ls "$PI_AI/dist/providers/data" # one JSON per provider source, keyed by api
|
|
114
207
|
```
|
|
115
208
|
|
|
116
|
-
|
|
117
|
-
`
|
|
209
|
+
`source` must be an id the installed pi-ai build ships (`builtinProviders()`);
|
|
210
|
+
`ls` the data directory for the list this harness answers. A JSON entry is the
|
|
211
|
+
whole truth for a `source`: `id`, `name`, `cost`, `contextWindow`,
|
|
212
|
+
`maxTokens`, `input`, `reasoning`, `thinkingLevelMap`, `compat`, `baseUrl`.
|
|
118
213
|
A model declared without `template` and without `metadata` is served exactly as
|
|
119
214
|
this file describes it, so an id the vendor renamed or repriced shows stale
|
|
120
215
|
numbers until the catalog declares them. An entry that carries its own complete
|
|
@@ -125,9 +220,10 @@ not describe is wired.
|
|
|
125
220
|
|
|
126
221
|
| Field | Meaning |
|
|
127
222
|
| --- | --- |
|
|
128
|
-
| `apiKeyEnv` | read the key from this environment variable |
|
|
129
|
-
| `apiKeyRef` |
|
|
130
|
-
| `credentialProvider` | use a login flow's stored
|
|
223
|
+
| `apiKeyEnv` | additive config and `llm-pi-ai`: read the key from this environment variable |
|
|
224
|
+
| `apiKeyRef` | catalog: a host credential reference; without a credential service the route reads that environment variable. Refused on an `openai-codex` source, which needs OAuth |
|
|
225
|
+
| `credentialProvider` | catalog: use a login flow's stored grant. Needs an installed `source` and must equal that source id |
|
|
226
|
+
| no `auth` | catalog: the route carries no key of its own, as a local endpoint does |
|
|
131
227
|
|
|
132
228
|
Keys never belong in a patch, a settings file, or a repository. Put launch-time
|
|
133
229
|
values in `$DSH_HOME/.env` or the environment. A provider row whose credential is
|
|
@@ -137,9 +233,39 @@ missing fails its route only; other routes still serve.
|
|
|
137
233
|
|
|
138
234
|
```sh
|
|
139
235
|
dsh --profile tui list-models # provider/model<TAB>display name, one per reachable route
|
|
140
|
-
~/.agents/skills/dsh-tui-update-models/scripts/dump-model-catalog.mjs
|
|
236
|
+
~/.agents/skills/dsh-tui-update-models/scripts/dump-model-catalog.mjs \
|
|
237
|
+
[--home "$DSH_HOME"] [--profile tui] [--data <pi-ai data dir>] [--json]
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The dump reads the catalog row written as a single JSON flow map
|
|
241
|
+
(`- {"id":"dsh-provider-extra","config":{"catalog":…}}`), which is the shape
|
|
242
|
+
`pnpm catalog:setup` appends and the shape a hand-written patch keeps when one
|
|
243
|
+
line must stay machine-readable. A catalog spread over block YAML is invisible to
|
|
244
|
+
it; `list-models` still reads the composed result either way. `--data` points the
|
|
245
|
+
dump at a provider-data directory instead of the installed harness, which is how
|
|
246
|
+
a template clone or a `filter` route is read on a machine with no `dsh`. The dump
|
|
247
|
+
also reports what the resolver would refuse, and exits 1, because a catalog that
|
|
248
|
+
cannot compose serves nothing: incomplete metadata for an unknown model, a
|
|
249
|
+
`defaultMaxTokens` above capacity, both or neither of `models`/`filter`, a
|
|
250
|
+
default the served selection does not contain, an effort the selected model does
|
|
251
|
+
not offer, and a default that is null while models are served.
|
|
252
|
+
|
|
253
|
+
To validate a candidate on a disposable home, provider-extra's clone-only setup
|
|
254
|
+
composes it and disables the competing rows itself:
|
|
255
|
+
|
|
256
|
+
```sh
|
|
257
|
+
cd <dsh-provider-extra checkout> && pnpm catalog:setup \
|
|
258
|
+
--helper <plugin checkout>/.agents/skills/dsh-tui-dogfood/scripts/run-plugin-from-worktree.sh \
|
|
259
|
+
--source-home ~/.dsh --home /tmp/dsh-catalog-check --profile tui \
|
|
260
|
+
--catalog /tmp/catalog-config.json [--allow-row ROW_ID] [--dsh /abs/path/to/dsh/lib/bin.js]
|
|
141
261
|
```
|
|
142
262
|
|
|
263
|
+
`--catalog` is a private JSON file holding only `{"catalog": {...}}`. It prints
|
|
264
|
+
`composition-verified` on success, which is composition only: not runtime
|
|
265
|
+
ownership, not authentication. `--allow-row` asserts a provider-looking row
|
|
266
|
+
registers no provider and no default owner, because setup cannot see row
|
|
267
|
+
provenance and refuses rather than guesses.
|
|
268
|
+
|
|
143
269
|
`list-models` exits zero only after printing a line, so a deployment that
|
|
144
270
|
advertises nothing cannot pass for one that was enumerated.
|
|
145
271
|
|
|
@@ -150,9 +276,13 @@ For every model you touched, confirm against the vendor's own page:
|
|
|
150
276
|
is priced 2x" is a billing threshold, not the window);
|
|
151
277
|
3. the accepted reasoning effort values, and how the vendor spells "no reasoning"
|
|
152
278
|
(OpenAI: `none`; codex: omit the parameter);
|
|
153
|
-
4. whether the route bills per token at all - a
|
|
154
|
-
`0`,
|
|
155
|
-
|
|
279
|
+
4. whether the route bills per token at all - a metered API wants its published
|
|
280
|
+
rates, and a credit or token plan writes `0`, which reads as unknown pricing
|
|
281
|
+
rather than free inference;
|
|
282
|
+
5. that an `xhigh` or `max` mapping exists when the vendor documents one;
|
|
283
|
+
6. that the same model id served on two routes carries the same facts: each route
|
|
284
|
+
inherits its own `source` entry, so one declared `contextWindow` and one
|
|
285
|
+
inherited is two different ctx gauges for one model.
|
|
156
286
|
|
|
157
287
|
A model with no vendor page (stealth releases, previews) is documented only by the
|
|
158
288
|
routing catalog: its pi.dev model page and `models.dev/api.json`.
|