@gtrabanco/pi-nan-provider 0.8.0 → 0.10.2

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
@@ -51,7 +51,7 @@ maintainer's quota, so they are **permission-gated**:
51
51
  gate covers ad-hoc diagnosis only, never `bun test`.
52
52
  - **Default to `qwen3.6` — it is unlimited.**
53
53
  - **For massive/bulk probes prefer a model the maintainer uses less with a large
54
- token budget, e.g. `mimo-v2.5`** (1M context).
54
+ token budget, e.g. `mimo-v2.6-flash`** (1M context, 1.0B monthly quota).
55
55
  - **When the model under investigation is the point** (e.g. reproducing a
56
56
  model-specific 400), use it, but minimize tokens: smallest viable prompt,
57
57
  lowest `max_tokens`, stop at the first decisive response.
@@ -77,7 +77,7 @@ Every PR that changes code MUST bump `package.json` version in the same PR; CI p
77
77
 
78
78
  ## Verified API facts (do not re-derive from stale docs)
79
79
 
80
- - **Extension-side pi-ai imports + streaming-API instance binding (v0.6.10; verified on pi-ai 0.83.0–0.84.4 and pi 0.87.0):**
80
+ - **Extension-side pi-ai imports + streaming-API instance binding (v0.6.10; verified on pi-ai 0.83.0–0.87.1 and pi 0.87.1):**
81
81
  statically import ONLY the bare `@earendil-works/pi-ai` root from `src/`. pi's
82
82
  extension loader maps that specifier to the compat entrypoint on the bundled
83
83
  CLI, Node-mode jiti aliases and compiled-binary virtualModules, and compat
@@ -107,7 +107,7 @@ Every PR that changes code MUST bump `package.json` version in the same PR; CI p
107
107
  `openAICompletionsApi`; `createProvider` and `envApiKeyAuth(name, envVars)` are
108
108
  on the root. `envApiKeyAuth` implements exactly: stored credential key wins →
109
109
  first set env var → unconfigured; `login()` prompts with `{ type: "secret" }`.
110
- - pi awaits extension factories (`await factory(api)`) on 0.83.0 and 0.84.4
110
+ - pi awaits extension factories (`await factory(api)`) on 0.83.0 through 0.87.1
111
111
  alike, so the extension entrypoint may be async (v0.5.0: streaming-API
112
112
  resolution needs it).
113
113
  - pi-ai 0.83.0 runtime surface verified identical for this package's needs:
@@ -116,10 +116,48 @@ Every PR that changes code MUST bump `package.json` version in the same PR; CI p
116
116
  `fetchModels(context)`, `filterModels(models, credential)`, `api`) and
117
117
  `RefreshModelsContext.credential` match 0.84.4; `registerProvider` has both
118
118
  the full-`Provider` and `(name, config)` overloads in 0.83's ExtensionAPI.
119
+ (pi-ai 0.86 changed provider stream entry points to a branded `TranscriptContext`
120
+ — see next bullet — but this package's runtime is untouched.)
121
+ - **pi-ai 0.86.0 `TranscriptContext` migration (verified 2026-09-29):** provider
122
+ stream entry points (`Provider.stream`, `ProviderStreams.stream/streamSimple`,
123
+ `StreamFunction`) now take a branded `TranscriptContext = { messages: Message[] }`
124
+ instead of the public `Context = { systemPrompt?: string; messages; tools? }`.
125
+ `normalizeContext(context: Context): TranscriptContext` (exported from the pi-ai
126
+ root) folds prompt + tools into a leading system message
127
+ `{ role: "system", content: <prompt>, toolsAdded: <tools>, timestamp: 0 }`.
128
+ `Context` itself still exists and is what the public entry points accept, so raw
129
+ `Context` literals only fail where a *provider-level* stream is called directly
130
+ (the 6 test call sites). Tests now build contexts with `normalizeContext(...)`.
131
+ This package's `src/` files never hand-build a provider context, so no `src/`
132
+ code changed: `wrapApiForStrictSanitization` forwards `context` untouched, and
133
+ `withContextOverflowClassification` only *reads* it for the estimate, which still
134
+ counts prompt + tools because they ride inside `messages[0]`.
135
+ - pi 0.87 rejects extension tools that declare no parameter schema at registration;
136
+ all this package's MCP tools declare TypeBox `parameters`, so they register fine.
137
+ pi 0.87 also changed `pi.on()` to return an unsubscribe function (unused by
138
+ this package).
119
139
  - `pi.registerProvider(provider)` accepts a complete pi-ai `Provider`; pi's Models
120
140
  runtime then drives `fetchModels` refreshes (network refresh at interactive
121
141
  startup and periodically, cache-only at registration) and persists the overlay.
122
142
  A `fetchModels` rejection never blocks startup.
143
+ - **pi 0.99 virtual models do not break the provider, but they do interact with the
144
+ cross-model thinking guard** (verified pi 0.99.1/0.99.2). A virtual model
145
+ (`pi.registerVirtualModel`, api `pi-virtual`) can be registered under `nan` and
146
+ route to physical `nan` models via `ctx.modelRegistry.find("nan", id)` — no
147
+ provider-side change needed. Inside the extension `context` hook, though,
148
+ `ctx.model` is the **selection** (the virtual entry), never the routed physical
149
+ model (`core/virtual-models.js`; `agent-session.js` "The selection stays in
150
+ agent state; only this request uses the routed model"), and the routed target is
151
+ not exposed to the hook. `stripCrossModelThinking` therefore skips virtual
152
+ selections (`src/cross-model-thinking-guard.ts`, `VIRTUAL_MODEL_API`); otherwise
153
+ its same-model check would strip reasoning on every continuation. Registered
154
+ virtual models are otherwise transparent to the catalog/factory.
155
+ - **Factory-time ExtensionAPI has NO `modelRegistry`** (verified pi 0.99.1):
156
+ The `ExtensionAPI` interface does not include `modelRegistry`; it exists only on
157
+ `ExtensionContext` (the `ctx` passed to command/event handlers). At factory time,
158
+ stored credentials are resolved via `readStoredCredential("nan")` from
159
+ `@earendil-works/pi-coding-agent`. Command-time paths (`/nan-mcp`, `/nan-usage`) use
160
+ `ctx.modelRegistry`.
123
161
  - models.json overrides compose **above** registered native providers.
124
162
  - Capability values that diverge from models.dev are recorded as build-time
125
163
  `MANUAL_OVERRIDES` (mandatory provenance note) in `scripts/manual-overrides.ts`,
@@ -132,20 +170,37 @@ Every PR that changes code MUST bump `package.json` version in the same PR; CI p
132
170
  override (maintainer-confirmed 2026-09-05) was withdrawn 2026-09-07 — the
133
171
  updated NaN docs still say 262K "the model's native window" and models.dev
134
172
  agrees at 262,144.
135
- - NaN's official chat model list (https://nan.builders/openapi.json `model`
136
- param description + https://nan.builders/docs/models, checked 2026-09-07):
137
- community `deepseek-v4-flash`, `mimo-v2.5`, `qwen3.8-flash`, `glm5.3-flash`,
138
- `qwen3.6`, `gemma4` (all text+image vision) + premium-tier `glm5.3`
173
+ - **Which models exist**: https://nan.builders/docs/models is the source of
174
+ truth (maintainer instruction, reaffirmed 2026-09-29), cross-checked against
175
+ the `model` param description in https://nan.builders/openapi.json; models.dev
176
+ (provider `nan`) supplies the numeric limits for the ids it documents, and
177
+ every divergence or exclusion is recorded with provenance.
178
+ Official chat models (checked 2026-09-29): community `deepseek-v4-flash`,
179
+ `mimo-v2.6-flash`, `qwen3.8-flash`, `glm5.3-flash`, `qwen3.6`, `gemma4`
180
+ (all text+image vision) + premium-tier `glm5.3`
139
181
  (~753B MoE, text-only input, 1M context, 400M tokens/rolling 4h window).
140
182
  glm5.3 is now documented by models.dev too (1M context / 131,072 max output,
141
183
  checked 2026-09-13) but stays out of the static catalog via
142
184
  `LIVE_ONLY_MODEL_IDS` (premium tier) so a non-premium key never sees a
143
185
  model it cannot call when the live `/models` fetch is unavailable; premium
144
186
  keys still receive it live with conservative placeholder limits.
145
- `glm5.2` was removed by the provider (2026-09-05); models.dev may still list
146
- it — the generator excludes it via `PROVIDER_REMOVED_MODEL_IDS`. Non-chat
147
- endpoints: qwen3-embedding, rerank, kokoro (TTS), whisper (STT),
148
- flux-2-klein (images) — MCP-bridge territory, not chat catalog models.
187
+ Removed by the provider: `glm5.2` (2026-09-05) and `mimo-v2.5` (checked
188
+ 2026-09-29 — zero mentions in the docs and in openapi.json, and models.dev
189
+ dropped it too; superseded by `mimo-v2.6-flash`). models.dev may re-list a
190
+ removed id, so the generator excludes them via `PROVIDER_REMOVED_MODEL_IDS`,
191
+ and the four community ids the docs require (`qwen3.6`, `gemma4`,
192
+ `deepseek-v4-flash`, `mimo-v2.6-flash`) are `REQUIRED_MODEL_IDS` — if one
193
+ never reaches the catalog (from models.dev or from a manual-only entry),
194
+ generation exits non-zero instead of inventing data.
195
+ `mimo-v2.6-flash` entered through `MANUAL_ONLY_MODEL_IDS` while models.dev
196
+ lacked it; models.dev started listing it on 2026-09-29, and the generator
197
+ then skips the manual entry (attaching its note to the models.dev-derived
198
+ entry) so the id is never emitted twice.
199
+ Non-chat endpoints: qwen3-embedding, rerank, kokoro (TTS), whisper (STT),
200
+ flux-2-klein and qwen-image-2.1 (images) — MCP-bridge territory, not chat
201
+ catalog models; `qwen-image-2.1` also sits on models.dev with
202
+ `limit.output: 0`, so generation excludes it via `NON_CHAT_MODEL_IDS`
203
+ instead of flagging it "needs manual verification" forever.
149
204
  - NaN can close an SSE stream **before** `finish_reason`. The catalog sets
150
205
  `supportsFinishReason: true` so pi-ai raises the retryable
151
206
  `Stream ended without finish_reason` (pi-ai's `RETRYABLE_PROVIDER_ERROR_PATTERN`
@@ -165,6 +220,7 @@ Every PR that changes code MUST bump `package.json` version in the same PR; CI p
165
220
  `test/issue-2-truncated-stream.test.ts`, `test/issue-4-token-usage.test.ts`,
166
221
  `test/issue-7-streaming-usage-default.test.ts`;
167
222
  issues #2, #4 and #7.
223
+ - **Catalog now declares `thinkingLevelMap` for reasoning suppression** (issue #16, measured 2026-09-27 against live gateway, repro script in the issue). `deepseek-v4-flash` → `{ off: "none" }` (0 reasoning tokens, reproducible); `glm5.3-flash` → `{ off: "minimal" }` (38 reasoning tokens + answer; `none` does NOT work on glm5.3 — 13,382 tokens); `qwen3.6` and `gemma4` → `{ off: "none" }` (gap left by the measured-only fix: the NaN docs state that with no parameter both reason by default with a 16,384-token budget and that `none`/`minimal` skip the reasoning phase entirely — https://nan.builders/docs/models#controlling-reasoning, checked 2026-10-01 — so `off` must send `none` explicitly). `reasoningEffortValues` corrected: deepseek-v4-flash from `[]` to `["none"]`; glm5.3-flash from `["low","medium","high","max"]` to include `"minimal"`. README limits table notes that `maxTokens` does NOT bound the reasoning phase (measured: max_tokens=512 still yielded 13,376 reasoning tokens). pi-ai's `buildRequest` (0.99.1) maps `reasoning: "off"` → `reasoningEffort: undefined` → fires the off-branch that checks `model.thinkingLevelMap?.off` (verified in pi-ai source ~line 718-722). A model whose reasoning depth is genuinely not adjustable (`qwen3.8-flash`, `mimo-v2.6-flash`) keeps no `thinkingLevelMap`: `off` sends nothing and the model uses its own default.
168
224
  - A NaN request that still exceeds the destination model's context window (the
169
225
  cross-model thinking guard is disabled with `NAN_THINKING_GUARD=0`, the
170
226
  inflation is not a `thinking` block, or the window is smaller) gets NaN's
@@ -178,19 +234,44 @@ Every PR that changes code MUST bump `package.json` version in the same PR; CI p
178
234
  `test/context-overflow-classifier.test.ts`; issue #3.
179
235
  - Relative imports inside this package use `.ts` extensions (pi's official
180
236
  extension examples do the same; pi transpiles extension sources).
181
- - pi intentionally has NO built-in MCP client (docs/usage.md). MCP integration
182
- happens by bridging servers into pi custom tools via `pi.registerTool()`:
237
+ - pi 0.87 breaking changes NOT used by this package: `ContextEditEntry` added to
238
+ the `SessionEntry` union, expanded `TurnEndEvent`, removed `shouldStopAfterTurn`,
239
+ `SessionManager` canonical for provider context. The package touches none of them.
240
+ - Since 0.10.0, this package uses native MCP via `pi.registerMcpServer()`.
241
+ Both servers are session-scoped, visible in `/mcp` with source "extension".
242
+ Server names and tool IDs:
243
+ - `nan-search` → tools: `mcp__nan-search__web_search`
244
+ (official remote, HTTP, `Authorization: Bearer <key>` header)
245
+ - `nan-media` → tools: `mcp__nan-media__generate_image/edit_image/text_to_speech/list_voices/speech_to_text`
246
+ (community stdio, `npx -y nan-mcp-server@1.1.2`, 120s timeout)
247
+ - `exposure: "direct"` on both — tools are declared to the model.
248
+ - User `mcp.json` entries with the same name take precedence.
249
+ - `/nan-mcp enable/disable` calls `pi.registerMcpServer()` / `pi.unregisterMcpServer()` directly.
250
+ - Missing `NAN_API_KEY` at load: web-search server NOT registered (console.warn).
251
+ NaN integration facts remain:
183
252
  - NaN's official remote MCP server: `https://api.nan.builders/mcp` (host
184
253
  root, NOT /v1; JSON-RPC 2.0 over streamable HTTP, stateless; same `sk-`
185
254
  key, shared rate limit/quota). Spec: https://nan.builders/openapi.json
186
- (tag "MCP"). Currently exposes `web_search` (same args as POST /v1/search);
187
- "growing registry" — use tools/list to discover.
255
+ (tag "MCP"). Currently exposes `web_search`.
188
256
  - Community `nan-mcp-server` (https://github.com/luciferfran/nan-mcp-server):
189
- stdio MCP server, spawned per tool call (lazy), opt-in NAN_MEDIA_MCP=1,
190
- version-pinned via NAN_MEDIA_MCP_VERSION (default 1.0.8) or a full command
191
- override via NAN_MEDIA_MCP_COMMAND. Tools: generate_image, edit_image,
192
- text_to_speech, list_voices, speech_to_text, embed, rerank, list_models
193
- (we bridge the audio/image/transcription scope). An automated check
194
- (scripts/check-nan-mcp-server.ts + .github/workflows/check-nan-mcp-server-update.yml)
195
- compares the npm registry against the pin weekly and files a `dependencies` issue
196
- with a breaking/safe verdict from the live server tool surface (unpkg).
257
+ stdio MCP server, version-pinned via NAN_MEDIA_MCP_VERSION (default 1.1.2)
258
+ or full command override via NAN_MEDIA_MCP_COMMAND. Tools: generate_image,
259
+ edit_image, text_to_speech, list_voices, speech_to_text.
260
+ - Automated check (scripts/check-nan-mcp-server.ts) compares the npm registry
261
+ against the pin weekly and files a `dependencies` issue when a newer release
262
+ exists, with a breaking/safe verdict from the live server tool surface.
263
+ - **pi reports unhandled MCP registrations right after `session_start`** (verified
264
+ on pi 0.99.1): `AgentSession.bindExtensions()` emits `session_start` and then
265
+ calls `ExtensionRunner.reportUnhandledMcpServers()`, which raises
266
+ `MCP server "<name>" is registered, but no loaded extension connects MCP
267
+ servers` whenever no loaded extension handles `mcp_servers_change`. A host
268
+ that builds `DefaultResourceLoader` WITHOUT `extensionFactories` loads no
269
+ `builtin:*` extension at all — PI WEB's `createAgentSessionServices` does
270
+ exactly that (pi-web 1.202609.1, verified 2026-09-30 with a faithful loader
271
+ run: packages load, `builtin:mcp` absent, no `/mcp` command), so its sessions
272
+ have no MCP connector and `mcp.json` servers never connect either.
273
+ `src/mcp/host-support.ts` detects the gap through the `/mcp` command
274
+ (`pi.getCommands()` missing → conservative "connector present"), claims
275
+ `mcp_servers_change` so pi's report stays silent, and warns once per session;
276
+ `/nan-mcp enable` persists the toggle without registering. Regression tests:
277
+ `test/mcp-host-connector.test.ts`.
package/README.es.md CHANGED
@@ -1,11 +1,11 @@
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.8.0-blue)](https://github.com/gtrabanco/pi-nan-provider/releases)
4
+ [![Version](https://img.shields.io/badge/version-0.10.0-blue)](https://github.com/gtrabanco/pi-nan-provider/releases)
5
5
 
6
- [NaN Builders](https://nan.builders) model provider + MCP bridges para [pi](https://github.com/earendil-works/pi).
6
+ [NaN Builders](https://nan.builders) model provider + MCP nativo para [pi](https://github.com/earendil-works/pi).
7
7
 
8
- Registra el proveedor `nan` vía `pi.registerProvider()` usando la API OpenAI-compatible de NaN (`https://api.nan.builders/v1`), y conecta las herramientas MCP de NaN en pi con `pi.registerTool()`.
8
+ Registra el proveedor `nan` vía `pi.registerProvider()` usando la API OpenAI-compatible de NaN (`https://api.nan.builders/v1`), y registra servidores MCP nativos con `pi.registerMcpServer()`.
9
9
 
10
10
  ---
11
11
 
@@ -97,40 +97,72 @@ Puedes sobrescribir el `compat` de cualquier modelo en `~/.pi/agent/models.json`
97
97
 
98
98
  Consigue una clave en la [plataforma NaN](https://cloud.nan.builders/r/7GK06FX8) (ajustes de usuario → API Keys; enlace de referidos).
99
99
 
100
- ## 🔌 Puentes MCP
100
+ ## 🔌 MCP nativo
101
101
 
102
- Dado que [pi no incluye un cliente MCP integrado](https://github.com/earendil-works/pi/blob/main/docs/usage.md), este paquete conecta los servidores MCP como **herramientas nativas de pi**.
102
+ 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).
103
103
 
104
- Ambos puentes están **activados y son perezosos (lazy) por defecto**. Usa `/nan-mcp` para gestionarlos.
104
+ Ambos puentes están **activados por defecto** (por sesión, visibles en `/mcp`). Usa `/nan-mcp` para gestionarlos.
105
105
 
106
106
  ### 🛠️ Comando de Gestión: `/nan-mcp`
107
107
 
108
108
  | Comando | Efecto |
109
109
  | :--- | :--- |
110
- | `/nan-mcp status` | Muestra el estado actual de ambos puentes. |
110
+ | `/nan-mcp status` | Muestra el estado actual de ambos puentes y la resolución de la clave API. |
111
111
  | `/nan-mcp enable [target]` | Activa `web-search` o `nan-mcp-server` (persiste). |
112
112
  | `/nan-mcp disable [target]` | Desactiva un puente de forma persistente. |
113
113
 
114
+ Ambos puentes resuelven la credencial almacenada de `/login nan` (almacenada → env). Si ninguna se resuelve, el puente no se registra y se muestra un aviso en la salida de estado.
115
+
116
+ > [!NOTE]
117
+ > El registro ocurre cuando se carga la extensión. Si ejecutas `/login nan` **después** de que la sesión haya empezado, la factoría ya se ejecutó — usa `/reload` (o `/nan-mcp enable`) para registrar los puentes sin reiniciar pi.
118
+
119
+ ### 🚧 Hosts sin la extensión MCP integrada de pi (PI WEB)
120
+
121
+ pi conecta los servidores registrados desde la extensión que maneja `mcp_servers_change` — la
122
+ extensión integrada `mcp`, que además registra `/mcp`. Los hosts que construyen el resource loader
123
+ de pi sin `extensionFactories` no cargan **ninguna** extensión integrada (verificado:
124
+ `createAgentSessionServices` de PI WEB, pi 0.99.1 / pi-web 1.202609.1), así que nada conecta los
125
+ servidores y pi reporta `MCP server "nan-media" is registered, but no loaded extension connects MCP
126
+ servers`.
127
+
128
+ Ahí los puentes no pueden exponer herramientas, así que este paquete reemplaza ese error críptico
129
+ con un único aviso por sesión, mantiene los registros inactivos y deja que `/nan-mcp enable` persista
130
+ el toggle sin registrar un servidor que nunca podrá conectarse. Para tener MCP en la CLI, activa el
131
+ conector con `pi config` → Built-in extensions → `mcp`.
132
+
133
+ ### Funciones de pi 0.99 y modelos NaN
134
+
135
+ - **Modelos virtuales** — registra un router bajo `nan` (`pi.registerVirtualModel({ provider: "nan", ... })`);
136
+ enruta a los modelos físicos de NaN sin configuración por parte del proveedor. El guard de
137
+ razonamiento entre modelos se salta las selecciones virtuales, porque el modelo físico enrutado no
138
+ es visible para el hook `context`.
139
+ - **Codemode** — funciona con cualquier modelo NaN que soporte tool calling. Las herramientas MCP de
140
+ este paquete usan `exposure: "direct"`, así que los scripts de codemode también pueden llamarlas.
141
+ Requiere la extensión integrada `codemode` de pi (ver la nota de PI WEB arriba).
142
+ - **Modelos clasificadores** — no aplica: NaN no expone ninguna API de clasificación (solo chat y
143
+ endpoints de embedding/rerank/audio/imagen), así que este proveedor no registra modelos
144
+ `type: "classifier"`.
145
+
114
146
  ---
115
147
 
116
148
  ### 1. Servidor MCP oficial de NaN
117
149
  *Puente oficial para herramientas remotas vía [https://api.nan.builders/mcp](https://nan.builders/docs/api).*
118
150
 
119
- - **`nan_web_search(query, ...)`**: Realiza búsquedas web a través del gateway de NaN.
151
+ - **`mcp__nan-search__web_search(query, ...)`**: Realiza búsquedas web a través del gateway de NaN.
120
152
 
121
153
  ### 2. Servidor MCP de Media (Comunidad)
122
154
  *Conecta [`nan-mcp-server`](https://github.com/luciferfran/nan-mcp-server) mediante un cliente stdio local mínimo.*
123
155
 
124
- - **Carga Perezosa (Lazy)**: El proceso del servidor se lanza **solo** cuando se invoca una herramienta y se cierra inmediatamente después.
156
+ - **Por sesión**: El servidor se conecta al registrarse (eager). Aparece en `/mcp` con fuente "extension".
125
157
  - **Configuración**: Los archivos se guardan en `~/nan-mcp-output/`.
126
158
 
127
159
  | Herramienta | Propósito |
128
160
  | :--- | :--- |
129
- | `nan_generate_image` | Generación de imágenes (flux-2-klein) |
130
- | `nan_edit_image` | Edición imagen→imagen (flux-2-klein) |
131
- | `nan_text_to_speech` | Síntesis de audio (kokoro) |
132
- | `nan_list_voices` | Listar voces disponibles |
133
- | `nan_speech_to_text` | Transcripción de audio (whisper) |
161
+ | `mcp__nan-media__generate_image` | Generación de imágenes (flux-2-klein) |
162
+ | `mcp__nan-media__edit_image` | Edición imagen→imagen (flux-2-klein) |
163
+ | `mcp__nan-media__text_to_speech` | Síntesis de audio (kokoro) |
164
+ | `mcp__nan-media__list_voices` | Listar voces disponibles |
165
+ | `mcp__nan-media__speech_to_text` | Transcripción de audio (whisper) |
134
166
 
135
167
  #### 🔧 Configuración del Puente de Media
136
168
 
@@ -260,12 +292,33 @@ Catálogo base (verificado contra [docs de NaN](https://nan.builders/docs/models
260
292
  | `qwen3.6` | 262,144 | 65,536 | texto, imagen | ✅ |
261
293
  | `gemma4` | 262,144 | 32,768 | texto, imagen | ✅ |
262
294
  | `deepseek-v4-flash` | 1,000,000 | 384,000 | texto, imagen | ✅ |
263
- | `mimo-v2.5` | 1,048,576 | 131,072 | texto, imagen | ✅ |
295
+ | `mimo-v2.6-flash` | 1,048,576 | 131,072 | texto, imagen | ✅ |
264
296
  | `glm5.3-flash` | 1,000,000 | 131,072 | texto, imagen | ✅ |
265
297
  | `qwen3.8-flash` | 262,144 | 131,072 | texto, imagen | ✅ |
266
298
 
299
+ > [!NOTE]
300
+ > `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.
301
+ >
302
+ > `mimo-v2.5` fue eliminado por NaN: ya no aparece ni en [los docs de NaN](https://nan.builders/docs/models) ni en la [lista de modelos del OpenAPI](https://nan.builders/openapi.json) (comprobado 2026-09-29), así que sale del catálogo — lo sustituye `mimo-v2.6-flash` (mismos límites 1,048,576 / 131,072 y cuota mensual de 1.0B). El consumo histórico de `mimo-v2.5` en `/nan-usage` sigue apareciendo en la sección de modelos no documentados.
303
+
267
304
  ---
268
305
 
306
+ ---
307
+
308
+ ## 🧠 Controles de razonamiento
309
+
310
+ El parámetro `reasoning_effort` de NaN controla cuánto piensa el modelo antes de responder — pero el grado de control varía por modelo:
311
+
312
+ | Modelo | Effort de razonamiento | Cómo funciona |
313
+ | :--- | :--- | :--- |
314
+ | `glm5.3`, `glm5.3-flash` | `minimal` · `low` · `medium` · `high` · `max` | Completamente controlable — valores más altos permiten al modelo razonar más; `minimal` produce ~38 tokens de razonamiento (issue #16, medido 2026-09-27); `none` NO suprime el razonamiento en glm5.3-flash (13,382 tokens) |
315
+ | `qwen3.6`, `gemma4` | `none` · `minimal` · `low` · `medium` · `high` · `max` | `none`/`minimal` omiten el razonamiento por completo; otros valores limitan a 2K / 8K / 16K / 32K tokens. `thinking: "off"` envía `reasoning_effort: "none"` automáticamente vía el `thinkingLevelMap` del catálogo — los docs de NaN dicen que sin parámetro estos modelos razonan por defecto (presupuesto de 16,384 tokens), así que `off` debe ser explícito |
316
+ | `deepseek-v4-flash` | `none` · `minimal` · `low` · `medium` · `high` · `max` (el catálogo declara ["none"]) | `none` deshabilita el razonamiento de forma fiable (0 tokens de razonamiento, medido 2026-09-27); cualquier otro valor deja 13-14K tokens de razonamiento y causa razonamiento descontrolado (finish_reason:"length" con CERO texto de respuesta) — el catálogo solo declara `none` porque el resto es ruido. `thinking: "off"` ahora envía `reasoning_effort: "none"` automáticamente vía el `thinkingLevelMap` del catálogo |
317
+ | `qwen3.8-flash`, `mimo-v2.6-flash` | *(aceptado pero no ajustable)* | El parámetro es aceptado y nunca rechazado, pero el modelo gestiona su propia profundidad de razonamiento — nunca es un error enviar un valor que estos modelos no ajustan |
318
+
319
+ > [!NOTE]
320
+ > `maxTokens` NO limita la fase de razonamiento en los modelos de NaN. La fase de razonamiento corre hasta completarse (o se trunca a ~60,000 caracteres para deepseek-v4-flash) independientemente de `maxTokens` — medido: `max_tokens=512` en deepseek-v4-flash aún consumió 13,376 tokens de completación, todos de razonamiento (26x el techo). Este es un comportamiento del gateway confirmado por el issue #16 (medido 2026-09-27).
321
+
269
322
  ## 🚀 Desarrollo
270
323
 
271
324
  ```bash
package/README.md CHANGED
@@ -1,11 +1,11 @@
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.8.0-blue)](https://github.com/gtrabanco/pi-nan-provider/releases)
4
+ [![Version](https://img.shields.io/badge/version-0.10.0-blue)](https://github.com/gtrabanco/pi-nan-provider/releases)
5
5
 
6
- [NaN Builders](https://nan.builders) model provider + MCP bridges for [pi](https://github.com/earendil-works/pi).
6
+ [NaN Builders](https://nan.builders) model provider + native MCP servers for [pi](https://github.com/earendil-works/pi).
7
7
 
8
- Registers the `nan` provider via `pi.registerProvider()` using NaN's OpenAI-compatible API (`https://api.nan.builders/v1`), and bridges NaN's MCP tools into pi with `pi.registerTool()`.
8
+ Registers the `nan` provider via `pi.registerProvider()` using NaN's OpenAI-compatible API (`https://api.nan.builders/v1`), and registers MCP servers via pi's native `pi.registerMcpServer()`.
9
9
 
10
10
  ---
11
11
 
@@ -97,40 +97,69 @@ You can override any model's `compat` per-model in `~/.pi/agent/models.json` (pi
97
97
 
98
98
  Get a key from the [NaN platform](https://cloud.nan.builders/r/7GK06FX8) (user settings → API Keys; referral link).
99
99
 
100
- ## 🔌 MCP Bridges
100
+ ## 🔌 Native MCP Servers
101
101
 
102
- Since [pi does not include a built-in MCP client](https://github.com/earendil-works/pi/blob/main/docs/usage.md), this package bridges MCP servers as **native pi tools**.
102
+ 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).
103
103
 
104
- Both bridges are **enabled and lazy by default**. Use `/nan-mcp` to manage them.
104
+ Both bridges are **enabled by default** (session-scoped, visible in `/mcp`). Use `/nan-mcp` to manage them.
105
105
 
106
106
  ### 🛠️ Management Command: `/nan-mcp`
107
107
 
108
108
  | Command | Effect |
109
109
  | :--- | :--- |
110
- | `/nan-mcp status` | Shows current state of both bridges. |
110
+ | `/nan-mcp status` | Shows current state of both bridges and API key resolution. |
111
111
  | `/nan-mcp enable [target]` | Enables `web-search` or `nan-mcp-server` (persisted). |
112
112
  | `/nan-mcp disable [target]` | Disables a bridge persistently. |
113
113
 
114
+ Both bridges resolve the stored `/login nan` credential (stored → env). If neither resolves, the bridge does not register and a warning is shown in the status output.
115
+
116
+ > [!NOTE]
117
+ > Registration happens when the extension loads. If you run `/login nan` **after** the session started, the factory has already run — run `/reload` (or `/nan-mcp enable`) to register the bridges without restarting pi.
118
+
119
+ ### 🚧 Hosts without pi's built-in MCP extension (PI WEB)
120
+
121
+ pi connects registered servers from the extension that handles `mcp_servers_change` — the built-in
122
+ `mcp` extension, which also registers `/mcp`. Hosts that build pi's resource loader without
123
+ `extensionFactories` load **no** built-in extension at all (verified: PI WEB's
124
+ `createAgentSessionServices`, pi 0.99.1 / pi-web 1.202609.1), so nothing connects the servers and pi
125
+ reports `MCP server "nan-media" is registered, but no loaded extension connects MCP servers`.
126
+
127
+ There the bridges cannot expose tools, so this package replaces that cryptic error with one warning
128
+ per session, keeps the registrations idle, and lets `/nan-mcp enable` persist the toggle without
129
+ registering a server that can never connect. To get MCP in the CLI, enable the connector with
130
+ `pi config` → Built-in extensions → `mcp`.
131
+
132
+ ### pi 0.99 features and NaN models
133
+
134
+ - **Virtual models** — register a router under `nan` (`pi.registerVirtualModel({ provider: "nan", ... })`);
135
+ it routes to the physical NaN models with no provider-side configuration. The cross-model thinking
136
+ guard skips virtual selections, because the routed physical model is not visible to the `context` hook.
137
+ - **Codemode** — works with any tool-calling NaN model. This package's MCP tools use
138
+ `exposure: "direct"`, so codemode scripts can call them as well. Requires pi's built-in `codemode`
139
+ extension (see the PI WEB note above).
140
+ - **Classifier models** — not applicable: NaN exposes no classifier API (chat plus
141
+ embedding/rerank/audio/image endpoints only), so this provider registers no `type: "classifier"` models.
142
+
114
143
  ---
115
144
 
116
145
  ### 1. Official NaN MCP Server
117
- *Official bridge for remote tools via [https://api.nan.builders/mcp](https://nan.builders/docs/api).*
146
+ *Official remote MCP server registered natively via `pi.registerMcpServer()`.*
118
147
 
119
- - **`nan_web_search(query, ...)`**: Performs web searches through NaN's gateway.
148
+ - **`mcp__nan-search__web_search(query, ...)`**: Performs web searches through NaN's gateway.
120
149
 
121
150
  ### 2. Community Media MCP Server
122
151
  *Bridges [`nan-mcp-server`](https://github.com/luciferfran/nan-mcp-server) via a minimal local stdio client.*
123
152
 
124
- - **Lazy Loading**: The server process is spawned **only** when a tool is invoked and terminated immediately after.
153
+ - **Session-scoped**: The server connects at registration time (eager). Appears in `/mcp` with source "extension".
125
154
  - **Config**: Files land in `~/nan-mcp-output/`.
126
155
 
127
156
  | Tool | Purpose |
128
157
  | :--- | :--- |
129
- | `nan_generate_image` | Image generation (flux-2-klein) |
130
- | `nan_edit_image` | Image-to-image editing (flux-2-klein) |
131
- | `nan_text_to_speech` | Audio synthesis (kokoro) |
132
- | `nan_list_voices` | List available voices |
133
- | `nan_speech_to_text` | Audio transcription (whisper) |
158
+ | `mcp__nan-media__generate_image` | Image generation (flux-2-klein) |
159
+ | `mcp__nan-media__edit_image` | Image-to-image editing (flux-2-klein) |
160
+ | `mcp__nan-media__text_to_speech` | Audio synthesis (kokoro) |
161
+ | `mcp__nan-media__list_voices` | List available voices |
162
+ | `mcp__nan-media__speech_to_text` | Audio transcription (whisper) |
134
163
 
135
164
  #### 🔧 Media Bridge Configuration
136
165
 
@@ -260,13 +289,14 @@ Baseline catalog (verified against [NaN docs](https://nan.builders/docs/models)
260
289
  | `qwen3.6` | 262,144 | 65,536 | text, image | ✅ |
261
290
  | `gemma4` | 262,144 | 32,768 | text, image | ✅ |
262
291
  | `deepseek-v4-flash` | 1,000,000 | 384,000 | text, image | ✅ |
263
- | `mimo-v2.5` | 1,048,576 | 131,072 | text, image | ✅ |
264
292
  | `mimo-v2.6-flash` | 1,048,576 | 131,072 | text, image | ✅ |
265
293
  | `glm5.3-flash` | 1,000,000 | 131,072 | text, image | ✅ |
266
294
  | `qwen3.8-flash` | 262,144 | 131,072 | text, image | ✅ |
267
295
 
268
296
  > [!NOTE]
269
- > `mimo-v2.6-flash` is served by NaN but not yet listed on models.dev provider `nan`; it enters the catalog through a manual-only entry with the same limits as `mimo-v2.5`. The model will be auto-detected from models.dev once added there.
297
+ > `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.
298
+ >
299
+ > `mimo-v2.5` was removed by NaN: it no longer appears in the [NaN docs](https://nan.builders/docs/models) nor in the [OpenAPI model list](https://nan.builders/openapi.json) (checked 2026-09-29), so it is out of the catalog — `mimo-v2.6-flash` replaces it (same 1,048,576 / 131,072 limits, 1.0B monthly quota). Historical `/nan-usage` consumption for `mimo-v2.5` still shows under the undocumented-models section.
270
300
 
271
301
  ---
272
302
 
@@ -276,9 +306,13 @@ NaN's `reasoning_effort` parameter controls how much the model thinks before ans
276
306
 
277
307
  | Model | Reasoning effort | How it works |
278
308
  | :--- | :--- | :--- |
279
- | `glm5.3`, `glm5.3-flash` | `low` · `medium` · `high` · `max` | Fully controllable — higher values let the model reason longer |
280
- | `qwen3.6`, `gemma4` | `none` · `minimal` · `low` · `medium` · `high` · `max` | `none`/`minimal` skip reasoning entirely; others cap at 2K / 8K / 16K / 32K tokens |
281
- | `deepseek-v4-flash`, `qwen3.8-flash`, `mimo-v2.5`, `mimo-v2.6-flash` | *(accepted but not adjustable)* | The parameter is accepted and never rejected, but the model manages its own reasoning depth — it is never an error to send a value these models don't adjust |
309
+ | `glm5.3`, `glm5.3-flash` | `minimal` · `low` · `medium` · `high` · `max` | Fully controllable — higher values let the model reason longer; `minimal` yields ~38 reasoning tokens (issue #16, measured 2026-09-27); `none` does NOT suppress reasoning on glm5.3-flash (13,382 tokens) |
310
+ | `qwen3.6`, `gemma4` | `none` · `minimal` · `low` · `medium` · `high` · `max` | `none`/`minimal` skip reasoning entirely; others cap at 2K / 8K / 16K / 32K tokens. `thinking: "off"` sends `reasoning_effort: "none"` automatically via the catalog's `thinkingLevelMap` — the NaN docs state that with no parameter these models reason by default (16,384-token budget), so off must be explicit |
311
+ | `deepseek-v4-flash` | `none` · `minimal` · `low` · `medium` · `high` · `max` (catalog declares ["none"]) | `none` reliably disables reasoning (0 reasoning tokens, measured 2026-09-27); every other value leaves 13-14K reasoning tokens and causes runaway reasoning (finish_reason:"length" with ZERO answer text) — the catalog only declares `none` because the rest are useless. `thinking: "off"` now sends `reasoning_effort: "none"` automatically via the catalog's `thinkingLevelMap` |
312
+ | `qwen3.8-flash`, `mimo-v2.6-flash` | *(accepted but not adjustable)* | The parameter is accepted and never rejected, but the model manages its own reasoning depth — it is never an error to send a value these models don't adjust |
313
+ > [!NOTE]
314
+ > `maxTokens` does NOT bound the reasoning phase on NaN models. The reasoning phase runs to completion (or truncates at ~60,000 chars for deepseek-v4-flash) regardless of `maxTokens` — measured: `max_tokens=512` on deepseek-v4-flash still consumed 13,376 completion tokens, all reasoning (26x the ceiling). This is gateway behavior confirmed by issue #16 (measured 2026-09-27).
315
+
282
316
 
283
317
  ---
284
318
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtrabanco/pi-nan-provider",
3
- "version": "0.8.0",
3
+ "version": "0.10.2",
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.83.0 <1",
46
- "@earendil-works/pi-coding-agent": ">=0.83.0 <1"
45
+ "@earendil-works/pi-ai": ">=0.99.0 <1",
46
+ "@earendil-works/pi-coding-agent": ">=0.99.0 <1"
47
47
  },
48
48
  "devDependencies": {
49
- "@earendil-works/pi-ai": "0.84.4",
50
- "@earendil-works/pi-coding-agent": "0.84.4",
49
+ "@earendil-works/pi-ai": "0.99.1",
50
+ "@earendil-works/pi-coding-agent": "0.99.1",
51
51
  "@types/bun": "1.4",
52
52
  "typescript": "7"
53
53
  },
@@ -42,8 +42,16 @@
42
42
 
43
43
  import {
44
44
  DEFAULT_NAN_MEDIA_MCP_VERSION,
45
- NAN_MEDIA_MCP_SERVER_TOOLS,
46
- } from "../src/mcp/nan-media.ts";
45
+ } from "../src/mcp/media-server.ts";
46
+
47
+ /** The audio/image/transcription scope we bridge from the community server. */
48
+ const bridgedtools = [
49
+ "generate_image",
50
+ "edit_image",
51
+ "text_to_speech",
52
+ "list_voices",
53
+ "speech_to_text",
54
+ ] as const;
47
55
 
48
56
  /** npm package name of the community stdio MCP server we bridge. */
49
57
  export const NAN_MCP_SERVER_PACKAGE = "nan-mcp-server";
@@ -143,7 +151,7 @@ export function assessUpdate(latestTools: string[]): {
143
151
  removedTools: string[];
144
152
  reason: string;
145
153
  } {
146
- const bridged: readonly string[] = [...NAN_MEDIA_MCP_SERVER_TOOLS];
154
+ const bridged: readonly string[] = [...bridgedtools];
147
155
  const latest = new Set(latestTools.map((t) => t.trim()));
148
156
 
149
157
  const removedTools = bridged.filter((tool) => !latest.has(tool));