@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 +104 -23
- package/README.es.md +68 -15
- package/README.md +54 -20
- package/package.json +5 -5
- package/scripts/check-nan-mcp-server.ts +11 -3
- package/scripts/generate-models.ts +66 -12
- package/scripts/manual-overrides.ts +78 -14
- package/scripts/models.generated.ts +82 -58
- package/src/commands.ts +181 -55
- package/src/context-overflow-classifier.ts +1 -1
- package/src/cross-model-thinking-guard.ts +19 -1
- package/src/fetch-models.ts +11 -0
- package/src/index.ts +99 -41
- package/src/mcp/api-key.ts +109 -0
- package/src/mcp/host-support.ts +94 -0
- package/src/mcp/media-server.ts +56 -0
- package/src/mcp/state.ts +6 -0
- package/src/provider-factory.ts +7 -2
- package/src/usage.ts +10 -5
- package/src/mcp/nan-media.ts +0 -236
- package/src/mcp/nan-search.ts +0 -241
- package/src/mcp/stdio-client.ts +0 -222
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.
|
|
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.
|
|
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
|
|
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
|
-
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
182
|
-
|
|
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
|
|
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,
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
[](https://opensource.org/licenses/MIT)
|
|
4
|
-
[](https://github.com/gtrabanco/pi-nan-provider/releases)
|
|
5
5
|
|
|
6
|
-
[NaN Builders](https://nan.builders) model provider + MCP
|
|
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
|
|
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
|
-
## 🔌
|
|
100
|
+
## 🔌 MCP nativo
|
|
101
101
|
|
|
102
|
-
Dado que [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
|
|
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
|
-
- **`
|
|
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
|
-
- **
|
|
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
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
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.
|
|
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
|
[](https://opensource.org/licenses/MIT)
|
|
4
|
-
[](https://github.com/gtrabanco/pi-nan-provider/releases)
|
|
5
5
|
|
|
6
|
-
[NaN Builders](https://nan.builders) model provider + MCP
|
|
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
|
|
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
|
|
100
|
+
## 🔌 Native MCP Servers
|
|
101
101
|
|
|
102
|
-
Since [pi
|
|
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
|
|
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
|
|
146
|
+
*Official remote MCP server registered natively via `pi.registerMcpServer()`.*
|
|
118
147
|
|
|
119
|
-
- **`
|
|
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
|
-
- **
|
|
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
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
46
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
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.
|
|
50
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
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
|
-
|
|
46
|
-
|
|
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[] = [...
|
|
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));
|