@gtrabanco/pi-nan-provider 0.10.2 → 0.10.3
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 +10 -3
- package/README.es.md +2 -0
- package/README.md +2 -0
- package/package.json +1 -1
- package/scripts/generate-models.ts +15 -12
- package/scripts/models.generated.ts +7 -2
- package/src/fetch-models.ts +72 -16
package/AGENTS.md
CHANGED
|
@@ -198,9 +198,16 @@ Every PR that changes code MUST bump `package.json` version in the same PR; CI p
|
|
|
198
198
|
entry) so the id is never emitted twice.
|
|
199
199
|
Non-chat endpoints: qwen3-embedding, rerank, kokoro (TTS), whisper (STT),
|
|
200
200
|
flux-2-klein and qwen-image-2.1 (images) — MCP-bridge territory, not chat
|
|
201
|
-
catalog models
|
|
202
|
-
`
|
|
203
|
-
|
|
201
|
+
catalog models. The classification is now applied at RUNTIME too:
|
|
202
|
+
`NON_CHAT_MODEL_IDS` is exported from `src/fetch-models.ts` (single source
|
|
203
|
+
of truth: id → provenance reason), imported by `scripts/generate-models.ts`
|
|
204
|
+
(one definition, no duplicate), enforced by `mergeLiveWithGenerated` (those
|
|
205
|
+
ids land in the `nonChat` bucket and never enter the registry), and filtered
|
|
206
|
+
defensively by `baselineModels`. The generator excluded `qwen-image-2.1` via
|
|
207
|
+
this constant before (it also sits on models.dev with `limit.output: 0`,
|
|
208
|
+
excluded instead of being flagged "needs manual verification" forever); all
|
|
209
|
+
six ids are now under the same guard so they can never be emitted as chat
|
|
210
|
+
models or registered at runtime.
|
|
204
211
|
- NaN can close an SSE stream **before** `finish_reason`. The catalog sets
|
|
205
212
|
`supportsFinishReason: true` so pi-ai raises the retryable
|
|
206
213
|
`Stream ended without finish_reason` (pi-ai's `RETRYABLE_PROVIDER_ERROR_PATTERN`
|
package/README.es.md
CHANGED
|
@@ -41,6 +41,8 @@ El proveedor utiliza un **catálogo de modelos de dos capas** para garantizar la
|
|
|
41
41
|
> [!IMPORTANT]
|
|
42
42
|
> **Detección de Tier**: La lista en vivo es la autoridad. Si tu clave tiene acceso premium, esos modelos aparecerán automáticamente; de lo contrario, se filtran.
|
|
43
43
|
|
|
44
|
+
Los ids no-chat que devuelve `/models` en vivo (embedding, rerank, TTS, STT, imagen) **no se registran** como modelos de chat — la clasificación es el único `NON_CHAT_MODEL_IDS` exportado desde `src/fetch-models.ts` (la misma fuente que usa el generador en build-time). La fusión salta sus marcadores y los coloca en un bucket observable `nonChat`; `baselineModels` también los filtra como capa defensiva. Esto evita que el selector de chat de pi ofrezca endpoints que no pueden servir chat (las peticiones devolverían 404).
|
|
45
|
+
|
|
44
46
|
El registro es síncrono a propósito: el catálogo de fallback está disponible al instante, y el runtime de Models de pi dirige el refresco en vivo (refresco de red en el arranque interactivo y periódico, solo caché en el registro), persistiendo el overlay entre ejecuciones.
|
|
45
47
|
|
|
46
48
|
### 🧠 Seguridad al cambiar de modelo (guard de razonamiento cross-model)
|
package/README.md
CHANGED
|
@@ -41,6 +41,8 @@ The provider uses a **two-layer model catalog** to ensure reliability:
|
|
|
41
41
|
> [!IMPORTANT]
|
|
42
42
|
> **Tier Detection**: The live list is authoritative. If your key has premium access, those models will appear automatically; otherwise, they are filtered out.
|
|
43
43
|
|
|
44
|
+
NaN's live `/models` also returns non-chat endpoint ids (embedding, rerank, TTS, STT, image). These are **not registered** as chat models — the classification is the single exported `NON_CHAT_MODEL_IDS` from `src/fetch-models.ts` (the same source used by the build-time generator). The merge skips their placeholders and places them in an observable `nonChat` bucket; `baselineModels` also filters them defensively. This prevents pi's chat picker from offering endpoints that cannot serve chat (requests would 404).
|
|
45
|
+
|
|
44
46
|
The registration is synchronous on purpose: the generated fallback catalog is available immediately, and pi's Models runtime drives the live refresh (network refresh at interactive startup and periodically, cache-only at registration), persisting the overlay between runs.
|
|
45
47
|
|
|
46
48
|
### 🧠 Model-switch safety (cross-model reasoning guard)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtrabanco/pi-nan-provider",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
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",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
* MANUAL_ONLY entry — or this script exits non-zero.
|
|
26
26
|
* - Any other models.dev entry missing `limit.context`/`limit.output` is
|
|
27
27
|
* skipped and flagged "needs manual verification" — never guessed. Entries
|
|
28
|
-
* that are not chat models are excluded via NON_CHAT_MODEL_IDS instead
|
|
28
|
+
* that are not chat models are excluded via NON_CHAT_MODEL_IDS instead
|
|
29
|
+
* (defined in `src/fetch-models.ts` and also enforced at runtime).
|
|
29
30
|
* - models.dev modalities are intersected with pi's supported input set
|
|
30
31
|
* ("text" | "image"); e.g. mimo-v2.6-flash's audio input is not
|
|
31
32
|
* representable in pi's Model type and is dropped from `input` (noted on
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
*/
|
|
34
35
|
|
|
35
36
|
import type { GeneratedModelEntry } from "../src/fetch-models.ts";
|
|
37
|
+
import { NON_CHAT_MODEL_IDS } from "../src/fetch-models.ts";
|
|
36
38
|
import type { ManualModelOverride } from "./manual-overrides.ts";
|
|
37
39
|
import {
|
|
38
40
|
MANUAL_OVERRIDES,
|
|
@@ -97,17 +99,10 @@ const PROVIDER_REMOVED_MODEL_IDS: Record<string, string> = {
|
|
|
97
99
|
"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
100
|
};
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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)",
|
|
110
|
-
};
|
|
102
|
+
// NON_CHAT_MODEL_IDS (six non-chat endpoint ids) is imported from its single
|
|
103
|
+
// source of truth, `src/fetch-models.ts`, where the runtime live merge applies
|
|
104
|
+
// the same classification. Here it feeds convertModel's exclusion and the
|
|
105
|
+
// exclusion-note list in GENERATED_CATALOG_META.notes.
|
|
111
106
|
|
|
112
107
|
/**
|
|
113
108
|
* Premium/tier-gated models that models.dev documents but that are deliberately
|
|
@@ -346,6 +341,14 @@ async function main(): Promise<void> {
|
|
|
346
341
|
// so the manual data never disappears silently.
|
|
347
342
|
const entryById = new Map(entries.map((entry) => [entry.id, entry]));
|
|
348
343
|
for (const [modelId, detail] of Object.entries(MANUAL_ONLY_MODEL_IDS)) {
|
|
344
|
+
// Guard: a MANUAL_ONLY_MODEL_IDS entry must not be a non-chat id.
|
|
345
|
+
// (REQUIRED_MODEL_IDS can't land here: if a required id were non-chat,
|
|
346
|
+
// convertModel skips it and the existing missing-required fail already
|
|
347
|
+
// fires — but this guard is explicit for MANUAL_ONLY.)
|
|
348
|
+
const nonChatReason = NON_CHAT_MODEL_IDS[modelId];
|
|
349
|
+
if (nonChatReason) {
|
|
350
|
+
fail(`MANUAL_ONLY_MODEL_IDS id "${modelId}" is classified as non-chat (${nonChatReason}) — this is a contradictory repo configuration`);
|
|
351
|
+
}
|
|
349
352
|
const existing = entryById.get(modelId);
|
|
350
353
|
if (existing) {
|
|
351
354
|
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})`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// This file is auto-generated by scripts/generate-models.ts
|
|
2
2
|
// Do not edit manually — run `bun run generate-models` to update.
|
|
3
3
|
//
|
|
4
|
-
// Source: https://models.dev/api.json (provider "nan"), fetched 2026-10-01T10:
|
|
4
|
+
// Source: https://models.dev/api.json (provider "nan"), fetched 2026-10-01T10:56:14.717Z
|
|
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.
|
|
@@ -455,7 +455,7 @@ export const NAN_GENERATED_MODELS: readonly GeneratedModelEntry[] = [
|
|
|
455
455
|
export const GENERATED_CATALOG_META = {
|
|
456
456
|
source: "https://models.dev/api.json",
|
|
457
457
|
modelsDevProvider: "nan",
|
|
458
|
-
fetchedAt: "2026-10-01T10:
|
|
458
|
+
fetchedAt: "2026-10-01T10:56:14.717Z",
|
|
459
459
|
modelCount: 6,
|
|
460
460
|
models: ["deepseek-v4-flash","gemma4","glm5.3-flash","mimo-v2.6-flash","qwen3.6","qwen3.8-flash"],
|
|
461
461
|
notes: [
|
|
@@ -463,6 +463,11 @@ export const GENERATED_CATALOG_META = {
|
|
|
463
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)",
|
|
464
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
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)",
|
|
466
|
+
"non-chat: \"qwen3-embedding\" excluded from the chat catalog (embedding endpoint (https://nan.builders/docs/models, checked 2026-10-01) — not a chat model)",
|
|
467
|
+
"non-chat: \"rerank\" excluded from the chat catalog (rerank endpoint (https://nan.builders/docs/models, checked 2026-10-01) — not a chat model)",
|
|
468
|
+
"non-chat: \"kokoro\" excluded from the chat catalog (text-to-speech (TTS) endpoint (https://nan.builders/docs/models, checked 2026-10-01) — not a chat model)",
|
|
469
|
+
"non-chat: \"whisper\" excluded from the chat catalog (speech-to-text (STT) endpoint (https://nan.builders/docs/models, checked 2026-10-01) — not a chat model)",
|
|
470
|
+
"non-chat: \"flux-2-klein\" excluded from the chat catalog (image generation endpoint (https://nan.builders/docs/models, checked 2026-10-01) — not a chat model)",
|
|
466
471
|
"compat matches the maintainer's working ~/.pi/agent/models.json LiteLLM config for api.nan.builders (2026-09-04): supportsDeveloperRole false, supportsReasoningEffort true, maxTokensField max_tokens. NaN's docs example sets only supportsDeveloperRole: true and is not battle-tested. supportsFinishReason true (2026-09-13, issue #2): the LiteLLM gateway intermittently closes SSE streams before emitting finish_reason; with true pi-ai raises 'Stream ended without finish_reason', which matches pi-ai's retryable-provider pattern ('ended without') and is retried automatically, whereas false silently synthesized stop/toolUse and stalled the turn mid-answer. supportsUsageInStreaming true (2026-09-16, issue #7): NaN's published schema is silent about stream_options, but the live gateway honors it — two identical streaming calls per model, differing only in stream_options: { include_usage: true }, returned 0 usage chunks without it and exactly 1 with it (prompt/completion/reasoning/cached token counts) on deepseek-v4-flash, glm5.3-flash, qwen3.6, mimo-v2.5 and gemma4, and a real pi session then recorded token counts where it recorded zeros. pi-ai only sends stream_options when this is not false, and the sanitizer forwards it when the model declares true, so chat models opt in by default and usage is reported (issue #4). A model that does not report streaming usage can still opt out per model with a models.json compat override (supportsUsageInStreaming: false); the sanitizer then strips stream_options and the payload stays strict.",
|
|
467
472
|
"input includes image: NaN serves the Vision-Exp variant ('takes images as input', https://nan.builders/docs/models; the image_url content-parts in https://nan.builders/openapi.json list deepseek-v4-flash among the vision models). models.dev provider nan also lists text+image now (DeepSeek V4.1 Flash entry, checked 2026-09-13; its 2026-09-07 snapshot listed text only), so this override is kept as a pin for the vision capability rather than as a divergence. reasoning_effort_values=[\"none\"] (issue #16, measured 2026-09-27 against live gateway, repro included in the issue): only \"none\" deterministically disables reasoning (0 reasoning tokens, answer produced). Every other value leaves 13-14K reasoning tokens and causes runaway reasoning (finish_reason:\"length\" with ZERO answer text). The catalog declares only [\"none\"] because the remaining values are useless noise. thinkingLevelMap { off: \"none\" } (issue #16, measured 2026-09-27): when pi maps reasoning:\"off\" to reasoningEffort:undefined, the thinkingLevelMap.off=\"none\" supplies the effort so the gateway receives reasoning_effort:\"none\" and does NOT enter runaway reasoning mode.",
|
|
468
473
|
"thinkingLevelMap { off: \"none\" } (issue #16): NaN docs state that with no reasoning_effort parameter gemma4 defaults to reasoning ON (16,384-token budget), while `none`/`minimal` skip reasoning entirely (https://nan.builders/docs/models#controlling-reasoning, checked 2026-10-01). pi maps reasoning:\"off\" to reasoningEffort:undefined, so without this map `thinking: off` sent no parameter and the model reasoned anyway; the map sends reasoning_effort:\"none\" and actually disables reasoning.",
|
package/src/fetch-models.ts
CHANGED
|
@@ -12,13 +12,15 @@
|
|
|
12
12
|
* only model `id`s — no capability fields. It is used as the authoritative
|
|
13
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
|
-
*
|
|
15
|
+
* Merge: live IDs × generated capability data — non-chat IDs (see
|
|
16
|
+
* `NON_CHAT_MODEL_IDS`) are excluded from the registered model set and
|
|
17
|
+
* reported in `MergedCatalog.nonChat`; all other live IDs surface: a live ID
|
|
18
|
+
* with generated data keeps its generated capabilities, and an uncatalogued
|
|
19
|
+
* live ID (e.g. a new or undocumented model) gets conservative placeholder
|
|
20
|
+
* limits (the same defaults used in custom-provider.md's dynamic-discovery
|
|
21
|
+
* example) with no reasoning support — capabilities stay "unknown", nothing is
|
|
22
|
+
* fabricated. On fetch failure, timeout, or an unusable response, callers fall
|
|
23
|
+
* back to the generated catalog so startup is never blocked.
|
|
22
24
|
*/
|
|
23
25
|
|
|
24
26
|
import type { Model, OpenAICompletionsCompat } from "@earendil-works/pi-ai";
|
|
@@ -81,6 +83,32 @@ export const UNKNOWN_MODEL_LIMITS = { contextWindow: 128_000, maxTokens: 4_096 }
|
|
|
81
83
|
/** Timeout for the live /models fetch; matches the pi-synthetic-provider precedent (~3s). */
|
|
82
84
|
export const DEFAULT_MODELS_TIMEOUT_MS = 3_000;
|
|
83
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Live endpoint IDs classified as non-chat and excluded from the registered
|
|
88
|
+
* model set. Keys are model ids; values are provenance reasons citing the
|
|
89
|
+
* source that designated the endpoint as non-chat.
|
|
90
|
+
*
|
|
91
|
+
* Source: https://nan.builders/docs/models (checked 2026-10-01), plus
|
|
92
|
+
* qwen-image-2.1: https://models.dev API `limit.output: 0` (checked 2026-09-29).
|
|
93
|
+
* These are MCP-bridge territory (embedding / rerank / TTS / STT / image
|
|
94
|
+
* generation), not chat catalog models — see AGENTS.md classification notes
|
|
95
|
+
* and issue #15.
|
|
96
|
+
*/
|
|
97
|
+
export const NON_CHAT_MODEL_IDS: Readonly<Record<string, string>> = {
|
|
98
|
+
"qwen3-embedding":
|
|
99
|
+
"embedding endpoint (https://nan.builders/docs/models, checked 2026-10-01) — not a chat model",
|
|
100
|
+
rerank:
|
|
101
|
+
"rerank endpoint (https://nan.builders/docs/models, checked 2026-10-01) — not a chat model",
|
|
102
|
+
kokoro:
|
|
103
|
+
"text-to-speech (TTS) endpoint (https://nan.builders/docs/models, checked 2026-10-01) — not a chat model",
|
|
104
|
+
whisper:
|
|
105
|
+
"speech-to-text (STT) endpoint (https://nan.builders/docs/models, checked 2026-10-01) — not a chat model",
|
|
106
|
+
"flux-2-klein":
|
|
107
|
+
"image generation endpoint (https://nan.builders/docs/models, checked 2026-10-01) — not a chat model",
|
|
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)",
|
|
110
|
+
} as const;
|
|
111
|
+
|
|
84
112
|
export interface CatalogSource {
|
|
85
113
|
/** Provider id as registered in pi, e.g. "nan". */
|
|
86
114
|
providerId: string;
|
|
@@ -108,7 +136,11 @@ export function toModel(entry: GeneratedModelEntry, source: CatalogSource): Mode
|
|
|
108
136
|
|
|
109
137
|
/** The generated fallback catalog as pi-ai Models for the given provider. */
|
|
110
138
|
export function baselineModels(source: CatalogSource): Model<"openai-completions">[] {
|
|
111
|
-
|
|
139
|
+
// Defensive: the generated baseline never contains non-chat ids today,
|
|
140
|
+
// but the invariant must hold on every path (issue #15).
|
|
141
|
+
return NAN_GENERATED_MODELS.filter(
|
|
142
|
+
(entry) => !NON_CHAT_MODEL_IDS[entry.id],
|
|
143
|
+
).map((entry) => toModel(entry, source));
|
|
112
144
|
}
|
|
113
145
|
|
|
114
146
|
export interface LiveModelListOptions {
|
|
@@ -163,12 +195,18 @@ export interface MergedCatalog {
|
|
|
163
195
|
matched: string[];
|
|
164
196
|
/** Allowlisted live IDs kept with unknown capabilities (conservative limits). */
|
|
165
197
|
unknown: string[];
|
|
198
|
+
/**
|
|
199
|
+
* Live model IDs classified as non-chat and excluded from the registered
|
|
200
|
+
* model set, in live order. Populated from `NON_CHAT_MODEL_IDS` (issue #15).
|
|
201
|
+
*/
|
|
202
|
+
nonChat: string[];
|
|
166
203
|
}
|
|
167
204
|
|
|
168
205
|
/**
|
|
169
|
-
* Merge live model IDs with the generated capability catalog.
|
|
170
|
-
*
|
|
171
|
-
*
|
|
206
|
+
* Merge live model IDs with the generated capability catalog. Non-chat IDs
|
|
207
|
+
* (from `NON_CHAT_MODEL_IDS`) are excluded and reported in `nonChat`; all
|
|
208
|
+
* other live IDs surface: live IDs with generated data keep their generated
|
|
209
|
+
* capabilities; uncatalogued live IDs get conservative placeholder limits,
|
|
172
210
|
* `reasoning: false`, and zero cost (documented defaults, not invented
|
|
173
211
|
* capabilities).
|
|
174
212
|
*/
|
|
@@ -181,8 +219,15 @@ export function mergeLiveWithGenerated(
|
|
|
181
219
|
const models: Model<"openai-completions">[] = [];
|
|
182
220
|
const matched: string[] = [];
|
|
183
221
|
const unknown: string[] = [];
|
|
222
|
+
const nonChat: string[] = [];
|
|
184
223
|
|
|
185
224
|
for (const id of liveIds) {
|
|
225
|
+
// Issue #15: non-chat endpoint ids must never surface as chat models.
|
|
226
|
+
const nonChatReason = NON_CHAT_MODEL_IDS[id];
|
|
227
|
+
if (nonChatReason) {
|
|
228
|
+
nonChat.push(id);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
186
231
|
const entry = byId.get(id);
|
|
187
232
|
if (entry) {
|
|
188
233
|
models.push(toModel(entry, source));
|
|
@@ -217,7 +262,7 @@ export function mergeLiveWithGenerated(
|
|
|
217
262
|
}
|
|
218
263
|
}
|
|
219
264
|
|
|
220
|
-
return { models, matched, unknown };
|
|
265
|
+
return { models, matched, unknown, nonChat };
|
|
221
266
|
}
|
|
222
267
|
|
|
223
268
|
export interface FetchModelsOptions {
|
|
@@ -240,13 +285,19 @@ export interface ResolvedCatalog {
|
|
|
240
285
|
liveIds: Set<string> | undefined;
|
|
241
286
|
/** Allowlisted live IDs kept with unknown capabilities (present only when liveIds is set). */
|
|
242
287
|
unknownIds: string[];
|
|
288
|
+
/**
|
|
289
|
+
* Live model IDs classified as non-chat and excluded from the registered
|
|
290
|
+
* model set (issue #15). Empty when the live fetch failed.
|
|
291
|
+
*/
|
|
292
|
+
nonChatIds: string[];
|
|
243
293
|
}
|
|
244
294
|
|
|
245
295
|
/**
|
|
246
296
|
* Resolve the effective catalog: live `/models` IDs × generated capability
|
|
247
297
|
* data. A successful live fetch is authoritative (tier-aware: it reflects
|
|
248
298
|
* exactly what your NaN key can use); failure degrades to the generated
|
|
249
|
-
* fallback so startup is never blocked.
|
|
299
|
+
* fallback so startup is never blocked. Non-chat IDs are excluded from
|
|
300
|
+
* models and reported in `nonChatIds` (issue #15).
|
|
250
301
|
*/
|
|
251
302
|
export async function resolveCatalog(
|
|
252
303
|
source: CatalogSource,
|
|
@@ -260,11 +311,16 @@ export async function resolveCatalog(
|
|
|
260
311
|
timeoutMs: options.timeoutMs,
|
|
261
312
|
fetchImpl: options.fetchImpl,
|
|
262
313
|
});
|
|
263
|
-
if (!liveIds) return { models: baseline, liveIds: undefined, unknownIds: [] };
|
|
314
|
+
if (!liveIds) return { models: baseline, liveIds: undefined, unknownIds: [], nonChatIds: [] };
|
|
264
315
|
const merged = mergeLiveWithGenerated(liveIds, source);
|
|
265
|
-
return {
|
|
316
|
+
return {
|
|
317
|
+
models: merged.models,
|
|
318
|
+
liveIds: new Set(liveIds),
|
|
319
|
+
unknownIds: merged.unknown,
|
|
320
|
+
nonChatIds: merged.nonChat,
|
|
321
|
+
};
|
|
266
322
|
} catch {
|
|
267
|
-
return { models: baseline, liveIds: undefined, unknownIds: [] };
|
|
323
|
+
return { models: baseline, liveIds: undefined, unknownIds: [], nonChatIds: [] };
|
|
268
324
|
}
|
|
269
325
|
}
|
|
270
326
|
|