@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 +21 -0
- package/README.md +187 -0
- package/config/config.example.json +21 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1715 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -0
- package/schemas/config.schema.json +543 -0
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
|
+
[](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
|
+
}
|
package/dist/index.d.ts
ADDED