@mzwing/pi-model-info 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lockinwize Lolite
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is furnished
10
+ to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # @mzwing/pi-model-info
2
+
3
+ [![npm](https://img.shields.io/npm/v/@mzwing/pi-model-info)](https://www.npmjs.com/package/@mzwing/pi-model-info)
4
+
5
+ Completes third-party model metadata in [Pi](https://pi.dev) from the pi.dev and models.dev
6
+ catalogs.
7
+
8
+ ## The problem
9
+
10
+ Pi only knows the models in its own catalog. Point it at a relay, gateway, or any provider it
11
+ has not indexed, and every model gets placeholders instead of metadata:
12
+
13
+ ```
14
+ reasoning: false input: ["text"] cost: {0,0,0,0} contextWindow: 128000 maxTokens: 16384
15
+ ```
16
+
17
+ That means wrong compaction timing, `$0.00` cost accounting, and reasoning models Pi does not
18
+ know are reasoning models. The alternative is hand-writing every field into `models.json`
19
+ `modelOverrides` and re-editing it whenever anything changes upstream.
20
+
21
+ This extension looks each model up in two catalogs and fills the gaps at runtime.
22
+
23
+ ## What it does not do
24
+
25
+ It never creates providers, never discovers models, never changes `id` / `provider` / `baseUrl` /
26
+ `api` / auth / transport, and never writes to any of your files. Remove it and Pi goes back to
27
+ exactly what it did before.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pi install npm:@mzwing/pi-model-info
33
+ ```
34
+
35
+ Then opt a provider in — nothing happens until you do:
36
+
37
+ ```jsonc
38
+ // ~/.pi/agent/extensions/pi-model-info/config.json
39
+ { "providers": { "my-relay": {} } }
40
+ ```
41
+
42
+ ## Pairing with a discovery extension
43
+
44
+ If your provider's models come from something like
45
+ [`pi-openai-api-models-sync`](https://www.npmjs.com/package/pi-openai-api-models-sync) — a
46
+ `models.json` provider with `"models": []` whose real list is fetched from `/v1/models` — the two
47
+ extensions are designed to work together: **that one discovers _which_ models exist, this one
48
+ completes _what they are_.**
49
+
50
+ Ordering is guaranteed, not lucky. Discovery extensions register from inside their factory, which
51
+ Pi flushes before any session event; this extension registers from `session_start`, strictly
52
+ after. It only ever sends the `models` key, so Pi's merge preserves the sibling's `baseUrl`,
53
+ `api`, and `apiKey` — and it never calls `unregisterProvider` on a registration carrying keys it
54
+ did not write, because that call would take those credentials with it.
55
+
56
+ Where the sibling already supplied a real value and the catalogs cannot resolve the model, its
57
+ value is kept byte-for-byte. Where you prefer its numbers even when a catalog does resolve, set
58
+ `contextWindowPolicy` / `costPolicy` / `capabilityPolicy` to `"keep"`.
59
+
60
+ ## Sources
61
+
62
+ | Source | Contents |
63
+ | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
64
+ | `pi.dev/api/models` | Already in Pi's own model shape: cost, limits, capabilities, `thinkingLevelMap`, `compat`. The only source of pricing. |
65
+ | `models.dev/models.json` | Provider-agnostic limits and capabilities for models pi.dev does not carry, and the vendor oracle used to break ties. Carries no pricing. |
66
+
67
+ Both are cached on disk under `~/.pi/agent/extensions/pi-model-info/cache/`, revalidated with
68
+ `If-None-Match`, and served stale if a fetch fails. Nothing is fetched on Pi's startup path.
69
+
70
+ `models.dev/api.json` — the 4.2 MB per-provider catalog — is deliberately not used. Where cost
71
+ cannot be resolved it is left alone: Pi's `0` reads as "unknown", while a guessed price reads as
72
+ fact.
73
+
74
+ ## How a model is matched
75
+
76
+ ```
77
+ explicit alias
78
+
79
+ the original id, exactly as your provider spells it
80
+
81
+ the id with at most one prefix and one suffix removed
82
+
83
+ unresolved — nothing is changed
84
+ ```
85
+
86
+ The original id is always tried first because catalogs really do carry `:free` and `-free` as
87
+ separate entries with their own pricing.
88
+
89
+ When one id exists under several catalog providers, the winner is chosen in a fixed order — a
90
+ `catalogProvider` you configured, then the provider id itself, then the vendor named in the id,
91
+ then models.dev's collision-free vendor index. If none of those decides, the model stays
92
+ unresolved and `/model-info` lists the candidates so you can add an alias. There is no fuzzy
93
+ matching and no model involved.
94
+
95
+ ## Configuration
96
+
97
+ Global at `~/.pi/agent/extensions/pi-model-info/config.json`, per-project at
98
+ `.pi/extensions/pi-model-info/config.json`; the project scope wins. See
99
+ [`config/config.example.json`](./config/config.example.json) and
100
+ [`schemas/config.schema.json`](./schemas/config.schema.json).
101
+
102
+ ### Per provider
103
+
104
+ | Key | Default | Meaning |
105
+ | --------------------- | --------- | ---------------------------------------------------------------------------------- |
106
+ | `catalogProvider` | — | Scope lookups to one catalog provider, e.g. `openrouter`. |
107
+ | `costMultiplier` | `1` | Relay markup, applied to catalog pricing only — never to a rule's explicit `0`. |
108
+ | `costPolicy` | `catalog` | `zero` to force free, `keep` to leave pricing alone. |
109
+ | `contextWindowPolicy` | `catalog` | `min` never raises the limit past what Pi already had; `keep` leaves limits alone. |
110
+ | `capabilityPolicy` | `catalog` | `widen` only ever adds a capability; `keep` leaves them alone. |
111
+ | `useCatalogName` | `false` | Rename models to their catalog names. |
112
+ | `mapThinkingLevels` | `false` | Derive a thinking-level map from models.dev. pi.dev's real map is always used. |
113
+ | `allowDynamic` | `false` | Suppress the warning for a provider that refreshes its own model list. |
114
+ | `models` | — | Per-model gates: `alias`, `override`, `skip`, `prefixes`, `suffixes`. |
115
+
116
+ `contextWindowPolicy: "min"` and `capabilityPolicy: "keep"` exist because inflating a limit or
117
+ promoting a capability the relay does not actually support turns into a failed request rather
118
+ than a bad estimate.
119
+
120
+ ### Rules
121
+
122
+ `-free` and `:free` are built in and set the cost to zero. Add your own:
123
+
124
+ ```jsonc
125
+ {
126
+ "rules": [
127
+ {
128
+ "id": "mainfei",
129
+ "kind": "suffix",
130
+ "value": "-mainfei",
131
+ "override": { "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } },
132
+ },
133
+ ],
134
+ }
135
+ ```
136
+
137
+ A rule's `override` applies only when that rule was actually used for the match. Rules are tried
138
+ longest value first, and single strips before double strips.
139
+
140
+ Per model, `suffixes` unset means every rule, `[]` means none, and `["mainfei"]` means only that
141
+ one. `prefixes` works the same way.
142
+
143
+ ### Merge order
144
+
145
+ ```
146
+ what Pi already had
147
+ → catalog metadata (filtered by the policies above)
148
+ → the matched prefix rule's override
149
+ → the matched suffix rule's override
150
+ → the model's own override
151
+ ```
152
+
153
+ Two things always win over all of it: fields you hand-wrote in `models.json` `models[]` are never
154
+ overwritten by a catalog, and Pi applies `models.json` `modelOverrides` above this extension
155
+ entirely.
156
+
157
+ ## `/model-info`
158
+
159
+ ```
160
+ /model-info what was completed, what stayed ambiguous, catalog freshness
161
+ /model-info <provider>/<model> where each field came from
162
+ /model-info refresh re-check the catalogs now
163
+ ```
164
+
165
+ ```
166
+ requested: my-relay/gpt-5.6-sol-free
167
+ canonical: openai/gpt-5.6-sol (pi.dev)
168
+ match: stripped
169
+ rule: free-dash
170
+
171
+ context: 1050000 from pi.dev
172
+ maxTokens: 128000 from pi.dev
173
+ reasoning: true from pi.dev
174
+ cost: $0/$0 per Mtok from rule 'free-dash'
175
+ ```
176
+
177
+ ## Limits
178
+
179
+ - Completing a provider replaces its model list, so a provider that discovers models
180
+ _dynamically_ has that list frozen for the session. This extension warns when it sees one and
181
+ re-reads the list every session; a mid-session change is picked up on the next agent turn.
182
+ - If two extensions complete the same provider, the last one to register wins.
183
+ - With no cache and no network, nothing is applied at all rather than partially.
184
+
185
+ ## License
186
+
187
+ [MIT](./LICENSE)
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "../schemas/config.schema.json",
3
+ "providers": {
4
+ "my-relay": {
5
+ "catalogProvider": "openrouter",
6
+ "costMultiplier": 1.2,
7
+ "models": {
8
+ "gpt-special": { "alias": "openai/gpt-5.6-sol" },
9
+ "house-model": { "skip": true }
10
+ }
11
+ }
12
+ },
13
+ "rules": [
14
+ {
15
+ "id": "mainfei",
16
+ "kind": "suffix",
17
+ "value": "-mainfei",
18
+ "override": { "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } }
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,6 @@
1
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ //#region src/index.d.ts
3
+ declare function modelInfoExtension(pi: ExtensionAPI): void;
4
+ //#endregion
5
+ export { modelInfoExtension as default };
6
+ //# sourceMappingURL=index.d.ts.map