@juspay/neurolink 12.41.6 → 12.41.8

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.
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { ModelResolver, formatSearchResults, formatRecommendation, formatComparison, } from "../../models/modelResolver.js";
7
7
  import { getAllModels, getModelsByProvider, getAvailableProviders, formatModelForDisplay, } from "../../models/modelRegistry.js";
8
+ import { getCatalogJsonEntries } from "../../providers/catalog/loader.js";
8
9
  import chalk from "chalk";
9
10
  import ora from "ora";
10
11
  import { logger } from "../../utils/logger.js";
@@ -334,9 +335,23 @@ export class ModelsCommandFactory {
334
335
  try {
335
336
  const { AIProviderFactory } = await import("../../core/factory.js");
336
337
  const provider = (await AIProviderFactory.createProvider(providerName));
337
- const liveIds = typeof provider.getAvailableModels === "function"
338
+ const rawLiveIds = typeof provider.getAvailableModels === "function"
338
339
  ? await provider.getAvailableModels()
339
340
  : [];
341
+ // Project against the JSON catalog's non-retired ids, mirroring
342
+ // getAllModels()'s listing filter: a catalog-backed provider's
343
+ // vendor `/models` endpoint often keeps listing an id long after
344
+ // it 404s on chat completions (see e.g. fireworks.json's
345
+ // `evidence.liveMatrix`), so an unfiltered live listing would
346
+ // print a retired id as a usable "live" result. Providers with no
347
+ // catalog entry (litellm, ollama, openai-compatible gateways,
348
+ // lm-studio, llamacpp) have nothing to project against, so their
349
+ // live discovery stays unrestricted — that is the whole point of
350
+ // this fallback for them.
351
+ const catalogEntry = getCatalogJsonEntries().find((entry) => entry.id === providerName);
352
+ const liveIds = catalogEntry
353
+ ? rawLiveIds.filter((id) => catalogEntry.models.catalog[id]?.status !== "retired")
354
+ : rawLiveIds;
340
355
  if (liveIds.length > 0) {
341
356
  if (spinner) {
342
357
  spinner.succeed(`Found ${liveIds.length} models (live from ${providerName})`);
@@ -23,7 +23,7 @@
23
23
  "enumMember": "KIMI_K2P6",
24
24
  "vision": false,
25
25
  "status": "retired",
26
- "description": "Retired 2026-09 — still on the Fireworks roster, but a real chat call returns 404 'not deployed' (since the 2026-09-26 nightly)"
26
+ "description": "Retired 2026-09 — still listed on the Fireworks /models roster, but the chat endpoint 404s 'Model not found, inaccessible, and/or not deployed' (since the 2026-09-26 nightly); superseded by kimi-k3, same Moonshot family"
27
27
  },
28
28
  "accounts/fireworks/models/gpt-oss-120b": {
29
29
  "enumMember": "GPT_OSS_120B",
@@ -107,7 +107,8 @@
107
107
  "topModels": [
108
108
  "accounts/fireworks/models/kimi-k3",
109
109
  "accounts/fireworks/models/gpt-oss-120b",
110
- "accounts/fireworks/models/qwen3p8-max"
110
+ "accounts/fireworks/models/qwen3p8-max",
111
+ "accounts/fireworks/models/glm-5p3"
111
112
  ]
112
113
  },
113
114
  "capabilities": {
@@ -146,7 +147,7 @@
146
147
  },
147
148
  "liveMatrix": {
148
149
  "date": "2026-09-28",
149
- "result": "kimi-k2p6 is still listed on /models but a real chat call now returns 404 'not deployed', which failed the nightly model-liveness gate from 2026-09-26 and sent the matrix vision test down the text-only fallback. kimi-k3 answers a text call and a call carrying a PNG with 200, so it becomes the default; gpt-oss-120b, qwen3p8-max and glm-5p3 also answer text calls. Earlier (2026-08-30): deepseek-v4-pro was listed but hung past 120s, six ids returned 404 'not deployed', and glm-5p3 answers 'does not support image inputs'."
150
+ "result": "Issue #1796: the 2026-09-26 nightly model-liveness gate reported the default, kimi-k2p6, as dead, sending the matrix vision test down the text-only fallback. Confirmed live — GET /models still lists kimi-k2p6, but POST /chat/completions 404s 'Model not found, inaccessible, and/or not deployed', the same still-listed-but-unservable pattern deepseek-v4-pro hit in 2026-08 (hung past 120s rather than 404ing). Moved the default to kimi-k3, the same Moonshot family's newest generation: 200 OK on both a text-only call and a call carrying a PNG, and a full-length call returns a proper OpenAI-shaped tool_calls entry with finish_reason=tool_calls, confirming vision and tool support both survive the swap. kimi-k2p6 marked retired rather than deleted, per the frozen-enum rule. gpt-oss-120b, qwen3p8-max and glm-5p3 (the rest of fallbacks/topModels) were re-probed alive at the same time; glm-5p3 still only answers text calls, not image ones, as found 2026-08-30."
150
151
  },
151
152
  "addedInPR": "https://github.com/juspay/neurolink/pull/1587"
152
153
  }
@@ -172,6 +172,38 @@ export declare class GoogleVertexProvider extends BaseProvider {
172
172
  * This bypasses @ai-sdk/google-vertex to properly handle thought_signature
173
173
  */
174
174
  private executeNativeGemini3Stream;
175
+ /**
176
+ * Resolve the text a native Vertex *generate* call actually sends, for
177
+ * both `executeNativeGemini3Generate` and `executeNativeAnthropicGenerate`.
178
+ *
179
+ * The baseline here is the same `input.text || prompt || "Please
180
+ * respond."` chain both call sites have always used — `input.text` wins
181
+ * when set because the file preprocessors (`processUnifiedFilesArray`,
182
+ * `processCSVFilesForNativeSDK`) append attached-file content to it while
183
+ * `options.prompt` stays the pre-attachment snapshot; `prompt` is the
184
+ * fallback for the legacy prompt-only `generateText()` convention, which
185
+ * never sets `input` at all; the literal is the last resort when neither
186
+ * is set.
187
+ *
188
+ * That `||` chain alone has a gap model middleware falls into:
189
+ * `applyTurnParamsRewrite` writes every rewritten turn back onto
190
+ * `options.input.text`, including an *emptied* one (a redaction). `||`
191
+ * treats that empty string exactly like an unset `input.text` and falls
192
+ * through to `options.prompt` — which middleware never touched, since it
193
+ * is a plain snapshot taken before middleware ran — so a redaction a
194
+ * middleware applied is silently discarded and the ORIGINAL text still
195
+ * reaches Vertex.
196
+ *
197
+ * The fix is narrow: when `input.text` is *set* (not undefined) and
198
+ * differs from `prompt`, something deliberately changed it since the
199
+ * snapshot was taken — a middleware rewrite, or a file preprocessor's
200
+ * attachment-content append — and that divergence itself is the signal to
201
+ * trust `input.text` outright, empty string included. When the two agree
202
+ * (the untouched, no-middleware case, including the STT-silence
203
+ * `input.text === prompt === ""` placeholder) there is no divergence to
204
+ * trust, so the original fallback chain still applies unchanged.
205
+ */
206
+ private resolveNativeGenerateText;
175
207
  /**
176
208
  * Execute generate using native @google/genai SDK for Gemini 3 models on Vertex AI
177
209
  * This bypasses @ai-sdk/google-vertex to properly handle thought_signature
@@ -293,15 +325,196 @@ export declare class GoogleVertexProvider extends BaseProvider {
293
325
  * `hasLifecycleErrorFired`) skips its own fire for the same failure.
294
326
  */
295
327
  private wrapStreamResultWithLifecycle;
328
+ /**
329
+ * The text a native Vertex turn is built from before any model middleware
330
+ * rewrite — `options.input.text` when the caller set it (the normal
331
+ * `generate()`/`stream()` convention), else `options.prompt` (the legacy
332
+ * prompt-only `generateText()` convention, where `input` is never
333
+ * populated — see `generateTextInInstanceScope` in `src/lib/neurolink.ts`,
334
+ * which validates `options.prompt` and never touches `options.input`).
335
+ *
336
+ * `buildTurnPromptForMiddleware` and `applyTurnParamsRewrite` both read
337
+ * this so they agree on one baseline: the first so a prompt-only caller
338
+ * shows middleware its real text instead of an empty turn, the second so
339
+ * a middleware-rewritten turn is compared against what middleware actually
340
+ * started from — not a baseline that ignores `prompt` entirely, which
341
+ * would make a prompt-only call look "unedited" no matter what middleware
342
+ * did to it. `StreamOptions` carries no top-level `prompt` field, hence
343
+ * the `"prompt" in options` guard instead of a cast.
344
+ */
345
+ private vertexTurnBaselineText;
346
+ /**
347
+ * Build the V3 entry-turn prompt middleware sees for a native call.
348
+ * Native Vertex loops build their own multi-turn conversation internally
349
+ * once they run — this is only the caller's system prompt + text input,
350
+ * the one turn middleware is given visibility into before that loop
351
+ * starts.
352
+ *
353
+ * The user turn is an array of text parts rather than a bare string. That
354
+ * is the V3 prompt shape, and prompt-editing middleware is written against
355
+ * it: one that spreads `content` into a longer array turns a string into
356
+ * one element per character, and one that calls `.map` / `.filter` on it
357
+ * throws. The AI Studio path already hands its middleware parts
358
+ * (`geminiContentsToV3Prompt`); this makes Vertex agree with it.
359
+ *
360
+ * The text itself comes from `vertexTurnBaselineText`, not
361
+ * `options.input?.text` alone — a prompt-only `generateText()` caller
362
+ * never sets `input`, and without the `options.prompt` fallback middleware
363
+ * would be shown an empty turn and have nothing of the caller's to inspect
364
+ * or build on.
365
+ */
366
+ private buildTurnPromptForMiddleware;
367
+ /** Sampling params a native call already honors, carried onto the V3 view. */
368
+ private buildTurnV3Params;
369
+ /**
370
+ * Read a middleware-transformed prompt back into the two things a native
371
+ * Vertex turn can carry: one system instruction and one user text.
372
+ *
373
+ * Every system message counts (joined with a blank line, the way
374
+ * `v3PromptToGeminiContents` joins them for AI Studio) and so does every
375
+ * user message, not just the last. A middleware that appends or prepends
376
+ * context leaves the caller's own text in the prompt beside it; reading
377
+ * only the last user message would send the addition without the question
378
+ * it was meant to qualify, or drop it and send the question bare. Text
379
+ * blocks — across messages and across the parts of one message — are joined
380
+ * with a blank line for the same reason: they are separate blocks, and
381
+ * gluing them runs the last word of one into the first of the next.
382
+ *
383
+ * `unmapped` counts what a native turn has no place for — non-text user
384
+ * parts (files, images) and assistant or tool messages — so the caller can
385
+ * say it was dropped rather than drop it silently.
386
+ */
387
+ private readTurnFromPrompt;
388
+ /**
389
+ * Apply a middleware-transformed turn back onto the native call options.
390
+ *
391
+ * The system instruction and the user text are each written back only when
392
+ * they differ from what middleware actually started from — `systemPrompt`
393
+ * against `options.systemPrompt`, user text against
394
+ * `vertexTurnBaselineText(options)`, not `options.input?.text` alone — so a
395
+ * middleware that touches neither leaves both exactly as they were. An
396
+ * edit is honoured in every direction — a changed, added or removed system
397
+ * message, an appended or prepended user message, a user message emptied
398
+ * outright (a redaction) — because every native generate/stream executor
399
+ * resolves its wire text through `options.input.text` once this write-back
400
+ * has run (`resolveNativeGenerateText` on the generate paths, which
401
+ * special-cases exactly this divergence so an emptied `input.text` is not
402
+ * mistaken for an unset one and does not fall back to the stale
403
+ * `options.prompt` snapshot; a direct `options.input.text` read on the
404
+ * stream paths, which have no such fallback to fall into). Comparing
405
+ * against `options.input?.text` alone — ignoring `options.prompt` — would
406
+ * make a prompt-only `generateText()` caller's untouched turn look
407
+ * "changed" the first time middleware ran (nothing to compare `turn.text`
408
+ * against but `""`), writing the real prompt into `input.text` as a
409
+ * spurious edit on every such call even when no middleware did anything.
410
+ *
411
+ * Sampling params are written back the same way and can be removed as well
412
+ * as changed: a middleware that drops `temperature` or `topP` from the
413
+ * params must reach the wire as "not sent", not as the caller's original
414
+ * value kept because the key went missing.
415
+ *
416
+ * `tools` is offered to middleware read-only — a middleware that rewrites
417
+ * it gets a WARN rather than a silent drop, matching the OpenAI-compatible
418
+ * streaming path's contract: native tool wiring is derived independently
419
+ * of this V3 view, and re-deriving it here would diverge from it.
420
+ */
421
+ private applyTurnParamsRewrite;
422
+ /**
423
+ * Strip the lifecycle middleware from the options handed to
424
+ * `applyMiddlewareToModel` for a native Vertex call, when something else is
425
+ * already firing the same callbacks.
426
+ *
427
+ * NeuroLink's `applyGenerateLifecycleMiddleware` /
428
+ * `applyStreamLifecycleMiddleware` (neurolink.ts) mirror a caller's
429
+ * top-level `onFinish` / `onError` / `onChunk` into
430
+ * `options.middleware.middlewareConfig.lifecycle.config` — the exact same
431
+ * callback references — before this provider sees `options`. Left enabled,
432
+ * the built-in lifecycle middleware would fire them again from inside the
433
+ * bridge, and with malformed payloads: it reads the legacy `textDelta` chunk
434
+ * field and a bare `finishReason`, neither of which a V3 model produces.
435
+ *
436
+ * Who else fires them differs by path, so the exclusion does too:
437
+ *
438
+ * - generate: only `fireGenerateOnFinish` / `fireGenerateOnError`, which read
439
+ * the TOP-LEVEL callbacks. The entry is stripped only when one is present.
440
+ * A caller who configures `middlewareConfig.lifecycle` directly has no
441
+ * other firer here, so that middleware applies exactly once, unmodified.
442
+ * - stream: `BaseProvider.stream()` wraps every stream in
443
+ * `wrapStreamWithLifecycleCallbacks`, which fires whatever sits in
444
+ * `lifecycle.config` however it got there — top-level shorthand or direct
445
+ * config. The entry is stripped whenever it exists, or a direct-config
446
+ * caller would get every callback twice (and `onChunk` a third time, for
447
+ * the `finish` part the middleware also reports).
448
+ *
449
+ * Only the `lifecycle` entry is disabled, so every other configured
450
+ * middleware (guardrails, transformParams, custom hooks, analytics, ...)
451
+ * applies normally.
452
+ */
453
+ private excludeAutoLifecycleMiddleware;
454
+ /**
455
+ * Run the native Gemini generate loop and, when Vertex rejects an
456
+ * over-constrained `responseSchema`, retry once without it.
457
+ *
458
+ * The retry sits inside the function `runNativeGenerateWithMiddleware` hands
459
+ * to the model bridge rather than around the bridge, because it is
460
+ * provider-internal recovery: model middleware must see one `doGenerate`
461
+ * that succeeds. Retrying outside would show every middleware the failed
462
+ * first call — the lifecycle middleware would report an `onError` for a
463
+ * request that then succeeded, and `transformParams` would run twice.
464
+ */
465
+ private executeNativeGemini3GenerateWithSchemaFallback;
466
+ /**
467
+ * Apply the configured middleware chain around a native Vertex generate
468
+ * loop. `callNative` is `executeNativeAnthropicGenerate` or
469
+ * `executeNativeGemini3Generate` — a self-contained black box that runs
470
+ * its own multi-step tool loop and returns a fully-resolved
471
+ * `EnhancedGenerateResult`. Wrapping happens around that call, not inside
472
+ * it: `transformParams` can rewrite the entry turn before the loop starts,
473
+ * and `wrapGenerate` can observe the result or replace it outright (a
474
+ * guardrail that blocks the call entirely never reaches `callNative`, so
475
+ * `nativeResult` stays unset and a settled result is synthesized purely
476
+ * from what middleware produced, then run through `enhanceResult` like any
477
+ * other result so `enableAnalytics` / `enableEvaluation` still apply).
478
+ */
479
+ private runNativeGenerateWithMiddleware;
480
+ /**
481
+ * Apply the configured middleware chain around a native Vertex stream
482
+ * loop. `callNative` is `executeNativeAnthropicStream` or
483
+ * `executeNativeGemini3Stream`. Both are bridged through the same lazy V3
484
+ * `ReadableStream` (`nativeChunksToV3Stream` / `v3StreamToNativeChunks`)
485
+ * regardless of whether the underlying loop is genuinely live (Anthropic)
486
+ * or already fully resolved (Gemini3) by the time it returns — pulling one
487
+ * item at a time is correct for both, and never turns a live stream into
488
+ * collect-then-replay.
489
+ *
490
+ * A guardrail that blocks the call never invokes `doStream`, so
491
+ * `nativeResult` stays unset; a `StreamResult` is synthesized instead,
492
+ * with `usage` / `finishReason` resolving once the merged stream is fully
493
+ * drained (the same late-resolving-getter contract
494
+ * `preserveStreamResultAccessors` already exists for).
495
+ */
496
+ private runNativeStreamWithMiddleware;
296
497
  /**
297
498
  * Re-apply getter-based accessor properties from a source StreamResult onto
298
499
  * a wrapper copy. Wrapper spreads (`{ ...result }`) invoke and SNAPSHOT
299
500
  * enumerable getters at wrap time — for background-loop streams (the native
300
501
  * Anthropic path) that resolve finishReason / structuredOutput / toolCalls
301
- * only as the consumer drains, the snapshot is permanently undefined/empty.
302
- * Copying the accessor descriptors keeps the wrapped result live. Results
303
- * built from plain data properties (the buffered Gemini paths) have no
304
- * getters and pass through untouched.
502
+ * / usage only as the consumer drains, the snapshot is permanently
503
+ * undefined/empty. Copying the accessor descriptors keeps the wrapped
504
+ * result live. Results built from plain data properties (the buffered
505
+ * Gemini paths, and the native Anthropic stream's own `usage` — a mutable
506
+ * object reference rather than a getter, so a spread keeps its identity
507
+ * and later mutations stay visible) have no matching getter here and pass
508
+ * through untouched.
509
+ *
510
+ * The re-applied property also accepts a direct write: NeuroLink.stream()
511
+ * backstops a missing `usage` from analytics once the outer stream drains
512
+ * by assigning `mcpStreamOutcome.usage = resolvedUsage` on whatever result
513
+ * object it was handed. Copying the bare getter-only descriptor verbatim
514
+ * would turn that assignment into a TypeError ("Cannot set property usage
515
+ * of #<Object> which has only a getter"), so each re-applied accessor is
516
+ * rebuilt with a setter that remembers an override and prefers it over the
517
+ * live source getter once one has been written.
305
518
  */
306
519
  private preserveStreamResultAccessors;
307
520
  /**