@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
|
@@ -8,16 +8,28 @@
|
|
|
8
8
|
* package. Run before publishing (`bun run generate-models`, wired into
|
|
9
9
|
* `prepublishOnly`).
|
|
10
10
|
*
|
|
11
|
+
* Sources of truth (maintainer instruction, reaffirmed 2026-09-29):
|
|
12
|
+
* - https://nan.builders/docs/models decides WHICH models exist — which ids
|
|
13
|
+
* belong in the chat catalog, which are premium/live-only, which were
|
|
14
|
+
* removed by the provider, which are not chat models at all. Every
|
|
15
|
+
* exclusion cites it.
|
|
16
|
+
* - models.dev (provider "nan") supplies the numeric limits for the ids it
|
|
17
|
+
* documents; a divergence from the docs is recorded in MANUAL_OVERRIDES
|
|
18
|
+
* with a source note.
|
|
19
|
+
*
|
|
11
20
|
* Provenance rules (enforced, not decorative):
|
|
12
21
|
* - Every emitted number must come from models.dev or an explicit
|
|
13
22
|
* MANUAL_OVERRIDES note recording where it was confirmed. Nothing invented.
|
|
14
|
-
* - The
|
|
15
|
-
*
|
|
23
|
+
* - The official community chat models (qwen3.6, gemma4, deepseek-v4-flash,
|
|
24
|
+
* mimo-v2.6-flash) MUST reach the catalog — from models.dev or from a
|
|
25
|
+
* MANUAL_ONLY entry — or this script exits non-zero.
|
|
16
26
|
* - Any other models.dev entry missing `limit.context`/`limit.output` is
|
|
17
|
-
* skipped and flagged "needs manual verification" — never guessed.
|
|
27
|
+
* skipped and flagged "needs manual verification" — never guessed. Entries
|
|
28
|
+
* that are not chat models are excluded via NON_CHAT_MODEL_IDS instead.
|
|
18
29
|
* - models.dev modalities are intersected with pi's supported input set
|
|
19
|
-
* ("text" | "image"); e.g. mimo-v2.
|
|
20
|
-
* pi's Model type and is dropped from `input` (noted on
|
|
30
|
+
* ("text" | "image"); e.g. mimo-v2.6-flash's audio input is not
|
|
31
|
+
* representable in pi's Model type and is dropped from `input` (noted on
|
|
32
|
+
* the entry).
|
|
21
33
|
*/
|
|
22
34
|
|
|
23
35
|
import type { GeneratedModelEntry } from "../src/fetch-models.ts";
|
|
@@ -39,17 +51,23 @@ const REASONING_EFFORT_VALUES_FROM_DOCS: Record<string, string[]> = {
|
|
|
39
51
|
...REASONING_EFFORT_VALUES,
|
|
40
52
|
// deepseek-v4-flash: any value (no effect) — model decides per request
|
|
41
53
|
"deepseek-v4-flash": [],
|
|
42
|
-
// qwen3.8-flash, mimo-v2.
|
|
54
|
+
// qwen3.8-flash, mimo-v2.6-flash: accepted, depth not adjustable
|
|
43
55
|
"qwen3.8-flash": [],
|
|
44
|
-
"mimo-v2.
|
|
56
|
+
"mimo-v2.6-flash": [],
|
|
45
57
|
};
|
|
46
58
|
|
|
47
59
|
const MODELS_DEV_API_URL = "https://models.dev/api.json";
|
|
48
60
|
const SOURCE_PROVIDER_ID = "nan";
|
|
49
61
|
const FETCH_TIMEOUT_MS = 15_000;
|
|
50
62
|
|
|
51
|
-
/**
|
|
52
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Official community chat models per https://nan.builders/docs/models
|
|
65
|
+
* (checked 2026-09-29; glm5.3 is premium-tier and deliberately live-only).
|
|
66
|
+
* Each MUST reach the catalog — from models.dev or from a MANUAL_ONLY entry —
|
|
67
|
+
* or generation fails. The docs decide this set, not models.dev: models.dev
|
|
68
|
+
* dropped mimo-v2.5 together with the docs in 2026-09.
|
|
69
|
+
*/
|
|
70
|
+
const REQUIRED_MODEL_IDS = ["qwen3.6", "gemma4", "deepseek-v4-flash", "mimo-v2.6-flash"] as const;
|
|
53
71
|
|
|
54
72
|
/** pi's Model.input only supports these values. */
|
|
55
73
|
const PI_SUPPORTED_INPUT = new Set(["text", "image"]);
|
|
@@ -75,6 +93,20 @@ const MANUAL_NOTES: Record<string, string> = {
|
|
|
75
93
|
const PROVIDER_REMOVED_MODEL_IDS: Record<string, string> = {
|
|
76
94
|
"glm5.2":
|
|
77
95
|
"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",
|
|
96
|
+
"mimo-v2.5":
|
|
97
|
+
"removed by NaN (checked 2026-09-29): zero mentions in https://nan.builders/docs/models and in the model param description of https://nan.builders/openapi.json, and models.dev provider nan dropped it as well — superseded by mimo-v2.6-flash; excluded so regeneration does not resurrect it",
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* models.dev entries that are not chat models. They can never join a chat
|
|
102
|
+
* catalog (their output is an image, and models.dev reports limit.output 0),
|
|
103
|
+
* so they are excluded with the docs as the reason instead of being flagged
|
|
104
|
+
* "needs manual verification" forever — that flag would suggest limits worth
|
|
105
|
+
* confirming for a model this package must not serve as chat.
|
|
106
|
+
*/
|
|
107
|
+
const NON_CHAT_MODEL_IDS: Record<string, string> = {
|
|
108
|
+
"qwen-image-2.1":
|
|
109
|
+
"image-generation model (text→image; output modality image) documented in https://nan.builders/docs/models and https://nan.builders/openapi.json (checked 2026-09-29) — not a chat model, out of the static chat catalog (models.dev reports limit.output 0)",
|
|
78
110
|
};
|
|
79
111
|
|
|
80
112
|
/**
|
|
@@ -205,6 +237,7 @@ function buildManualOnlyModelEntry(
|
|
|
205
237
|
contextWindow: override?.contextWindow ?? 1_048_576,
|
|
206
238
|
maxTokens: override?.maxTokens ?? 131_072,
|
|
207
239
|
reasoningEffortValues,
|
|
240
|
+
...(override?.thinkingLevelMap ? { thinkingLevelMap: { ...override.thinkingLevelMap } } : {}),
|
|
208
241
|
compat: { ...NAN_COMPAT },
|
|
209
242
|
notes: [
|
|
210
243
|
NAN_COMPAT_NOTE,
|
|
@@ -221,6 +254,10 @@ function convertModel(modelId: string, m: ModelsDevModel): GeneratedModel | { sk
|
|
|
221
254
|
if (removedReason) {
|
|
222
255
|
return { skip: `provider-removed: "${modelId}" excluded from the catalog (${removedReason})` };
|
|
223
256
|
}
|
|
257
|
+
const nonChatReason = NON_CHAT_MODEL_IDS[modelId];
|
|
258
|
+
if (nonChatReason) {
|
|
259
|
+
return { skip: `non-chat: "${modelId}" excluded from the chat catalog (${nonChatReason})` };
|
|
260
|
+
}
|
|
224
261
|
const liveOnlyReason = LIVE_ONLY_MODEL_IDS[modelId];
|
|
225
262
|
if (liveOnlyReason) {
|
|
226
263
|
return { skip: `live-only: "${modelId}" kept out of the static catalog (${liveOnlyReason})` };
|
|
@@ -264,6 +301,7 @@ function convertModel(modelId: string, m: ModelsDevModel): GeneratedModel | { sk
|
|
|
264
301
|
contextWindow: override?.contextWindow ?? contextWindow,
|
|
265
302
|
maxTokens: override?.maxTokens ?? maxTokens,
|
|
266
303
|
reasoningEffortValues,
|
|
304
|
+
...(override?.thinkingLevelMap ? { thinkingLevelMap: { ...override.thinkingLevelMap } } : {}),
|
|
267
305
|
compat: { ...NAN_COMPAT },
|
|
268
306
|
notes: [
|
|
269
307
|
NAN_COMPAT_NOTE,
|
|
@@ -301,8 +339,20 @@ async function main(): Promise<void> {
|
|
|
301
339
|
}
|
|
302
340
|
}
|
|
303
341
|
|
|
304
|
-
// Add manual-only models (not
|
|
342
|
+
// Add manual-only models (models.dev provider "nan" does not list them).
|
|
343
|
+
// When models.dev starts listing one, the manual entry is NOT emitted —
|
|
344
|
+
// otherwise the id would reach the catalog twice — and the reason plus the
|
|
345
|
+
// original manual provenance are attached to the models.dev-derived entry,
|
|
346
|
+
// so the manual data never disappears silently.
|
|
347
|
+
const entryById = new Map(entries.map((entry) => [entry.id, entry]));
|
|
305
348
|
for (const [modelId, detail] of Object.entries(MANUAL_ONLY_MODEL_IDS)) {
|
|
349
|
+
const existing = entryById.get(modelId);
|
|
350
|
+
if (existing) {
|
|
351
|
+
const note = `manual-only: "${modelId}" skipped: models.dev provider "${SOURCE_PROVIDER_ID}" now lists it, so the manual-only entry is not emitted and the models.dev values are used (${detail})`;
|
|
352
|
+
existing.notes = [...(existing.notes ?? []), note];
|
|
353
|
+
console.warn(`generate-models: skipped ${note}`);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
306
356
|
const override = MANUAL_OVERRIDES[modelId];
|
|
307
357
|
if (override) {
|
|
308
358
|
console.log(`generate-models: manual override for "${modelId}" (${Object.keys(override).filter((k) => k !== "note").join(", ")})`);
|
|
@@ -318,8 +368,9 @@ async function main(): Promise<void> {
|
|
|
318
368
|
const missingRequired = REQUIRED_MODEL_IDS.filter((id) => !entryIds.has(id));
|
|
319
369
|
if (missingRequired.length > 0) {
|
|
320
370
|
fail(
|
|
321
|
-
`required models missing from
|
|
322
|
-
"do NOT invent their data; confirm
|
|
371
|
+
`required models missing from the catalog: ${missingRequired.join(", ")} (official community chat models per ` +
|
|
372
|
+
"https://nan.builders/docs/models) — do NOT invent their data; confirm them in the docs and add them " +
|
|
373
|
+
"via MANUAL_OVERRIDES (models.dev lists them) or MANUAL_ONLY_MODEL_IDS (models.dev does not), with a source note.",
|
|
323
374
|
);
|
|
324
375
|
}
|
|
325
376
|
|
|
@@ -333,6 +384,9 @@ async function main(): Promise<void> {
|
|
|
333
384
|
...Object.entries(PROVIDER_REMOVED_MODEL_IDS).map(
|
|
334
385
|
([id, reason]) => `provider-removed: "${id}" excluded from the catalog (${reason})`,
|
|
335
386
|
),
|
|
387
|
+
...Object.entries(NON_CHAT_MODEL_IDS).map(
|
|
388
|
+
([id, reason]) => `non-chat: "${id}" excluded from the chat catalog (${reason})`,
|
|
389
|
+
),
|
|
336
390
|
...Object.entries(LIVE_ONLY_MODEL_IDS).map(
|
|
337
391
|
([id, reason]) => `live-only: "${id}" kept out of the static catalog (${reason})`,
|
|
338
392
|
),
|
|
@@ -37,13 +37,20 @@ export interface ManualModelOverride {
|
|
|
37
37
|
maxTokens?: number;
|
|
38
38
|
/** Reasoning effort values as declared by NaN docs. An empty array means the parameter is accepted but depth is model-managed (not adjustable by the user). */
|
|
39
39
|
reasoningEffortValues?: string[];
|
|
40
|
+
/**
|
|
41
|
+
* Mapping from pi-ai's user-facing thinking levels to model-specific effort
|
|
42
|
+
* strings. Only the "off" key is needed to suppress runaway reasoning.
|
|
43
|
+
* When `reasoning: "off"` maps to undefined in pi-ai, thinkingLevelMap.off
|
|
44
|
+
* supplies the effort value that NaN understands (e.g. "none" or "minimal").
|
|
45
|
+
*/
|
|
46
|
+
thinkingLevelMap?: Partial<Record<string, string | null>>;
|
|
40
47
|
/** Required provenance note; emitted verbatim onto the generated entry. */
|
|
41
48
|
note: string;
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
/**
|
|
45
|
-
*
|
|
46
|
-
* #controlling-reasoning, checked 2026-09-
|
|
52
|
+
* Manual reasoning effort values sourced from the NaN docs
|
|
53
|
+
* (https://nan.builders/docs/models #controlling-reasoning, checked 2026-09-29).
|
|
47
54
|
*
|
|
48
55
|
* models.dev has NO `reasoning_effort_values` field — only `reasoning_options`
|
|
49
56
|
* (which is [{type:"toggle"}] or []), so this mapping must be hand-maintained
|
|
@@ -53,28 +60,48 @@ export interface ManualModelOverride {
|
|
|
53
60
|
*
|
|
54
61
|
* Per-model contract from the docs:
|
|
55
62
|
* - glm5.3, glm5.3-flash: fully controllable (low/medium/high/max)
|
|
63
|
+
* + minimal also accepted (issue #16, measured 2026-09-27): minimal
|
|
64
|
+
* yields 38 reasoning tokens vs 2,288 for high — the docs simply
|
|
65
|
+
* omitted it because it was not originally advertised, but it IS
|
|
66
|
+
* accepted and effective by the gateway.
|
|
56
67
|
* - qwen3.6, gemma4: none/minimal skip reasoning, others cap depth
|
|
57
|
-
* - deepseek-v4-flash: any value accepted but
|
|
58
|
-
*
|
|
68
|
+
* - deepseek-v4-flash: any value accepted but only "none" deterministically
|
|
69
|
+
* disables reasoning (issue #16, measured 2026-09-27); every other value
|
|
70
|
+
* (minimal/low/medium/high/omitted) leaves 13-14K reasoning tokens with
|
|
71
|
+
* runaway reasoning and finish_reason:"length" with ZERO answer. Since only
|
|
72
|
+
* "none" has a measurable effect, the catalog declares ["none"].
|
|
73
|
+
* - qwen3.8-flash, mimo-v2.6-flash: accepted, depth not adjustable
|
|
74
|
+
*
|
|
75
|
+
* (mimo-v2.5, which used to share that last row, was removed by NaN — absent
|
|
76
|
+
* from the docs and from openapi.json, checked 2026-09-29.)
|
|
59
77
|
*
|
|
60
78
|
* A model with reasoning_effort_values=[] means the parameter is accepted but
|
|
61
79
|
* the model manages its own reasoning depth — it is never an error.
|
|
62
80
|
*/
|
|
63
81
|
export const REASONING_EFFORT_VALUES: Record<string, string[]> = {
|
|
64
82
|
"glm5.3": ["low", "medium", "high", "max"],
|
|
65
|
-
"glm5.3-flash": ["low", "medium", "high", "max"],
|
|
83
|
+
"glm5.3-flash": ["minimal", "low", "medium", "high", "max"],
|
|
66
84
|
"qwen3.6": ["none", "minimal", "low", "medium", "high", "max"],
|
|
67
85
|
"gemma4": ["none", "minimal", "low", "medium", "high", "max"],
|
|
68
86
|
};
|
|
69
87
|
|
|
70
88
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
89
|
+
* Provenance for the manual-only mimo-v2.6-flash entry, shared by
|
|
90
|
+
* MANUAL_ONLY_MODEL_IDS (generator) and MANUAL_ONLY_MODELS (tests) so both
|
|
91
|
+
* sides assert the exact same string.
|
|
92
|
+
*/
|
|
93
|
+
const MANUAL_ONLY_NOTE =
|
|
94
|
+
"omnimodal model (text, image, audio input) served by NaN; limits and modalities from https://nan.builders/docs/models#mimo-v2-6-flash (checked 2026-09-25, quotas re-checked 2026-09-29): 1,048,576 context / 131,072 max output / 1.0B monthly quota per member. Reasoning: accepted, depth not adjustable (docs table, checked 2026-09-29). Tool calling: yes. Streaming: yes. Input modalities (pi-representable): text, image (audio not representable in pi's Model type). Fallback entry: emitted only while models.dev provider nan does not list the model — models.dev started listing it on 2026-09-29, so the generator then keeps the models.dev values and attaches this note to that entry instead of emitting a second one.";
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Models NaN serves that models.dev provider nan may not list. Mirrors the
|
|
98
|
+
* generator's MANUAL_ONLY_MODEL_IDS — kept in sync so the generator and the
|
|
99
|
+
* tests share the same source. An entry stops being emitted as soon as
|
|
100
|
+
* models.dev lists the model; the note above then rides on the models.dev
|
|
101
|
+
* entry, so the manual data never vanishes silently.
|
|
74
102
|
*/
|
|
75
103
|
export const MANUAL_ONLY_MODEL_IDS: Record<string, string> = {
|
|
76
|
-
"mimo-v2.6-flash":
|
|
77
|
-
"omnimodal model (text, image, audio input) served by NaN, not yet listed on models.dev provider nan (checked 2026-09-25); included so the live /models refresh does not hand it UNKNOWN_MODEL_LIMITS. Same limits as mimo-v2.5: 1,048,576 / 131,072 / 1.0B monthly quota per member. Reasoning: accepted, depth not adjustable. Tool calling: yes. Streaming: yes. Input modalities (pi-representable): text, image (audio not representable in pi's Model type).",
|
|
104
|
+
"mimo-v2.6-flash": MANUAL_ONLY_NOTE,
|
|
78
105
|
};
|
|
79
106
|
|
|
80
107
|
/**
|
|
@@ -89,15 +116,52 @@ export const MANUAL_ONLY_MODELS: Record<string, ManualModelOverride> = {
|
|
|
89
116
|
reasoningEffortValues: [],
|
|
90
117
|
contextWindow: 1_048_576,
|
|
91
118
|
maxTokens: 131_072,
|
|
92
|
-
note:
|
|
119
|
+
note: MANUAL_ONLY_NOTE,
|
|
93
120
|
},
|
|
94
121
|
};
|
|
95
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Manual overrides keyed by model id. Fields override the models.dev-derived
|
|
125
|
+
* entry one-for-one. Every override carries a provenance note.
|
|
126
|
+
*
|
|
127
|
+
* Issue #16 (measured 2026-09-27 against live gateway, repro included):
|
|
128
|
+
* - deepseek-v4-flash: reasoning_effort_values=["none"] (only "none" suppresses);
|
|
129
|
+
* thinkingLevelMap={off:"none"} (supplies reasoning_effort when pi maps
|
|
130
|
+
* reasoning:"off" → undefined).
|
|
131
|
+
* - glm5.3-flash: reasoningEffortValues includes "minimal" (not documented but
|
|
132
|
+
* effective); thinkingLevelMap={off:"minimal"} (supplies reasoning_effort
|
|
133
|
+
* when pi maps reasoning:"off" → undefined).
|
|
134
|
+
* - qwen3.6, gemma4: the NaN docs already document `none` as "skip the
|
|
135
|
+
* reasoning phase entirely" and state that with no parameter the model
|
|
136
|
+
* defaults to reasoning ON (16,384-token budget), so off must send
|
|
137
|
+
* `none` explicitly — thinkingLevelMap={off:"none"} (NaN docs
|
|
138
|
+
* #controlling-reasoning, checked 2026-10-01). No measurement needed: the
|
|
139
|
+
* docs are explicit about both the default and the `none` behavior.
|
|
140
|
+
*/
|
|
96
141
|
export const MANUAL_OVERRIDES: Record<string, ManualModelOverride> = {
|
|
97
142
|
"deepseek-v4-flash": {
|
|
98
143
|
input: ["text", "image"],
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
note:
|
|
144
|
+
reasoningEffortValues: ["none"],
|
|
145
|
+
thinkingLevelMap: { off: "none" },
|
|
146
|
+
note:
|
|
147
|
+
"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. " +
|
|
148
|
+
"reasoning_effort_values=[\"none\"] (issue #16, measured 2026-09-27 against live gateway, repro included in the issue): only \"none\" deterministically disables reasoning (0 reasoning tokens, answer produced). Every other value leaves 13-14K reasoning tokens and causes runaway reasoning (finish_reason:\"length\" with ZERO answer text). The catalog declares only [\"none\"] because the remaining values are useless noise. " +
|
|
149
|
+
"thinkingLevelMap { off: \"none\" } (issue #16, measured 2026-09-27): when pi maps reasoning:\"off\" to reasoningEffort:undefined, the thinkingLevelMap.off=\"none\" supplies the effort so the gateway receives reasoning_effort:\"none\" and does NOT enter runaway reasoning mode.",
|
|
150
|
+
},
|
|
151
|
+
"glm5.3-flash": {
|
|
152
|
+
thinkingLevelMap: { off: "minimal" },
|
|
153
|
+
note:
|
|
154
|
+
"thinkingLevelMap { off: \"minimal\" } (issue #16, measured 2026-09-27 against live gateway, repro included in the issue): when pi maps reasoning:\"off\" to reasoningEffort:undefined, the thinkingLevelMap.off=\"minimal\" supplies the effort so the gateway receives reasoning_effort:\"minimal\" (38 reasoning tokens + answer). Note: \"none\" does NOT suppress reasoning on glm5.3-flash (13,382 tokens) — only \"minimal\" was measured to be effective. " +
|
|
155
|
+
"reasoning_effort_values includes \"minimal\" (not documented by NaN docs but accepted and effective by the gateway, issue #16).",
|
|
156
|
+
},
|
|
157
|
+
"qwen3.6": {
|
|
158
|
+
thinkingLevelMap: { off: "none" },
|
|
159
|
+
note:
|
|
160
|
+
"thinkingLevelMap { off: \"none\" } (issue #16): NaN docs state that with no reasoning_effort parameter every model uses its own default and that default is reasoning ON for qwen3.6 (16,384-token budget), while `none` and `minimal` skip the reasoning phase entirely (https://nan.builders/docs/models#controlling-reasoning, checked 2026-10-01). pi maps reasoning:\"off\" to reasoningEffort:undefined, so without this map `thinking: off` sent no parameter and the model reasoned anyway; the map sends reasoning_effort:\"none\" and actually disables reasoning.",
|
|
161
|
+
},
|
|
162
|
+
"gemma4": {
|
|
163
|
+
thinkingLevelMap: { off: "none" },
|
|
164
|
+
note:
|
|
165
|
+
"thinkingLevelMap { off: \"none\" } (issue #16): NaN docs state that with no reasoning_effort parameter gemma4 defaults to reasoning ON (16,384-token budget), while `none`/`minimal` skip reasoning entirely (https://nan.builders/docs/models#controlling-reasoning, checked 2026-10-01). pi maps reasoning:\"off\" to reasoningEffort:undefined, so without this map `thinking: off` sent no parameter and the model reasoned anyway; the map sends reasoning_effort:\"none\" and actually disables reasoning.",
|
|
102
166
|
},
|
|
103
167
|
};
|
|
@@ -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-
|
|
4
|
+
// Source: https://models.dev/api.json (provider "nan"), fetched 2026-10-01T10:18:48.654Z
|
|
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.
|
|
@@ -28,7 +28,12 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
28
28
|
},
|
|
29
29
|
"contextWindow": 1000000,
|
|
30
30
|
"maxTokens": 384000,
|
|
31
|
-
"reasoningEffortValues": [
|
|
31
|
+
"reasoningEffortValues": [
|
|
32
|
+
"none"
|
|
33
|
+
],
|
|
34
|
+
"thinkingLevelMap": {
|
|
35
|
+
"off": "none"
|
|
36
|
+
},
|
|
32
37
|
"compat": {
|
|
33
38
|
"supportsDeveloperRole": false,
|
|
34
39
|
"supportsReasoningEffort": true,
|
|
@@ -38,7 +43,7 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
38
43
|
},
|
|
39
44
|
"notes": [
|
|
40
45
|
"compat matches the maintainer's working ~/.pi/agent/models.json LiteLLM config for api.nan.builders (2026-09-04): supportsDeveloperRole false, supportsReasoningEffort true, maxTokensField max_tokens. NaN's docs example sets only supportsDeveloperRole: true and is not battle-tested. supportsFinishReason true (2026-09-13, issue #2): the LiteLLM gateway intermittently closes SSE streams before emitting finish_reason; with true pi-ai raises 'Stream ended without finish_reason', which matches pi-ai's retryable-provider pattern ('ended without') and is retried automatically, whereas false silently synthesized stop/toolUse and stalled the turn mid-answer. supportsUsageInStreaming true (2026-09-16, issue #7): NaN's published schema is silent about stream_options, but the live gateway honors it — two identical streaming calls per model, differing only in stream_options: { include_usage: true }, returned 0 usage chunks without it and exactly 1 with it (prompt/completion/reasoning/cached token counts) on deepseek-v4-flash, glm5.3-flash, qwen3.6, mimo-v2.5 and gemma4, and a real pi session then recorded token counts where it recorded zeros. pi-ai only sends stream_options when this is not false, and the sanitizer forwards it when the model declares true, so chat models opt in by default and usage is reported (issue #4). A model that does not report streaming usage can still opt out per model with a models.json compat override (supportsUsageInStreaming: false); the sanitizer then strips stream_options and the payload stays strict.",
|
|
41
|
-
"input includes image: NaN serves the Vision-Exp variant ('takes images as input', https://nan.builders/docs/models; the image_url content-parts in https://nan.builders/openapi.json list deepseek-v4-flash among the vision models). models.dev provider nan also lists text+image now (DeepSeek V4.1 Flash entry, checked 2026-09-13; its 2026-09-07 snapshot listed text only), so this override is kept as a pin for the vision capability rather than as a divergence. reasoning_effort_values=[]
|
|
46
|
+
"input includes image: NaN serves the Vision-Exp variant ('takes images as input', https://nan.builders/docs/models; the image_url content-parts in https://nan.builders/openapi.json list deepseek-v4-flash among the vision models). models.dev provider nan also lists text+image now (DeepSeek V4.1 Flash entry, checked 2026-09-13; its 2026-09-07 snapshot listed text only), so this override is kept as a pin for the vision capability rather than as a divergence. reasoning_effort_values=[\"none\"] (issue #16, measured 2026-09-27 against live gateway, repro included in the issue): only \"none\" deterministically disables reasoning (0 reasoning tokens, answer produced). Every other value leaves 13-14K reasoning tokens and causes runaway reasoning (finish_reason:\"length\" with ZERO answer text). The catalog declares only [\"none\"] because the remaining values are useless noise. thinkingLevelMap { off: \"none\" } (issue #16, measured 2026-09-27): when pi maps reasoning:\"off\" to reasoningEffort:undefined, the thinkingLevelMap.off=\"none\" supplies the effort so the gateway receives reasoning_effort:\"none\" and does NOT enter runaway reasoning mode."
|
|
42
47
|
],
|
|
43
48
|
"extras": {
|
|
44
49
|
"id": "deepseek-v4-flash",
|
|
@@ -71,7 +76,8 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
71
76
|
"cost": {
|
|
72
77
|
"input": 0,
|
|
73
78
|
"output": 0
|
|
74
|
-
}
|
|
79
|
+
},
|
|
80
|
+
"canonical_model_id": "deepseek/deepseek-v4.1-flash"
|
|
75
81
|
}
|
|
76
82
|
},
|
|
77
83
|
{
|
|
@@ -98,6 +104,9 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
98
104
|
"high",
|
|
99
105
|
"max"
|
|
100
106
|
],
|
|
107
|
+
"thinkingLevelMap": {
|
|
108
|
+
"off": "none"
|
|
109
|
+
},
|
|
101
110
|
"compat": {
|
|
102
111
|
"supportsDeveloperRole": false,
|
|
103
112
|
"supportsReasoningEffort": true,
|
|
@@ -106,7 +115,8 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
106
115
|
"maxTokensField": "max_tokens"
|
|
107
116
|
},
|
|
108
117
|
"notes": [
|
|
109
|
-
"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."
|
|
118
|
+
"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.",
|
|
119
|
+
"thinkingLevelMap { off: \"none\" } (issue #16): NaN docs state that with no reasoning_effort parameter gemma4 defaults to reasoning ON (16,384-token budget), while `none`/`minimal` skip reasoning entirely (https://nan.builders/docs/models#controlling-reasoning, checked 2026-10-01). pi maps reasoning:\"off\" to reasoningEffort:undefined, so without this map `thinking: off` sent no parameter and the model reasoned anyway; the map sends reasoning_effort:\"none\" and actually disables reasoning."
|
|
110
120
|
],
|
|
111
121
|
"extras": {
|
|
112
122
|
"id": "gemma4",
|
|
@@ -117,7 +127,14 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
117
127
|
"reasoning": true,
|
|
118
128
|
"reasoning_options": [
|
|
119
129
|
{
|
|
120
|
-
"type": "
|
|
130
|
+
"type": "effort",
|
|
131
|
+
"values": [
|
|
132
|
+
"none",
|
|
133
|
+
"low",
|
|
134
|
+
"medium",
|
|
135
|
+
"high",
|
|
136
|
+
"max"
|
|
137
|
+
]
|
|
121
138
|
}
|
|
122
139
|
],
|
|
123
140
|
"tool_call": true,
|
|
@@ -142,7 +159,8 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
142
159
|
"cost": {
|
|
143
160
|
"input": 0,
|
|
144
161
|
"output": 0
|
|
145
|
-
}
|
|
162
|
+
},
|
|
163
|
+
"canonical_model_id": "google/gemma-4-26b-a4b-it"
|
|
146
164
|
}
|
|
147
165
|
},
|
|
148
166
|
{
|
|
@@ -162,11 +180,15 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
162
180
|
"contextWindow": 1000000,
|
|
163
181
|
"maxTokens": 131072,
|
|
164
182
|
"reasoningEffortValues": [
|
|
183
|
+
"minimal",
|
|
165
184
|
"low",
|
|
166
185
|
"medium",
|
|
167
186
|
"high",
|
|
168
187
|
"max"
|
|
169
188
|
],
|
|
189
|
+
"thinkingLevelMap": {
|
|
190
|
+
"off": "minimal"
|
|
191
|
+
},
|
|
170
192
|
"compat": {
|
|
171
193
|
"supportsDeveloperRole": false,
|
|
172
194
|
"supportsReasoningEffort": true,
|
|
@@ -175,7 +197,8 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
175
197
|
"maxTokensField": "max_tokens"
|
|
176
198
|
},
|
|
177
199
|
"notes": [
|
|
178
|
-
"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."
|
|
200
|
+
"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.",
|
|
201
|
+
"thinkingLevelMap { off: \"minimal\" } (issue #16, measured 2026-09-27 against live gateway, repro included in the issue): when pi maps reasoning:\"off\" to reasoningEffort:undefined, the thinkingLevelMap.off=\"minimal\" supplies the effort so the gateway receives reasoning_effort:\"minimal\" (38 reasoning tokens + answer). Note: \"none\" does NOT suppress reasoning on glm5.3-flash (13,382 tokens) — only \"minimal\" was measured to be effective. reasoning_effort_values includes \"minimal\" (not documented by NaN docs but accepted and effective by the gateway, issue #16)."
|
|
179
202
|
],
|
|
180
203
|
"extras": {
|
|
181
204
|
"id": "glm5.3-flash",
|
|
@@ -184,7 +207,17 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
184
207
|
"family": "glm-flash",
|
|
185
208
|
"attachment": true,
|
|
186
209
|
"reasoning": true,
|
|
187
|
-
"reasoning_options": [
|
|
210
|
+
"reasoning_options": [
|
|
211
|
+
{
|
|
212
|
+
"type": "effort",
|
|
213
|
+
"values": [
|
|
214
|
+
"low",
|
|
215
|
+
"medium",
|
|
216
|
+
"high",
|
|
217
|
+
"max"
|
|
218
|
+
]
|
|
219
|
+
}
|
|
220
|
+
],
|
|
188
221
|
"tool_call": true,
|
|
189
222
|
"structured_output": true,
|
|
190
223
|
"temperature": true,
|
|
@@ -207,12 +240,13 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
207
240
|
"cost": {
|
|
208
241
|
"input": 0,
|
|
209
242
|
"output": 0
|
|
210
|
-
}
|
|
243
|
+
},
|
|
244
|
+
"canonical_model_id": "zhipuai/glm-5.3-flash"
|
|
211
245
|
}
|
|
212
246
|
},
|
|
213
247
|
{
|
|
214
|
-
"id": "mimo-v2.
|
|
215
|
-
"name": "MiMo-V2.
|
|
248
|
+
"id": "mimo-v2.6-flash",
|
|
249
|
+
"name": "MiMo-V2.6-Flash",
|
|
216
250
|
"reasoning": true,
|
|
217
251
|
"input": [
|
|
218
252
|
"text",
|
|
@@ -235,21 +269,21 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
235
269
|
"maxTokensField": "max_tokens"
|
|
236
270
|
},
|
|
237
271
|
"notes": [
|
|
238
|
-
"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."
|
|
272
|
+
"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.",
|
|
273
|
+
"manual-only: \"mimo-v2.6-flash\" skipped: models.dev provider \"nan\" now lists it, so the manual-only entry is not emitted and the models.dev values are used (omnimodal model (text, image, audio input) served by NaN; limits and modalities from https://nan.builders/docs/models#mimo-v2-6-flash (checked 2026-09-25, quotas re-checked 2026-09-29): 1,048,576 context / 131,072 max output / 1.0B monthly quota per member. Reasoning: accepted, depth not adjustable (docs table, checked 2026-09-29). Tool calling: yes. Streaming: yes. Input modalities (pi-representable): text, image (audio not representable in pi's Model type). Fallback entry: emitted only while models.dev provider nan does not list the model — models.dev started listing it on 2026-09-29, so the generator then keeps the models.dev values and attaches this note to that entry instead of emitting a second one.)"
|
|
239
274
|
],
|
|
240
275
|
"extras": {
|
|
241
|
-
"id": "mimo-v2.
|
|
242
|
-
"name": "MiMo-V2.
|
|
243
|
-
"description": "
|
|
276
|
+
"id": "mimo-v2.6-flash",
|
|
277
|
+
"name": "MiMo-V2.6-Flash",
|
|
278
|
+
"description": "MiMo Flash model for multimodal coding agents and long-context automation",
|
|
244
279
|
"family": "mimo",
|
|
245
280
|
"attachment": true,
|
|
246
281
|
"reasoning": true,
|
|
247
282
|
"reasoning_options": [],
|
|
248
283
|
"tool_call": true,
|
|
249
284
|
"temperature": true,
|
|
250
|
-
"
|
|
251
|
-
"
|
|
252
|
-
"last_updated": "2026-04-22",
|
|
285
|
+
"release_date": "2026-09-22",
|
|
286
|
+
"last_updated": "2026-09-22",
|
|
253
287
|
"modalities": {
|
|
254
288
|
"input": [
|
|
255
289
|
"text",
|
|
@@ -268,38 +302,10 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
268
302
|
"cost": {
|
|
269
303
|
"input": 0,
|
|
270
304
|
"output": 0
|
|
271
|
-
}
|
|
305
|
+
},
|
|
306
|
+
"canonical_model_id": "xiaomi/mimo-v2.6-flash"
|
|
272
307
|
}
|
|
273
308
|
},
|
|
274
|
-
{
|
|
275
|
-
"id": "mimo-v2.6-flash",
|
|
276
|
-
"name": "mimo-v2.6-flash",
|
|
277
|
-
"reasoning": true,
|
|
278
|
-
"input": [
|
|
279
|
-
"text",
|
|
280
|
-
"image"
|
|
281
|
-
],
|
|
282
|
-
"cost": {
|
|
283
|
-
"input": 0,
|
|
284
|
-
"output": 0,
|
|
285
|
-
"cacheRead": 0,
|
|
286
|
-
"cacheWrite": 0
|
|
287
|
-
},
|
|
288
|
-
"contextWindow": 1048576,
|
|
289
|
-
"maxTokens": 131072,
|
|
290
|
-
"compat": {
|
|
291
|
-
"supportsDeveloperRole": false,
|
|
292
|
-
"supportsReasoningEffort": true,
|
|
293
|
-
"supportsUsageInStreaming": true,
|
|
294
|
-
"supportsFinishReason": true,
|
|
295
|
-
"maxTokensField": "max_tokens"
|
|
296
|
-
},
|
|
297
|
-
"notes": [
|
|
298
|
-
"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.",
|
|
299
|
-
"manual-only: \"mimo-v2.6-flash\" not yet on models.dev provider nan (omnimodal model (text, image, audio input) served by NaN, not yet listed on models.dev provider nan (checked 2026-09-25); included so the live /models refresh does not hand it UNKNOWN_MODEL_LIMITS. Same limits as mimo-v2.5: 1,048,576 / 131,072 / 1.0B monthly quota per member. Reasoning: accepted, depth not adjustable. Tool calling: yes. Streaming: yes. Input modalities (pi-representable): text, image (audio not representable in pi's Model type).)."
|
|
300
|
-
],
|
|
301
|
-
"extras": {}
|
|
302
|
-
},
|
|
303
309
|
{
|
|
304
310
|
"id": "qwen3.6",
|
|
305
311
|
"name": "Qwen3.6 35B-A3B",
|
|
@@ -324,6 +330,9 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
324
330
|
"high",
|
|
325
331
|
"max"
|
|
326
332
|
],
|
|
333
|
+
"thinkingLevelMap": {
|
|
334
|
+
"off": "none"
|
|
335
|
+
},
|
|
327
336
|
"compat": {
|
|
328
337
|
"supportsDeveloperRole": false,
|
|
329
338
|
"supportsReasoningEffort": true,
|
|
@@ -332,7 +341,8 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
332
341
|
"maxTokensField": "max_tokens"
|
|
333
342
|
},
|
|
334
343
|
"notes": [
|
|
335
|
-
"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."
|
|
344
|
+
"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.",
|
|
345
|
+
"thinkingLevelMap { off: \"none\" } (issue #16): NaN docs state that with no reasoning_effort parameter every model uses its own default and that default is reasoning ON for qwen3.6 (16,384-token budget), while `none` and `minimal` skip the reasoning phase entirely (https://nan.builders/docs/models#controlling-reasoning, checked 2026-10-01). pi maps reasoning:\"off\" to reasoningEffort:undefined, so without this map `thinking: off` sent no parameter and the model reasoned anyway; the map sends reasoning_effort:\"none\" and actually disables reasoning."
|
|
336
346
|
],
|
|
337
347
|
"extras": {
|
|
338
348
|
"id": "qwen3.6",
|
|
@@ -343,7 +353,14 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
343
353
|
"reasoning": true,
|
|
344
354
|
"reasoning_options": [
|
|
345
355
|
{
|
|
346
|
-
"type": "
|
|
356
|
+
"type": "effort",
|
|
357
|
+
"values": [
|
|
358
|
+
"none",
|
|
359
|
+
"low",
|
|
360
|
+
"medium",
|
|
361
|
+
"high",
|
|
362
|
+
"max"
|
|
363
|
+
]
|
|
347
364
|
}
|
|
348
365
|
],
|
|
349
366
|
"tool_call": true,
|
|
@@ -368,7 +385,8 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
368
385
|
"cost": {
|
|
369
386
|
"input": 0,
|
|
370
387
|
"output": 0
|
|
371
|
-
}
|
|
388
|
+
},
|
|
389
|
+
"canonical_model_id": "alibaba/qwen3.6-35b-a3b"
|
|
372
390
|
}
|
|
373
391
|
},
|
|
374
392
|
{
|
|
@@ -428,7 +446,8 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
428
446
|
"cost": {
|
|
429
447
|
"input": 0,
|
|
430
448
|
"output": 0
|
|
431
|
-
}
|
|
449
|
+
},
|
|
450
|
+
"canonical_model_id": "alibaba/qwen3.8-flash"
|
|
432
451
|
}
|
|
433
452
|
}
|
|
434
453
|
];
|
|
@@ -436,15 +455,20 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
436
455
|
export const GENERATED_CATALOG_META = {
|
|
437
456
|
source: "https://models.dev/api.json",
|
|
438
457
|
modelsDevProvider: "nan",
|
|
439
|
-
fetchedAt: "2026-
|
|
440
|
-
modelCount:
|
|
441
|
-
models: ["deepseek-v4-flash","gemma4","glm5.3-flash","mimo-v2.
|
|
458
|
+
fetchedAt: "2026-10-01T10:18:48.654Z",
|
|
459
|
+
modelCount: 6,
|
|
460
|
+
models: ["deepseek-v4-flash","gemma4","glm5.3-flash","mimo-v2.6-flash","qwen3.6","qwen3.8-flash"],
|
|
442
461
|
notes: [
|
|
462
|
+
"non-chat: \"qwen-image-2.1\" excluded from the chat catalog (image-generation model (text→image; output modality image) documented in https://nan.builders/docs/models and https://nan.builders/openapi.json (checked 2026-09-29) — not a chat model, out of the static chat catalog (models.dev reports limit.output 0))",
|
|
443
463
|
"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)",
|
|
444
464
|
"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)",
|
|
465
|
+
"provider-removed: \"mimo-v2.5\" excluded from the catalog (removed by NaN (checked 2026-09-29): zero mentions in https://nan.builders/docs/models and in the model param description of https://nan.builders/openapi.json, and models.dev provider nan dropped it as well — superseded by mimo-v2.6-flash; excluded so regeneration does not resurrect it)",
|
|
445
466
|
"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.",
|
|
446
|
-
"input includes image: NaN serves the Vision-Exp variant ('takes images as input', https://nan.builders/docs/models; the image_url content-parts in https://nan.builders/openapi.json list deepseek-v4-flash among the vision models). models.dev provider nan also lists text+image now (DeepSeek V4.1 Flash entry, checked 2026-09-13; its 2026-09-07 snapshot listed text only), so this override is kept as a pin for the vision capability rather than as a divergence. reasoning_effort_values=[]
|
|
447
|
-
"
|
|
467
|
+
"input includes image: NaN serves the Vision-Exp variant ('takes images as input', https://nan.builders/docs/models; the image_url content-parts in https://nan.builders/openapi.json list deepseek-v4-flash among the vision models). models.dev provider nan also lists text+image now (DeepSeek V4.1 Flash entry, checked 2026-09-13; its 2026-09-07 snapshot listed text only), so this override is kept as a pin for the vision capability rather than as a divergence. reasoning_effort_values=[\"none\"] (issue #16, measured 2026-09-27 against live gateway, repro included in the issue): only \"none\" deterministically disables reasoning (0 reasoning tokens, answer produced). Every other value leaves 13-14K reasoning tokens and causes runaway reasoning (finish_reason:\"length\" with ZERO answer text). The catalog declares only [\"none\"] because the remaining values are useless noise. thinkingLevelMap { off: \"none\" } (issue #16, measured 2026-09-27): when pi maps reasoning:\"off\" to reasoningEffort:undefined, the thinkingLevelMap.off=\"none\" supplies the effort so the gateway receives reasoning_effort:\"none\" and does NOT enter runaway reasoning mode.",
|
|
468
|
+
"thinkingLevelMap { off: \"none\" } (issue #16): NaN docs state that with no reasoning_effort parameter gemma4 defaults to reasoning ON (16,384-token budget), while `none`/`minimal` skip reasoning entirely (https://nan.builders/docs/models#controlling-reasoning, checked 2026-10-01). pi maps reasoning:\"off\" to reasoningEffort:undefined, so without this map `thinking: off` sent no parameter and the model reasoned anyway; the map sends reasoning_effort:\"none\" and actually disables reasoning.",
|
|
469
|
+
"thinkingLevelMap { off: \"minimal\" } (issue #16, measured 2026-09-27 against live gateway, repro included in the issue): when pi maps reasoning:\"off\" to reasoningEffort:undefined, the thinkingLevelMap.off=\"minimal\" supplies the effort so the gateway receives reasoning_effort:\"minimal\" (38 reasoning tokens + answer). Note: \"none\" does NOT suppress reasoning on glm5.3-flash (13,382 tokens) — only \"minimal\" was measured to be effective. reasoning_effort_values includes \"minimal\" (not documented by NaN docs but accepted and effective by the gateway, issue #16).",
|
|
470
|
+
"manual-only: \"mimo-v2.6-flash\" skipped: models.dev provider \"nan\" now lists it, so the manual-only entry is not emitted and the models.dev values are used (omnimodal model (text, image, audio input) served by NaN; limits and modalities from https://nan.builders/docs/models#mimo-v2-6-flash (checked 2026-09-25, quotas re-checked 2026-09-29): 1,048,576 context / 131,072 max output / 1.0B monthly quota per member. Reasoning: accepted, depth not adjustable (docs table, checked 2026-09-29). Tool calling: yes. Streaming: yes. Input modalities (pi-representable): text, image (audio not representable in pi's Model type). Fallback entry: emitted only while models.dev provider nan does not list the model — models.dev started listing it on 2026-09-29, so the generator then keeps the models.dev values and attaches this note to that entry instead of emitting a second one.)",
|
|
471
|
+
"thinkingLevelMap { off: \"none\" } (issue #16): NaN docs state that with no reasoning_effort parameter every model uses its own default and that default is reasoning ON for qwen3.6 (16,384-token budget), while `none` and `minimal` skip the reasoning phase entirely (https://nan.builders/docs/models#controlling-reasoning, checked 2026-10-01). pi maps reasoning:\"off\" to reasoningEffort:undefined, so without this map `thinking: off` sent no parameter and the model reasoned anyway; the map sends reasoning_effort:\"none\" and actually disables reasoning.",
|
|
448
472
|
"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)."
|
|
449
473
|
],
|
|
450
474
|
} as const;
|