@lokeraar/pi-enclave-bridge 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 Lokeraar
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
10
+ furnished 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,245 @@
1
+ # @lokeraar/pi-enclave-bridge
2
+
3
+ EnClave provider for [Pi](https://pi.dev). The live router catalog shows up in
4
+ `/model` with **real context windows, real per-token prices, live membership and
5
+ the router's task aliases** — resolved from the model catalog Pi already ships,
6
+ with no extra account required.
7
+
8
+ ## ⚡ Quick Start
9
+
10
+ ```bash
11
+ pi install npm:@lokeraar/pi-enclave-bridge
12
+ ```
13
+
14
+ Then log in once:
15
+
16
+ ```
17
+ /login EnClave
18
+ ```
19
+
20
+ The catalog builds itself. `/model` shows every model the router serves for your
21
+ key, with the values resolved and each one attributed to where it came from.
22
+
23
+ > **Local copies conflict.** If you also keep `enclave-bridge.ts` in
24
+ > `~/.pi/agent/extensions/`, remove it first. Two registrations of the same
25
+ > provider fight over the model list. `PI_ENCLAVE_LIVE=0` does not fix this; only
26
+ > removing one of them does.
27
+
28
+ ## Why this package exists
29
+
30
+ EnClave's router (`https://router.enclave.ai/v1`) is an OpenAI-compatible
31
+ gateway, and its `/models` endpoint is unusually honest: it declares the context
32
+ window, the price per million tokens, and whether *your* key has a healthy route
33
+ to each model.
34
+
35
+ It is still silent on the things a coding agent actually needs to make decisions:
36
+ which reasoning-effort values the model implements, whether it takes images, and
37
+ how much output it will really produce. Left alone, a config file fills those
38
+ with round placeholder numbers that look exactly like facts.
39
+
40
+ This package resolves them, and never invents one.
41
+
42
+ ## ⚙️ How it works
43
+
44
+ Two layers, in strict order:
45
+
46
+ ```
47
+ the vendor's own model card > openrouter > the other 41 catalogs Pi ships
48
+ what is already in models.json is the fallback, used only when no catalog
49
+ knows the model at all
50
+ ```
51
+
52
+ **The vendor's card outranks everything.** A catalog records what a *reseller*
53
+ believes a model accepts; the card records what the model *implements*. When they
54
+ disagree the catalog is usually not lying — it is describing the gateway's shape.
55
+ EnClave accepts all six effort values for `glm-5.3`; the card says the model only
56
+ implements low, high and max. The extra values are accepted and then ignored,
57
+ which is worse than not offering them: Pi would show a thinking level that
58
+ silently does nothing.
59
+
60
+ **OpenRouter leads the catalogs.** It is the largest model router in the world
61
+ and the catalog is its core business. The other catalogs Pi ships (42 of them)
62
+ confirm that a model exists and agree on its structure; they fill a field the ones
63
+ above left empty and never override.
64
+
65
+ **Nothing is averaged.** A number nobody published is not a consensus, it is an
66
+ invention. Where two sources disagree, the higher-ranked one wins and the other
67
+ is recorded as dissent.
68
+
69
+ **No credential is needed to read a catalog.** The files live in Pi's own package
70
+ (`pi-ai/dist/providers/data/`). A key is only needed to *call* an API, not to
71
+ read what Pi already installed — so a fresh Pi with no accounts anywhere still
72
+ gets the full catalog.
73
+
74
+ ### 🔎 Two refresh phases
75
+
76
+ Pi drives the refresh; the extension never invents a value in it.
77
+
78
+ | Phase | When | What it does |
79
+ |---|---|---|
80
+ | **Cache-only restore** | Every runtime creation | Rebuilds the catalog from `models.json` plus the store. Instant, works offline. |
81
+ | **Live membership** | Interactive startup, `/model` search | Fetches `/models` and adds ids the endpoint started serving, drops ids it stopped. |
82
+
83
+ Live membership only ever changes *who* is in the list. The values come from
84
+ `models.json`, which `scripts/sync-models.mjs` owns.
85
+
86
+ Kill switch: `PI_ENCLAVE_LIVE=0` freezes the catalog.
87
+
88
+ ### 🏷️ Which models get published
89
+
90
+ A model appears only if the router lists it, `routeable_endpoint_count > 0`, and
91
+ it answers a request.
92
+
93
+ Three ways a listed model can still be unusable, and all three are handled:
94
+
95
+ | Signal | What it means |
96
+ |---|---|
97
+ | listed, `routeable_endpoint_count: 0` | Your key has no healthy route. Every request 404s. |
98
+ | answers `502 "provider returned HTTP 410"` | The catalog calls it healthy; the inference provider behind it is gone. `410 Gone` is permanent, not a blip. |
99
+ | simply absent from `/models` | Retired upstream. |
100
+
101
+ The third one had a real effect: `cyberouter/remediation` is a router alias that
102
+ routes by task score, and it was failing because the top-scoring remediation
103
+ model was one of the dead ones.
104
+
105
+ ### 🔀 Router aliases
106
+
107
+ `cyberouter/auto` plus one per security task (`vuln-discovery`, `exploit-dev`,
108
+ `remediation`, `triage`) are usable as a model and are published.
109
+
110
+ They live in a sibling field of the catalog, not inside `data`, so a parser that
111
+ only reads `data` silently loses all five. Their window and price depend on which
112
+ concrete model the router picks per request, so both are bounded rather than
113
+ guessed: context at the catalog floor, price at the catalog ceiling.
114
+
115
+ They are deliberately **never** resolved from a catalog. OpenRouter has a model
116
+ called `auto` too, advertising a 2,000,000 window — a different thing that shares
117
+ the name, and a lie here.
118
+
119
+ ## 🔑 Authentication
120
+
121
+ ```
122
+ /login EnClave
123
+ ```
124
+
125
+ The key lives in `~/.pi/agent/auth.json`, managed by Pi. The catalog needs no key;
126
+ only live membership and the liveness check do.
127
+
128
+ ## 📊 Models
129
+
130
+ Values are resolved per model and written to `providers.EnClave.models` in
131
+ `models.json`. Each one records exactly where it came from:
132
+
133
+ ```json
134
+ "donor": {
135
+ "source": "openrouter",
136
+ "matchedId": "qwen/qwen3.8-max-0902",
137
+ "corroborating": ["opencode", "opencode-go", "qwen-token-plan", "…"],
138
+ "rule": "corroborated"
139
+ }
140
+ ```
141
+
142
+ `matchedId` is the **full id with its prefix**, not the short name, so a match can
143
+ be audited without guessing — including when it resolved through a dated vendor
144
+ slug.
145
+
146
+ ### What a donor may not set
147
+
148
+ | Field | Owner | Why |
149
+ |---|---|---|
150
+ | `contextWindow` | the live catalog | It states what this endpoint actually serves. |
151
+ | `cost` | the live catalog | A catalog's price is for a different reseller. |
152
+ | `compat` | never inherited | `thinkingFormat: "openrouter"` and friends describe how *OpenRouter* wants reasoning framed. EnClave speaks the OpenAI shape. |
153
+
154
+ ### The ceiling clamp
155
+
156
+ An output ceiling larger than the context can hold is not a bigger claim, it is an
157
+ impossible one — a request cannot ask for more output than the window contains,
158
+ and the endpoint says so:
159
+
160
+ > This request needs about N tokens (messages + tools + max_tokens)
161
+
162
+ A donor value is therefore clamped to the window minus a 2,048-token prompt
163
+ reserve. It cannot equal the window either: measured, 262,144 was rejected while
164
+ 261,120 passed.
165
+
166
+ A value inside the limit is used exactly as given. The clamp removes
167
+ impossibilities; it does not second-guess the catalog.
168
+
169
+ In practice the published value is a **ceiling, not a fixed request**. Pi reduces
170
+ it per turn to `min(published, contextWindow − prompt − 4096)`.
171
+
172
+ ## 🧠 Reasoning controls
173
+
174
+ Each model gets a `thinkingLevelMap`. A level mapped to `null` is not offered, so
175
+ Pi snaps to the nearest supported level instead of sending a value the gateway
176
+ refuses. `off: null` means thinking cannot be switched off and the option is
177
+ hidden entirely.
178
+
179
+ Two models carry a vendor card that narrows what the catalog claims:
180
+
181
+ | Model | Card says | Catalog claims |
182
+ |---|---|---|
183
+ | `glm-5.3` | 131 072 out · low, high, max · text only | 943 718 out · six levels |
184
+ | `glm-5.2` | 131 072 out · high, max · text only | 943 718 out · six levels |
185
+
186
+ ## ⚙️ Configuration
187
+
188
+ | Variable | Effect |
189
+ |---|---|
190
+ | `ENCLAVE_API_KEY` | Credential for the maintenance script. The extension takes it from `/login`. |
191
+ | `PI_ENCLAVE_LIVE=0` | Kill switch — freezes the catalog. |
192
+
193
+ ## 🚀 Development
194
+
195
+ ```bash
196
+ git clone https://github.com/Lokeraar/pi-EnClave-bridge
197
+ cd pi-EnClave-bridge
198
+ npm test
199
+ ```
200
+
201
+ ### Where each value comes from
202
+
203
+ The catalog Pi ships is at:
204
+
205
+ ```
206
+ <pi-ai>/dist/providers/data/<provider>.json
207
+ ```
208
+
209
+ 42 of them, keyed by API and then by model id. The directory name carries the
210
+ pi-ai version and a dependency hash, so it changes on every Pi update and any
211
+ stored path dies with it — the lookup therefore walks the tree at run time, asking
212
+ Node to resolve the copy Pi loads first and falling back to the agent's store.
213
+
214
+ Models whose id ends in `free` are skipped: they routinely ship with capabilities
215
+ cut down, so their numbers describe a reduced product.
216
+
217
+ ### Maintaining the curated values
218
+
219
+ Report-only. It never writes without you asking, and the diff is the deliverable.
220
+
221
+ ```bash
222
+ node --experimental-strip-types scripts/sync-models.mjs --dry-run
223
+ node --experimental-strip-types scripts/sync-models.mjs
224
+ ```
225
+
226
+ It reports which donor supplied each value, who corroborated it, what is still
227
+ missing, and what it excluded. Then it writes `models.json` and leaves a backup.
228
+
229
+ To add a vendor correction, add it to `VENDOR_SPEC` in `donors.ts` with the reason
230
+ it exists, so a later reader can check it against the model card.
231
+
232
+ ### Tests
233
+
234
+ ```bash
235
+ node --experimental-strip-types scripts/test-sync.mjs
236
+ ```
237
+
238
+ 58 offline checks: bare-name matching and the near miss that must not match, the
239
+ model-card precedence, the strict donor order, nothing-averaged, free-model
240
+ exclusion, dated slugs, alias exclusion, the ceiling clamp, and a simulated Pi
241
+ update that renames the catalog folder.
242
+
243
+ ## License
244
+
245
+ MIT