@gtrabanco/pi-nan-provider 0.6.7 → 0.6.9
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 +15 -8
- package/README.es.md +3 -3
- package/README.md +3 -3
- package/package.json +1 -1
- package/scripts/generate-models.ts +11 -9
- package/scripts/models.generated.ts +16 -16
- package/src/fetch-models.ts +29 -57
- package/src/mcp/nan-media.ts +4 -4
- package/src/mcp/stdio-client.ts +1 -1
- package/src/openai-compat-sanitizer.ts +10 -8
- package/src/provider-factory.ts +7 -6
package/AGENTS.md
CHANGED
|
@@ -136,14 +136,21 @@ Every PR that changes code MUST bump `package.json` version in the same PR; CI p
|
|
|
136
136
|
`supportsFinishReason: true` so pi-ai raises the retryable
|
|
137
137
|
`Stream ended without finish_reason` (pi-ai's `RETRYABLE_PROVIDER_ERROR_PATTERN`
|
|
138
138
|
matches `"ended without"`, so the turn is retried) instead of silently
|
|
139
|
-
synthesizing `stop`/`toolUse`. `supportsUsageInStreaming`
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
`
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
139
|
+
synthesizing `stop`/`toolUse`. `supportsUsageInStreaming` is `true` for chat
|
|
140
|
+
models (issue #7): NaN's published schema does not document `stream_options`,
|
|
141
|
+
but the live gateway honors `stream_options.include_usage` — measured
|
|
142
|
+
2026-09-16 on `deepseek-v4-flash`, `glm5.3-flash`, `qwen3.6`, `mimo-v2.5` and
|
|
143
|
+
`gemma4` (0 usage chunks without the flag, exactly 1 with it, carrying
|
|
144
|
+
prompt/completion/reasoning/cached counts; a real pi session then recorded
|
|
145
|
+
real tokens where it recorded zeros). The sanitizer gates its
|
|
146
|
+
`stream_options` removal on the model's effective
|
|
147
|
+
`compat.supportsUsageInStreaming`, so the stock catalog reports real usage and
|
|
148
|
+
a per-model `models.json` override of `false` restores the strict payload
|
|
149
|
+
(`test/issue-4-token-usage.test.ts`, `test/issue-7-streaming-usage-default.test.ts`;
|
|
150
|
+
issues #2, #4, #7). Regression tests:
|
|
151
|
+
`test/issue-2-truncated-stream.test.ts`, `test/issue-4-token-usage.test.ts`,
|
|
152
|
+
`test/issue-7-streaming-usage-default.test.ts`;
|
|
153
|
+
issues #2, #4 and #7.
|
|
147
154
|
- A NaN request that still exceeds the destination model's context window (the
|
|
148
155
|
cross-model thinking guard is disabled with `NAN_THINKING_GUARD=0`, the
|
|
149
156
|
inflation is not a `thinking` block, or the window is smaller) gets NaN's
|
package/README.es.md
CHANGED
|
@@ -71,14 +71,14 @@ Puedes sobrescribir el `compat` de cualquier modelo en `~/.pi/agent/models.json`
|
|
|
71
71
|
|
|
72
72
|
> Poner `supportsFinishReason: false` restaura el antiguo stall silencioso — no recomendado.
|
|
73
73
|
|
|
74
|
-
**Uso de tokens en streaming:** `supportsUsageInStreaming` es `
|
|
74
|
+
**Uso de tokens en streaming:** `supportsUsageInStreaming` es `true` por defecto. El esquema publicado de NaN no documenta `stream_options`, pero el gateway real lo acepta y lo aplica — medido el 2026-09-16 ([#7](https://github.com/gtrabanco/pi-nan-provider/issues/7)): dos llamadas de streaming idénticas por modelo, 0 chunks de usage sin el flag y exactamente 1 con él, en `deepseek-v4-flash`, `glm5.3-flash`, `qwen3.6`, `mimo-v2.5` y `gemma4`. Por eso pi muestra tokens reales de entrada/salida/reasoning/caché en lugar de ceros. Si un modelo resulta no devolver el usage en streaming, desactívalo por modelo — el sanitizer entonces elimina `stream_options` y el payload vuelve a ser estricto:
|
|
75
75
|
|
|
76
76
|
```json
|
|
77
77
|
{
|
|
78
78
|
"providers": {
|
|
79
79
|
"nan": {
|
|
80
80
|
"modelOverrides": {
|
|
81
|
-
"
|
|
81
|
+
"some-model": { "compat": { "supportsUsageInStreaming": false } }
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
}
|
|
@@ -137,7 +137,7 @@ Ambos puentes están **activados y son perezosos (lazy) por defecto**. Usa `/nan
|
|
|
137
137
|
| Variable | Por defecto | Descripción |
|
|
138
138
|
| :--- | :--- | :--- |
|
|
139
139
|
| `NAN_MEDIA_MCP` | — | Override por sesión (`0` o `false` para desactivar). |
|
|
140
|
-
| `NAN_MEDIA_MCP_VERSION` | `1.
|
|
140
|
+
| `NAN_MEDIA_MCP_VERSION` | `1.1.2` | Versión del servidor fijada (recomendado). |
|
|
141
141
|
| `NAN_MEDIA_MCP_COMMAND` | — | Override del comando personalizado. |
|
|
142
142
|
| `NAN_MEDIA_MCP_TIMEOUT_MS` | `120000` | Timeout por llamada. |
|
|
143
143
|
| `NAN_MCP_TOOLS` | — | Override para el puente oficial (`0` para desactivar). |
|
package/README.md
CHANGED
|
@@ -71,14 +71,14 @@ You can override any model's `compat` per-model in `~/.pi/agent/models.json` (pi
|
|
|
71
71
|
|
|
72
72
|
> Setting `supportsFinishReason: false` restores the old silent-stall behavior — not recommended.
|
|
73
73
|
|
|
74
|
-
**Streaming token usage:** `supportsUsageInStreaming` is `
|
|
74
|
+
**Streaming token usage:** `supportsUsageInStreaming` is `true` by default. NaN's published schema does not document `stream_options`, but the live gateway honors it — measured 2026-09-16 ([#7](https://github.com/gtrabanco/pi-nan-provider/issues/7)): two identical streaming calls per model, 0 usage chunks without the flag and exactly 1 with it, on `deepseek-v4-flash`, `glm5.3-flash`, `qwen3.6`, `mimo-v2.5` and `gemma4`. pi therefore reports real input/output/reasoning/cache token counts instead of zeros. If a model turns out not to report streaming usage, opt out per model — the request sanitizer then strips `stream_options` and the payload stays strict:
|
|
75
75
|
|
|
76
76
|
```json
|
|
77
77
|
{
|
|
78
78
|
"providers": {
|
|
79
79
|
"nan": {
|
|
80
80
|
"modelOverrides": {
|
|
81
|
-
"
|
|
81
|
+
"some-model": { "compat": { "supportsUsageInStreaming": false } }
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
}
|
|
@@ -137,7 +137,7 @@ Both bridges are **enabled and lazy by default**. Use `/nan-mcp` to manage them.
|
|
|
137
137
|
| Variable | Default | Description |
|
|
138
138
|
| :--- | :--- | :--- |
|
|
139
139
|
| `NAN_MEDIA_MCP` | — | Per-session override (`0` or `false` to disable). |
|
|
140
|
-
| `NAN_MEDIA_MCP_VERSION` | `1.
|
|
140
|
+
| `NAN_MEDIA_MCP_VERSION` | `1.1.2` | Pinned server version (recommended). |
|
|
141
141
|
| `NAN_MEDIA_MCP_COMMAND` | — | Custom command override. |
|
|
142
142
|
| `NAN_MEDIA_MCP_TIMEOUT_MS` | `120000` | Per-call timeout. |
|
|
143
143
|
| `NAN_MCP_TOOLS` | — | Override for the official bridge (`0` to disable). |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtrabanco/pi-nan-provider",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.9",
|
|
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",
|
|
@@ -83,14 +83,16 @@ const LIVE_ONLY_MODEL_IDS: Record<string, string> = {
|
|
|
83
83
|
const NAN_COMPAT = {
|
|
84
84
|
supportsDeveloperRole: false,
|
|
85
85
|
supportsReasoningEffort: true,
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
supportsUsageInStreaming: false
|
|
86
|
+
// NaN's published schema is silent about `stream_options`, but the live
|
|
87
|
+
// gateway honors it (issue #7, measured 2026-09-16 on five chat models:
|
|
88
|
+
// 0 usage chunks without the flag, exactly 1 with it). pi-ai only sends
|
|
89
|
+
// `stream_options: { include_usage: true }` when this is not false, and the
|
|
90
|
+
// sanitizer forwards it when the model declares true, so chat models opt in
|
|
91
|
+
// by default and pi reports real token counts instead of zeros (issue #4).
|
|
92
|
+
// A model that does not report streaming usage can still opt out per model
|
|
93
|
+
// with a models.json compat override (`supportsUsageInStreaming: false`);
|
|
94
|
+
// the sanitizer then strips `stream_options` and the payload stays strict.
|
|
95
|
+
supportsUsageInStreaming: true,
|
|
94
96
|
// The NaN/LiteLLM gateway intermittently closes SSE streams before emitting
|
|
95
97
|
// `finish_reason`. With true, pi-ai raises "Stream ended without
|
|
96
98
|
// finish_reason", which its retryable-provider pattern ("ended without")
|
|
@@ -102,7 +104,7 @@ const NAN_COMPAT = {
|
|
|
102
104
|
};
|
|
103
105
|
|
|
104
106
|
const NAN_COMPAT_NOTE =
|
|
105
|
-
"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
|
|
107
|
+
"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.";
|
|
106
108
|
|
|
107
109
|
interface ModelsDevModel {
|
|
108
110
|
id?: string;
|
|
@@ -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-09-
|
|
4
|
+
// Source: https://models.dev/api.json (provider "nan"), fetched 2026-09-16T19:40:56.569Z
|
|
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.
|
|
@@ -31,12 +31,12 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
31
31
|
"compat": {
|
|
32
32
|
"supportsDeveloperRole": false,
|
|
33
33
|
"supportsReasoningEffort": true,
|
|
34
|
-
"supportsUsageInStreaming":
|
|
34
|
+
"supportsUsageInStreaming": true,
|
|
35
35
|
"supportsFinishReason": true,
|
|
36
36
|
"maxTokensField": "max_tokens"
|
|
37
37
|
},
|
|
38
38
|
"notes": [
|
|
39
|
-
"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
|
|
39
|
+
"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.",
|
|
40
40
|
"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."
|
|
41
41
|
],
|
|
42
42
|
"extras": {
|
|
@@ -92,12 +92,12 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
92
92
|
"compat": {
|
|
93
93
|
"supportsDeveloperRole": false,
|
|
94
94
|
"supportsReasoningEffort": true,
|
|
95
|
-
"supportsUsageInStreaming":
|
|
95
|
+
"supportsUsageInStreaming": true,
|
|
96
96
|
"supportsFinishReason": true,
|
|
97
97
|
"maxTokensField": "max_tokens"
|
|
98
98
|
},
|
|
99
99
|
"notes": [
|
|
100
|
-
"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
|
|
100
|
+
"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."
|
|
101
101
|
],
|
|
102
102
|
"extras": {
|
|
103
103
|
"id": "gemma4",
|
|
@@ -155,18 +155,18 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
155
155
|
"compat": {
|
|
156
156
|
"supportsDeveloperRole": false,
|
|
157
157
|
"supportsReasoningEffort": true,
|
|
158
|
-
"supportsUsageInStreaming":
|
|
158
|
+
"supportsUsageInStreaming": true,
|
|
159
159
|
"supportsFinishReason": true,
|
|
160
160
|
"maxTokensField": "max_tokens"
|
|
161
161
|
},
|
|
162
162
|
"notes": [
|
|
163
|
-
"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
|
|
163
|
+
"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."
|
|
164
164
|
],
|
|
165
165
|
"extras": {
|
|
166
166
|
"id": "glm5.3-flash",
|
|
167
167
|
"name": "GLM-5.3-Flash",
|
|
168
168
|
"description": "Native multimodal GLM model for efficient coding and long-horizon agent tasks",
|
|
169
|
-
"family": "glm",
|
|
169
|
+
"family": "glm-flash",
|
|
170
170
|
"attachment": true,
|
|
171
171
|
"reasoning": true,
|
|
172
172
|
"reasoning_options": [],
|
|
@@ -214,12 +214,12 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
214
214
|
"compat": {
|
|
215
215
|
"supportsDeveloperRole": false,
|
|
216
216
|
"supportsReasoningEffort": true,
|
|
217
|
-
"supportsUsageInStreaming":
|
|
217
|
+
"supportsUsageInStreaming": true,
|
|
218
218
|
"supportsFinishReason": true,
|
|
219
219
|
"maxTokensField": "max_tokens"
|
|
220
220
|
},
|
|
221
221
|
"notes": [
|
|
222
|
-
"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
|
|
222
|
+
"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."
|
|
223
223
|
],
|
|
224
224
|
"extras": {
|
|
225
225
|
"id": "mimo-v2.5",
|
|
@@ -274,12 +274,12 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
274
274
|
"compat": {
|
|
275
275
|
"supportsDeveloperRole": false,
|
|
276
276
|
"supportsReasoningEffort": true,
|
|
277
|
-
"supportsUsageInStreaming":
|
|
277
|
+
"supportsUsageInStreaming": true,
|
|
278
278
|
"supportsFinishReason": true,
|
|
279
279
|
"maxTokensField": "max_tokens"
|
|
280
280
|
},
|
|
281
281
|
"notes": [
|
|
282
|
-
"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
|
|
282
|
+
"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."
|
|
283
283
|
],
|
|
284
284
|
"extras": {
|
|
285
285
|
"id": "qwen3.6",
|
|
@@ -337,12 +337,12 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
337
337
|
"compat": {
|
|
338
338
|
"supportsDeveloperRole": false,
|
|
339
339
|
"supportsReasoningEffort": true,
|
|
340
|
-
"supportsUsageInStreaming":
|
|
340
|
+
"supportsUsageInStreaming": true,
|
|
341
341
|
"supportsFinishReason": true,
|
|
342
342
|
"maxTokensField": "max_tokens"
|
|
343
343
|
},
|
|
344
344
|
"notes": [
|
|
345
|
-
"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
|
|
345
|
+
"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.",
|
|
346
346
|
"contextWindow 262,144: the earlier 1,000,000 override (maintainer-confirmed 2026-09-05) was withdrawn 2026-09-07 — the updated https://nan.builders/docs/models still states '262K token context, the model's native window' and models.dev agrees at 262,144; NaN docs are treated as the most reliable source (maintainer instruction, 2026-09-07)."
|
|
347
347
|
],
|
|
348
348
|
"extras": {
|
|
@@ -382,13 +382,13 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
382
382
|
export const GENERATED_CATALOG_META = {
|
|
383
383
|
source: "https://models.dev/api.json",
|
|
384
384
|
modelsDevProvider: "nan",
|
|
385
|
-
fetchedAt: "2026-09-
|
|
385
|
+
fetchedAt: "2026-09-16T19:40:56.569Z",
|
|
386
386
|
modelCount: 6,
|
|
387
387
|
models: ["deepseek-v4-flash","gemma4","glm5.3-flash","mimo-v2.5","qwen3.6","qwen3.8-flash"],
|
|
388
388
|
notes: [
|
|
389
389
|
"live-only: \"glm5.3\" kept out of the static catalog (premium-tier model (models.dev now documents it with 1M context / 131,072 max output; NaN docs https://nan.builders/docs/models + https://nan.builders/openapi.json, checked 2026-09-13) kept live-only so a non-premium key never sees a model it cannot call when the live /models fetch is unavailable; premium keys still get it via the /models refresh with conservative placeholder limits)",
|
|
390
390
|
"provider-removed: \"glm5.2\" excluded from the catalog (removed by NaN (2026-09-05); absent from the official chat model list in https://nan.builders/openapi.json and https://nan.builders/docs/models (checked 2026-09-07) while models.dev provider nan still listed it — excluded so regeneration does not resurrect it)",
|
|
391
|
-
"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
|
|
391
|
+
"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.",
|
|
392
392
|
"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.",
|
|
393
393
|
"contextWindow 262,144: the earlier 1,000,000 override (maintainer-confirmed 2026-09-05) was withdrawn 2026-09-07 — the updated https://nan.builders/docs/models still states '262K token context, the model's native window' and models.dev agrees at 262,144; NaN docs are treated as the most reliable source (maintainer instruction, 2026-09-07)."
|
|
394
394
|
],
|
package/src/fetch-models.ts
CHANGED
|
@@ -9,19 +9,16 @@
|
|
|
9
9
|
*
|
|
10
10
|
* 2. Runtime fetch of the provider's own `/models` endpoint. NaN runs
|
|
11
11
|
* LiteLLM behind an OpenAI-compatible facade, so the response carries
|
|
12
|
-
* only model `id`s — no capability fields. It is used
|
|
13
|
-
*
|
|
12
|
+
* only model `id`s — no capability fields. It is used as the authoritative
|
|
13
|
+
* list of what the endpoint returns for the API key.
|
|
14
14
|
*
|
|
15
|
-
* Merge: live IDs × generated capability data —
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* and non-chat endpoints never surface. On fetch failure, timeout, or an
|
|
23
|
-
* unusable response, callers fall back to the generated catalog so startup
|
|
24
|
-
* is never blocked.
|
|
15
|
+
* Merge: live IDs × generated capability data — all live IDs surface:
|
|
16
|
+
* a live ID with generated data keeps its generated capabilities, and an
|
|
17
|
+
* uncatalogued live ID (e.g. a new or undocumented model) gets conservative
|
|
18
|
+
* placeholder limits (the same defaults used in custom-provider.md's
|
|
19
|
+
* dynamic-discovery example) with no reasoning support — capabilities stay
|
|
20
|
+
* "unknown", nothing is fabricated. On fetch failure, timeout, or an unusable
|
|
21
|
+
* response, callers fall back to the generated catalog so startup is never blocked.
|
|
25
22
|
*/
|
|
26
23
|
|
|
27
24
|
import type { Model, OpenAICompletionsCompat } from "@earendil-works/pi-ai";
|
|
@@ -64,28 +61,6 @@ export const NAN_COMPAT_API = "openai-completions" as const;
|
|
|
64
61
|
*/
|
|
65
62
|
export const UNKNOWN_MODEL_LIMITS = { contextWindow: 128_000, maxTokens: 4_096 } as const;
|
|
66
63
|
|
|
67
|
-
/**
|
|
68
|
-
* The allowlist of model ids this package may surface. Everything a live
|
|
69
|
-
* /models response returns that is NOT here is dropped — so models that are
|
|
70
|
-
* undocumented (and might become available later, e.g. one NaN starts
|
|
71
|
-
* serving after a release) can never leak through the package.
|
|
72
|
-
*
|
|
73
|
-
* Every current chat model is listed: the six models.dev-documented entries
|
|
74
|
-
* plus the premium live-only glm5.3 (which NaN serves via /models but that
|
|
75
|
-
* models.dev omits — see the "unemittable" note in scripts/generate-models.ts).
|
|
76
|
-
* Non-chat endpoints (flux-2-klein, kokoro, whisper, qwen3-embedding, rerank)
|
|
77
|
-
* are deliberately excluded: they are MCP-bridge territory, not chat models.
|
|
78
|
-
*/
|
|
79
|
-
const ALLOWED_MODEL_IDS = [
|
|
80
|
-
"deepseek-v4-flash",
|
|
81
|
-
"gemma4",
|
|
82
|
-
"glm5.3-flash",
|
|
83
|
-
"glm5.3",
|
|
84
|
-
"mimo-v2.5",
|
|
85
|
-
"qwen3.6",
|
|
86
|
-
"qwen3.8-flash",
|
|
87
|
-
] as const;
|
|
88
|
-
|
|
89
64
|
/** Timeout for the live /models fetch; matches the pi-synthetic-provider precedent (~3s). */
|
|
90
65
|
export const DEFAULT_MODELS_TIMEOUT_MS = 3_000;
|
|
91
66
|
|
|
@@ -113,10 +88,9 @@ export function toModel(entry: GeneratedModelEntry, source: CatalogSource): Mode
|
|
|
113
88
|
};
|
|
114
89
|
}
|
|
115
90
|
|
|
116
|
-
/** The generated fallback catalog as pi-ai Models for the given provider
|
|
91
|
+
/** The generated fallback catalog as pi-ai Models for the given provider. */
|
|
117
92
|
export function baselineModels(source: CatalogSource): Model<"openai-completions">[] {
|
|
118
|
-
|
|
119
|
-
return NAN_GENERATED_MODELS.filter((entry) => allowed.has(entry.id)).map((entry) => toModel(entry, source));
|
|
93
|
+
return NAN_GENERATED_MODELS.map((entry) => toModel(entry, source));
|
|
120
94
|
}
|
|
121
95
|
|
|
122
96
|
export interface LiveModelListOptions {
|
|
@@ -174,12 +148,11 @@ export interface MergedCatalog {
|
|
|
174
148
|
}
|
|
175
149
|
|
|
176
150
|
/**
|
|
177
|
-
* Merge live model IDs with the generated capability catalog
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
* live ID is dropped.
|
|
151
|
+
* Merge live model IDs with the generated capability catalog. All live IDs
|
|
152
|
+
* surface: live IDs with generated data keep their generated capabilities;
|
|
153
|
+
* uncatalogued live IDs get conservative placeholder limits,
|
|
154
|
+
* `reasoning: false`, and zero cost (documented defaults, not invented
|
|
155
|
+
* capabilities).
|
|
183
156
|
*/
|
|
184
157
|
export function mergeLiveWithGenerated(
|
|
185
158
|
liveIds: readonly string[],
|
|
@@ -187,29 +160,28 @@ export function mergeLiveWithGenerated(
|
|
|
187
160
|
generated: readonly GeneratedModelEntry[] = NAN_GENERATED_MODELS,
|
|
188
161
|
): MergedCatalog {
|
|
189
162
|
const byId = new Map(generated.map((entry) => [entry.id, entry]));
|
|
190
|
-
const allowed = new Set<string>(ALLOWED_MODEL_IDS);
|
|
191
163
|
const models: Model<"openai-completions">[] = [];
|
|
192
164
|
const matched: string[] = [];
|
|
193
165
|
const unknown: string[] = [];
|
|
194
166
|
|
|
195
167
|
for (const id of liveIds) {
|
|
196
|
-
if (!allowed.has(id)) continue;
|
|
197
168
|
const entry = byId.get(id);
|
|
198
169
|
if (entry) {
|
|
199
170
|
models.push(toModel(entry, source));
|
|
200
171
|
matched.push(id);
|
|
201
172
|
} else {
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
173
|
+
// Placeholder for uncatalogued live ids (e.g. new or undocumented):
|
|
174
|
+
// limits are the documented safe envelope and capabilities stay
|
|
175
|
+
// "unknown". The compat keys are the same gateway-behavior flags
|
|
176
|
+
// applied to every catalog model in NAN_COMPAT: the NaN/LiteLLM
|
|
177
|
+
// gateway cuts SSE streams before finish_reason, so supportsFinishReason
|
|
178
|
+
// must stay true (pi-ai then raises the retryable "Stream ended without
|
|
179
|
+
// finish_reason" instead of silently stalling), and
|
|
180
|
+
// supportsUsageInStreaming is true because the live gateway honors
|
|
181
|
+
// `stream_options.include_usage` even though the published schema is
|
|
182
|
+
// silent (issue #7, measured 2026-09-16): the sanitizer forwards the
|
|
183
|
+
// field and pi reports real token counts. A model can still opt out
|
|
184
|
+
// with a models.json override of false.
|
|
213
185
|
models.push({
|
|
214
186
|
id,
|
|
215
187
|
name: id,
|
|
@@ -221,7 +193,7 @@ export function mergeLiveWithGenerated(
|
|
|
221
193
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
222
194
|
contextWindow: UNKNOWN_MODEL_LIMITS.contextWindow,
|
|
223
195
|
maxTokens: UNKNOWN_MODEL_LIMITS.maxTokens,
|
|
224
|
-
compat: { supportsFinishReason: true, supportsUsageInStreaming:
|
|
196
|
+
compat: { supportsFinishReason: true, supportsUsageInStreaming: true },
|
|
225
197
|
});
|
|
226
198
|
unknown.push(id);
|
|
227
199
|
}
|
package/src/mcp/nan-media.ts
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* other env vars inherit from your environment (generated files land in
|
|
15
15
|
* ~/nan-mcp-output/ by default).
|
|
16
16
|
* - Version pinning follows the upstream server's own supply-chain guidance:
|
|
17
|
-
* NAN_MEDIA_MCP_VERSION (default "1.
|
|
18
|
-
* NAN_MEDIA_MCP_COMMAND (space-separated, e.g. "bunx nan-mcp-server@1.
|
|
17
|
+
* NAN_MEDIA_MCP_VERSION (default "1.1.2"), or pass a custom command with
|
|
18
|
+
* NAN_MEDIA_MCP_COMMAND (space-separated, e.g. "bunx nan-mcp-server@1.1.2").
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { Type, type TSchema } from "@earendil-works/pi-ai";
|
|
@@ -28,7 +28,7 @@ export const NAN_MEDIA_MCP_ENV = "NAN_MEDIA_MCP";
|
|
|
28
28
|
export const NAN_MEDIA_MCP_VERSION_ENV = "NAN_MEDIA_MCP_VERSION";
|
|
29
29
|
export const NAN_MEDIA_MCP_COMMAND_ENV = "NAN_MEDIA_MCP_COMMAND";
|
|
30
30
|
export const NAN_MEDIA_MCP_TIMEOUT_ENV = "NAN_MEDIA_MCP_TIMEOUT_MS";
|
|
31
|
-
export const DEFAULT_NAN_MEDIA_MCP_VERSION = "1.
|
|
31
|
+
export const DEFAULT_NAN_MEDIA_MCP_VERSION = "1.1.2";
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* The MCP tool names on the stdio nan-mcp-server that this package bridges as
|
|
@@ -143,7 +143,7 @@ function defineMediaTool<TParams extends TSchema>(spec: MediaToolSpec<TParams>):
|
|
|
143
143
|
/**
|
|
144
144
|
* Build the media tool set. Registered only when NAN_MEDIA_MCP=1 and the
|
|
145
145
|
* runtime supports registerTool; execution spawns the MCP server per call.
|
|
146
|
-
* Schemas mirror nan-mcp-server's zod input schemas (v1.
|
|
146
|
+
* Schemas mirror nan-mcp-server's zod input schemas (v1.1.2).
|
|
147
147
|
*/
|
|
148
148
|
export function createNanMediaTools(): ToolDefinition[] {
|
|
149
149
|
return [
|
package/src/mcp/stdio-client.ts
CHANGED
|
@@ -18,7 +18,7 @@ const PROTOCOL_VERSION = "2024-11-05";
|
|
|
18
18
|
const CLIENT_INFO = { name: "pi-nan-provider", version: "0.2.0" };
|
|
19
19
|
|
|
20
20
|
export interface StdioMcpCallOptions {
|
|
21
|
-
/** Command to spawn, e.g. ["npx", "-y", "nan-mcp-server@1.
|
|
21
|
+
/** Command to spawn, e.g. ["npx", "-y", "nan-mcp-server@1.1.2"]. */
|
|
22
22
|
command: readonly string[];
|
|
23
23
|
/** Extra environment for the child (merged over process.env). */
|
|
24
24
|
env?: Record<string, string | undefined>;
|
|
@@ -33,14 +33,16 @@
|
|
|
33
33
|
* not silently lost.
|
|
34
34
|
* 4. Top-level fields NaN's schema does not list: `store` and
|
|
35
35
|
* `stream_options`. These are opt-in/usage fields pi-ai sends by default
|
|
36
|
-
* for a "standard" provider; NaN does not document
|
|
37
|
-
* removed by default. `stream_options` is the one
|
|
38
|
-
* explicitly opted into: when the model's effective
|
|
39
|
-
* `compat.supportsUsageInStreaming` is true (
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
36
|
+
* for a "standard" provider; NaN's published schema does not document
|
|
37
|
+
* them, so they are removed by default. `stream_options` is the one
|
|
38
|
+
* exception that can be explicitly opted into: when the model's effective
|
|
39
|
+
* `compat.supportsUsageInStreaming` is true (the catalog default for chat
|
|
40
|
+
* models since issue #7 — the live gateway was measured honoring
|
|
41
|
+
* `include_usage` on 2026-09-16 — or a user `models.json` override), the
|
|
42
|
+
* gateway reports usage and `stream_options` is preserved — deleting it
|
|
43
|
+
* unconditionally would silently zero out `message.usage` (issue #4).
|
|
44
|
+
* A per-model override of `false` keeps the strict payload. `store` is
|
|
45
|
+
* always removed.
|
|
44
46
|
* 5. An EMPTY `tools` array. Verified against the live gateway (2026-09-09):
|
|
45
47
|
* NaN rejects `tools: []` with the same 400, while `stream: true`, a
|
|
46
48
|
* `system` message, string content, and a `tool` role message are all
|
package/src/provider-factory.ts
CHANGED
|
@@ -88,16 +88,17 @@ export async function resolveOpenAICompletionsApi(): Promise<OpenAICompletionsAp
|
|
|
88
88
|
* ./openai-compat-sanitizer.ts). NaN returns HTTP 400 `Invalid request. Check
|
|
89
89
|
* your request parameters.` for any payload that violates it — including a
|
|
90
90
|
* replayed assistant message with a `toolCall` block inside `content`, a
|
|
91
|
-
* `reasoning_details` field, or undocumented top-level
|
|
92
|
-
*
|
|
91
|
+
* `reasoning_details` field, or the undocumented top-level `store` field.
|
|
92
|
+
* Sanitizing via the `onPayload` hook works regardless of
|
|
93
93
|
* which pi-ai version the runtime bundles, so the fix is not tied to a
|
|
94
94
|
* specific upstream build.
|
|
95
95
|
*
|
|
96
96
|
* `stream_options` is the one field whose removal is conditional: when the
|
|
97
|
-
* model's effective `compat.supportsUsageInStreaming` is true (catalog
|
|
98
|
-
* or user `models.json` override),
|
|
99
|
-
* return it — stripping the field
|
|
100
|
-
* (issue #4). Every other model keeps the
|
|
97
|
+
* model's effective `compat.supportsUsageInStreaming` is true (the catalog
|
|
98
|
+
* default for chat models since issue #7, or a user `models.json` override),
|
|
99
|
+
* pi-ai requested usage and the gateway will return it — stripping the field
|
|
100
|
+
* would silently zero `message.usage` (issue #4). Every other model keeps the
|
|
101
|
+
* strict payload.
|
|
101
102
|
*
|
|
102
103
|
* Any caller-supplied `onPayload` (e.g. pi's own debug/passthrough hook) is
|
|
103
104
|
* preserved and chained AFTER sanitization, so the final payload is always
|