pi-better-model-select 0.1.3 → 0.2.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/README.md +52 -84
- package/docs/configuration.md +105 -0
- package/docs/development.md +104 -0
- package/extensions/bms.ts +112 -36
- package/extensions/lib/agents.ts +60 -0
- package/extensions/lib/defaults-models.ts +15 -0
- package/extensions/lib/defaults-store.ts +108 -0
- package/extensions/lib/defaults.ts +149 -0
- package/extensions/lib/effort-editor.ts +69 -0
- package/extensions/lib/flows.ts +83 -59
- package/extensions/lib/meta.ts +21 -7
- package/extensions/lib/picker-dialog.ts +21 -0
- package/extensions/lib/picker.ts +26 -3
- package/extensions/lib/store.ts +60 -12
- package/package.json +8 -2
package/README.md
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
# pi-better-model-select
|
|
2
2
|
|
|
3
|
-
A searchable
|
|
4
|
-
|
|
5
|
-
`/bms` opens the effective model list from `models.json` plus the optional
|
|
6
|
-
`models.local.json` overlay, sorted by how often you pick each model. Start
|
|
7
|
-
typing to filter; press enter to switch the session to the highlighted model.
|
|
8
|
-
Add, update and delete live on ctrl-chords so plain keystrokes always go to the
|
|
9
|
-
search box.
|
|
3
|
+
A searchable model picker for [Pi](https://pi.dev). Switch models, edit model definitions, and save default models and thinking effort. Models you pick most often appear first.
|
|
10
4
|
|
|
11
5
|
## Install
|
|
12
6
|
|
|
@@ -14,108 +8,82 @@ search box.
|
|
|
14
8
|
pi install npm:pi-better-model-select
|
|
15
9
|
```
|
|
16
10
|
|
|
17
|
-
|
|
11
|
+
Run `/bms`, type to filter by name, model ID, or provider, then press Enter to switch models for the current session.
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
## Commands and shortcuts
|
|
14
|
+
|
|
15
|
+
| Command | Action |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `/bms` | Open the model picker |
|
|
18
|
+
| `/bms-add` | Add a model |
|
|
19
|
+
| `/bms-defaults` | Configure Pi and subagent defaults |
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
Inside `/bms`:
|
|
25
22
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
23
|
+
| Key | Action |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| ↑ / ↓ | Move through models |
|
|
26
|
+
| Enter | Use the selected model for this session |
|
|
27
|
+
| Ctrl+D | Choose effort, save as the Pi default, and use it now |
|
|
28
|
+
| Ctrl+G | Open the defaults menu |
|
|
29
|
+
| Ctrl+A / Ctrl+U / Ctrl+X | Add / update / delete a model |
|
|
30
|
+
| Ctrl+O | Set write mode and provider filters |
|
|
31
|
+
| Ctrl+R | Reset usage ranking |
|
|
32
|
+
| Esc | Close |
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
The picker marks the active model with `[current]` and the saved startup model with `[default]`. Only Enter selections count toward usage ranking.
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
| ---------- | --------------------------------------------------- |
|
|
35
|
-
| `↑` `↓` | Move through the list |
|
|
36
|
-
| *type* | Fuzzy filter by display name, model id or provider |
|
|
37
|
-
| `enter` | Load the highlighted model |
|
|
38
|
-
| `ctrl+a` | Add a model |
|
|
39
|
-
| `ctrl+u` | Update the highlighted model |
|
|
40
|
-
| `ctrl+x` | Delete the highlighted model |
|
|
41
|
-
| `ctrl+r` | Reset usage statistics |
|
|
42
|
-
| `esc` | Close |
|
|
36
|
+
## Default models and effort
|
|
43
37
|
|
|
44
|
-
|
|
38
|
+
New in BMS `0.2.0`: save a model and thinking effort for Pi, a subagent fallback, or an individual agent role.
|
|
45
39
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
drifts toward your habits.
|
|
40
|
+
- Use Ctrl+D in `/bms` for the main Pi default.
|
|
41
|
+
- Use `/bms-defaults` for all defaults. The menu starts in user scope; select the scope row to switch to a trusted project's settings.
|
|
42
|
+
- Agent model and effort overrides are independent. You can inherit the parent model while choosing a different effort.
|
|
50
43
|
|
|
51
|
-
|
|
52
|
-
session restore does not, which keeps the ranking a record of deliberate choices
|
|
53
|
-
rather than of how often you restart pi.
|
|
44
|
+
Removing an override restores the agent's own definition and fallback rules. Effort `off` is explicit, not inheritance. The menu shows saved settings and their sources, not running-child state.
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
when the picker opens. `ctrl+r` clears everything.
|
|
46
|
+
Run `/reload` after changing agent defaults or per-model effort preferences. Existing children stay unchanged. Test Pi startup defaults in a fresh session, since resumed sessions restore their recorded model and effort.
|
|
57
47
|
|
|
58
|
-
|
|
48
|
+
### Optional pi-subagents integration
|
|
59
49
|
|
|
60
|
-
|
|
61
|
-
cap, reasoning support and input modalities from, in order:
|
|
50
|
+
[pi-subagents](https://github.com/nicobailon/pi-subagents) is a separate extension. Install it only if you want subagents:
|
|
62
51
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
52
|
+
```bash
|
|
53
|
+
pi install npm:pi-subagents
|
|
54
|
+
```
|
|
66
55
|
|
|
67
|
-
|
|
68
|
-
nothing from steps 1 and 2, and the flow falls back to name-based heuristics
|
|
69
|
-
with editable defaults. Before a write, BMS asks you to choose
|
|
70
|
-
`models.local.json` or `models.json`. The local file is listed first and acts as
|
|
71
|
-
a personal BMS overlay. The selected file is re-read immediately before the
|
|
72
|
-
write so a concurrent edit in another pi session is not clobbered.
|
|
56
|
+
BMS discovers its agent roles and edits their saved model and effort defaults. It does not install `pi-subagents` automatically. External runner roles are read-only because their model settings belong to the runner.
|
|
73
57
|
|
|
74
|
-
|
|
58
|
+
Discovery has now been checked with **pi-subagents `0.82.1`**, updated from the original `0.79.0` test. BMS handles the newer bundled entry-point layout. The adapter still uses internal modules, so future package changes may require an update. If discovery fails, **Agent discovery details** shows the error. Pi defaults work without this integration.
|
|
75
59
|
|
|
76
|
-
|
|
77
|
-
rebranded distributions work without special handling:
|
|
60
|
+
### Supported effort levels
|
|
78
61
|
|
|
79
|
-
-
|
|
80
|
-
- `<agent-dir>/models.local.json` is an optional personal overlay managed by BMS.
|
|
81
|
-
- `<agent-dir>/bms-usage.json` stores selection counters.
|
|
62
|
+
BMS uses provider-reported effort lists when available and Pi's capability restrictions where supplied. Otherwise it offers Pi's standard levels, marked as provider support unverified:
|
|
82
63
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
same model exists in both files, fields from `models.local.json` win.
|
|
64
|
+
```text
|
|
65
|
+
off · minimal · low · medium · high · xhigh · max
|
|
66
|
+
```
|
|
87
67
|
|
|
88
|
-
|
|
89
|
-
BMS writes `{ "id": "...", "bmsDeleted": true }` to the local overlay instead.
|
|
90
|
-
This BMS-owned tombstone keeps generated or externally managed model entries
|
|
91
|
-
hidden across restarts. Delete the tombstone from `models.local.json` to restore
|
|
92
|
-
the inherited model.
|
|
68
|
+
Add/update also lets you edit supported levels and set a per-model preferred effort. These hints do not change Pi's provider adapters. If Pi clamps an effort, BMS asks before saving the applied level. No paid model probes are made.
|
|
93
69
|
|
|
94
|
-
|
|
95
|
-
cannot truncate either configuration file.
|
|
70
|
+
## Where changes are saved
|
|
96
71
|
|
|
97
|
-
|
|
72
|
+
Ctrl+O selects the model write mode:
|
|
98
73
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
pi -e . -p "say OK" # load the extension without installing it
|
|
104
|
-
```
|
|
74
|
+
| Mode | Behavior |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| Overlay, the default | Save model edits in `models.bms.json`; leave `models.json` unchanged |
|
|
77
|
+
| Direct | Write model edits to Pi's `models.json` |
|
|
105
78
|
|
|
106
|
-
|
|
79
|
+
Defaults always go into native Pi `settings.json`, regardless of write mode. User files normally live in `~/.pi/agent`; project defaults use `.pi/settings.json`. BMS respects `PI_CODING_AGENT_DIR` and shows the selected settings paths.
|
|
107
80
|
|
|
108
|
-
|
|
81
|
+
Provider filters hide entries only in `/bms`, not Pi's `/model` picker. Default-assignment menus also include authenticated Pi registry models.
|
|
109
82
|
|
|
110
|
-
|
|
111
|
-
make publish
|
|
112
|
-
```
|
|
83
|
+
## Further documentation
|
|
113
84
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
exists, publishes the public scoped package, and waits for npm to expose the
|
|
117
|
-
new version. npm can take several minutes to process a package. If verification
|
|
118
|
-
times out, rerun `make verify-publish`; do not publish the same version again.
|
|
85
|
+
- [Configuration reference](https://g.han.zone/msenebald/pi-better-model-select/src/branch/main/docs/configuration.md): file paths, precedence, overlays, metadata, and integration limits.
|
|
86
|
+
- [Development and testing](https://g.han.zone/msenebald/pi-better-model-select/src/branch/main/docs/development.md): local loading, isolated tests, checks, and publishing.
|
|
119
87
|
|
|
120
88
|
## License
|
|
121
89
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Configuration reference
|
|
2
|
+
|
|
3
|
+
See the [README](../README.md) for installation and shortcuts.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
`<agent-dir>` is normally `~/.pi/agent`. BMS uses Pi's `getAgentDir()`, so it respects `PI_CODING_AGENT_DIR`.
|
|
8
|
+
|
|
9
|
+
| File | Contents |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `<agent-dir>/models.json` | Pi model definitions |
|
|
12
|
+
| `<agent-dir>/models.bms.json` | BMS model overlay |
|
|
13
|
+
| `<agent-dir>/bms-settings.json` | Write mode and provider filters |
|
|
14
|
+
| `<agent-dir>/bms-usage.json` | Selection counts and recency |
|
|
15
|
+
| `<agent-dir>/bms-model-metadata.json` | Effort capability hints and their source |
|
|
16
|
+
| `<agent-dir>/settings.json` | User defaults and per-model effort preferences |
|
|
17
|
+
| `<project>/.pi/settings.json` | Project defaults |
|
|
18
|
+
|
|
19
|
+
## Model write modes
|
|
20
|
+
|
|
21
|
+
Choose the write mode through Ctrl+O in `/bms`.
|
|
22
|
+
|
|
23
|
+
- Overlay mode, the default, leaves `models.json` unchanged and writes edits to `models.bms.json`.
|
|
24
|
+
- Direct mode writes add, update, and delete operations to `models.json`.
|
|
25
|
+
|
|
26
|
+
Pi does not load the overlay itself. BMS merges it over `models.json` by provider and model ID, then registers the merged providers through Pi's extension API. Overlay fields win for duplicate models. BMS automatically renames its old `models.local.json` overlay to `models.bms.json`.
|
|
27
|
+
|
|
28
|
+
Deleting an inherited model in overlay mode writes a tombstone:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{ "id": "model-id", "bmsDeleted": true }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Remove that entry from `models.bms.json` to restore the inherited model. Model files are re-read before writes and saved through a temporary file and rename. This does not provide a shared lock against other applications.
|
|
35
|
+
|
|
36
|
+
Provider filters affect only `/bms`. They do not change Pi's `/model` picker or model definitions. Default-assignment menus ignore the filters and also include authenticated Pi registry models.
|
|
37
|
+
|
|
38
|
+
## Defaults and precedence
|
|
39
|
+
|
|
40
|
+
Default settings are independent of the model write mode.
|
|
41
|
+
|
|
42
|
+
| Setting | Native Pi settings field |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| Pi model | `defaultProvider`, `defaultModel` |
|
|
45
|
+
| Pi effort | `defaultThinkingLevel` |
|
|
46
|
+
| Per-model preferred effort | `modelThinkingLevels["provider/model"]` |
|
|
47
|
+
| Subagent fallback | `subagents.defaultModel`, `subagents.defaultThinking` |
|
|
48
|
+
| Role override | `subagents.agentOverrides.<name>.model` and `.thinking` |
|
|
49
|
+
|
|
50
|
+
The defaults menu starts in user scope. Project scope requires a trusted project. Pi defaults use the working directory's `.pi/settings.json`. Agent defaults use the project root resolved by `pi-subagents`, which may differ in nested repositories. The menu shows the paths.
|
|
51
|
+
|
|
52
|
+
Project settings and provider-specific role overrides may take precedence. BMS shows override sources and warns when a saved Pi default would be shadowed. Saving a Pi default removes the selected model's per-model effort preference in the same scope, so it cannot override the new default.
|
|
53
|
+
|
|
54
|
+
For agent roles:
|
|
55
|
+
|
|
56
|
+
- **Inherit parent model** writes `model: "inherit"`.
|
|
57
|
+
- **Remove model override** restores agent-definition and fallback rules.
|
|
58
|
+
- **Automatic** or **Remove effort override** removes that scope's effort preference. It does not force parent effort inheritance. `pi-subagents` has no separate persistent setting for that.
|
|
59
|
+
- **Off** is an explicit effort level, not inheritance.
|
|
60
|
+
|
|
61
|
+
Changing the Pi default also switches the main session. Subagent assignments do not switch it or increment usage counts. Cancelled or failed model switches leave saved defaults unchanged. If Pi clamps an effort, BMS asks before saving the applied level.
|
|
62
|
+
|
|
63
|
+
Run `/reload` after changing agent defaults or per-model effort preferences. Running children keep their existing configuration, and subagent model-scope policies still apply at launch. Resumed Pi sessions restore their recorded model and effort. Test startup defaults in a fresh session without CLI model or effort overrides.
|
|
64
|
+
|
|
65
|
+
Settings writes preserve unrelated fields, reject malformed JSON, and use atomic rename. A `.bms-lock` file serializes BMS settings writers only. After a crash, remove a stale lock only after checking that no BMS writer is active. Updates across model definitions, settings, and capability metadata are not a multi-file transaction.
|
|
66
|
+
|
|
67
|
+
## Effort levels and metadata
|
|
68
|
+
|
|
69
|
+
BMS uses explicit provider effort lists when supplied, then native Pi capability restrictions where available. Otherwise it offers Pi's standard levels and labels provider support as unverified:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
off, minimal, low, medium, high, xhigh, max
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The metadata adapter recognizes `supported_reasoning_efforts`, `supported_thinking_levels`, and `reasoning_efforts` containing Pi effort names. A generic reasoning flag or `supported_parameters: ["reasoning_effort"]` does not specify supported levels.
|
|
76
|
+
|
|
77
|
+
Add/update lets you edit the available levels and choose an optional per-model preferred effort. Non-reasoning models offer only `off`; correct the reasoning flag first if the endpoint metadata is wrong. BMS makes no paid model probes.
|
|
78
|
+
|
|
79
|
+
Capability hints guide menus. They do not change provider adapters or prove an endpoint honors a level. Pi may clamp effort or map it to a token budget. Provider errors remain visible rather than triggering a model fallback.
|
|
80
|
+
|
|
81
|
+
BMS blocks capability edits that conflict with directly pinned defaults in the inspected user/project settings. Update those defaults first. This check does not audit every inherited agent definition or settings file elsewhere on disk.
|
|
82
|
+
|
|
83
|
+
When adding or updating models, BMS tries these metadata sources in order:
|
|
84
|
+
|
|
85
|
+
1. `GET {baseUrl}/model/info` for LiteLLM proxies.
|
|
86
|
+
2. `GET {baseUrl}/models/{id}` for OpenAI-style and proxy-specific fields.
|
|
87
|
+
3. The public LiteLLM model cost map on GitHub.
|
|
88
|
+
|
|
89
|
+
These requests are best-effort. Missing metadata falls back to name-based heuristics and editable defaults for context window, output limit, reasoning support, and input types.
|
|
90
|
+
|
|
91
|
+
## Optional pi-subagents integration
|
|
92
|
+
|
|
93
|
+
[pi-subagents](https://github.com/nicobailon/pi-subagents) is a separate Pi extension. BMS configures its saved model and effort defaults; it does not install it or launch agents to discover roles.
|
|
94
|
+
|
|
95
|
+
Discovery was originally tested with `0.79.0` and has now been checked against `0.82.1`. BMS supports both the older root entry point and the newer `bundle/index.js` package layout. Discovery still uses internal discovery/runtime modules because the public agents API does not expose role discovery. Future incompatible versions may need an adapter update.
|
|
96
|
+
|
|
97
|
+
The package must be resolvable from BMS or `<agent-dir>/npm/node_modules`. Discovery includes built-in, package, user, project, and runtime roles when supported by the installed API. Untrusted project agents and settings are excluded. External runner roles are shown read-only; configure their models through the runner's own settings.
|
|
98
|
+
|
|
99
|
+
If discovery fails, the menu offers **Agent discovery details** with the error. Main Pi defaults remain usable.
|
|
100
|
+
|
|
101
|
+
## Usage ranking
|
|
102
|
+
|
|
103
|
+
Only models loaded with Enter in `/bms` count toward ranking. BMS sorts by selection count, then recency, then alphabetically. Default assignments, `/model`, model cycling, and session restores do not count.
|
|
104
|
+
|
|
105
|
+
BMS prunes statistics for models missing from the effective configuration when the picker opens. Ctrl+R clears all usage statistics.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Development and testing
|
|
2
|
+
|
|
3
|
+
Run these commands from the repository checkout:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm ci
|
|
7
|
+
npm run gate
|
|
8
|
+
npm run test:smoke # optional: installed Pi, Python 3 and a POSIX PTY required
|
|
9
|
+
pi --no-extensions -e ./extensions/bms.ts # temporary load; no install or publish
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Temporary local testing
|
|
13
|
+
|
|
14
|
+
Use a fresh terminal in this checkout. The command above disables installed
|
|
15
|
+
extensions to avoid loading two copies of BMS. It still uses your real Pi settings,
|
|
16
|
+
so default changes persist. For an isolated test, use a scratch agent directory
|
|
17
|
+
and project directory instead:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
REPO="$PWD"
|
|
21
|
+
SANDBOX="$(mktemp -d -t bms-local)"
|
|
22
|
+
mkdir -p "$SANDBOX/agent" "$SANDBOX/project/.pi"
|
|
23
|
+
printf '{}\n' > "$SANDBOX/project/.pi/settings.json"
|
|
24
|
+
# Optional: copy your model definitions. Review literal credentials in this file.
|
|
25
|
+
cp "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}/models.json" "$SANDBOX/agent/models.json"
|
|
26
|
+
cd "$SANDBOX/project"
|
|
27
|
+
PI_CODING_AGENT_DIR="$SANDBOX/agent" pi --no-extensions -e "$REPO/extensions/bms.ts"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Provider API-key environment variables remain available. The isolated directory
|
|
31
|
+
has no login credentials; use `/login` there if needed. Do not commit credentials
|
|
32
|
+
or scratch settings. To test agent roles, load your installed pi-subagents entry
|
|
33
|
+
explicitly with another `-e` and make the package resolvable in the scratch agent's
|
|
34
|
+
`npm/node_modules`, or use your real agent directory with backups. For the standard
|
|
35
|
+
Pi npm install layout with pi-subagents 0.82.1, before launching:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
REAL_AGENT="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
39
|
+
ln -s "$REAL_AGENT/npm" "$SANDBOX/agent/npm"
|
|
40
|
+
PI_CODING_AGENT_DIR="$SANDBOX/agent" pi --no-extensions \
|
|
41
|
+
-e "$REPO/extensions/bms.ts" -e "$REAL_AGENT/npm/node_modules/pi-subagents/bundle/index.js"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This symlinks installed package code, not your settings or auth files. Do not run
|
|
45
|
+
package installation/update commands in this sandbox, since they would modify the
|
|
46
|
+
shared package directory.
|
|
47
|
+
|
|
48
|
+
The automated `npm run test:smoke` uses an unreachable dummy endpoint and
|
|
49
|
+
throwaway settings. It checks command registration, the real TUI default-save
|
|
50
|
+
flow, the defaults shortcut, and restoration in a fresh Pi process. No model
|
|
51
|
+
prompts are sent. The regular test suite uses mocked provider metadata and UI.
|
|
52
|
+
|
|
53
|
+
Manual checks:
|
|
54
|
+
|
|
55
|
+
1. `/bms`: filter and use Enter. Only the current session should change.
|
|
56
|
+
2. Reopen `/bms`, press Ctrl+D, choose effort and confirm. Check both markers,
|
|
57
|
+
the footer effort, and the scratch `agent/settings.json`.
|
|
58
|
+
3. Cancel the effort dialog and confirmation. Neither should change defaults.
|
|
59
|
+
4. `/bms-defaults`: select a Pi model, then select the same model again to change
|
|
60
|
+
only its default effort. Test user and trusted-project scope.
|
|
61
|
+
5. Configure a subagent fallback and a role model/effort. Confirm the parent
|
|
62
|
+
session stays unchanged. Test explicit model inheritance and removal of each
|
|
63
|
+
override, then `/reload` and inspect `/subagents-models reviewer`.
|
|
64
|
+
6. Add/update a model. Check provider-reported effort lists, the unverified Pi
|
|
65
|
+
fallback, manual levels, and the preferred effort. Try removing a level that
|
|
66
|
+
a directly pinned default uses; BMS should block the conflicting edit.
|
|
67
|
+
7. Exit and start a fresh Pi session with the same scratch paths. Confirm startup
|
|
68
|
+
model and effort. Do not use `--continue`, `--resume`, or CLI model/effort overrides.
|
|
69
|
+
8. Test a narrow terminal and verify the list, wrapped hints, and search still work.
|
|
70
|
+
|
|
71
|
+
Quit Pi before deleting the sandbox with `rm -rf "$SANDBOX"`, then return to the
|
|
72
|
+
checkout with `cd "$REPO"`. The test commands do not publish or install BMS.
|
|
73
|
+
|
|
74
|
+
## Publishing
|
|
75
|
+
|
|
76
|
+
Publish from `main` after the feature PR is reviewed and merged. Do not publish from the unmerged feature branch.
|
|
77
|
+
|
|
78
|
+
For the current `0.2.0` release:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
git switch main
|
|
82
|
+
git pull --ff-only origin main
|
|
83
|
+
npm ci
|
|
84
|
+
npm run gate
|
|
85
|
+
npm run test:smoke
|
|
86
|
+
npm pack --dry-run
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Check that the working tree is clean, both package manifests say `0.2.0`, and the package contains `README.md`, `docs/configuration.md`, and `docs/development.md`. Tests and the internal PR draft must not be included. The README links point to `main`, so merge the documentation before publishing.
|
|
90
|
+
|
|
91
|
+
Then publish to the public npm registry:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
make publish
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For later releases, update both manifests with `npm version <version> --no-git-tag-version`, and include that change in a reviewed PR before publishing.
|
|
98
|
+
|
|
99
|
+
The target runs the full package gate, checks npm authentication, opens npm's
|
|
100
|
+
browser login when needed, skips the upload if that exact version already
|
|
101
|
+
exists, publishes the public package, and waits for npm to expose the
|
|
102
|
+
new version. npm can take several minutes to process a package. If verification
|
|
103
|
+
times out, rerun `make verify-publish`; do not publish the same version again.
|
|
104
|
+
|
package/extensions/bms.ts
CHANGED
|
@@ -4,15 +4,19 @@
|
|
|
4
4
|
// with add / update / delete wired to ctrl-chords so plain typing always goes
|
|
5
5
|
// to the search box.
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { defaultsFlow, piPreference, setMainDefault } from "./lib/defaults.ts";
|
|
9
|
+
import { readSettings, settingsPath } from "./lib/defaults-store.ts";
|
|
10
|
+
import { pickerDialog } from "./lib/picker-dialog.ts";
|
|
9
11
|
import { addFlow, deleteFlow, updateFlow, type FlowCtx } from "./lib/flows.ts";
|
|
10
|
-
import {
|
|
12
|
+
import { rankModels } from "./lib/picker.ts";
|
|
11
13
|
import {
|
|
12
14
|
loadConfig,
|
|
13
15
|
loadConfigFile,
|
|
16
|
+
loadSettings,
|
|
14
17
|
pruneUsage,
|
|
15
18
|
recordUsage,
|
|
19
|
+
saveSettings,
|
|
16
20
|
saveUsage,
|
|
17
21
|
type ModelEntry,
|
|
18
22
|
type ModelsConfig,
|
|
@@ -47,11 +51,13 @@ function providerConfig(
|
|
|
47
51
|
|
|
48
52
|
/** Apply the personal BMS overlay as extension providers. */
|
|
49
53
|
function syncLocalConfig(pi: ExtensionAPI, ctx: any): ModelsConfig | null {
|
|
50
|
-
const
|
|
54
|
+
const settings = loadSettings();
|
|
55
|
+
const overlay =
|
|
56
|
+
settings.writeMode === "overlay" ? loadConfigFile(ctx, "models.bms.json", true) : { providers: {} };
|
|
51
57
|
const effective = loadConfig(ctx);
|
|
52
|
-
if (!
|
|
58
|
+
if (!overlay || !effective) return null;
|
|
53
59
|
|
|
54
|
-
const next = new Set(Object.keys(
|
|
60
|
+
const next = new Set(Object.keys(overlay.providers ?? {}));
|
|
55
61
|
for (const providerName of registeredLocalProviders) {
|
|
56
62
|
if (!next.has(providerName)) pi.unregisterProvider(providerName);
|
|
57
63
|
}
|
|
@@ -62,7 +68,7 @@ function syncLocalConfig(pi: ExtensionAPI, ctx: any): ModelsConfig | null {
|
|
|
62
68
|
try {
|
|
63
69
|
pi.registerProvider(providerName, providerConfig(ctx, providerName, provider) as any);
|
|
64
70
|
} catch (e) {
|
|
65
|
-
ctx.ui.notify(`Cannot apply ${providerName} from models.
|
|
71
|
+
ctx.ui.notify(`Cannot apply ${providerName} from models.bms.json: ${e}`, "error");
|
|
66
72
|
return null;
|
|
67
73
|
}
|
|
68
74
|
}
|
|
@@ -73,6 +79,52 @@ function syncLocalConfig(pi: ExtensionAPI, ctx: any): ModelsConfig | null {
|
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
/** Re-read model configuration from disk and switch the session to the given model. */
|
|
82
|
+
async function optionsFlow(ctx: any, config: ModelsConfig): Promise<void> {
|
|
83
|
+
for (;;) {
|
|
84
|
+
const settings = loadSettings();
|
|
85
|
+
const writeLabel =
|
|
86
|
+
settings.writeMode === "overlay" ? "BMS overlay (models.bms.json)" : "Pi config (models.json)";
|
|
87
|
+
const hidden = settings.disabledProviders.length;
|
|
88
|
+
const pick = await ctx.ui.select("BMS options", [
|
|
89
|
+
`Write mode: ${writeLabel}`,
|
|
90
|
+
`Provider filters: ${hidden} hidden`,
|
|
91
|
+
"Done",
|
|
92
|
+
]);
|
|
93
|
+
if (!pick || pick === "Done") return;
|
|
94
|
+
|
|
95
|
+
if (pick.startsWith("Write mode:")) {
|
|
96
|
+
const mode = await ctx.ui.select("Where should BMS write model changes?", [
|
|
97
|
+
"BMS overlay (models.bms.json)",
|
|
98
|
+
"Pi config (models.json)",
|
|
99
|
+
]);
|
|
100
|
+
if (!mode) continue;
|
|
101
|
+
settings.writeMode = mode.startsWith("BMS overlay") ? "overlay" : "direct";
|
|
102
|
+
saveSettings(settings);
|
|
103
|
+
ctx.ui.notify(`BMS write mode: ${settings.writeMode}.`, "info");
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const providers = Object.keys(config.providers ?? {}).sort();
|
|
108
|
+
for (;;) {
|
|
109
|
+
const current = loadSettings();
|
|
110
|
+
const options = providers.map((provider) =>
|
|
111
|
+
`${current.disabledProviders.includes(provider) ? "[ ]" : "[x]"} ${provider}`,
|
|
112
|
+
);
|
|
113
|
+
const choice = await ctx.ui.select(
|
|
114
|
+
"Providers shown in /bms (select to toggle, ESC when done)",
|
|
115
|
+
options,
|
|
116
|
+
);
|
|
117
|
+
if (!choice) break;
|
|
118
|
+
const provider = choice.slice(4);
|
|
119
|
+
const disabled = new Set(current.disabledProviders);
|
|
120
|
+
if (disabled.has(provider)) disabled.delete(provider);
|
|
121
|
+
else disabled.add(provider);
|
|
122
|
+
current.disabledProviders = [...disabled].sort();
|
|
123
|
+
saveSettings(current);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
76
128
|
async function loadModel(
|
|
77
129
|
pi: ExtensionAPI,
|
|
78
130
|
ctx: any,
|
|
@@ -115,6 +167,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
115
167
|
syncLocalConfig(pi, ctx);
|
|
116
168
|
});
|
|
117
169
|
|
|
170
|
+
const report = async (ctx: any, action: () => Promise<void>) => {
|
|
171
|
+
try { await action(); } catch (error) { ctx.ui.notify(`BMS: ${error}`, "error"); }
|
|
172
|
+
};
|
|
173
|
+
const addModel = async (ctx: any) => addFlow(ctx, async (provider, modelId) => {
|
|
174
|
+
if (syncLocalConfig(pi, ctx)) await report(ctx, () => setMainDefault(pi, ctx, provider, modelId));
|
|
175
|
+
});
|
|
176
|
+
pi.registerCommand("bms-defaults", {
|
|
177
|
+
description: "Configure Pi and subagent default models and effort levels",
|
|
178
|
+
handler: async (_args, ctx: any) => {
|
|
179
|
+
if (ctx.mode !== "tui") { ctx.ui.notify("/bms-defaults needs an interactive terminal.", "warning"); return; }
|
|
180
|
+
await report(ctx, async () => {
|
|
181
|
+
const config = syncLocalConfig(pi, ctx);
|
|
182
|
+
if (config) await defaultsFlow(pi, ctx, config);
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
|
|
118
187
|
pi.registerCommand("bms", {
|
|
119
188
|
description: "Better model select — searchable, usage-ranked model picker",
|
|
120
189
|
handler: async (_args, ctx: any) => {
|
|
@@ -129,48 +198,52 @@ export default function (pi: ExtensionAPI) {
|
|
|
129
198
|
if (!config) return;
|
|
130
199
|
pruneUsage(config);
|
|
131
200
|
|
|
132
|
-
const
|
|
201
|
+
const settings = loadSettings();
|
|
202
|
+
const rows = rankModels(config, settings.disabledProviders);
|
|
133
203
|
if (rows.length === 0) {
|
|
204
|
+
const total = Object.values(config.providers ?? {}).reduce(
|
|
205
|
+
(sum, provider) => sum + (provider.models?.length ?? 0),
|
|
206
|
+
0,
|
|
207
|
+
);
|
|
208
|
+
if (total > 0) {
|
|
209
|
+
const open = await ctx.ui.confirm(
|
|
210
|
+
"No models visible",
|
|
211
|
+
"All providers are hidden by BMS filters. Open options?",
|
|
212
|
+
);
|
|
213
|
+
if (!open) return;
|
|
214
|
+
await optionsFlow(ctx, config);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
134
217
|
const add = await ctx.ui.confirm(
|
|
135
218
|
"No models configured",
|
|
136
219
|
"The effective model configuration has no models. Add one now?",
|
|
137
220
|
);
|
|
138
221
|
if (!add) return;
|
|
139
|
-
await
|
|
222
|
+
await report(ctx, () => addModel(ctx));
|
|
140
223
|
continue;
|
|
141
224
|
}
|
|
142
225
|
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const picker = new ModelPicker(
|
|
152
|
-
rows,
|
|
153
|
-
theme,
|
|
154
|
-
() => tui.requestRender(),
|
|
155
|
-
(a: PickerAction) => done(a),
|
|
156
|
-
);
|
|
157
|
-
picker.focused = true;
|
|
158
|
-
container.addChild(picker);
|
|
159
|
-
container.addChild(new DynamicBorder(accent));
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
render: (w: number) => container.render(w),
|
|
163
|
-
invalidate: () => container.invalidate(),
|
|
164
|
-
handleInput: (data: string) => picker.handleInput(data),
|
|
165
|
-
};
|
|
166
|
-
});
|
|
226
|
+
try {
|
|
227
|
+
const preference = piPreference(readSettings(settingsPath(ctx.cwd, "user")), ctx.isProjectTrusted?.() ? readSettings(settingsPath(ctx.cwd, "project")) : {});
|
|
228
|
+
for (const row of rows) {
|
|
229
|
+
row.current = ctx.model?.provider === row.provider && ctx.model?.id === row.modelId;
|
|
230
|
+
row.default = preference.provider === row.provider && preference.model === row.modelId;
|
|
231
|
+
}
|
|
232
|
+
} catch (error) { ctx.ui.notify(`Cannot read defaults: ${error}`, "warning"); }
|
|
233
|
+
const action = await pickerDialog(ctx, rows);
|
|
167
234
|
|
|
168
235
|
switch (action?.kind) {
|
|
169
236
|
case "load":
|
|
170
237
|
await loadModel(pi, ctx, action.provider, action.modelId);
|
|
171
238
|
return;
|
|
239
|
+
case "set-default":
|
|
240
|
+
await report(ctx, () => setMainDefault(pi, ctx, action.provider, action.modelId));
|
|
241
|
+
break;
|
|
242
|
+
case "defaults":
|
|
243
|
+
await report(ctx, () => defaultsFlow(pi, ctx, config));
|
|
244
|
+
break;
|
|
172
245
|
case "add":
|
|
173
|
-
await
|
|
246
|
+
await report(ctx, () => addModel(ctx));
|
|
174
247
|
break;
|
|
175
248
|
case "update":
|
|
176
249
|
await updateFlow(
|
|
@@ -188,6 +261,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
188
261
|
: undefined,
|
|
189
262
|
);
|
|
190
263
|
break;
|
|
264
|
+
case "options":
|
|
265
|
+
await optionsFlow(ctx, config);
|
|
266
|
+
break;
|
|
191
267
|
case "reset": {
|
|
192
268
|
const ok = await ctx.ui.confirm(
|
|
193
269
|
"Reset usage statistics?",
|
|
@@ -208,7 +284,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
208
284
|
|
|
209
285
|
// Standalone add, kept for muscle memory and non-interactive convenience.
|
|
210
286
|
pi.registerCommand("bms-add", {
|
|
211
|
-
description: "Add a model from a provider's endpoint
|
|
212
|
-
handler: async (_args, ctx: any) =>
|
|
287
|
+
description: "Add a model from a provider's endpoint using the configured write mode",
|
|
288
|
+
handler: async (_args, ctx: any) => report(ctx, () => addModel(ctx)),
|
|
213
289
|
});
|
|
214
290
|
}
|