@gtrabanco/pi-nan-provider 0.10.3 → 1.0.1

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.md CHANGED
@@ -65,13 +65,15 @@ factory in `src/provider-factory.ts`. A second provider-specific file is a smell
65
65
  refactor back to the factory and add a config entry in `src/providers.ts` instead.
66
66
  The `factory is shared` test in `test/provider-factory.test.ts` guards this contract.
67
67
 
68
- ## Version policy (strict semver)
68
+ ## Version policy (strict semver, pi-aligned scheme)
69
69
 
70
70
  Every PR that changes code MUST bump `package.json` version in the same PR; CI publishes only when the version differs from npm.
71
71
 
72
- - **PATCH** (`0.1.z`): bug fixes, docs, comment-only changes, catalog regeneration with identical values.
73
- - **MINOR** (`0.x.0`): new features — new provider entries, new MCP tools, new env vars/config options, and (while `0.x`) breaking changes, each breaking change called out explicitly in the PR/changelog.
74
- - **MAJOR** (`x.0.0`): breaking changes once `1.0.0` is reached.
72
+ Since 1.0.0 the package version **aligns its MAJOR.MINOR with the pi release line the release is verified against** (pi 1.0.0 → package `1.0.x`; when pi ships 1.1.0, the next package release takes `1.1.0` even if it only carries fixes). The version marks the verified baseline, not the full support range — the npm `peerDependencies` range stays independent and usually wider.
73
+
74
+ - **PATCH** (`1.0.z`): bug fixes, docs, comment-only changes, catalog regeneration with identical values — the usual release cadence between pi minor releases.
75
+ - **MINOR** (`1.x.0`): reserved for tracking pi's minor releases (and may carry new features — new provider entries, new MCP tools, new env vars/config options).
76
+ - **MAJOR** (`x.0.0`): breaking changes, and re-alignment if pi ever ships a new major.
75
77
  - Never reuse a published version; never publish with failing tests (CI gates publish on tests + typecheck).
76
78
  - The npm registry is the source of truth for "published"; `.github/workflows/publish.yml` compares `package.json` against `npm view` and publishes only on difference.
77
79
 
@@ -247,23 +249,32 @@ Every PR that changes code MUST bump `package.json` version in the same PR; CI p
247
249
  - Since 0.10.0, this package uses native MCP via `pi.registerMcpServer()`.
248
250
  Both servers are session-scoped, visible in `/mcp` with source "extension".
249
251
  Server names and tool IDs:
250
- - `nan-search` → tools: `mcp__nan-search__web_search`
252
+ - `nan-search` → tools: `mcp__nan_search__web_search`
251
253
  (official remote, HTTP, `Authorization: Bearer <key>` header)
252
- - `nan-media` → tools: `mcp__nan-media__generate_image/edit_image/text_to_speech/list_voices/speech_to_text`
253
- (community stdio, `npx -y nan-mcp-server@1.1.2`, 120s timeout)
254
+ - `nan-media` → tools: `mcp__nan_media__generate_image/edit_image/text_to_speech/list_voices/speech_to_text/list_models/embed_text/rerank_documents`
255
+ (community stdio, `npx -y nan-mcp-server@1.1.2`, 120s timeout; 8 tools as of 1.1.2)
254
256
  - `exposure: "direct"` on both — tools are declared to the model.
255
257
  - User `mcp.json` entries with the same name take precedence.
256
258
  - `/nan-mcp enable/disable` calls `pi.registerMcpServer()` / `pi.unregisterMcpServer()` directly.
257
259
  - Missing `NAN_API_KEY` at load: web-search server NOT registered (console.warn).
258
260
  NaN integration facts remain:
261
+ - pi >=0.99.2 sanitizes MCP tool/namespace names by replacing every character except
262
+ `[A-Za-z0-9_]` with `"_"` (0.99.0–0.99.1 kept `-`). So `mcp__nan-search__web_search`
263
+ became `mcp__nan_search__web_search` and `mcp__nan-media__*` became
264
+ `mcp__nan_media__*`. Server names differing only in `-`/`_` now collide
265
+ (`nan-search` and `nan-media` do not collide, they differ in more than one character).
266
+ - pi 0.99.2+/1.0 accept `McpServerConfig.description` (system-prompt listing plus
267
+ tool-search ranking); `validateMcpServerConfig` in 0.99.0–0.99.1 ignores unknown
268
+ keys, so passing `description` stays compatible.
259
269
  - NaN's official remote MCP server: `https://api.nan.builders/mcp` (host
260
270
  root, NOT /v1; JSON-RPC 2.0 over streamable HTTP, stateless; same `sk-`
261
271
  key, shared rate limit/quota). Spec: https://nan.builders/openapi.json
262
272
  (tag "MCP"). Currently exposes `web_search`.
263
273
  - Community `nan-mcp-server` (https://github.com/luciferfran/nan-mcp-server):
264
274
  stdio MCP server, version-pinned via NAN_MEDIA_MCP_VERSION (default 1.1.2)
265
- or full command override via NAN_MEDIA_MCP_COMMAND. Tools: generate_image,
266
- edit_image, text_to_speech, list_voices, speech_to_text.
275
+ or full command override via NAN_MEDIA_MCP_COMMAND. Tools (1.1.2): generate_image,
276
+ edit_image, text_to_speech, list_voices, speech_to_text, list_models, embed_text,
277
+ rerank_documents.
267
278
  - Automated check (scripts/check-nan-mcp-server.ts) compares the npm registry
268
279
  against the pin weekly and files a `dependencies` issue when a newer release
269
280
  exists, with a breaking/safe verdict from the live server tool surface.
@@ -282,3 +293,51 @@ Every PR that changes code MUST bump `package.json` version in the same PR; CI p
282
293
  `mcp_servers_change` so pi's report stays silent, and warns once per session;
283
294
  `/nan-mcp enable` persists the toggle without registering. Regression tests:
284
295
  `test/mcp-host-connector.test.ts`.
296
+ - **pi 1.0.0 / pi-ai 1.0.0 compat** (verified 2026-10-01): the peer range is now
297
+ `">=0.99.0 <2"` (widened from `">=0.99.0 <1"`); the pi-ai 1.0.0 index/compat/types
298
+ type surfaces are identical to 0.99.1 (the release only adds a `./models` subpath,
299
+ Anthropic federation env consts, and a z.ai CN overflow pattern); the package's
300
+ tests and typecheck pass against 1.0.0 unchanged.
301
+ - **Native image models** (verified 2026-10-01): pi 1.0 lets a provider register
302
+ `ImageModel` entries (`type: "image"`) plus a `Provider.images` implementation map
303
+ keyed by a custom `ImageApi` id (`"nan-images"`); codemode reaches them with
304
+ `models.generateImages()` / `getModelOfType("image", provider, id)`, extensions with
305
+ `ctx.modelRegistry.generateImages()`. pi-ai never applies `filterModels` to
306
+ non-chat models, so image availability is not tier-filtered by the live `/models`
307
+ list — the 403 at call time is the tier signal. NaN endpoints and quotas are
308
+ documented in the package's image-model notes.
309
+ - **Cold-cache 524 wedge: measured practical ceiling + two-threshold classifier**
310
+ (issue #18, measured 2026-10-01 with direct cold curl probes, no pi in the
311
+ loop): api.nan.builders sits behind Cloudflare with a 120 s Proxy Read
312
+ Timeout. A COLD prompt of 208,036 tokens (~960 KB body) answered in 12.9 s;
313
+ ~220k and ~286k tokens returned HTTP 524 `origin_response_timeout` at ~126 s
314
+ (JSON body or Cloudflare HTML page). Warm (cached) requests of the same size
315
+ answer in ~10 s. pi-ai (0.99.x–1.0.0) lists "524" in
316
+ `RETRYABLE_PROVIDER_ERROR_PATTERN` (retrying an identical cold payload always
317
+ fails) and `isContextOverflow()` matches nothing → the session wedges.
318
+ `src/context-overflow-classifier.ts` therefore has TWO independent
319
+ thresholds: the 400 branch (issue #3) keys on the model's `contextWindow`;
320
+ the 524 branch keys on `NAN_COLD_CACHE_CEILING_TOKENS` = 200,000 ONLY
321
+ (conservative midpoint below the last-working 208k measurement) — the
322
+ declared catalog windows are NOT reduced. A 524 below the ceiling stays a
323
+ transient retryable error. Compaction is a single pass with no hard-truncation
324
+ fallback in pi, so the rewrite rescues sessions that one compaction can bring
325
+ under the ceiling; far-over sessions still wedge (same limit as the #3 path).
326
+ - **Stale persisted catalog shadowing (issue #19, verified on pi-ai/pi 1.0.0):**
327
+ pi-ai's `createProvider`-built `refreshModels` restores
328
+ `context.stored.models` VERBATIM in the cache-only phase
329
+ (`dynamicModels = restored`, a closure variable unreachable from outside) and
330
+ `currentModels()` lets a dynamic entry REPLACE the static baseline entry with
331
+ the same id+type — so a `models-store.json` entry written by an older package
332
+ version (e.g. without `thinkingLevelMap`) shadows the newer generated
333
+ capability data whenever its `checkedAt` is fresh; `ModelsStoreEntry` has no
334
+ version/staleness signal. pi's `ModelsImpl` resolves models exclusively via
335
+ the provider object's `getModels()`/`getAllModels()`. Fix: `src/provider-factory.ts`
336
+ wraps both accessors (`withStaleCatalogOverlay`) to apply generated-catalog
337
+ capability data at READ time (chat ids from the generated catalog, image ids
338
+ from `baselineImageModels`) — the store decides which ids exist, the
339
+ generated catalog decides capability data; uncatalogued ids (live-only
340
+ `glm5.3`, ghosts) pass through untouched. `refreshModels` is NOT overridden:
341
+ the network phase already persists correct merged data. Option (a) of the
342
+ reporter (version-stamping persisted entries) is NOT implementable
343
+ provider-side: the store write path belongs to pi-ai.
package/README.es.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @gtrabanco/pi-nan-provider
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
- [![Version](https://img.shields.io/badge/version-0.10.0-blue)](https://github.com/gtrabanco/pi-nan-provider/releases)
4
+ [![Version](https://img.shields.io/badge/version-0.11.0-blue)](https://github.com/gtrabanco/pi-nan-provider/releases)
5
5
 
6
6
  [NaN Builders](https://nan.builders) model provider + MCP nativo para [pi](https://github.com/earendil-works/pi).
7
7
 
@@ -101,10 +101,13 @@ Consigue una clave en la [plataforma NaN](https://cloud.nan.builders/r/7GK06FX8)
101
101
 
102
102
  ## 🔌 MCP nativo
103
103
 
104
- Dado que [pi 0.99.0 incluye un cliente MCP integrado](https://github.com/earendil-works/pi/blob/main/docs/usage.md), este paquete migra sus puentes a MCP nativo (requiere `pi >=0.99` — cambio rotundo).
104
+ Dado que [pi 0.99.0 incluye un cliente MCP integrado](https://github.com/earendil-works/pi/blob/main/docs/usage.md), este paquete migra sus puentes a MCP nativo (requiere `pi >=0.99, <2` — pi 0.99 hasta 1.0 son compatibles; el rango peer se amplió a `<2` en 0.11.0).
105
105
 
106
106
  Ambos puentes están **activados por defecto** (por sesión, visibles en `/mcp`). Usa `/nan-mcp` para gestionarlos.
107
107
 
108
+ > [!TIP]
109
+ > ¿Prefieres configurar los servidores MCP de NaN tú mismo? Desde pi 0.99.2/1.0, un servidor MCP HTTP en el `mcp.json` global puede autenticarse con tu token de `/login nan` en vez de una clave copiada: `{ "auth": { "provider": "nan" } }` (solo en el `mcp.json` global; requiere `https`). El registro integrado de `nan-search` sigue usando `NAN_API_KEY`/la credencial almacenada.
110
+
108
111
  ### 🛠️ Comando de Gestión: `/nan-mcp`
109
112
 
110
113
  | Comando | Efecto |
@@ -132,7 +135,7 @@ con un único aviso por sesión, mantiene los registros inactivos y deja que `/n
132
135
  el toggle sin registrar un servidor que nunca podrá conectarse. Para tener MCP en la CLI, activa el
133
136
  conector con `pi config` → Built-in extensions → `mcp`.
134
137
 
135
- ### Funciones de pi 0.99 y modelos NaN
138
+ ### Funciones de pi 0.99-1.0 y modelos NaN
136
139
 
137
140
  - **Modelos virtuales** — registra un router bajo `nan` (`pi.registerVirtualModel({ provider: "nan", ... })`);
138
141
  enruta a los modelos físicos de NaN sin configuración por parte del proveedor. El guard de
@@ -145,12 +148,36 @@ conector con `pi config` → Built-in extensions → `mcp`.
145
148
  endpoints de embedding/rerank/audio/imagen), así que este proveedor no registra modelos
146
149
  `type: "classifier"`.
147
150
 
151
+ - **Modelos de imagen nativos de NaN** — flux-2-klein (text-to-image + image-to-image) y
152
+ qwen-image-2.1 (text-to-image) se registran como modelos de imagen de pi (`type: "image"`,
153
+ api `"nan-images"`). No aparecen en `/model`; se acceden desde codemode
154
+ con `models.generateImages(model, { input })` o desde extensiones con
155
+ `ctx.modelRegistry.generateImages()`:
156
+
157
+ ```
158
+ const painter = await models.getModelOfType("image", "nan", "flux-2-klein")
159
+ const result = await models.generateImages(painter, { input: [{ type: "text", text: "A red fox in the snow, watercolor" }] })
160
+ if (result.stopReason !== "stop") return result.errorMessage
161
+ for (const block of result.output) if (block.type === "image") image(block)
162
+ ```
163
+
164
+ Ambos modelos necesitan el tier de membresía "inference" (403 de lo contrario); los endpoints
165
+ de imagen tienen su propio límite de tasa separado del chat (20 peticiones/min, 100
166
+ peticiones/mes compartido entre los dos modelos) y no consumen el presupuesto de tokens del
167
+ chat. [docs de NaN](https://nan.builders/docs/models) y [OpenAPI](https://nan.builders/openapi.json)
168
+ (comprobado 2026-10-01). El servidor MCP nan-media de la comunidad ya ofrece la misma
169
+ generación y edición como herramientas MCP, mientras que los modelos de imagen nativos permiten
170
+ que codemode y las extensiones los llamen directamente.
171
+
148
172
  ---
149
173
 
150
174
  ### 1. Servidor MCP oficial de NaN
151
175
  *Puente oficial para herramientas remotas vía [https://api.nan.builders/mcp](https://nan.builders/docs/api).*
152
176
 
153
- - **`mcp__nan-search__web_search(query, ...)`**: Realiza búsquedas web a través del gateway de NaN.
177
+ - **`mcp__nan_search__web_search(query, ...)`**: Realiza búsquedas web a través del gateway de NaN.
178
+ > pi 0.99.0–0.99.1 usaba la forma con guiones (`mcp__nan-search__web_search`); pi 0.99.2+ sanitiza
179
+ > los nombres de herramientas a solo `[A-Za-z0-9_]`, así que los guiones se convirtieron en
180
+ > guiones bajos. La forma con guiones bajos aplica desde 0.99.2 hasta pi 1.0.
154
181
 
155
182
  ### 2. Servidor MCP de Media (Comunidad)
156
183
  *Conecta [`nan-mcp-server`](https://github.com/luciferfran/nan-mcp-server) mediante un cliente stdio local mínimo.*
@@ -160,11 +187,19 @@ conector con `pi config` → Built-in extensions → `mcp`.
160
187
 
161
188
  | Herramienta | Propósito |
162
189
  | :--- | :--- |
163
- | `mcp__nan-media__generate_image` | Generación de imágenes (flux-2-klein) |
164
- | `mcp__nan-media__edit_image` | Edición imagen→imagen (flux-2-klein) |
165
- | `mcp__nan-media__text_to_speech` | Síntesis de audio (kokoro) |
166
- | `mcp__nan-media__list_voices` | Listar voces disponibles |
167
- | `mcp__nan-media__speech_to_text` | Transcripción de audio (whisper) |
190
+ | `mcp__nan_media__generate_image` | Generación de imágenes (flux-2-klein) |
191
+ | `mcp__nan_media__edit_image` | Edición imagen→imagen (flux-2-klein) |
192
+ | `mcp__nan_media__text_to_speech` | Síntesis de audio (kokoro) |
193
+ | `mcp__nan_media__list_voices` | Listar voces disponibles |
194
+ | `mcp__nan_media__speech_to_text` | Transcripción de audio (whisper) |
195
+ | `mcp__nan_media__list_models` | Listar los ids de modelos que alcanza la key |
196
+ | `mcp__nan_media__embed_text` | Embeddings de texto (qwen3-embedding) |
197
+ | `mcp__nan_media__rerank_documents` | Reordenación de documentos (Qwen3-Reranker) |
198
+
199
+ > pi 0.99.0–0.99.1 usaba las formas con guiones (`mcp__nan-search__web_search`,
200
+ > `mcp__nan-media__*`); pi 0.99.2+ sanitiza los nombres de herramientas a solo
201
+ > `[A-Za-z0-9_]`, así que los guiones se convirtieron en guiones bajos. La forma
202
+ > con guiones bajos aplica desde 0.99.2 hasta pi 1.0.
168
203
 
169
204
  #### 🔧 Configuración del Puente de Media
170
205
 
@@ -289,6 +324,8 @@ Los errores siguen siendo accionables: `401` → ejecuta `/login nan` o corrige
289
324
 
290
325
  Catálogo base (verificado contra [docs de NaN](https://nan.builders/docs/models) y [OpenAPI](https://nan.builders/openapi.json)).
291
326
 
327
+ Modelos de chat:
328
+
292
329
  | Modelo | Contexto | Máx. Salida | Entrada | Razonamiento |
293
330
  | :--- | :--- | :--- | :--- | :---: |
294
331
  | `qwen3.6` | 262,144 | 65,536 | texto, imagen | ✅ |
@@ -298,6 +335,17 @@ Catálogo base (verificado contra [docs de NaN](https://nan.builders/docs/models
298
335
  | `glm5.3-flash` | 1,000,000 | 131,072 | texto, imagen | ✅ |
299
336
  | `qwen3.8-flash` | 262,144 | 131,072 | texto, imagen | ✅ |
300
337
 
338
+ > [!NOTE]
339
+ > **Techo práctico con caché de prompt fría (issue #18).** Las ventanas de contexto de la tabla son las ventanas reales de los modelos, pero el borde de api.nan.builders (Cloudflare) aborta las respuestas que superan su Proxy Read Timeout de 120 s. Medido con peticiones frías directas (relleno único, sin caché de prompt; 2026-10-01): 208.036 tokens (~960 KB de cuerpo) respondió en 12,9 s mientras que ~220k y ~286k tokens devolvieron **HTTP 524 `origin_response_timeout`** a los ~126 s; los mismos tamaños con caché cálida responden en ~10 s. El techo práctico para peticiones frías queda por tanto muy por debajo de las ventanas declaradas. Desde 1.0.0 el provider reclasifica un 524 en una petición estimada por encima de 200.000 tokens (`NAN_COLD_CACHE_CEILING_TOKENS`) como desbordamiento de contexto para que pi compacte y reintente en vez de quedarse colgado; un 524 en una petición menor se deja intacto (timeout transitorio genuino).
340
+
341
+ Modelos de imagen no-chat (disponibles vía `models.generateImages()` /
342
+ `getModelOfType("image", …)`, no a través de `/model`):
343
+
344
+ | Modelo | Entrada | Propósito |
345
+ | :--- | :--- | :--- |
346
+ | `flux-2-klein` | texto, imagen | Text-to-image + image-to-image |
347
+ | `qwen-image-2.1` | texto | Text-to-image |
348
+
301
349
  > [!NOTE]
302
350
  > `mimo-v2.6-flash` está servido por NaN y desde el 2026-09-29 aparece también en models.dev (proveedor `nan`); la entrada manual que lo sostenía se conserva como fallback y su nota de procedencia sigue unida a la entrada generada.
303
351
  >
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @gtrabanco/pi-nan-provider
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
- [![Version](https://img.shields.io/badge/version-0.10.0-blue)](https://github.com/gtrabanco/pi-nan-provider/releases)
4
+ [![Version](https://img.shields.io/badge/version-0.11.0-blue)](https://github.com/gtrabanco/pi-nan-provider/releases)
5
5
 
6
6
  [NaN Builders](https://nan.builders) model provider + native MCP servers for [pi](https://github.com/earendil-works/pi).
7
7
 
@@ -101,10 +101,13 @@ Get a key from the [NaN platform](https://cloud.nan.builders/r/7GK06FX8) (user s
101
101
 
102
102
  ## 🔌 Native MCP Servers
103
103
 
104
- Since [pi 0.99.0 ships a built-in MCP client](https://github.com/earendil-works/pi/blob/main/docs/usage.md), this package migrates its bridges to native MCP (requires `pi >=0.99` — breaking).
104
+ Since [pi 0.99.0 ships a built-in MCP client](https://github.com/earendil-works/pi/blob/main/docs/usage.md), this package migrates its bridges to native MCP (requires `pi >=0.99, <2` — pi 0.99 through 1.0 are supported; the peer range was widened to `<2` in 0.11.0).
105
105
 
106
106
  Both bridges are **enabled by default** (session-scoped, visible in `/mcp`). Use `/nan-mcp` to manage them.
107
107
 
108
+ > [!TIP]
109
+ > Prefer to configure NaN's MCP servers yourself? Since pi 0.99.2/1.0 an HTTP MCP server in the global `mcp.json` can authenticate with your `/login nan` token instead of a copied key: `{ "auth": { "provider": "nan" } }` (global `mcp.json` only; `https` required). The bundled `nan-search` registration keeps using `NAN_API_KEY`/the stored credential.
110
+
108
111
  ### 🛠️ Management Command: `/nan-mcp`
109
112
 
110
113
  | Command | Effect |
@@ -131,7 +134,7 @@ per session, keeps the registrations idle, and lets `/nan-mcp enable` persist th
131
134
  registering a server that can never connect. To get MCP in the CLI, enable the connector with
132
135
  `pi config` → Built-in extensions → `mcp`.
133
136
 
134
- ### pi 0.99 features and NaN models
137
+ ### pi 0.99-1.0 features and NaN models
135
138
 
136
139
  - **Virtual models** — register a router under `nan` (`pi.registerVirtualModel({ provider: "nan", ... })`);
137
140
  it routes to the physical NaN models with no provider-side configuration. The cross-model thinking
@@ -142,12 +145,36 @@ registering a server that can never connect. To get MCP in the CLI, enable the c
142
145
  - **Classifier models** — not applicable: NaN exposes no classifier API (chat plus
143
146
  embedding/rerank/audio/image endpoints only), so this provider registers no `type: "classifier"` models.
144
147
 
148
+ - **Native NaN image models** — flux-2-klein (text-to-image + image-to-image) and
149
+ qwen-image-2.1 (text-to-image) are registered as pi image models (`type: "image"`,
150
+ api `"nan-images"`). They do not appear in `/model`; they are reached through codemode
151
+ with `models.generateImages(model, { input })` or from extensions with
152
+ `ctx.modelRegistry.generateImages()`:
153
+
154
+ ```
155
+ const painter = await models.getModelOfType("image", "nan", "flux-2-klein")
156
+ const result = await models.generateImages(painter, { input: [{ type: "text", text: "A red fox in the snow, watercolor" }] })
157
+ if (result.stopReason !== "stop") return result.errorMessage
158
+ for (const block of result.output) if (block.type === "image") image(block)
159
+ ```
160
+
161
+ Both models need the "inference" membership tier (403 otherwise); the image endpoints
162
+ are rate-limited separately from chat (20 requests/min, 100 requests/month shared
163
+ between the two models) and do not consume the chat token budget.
164
+ [NaN docs](https://nan.builders/docs/models) and [OpenAPI](https://nan.builders/openapi.json)
165
+ (checked 2026-10-01). The community nan-media MCP server already offers the same
166
+ generation and editing as MCP tools, while the native image models let codemode and
167
+ extensions call them directly.
168
+
145
169
  ---
146
170
 
147
171
  ### 1. Official NaN MCP Server
148
172
  *Official remote MCP server registered natively via `pi.registerMcpServer()`.*
149
173
 
150
- - **`mcp__nan-search__web_search(query, ...)`**: Performs web searches through NaN's gateway.
174
+ - **`mcp__nan_search__web_search(query, ...)`**: Performs web searches through NaN's gateway.
175
+ > pi 0.99.0–0.99.1 used the hyphenated form (`mcp__nan-search__web_search`); pi 0.99.2+ sanitizes
176
+ > tool names to `[A-Za-z0-9_]` only, so hyphens became underscores. The underscore form applies
177
+ > from 0.99.2 onward through pi 1.0.
151
178
 
152
179
  ### 2. Community Media MCP Server
153
180
  *Bridges [`nan-mcp-server`](https://github.com/luciferfran/nan-mcp-server) via a minimal local stdio client.*
@@ -157,11 +184,19 @@ registering a server that can never connect. To get MCP in the CLI, enable the c
157
184
 
158
185
  | Tool | Purpose |
159
186
  | :--- | :--- |
160
- | `mcp__nan-media__generate_image` | Image generation (flux-2-klein) |
161
- | `mcp__nan-media__edit_image` | Image-to-image editing (flux-2-klein) |
162
- | `mcp__nan-media__text_to_speech` | Audio synthesis (kokoro) |
163
- | `mcp__nan-media__list_voices` | List available voices |
164
- | `mcp__nan-media__speech_to_text` | Audio transcription (whisper) |
187
+ | `mcp__nan_media__generate_image` | Image generation (flux-2-klein) |
188
+ | `mcp__nan_media__edit_image` | Image-to-image editing (flux-2-klein) |
189
+ | `mcp__nan_media__text_to_speech` | Audio synthesis (kokoro) |
190
+ | `mcp__nan_media__list_voices` | List available voices |
191
+ | `mcp__nan_media__speech_to_text` | Audio transcription (whisper) |
192
+ | `mcp__nan_media__list_models` | List the NaN model ids the key can reach |
193
+ | `mcp__nan_media__embed_text` | Text embeddings (qwen3-embedding) |
194
+ | `mcp__nan_media__rerank_documents` | Document reranking (Qwen3-Reranker) |
195
+
196
+ > pi 0.99.0–0.99.1 used the hyphenated forms (`mcp__nan-search__web_search`,
197
+ > `mcp__nan-media__*`); pi 0.99.2+ sanitizes tool names to `[A-Za-z0-9_]` only,
198
+ > so hyphens became underscores. The underscore form applies from 0.99.2 onward
199
+ > through pi 1.0.
165
200
 
166
201
  #### 🔧 Media Bridge Configuration
167
202
 
@@ -286,15 +321,30 @@ Failures stay actionable: `401` → run `/login nan` or fix `NAN_API_KEY`, `404`
286
321
 
287
322
  Baseline catalog (verified against [NaN docs](https://nan.builders/docs/models) and [OpenAPI](https://nan.builders/openapi.json)).
288
323
 
324
+ Chat models:
325
+
289
326
  | Model | Context | Max Output | Input | Reasoning |
290
327
  | :--- | :--- | :--- | :--- | :---: |
291
328
  | `qwen3.6` | 262,144 | 65,536 | text, image | ✅ |
292
329
  | `gemma4` | 262,144 | 32,768 | text, image | ✅ |
293
- | `deepseek-v4-flash` | 1,000,000 | 384,000 | text, image | ✅ |
330
+ | `deepseek-v4-flash` | 1,000,000 | 32,768* | text, image | ✅ |
294
331
  | `mimo-v2.6-flash` | 1,048,576 | 131,072 | text, image | ✅ |
295
332
  | `glm5.3-flash` | 1,000,000 | 131,072 | text, image | ✅ |
296
333
  | `qwen3.8-flash` | 262,144 | 131,072 | text, image | ✅ |
297
334
 
335
+ > [!NOTE]
336
+ > `*` deepseek-v4-flash max output 32,768 is the ceiling from the official NaN pi docs (https://nan.builders/docs/pi, "the same set the NaN CLI writes", issue #20, 2026-10-02); models.dev still lists 384,000 but the gateway rejects `max_tokens` above ~256k with 403.
337
+ >
338
+ > **Cold prompt cache practical ceiling (issue #18).** The context windows above are the models' real windows, but the api.nan.builders edge (Cloudflare) aborts responses past its 120 s Proxy Read Timeout. Measured with direct cold requests (unique filler, no prompt cache; 2026-10-01): 208,036 tokens (~960 KB body) answered in 12.9 s while ~220k and ~286k tokens returned **HTTP 524 `origin_response_timeout`** at ~126 s; the same sizes with a warm prompt cache answer in ~10 s. The practical ceiling for cold requests is therefore well below the declared windows. Since 1.0.0 the provider reclassifies a 524 on an estimated over-200,000-token request (`NAN_COLD_CACHE_CEILING_TOKENS`) as a context overflow so pi compacts and retries instead of wedging; a 524 on a smaller request is left alone (genuine transient timeout).
339
+
340
+ Non-chat image models (available through `models.generateImages()` /
341
+ `getModelOfType("image", …)`, not through `/model`):
342
+
343
+ | Model | Input | Purpose |
344
+ | :--- | :--- | :--- |
345
+ | `flux-2-klein` | text, image | Text-to-image + image-to-image |
346
+ | `qwen-image-2.1` | text | Text-to-image |
347
+
298
348
  > [!NOTE]
299
349
  > `mimo-v2.6-flash` is served by NaN and has been listed by models.dev provider `nan` since 2026-09-29. Before that it entered the catalog through a manual-only entry; that entry is kept as a fallback (re-emitted automatically if models.dev drops the model again) and its provenance note stays attached to the generated entry.
300
350
  >
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtrabanco/pi-nan-provider",
3
- "version": "0.10.3",
3
+ "version": "1.0.1",
4
4
  "description": "NaN Builders (api.nan.builders) model provider for pi - OpenAI-compatible registration with a models.dev-generated fallback, tier-aware live catalog, and MCP bridges (official web search + optional community media server)",
5
5
  "keywords": [
6
6
  "pi",
@@ -42,12 +42,12 @@
42
42
  "check-pi-sdk-versions": "node scripts/check-pi-sdk-versions.mjs --report"
43
43
  },
44
44
  "peerDependencies": {
45
- "@earendil-works/pi-ai": ">=0.99.0 <1",
46
- "@earendil-works/pi-coding-agent": ">=0.99.0 <1"
45
+ "@earendil-works/pi-ai": ">=0.99.0 <2",
46
+ "@earendil-works/pi-coding-agent": ">=0.99.0 <2"
47
47
  },
48
48
  "devDependencies": {
49
- "@earendil-works/pi-ai": "0.99.1",
50
- "@earendil-works/pi-coding-agent": "0.99.1",
49
+ "@earendil-works/pi-ai": "1.0.0",
50
+ "@earendil-works/pi-coding-agent": "1.0.0",
51
51
  "@types/bun": "1.4",
52
52
  "typescript": "7"
53
53
  },
@@ -141,10 +141,12 @@ export const MANUAL_ONLY_MODELS: Record<string, ManualModelOverride> = {
141
141
  export const MANUAL_OVERRIDES: Record<string, ManualModelOverride> = {
142
142
  "deepseek-v4-flash": {
143
143
  input: ["text", "image"],
144
+ maxTokens: 32_768,
144
145
  reasoningEffortValues: ["none"],
145
146
  thinkingLevelMap: { off: "none" },
146
147
  note:
147
148
  "input includes image: NaN serves the Vision-Exp variant ('takes images as input', https://nan.builders/docs/models; the image_url content-parts in https://nan.builders/openapi.json list deepseek-v4-flash among the vision models). models.dev provider nan also lists text+image now (DeepSeek V4.1 Flash entry, checked 2026-09-13; its 2026-09-07 snapshot listed text only), so this override is kept as a pin for the vision capability rather than as a divergence. " +
149
+ "maxTokens 32768 per the official NaN pi docs (https://nan.builders/docs/pi, 'the same set the NaN CLI writes', checked 2026-10-02) closing issue #20 — the gateway now rejects max_tokens above ~256k for this model with 403 permission_error (measured 2026-10-02: 256,000 → 200; 262,143 → 403), and the previous models.dev-derived 384000 made every pi request fail since pi-ai sends max_tokens = model.maxTokens when no cap is set. models.dev still lists 384000, so this override pins the documented value. " +
148
150
  "reasoning_effort_values=[\"none\"] (issue #16, measured 2026-09-27 against live gateway, repro included in the issue): only \"none\" deterministically disables reasoning (0 reasoning tokens, answer produced). Every other value leaves 13-14K reasoning tokens and causes runaway reasoning (finish_reason:\"length\" with ZERO answer text). The catalog declares only [\"none\"] because the remaining values are useless noise. " +
149
151
  "thinkingLevelMap { off: \"none\" } (issue #16, measured 2026-09-27): when pi maps reasoning:\"off\" to reasoningEffort:undefined, the thinkingLevelMap.off=\"none\" supplies the effort so the gateway receives reasoning_effort:\"none\" and does NOT enter runaway reasoning mode.",
150
152
  },
@@ -1,7 +1,7 @@
1
1
  // This file is auto-generated by scripts/generate-models.ts
2
2
  // Do not edit manually — run `bun run generate-models` to update.
3
3
  //
4
- // Source: https://models.dev/api.json (provider "nan"), fetched 2026-10-01T10:56:14.717Z
4
+ // Source: https://models.dev/api.json (provider "nan"), fetched 2026-10-02T22:53:40.291Z
5
5
  // Provenance: every contextWindow/maxTokens/input/cost value traces to
6
6
  // models.dev or to the per-entry notes below. Nothing is invented; entries
7
7
  // models.dev documents incompletely are omitted and flagged instead.
@@ -27,7 +27,7 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
27
27
  "cacheWrite": 0
28
28
  },
29
29
  "contextWindow": 1000000,
30
- "maxTokens": 384000,
30
+ "maxTokens": 32768,
31
31
  "reasoningEffortValues": [
32
32
  "none"
33
33
  ],
@@ -43,7 +43,7 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
43
43
  },
44
44
  "notes": [
45
45
  "compat matches the maintainer's working ~/.pi/agent/models.json LiteLLM config for api.nan.builders (2026-09-04): supportsDeveloperRole false, supportsReasoningEffort true, maxTokensField max_tokens. NaN's docs example sets only supportsDeveloperRole: true and is not battle-tested. supportsFinishReason true (2026-09-13, issue #2): the LiteLLM gateway intermittently closes SSE streams before emitting finish_reason; with true pi-ai raises 'Stream ended without finish_reason', which matches pi-ai's retryable-provider pattern ('ended without') and is retried automatically, whereas false silently synthesized stop/toolUse and stalled the turn mid-answer. supportsUsageInStreaming true (2026-09-16, issue #7): NaN's published schema is silent about stream_options, but the live gateway honors it — two identical streaming calls per model, differing only in stream_options: { include_usage: true }, returned 0 usage chunks without it and exactly 1 with it (prompt/completion/reasoning/cached token counts) on deepseek-v4-flash, glm5.3-flash, qwen3.6, mimo-v2.5 and gemma4, and a real pi session then recorded token counts where it recorded zeros. pi-ai only sends stream_options when this is not false, and the sanitizer forwards it when the model declares true, so chat models opt in by default and usage is reported (issue #4). A model that does not report streaming usage can still opt out per model with a models.json compat override (supportsUsageInStreaming: false); the sanitizer then strips stream_options and the payload stays strict.",
46
- "input includes image: NaN serves the Vision-Exp variant ('takes images as input', https://nan.builders/docs/models; the image_url content-parts in https://nan.builders/openapi.json list deepseek-v4-flash among the vision models). models.dev provider nan also lists text+image now (DeepSeek V4.1 Flash entry, checked 2026-09-13; its 2026-09-07 snapshot listed text only), so this override is kept as a pin for the vision capability rather than as a divergence. reasoning_effort_values=[\"none\"] (issue #16, measured 2026-09-27 against live gateway, repro included in the issue): only \"none\" deterministically disables reasoning (0 reasoning tokens, answer produced). Every other value leaves 13-14K reasoning tokens and causes runaway reasoning (finish_reason:\"length\" with ZERO answer text). The catalog declares only [\"none\"] because the remaining values are useless noise. thinkingLevelMap { off: \"none\" } (issue #16, measured 2026-09-27): when pi maps reasoning:\"off\" to reasoningEffort:undefined, the thinkingLevelMap.off=\"none\" supplies the effort so the gateway receives reasoning_effort:\"none\" and does NOT enter runaway reasoning mode."
46
+ "input includes image: NaN serves the Vision-Exp variant ('takes images as input', https://nan.builders/docs/models; the image_url content-parts in https://nan.builders/openapi.json list deepseek-v4-flash among the vision models). models.dev provider nan also lists text+image now (DeepSeek V4.1 Flash entry, checked 2026-09-13; its 2026-09-07 snapshot listed text only), so this override is kept as a pin for the vision capability rather than as a divergence. maxTokens 32768 per the official NaN pi docs (https://nan.builders/docs/pi, 'the same set the NaN CLI writes', checked 2026-10-02) closing issue #20 — the gateway now rejects max_tokens above ~256k for this model with 403 permission_error (measured 2026-10-02: 256,000 → 200; 262,143 → 403), and the previous models.dev-derived 384000 made every pi request fail since pi-ai sends max_tokens = model.maxTokens when no cap is set. models.dev still lists 384000, so this override pins the documented value. reasoning_effort_values=[\"none\"] (issue #16, measured 2026-09-27 against live gateway, repro included in the issue): only \"none\" deterministically disables reasoning (0 reasoning tokens, answer produced). Every other value leaves 13-14K reasoning tokens and causes runaway reasoning (finish_reason:\"length\" with ZERO answer text). The catalog declares only [\"none\"] because the remaining values are useless noise. thinkingLevelMap { off: \"none\" } (issue #16, measured 2026-09-27): when pi maps reasoning:\"off\" to reasoningEffort:undefined, the thinkingLevelMap.off=\"none\" supplies the effort so the gateway receives reasoning_effort:\"none\" and does NOT enter runaway reasoning mode."
47
47
  ],
48
48
  "extras": {
49
49
  "id": "deepseek-v4-flash",
@@ -455,7 +455,7 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
455
455
  export const GENERATED_CATALOG_META = {
456
456
  source: "https://models.dev/api.json",
457
457
  modelsDevProvider: "nan",
458
- fetchedAt: "2026-10-01T10:56:14.717Z",
458
+ fetchedAt: "2026-10-02T22:53:40.291Z",
459
459
  modelCount: 6,
460
460
  models: ["deepseek-v4-flash","gemma4","glm5.3-flash","mimo-v2.6-flash","qwen3.6","qwen3.8-flash"],
461
461
  notes: [
@@ -469,7 +469,7 @@ export const GENERATED_CATALOG_META = {
469
469
  "non-chat: \"whisper\" excluded from the chat catalog (speech-to-text (STT) endpoint (https://nan.builders/docs/models, checked 2026-10-01) — not a chat model)",
470
470
  "non-chat: \"flux-2-klein\" excluded from the chat catalog (image generation endpoint (https://nan.builders/docs/models, checked 2026-10-01) — not a chat model)",
471
471
  "compat matches the maintainer's working ~/.pi/agent/models.json LiteLLM config for api.nan.builders (2026-09-04): supportsDeveloperRole false, supportsReasoningEffort true, maxTokensField max_tokens. NaN's docs example sets only supportsDeveloperRole: true and is not battle-tested. supportsFinishReason true (2026-09-13, issue #2): the LiteLLM gateway intermittently closes SSE streams before emitting finish_reason; with true pi-ai raises 'Stream ended without finish_reason', which matches pi-ai's retryable-provider pattern ('ended without') and is retried automatically, whereas false silently synthesized stop/toolUse and stalled the turn mid-answer. supportsUsageInStreaming true (2026-09-16, issue #7): NaN's published schema is silent about stream_options, but the live gateway honors it — two identical streaming calls per model, differing only in stream_options: { include_usage: true }, returned 0 usage chunks without it and exactly 1 with it (prompt/completion/reasoning/cached token counts) on deepseek-v4-flash, glm5.3-flash, qwen3.6, mimo-v2.5 and gemma4, and a real pi session then recorded token counts where it recorded zeros. pi-ai only sends stream_options when this is not false, and the sanitizer forwards it when the model declares true, so chat models opt in by default and usage is reported (issue #4). A model that does not report streaming usage can still opt out per model with a models.json compat override (supportsUsageInStreaming: false); the sanitizer then strips stream_options and the payload stays strict.",
472
- "input includes image: NaN serves the Vision-Exp variant ('takes images as input', https://nan.builders/docs/models; the image_url content-parts in https://nan.builders/openapi.json list deepseek-v4-flash among the vision models). models.dev provider nan also lists text+image now (DeepSeek V4.1 Flash entry, checked 2026-09-13; its 2026-09-07 snapshot listed text only), so this override is kept as a pin for the vision capability rather than as a divergence. reasoning_effort_values=[\"none\"] (issue #16, measured 2026-09-27 against live gateway, repro included in the issue): only \"none\" deterministically disables reasoning (0 reasoning tokens, answer produced). Every other value leaves 13-14K reasoning tokens and causes runaway reasoning (finish_reason:\"length\" with ZERO answer text). The catalog declares only [\"none\"] because the remaining values are useless noise. thinkingLevelMap { off: \"none\" } (issue #16, measured 2026-09-27): when pi maps reasoning:\"off\" to reasoningEffort:undefined, the thinkingLevelMap.off=\"none\" supplies the effort so the gateway receives reasoning_effort:\"none\" and does NOT enter runaway reasoning mode.",
472
+ "input includes image: NaN serves the Vision-Exp variant ('takes images as input', https://nan.builders/docs/models; the image_url content-parts in https://nan.builders/openapi.json list deepseek-v4-flash among the vision models). models.dev provider nan also lists text+image now (DeepSeek V4.1 Flash entry, checked 2026-09-13; its 2026-09-07 snapshot listed text only), so this override is kept as a pin for the vision capability rather than as a divergence. maxTokens 32768 per the official NaN pi docs (https://nan.builders/docs/pi, 'the same set the NaN CLI writes', checked 2026-10-02) closing issue #20 — the gateway now rejects max_tokens above ~256k for this model with 403 permission_error (measured 2026-10-02: 256,000 → 200; 262,143 → 403), and the previous models.dev-derived 384000 made every pi request fail since pi-ai sends max_tokens = model.maxTokens when no cap is set. models.dev still lists 384000, so this override pins the documented value. reasoning_effort_values=[\"none\"] (issue #16, measured 2026-09-27 against live gateway, repro included in the issue): only \"none\" deterministically disables reasoning (0 reasoning tokens, answer produced). Every other value leaves 13-14K reasoning tokens and causes runaway reasoning (finish_reason:\"length\" with ZERO answer text). The catalog declares only [\"none\"] because the remaining values are useless noise. thinkingLevelMap { off: \"none\" } (issue #16, measured 2026-09-27): when pi maps reasoning:\"off\" to reasoningEffort:undefined, the thinkingLevelMap.off=\"none\" supplies the effort so the gateway receives reasoning_effort:\"none\" and does NOT enter runaway reasoning mode.",
473
473
  "thinkingLevelMap { off: \"none\" } (issue #16): NaN docs state that with no reasoning_effort parameter gemma4 defaults to reasoning ON (16,384-token budget), while `none`/`minimal` skip reasoning entirely (https://nan.builders/docs/models#controlling-reasoning, checked 2026-10-01). pi maps reasoning:\"off\" to reasoningEffort:undefined, so without this map `thinking: off` sent no parameter and the model reasoned anyway; the map sends reasoning_effort:\"none\" and actually disables reasoning.",
474
474
  "thinkingLevelMap { off: \"minimal\" } (issue #16, measured 2026-09-27 against live gateway, repro included in the issue): when pi maps reasoning:\"off\" to reasoningEffort:undefined, the thinkingLevelMap.off=\"minimal\" supplies the effort so the gateway receives reasoning_effort:\"minimal\" (38 reasoning tokens + answer). Note: \"none\" does NOT suppress reasoning on glm5.3-flash (13,382 tokens) — only \"minimal\" was measured to be effective. reasoning_effort_values includes \"minimal\" (not documented by NaN docs but accepted and effective by the gateway, issue #16).",
475
475
  "manual-only: \"mimo-v2.6-flash\" skipped: models.dev provider \"nan\" now lists it, so the manual-only entry is not emitted and the models.dev values are used (omnimodal model (text, image, audio input) served by NaN; limits and modalities from https://nan.builders/docs/models#mimo-v2-6-flash (checked 2026-09-25, quotas re-checked 2026-09-29): 1,048,576 context / 131,072 max output / 1.0B monthly quota per member. Reasoning: accepted, depth not adjustable (docs table, checked 2026-09-29). Tool calling: yes. Streaming: yes. Input modalities (pi-representable): text, image (audio not representable in pi's Model type). Fallback entry: emitted only while models.dev provider nan does not list the model — models.dev started listing it on 2026-09-29, so the generator then keeps the models.dev values and attaches this note to that entry instead of emitting a second one.)",
package/src/commands.ts CHANGED
@@ -3,9 +3,11 @@
3
3
  * package registers:
4
4
  *
5
5
  * - `nan-search` — the official NaN remote MCP server (api.nan.builders/mcp),
6
- * exposing `mcp__nan-search__web_search`. Default: enabled.
6
+ * exposing `mcp__nan_search__web_search` (pi >=0.99.2). Default: enabled.
7
+ * (pi 0.99.0–0.99.1 used the hyphenated form `mcp__nan-search__web_search`.)
7
8
  * - `nan-media` — the community stdio media server (flux-2-klein / kokoro /
8
- * whisper), exposing `mcp__nan-media__generate_image` etc. Default: enabled.
9
+ * whisper), exposing `mcp__nan_media__generate_image` etc. Default: enabled.
10
+ * (pi 0.99.0–0.99.1 used the hyphenated form `mcp__nan-media__*`.)
9
11
  *
10
12
  * Both are session-scoped native servers (visible in /mcp, source "extension").
11
13
  * /nan-mcp enables/disables them via pi.registerMcpServer / pi.unregisterMcpServer,
@@ -74,8 +76,8 @@ function serverName(bridge: BridgeKey): string {
74
76
  /** Describe the bridge in the status message. */
75
77
  function bridgeDescription(bridge: BridgeKey): string {
76
78
  return bridge === "webSearch"
77
- ? "web-search bridge (official NaN MCP → mcp__nan-search__web_search)"
78
- : "nan-mcp-server bridge (community media MCP → mcp__nan-media__generate_image/edit_image/text_to_speech/list_voices/speech_to_text)";
79
+ ? "web-search bridge (official NaN MCP → mcp__nan_search__web_search)"
80
+ : "nan-mcp-server bridge (community media MCP → mcp__nan_media__generate_image/edit_image/text_to_speech/list_voices/speech_to_text)";
79
81
  }
80
82
 
81
83
  /** Check if a native server of this name is currently registered. */