@juspay/neurolink 12.41.2 → 12.41.4

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.
@@ -24,7 +24,7 @@ import { sanitizeErrorCause } from "../utils/logSanitize.js";
24
24
  import { createAnalytics as buildAnalytics } from "./analytics.js";
25
25
  import { ErrorCategory, ErrorSeverity } from "../constants/enums.js";
26
26
  import { duckTypedStatusCode, extractRetryAfterMsFromError, } from "../utils/providerRetry.js";
27
- import { hasLifecycleErrorFired, markLifecycleErrorFired, } from "../utils/lifecycleCallbacks.js";
27
+ import { getLifecycleMiddlewareConfig, hasLifecycleErrorFired, markLifecycleErrorFired, } from "../utils/lifecycleCallbacks.js";
28
28
  import { resolveLifecycleTimeoutMs } from "../utils/lifecycleTimeout.js";
29
29
  import { logger } from "../utils/logger.js";
30
30
  import { interleaveTTSStream } from "../utils/ttsStream.js";
@@ -51,18 +51,6 @@ import { generateOnceNative } from "../utils/nativeSingleShot.js";
51
51
  import { validateExecutionControl } from "../utils/parameterValidation.js";
52
52
  import { extractTokenUsage } from "../utils/tokenUtils.js";
53
53
  import { preserveLiveStreamAccessors } from "../utils/streamResultAccessors.js";
54
- /**
55
- * Read the consumer-facing lifecycle callbacks buried inside a request's
56
- * middleware blob. The parameter is `unknown` on purpose: request options
57
- * arrive as several structurally-unrelated shapes (StreamOptions,
58
- * TextGenerationOptions), and the lifecycle branch is an optional add-on
59
- * none of them declare — a single structural view keeps the read cast-free
60
- * at every call site.
61
- */
62
- function getLifecycleMiddlewareConfig(options) {
63
- return options?.middleware
64
- ?.middlewareConfig?.lifecycle?.config;
65
- }
66
54
  /**
67
55
  * Abstract base class for all AI providers
68
56
  * Tools are integrated as first-class citizens - always available by default
@@ -819,9 +807,32 @@ export class BaseProvider {
819
807
  yield chunk;
820
808
  }
821
809
  if (onFinish) {
810
+ // Read `result.usage` / `result.finishReason` here, after the
811
+ // drain loop above has fully exhausted `upstreamIterable` — the
812
+ // same point native background-loop providers (Vertex's Gemini3
813
+ // and Claude paths) resolve these fields by, whether they are
814
+ // plain values set before `result` was returned or live getters
815
+ // preserved through `withStreamModelFallback`
816
+ // (`preserveLiveStreamAccessors`). Both fields are generic
817
+ // `StreamResult` members for every provider, not Vertex-specific,
818
+ // so this enrichment is additive for all of them: only present
819
+ // when `result` actually carries a value, never a synthesized
820
+ // default (a provider that never populated these fields keeps
821
+ // getting the exact same bare `{text, duration}` payload as
822
+ // before).
823
+ const { usage, finishReason } = result;
822
824
  await safeFire(() => onFinish({
823
825
  text: accumulated,
824
826
  duration: Date.now() - startTime,
827
+ ...(usage
828
+ ? {
829
+ usage: {
830
+ promptTokens: usage.input ?? 0,
831
+ completionTokens: usage.output ?? 0,
832
+ },
833
+ }
834
+ : {}),
835
+ ...(finishReason ? { finishReason } : {}),
825
836
  }), "onFinish");
826
837
  }
827
838
  }
@@ -1,6 +1,6 @@
1
1
  import { logger } from "../../utils/logger.js";
2
2
  import { isRecoverableError } from "../../utils/errorHandling.js";
3
- import { fireOnErrorOnce } from "../../utils/lifecycleCallbacks.js";
3
+ import { fireOnErrorOnce, isLifecycleStreamCallbacksOwnedByBaseProvider, } from "../../utils/lifecycleCallbacks.js";
4
4
  /**
5
5
  * Normalize a thrown value to an `Error` while preserving structured
6
6
  * fields (`code`, `status`, `statusCode`, `retryAfter`, `details`,
@@ -88,19 +88,42 @@ export function createLifecycleMiddleware(config = {}) {
88
88
  if (!config.onChunk && !config.onFinish && !config.onError) {
89
89
  return result;
90
90
  }
91
+ // BaseProvider.wrapStreamWithLifecycleCallbacks already fires
92
+ // onChunk/onFinish for this exact config object, from NeuroLink's
93
+ // own normalized stream, for every NeuroLink-internal stream() call
94
+ // regardless of provider/transport. When that marker is present,
95
+ // firing onChunk/onFinish here too would deliver each one twice —
96
+ // once from these raw, provider-shaped V3 chunks, once from
97
+ // BaseProvider's normalized ones. onError is NOT covered by this
98
+ // flag: it keeps its own, independent dedup via `fireOnErrorOnce`'s
99
+ // shared fired-marker below, unchanged.
100
+ const streamCallbacksOwnedByBaseProvider = isLifecycleStreamCallbacksOwnedByBaseProvider(config);
91
101
  let sequenceNumber = 0;
92
102
  let accumulatedText = "";
93
103
  const transformStream = new TransformStream({
94
104
  transform(chunk, controller) {
95
105
  try {
96
- if (chunk.type === "text-delta") {
97
- accumulatedText += chunk.textDelta;
106
+ // V3 stream parts carry the delta on `delta` (see
107
+ // LanguageModelV3StreamPart in types/aiCompat.ts); `textDelta`
108
+ // is a V2-era AI-SDK field that V3 providers (e.g. Bedrock's
109
+ // bridge, this package's own openaiChatCompletionsBase) never
110
+ // set. Reading `chunk.textDelta` alone silently produced
111
+ // `undefined`, which string-concatenated into a literal
112
+ // "undefined" per chunk. Falling back to `textDelta` keeps any
113
+ // caller still emitting the older shape working unchanged.
114
+ const textDelta = chunk.type === "text-delta"
115
+ ? (chunk.delta ?? chunk.textDelta)
116
+ : undefined;
117
+ if (chunk.type === "text-delta" && textDelta !== undefined) {
118
+ accumulatedText += textDelta;
98
119
  }
99
- if (config.onChunk && chunk.type) {
120
+ if (config.onChunk &&
121
+ chunk.type &&
122
+ !streamCallbacksOwnedByBaseProvider) {
100
123
  try {
101
124
  const callbackResult = config.onChunk({
102
125
  type: chunk.type,
103
- textDelta: chunk.type === "text-delta" ? chunk.textDelta : undefined,
126
+ textDelta,
104
127
  sequenceNumber: sequenceNumber++,
105
128
  });
106
129
  Promise.resolve(callbackResult).catch((e) => {
@@ -124,7 +147,7 @@ export function createLifecycleMiddleware(config = {}) {
124
147
  }
125
148
  },
126
149
  flush() {
127
- if (config.onFinish) {
150
+ if (config.onFinish && !streamCallbacksOwnedByBaseProvider) {
128
151
  try {
129
152
  const callbackResult = config.onFinish({
130
153
  text: accumulatedText,
package/dist/neurolink.js CHANGED
@@ -79,7 +79,7 @@ import { tracers } from "./telemetry/tracers.js";
79
79
  import { getConversationMessages, storeConversationTurn, } from "./utils/conversationMemory.js";
80
80
  // Enhanced error handling imports
81
81
  import { CircuitBreaker, ERROR_CODES, ErrorFactory, isAbortError, isRetriableError, logStructuredError, NeuroLinkError, withRetry, withTimeout, } from "./utils/errorHandling.js";
82
- import { hasLifecycleErrorFired, markLifecycleErrorFired, } from "./utils/lifecycleCallbacks.js";
82
+ import { hasLifecycleErrorFired, markLifecycleErrorFired, markLifecycleStreamCallbacksOwnedByBaseProvider, } from "./utils/lifecycleCallbacks.js";
83
83
  import { interleaveTTSStream } from "./utils/ttsStream.js";
84
84
  import { resolveLifecycleTimeoutMs } from "./utils/lifecycleTimeout.js";
85
85
  import { cloneOptionsForCallIsolation } from "./utils/cloneOptions.js";
@@ -782,6 +782,20 @@ export class NeuroLink {
782
782
  if (!options.onFinish && !options.onError && !options.onChunk) {
783
783
  return;
784
784
  }
785
+ const config = {
786
+ ...options.middleware?.middlewareConfig?.lifecycle?.config,
787
+ ...(options.onFinish !== undefined ? { onFinish: options.onFinish } : {}),
788
+ ...(options.onError !== undefined ? { onError: options.onError } : {}),
789
+ ...(options.onChunk !== undefined ? { onChunk: options.onChunk } : {}),
790
+ };
791
+ // BaseProvider.wrapStreamWithLifecycleCallbacks unconditionally wraps
792
+ // every provider's stream() result and fires onChunk/onFinish from
793
+ // there, reliably, for this exact config object. Stamp it so the
794
+ // model-level lifecycle middleware (builtin/lifecycle.ts) skips its own
795
+ // onChunk/onFinish firing for it instead of delivering each one twice.
796
+ // See markLifecycleStreamCallbacksOwnedByBaseProvider's doc comment for
797
+ // why generate()'s config below is never stamped this way.
798
+ markLifecycleStreamCallbacksOwnedByBaseProvider(config);
785
799
  options.middleware = {
786
800
  ...options.middleware,
787
801
  middlewareConfig: {
@@ -789,18 +803,7 @@ export class NeuroLink {
789
803
  lifecycle: {
790
804
  ...options.middleware?.middlewareConfig?.lifecycle,
791
805
  enabled: true,
792
- config: {
793
- ...options.middleware?.middlewareConfig?.lifecycle?.config,
794
- ...(options.onFinish !== undefined
795
- ? { onFinish: options.onFinish }
796
- : {}),
797
- ...(options.onError !== undefined
798
- ? { onError: options.onError }
799
- : {}),
800
- ...(options.onChunk !== undefined
801
- ? { onChunk: options.onChunk }
802
- : {}),
803
- },
806
+ config,
804
807
  },
805
808
  },
806
809
  };
@@ -271,6 +271,26 @@ export declare class GoogleVertexProvider extends BaseProvider {
271
271
  * this for free via the AI SDK `wrapStream` middleware; native @google/genai
272
272
  * bypasses that wrapper, so native consumers need their lifecycle
273
273
  * callbacks invoked from here.
274
+ *
275
+ * `BaseProvider.stream()` always passes the result this method returns
276
+ * through `wrapStreamWithLifecycleCallbacks` afterwards too (that wrapper
277
+ * has "no early return when there are no callbacks" — see its own doc
278
+ * comment) — so unconditionally firing here double-fires onChunk/onFinish
279
+ * for every native Vertex stream. `isLifecycleStreamCallbacksOwnedByBaseProvider`
280
+ * reads the SAME nested lifecycle config off the SAME `options` object
281
+ * `wrapStreamWithLifecycleCallbacks` reads from (NeuroLink.applyStreamLifecycleMiddleware
282
+ * stamps it before either method runs), so both sites agree on which one
283
+ * actually fires without a second, Vertex-specific marker — and without
284
+ * relying on a Symbol on the `StreamResult` itself, which would not
285
+ * survive `withStreamModelFallback`'s rebuild of a plain forwarding object
286
+ * between this method and `wrapStreamWithLifecycleCallbacks`.
287
+ *
288
+ * onError is deliberately NOT gated by that marker — same precedent as
289
+ * `middleware/builtin/lifecycle.ts`'s `wrapStream` ("onError is NOT
290
+ * covered by this flag: it keeps its own, independent dedup via
291
+ * fireOnErrorOnce's shared fired-marker"). `fireOnErrorOnce` stamps the
292
+ * error so `BaseProvider`'s own catch (which checks
293
+ * `hasLifecycleErrorFired`) skips its own fire for the same failure.
274
294
  */
275
295
  private wrapStreamResultWithLifecycle;
276
296
  /**
@@ -22,6 +22,7 @@ import { FileDetector } from "../../utils/fileDetector.js";
22
22
  import { mergeMediaFileAliases, normalizeVisionImageFormats, processUnifiedFilesArray, } from "../../utils/messageBuilder.js";
23
23
  import { logger } from "../../utils/logger.js";
24
24
  import { drainDetachedPump } from "../../utils/drainDetachedPump.js";
25
+ import { fireOnErrorOnce, getLifecycleMiddlewareConfig, isLifecycleStreamCallbacksOwnedByBaseProvider, } from "../../utils/lifecycleCallbacks.js";
25
26
  import { GEMINI_ELISION_NOTE, planGeminiLoopReclaim, previewGeminiToolResponseText, } from "../../context/geminiLoopGuard.js";
26
27
  import { ANTHROPIC_ELISION_NOTE, planAnthropicLoopReclaim, previewAnthropicToolResultText, } from "../../context/anthropicLoopGuard.js";
27
28
  import { hasRestrictedOutputLimit, RESTRICTED_OUTPUT_TOKEN_LIMIT, toVertexAnthropicModelId, } from "../../utils/modelDetection.js";
@@ -5618,24 +5619,47 @@ export class GoogleVertexProvider extends BaseProvider {
5618
5619
  * this for free via the AI SDK `wrapStream` middleware; native @google/genai
5619
5620
  * bypasses that wrapper, so native consumers need their lifecycle
5620
5621
  * callbacks invoked from here.
5622
+ *
5623
+ * `BaseProvider.stream()` always passes the result this method returns
5624
+ * through `wrapStreamWithLifecycleCallbacks` afterwards too (that wrapper
5625
+ * has "no early return when there are no callbacks" — see its own doc
5626
+ * comment) — so unconditionally firing here double-fires onChunk/onFinish
5627
+ * for every native Vertex stream. `isLifecycleStreamCallbacksOwnedByBaseProvider`
5628
+ * reads the SAME nested lifecycle config off the SAME `options` object
5629
+ * `wrapStreamWithLifecycleCallbacks` reads from (NeuroLink.applyStreamLifecycleMiddleware
5630
+ * stamps it before either method runs), so both sites agree on which one
5631
+ * actually fires without a second, Vertex-specific marker — and without
5632
+ * relying on a Symbol on the `StreamResult` itself, which would not
5633
+ * survive `withStreamModelFallback`'s rebuild of a plain forwarding object
5634
+ * between this method and `wrapStreamWithLifecycleCallbacks`.
5635
+ *
5636
+ * onError is deliberately NOT gated by that marker — same precedent as
5637
+ * `middleware/builtin/lifecycle.ts`'s `wrapStream` ("onError is NOT
5638
+ * covered by this flag: it keeps its own, independent dedup via
5639
+ * fireOnErrorOnce's shared fired-marker"). `fireOnErrorOnce` stamps the
5640
+ * error so `BaseProvider`'s own catch (which checks
5641
+ * `hasLifecycleErrorFired`) skips its own fire for the same failure.
5621
5642
  */
5622
5643
  wrapStreamResultWithLifecycle(options, result, startTime) {
5623
5644
  const onChunk = options.onChunk;
5624
5645
  const onFinish = options
5625
5646
  .onFinish;
5626
- const onError = options
5627
- .onError;
5647
+ // `StreamOptions.onError` is already properly typed as `OnErrorCallback`
5648
+ // (unlike the two casts above, kept from before that field existed on
5649
+ // the type) — read directly so `fireOnErrorOnce` below gets it without
5650
+ // a cast.
5651
+ const onError = options.onError;
5628
5652
  if (typeof onChunk !== "function" &&
5629
5653
  typeof onFinish !== "function" &&
5630
5654
  typeof onError !== "function") {
5631
5655
  return result;
5632
5656
  }
5657
+ const streamCallbacksOwnedByBaseProvider = isLifecycleStreamCallbacksOwnedByBaseProvider(getLifecycleMiddlewareConfig(options));
5633
5658
  const originalIterable = result.stream;
5634
5659
  let accumulated = "";
5635
5660
  let sequence = 0;
5636
- const provider = this.providerName;
5637
5661
  const fireOnChunk = (payload) => {
5638
- if (typeof onChunk !== "function") {
5662
+ if (typeof onChunk !== "function" || streamCallbacksOwnedByBaseProvider) {
5639
5663
  return;
5640
5664
  }
5641
5665
  try {
@@ -5647,7 +5671,8 @@ export class GoogleVertexProvider extends BaseProvider {
5647
5671
  }
5648
5672
  };
5649
5673
  const fireOnFinish = (payload) => {
5650
- if (typeof onFinish !== "function") {
5674
+ if (typeof onFinish !== "function" ||
5675
+ streamCallbacksOwnedByBaseProvider) {
5651
5676
  return;
5652
5677
  }
5653
5678
  try {
@@ -5686,21 +5711,18 @@ export class GoogleVertexProvider extends BaseProvider {
5686
5711
  });
5687
5712
  }
5688
5713
  catch (err) {
5689
- if (typeof onError === "function") {
5690
- try {
5691
- const errInst = err instanceof Error ? err : new Error(String(err));
5692
- const cbResult = onError({
5693
- error: errInst,
5694
- duration: Date.now() - startTime,
5695
- recoverable: false,
5696
- });
5697
- Promise.resolve(cbResult).catch((e) => logger.warn(`[${provider}] onError callback rejected: ${e instanceof Error ? e.message : String(e)}`));
5698
- }
5699
- catch (e) {
5700
- logger.warn(`[${provider}] onError callback threw: ${e instanceof Error ? e.message : String(e)}`);
5701
- }
5702
- }
5703
- throw err;
5714
+ const errInst = err instanceof Error ? err : new Error(String(err));
5715
+ fireOnErrorOnce(onError, errInst, {
5716
+ error: errInst,
5717
+ duration: Date.now() - startTime,
5718
+ recoverable: false,
5719
+ });
5720
+ // Rethrow the normalized `errInst` (not the raw `err`) so the
5721
+ // fired-mark `fireOnErrorOnce` just stamped on it propagates to
5722
+ // BaseProvider's own catch (`hasLifecycleErrorFired`), which
5723
+ // would otherwise see an unstamped error and fire onError again
5724
+ // for the same logical failure.
5725
+ throw errInst;
5704
5726
  }
5705
5727
  },
5706
5728
  };
@@ -27,7 +27,36 @@
27
27
  * structuredClone / cross-realm postMessage where a closed-over
28
28
  * WeakSet does not.
29
29
  */
30
- import type { LifecycleErrorPayload, OnErrorCallback } from "../types/index.js";
30
+ import type { LifecycleErrorPayload, LifecycleMiddlewareConfig, OnErrorCallback } from "../types/index.js";
31
+ /**
32
+ * Read the consumer-facing lifecycle callbacks buried inside a request's
33
+ * middleware blob. The parameter is `unknown` on purpose: request options
34
+ * arrive as several structurally-unrelated shapes (StreamOptions,
35
+ * TextGenerationOptions), and the lifecycle branch is an optional add-on
36
+ * none of them declare — a single structural view keeps the read cast-free
37
+ * at every call site.
38
+ *
39
+ * Shared (moved out of `BaseProvider`) because two independent firing sites
40
+ * now need the same read: `BaseProvider.wrapStreamWithLifecycleCallbacks`
41
+ * (every provider's stream) and `GoogleVertexProvider.wrapStreamResultWithLifecycle`
42
+ * (Vertex's own native-stream firer, which must check the SAME
43
+ * `isLifecycleStreamCallbacksOwnedByBaseProvider` marker on the SAME nested
44
+ * config object that BaseProvider's wrapper reads from `options`, so that
45
+ * both sites agree on who owns firing without a second marker).
46
+ */
47
+ export declare function getLifecycleMiddlewareConfig(options: unknown): LifecycleMiddlewareConfig | undefined;
48
+ /**
49
+ * Stamp a stream lifecycle config object with
50
+ * `STREAM_CALLBACKS_OWNED_BY_BASE_PROVIDER`. See that symbol's doc comment
51
+ * for who may call this (only `applyStreamLifecycleMiddleware`) and why.
52
+ */
53
+ export declare function markLifecycleStreamCallbacksOwnedByBaseProvider(config: object): void;
54
+ /**
55
+ * True when `markLifecycleStreamCallbacksOwnedByBaseProvider` already
56
+ * stamped this config. The model-level lifecycle middleware reads this to
57
+ * decide whether to fire its own onChunk/onFinish for a stream config.
58
+ */
59
+ export declare function isLifecycleStreamCallbacksOwnedByBaseProvider(config: unknown): boolean;
31
60
  /**
32
61
  * Returns true when `markLifecycleErrorFired` or a previous
33
62
  * `fireOnErrorOnce` call has already stamped this error.
@@ -28,6 +28,99 @@
28
28
  * WeakSet does not.
29
29
  */
30
30
  const ON_ERROR_FIRED = Symbol.for("neurolink.onErrorFired");
31
+ /**
32
+ * Read the consumer-facing lifecycle callbacks buried inside a request's
33
+ * middleware blob. The parameter is `unknown` on purpose: request options
34
+ * arrive as several structurally-unrelated shapes (StreamOptions,
35
+ * TextGenerationOptions), and the lifecycle branch is an optional add-on
36
+ * none of them declare — a single structural view keeps the read cast-free
37
+ * at every call site.
38
+ *
39
+ * Shared (moved out of `BaseProvider`) because two independent firing sites
40
+ * now need the same read: `BaseProvider.wrapStreamWithLifecycleCallbacks`
41
+ * (every provider's stream) and `GoogleVertexProvider.wrapStreamResultWithLifecycle`
42
+ * (Vertex's own native-stream firer, which must check the SAME
43
+ * `isLifecycleStreamCallbacksOwnedByBaseProvider` marker on the SAME nested
44
+ * config object that BaseProvider's wrapper reads from `options`, so that
45
+ * both sites agree on who owns firing without a second marker).
46
+ */
47
+ export function getLifecycleMiddlewareConfig(options) {
48
+ return options?.middleware
49
+ ?.middlewareConfig?.lifecycle?.config;
50
+ }
51
+ /**
52
+ * Marks a stream lifecycle config as "BaseProvider.wrapStreamWithLifecycleCallbacks
53
+ * already fires onChunk/onFinish for this config, authoritatively, from
54
+ * NeuroLink's own normalized stream — the model-level lifecycle middleware
55
+ * (middleware/builtin/lifecycle.ts) must not ALSO fire them from the raw
56
+ * per-provider V3 chunks it sees."
57
+ *
58
+ * Only `NeuroLink.applyStreamLifecycleMiddleware()` stamps a config with this
59
+ * marker, and only for stream()'s config: it is the one function that builds
60
+ * a lifecycle config fresh from stream()'s top-level onChunk/onFinish/onError
61
+ * options, for a call that unconditionally passes through
62
+ * `BaseProvider.wrapStreamWithLifecycleCallbacks` afterwards — see that
63
+ * method's doc comment ("the only point every provider's stream passes
64
+ * through unconditionally"). Without this marker both layers fire
65
+ * onChunk/onFinish for the same logical stream: once (unreliable, raw,
66
+ * provider-shape-dependent) from the model-level middleware, once (reliable,
67
+ * normalized) from BaseProvider.
68
+ *
69
+ * `NeuroLink.applyGenerateLifecycleMiddleware()` must NEVER stamp its config
70
+ * this way. BaseProvider has no equivalent guaranteed-delivery layer for
71
+ * generate()'s onFinish: `BaseProvider.finalizeNativeGenerate`'s own doc
72
+ * comment states onFinish is deliberately NOT fired there because "the
73
+ * native paths now wrap their model, so the lifecycle middleware fires it" —
74
+ * the model-level middleware is the SOLE firing site for every native
75
+ * generate loop that goes through the model wrap (sagemaker,
76
+ * openaiChatCompletionsBase, anthropic). The one generate path that bypasses
77
+ * the model wrap (GoogleVertex's native @google/genai loop) fires onFinish
78
+ * itself via `BaseProvider.fireGenerateOnFinish` precisely because the
79
+ * middleware never runs for it. Stamping the generate config would make the
80
+ * model-level middleware suppress its own firing with nothing left to fire
81
+ * it, silently breaking onFinish for every one of those providers.
82
+ *
83
+ * A config a consumer builds directly — e.g. calling the exported
84
+ * `createLifecycleMiddleware` and applying it to their own model via the
85
+ * `ai` package's `wrapLanguageModel`, entirely outside NeuroLink's
86
+ * BaseProvider/stream()/generate() call path — is never stamped, so that
87
+ * standalone use keeps firing onChunk/onFinish exactly as before (CLAUDE.md
88
+ * rule 5: public SDK API must not break existing callers).
89
+ *
90
+ * Symbol-stamped on the object itself (not a typed field) for the same
91
+ * reason as `ON_ERROR_FIRED` above: it survives across separate module
92
+ * copies of the same config object where a closed-over WeakSet would not.
93
+ */
94
+ const STREAM_CALLBACKS_OWNED_BY_BASE_PROVIDER = Symbol.for("neurolink.lifecycleStreamCallbacksOwnedByBaseProvider");
95
+ /**
96
+ * Stamp a stream lifecycle config object with
97
+ * `STREAM_CALLBACKS_OWNED_BY_BASE_PROVIDER`. See that symbol's doc comment
98
+ * for who may call this (only `applyStreamLifecycleMiddleware`) and why.
99
+ */
100
+ export function markLifecycleStreamCallbacksOwnedByBaseProvider(config) {
101
+ try {
102
+ Object.defineProperty(config, STREAM_CALLBACKS_OWNED_BY_BASE_PROVIDER, {
103
+ value: true,
104
+ enumerable: false,
105
+ writable: false,
106
+ configurable: false,
107
+ });
108
+ }
109
+ catch {
110
+ // Non-extensible config object — worst case is the pre-fix double fire.
111
+ }
112
+ }
113
+ /**
114
+ * True when `markLifecycleStreamCallbacksOwnedByBaseProvider` already
115
+ * stamped this config. The model-level lifecycle middleware reads this to
116
+ * decide whether to fire its own onChunk/onFinish for a stream config.
117
+ */
118
+ export function isLifecycleStreamCallbacksOwnedByBaseProvider(config) {
119
+ if (config === null || typeof config !== "object") {
120
+ return false;
121
+ }
122
+ return (config[STREAM_CALLBACKS_OWNED_BY_BASE_PROVIDER] === true);
123
+ }
31
124
  function stampFired(error) {
32
125
  try {
33
126
  Object.defineProperty(error, ON_ERROR_FIRED, {
@@ -7106,17 +7106,17 @@
7106
7106
  {"objectID":"7bc31a9a4e8ceaa9d384ce6953e41c6991344d5affeecf269a14248d769eb2f3","title":"\"xAI account has insufficient quota\"","url":"/docs/getting-started/providers/xai#xai-account-has-insufficient-quota","content":"Top up at https://console.x.ai/.","hierarchy":{"lvl0":"Getting Started","lvl1":"xAI Grok Provider Guide","lvl2":"\"xAI account has insufficient quota\"","lvl3":""}},
7107
7107
  {"objectID":"62c0c746ffa186a922276c344852f82e4ee3242f81c67b58a433defc4f4a4924","title":"\"Model not found\"","url":"/docs/getting-started/providers/xai#model-not-found","content":"Use one of the documented model IDs above. Custom fine-tunes are not\nexposed through the public API at this time.","hierarchy":{"lvl0":"Getting Started","lvl1":"xAI Grok Provider Guide","lvl2":"\"Model not found\"","lvl3":""}},
7108
7108
  {"objectID":"a55b6d892c11d17b5222d76096529452815cd15809b93c491a3c484756185910","title":"See Also","url":"/docs/getting-started/providers/xai#see-also","content":"Adding a new LLM provider — internal reference for the integration pattern this provider follows\nDeepSeek Provider — sibling OpenAI-compat provider with reasoning models\nGroq Provider — sibling OpenAI-compat provider with sub-100ms inference\n\nNeed Help? Open a GitHub Discussion or issue.","hierarchy":{"lvl0":"Getting Started","lvl1":"xAI Grok Provider Guide","lvl2":"See Also","lvl3":""}},
7109
- {"objectID":"03a891da2cc3529b3e4bfc042e5834454a779a2d3fa9389367ba4b3d05266871","title":"XOR Provider Guide","url":"/docs/getting-started/providers/xor","content":"XOR Provider Guide\n\nA provider of decide — the same typed boolean / choice /\nscore answers as TypeSafe's Jev and Laya, from\nJuspay's open-weights model, and the one that also reads images and a video. It\nemits no text at all.\n\nOverview\n\nXOR is Juspay's Apache-2.0 decision model, post-trained from Qwen3.6-35B-A3B;\nits model id is xor-1.1. You send one state plus named, typed questions, and\noptionally images or a video. XOR answers every question in a single batched\npass and returns a typed answer for each. NeuroLink calls whatever base URL you\nconfigure — a deployment of the model, or a LiteLLM proxy with a route to one.\nThere is no built-in endpoint. The weights and setup instructions are at\nhuggingface.co/juspay/xor.\n\nKey Facts\n\n| | |\n| --------------------- | -------------------------------------------------------------------- |\n| Inference type | decide only |\n| Model | xor-1.1 by default; XOR_MODEL overrides it |\n| Media | up to 8 images and one video; the whole request body at most 8 MB |\n| Input window | about 200,000 estimated tokens of state |\n| Questions per request | no cap in NeuroLink; a choice or score takes 2 to 255 options |\n| Endpoint | <base URL>/v1/systemone; the base URL is required, with no default |\n| Precedence | used automatically only when TypeSafe and Laya are not configured |\n\nQuick Start\nGet an endpoint and a key\n\nRun a deployment of XOR (see\nhuggingface.co/juspay/xor), or use a\nLiteLLM proxy with a route to one. NeuroLink posts to <base URL>/v1/systemone.\nThe base URL is the origin of the deployment or of the proxy route, and a\ntrailing /v1 is accepted. On a LiteLLM proxy the key's team must be allowed\nthe xor-1.1 model; otherwise the proxy answers 403 team_model_access_denied.\nConfigure\n\nBoth the base URL and the key are required. Set them in the environment:\n\nor in the config passed to the SDK, exactly as for any other provider. Values\npassed per call override the constructor's, which override the environment:\nUse it\n\nFrom the CLI\n\nThe CLI reads the same environment variables:\n\nImages and video\n\nXOR reads images and one video alongside the state. TypeSafe and Laya do not.\n\nFrom the CLI, pass --image <path> (repeatable) and --video <path>:\n\nThe rules:\nUp to 8 images and one video per request, in images and video.\nEach is a Buffer, a local file path or a data: URL. An http(s) URL is\n refused: NeuroLink does not fetch media for you.\nThe type comes from the bytes, not the file extension: PNG, JPEG, WebP\n and GIF images, and MP4, MOV and WebM video. NeuroLink\n sends a Buffer or a file to XOR as a data: URL. A data: URL you pass\n yourself must hold base64 image or video content, and is sent as given.\nThe whole request body may be at most 8 MB. The limit applies to the\n encoded body, so the base64 form counts, not the size of the files on disk. A\n file over the limit is refused from its size, before it is read.\nWhat can be checked locally is refused before any request, as a\n non-retryable invalid_request: a missing file, a directory, an empty Buffer\n or file, bytes that are not a recognised image or video, a remote URL or any\n other URL scheme, a string that is not a path or a data: URL, more than 8\n images, or a body over 8 MB. Bytes with the right signature that the model\n cannot decode are sent, and the server's refusal comes back as a server\n error, retried once.\nTypeSafe and Laya refuse media too, before any request, and the error\n names the providers that accept it.\nImages and a video can be sent together (up to 8 images and one video),\n but the model does not reliably tell the two apart.\nThe result carries mediaBytes, the encoded size of the media sent. The\n decide spans carry decision.images.count and decision.media.bytes, and\n never any base64.\n\nWhen NeuroLink uses it\n\nEvery built-in consumer of decide — model routing, relevance-driven\ncompaction, tool routing and RAG planning — asks for the default decision\nprovider. That is the first one that is configured, in the environment or in the\ncredentials passed to the SDK, in the order TypeSafe, Laya, XOR. TypeSafe\ncounts with either of its keys, TYPESAFE_API_KEY or AI_GATEWAY_API_KEY. Laya\nand XOR each count only with both their key and their base URL. A caller can\nalways name XOR with provider: \"xor\". So:\nA TypeSafe key, or Laya's key and base URL, plus XOR's key and base URL:\n built-in features use TypeSafe or Laya, in that order; XOR runs only where a\n caller asks for provider: \"xor\".\nOnly XOR's key and base URL: built-in features use XOR.\nAn XOR key with no base URL: XOR is not configured. Built-in features\n ignore it, and provider: \"xor\" fails with XOR requires a base URL.\nNone of them: everything behaves exactly as it did without a ","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"","lvl3":""}},
7109
+ {"objectID":"03a891da2cc3529b3e4bfc042e5834454a779a2d3fa9389367ba4b3d05266871","title":"XOR Provider Guide","url":"/docs/getting-started/providers/xor","content":"XOR Provider Guide\n\nA provider of decide — the same typed boolean / choice /\nscore answers as TypeSafe's Jev and Laya, from\nJuspay's open-weights model, and the one that also reads images and a video. It\nemits no text at all.\n\nOverview\n\nXOR is Juspay's Apache-2.0 decision model, post-trained from Qwen3.6-35B-A3B;\nits model id is xor-1.1. You send one state plus named, typed questions, and\noptionally images or a video. XOR answers every question in a single batched\npass and returns a typed answer for each. NeuroLink calls whatever base URL you\nconfigure — a deployment of the model, or a LiteLLM proxy with a route to one.\nThere is no built-in endpoint. The weights and setup instructions are at\nhuggingface.co/juspay/xor.\n\nKey Facts\n\n| | |\n| --------------------- | -------------------------------------------------------------------- |\n| Inference type | decide only |\n| Model | xor-1.1 by default; XOR_MODEL sets the name your proxy uses |\n| Media | up to 8 images and one video; the whole request body at most 8 MB |\n| Input window | about 200,000 estimated tokens of state |\n| Questions per request | no cap in NeuroLink; a choice or score takes 2 to 255 options |\n| Endpoint | <base URL>/v1/systemone; the base URL is required, with no default |\n| Precedence | used automatically only when TypeSafe and Laya are not configured |\n\nQuick Start\nGet an endpoint and a key\n\nRun a deployment of XOR (see\nhuggingface.co/juspay/xor), or use a\nLiteLLM proxy with a route to one. NeuroLink posts to <base URL>/v1/systemone.\nThe base URL is the origin of the deployment or of the proxy route, and a\ntrailing /v1 is accepted. On a LiteLLM proxy the key's team must be allowed\nthe model you ask for (xor-1.1 by default); otherwise the proxy answers 403\nteam_model_access_denied, and its message lists the models the team can use. A\nproxy may serve XOR under a name of its own: set XOR_MODEL to that name.\nConfigure\n\nBoth the base URL and the key are required. Set them in the environment:\n\nor in the config passed to the SDK, exactly as for any other provider. Values\npassed per call override the constructor's, which override the environment:\nUse it\n\nFrom the CLI\n\nThe CLI reads the same environment variables:\n\nImages and video\n\nXOR reads images and one video alongside the state. TypeSafe and Laya do not.\n\nFrom the CLI, pass --image <path> (repeatable) and --video <path>:\n\nThe rules:\nUp to 8 images and one video per request, in images and video.\nEach is a Buffer, a local file path or a data: URL. An http(s) URL is\n refused: NeuroLink does not fetch media for you.\nThe type comes from the bytes, not the file extension: PNG, JPEG, WebP\n and GIF images, and MP4, MOV and WebM video. NeuroLink\n sends a Buffer or a file to XOR as a data: URL. A data: URL you pass\n yourself must hold base64 image or video content, and is sent as given.\nThe whole request body may be at most 8 MB. The limit applies to the\n encoded body, so the base64 form counts, not the size of the files on disk. A\n file over the limit is refused from its size, before it is read.\nWhat can be checked locally is refused before any request, as a\n non-retryable invalid_request: a missing file, a directory, an empty Buffer\n or file, bytes that are not a recognised image or video, a remote URL or any\n other URL scheme, a string that is not a path or a data: URL, more than 8\n images, or a body over 8 MB. Bytes with the right signature that the model\n cannot decode are sent, and the server's refusal comes back as a server\n error, retried once.\nTypeSafe and Laya refuse media too, before any request, and the error\n names the providers that accept it.\nImages and a video can be sent together (up to 8 images and one video),\n but the model does not reliably tell the two apart.\nThe result carries mediaBytes, the encoded size of the media sent. The\n decide spans carry decision.images.count and decision.media.bytes, and\n never any base64.\n\nWhen NeuroLink uses it\n\nEvery built-in consumer of decide — model routing, relevance-driven\ncompaction, tool routing and RAG planning — asks for the default decision\nprovider. That is the first one that is configured, in the environment or in the\ncredentials passed to the SDK, in the order TypeSafe, Laya, XOR. TypeSafe\ncounts with either of its keys, TYPESAFE_API_KEY or AI_GATEWAY_API_KEY. Laya\nand XOR each count only with both their key and their base URL. A caller can\nalways name XOR with provider: \"xor\". So:\nA TypeSafe key, or Laya's key and base URL, plus XOR's key and base URL:\n built-in features use TypeSafe or Laya, in that order; XOR runs only where a\n caller asks for provider: \"xor\".\nOnly XOR's key and base URL: built-in features use XOR.\nAn XOR key with no base URL: XOR is not configure","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"","lvl3":""}},
7110
7110
  {"objectID":"5ed0d5759a64d045ba6c8acb04b0b6e6fef6f00399b88df9847c053d52a8e70c","title":"XOR Provider Guide","url":"/docs/getting-started/providers/xor#xor-provider-guide","content":"A provider of decide — the same typed boolean / choice /\nscore answers as TypeSafe's Jev and Laya, from\nJuspay's open-weights model, and the one that also reads images and a video. It\nemits no text at all.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"XOR Provider Guide","lvl3":""}},
7111
7111
  {"objectID":"62f1da4b03072e86ddddfcfed9098a9efa475d349ea26f88c901347eca7fc77a","title":"Overview","url":"/docs/getting-started/providers/xor#overview","content":"XOR is Juspay's Apache-2.0 decision model, post-trained from Qwen3.6-35B-A3B;\nits model id is xor-1.1. You send one state plus named, typed questions, and\noptionally images or a video. XOR answers every question in a single batched\npass and returns a typed answer for each. NeuroLink calls whatever base URL you\nconfigure — a deployment of the model, or a LiteLLM proxy with a route to one.\nThere is no built-in endpoint. The weights and setup instructions are at\nhuggingface.co/juspay/xor.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Overview","lvl3":""}},
7112
- {"objectID":"2a68b1796658b1e42d1fb20868e2f28c9b9b13e9e83ebb2ac4411de95d15b8e3","title":"Key Facts","url":"/docs/getting-started/providers/xor#key-facts","content":"| | |\n| --------------------- | -------------------------------------------------------------------- |\n| Inference type | decide only |\n| Model | xor-1.1 by default; XOR_MODEL overrides it |\n| Media | up to 8 images and one video; the whole request body at most 8 MB |\n| Input window | about 200,000 estimated tokens of state |\n| Questions per request | no cap in NeuroLink; a choice or score takes 2 to 255 options |\n| Endpoint | <base URL>/v1/systemone; the base URL is required, with no default |\n| Precedence | used automatically only when TypeSafe and Laya are not configured |","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Key Facts","lvl3":""}},
7113
- {"objectID":"06cdd61cf369f5af93c8e03841be4ebddcc8e1fde819b193e35a9fabfa7db51a","title":"1. Get an endpoint and a key","url":"/docs/getting-started/providers/xor#1-get-an-endpoint-and-a-key","content":"Run a deployment of XOR (see\nhuggingface.co/juspay/xor), or use a\nLiteLLM proxy with a route to one. NeuroLink posts to <base URL>/v1/systemone.\nThe base URL is the origin of the deployment or of the proxy route, and a\ntrailing /v1 is accepted. On a LiteLLM proxy the key's team must be allowed\nthe xor-1.1 model; otherwise the proxy answers 403 team_model_access_denied.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"1. Get an endpoint and a key","lvl3":""}},
7112
+ {"objectID":"2a68b1796658b1e42d1fb20868e2f28c9b9b13e9e83ebb2ac4411de95d15b8e3","title":"Key Facts","url":"/docs/getting-started/providers/xor#key-facts","content":"| | |\n| --------------------- | -------------------------------------------------------------------- |\n| Inference type | decide only |\n| Model | xor-1.1 by default; XOR_MODEL sets the name your proxy uses |\n| Media | up to 8 images and one video; the whole request body at most 8 MB |\n| Input window | about 200,000 estimated tokens of state |\n| Questions per request | no cap in NeuroLink; a choice or score takes 2 to 255 options |\n| Endpoint | <base URL>/v1/systemone; the base URL is required, with no default |\n| Precedence | used automatically only when TypeSafe and Laya are not configured |","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Key Facts","lvl3":""}},
7113
+ {"objectID":"06cdd61cf369f5af93c8e03841be4ebddcc8e1fde819b193e35a9fabfa7db51a","title":"1. Get an endpoint and a key","url":"/docs/getting-started/providers/xor#1-get-an-endpoint-and-a-key","content":"Run a deployment of XOR (see\nhuggingface.co/juspay/xor), or use a\nLiteLLM proxy with a route to one. NeuroLink posts to <base URL>/v1/systemone.\nThe base URL is the origin of the deployment or of the proxy route, and a\ntrailing /v1 is accepted. On a LiteLLM proxy the key's team must be allowed\nthe model you ask for (xor-1.1 by default); otherwise the proxy answers 403\nteam_model_access_denied, and its message lists the models the team can use. A\nproxy may serve XOR under a name of its own: set XOR_MODEL to that name.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"1. Get an endpoint and a key","lvl3":""}},
7114
7114
  {"objectID":"540db8a33720c64f9e3e59c312e3d3e2476799c726c6e5729729ecaf2bbefa05","title":"2. Configure","url":"/docs/getting-started/providers/xor#2-configure","content":"Both the base URL and the key are required. Set them in the environment:\n\nor in the config passed to the SDK, exactly as for any other provider. Values\npassed per call override the constructor's, which override the environment:","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"2. Configure","lvl3":""}},
7115
7115
  {"objectID":"3fd4afc9a2670046c937e06586a65b3b5eadb6de1560ee1e1a3abf5f05524876","title":"From the CLI","url":"/docs/getting-started/providers/xor#from-the-cli","content":"The CLI reads the same environment variables:","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"From the CLI","lvl3":""}},
7116
7116
  {"objectID":"90a0f1f65868c0d5b68bb8fb5828a32f704d4ed3a164081275a6837b65a8e2f7","title":"Images and video","url":"/docs/getting-started/providers/xor#images-and-video","content":"XOR reads images and one video alongside the state. TypeSafe and Laya do not.\n\nFrom the CLI, pass --image <path> (repeatable) and --video <path>:\n\nThe rules:\nUp to 8 images and one video per request, in images and video.\nEach is a Buffer, a local file path or a data: URL. An http(s) URL is\n refused: NeuroLink does not fetch media for you.\nThe type comes from the bytes, not the file extension: PNG, JPEG, WebP\n and GIF images, and MP4, MOV and WebM video. NeuroLink\n sends a Buffer or a file to XOR as a data: URL. A data: URL you pass\n yourself must hold base64 image or video content, and is sent as given.\nThe whole request body may be at most 8 MB. The limit applies to the\n encoded body, so the base64 form counts, not the size of the files on disk. A\n file over the limit is refused from its size, before it is read.\nWhat can be checked locally is refused before any request, as a\n non-retryable invalid_request: a missing file, a directory, an empty Buffer\n or file, bytes that are not a recognised image or video, a remote URL or any\n other URL scheme, a string that is not a path or a data: URL, more than 8\n images, or a body over 8 MB. Bytes with the right signature that the model\n cannot decode are sent, and the server's refusal comes back as a server\n error, retried once.\nTypeSafe and Laya refuse media too, before any request, and the error\n names the providers that accept it.\nImages and a video can be sent together (up to 8 images and one video),\n but the model does not reliably tell the two apart.\nThe result carries mediaBytes, the encoded size of the media sent. The\n decide spans carry decision.images.count and decision.media.bytes, and\n never any base64.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Images and video","lvl3":""}},
7117
7117
  {"objectID":"4fea1eacec247a5a95e9c0d017549cb5233da507c593a841ce0e392eb2d11e2a","title":"When NeuroLink uses it","url":"/docs/getting-started/providers/xor#when-neurolink-uses-it","content":"Every built-in consumer of decide — model routing, relevance-driven\ncompaction, tool routing and RAG planning — asks for the default decision\nprovider. That is the first one that is configured, in the environment or in the\ncredentials passed to the SDK, in the order TypeSafe, Laya, XOR. TypeSafe\ncounts with either of its keys, TYPESAFE_API_KEY or AI_GATEWAY_API_KEY. Laya\nand XOR each count only with both their key and their base URL. A caller can\nalways name XOR with provider: \"xor\". So:\nA TypeSafe key, or Laya's key and base URL, plus XOR's key and base URL:\n built-in features use TypeSafe or Laya, in that order; XOR runs only where a\n caller asks for provider: \"xor\".\nOnly XOR's key and base URL: built-in features use XOR.\nAn XOR key with no base URL: XOR is not configured. Built-in features\n ignore it, and provider: \"xor\" fails with XOR requires a base URL.\nNone of them: everything behaves exactly as it did without a decision\n model.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"When NeuroLink uses it","lvl3":""}},
7118
- {"objectID":"20a784de9dff5b3afa1f5ee7c0c60e268a7274b7e0c2a447aca431642f8fcabc","title":"Limits","url":"/docs/getting-started/providers/xor#limits","content":"The window is large, but shared. NeuroLink allows about 200,000 estimated\ntokens of state, and refuses more before any network call, with\nmax_tokens_exceeded. The size is an estimate, not XOR's tokenizer: about four\ncharacters per token for ASCII text, and one token per character for other\nscripts, which errs toward refusing. The 200,000 figure is a conservative\ndefault under the deployment's 250,000-token prefill, and that prefill is shared\nby the state, the questions and any media. It has not been measured against a\nlive deployment. A state near the limit, or a lot of media, can therefore still\nbe refused by the server as too long. That arrives as max_tokens_exceeded\n(not retried) when the status is 413 or the message says the context was too\nlong, and otherwise as server (retried once). Built-in consumers treat a\nrefusal as \"carry on as before\".\n\nNo question cap. NeuroLink sets none. A choice or score takes 2 to 255\noptions, which the server enforces.\n\nThe default timeout is 5 seconds. Override it per call with timeoutMs, or\nwith --timeout on the CLI.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Limits","lvl3":""}},
7119
- {"objectID":"d3945a40a2095e352b8f5f71228a59bbcb1cb60823696dbe20f3fafffe6b7c29","title":"Errors","url":"/docs/getting-started/providers/xor#errors","content":"| Reply | Kind | Retried |\n| ------------------------------------------------------------ | --------------------------------------------------------- | --------- |\n| 401 | authentication — the provider instance stops retrying | no |\n| 403 / 402 | invalid_request — fixable; the instance is not disabled | no |\n| 413, or a 5xx other than 503 saying the context was exceeded | max_tokens_exceeded | no |\n| any other 4xx (for example 422, 404) | invalid_request | no |\n| 429 | rate_limit | yes, once |\n| 503 | overloaded | yes, once |\n| other 5xx | server | yes, once |\n| state over the window (local) | max_tokens_exceeded, with no network call | no |\n| unusable media (local) | invalid_request, with no network call | no |\n| no base URL configured (local) | invalid_request, with no network call | no |\n\nA 403 or 402 is deliberately not authentication. On a LiteLLM proxy a 403\nmeans the key's team does not allow xor-1.1 (team_model_access_denied), and\na 402 means the team has no budget. An admin can fix either, so the provider\ninstance keeps working once it is fixed and is not disabled. A 401 does disable\nthe instance: it stops sending requests after the first rejection, b","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Errors","lvl3":""}},
7118
+ {"objectID":"20a784de9dff5b3afa1f5ee7c0c60e268a7274b7e0c2a447aca431642f8fcabc","title":"Limits","url":"/docs/getting-started/providers/xor#limits","content":"The window is large, but shared. NeuroLink allows about 200,000 estimated\ntokens of state, and refuses more before any network call, with\nmax_tokens_exceeded. The size is an estimate, not XOR's tokenizer: about four\ncharacters per token for ASCII text, and one token per character for other\nscripts, which errs toward refusing. The 200,000 figure is a conservative\ndefault under the deployment's 250,000-token prefill, and that prefill is shared\nby the state, the questions and any media.\n\nOn one live LiteLLM route, a state of about 215,000 tokens (1.2 million\ncharacters of English) was accepted, and one of about 430,000 tokens was refused\nwith a 500 whose text says the input is longer than the model's 262,144-token\ncontext length. The estimate is not exact in either direction. English averaged\nabout 5.6 characters per token there, so the estimate over-counts it. Dense JSON\naveraged about 1.8, so a large dense state can pass the local check and still be\nrefused by the server. A structured (non-string) state of 100,000 Chinese\ncharacters also passed the local check and was refused by the server.\n\nA state near the limit, or a lot of media, can therefore still be refused by the\nserver as too long. That arrives as max_tokens_exceeded (not retried) when the\nstatus is 413 or the message says the context was too long, and otherwise as\nserver (retried once). Built-in consumers treat a refusal as \"carry on as\nbefore\".\n\nA proxy may cap parallel requests. On that route the key allowed 5 at a\ntime. After a burst of rejected requests (403 and 422 replies), every request\nwas answered 429 for about half an hour. NeuroLink retries a 429 once.\n\nNo question cap. NeuroLink sets none. A choice or score takes 2 to 255\noptions, which the server enforces.\n\nThe default timeout is 5 seconds. Override it per call with timeoutMs, or\nwith --timeout on the CLI.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Limits","lvl3":""}},
7119
+ {"objectID":"d3945a40a2095e352b8f5f71228a59bbcb1cb60823696dbe20f3fafffe6b7c29","title":"Errors","url":"/docs/getting-started/providers/xor#errors","content":"| Reply | Kind | Retried |\n| ------------------------------------------------------------ | --------------------------------------------------------- | --------- |\n| 401 | authentication — the provider instance stops retrying | no |\n| 403 / 402 | invalid_request — fixable; the instance is not disabled | no |\n| 413, or a 5xx other than 503 saying the context was exceeded | max_tokens_exceeded | no |\n| any other 4xx (for example 422, 404) | invalid_request | no |\n| 429 | rate_limit | yes, once |\n| 503 | overloaded | yes, once |\n| other 5xx | server | yes, once |\n| state over the window (local) | max_tokens_exceeded, with no network call | no |\n| unusable media (local) | invalid_request, with no network call | no |\n| no base URL configured (local) | invalid_request, with no network call | no |\n\nA 403 or 402 is deliberately not authentication. On a LiteLLM proxy a 403\nmeans the key's team does not allow the model you asked for\n(team_model_access_denied), and\na 402 means the team has no budget. An admin can fix either, so the provider\ninstance keeps working once it is fixed and is not disabled. A 401 does disable\nthe instance: it stops sending requests after the fi","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Errors","lvl3":""}},
7120
7120
  {"objectID":"37466cda852e51cad898009d283cae74e18a20774a3ca0fad9f146e3efb8a5a9","title":"Troubleshooting","url":"/docs/getting-started/providers/xor#troubleshooting","content":"XOR requires a base URL — set XOR_BASE_URL, or pass\n credentials.xor.baseURL. There is no default endpoint.\nThe XOR base URL must not carry credentials… — the base URL has a user\n name, a password, a query string or a fragment. Set it to the origin only, for\n example https://your-proxy.example.com, and give the key through\n XOR_API_KEY or credentials.xor.apiKey.\nThe XOR base URL must start with https:// or http:// — the value has no\n scheme (for example xor.internal:8080) or uses another scheme. Add\n https:// or http://.\nThe XOR base URL is not a valid absolute URL — the value cannot be\n parsed as a URL. Set it to an absolute origin.\nXOR requires an API key — set XOR_API_KEY, or pass\n credentials.xor.apiKey.\nA 401 — the endpoint rejected the key. The provider instance stops\n retrying after a rejection, so fix the key and construct a new one.\nA 403 with team_model_access_denied — the LiteLLM proxy's team for this\n key does not allow xor-1.1. Add the model to the team; the instance works\n again without a restart.\nmax_tokens_exceeded — the state is over about 200,000 estimated tokens,\n or the state, the questions and the media together are more than the\n deployment's prefill. Shorten the state, or send fewer or smaller images or a\n shorter video.\nImages seem to be ignored — a deployment started without\n OPENJEV_IMAGES=1 answers 200 and silently ignores images, and NeuroLink\n cannot detect that. Send a red image and a blue image and check that the two\n answers differ. Video is not affected.\nBuilt-in routing never uses XOR — a TypeSafe key, or Laya's key and base\n URL, is also set and takes precedence, or XOR has no base URL.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Troubleshooting","lvl3":""}},
7121
7121
  {"objectID":"cdd6e7c83dfa23afa5cf0bb7d23081881600f72a5ac64ca56d383736c209f285","title":"See also","url":"/docs/getting-started/providers/xor#see-also","content":"TypeSafe (Jev) Provider Guide\nLaya Provider Guide\nThe decide inference type\nXOR on Hugging Face","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"See also","lvl3":""}},
7122
7122
  {"objectID":"b62066defb7f4f21e315563cc367d3573fb4aacd3fa38685f76f1a3b110c0437","title":"Z.AI Provider Guide","url":"/docs/getting-started/providers/z-ai","content":"Z.AI Provider Guide\n\nZ.AI is a Tier-2 catalog provider: OpenAI-wire-compatible, so its entire\nintegration is one JSON file (src/lib/providers/catalog/z-ai.json) rather\nthan hand-written code. That file is the source of truth for everything on this\npage.\n\nVerification status: this entry is docs-verified only, not yet\nlive-verified. The vendor's GET https://api.z.ai/api/paas/v4/models needs\na key (it answers HTTP 401 without one), so the model ids come from Z.AI's\npublic docs and the roster has not been checked. No account was created and no\nAPI key was used to build this entry. evidence.liveMatrix is null until\nsomeone runs the live capability matrix with a real key (see\nVerification status below).\n\nKey Facts\nProvider id: z-ai (alias zai)\nProtocol: OpenAI-compatible (/chat/completions). The\n OpenAI Python SDK page\n says \"Z.AI provides interfaces compatible with OpenAI API\" and adds \"In some\n scenarios, there are still differences between Z.AI and OpenAI interfaces, but\n this does not affect overall compatibility.\"\nBase URL: https://api.z.ai/api/paas/v4 — the\n Introduction page calls it\n \"Z.ai Platform's general API endpoint\". Its warning says GLM Coding Plan users\n follow the Coding Plan tutorial to configure a dedicated endpoint, and the\n Coding Plan Endpoint Guide lists\n https://api.z.ai/api/coding/paas/v4 for OpenAI Chat Completions. This entry\n uses the general endpoint; Z_AI_BASE_URL overrides it.\nDefault model: glm-5.3 — the default of the model parameter in the\n Chat Completion reference\n and the model in its request examples\nModels in catalog: 12, curated from the 21 ids the Chat Completion\n reference lists (14 in its text-model enum, 7 in its vision-model enum). The\n nine ids left out to stay inside the 12-model cap are glm-4.5-x,\n glm-4.5-airx, glm-4.5-flash, glm-4-32b-0414-128k, glm-4.6v,\n glm-4.6v-flash, glm-4.6v-flashx, glm-4.5v and\n autoglm-phone-multilingual\nStreaming: supported — the\n Streaming Messages page\n documents stream=True with Server-Sent Events, and the Chat Completion\n reference says the Event Stream ends with a data: [DONE] message\nTool calling: model-dependent — the\n Function Calling page\n documents tools and a returned tool_calls array, and the Chat Completion\n reference says of tool_choice \"The default value is auto, and only auto is\n supported.\" For vision models it says tools are \"Only supported by\n GLM-5.3-Flash series, the GLM-4.6V series, and autoglm-phone-multilingual\"\nTools while streaming: supported (true) — the\n Thinking Mode page\n gives an Interleaved Thinking + Tool Calling example that sends tools with\n stream=True and reads tool_calls from the streamed deltas. The\n Tool Streaming Output page\n documents a separate tool_stream parameter, which this entry does not set.\n The Thinking Mode page also says \"thinking blocks should be explicitly\n preserved and returned together with the tool results.\", and its example\n appends an assistant message carrying reasoning_content and tool_calls\n before the tool message; this entry does not set\n quirks.replayReasoningContent\nStructured output: supported — the\n Structured Output page\n documents response_format set to {\"type\": \"json_object\"}. The Chat\n Completion reference lists text and json_object as the response_format\n type values, so the entry sets quirks.responseFormatDowngrade to send\n json_object for schema requests; generate({ schema }) still validates the\n result client-side. No request with json_schema was sent. The Chat\n Completion reference says of response_format \"Only text models support this\n field.\", and the\n GLM-5.3-Flash/FlashX page\n lists Structured Output among its Capabilities (\"Supports structured output\n formats such as JSON for seamless system integration.\")\nStructured output + tools together: not declared (false) — no combined\n probe was possible without credentials\nVision: glm-5.3-flash and glm-5.3-flashx — the\n GLM-5.3-Flash/FlashX page\n shows Input Modality \"Video / Image / Text / File\". The GLM-5.3 page says\n \"text-only inputs\", and the pages for the other nine catalog models show\n Input Modalities \"Text\"\nEmbeddings: not declared on this catalog entry\nThinking: not declared (false) — the entry sets none of the thinking\n or reasoning_effort request parameters. The Chat Completion reference\n documents both, and the\n GLM-5.3 page says \"GLM-5.3 always\n operates with reasoning enabled\"\nBilling: schema value free-tier, see Billing below\nKey format: none declared. The Introduction page shows the header\n Authorization: Bearer ZAI_API_KEY\n\nQuick Start\nGet an API key\nVisit: https://z.ai/manage-apikey/apikey-list — the Quick Start says to access the Z.AI Open Platform (https://z.ai/model-api) and \"Register or Login.\", then to \"Create an API Key\" in the API Keys management page and \"Copy your API Key for use.\"\nAuthentication is HTTP Bearer: the Introduction page (https://docs.z.ai/api-reference/introduction.md) shows the header Authorization: Bearer ZAIAPIKEY\n","hierarchy":{"lvl0":"Getting Started","lvl1":"Z.AI Provider Guide","lvl2":"","lvl3":""}},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "12.41.2",
3
+ "version": "12.41.4",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
6
6
  "author": {
@@ -699,7 +699,7 @@
699
699
  "brace-expansion@>=5.0.0 <5.0.12": ">=5.0.12",
700
700
  "@opentelemetry/core@>=2.0.0 <2.8.0": ">=2.8.0",
701
701
  "qs": "6.16.0",
702
- "fastify": "5.12.1",
702
+ "fastify": "5.12.5",
703
703
  "fast-uri": "3.1.8",
704
704
  "@xmldom/xmldom": "0.9.12"
705
705
  },