@juspay/neurolink 13.4.10 → 13.5.1

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.
Files changed (145) hide show
  1. package/CHANGELOG.md +2 -3
  2. package/README.md +3 -2
  3. package/dist/adapters/video/videoAnalyzer.d.ts +1 -0
  4. package/dist/adapters/video/videoAnalyzer.js +4 -0
  5. package/dist/autoresearch/index.d.ts +1 -0
  6. package/dist/autoresearch/index.js +4 -0
  7. package/dist/autoresearch/worker.js +4 -0
  8. package/dist/browser/neurolink.min.js +466 -466
  9. package/dist/cli/commands/decide.js +25 -5
  10. package/dist/cli/commands/observability.js +11 -4
  11. package/dist/cli/commands/telemetry.js +36 -6
  12. package/dist/cli/utils/formatters.d.ts +17 -0
  13. package/dist/cli/utils/formatters.js +39 -0
  14. package/dist/constants/langfuse.d.ts +17 -1
  15. package/dist/constants/langfuse.js +23 -1
  16. package/dist/constants/telemetry.d.ts +6 -0
  17. package/dist/constants/telemetry.js +6 -0
  18. package/dist/core/baseProvider.js +2 -0
  19. package/dist/core/geminiLoopAdapter.js +19 -0
  20. package/dist/core/loopEngine.js +10 -7
  21. package/dist/core/modules/StreamHandler.js +5 -4
  22. package/dist/core/modules/TelemetryHandler.d.ts +5 -1
  23. package/dist/core/modules/TelemetryHandler.js +16 -0
  24. package/dist/core/modules/ToolsManager.js +3 -0
  25. package/dist/core/nativeGenerateLoop.js +1 -1
  26. package/dist/evaluation/index.d.ts +1 -0
  27. package/dist/evaluation/index.js +4 -0
  28. package/dist/evaluation/ragasEvaluator.js +1 -0
  29. package/dist/factories/providerDescriptors.d.ts +4 -3
  30. package/dist/factories/providerDescriptors.js +6 -4
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/mcp/externalServerManager.js +6 -2
  34. package/dist/mcp/toolRegistry.js +3 -0
  35. package/dist/neurolink.d.ts +20 -10
  36. package/dist/neurolink.js +134 -65
  37. package/dist/observability/FEATURE-STATUS.md +0 -4
  38. package/dist/observability/exporterRegistry.d.ts +6 -1
  39. package/dist/observability/exporterRegistry.js +18 -0
  40. package/dist/observability/exporterSpanProcessor.d.ts +20 -6
  41. package/dist/observability/exporterSpanProcessor.js +166 -34
  42. package/dist/observability/exporters/baseExporter.d.ts +22 -3
  43. package/dist/observability/exporters/baseExporter.js +87 -5
  44. package/dist/observability/exporters/langfuseExporter.d.ts +29 -3
  45. package/dist/observability/exporters/langfuseExporter.js +251 -41
  46. package/dist/observability/langfuseMasking.d.ts +1 -1
  47. package/dist/observability/langfuseMasking.js +1 -1
  48. package/dist/observability/otlpEncoding.d.ts +18 -5
  49. package/dist/observability/otlpEncoding.js +69 -12
  50. package/dist/observability/utils/spanSerializer.d.ts +3 -2
  51. package/dist/observability/utils/spanSerializer.js +5 -3
  52. package/dist/processors/base/BaseFileProcessor.d.ts +1 -0
  53. package/dist/processors/base/BaseFileProcessor.js +4 -0
  54. package/dist/processors/index.d.ts +1 -0
  55. package/dist/processors/index.js +4 -0
  56. package/dist/processors/registry/ProcessorRegistry.d.ts +1 -0
  57. package/dist/processors/registry/ProcessorRegistry.js +5 -0
  58. package/dist/providers/amazonBedrock/client.js +7 -5
  59. package/dist/providers/amazonSagemaker.js +32 -12
  60. package/dist/providers/anthropic/client.js +1 -0
  61. package/dist/providers/anthropic/loopAdapter.js +7 -3
  62. package/dist/providers/catalog/provider-catalog.schema.json +64 -3
  63. package/dist/providers/catalog/schema.js +81 -1
  64. package/dist/providers/catalog/xor.json +30 -10
  65. package/dist/providers/configuredDecision.d.ts +8 -1
  66. package/dist/providers/configuredDecision.js +22 -0
  67. package/dist/providers/decide/dialects/openaiDecisions.js +8 -0
  68. package/dist/providers/decide/dialects/systemOne.js +28 -2
  69. package/dist/providers/decide/guards.d.ts +6 -0
  70. package/dist/providers/decide/guards.js +22 -0
  71. package/dist/providers/googleAiStudio/client.js +2 -0
  72. package/dist/providers/googleVertex/client.d.ts +6 -5
  73. package/dist/providers/googleVertex/client.js +46 -19
  74. package/dist/providers/openaiChatCompletionsBase.js +4 -4
  75. package/dist/providers/systemOneDecision.d.ts +30 -7
  76. package/dist/providers/systemOneDecision.js +71 -14
  77. package/dist/proxy/codexUsage.js +59 -2
  78. package/dist/proxy/proxyTracer.d.ts +25 -1
  79. package/dist/proxy/proxyTracer.js +146 -22
  80. package/dist/proxy/proxyTranslationEngine.js +4 -2
  81. package/dist/proxy/sseInterceptor.js +6 -4
  82. package/dist/rag/chunkers/BaseChunker.js +1 -0
  83. package/dist/rag/index.d.ts +1 -0
  84. package/dist/rag/index.js +4 -0
  85. package/dist/rag/ragIntegration.js +1 -0
  86. package/dist/rag/reranker/reranker.js +2 -0
  87. package/dist/rag/retrieval/vectorQueryTool.js +1 -0
  88. package/dist/server/routes/codexProxyRoutes.js +3 -1
  89. package/dist/server/utils/validation.d.ts +3 -3
  90. package/dist/services/server/ai/observability/instrumentation.d.ts +8 -7
  91. package/dist/services/server/ai/observability/instrumentation.js +217 -47
  92. package/dist/services/server/ai/observability/langfuseSetup.d.ts +19 -1
  93. package/dist/services/server/ai/observability/langfuseSetup.js +62 -4
  94. package/dist/services/server/ai/observability/otlpSetup.d.ts +12 -2
  95. package/dist/services/server/ai/observability/otlpSetup.js +55 -4
  96. package/dist/telemetry/aiMetrics.d.ts +56 -0
  97. package/dist/telemetry/aiMetrics.js +229 -0
  98. package/dist/telemetry/attributes.d.ts +24 -1
  99. package/dist/telemetry/attributes.js +94 -9
  100. package/dist/telemetry/chatMessages.d.ts +6 -0
  101. package/dist/telemetry/chatMessages.js +37 -0
  102. package/dist/telemetry/index.d.ts +1 -1
  103. package/dist/telemetry/index.js +1 -1
  104. package/dist/telemetry/observation.d.ts +5 -5
  105. package/dist/telemetry/observation.js +128 -39
  106. package/dist/telemetry/spanSanitizer.d.ts +2 -2
  107. package/dist/telemetry/spanSanitizer.js +76 -8
  108. package/dist/telemetry/telemetryStarter.d.ts +16 -0
  109. package/dist/telemetry/telemetryStarter.js +28 -0
  110. package/dist/telemetry/traceContext.d.ts +3 -3
  111. package/dist/telemetry/traceContext.js +3 -3
  112. package/dist/telemetry/tracers.d.ts +13 -1
  113. package/dist/telemetry/tracers.js +49 -10
  114. package/dist/telemetry/withSpan.d.ts +19 -3
  115. package/dist/telemetry/withSpan.js +149 -20
  116. package/dist/types/cli.d.ts +2 -0
  117. package/dist/types/decision.d.ts +117 -10
  118. package/dist/types/exporter.d.ts +98 -3
  119. package/dist/types/livekit.d.ts +16 -1
  120. package/dist/types/loopEngine.d.ts +16 -1
  121. package/dist/types/observability.d.ts +38 -5
  122. package/dist/types/providerCatalog.d.ts +12 -1
  123. package/dist/types/providers.d.ts +2 -0
  124. package/dist/types/proxy.d.ts +2 -0
  125. package/dist/types/span.d.ts +26 -0
  126. package/dist/utils/decisionAnswers.d.ts +7 -1
  127. package/dist/utils/decisionAnswers.js +43 -2
  128. package/dist/utils/decisionLimits.d.ts +13 -1
  129. package/dist/utils/decisionLimits.js +52 -1
  130. package/dist/utils/decisionMedia.d.ts +4 -2
  131. package/dist/utils/decisionMedia.js +216 -14
  132. package/dist/utils/timeout.js +4 -2
  133. package/dist/voice/livekit/realtimeMcpTools.js +11 -2
  134. package/dist/voice/livekit/realtimeMemory.d.ts +20 -2
  135. package/dist/voice/livekit/realtimeMemory.js +139 -46
  136. package/dist/voice/livekit/realtimeVoiceAgent.js +20 -8
  137. package/dist/voice/livekit/turnRecorder.js +22 -9
  138. package/dist/workflow/core/ensembleExecutor.js +2 -0
  139. package/dist/workflow/core/judgeScorer.js +1 -0
  140. package/dist/workflow/core/responseConditioner.js +1 -0
  141. package/dist/workflow/core/workflowRunner.js +6 -1
  142. package/dist/workflow/index.d.ts +1 -0
  143. package/dist/workflow/index.js +4 -0
  144. package/docs-site/static/search-index.json +56 -48
  145. package/package.json +21 -2
@@ -2375,7 +2375,7 @@
2375
2375
  {"objectID":"4f094fd61e2348ac879d6bed5a47682726fba98bf348ab466f147eac476d4b7b","title":"Telemetry Cross-Check","url":"/docs/features/claude-proxy-observability#telemetry-cross-check","content":"Use this tab to validate the OTEL export path itself.\nThese panels are shown as per-window OTEL deltas, not raw cumulative counter values.\nMetric Requests in Range, Metric Errors in Range, and Metric Retries in Range should broadly agree with the earlier log-derived charts.\nIf Metric Request Trend (5m) is flat while Request Trend (5m) is moving, the metrics pipeline is broken or delayed.\nIf costs or request body volume stop moving here while logs keep arriving, OTEL metrics are unhealthy even if log export still works.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy Observability","lvl2":"Telemetry Cross-Check","lvl3":""}},
2376
2376
  {"objectID":"c075b88266c39bebcadf533966f852a8499b58933fa0750e24a42a418d58ef3c","title":"Tokens, Cache & Cost","url":"/docs/features/claude-proxy-observability#tokens-cache-cost","content":"Use this tab to understand workload mix and cache behavior.\nPrompt Tokens (M) is prompt-side volume in millions: uncached input plus cache writes plus cache reads.\nCached Prompt Tokens Reused (M) is actual cache reuse. These tokens were read from an existing prompt cache entry.\nCached Prompt Tokens Written (M) is cache population. These tokens were written into a new cache entry on that request and can be reused by later requests.\nCache Reuse Ratio is reused cache tokens divided by newly written cache tokens. Values above 1 mean reuse is outpacing cache writes.\nMean Total Tokens per Request is average prompt-side plus output token volume per request, shown as raw tokens.\nInput vs Output Tokens per Request (5m) compares average input and output tokens per request as raw tokens, which is easier to read than total prompt-side volume when cache reuse is large.\nCache Reuse vs Cache Write Trend (5m, M) keeps cache movement on its own scale so cache traffic does not flatten the input/output chart.\nToken Volume by Model (M) tells you which model families are driving token volume.\nTop Sessions by Token Volume (M) helps identify unusually heavy sessions for trace drilldown.\nInput vs Output Tokens by Account / Route shows raw token totals by real account or fallback route, with internal final rows excluded.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy Observability","lvl2":"Tokens, Cache & Cost","lvl3":""}},
2377
2377
  {"objectID":"5c0cf2922947c702cf935fb47c4d37e12d31ac9346dd702fd5589d31db75b7ad","title":"Trace Drilldown","url":"/docs/features/claude-proxy-observability#trace-drilldown","content":"Use this tab after you know there is a problem and need request-level evidence.\nSlowest Operations by Mean Duration is the best starting point for deep latency debugging.\nSpan Status Mix tells you whether failures are surfacing in traces as well as logs.\nSpan Volume by Operation and Trace Volume Trend help confirm whether the trace pipeline matches traffic volume.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy Observability","lvl2":"Trace Drilldown","lvl3":""}},
2378
- {"objectID":"45db8d2cd8c2aa49d45464157eaab809fbc367f283acd0888a21e8a79d7f526f","title":"Key Correlation Fields","url":"/docs/features/claude-proxy-observability#key-correlation-fields","content":"These fields matter most when moving between logs, metrics, and traces:\n_timestamp: event time in OpenObserve\nrequest_id: request-level correlation key in proxy logs\ntrace_id: cross-signal trace correlation key\nspan_id: specific span correlation key\nevent.name: distinguishes request summaries from proxy.body_capture debug events in the shared OpenObserve log stream\naccount_name: which account handled the request\nai_model: which model served the request\nai_input_tokens: prompt/input tokens\nai_output_tokens: completion/output tokens\nai_cache_creation_tokens: tokens spent creating cache entries\nai_cache_read_tokens: tokens served from cache\n\nWhen a caller injects traceparent plus x-neurolink-session-id / x-neurolink-user-id / x-neurolink-conversation-id, the proxy attaches its spans to that upstream trace and preserves session-level attribution across SDK and proxy telemetry.\n\nai_cache_creation_tokens means prompt tokens written into a new cache entry.\nai_cache_read_tokens means prompt tokens reused from an existing cache entry.\nAll latency and duration panels are shown in whole seconds for faster scanning.\nCounts and token-heavy charts default to whole numbers when practical, while ratios, costs, and million/MB rollups are capped at two decimals.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy Observability","lvl2":"Key Correlation Fields","lvl3":""}},
2378
+ {"objectID":"45db8d2cd8c2aa49d45464157eaab809fbc367f283acd0888a21e8a79d7f526f","title":"Key Correlation Fields","url":"/docs/features/claude-proxy-observability#key-correlation-fields","content":"These fields matter most when moving between logs, metrics, and traces:\n_timestamp: event time in OpenObserve\nrequest_id: request-level correlation key in proxy logs\ntrace_id: cross-signal trace correlation key\nspan_id: specific span correlation key\nevent.name: distinguishes request summaries from proxy.body_capture debug events in the shared OpenObserve log stream\naccount_name: which account handled the request\nai_model: which model served the request\nai_input_tokens: prompt/input tokens\nai_output_tokens: completion/output tokens\nai_cache_creation_tokens: tokens spent creating cache entries\nai_cache_read_tokens: tokens served from cache\n\nWhen a caller injects traceparent plus x-neurolink-session-id / x-neurolink-user-id / x-neurolink-conversation-id, the proxy attaches its spans to that upstream trace and preserves session-level attribution across SDK and proxy telemetry.\n\nRequest and response bodies are recorded on traces only when NEUROLINK_PROXY_TRACE_BODY_LOGGING=true, the same opt-in as before the Langfuse migration. The prompt and answer of a translated request follow the same opt-in. Upstream error text is recorded without it, as before the migration, with credentials redacted and the text capped, and header values are recorded as received, except those of credential headers, which read [REDACTED]. See Observability: Proxy for how mask, redactIO and NEUROLINK_RECORD_INPUTS apply to them.\n\nai_cache_creation_tokens means prompt tokens written into a new cache entry.\nai_cache_read_tokens means prompt tokens reused from an existing cache entry.\nAll latency and duration panels are shown in whole seconds for faster scanning.\nCounts and token-heavy charts default to whole numbers when practical, while ratios, costs, and million/MB rollups are capped at two decimals.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy Observability","lvl2":"Key Correlation Fields","lvl3":""}},
2379
2379
  {"objectID":"8bb1c369b8726670a83b3b3a3153802808193a7695b7821cce795125774426ac","title":"Common Interpretation Patterns","url":"/docs/features/claude-proxy-observability#common-interpretation-patterns","content":"Rising Failed Request Share with flat traffic usually means a real reliability regression, not just more volume.\nRising 429 Rate-Limit Responses with high load on one account usually means the pool is exhausting the primary account as designed.\nLog traffic moving while the telemetry tab is flat means the OTEL metrics path needs attention.\nRising Cache Creation Tokens without matching Cache Read Tokens means prompt reuse is weak or the cache is still warming.\nA slow chart on the latency tab plus the same operation on the trace tab gives you the fastest path to a concrete trace investigation.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy Observability","lvl2":"Common Interpretation Patterns","lvl3":""}},
2380
2380
  {"objectID":"b0d3dc20fd803ddbb9b70e250c08dcb61c0625f053b4d2b37d514d6c04811ad0","title":"Request telemetry and evidence quality","url":"/docs/features/claude-proxy-observability#request-telemetry-and-evidence-quality","content":"Use neurolink proxy analyze --since 1h --format json to reconcile retained\nrequest, attempt, lifecycle, and capture-index records. Read dataQuality before\ninterpreting success rates or latency. The analyzer does not require captured\nprompt or response bodies.\n\nA client request has one generated requestId. Internal Codex fallback attempts\nretain their own ID plus parentRequestId, model, account, and reasoningEffort.\nThe final Claude record retains the configured fallbackPlan; attempt records\nshow which entries were actually tried. This evidence survives body retention.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy Observability","lvl2":"Request telemetry and evidence quality","lvl3":""}},
2381
2381
  {"objectID":"f8656d5ea4697344b223c17e9e251bb30391df25ba7f7abc5ea6f0c98a5ad33d","title":"Completion and timing","url":"/docs/features/claude-proxy-observability#completion-and-timing","content":"| Field | What it establishes |\n| ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| responseStatus on a lifecycle terminal | HTTP status committed by the adapter; a stream can still fail after HTTP 200 |\n| finalStatus, terminalOutcome, outcomeSource | Route outcome, joined before terminal publication; unknown means final evidence was unavailable |\n| transportOutcome | EOF, bodyless response, read error, or cancellation observed by the response adapter |\n| telemetryStatus | Whether terminal bookkeeping completed, timed out, failed, or lacked a final record; separate from the provider error |\n| latencyMs.firstChunk | First body chunk, including SSE control events |\n| latencyMs.firstUsefulOutput | First nonempty text or tool-argument delta parsed at the proxy; excludes thinking and control events |\n| latencyMs.terminal | Adapter terminal time, captured before waiting for bookkeeping or log writes |\n\nAnthropic streams require message_stop; native Codex streams require\nresponse.completed. In-band error events, incomplete responses, and EOF without\nthe expected completion event are failures even if HTTP 200 was already sent.\nAn unterminated SSE event is not dispatched completion evidence.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy Observability","lvl2":"Completion and timing","lvl3":""}},
@@ -2818,23 +2818,25 @@
2818
2818
  {"objectID":"5a5a49dbc72b02c8ce72d6d8646cdceef480dc78a4f59e6c53eba4fac7ee93b7","title":"Error Handling","url":"/docs/features/csv-support#error-handling","content":"Error Handling","hierarchy":{"lvl0":"Features","lvl1":"CSV File Support","lvl2":"Error Handling","lvl3":""}},
2819
2819
  {"objectID":"1e48831d1c4fc775af2633d0340bdeb3d11c96abc213cbdd814a53d14ce500df","title":"Related Features","url":"/docs/features/csv-support#related-features","content":"Office Documents: DOCX, PPTX, XLSX processing\nPDF Support: PDF document processing\nImage Support: Similar multimodal input for images\nFile Detection: Auto-detect file types with confidence scores\nMemory Efficient: Streaming parser for large files\nProvider Agnostic: Works across supported providers\nCLI Integration: Full CLI support with options","hierarchy":{"lvl0":"Features","lvl1":"CSV File Support","lvl2":"Related Features","lvl3":""}},
2820
2820
  {"objectID":"19734b9ffaeb8376dfcebd944730a4cedae77bc1e7ed769733b8b71b1758c421","title":"Summary","url":"/docs/features/csv-support#summary","content":"CSV support is multimodal input (like images)\nUse csvFiles array or files array (auto-detect)\nCustomize with csvOptions (maxRows, formatStyle, includeHeaders)\nWorks across supported providers (not just vision models)\nMemory efficient streaming parser\nCLI support with --csv, --file, --csv-max-rows, --csv-format\nOnly types exposed from package (not classes)","hierarchy":{"lvl0":"Features","lvl1":"CSV File Support","lvl2":"Summary","lvl3":""}},
2821
- {"objectID":"fce0b532226adb069bbeb5459dc5e4ec28037617ca805845768d918653ddbfd5","title":"The `decide` inference type","url":"/docs/features/decide-inference-type","content":"The decide inference type\n\nDeep-dive: generate, stream, decide: a third inference type for NeuroLink —\nwhy this shipped as a provider rather than a subsystem, the five call sites, and the four\ntransport bugs found by adding the Vercel AI Gateway (two of which produced plausible output).\n\nNeuroLink recognises three inference types. Two of them produce text:\n\n| Type | Call | Produces |\n| ------------ | ------------------------ | ------------------------------ |\n| generate | neurolink.generate() | text |\n| stream | neurolink.stream() | text, incrementally |\n| decide | neurolink.decide() | typed judgements — no text |\n\nA decision model takes one state plus a map of named, typed questions and\nreturns one typed answer per question, all evaluated in a single parallel pass.\nThere is no text anywhere in the response, so nothing has to be parsed back out\nof prose. The decide providers are TypeSafe's Jev, Convai Innovations'\nopen-weights Laya, Juspay's open-weights XOR, which also reads images and\nvideo, Subquadratic's hosted SubQ Snap, Perplexity's hosted Decisions\nAPI, which also reads images,\nCloudflare's Clef models on Workers AI,\nwhich also read images, OpenAI's\nDecisions API (a public beta), which also reads images, and ScaleDown's\nhosted Classify models, which read text only.\n\nThis is not neurolink.evaluate(), which scores an\nalready-generated response with RAGAS scorers. Different feature, different\nword.\n\nThe three primitives\n\n| Type | Question | Answer fields |\n| --------- | ------------------------------ | ------------------------------------------------ |\n| boolean | Is this statement true? | probability (0–1) — no confidence |\n| choice | Which option from this set? | choice, probabilities, confidence |\n| score | Rate against an ordered rubric | score, legend, probabilities, confidence |\n\nAll three mix freely in one call.\n\nVocabulary note. TypeSafe calls the yes/no primitive a noul and answers\nit in a field of the same name. The Vercel AI SDK and Pydantic AI both renamed\nthat to boolean/probability when exposing it, and NeuroLink follows them —\nthe vendor's spelling is translated inside TypeSafeProvider, so another\ndecision provider slots in without changing any call site.\n\nConfidence is not probability\n\nprobabilities says what the model thinks. confidence says _whether you\nshould act on it_. On TypeSafe's Jev it is calibrated — derived from the\ndistribution, not self-reported — which is what makes it usable as a gate. Each\nprovider reports its own: Perplexity's is, in Perplexity's words, the model's own\ncertainty estimate and not the top probability, Perplexity does not call it\ncalibrated, and NeuroLink has not measured whether it is, so tune a threshold on\nyour own data (see the\nPerplexity guide).\n\nCalibration is a property of groups of answers, not a promise about any\none. Where a confidence is calibrated, answers scored 0.8 are right about 80%\nof the time across many answers. It does not mean a specific 0.8 answer is right.\n\nA boolean carries no confidence of its own. Use decisionBooleanConfidence(p)\n— distance from a coin flip, so 0.5 → 0 and 0/1 → 1. Note also that a boolean\nand an equivalent two-option choice are not guaranteed to agree, and\ncomplementary booleans do not reliably sum to 1, so a threshold tuned on one\nquestion shape does not transfer to another.\n\nEnabling it\n\nGet a key at console.typesafe.ai/keys.\n\nLaya, at a Laya server or a LiteLLM proxy route\n\nLaya has no built-in endpoint. The base URL and key can equally come from the\nconfig passed to the SDK, new NeuroLink({ credentials: { laya: { baseURL, apiKey } } }),\nor per call; config set there counts when NeuroLink picks the default decision\nprovider, exactly as the environment does.\n\nWhen a TypeSafe key (TYPESAFE_API_KEY or AI_GATEWAY_API_KEY) is configured\nalongside Laya, TypeSafe is the default; Laya runs where a caller names it\n(provider: \"laya\") or when neither TypeSafe key is set. Laya counts as\nconfigured only with both its key and its base URL. See the\nLaya provider guide.\n\nXOR, at a deployment or a LiteLLM proxy route\n\nXOR, Juspay's open-weights decision model, has no built-in endpoint either.\nNeuroLink calls <base URL>/v1/decisions, and a trailing /v1 on the base URL\nis accepted. The base URL must be https://; http:// is accepted only for\nlocalhost, 127.0.0.1 and [::1], because every request carries the key and\nthe state. A redirect from the endpoint is refused, not followed. The base URL\nand key can equally come from the config passed to the SDK,\nnew NeuroLink({ credentials: { xor: { baseURL, apiKey } } }), or per call; config set there counts when NeuroLink picks the default decision\nprovider, exactly as the environment does. On a LiteLLM proxy the key's team\nmust allow xor-latest (and xor-omni for media), otherwise the proxy answers 403","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"","lvl3":""}},
2822
- {"objectID":"f91e7ef7473aa411ff8c0d3b04381325781477c1876408ffd5469dbaf2d9584c","title":"The decide inference type","url":"/docs/features/decide-inference-type#the-decide-inference-type","content":"Deep-dive: generate, stream, decide: a third inference type for NeuroLink —\nwhy this shipped as a provider rather than a subsystem, the five call sites, and the four\ntransport bugs found by adding the Vercel AI Gateway (two of which produced plausible output).\n\nNeuroLink recognises three inference types. Two of them produce text:\n\n| Type | Call | Produces |\n| ------------ | ------------------------ | ------------------------------ |\n| generate | neurolink.generate() | text |\n| stream | neurolink.stream() | text, incrementally |\n| decide | neurolink.decide() | typed judgements — no text |\n\nA decision model takes one state plus a map of named, typed questions and\nreturns one typed answer per question, all evaluated in a single parallel pass.\nThere is no text anywhere in the response, so nothing has to be parsed back out\nof prose. The decide providers are TypeSafe's Jev, Convai Innovations'\nopen-weights Laya, Juspay's open-weights XOR, which also reads images and\nvideo, Subquadratic's hosted SubQ Snap, Perplexity's hosted Decisions\nAPI, which also reads images,\nCloudflare's Clef models on Workers AI,\nwhich also read images, OpenAI's\nDecisions API (a public beta), which also reads images, and ScaleDown's\nhosted Classify models, which read text only.\n\nThis is not neurolink.evaluate(), which scores an\nalready-generated response with RAGAS scorers. Different feature, different\nword.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"The decide inference type","lvl3":""}},
2823
- {"objectID":"2c0f154b9ae29f0af1a097de9ccb435db1e8f5c35a4df8bb91526eadb7b5eb97","title":"The three primitives","url":"/docs/features/decide-inference-type#the-three-primitives","content":"| Type | Question | Answer fields |\n| --------- | ------------------------------ | ------------------------------------------------ |\n| boolean | Is this statement true? | probability (0–1) — no confidence |\n| choice | Which option from this set? | choice, probabilities, confidence |\n| score | Rate against an ordered rubric | score, legend, probabilities, confidence |\n\nAll three mix freely in one call.\n\nVocabulary note. TypeSafe calls the yes/no primitive a noul and answers\nit in a field of the same name. The Vercel AI SDK and Pydantic AI both renamed\nthat to boolean/probability when exposing it, and NeuroLink follows them —\nthe vendor's spelling is translated inside TypeSafeProvider, so another\ndecision provider slots in without changing any call site.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"The three primitives","lvl3":""}},
2821
+ {"objectID":"fce0b532226adb069bbeb5459dc5e4ec28037617ca805845768d918653ddbfd5","title":"The `decide` inference type","url":"/docs/features/decide-inference-type","content":"The decide inference type\n\nDeep-dive: generate, stream, decide: a third inference type for NeuroLink —\nwhy this shipped as a provider rather than a subsystem, the five call sites, and the four\ntransport bugs found by adding the Vercel AI Gateway (two of which produced plausible output).\n\nNeuroLink recognises three inference types. Two of them produce text:\n\n| Type | Call | Produces |\n| ------------ | ------------------------ | ------------------------------ |\n| generate | neurolink.generate() | text |\n| stream | neurolink.stream() | text, incrementally |\n| decide | neurolink.decide() | typed judgements — no text |\n\nA decision model takes one state plus a map of named, typed questions and\nreturns one typed answer per question, all evaluated in a single parallel pass.\nThere is no text anywhere in the response, so nothing has to be parsed back out\nof prose. The decide providers are TypeSafe's Jev, Convai Innovations'\nopen-weights Laya, Juspay's open-weights XOR, which also reads an image\nand audio and extracts typed values, Subquadratic's hosted SubQ Snap, Perplexity's hosted Decisions\nAPI, which also reads images,\nCloudflare's Clef models on Workers AI,\nwhich also read images, OpenAI's\nDecisions API (a public beta), which also reads images, and ScaleDown's\nhosted Classify models, which read text only.\n\nThis is not neurolink.evaluate(), which scores an\nalready-generated response with RAGAS scorers. Different feature, different\nword.\n\nThe three primitives\n\n| Type | Question | Answer fields |\n| --------- | ------------------------------ | ------------------------------------------------ |\n| boolean | Is this statement true? | probability (0–1) — no confidence |\n| choice | Which option from this set? | choice, probabilities, confidence |\n| score | Rate against an ordered rubric | score, legend, probabilities, confidence |\n\nAll three mix freely in one call.\n\nTwo more, on XOR: value, and choice with unknown\n\nA provider lists the question types it reads in its decisionLimits\n(questionTypes); without the list it reads the three above. Asking anything\nelse is refused before any request, as a non-retryable invalid_request, and a\nhost question of such a type is dropped on its own (see\ndecisionHooks). XOR reads two more:\n\n| Type | Question | Answer fields |\n| ----------------------------- | -------------------------------------- | ----------------------------------------------------------------------- |\n| value | Extract one typed field from the state | value (string or number; null when absent), confidence, unknown |\n| choice with unknown: true | Which option, or none of them? | as choice, plus unknown: true when none fits |\n\ndtype is string (the default), int or float; pattern is a regular\nexpression the extracted text must match. A value the state does not hold comes\nback as null with unknown: true — an answer, not a missing one.\nreadDecisionValue returns undefined only for an absent id or another type.\nWith unknown: true on a choice, the option count includes unknown, and no\noption may itself be named unknown.\n\nVocabulary note. TypeSafe calls the yes/no primitive a noul and answers\nit in a field of the same name. The Vercel AI SDK and Pydantic AI both renamed\nthat to boolean/probability when exposing it, and NeuroLink follows them —\nthe vendor's spelling is translated inside TypeSafeProvider, so another\ndecision provider slots in without changing any call site.\n\nConfidence is not probability\n\nprobabilities says what the model thinks. confidence says _whether you\nshould act on it_. On TypeSafe's Jev it is calibrated — derived from the\ndistribution, not self-reported — which is what makes it usable as a gate. Each\nprovider reports its own: Perplexity's is, in Perplexity's words, the model's own\ncertainty estimate and not the top probability, Perplexity does not call it\ncalibrated, and NeuroLink has not measured whether it is, so tune a threshold on\nyour own data (see the\nPerplexity guide).\n\nCalibration is a property of groups of answers, not a promise about any\none. Where a confidence is calibrated, answers scored 0.8 are right about 80%\nof the time across many answers. It does not mean a specific 0.8 answer is right.\n\nA boolean carries no confidence of its own. Use decisionBooleanConfidence(p)\n— distance from a coin flip, so 0.5 → 0 and 0/1 → 1. Note also that a boolean\nand an equivalent two-option choice are not guaranteed to agree, and\ncomplementary booleans do not reliably sum to 1, so a threshold tuned on one\nquestion shape does not transfer to another.\n\nEnabling it\n\nGet a key at console.typesafe.ai/keys.\n\nLaya, at a Laya server or a LiteLLM proxy route\n\nLaya has","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"","lvl3":""}},
2822
+ {"objectID":"f91e7ef7473aa411ff8c0d3b04381325781477c1876408ffd5469dbaf2d9584c","title":"The decide inference type","url":"/docs/features/decide-inference-type#the-decide-inference-type","content":"Deep-dive: generate, stream, decide: a third inference type for NeuroLink —\nwhy this shipped as a provider rather than a subsystem, the five call sites, and the four\ntransport bugs found by adding the Vercel AI Gateway (two of which produced plausible output).\n\nNeuroLink recognises three inference types. Two of them produce text:\n\n| Type | Call | Produces |\n| ------------ | ------------------------ | ------------------------------ |\n| generate | neurolink.generate() | text |\n| stream | neurolink.stream() | text, incrementally |\n| decide | neurolink.decide() | typed judgements — no text |\n\nA decision model takes one state plus a map of named, typed questions and\nreturns one typed answer per question, all evaluated in a single parallel pass.\nThere is no text anywhere in the response, so nothing has to be parsed back out\nof prose. The decide providers are TypeSafe's Jev, Convai Innovations'\nopen-weights Laya, Juspay's open-weights XOR, which also reads an image\nand audio and extracts typed values, Subquadratic's hosted SubQ Snap, Perplexity's hosted Decisions\nAPI, which also reads images,\nCloudflare's Clef models on Workers AI,\nwhich also read images, OpenAI's\nDecisions API (a public beta), which also reads images, and ScaleDown's\nhosted Classify models, which read text only.\n\nThis is not neurolink.evaluate(), which scores an\nalready-generated response with RAGAS scorers. Different feature, different\nword.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"The decide inference type","lvl3":""}},
2823
+ {"objectID":"2c0f154b9ae29f0af1a097de9ccb435db1e8f5c35a4df8bb91526eadb7b5eb97","title":"The three primitives","url":"/docs/features/decide-inference-type#the-three-primitives","content":"| Type | Question | Answer fields |\n| --------- | ------------------------------ | ------------------------------------------------ |\n| boolean | Is this statement true? | probability (0–1) — no confidence |\n| choice | Which option from this set? | choice, probabilities, confidence |\n| score | Rate against an ordered rubric | score, legend, probabilities, confidence |\n\nAll three mix freely in one call.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"The three primitives","lvl3":""}},
2824
+ {"objectID":"1b670f33dd764eee22cfd866c67ec16095d278e9e1b08c80fa6ef1f326553381","title":"Two more, on XOR: value, and choice with unknown","url":"/docs/features/decide-inference-type#two-more-on-xor-value-and-choice-with-unknown","content":"A provider lists the question types it reads in its decisionLimits\n(questionTypes); without the list it reads the three above. Asking anything\nelse is refused before any request, as a non-retryable invalid_request, and a\nhost question of such a type is dropped on its own (see\ndecisionHooks). XOR reads two more:\n\n| Type | Question | Answer fields |\n| ----------------------------- | -------------------------------------- | ----------------------------------------------------------------------- |\n| value | Extract one typed field from the state | value (string or number; null when absent), confidence, unknown |\n| choice with unknown: true | Which option, or none of them? | as choice, plus unknown: true when none fits |\n\ndtype is string (the default), int or float; pattern is a regular\nexpression the extracted text must match. A value the state does not hold comes\nback as null with unknown: true — an answer, not a missing one.\nreadDecisionValue returns undefined only for an absent id or another type.\nWith unknown: true on a choice, the option count includes unknown, and no\noption may itself be named unknown.\n\nVocabulary note. TypeSafe calls the yes/no primitive a noul and answers\nit in a field of the same name. The Vercel AI SDK and Pydantic AI both renamed\nthat to boolean/probability when exposing it, and NeuroLink follows them —\nthe vendor's spelling is translated inside TypeSafeProvider, so another\ndecision provider slots in without changing any call site.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Two more, on XOR: value, and choice with unknown","lvl3":""}},
2824
2825
  {"objectID":"ff28050e2ddf3c21c910bc84ca32105968e1f7ffdbc3b19a983bb9adc6e9cc36","title":"Confidence is not probability","url":"/docs/features/decide-inference-type#confidence-is-not-probability","content":"probabilities says what the model thinks. confidence says _whether you\nshould act on it_. On TypeSafe's Jev it is calibrated — derived from the\ndistribution, not self-reported — which is what makes it usable as a gate. Each\nprovider reports its own: Perplexity's is, in Perplexity's words, the model's own\ncertainty estimate and not the top probability, Perplexity does not call it\ncalibrated, and NeuroLink has not measured whether it is, so tune a threshold on\nyour own data (see the\nPerplexity guide).\n\nCalibration is a property of groups of answers, not a promise about any\none. Where a confidence is calibrated, answers scored 0.8 are right about 80%\nof the time across many answers. It does not mean a specific 0.8 answer is right.\n\nA boolean carries no confidence of its own. Use decisionBooleanConfidence(p)\n— distance from a coin flip, so 0.5 → 0 and 0/1 → 1. Note also that a boolean\nand an equivalent two-option choice are not guaranteed to agree, and\ncomplementary booleans do not reliably sum to 1, so a threshold tuned on one\nquestion shape does not transfer to another.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Confidence is not probability","lvl3":""}},
2825
2826
  {"objectID":"91bfa1275464235c40e00d8a9ac0805e9e181450cccb6235fabd43178bef7c02","title":"Enabling it","url":"/docs/features/decide-inference-type#enabling-it","content":"Get a key at console.typesafe.ai/keys.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Enabling it","lvl3":""}},
2826
2827
  {"objectID":"71509585343bb3860ff000ad67405f6ed19fbc26f8eb973ac1fe58088af460b4","title":"Laya, at a Laya server or a LiteLLM proxy route","url":"/docs/features/decide-inference-type#laya-at-a-laya-server-or-a-litellm-proxy-route","content":"Laya has no built-in endpoint. The base URL and key can equally come from the\nconfig passed to the SDK, new NeuroLink({ credentials: { laya: { baseURL, apiKey } } }),\nor per call; config set there counts when NeuroLink picks the default decision\nprovider, exactly as the environment does.\n\nWhen a TypeSafe key (TYPESAFE_API_KEY or AI_GATEWAY_API_KEY) is configured\nalongside Laya, TypeSafe is the default; Laya runs where a caller names it\n(provider: \"laya\") or when neither TypeSafe key is set. Laya counts as\nconfigured only with both its key and its base URL. See the\nLaya provider guide.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Laya, at a Laya server or a LiteLLM proxy route","lvl3":""}},
2827
- {"objectID":"19994573e52cd09183db8050eadaea8ece3e291ef40c3009a5c44dfa959bf99c","title":"XOR, at a deployment or a LiteLLM proxy route","url":"/docs/features/decide-inference-type#xor-at-a-deployment-or-a-litellm-proxy-route","content":"XOR, Juspay's open-weights decision model, has no built-in endpoint either.\nNeuroLink calls <base URL>/v1/decisions, and a trailing /v1 on the base URL\nis accepted. The base URL must be https://; http:// is accepted only for\nlocalhost, 127.0.0.1 and [::1], because every request carries the key and\nthe state. A redirect from the endpoint is refused, not followed. The base URL\nand key can equally come from the config passed to the SDK,\nnew NeuroLink({ credentials: { xor: { baseURL, apiKey } } }), or per call; config set there counts when NeuroLink picks the default decision\nprovider, exactly as the environment does. On a LiteLLM proxy the key's team\nmust allow xor-latest (and xor-omni for media), otherwise the proxy answers 403 team_model_access_denied.\n\nBuilt-in features use the first configured decision provider in the order\nTypeSafe, Laya, XOR, SubQ, Perplexity, Cloudflare Clef, OpenAI Decisions, ScaleDown. XOR counts as configured only with both its key\nand its base URL, and runs where a caller names it (provider: \"xor\") or when\nneither TypeSafe nor Laya is configured. It reads images and video; see\nImages and video and the\nXOR provider guide.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"XOR, at a deployment or a LiteLLM proxy route","lvl3":""}},
2828
+ {"objectID":"19994573e52cd09183db8050eadaea8ece3e291ef40c3009a5c44dfa959bf99c","title":"XOR, at a deployment or a LiteLLM proxy route","url":"/docs/features/decide-inference-type#xor-at-a-deployment-or-a-litellm-proxy-route","content":"XOR, Juspay's open-weights decision model, has no built-in endpoint either.\nNeuroLink calls <base URL>/v1/decisions, and a trailing /v1 on the base URL\nis accepted. The base URL must be https://; http:// is accepted only for\nlocalhost, 127.0.0.1 and [::1], because every request carries the key and\nthe state. A redirect from the endpoint is refused, not followed. The base URL\nand key can equally come from the config passed to the SDK,\nnew NeuroLink({ credentials: { xor: { baseURL, apiKey } } }), or per call; config set there counts when NeuroLink picks the default decision\nprovider, exactly as the environment does. On a LiteLLM proxy the key's team\nmust allow xor-latest (and xor-omni for media), otherwise the proxy answers 403 team_model_access_denied.\n\nBuilt-in features use the first configured decision provider in the order\nTypeSafe, Laya, XOR, SubQ, Perplexity, Cloudflare Clef, OpenAI Decisions, ScaleDown. XOR counts as configured only with both its key\nand its base URL, and runs where a caller names it (provider: \"xor\") or when\nneither TypeSafe nor Laya is configured. It reads one image and up to 30\nseconds of audio, but no video, and answers value questions; see\nImages and video and the\nXOR provider guide.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"XOR, at a deployment or a LiteLLM proxy route","lvl3":""}},
2828
2829
  {"objectID":"99d5ee10748044f10d6dd1e54785276aac1cf4375eca8dc6a1c9db90fc0c2158","title":"SubQ, at its hosted endpoint","url":"/docs/features/decide-inference-type#subq-at-its-hosted-endpoint","content":"Subquadratic's subq-snap-preview (public preview) speaks the same System One\nwire as TypeSafe, at a public endpoint, so a key alone configures it and there\nis no base URL to set. The key and base URL can equally come from the config\npassed to the SDK, new NeuroLink({ credentials: { subq: { apiKey, baseURL } } }),\nor per call. The provider id is subq; subquadratic and subq-snap name it\ntoo. It comes after TypeSafe, Laya and XOR and ahead of Perplexity and Cloudflare\nClef: its key belongs to no other provider, so it is set on purpose, whereas\ntheirs are shared with text providers and must not displace it. NeuroLink sends it no images or video: SubQ's image format is not\none the decide catalog can describe yet, so an image attached to a call is\nrefused before any request. The state window NeuroLink reports for it is advisory\n(reported, never enforced, not a guarantee); see the\nSubQ provider guide.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"SubQ, at its hosted endpoint","lvl3":""}},
2829
2830
  {"objectID":"dc8b9e9a2c66b1d2a60305a09a44263ceca755d40f04b6771e0a7c05a5237a64","title":"Perplexity, at its hosted endpoint","url":"/docs/features/decide-inference-type#perplexity-at-its-hosted-endpoint","content":"Perplexity's pplx-decider-v1-27b is a hosted API at a public endpoint, so a key\nalone configures it and there is no base URL to set. NeuroLink calls\n<base URL>/v1/decisions; a trailing /v1 on an override is accepted. The key\ncan equally come from the config passed to the SDK,\nnew NeuroLink({ credentials: { perplexityDecider: { apiKey } } }), or per call;\nconfig set there counts when a decide() call picks the default decision\nprovider, exactly as the environment does. The classifier router's auto gate\ncounts them too, per-call credentials included, so a\ncredentials.perplexityDecider passed on one call can make auto pick jev.\nThe provider id is perplexity-decider: the perplexity provider is the\nSonar text provider, which serves generate() and stream() and not decide().\n\nPERPLEXITY_API_KEY is shared with that text provider. A host that set it\nonly to use Sonar has therefore also configured decide, and built-in features\nwill use Perplexity whenever none of TypeSafe, Laya, XOR or SubQ is configured.\nPerplexity comes after XOR and SubQ in descriptor order, so it never displaces\none that is. Context compaction's relevance stage and summary gate need no opt-in of their\nown, so earlier conversation text starts going to Perplexity the first time a\nconversation outgrows its budget; model routing, tool routing and RAG planning do\nnothing unless enabled. To keep the shared key from activating decisions, pass\nthe text provider's key as credentials.perplexity instead of through the\nenvironment, and keep it out of .env too, because the SDK and the CLI load that\nfile into the environment, or configure one of TypeSafe, Laya, XOR and SubQ, which then\nreceives those texts instead. The Perplexity provider guide lists\nwhat each consumer sends\nand the\nexact switches.\nIt reads images but no video; see Images and video.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Perplexity, at its hosted endpoint","lvl3":""}},
2830
2831
  {"objectID":"86d184ec9e501d4e94e973c833eaee71bb40744e683334ad36c497c3f625387f","title":"Cloudflare Clef, on Workers AI","url":"/docs/features/decide-inference-type#cloudflare-clef-on-workers-ai","content":"Cloudflare's clef and clef-flash are hosted on Workers AI at a public\nendpoint, so there is no base URL to set. The account id is part of the route, so\nClef counts as configured only with both the token and the account id. They can\nequally come from the config passed to the SDK,\nnew NeuroLink({ credentials: { cloudflareClef: { apiKey, accountId } } })\n(baseURL is optional), or per call; config set there counts when a decide()\ncall picks the default decision provider, exactly as the environment does. The\nclassifier router's auto gate counts them too, per-call credentials included,\nso a credentials.cloudflareClef passed on one call can make auto pick jev.\nThe provider id is cloudflare-clef: the cloudflare provider is the Workers AI text\nprovider, which serves generate() and stream() and not decide(), and\ncredentials.cloudflare does not configure decide.\n\nThe Workers AI endpoint's state window changed during October 2026.\nCloudflare documents a 64K window; until 2026-10-04 text past about 2,048 tokens\nwas ignored without an error, and on 2026-10-07 states of at least 190,153 tokens\n(clef-flash) and 52,154 (clef) were read in full (hosted service or model:\nunknown). NeuroLink refuses a state it estimates at more than 16,000 tokens with\nmax_tokens_exceeded, which every built-in consumer treats as \"carry on as\nbefore\"; see Limits and gotchas. Latency grows with the\nstate, about 0.2 s per 1,000 tokens on large ones, so NeuroLink's default timeout\nadds 250 ms per 1,000 estimated tokens.\n\nCLOUDFLARE_API_KEY and CLOUDFLARE_ACCOUNT_ID are shared with that text\nprovider. A host that set them only to use Workers AI text has therefore also\nconfigured decide, and built-in features will use Clef whenever none of\nTypeSafe, Laya, XOR, SubQ or Perplexity is configured. Clef comes after\nTypeSafe, Laya, XOR, SubQ and Perplexity, so it never displaces one that is. No switch turns it off while the two\nvariables are set.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Cloudflare Clef, on Workers AI","lvl3":""}},
2831
2832
  {"objectID":"08fd87a8baa41fd742824c8457907e72804c393825cb2f67997a356c98b4211b","title":"OpenAI Decisions, at its hosted endpoint","url":"/docs/features/decide-inference-type#openai-decisions-at-its-hosted-endpoint","content":"OpenAI's Decisions API is a public beta at a public endpoint, so a key alone\nconfigures it and there is no base URL to set. NeuroLink calls\n<base URL>/v1/decisions; a trailing /v1 on an override is accepted. The key\ncan equally come from the config passed to the SDK,\nnew NeuroLink({ credentials: { openaiDecisions: { apiKey } } }), or per call;\nconfig set there counts when a decide() call picks the default decision\nprovider, exactly as the environment does. The classifier router's auto gate\ncounts them too, per-call credentials included, so a credentials.openaiDecisions\npassed on one call can make auto pick jev. The provider id is openai-decisions: the openai provider is the chat\nprovider, which serves generate() and stream() and not decide(), and\ncredentials.openai does not configure decide. Its wire is OpenAI's own (named\nquestions in an array, an array of answers), so NeuroLink translates both ways;\nthe question types you write are unchanged. See the provider guide's\nwire mapping.\n\nOPENAI_API_KEY is shared with that chat provider. A host that set it only to\nuse OpenAI chat has therefore also configured decide, and built-in features will\nuse OpenAI Decisions whenever none of TypeSafe, Laya, XOR, SubQ, Perplexity or Cloudflare\nClef is configured. It comes after those six in the order, so it never displaces\none that is; only ScaleDown follows it.\nThe built-in consumers (model routing, tool routing, relevance compaction, the\nsummary gate, RAG planning) and the speech-to-text correction check then make paid\ndecision calls, though they still fail open. No switch turns it off while the key\nis set (short of a gateway OPENAI_BASE_URL, below). neurolink.decide() and tryDecide() build a fresh provider for every\ncall, so a key OpenAI rejects costs one failing request per decision site each\ntime, and the 401 latch never carries across calls. The provider guide explains\nwhen NeuroLink uses it\nand\nhow the two providers share the key.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"OpenAI Decisions, at its hosted endpoint","lvl3":""}},
2832
2833
  {"objectID":"691b7e7dec264cbbdcf43939939e82963f66753635c1b5ca81b26b2be05c844b","title":"ScaleDown, at its hosted endpoint","url":"/docs/features/decide-inference-type#scaledown-at-its-hosted-endpoint","content":"ScaleDown's Classify models speak the same System One wire as TypeSafe, at a\npublic endpoint, so a key alone configures it and there is no base URL to set.\nNeuroLink calls <base URL>/v1/scaledown, drops trailing slashes from an\noverride, and sends the key in an x-api-key header and never as a Bearer token.\nThe key can equally come from the config passed to the SDK,\nnew NeuroLink({ credentials: { scaledown: { apiKey } } }), or per call. The\nprovider id is scaledown; scaledown-classify names it too.\n\nScaleDown is the last decision provider, and its key is its own. Nothing\nelse reads SCALEDOWN_API_KEY, so setting it can only be meant for ScaleDown. It\nstill comes last in the order: a host with only that key gets ScaleDown for the\nbuilt-in features, and a host that also has any earlier provider configured\n(including an OPENAI_API_KEY, PERPLEXITY_API_KEY or Cloudflare pair set only for\na text provider) gets that earlier provider and reaches ScaleDown only by naming\nit. When ScaleDown is the default, the built-in consumers make paid decision\ncalls, and still fail open.\n\nIt reads text only, and the state always goes out as a string. A string state\nis sent unchanged, any other value as its JSON text, and a string that is empty or\nonly whitespace is refused before any request, as an invalid_request. {} and\n[] are sent as that text. Images and video are refused before any request. Two\noptional keys in the entry's decide block say this to the shared engine,\nauthHeader: \"x-api-key\" and stateEncoding: \"text\"; an entry that omits them\nbehaves as before. The provider guide has the\nrequest and the\nbilling note:\na choice question is scored once per option, so input tokens grow with options\ntimes state length.\n\nMuch about the endpoint is not measured, including its context size, latency,\nrate limits and the maximum number of options, questions and levels; the guide\nlists what is not measured.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"ScaleDown, at its hosted endpoint","lvl3":""}},
2833
2834
  {"objectID":"7990ffc08aca0afbeae4310cfdcd3758daf52da4d9edd1da90a82b683f4d7d41","title":"Two transports","url":"/docs/features/decide-inference-type#two-transports","content":"The same model is reachable two ways. Which one runs is decided once, in the\nconstructor:\n\n| | Direct | Vercel AI Gateway |\n| ------------------- | ------------------ | --------------------------------------------- |\n| Key | TYPESAFE_API_KEY | AI_GATEWAY_API_KEY |\n| Endpoint | api.typesafe.ai | ai-gateway.vercel.sh/v4/ai/evaluation-model |\n| Model named in | request body | ai-model-id header |\n| Question vocabulary | noul | boolean |\n| confidence | on each answer | on providerMetadata, not on the answer |\n| Billed by | TypeSafe | Vercel |\n\nHolding both keys keeps the direct transport, so the confidence figures a\nhost already sees do not shift underneath it when a second key appears. Both\ntransports report the vendor's calibrated confidence — the gateway simply puts\nit somewhere else, under providerMetadata.typesafe.confidence.<questionId>,\nleaving the answer objects without one. Set TYPESAFE_TRANSPORT=gateway (or\ncredentials.typesafe.transport) to override.\n\nāš ļø Read that field, not the distribution peak. It is tempting to take\nmax(probabilities) when an answer carries no confidence, and on a\nnear-certain answer the two agree. On an uncertain one they do not, and not by a\nlittle: a measured four-way choice returned probabilities\n{alpha 0.16, beta 0.28, gamma 0.33, delta 0.23} — a peak of 0.33 against a\nreported confidence of 0.10. That gap straddles the default\nminUpgradeConfidence of 0.3, so the derived number clears a bar the real one\nfails and a near-random pick gets acted on as a confident one. The peak stays as\nthe fallback when neither source reports a confidence, and it is genuinely a\ndifferent quantity: an even distribution over N options lands near 1/N, not 0.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Two transports","lvl3":""}},
2834
- {"objectID":"e5df7db87d4cb31c67fcce769cf5a3573764cb358ed9ee4b41f7ce86cb3954b7","title":"Using it","url":"/docs/features/decide-inference-type#using-it","content":"readDecisionBoolean / readDecisionChoice / readDecisionScore validate at\nruntime and return undefined for a missing id or a mismatched type, so no\ncall site needs a type assertion.\n\nticketText is attacker-controlled, so the verdict is a hint: keep assignTo\nreversible or put a deterministic check in front of it (see the Untrusted input\nbullet under What it is bad at).\n\nUse decide() instead of tryDecide() when you want the failure to surface;\nit throws a ProviderError whose cause carries a typed kind\n(authentication, rate_limit, max_tokens_exceeded, …).\n\nMore questions than a provider takes. A provider that caps the questions in\none request (Laya and Cloudflare Clef take 64, Perplexity 128, OpenAI Decisions 200) refuses a longer map from decide() with max_tokens_exceeded, except\nOpenAI Decisions, whose row is advisory: its cap is reported and used to split,\nbut decide() sends a longer map whole and leaves the answer to the server.\ntryDecide(), which every built-in consumer calls, splits the map at the cap\ninstead, keeps the questions in the order given, runs\nup to four batches at a time and joins the answers into one result: usage is\nsummed, the latency is the slowest batch's, and requestId lists the batches'\nids. A batch that fails costs only its own questions, which then have no answer\n(the read* helpers return undefined for them, and every built-in consumer\ncarries on without that decision); tryDecide() returns null only when every\nbatch failed. The state and any media are sent with each batch, so a split\nrequest costs the state's tokens once for each batch. A provider with no cap\n(TypeSafe, XOR, SubQ, ScaleDown) always gets one request.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Using it","lvl3":""}},
2835
- {"objectID":"7232a490e9e72e01dbe1a12de9470a38ed66ef91aa076b8c8cc745770eb03545","title":"From the CLI","url":"/docs/features/decide-inference-type#from-the-cli","content":"The same primitive is available as neurolink decide [state], which calls\ndecide() (not tryDecide()) and prints one line per answer:\n\nTo ask about an image or a video, add --image <path> (repeatable) or\n--video <path>. XOR reads both, Perplexity, Cloudflare Clef and OpenAI Decisions\nread images only, and TypeSafe, Laya, SubQ and ScaleDown read neither:\n\nSee the CLI command reference for the full flag\nlist, including --state-file, --questions-file, --image, --video and\n--format json.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"From the CLI","lvl3":""}},
2835
+ {"objectID":"e5df7db87d4cb31c67fcce769cf5a3573764cb358ed9ee4b41f7ce86cb3954b7","title":"Using it","url":"/docs/features/decide-inference-type#using-it","content":"readDecisionBoolean / readDecisionChoice / readDecisionScore validate at\nruntime and return undefined for a missing id or a mismatched type, so no\ncall site needs a type assertion.\n\nticketText is attacker-controlled, so the verdict is a hint: keep assignTo\nreversible or put a deterministic check in front of it (see the Untrusted input\nbullet under What it is bad at).\n\nUse decide() instead of tryDecide() when you want the failure to surface;\nit throws a ProviderError whose cause carries a typed kind\n(authentication, rate_limit, max_tokens_exceeded, …).\n\nMore questions than a provider takes. A provider that caps the questions in\none request (Laya and Cloudflare Clef take 64, Perplexity and XOR 128, OpenAI Decisions 200) refuses a longer map from decide() with max_tokens_exceeded, except\nOpenAI Decisions, whose row is advisory: its cap is reported and used to split,\nbut decide() sends a longer map whole and leaves the answer to the server.\ntryDecide(), which every built-in consumer calls, splits the map at the cap\ninstead, keeps the questions in the order given, runs\nup to four batches at a time and joins the answers into one result: usage is\nsummed, the latency is the slowest batch's, and requestId lists the batches'\nids. A batch that fails costs only its own questions, which then have no answer\n(the read* helpers return undefined for them, and every built-in consumer\ncarries on without that decision); tryDecide() returns null only when every\nbatch failed. The state and any media are sent with each batch, so a split\nrequest costs the state's tokens once for each batch. A provider with no cap\n(TypeSafe, SubQ, ScaleDown) always gets one request.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Using it","lvl3":""}},
2836
+ {"objectID":"7232a490e9e72e01dbe1a12de9470a38ed66ef91aa076b8c8cc745770eb03545","title":"From the CLI","url":"/docs/features/decide-inference-type#from-the-cli","content":"The same primitive is available as neurolink decide [state], which calls\ndecide() (not tryDecide()) and prints one line per answer:\n\nTo ask about an image, a video or an audio clip, add --image <path>\n(repeatable), --video <path> or --audio <path>. XOR reads one image and\naudio, Perplexity, Cloudflare Clef and OpenAI Decisions read images only,\nTypeSafe, Laya, SubQ and ScaleDown read neither, and no shipped provider reads\nvideo. With --audio, the state may be left out:\n\nSee the CLI command reference for the full flag\nlist, including --state-file, --questions-file, --image, --video,\n--audio and --format json.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"From the CLI","lvl3":""}},
2836
2837
  {"objectID":"3b801c727cb399feb7390c3ad67aba9ea5f77581096bb852b7aafb87b70998aa","title":"A choice answer is also a ranking","url":"/docs/features/decide-inference-type#a-choice-answer-is-also-a-ranking","content":"readDecisionChoice returns ranked — every option sorted by probability,\nhighest first. One choice question over N options therefore ranks all N in a\nsingle request. This is the basis for picking from a large catalogue.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"A choice answer is also a ranking","lvl3":""}},
2837
- {"objectID":"e67682840a5ce9650dba2a1d437b0dfbba456cae60fa3f607127e209bf1ec3e7","title":"Images and video","url":"/docs/features/decide-inference-type#images-and-video","content":"A decision provider whose descriptor declares media limits reads images, and\npossibly one video, alongside state. XOR does, and so do Perplexity, Cloudflare\nClef and OpenAI Decisions, which declare images only and no video; TypeSafe, Laya, SubQ\nand ScaleDown do not. Two optional fields on the request carry them, for decide() and\ntryDecide() alike:\nimages — up to 8 images, the limit XOR, Perplexity and OpenAI Decisions each\n declare (OpenAI's is NeuroLink's own allowance, not a limit OpenAI documents);\n Cloudflare Clef takes up to 4.\nvideo — one video, for a provider that declares video (XOR).\n\nEach image and the video takes the same three input forms:\na Buffer;\na local file path;\na data:image/…;base64, or data:video/…;base64, URL.\n\nNeuroLink identifies a Buffer or a file from its bytes, not its extension — PNG,\nJPEG, WebP and GIF images, and MP4, MOV and WebM video —\nand sends it as a data: URL. A data: URL you pass yourself must hold base64\nimage or video content. Clef normalizes image/jpg to image/jpeg; other data URLs are sent as given. Perplexity and Cloudflare Clef read\nPNG, JPEG and WebP only, and refuse a GIF before any request. OpenAI Decisions\nsends every image as an inline data URL and checks no format list, because\nOpenAI's list is not established.\n\nWhat is refused. Everything NeuroLink can check is refused before any\nrequest, as a non-retryable invalid_request:\nan http(s) URL — media is not fetched for you, so pass a Buffer, a file path\n or a data: URL;\na string that is not a file path or a data: URL, such as bare base64 or a\n URL with another scheme;\na missing file, a directory, or an empty Buffer or file;\nsomething that is not an image or a video;\nmore than 8 images, or more than 4 for Cloudflare Clef;\na request body over the provider's limit: 8 MB for XOR, 32 MiB for Perplexity\n and for OpenAI Decisions (NeuroLink's own number for the latter), 256,000 bytes\n for Cloudflare Clef.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Images and video","lvl3":""}},
2838
+ {"objectID":"e67682840a5ce9650dba2a1d437b0dfbba456cae60fa3f607127e209bf1ec3e7","title":"Images and video","url":"/docs/features/decide-inference-type#images-and-video","content":"A decision provider whose descriptor declares media limits reads images, and\npossibly one video or one audio clip, alongside state. XOR, Perplexity,\nCloudflare Clef and OpenAI Decisions read images; XOR also reads audio; TypeSafe,\nLaya, SubQ and ScaleDown read none. No shipped provider reads video: XOR's\ngateway drops a video without a word (a solid red clip was answered the same as\ngarbage bytes), so NeuroLink refuses one rather than return a blind answer.\nThree optional fields on the request carry media, for decide() and\ntryDecide() alike:\nimages — up to 8 for Perplexity and OpenAI Decisions (OpenAI's is\n NeuroLink's own allowance, not a limit OpenAI documents), 4 for Cloudflare\n Clef, 1 for XOR.\nvideo — one video, for a provider that declares video (none today).\naudio — one clip, for a provider that declares audio (XOR); see\n Audio below.\n\nEach takes the same three input forms:\na Buffer;\na local file path;\na data:image/…, data:video/… or data:audio/…;base64, URL.\n\nNeuroLink identifies a Buffer or a file from its bytes, not its extension — PNG,\nJPEG, WebP and GIF images, and MP4, MOV and WebM video —\nand sends it as a data: URL. A data: URL you pass yourself must hold base64\nimage or video content. Clef normalizes image/jpg to image/jpeg; other data URLs are sent as given. Perplexity and Cloudflare Clef read\nPNG, JPEG and WebP only, and refuse a GIF before any request. OpenAI Decisions\nsends every image as an inline data URL and checks no format list, because\nOpenAI's list is not established.\n\nWhat is refused.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Images and video","lvl3":""}},
2839
+ {"objectID":"011a9e1749ee443f7a2e40da4b793f6af01dbe491ce0951d27619c297867bdf5","title":"Audio","url":"/docs/features/decide-inference-type#audio","content":"XOR hears one clip per request, as WAV, FLAC, Ogg (Vorbis or Opus) or MP3,\nidentified from its bytes and sent as { data, format }. The state may be empty\n(state: \"\"; the CLI's --audio lets you leave it out), and an image may go\nwith it.\n\nThe model hears the first 30 seconds and drops the rest without a word, at\nabout 25 input tokens a second. So a WAV, FLAC or Ogg (Opus or Vorbis) clip that\nruns longer is refused before any request, its length read from the file; an\nMP3's length is not read, and only its first 30 seconds count. With no model\nnamed and XOR_MODEL unset, a request carrying media or a value question goes\nto xor-omni and is checked against its window (the catalog's\ndecide.capableModel). AAC/M4A is refused by name: it was\nmeasured decoding to nothing while the request was still answered. A WAV without\na complete fmt and data chunk is refused too.\n\nThe result carries mediaBytes, the encoded size of the media sent. The\ndecide spans carry decision.images.count, decision.audio.count and\ndecision.media.bytes, and\nnever any base64. A deployment started without OPENJEV_IMAGES=1 answers 200\nand silently ignores images, which NeuroLink cannot detect; see the\nXOR provider guide.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Audio","lvl3":""}},
2838
2840
  {"objectID":"a6077cedd1ca3e03b757da9a2ef8c185a56d2f1032dd5ed43fe28bd71454836c","title":"The one rule: batch, never fan out","url":"/docs/features/decide-inference-type#the-one-rule-batch-never-fan-out","content":"This inverts the instinct you have from LLMs.\n\nOn TypeSafe, question count barely affects latency (measured against its live\nAPI):\n\n| questions | round trip | input tokens |\n| --------- | ---------- | ------------ |\n| 1 | 393 ms | 310 |\n| 10 | 390 ms | 481 |\n| 100 | 423 ms | 2 281 |\n| 400 | 465 ms | 8 581 |\n\nThese are TypeSafe's figures. perplexity-decider differs: about 0.3 to 1.1 s\nfor one question, about 65 ms for each further question (a request takes at\nmost 128), and an input time that grows faster than linearly with size: 3.9 s\nat 65,000 tokens, 10 s at 146,000 and 22.9 s at 251,000. See\nits latency section.\ncloudflare-clef takes 0.3 to 1.0 s for a small request and 1.1 to 1.3 s for\n64 questions (measured 2026-10-03). A 64-question request with the same\nquestions and a shorter state took 1.5 s on clef-flash and 2.3 s on clef on 2026-10-04; see\nits latency section.\nopenai-decisions has not been timed: its latency is not measured. Nor has\nscaledown: its latency is not measured, and a choice question is scored\nonce per option, so a request with many options may need a larger timeoutMs\nthan the 5-second default; see\nits timeouts note.\n\nOn TypeSafe, 400 questions cost ~70 ms more than one. Concurrent requests, by\ncontrast, queue: ten parallel calls take ~1.4 s wall with nine landing together\nat the end, while the server's own upstream time stays flat at 64–169 ms.\n\nSo on TypeSafe, 400 things in one request take ~465 ms; the same 400 as separate\nrequests take roughly a minute. Add every question you might need to the call\nyou are already making — speculative questions are nearly free on TypeSafe (on\nPerplexity each costs about 65 ms, up to 128 in a request; Cloudflare Clef takes\nup to 64), and a second round trip is not.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"The one rule: batch, never fan out","lvl3":""}},
2839
2841
  {"objectID":"0c1ce0b91d624674d6b4cc3b1f67fad986bcdfd379812b89e121784a94408ec3","title":"Model routing","url":"/docs/features/decide-inference-type#model-routing","content":"The classifier router gains a jev\nstrategy, and its default becomes auto — resolving to jev when a decision\nprovider is configured and heuristic when not.\n\nOne request asks for the difficulty tier, whether the task needs\nvision/tools/reasoning, whether carrying it out is risky, and which pool\nmember to use — all at once, in ~400 ms on TypeSafe's Jev.\n\n| | heuristic | llm | jev |\n| ---------------------- | ------------- | ------------------------------- | --------------------------------------------------- |\n| Added latency | 0 ms | ~1–8 s | ~400 ms on TypeSafe |\n| Cost per decision | none | a full LLM call | ~$0.00002 on TypeSafe |\n| Confidence | keyword score | self-reported (defaults to 0.7) | as the provider reports it (calibrated on TypeSafe) |\n| Picks a model directly | no | yes | yes |\n\nThe jev column is TypeSafe's figures; the\nPerplexity guide gives its\nlatency and the confidence it reports. The\nCloudflare Clef guide gives its\nlatency; a prompt NeuroLink estimates over 16,000 tokens is refused there and\nrouting falls back to the heuristic, as it does on any failure.\n\nThresholds are asymmetric, because the two mistakes do not cost the same:\nminUpgradeConfidence defaults to 0.3 (spending more on a wrong guess costs\nmoney) and minDowngradeConfidence to 0.6 (spending less on a wrong guess\nproduces a wrong answer).\n\nThe key upgrades routing; it does not switch routing on. The classifier\nrouter is still opt-in (classifierRouter.enabled) and still needs a pool,\nbecause NeuroLink cannot invent the set of models you are willing to route\nbetween. What the key changes is which classifier runs inside a router you\nalready enabled.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Model routing","lvl3":""}},
2840
2842
  {"objectID":"40f783ceca290c039cce7f0199ab5758d7a0719b0a91c335c1c6e490a72b1412","title":"The model catalogue","url":"/docs/features/decide-inference-type#the-model-catalogue","content":"Enabling classifierRouter.catalog widens the routable pool beyond what you\ndeclared by hand: candidates are built from the 64-model registry (7\nproviders — see the model catalogue\nfor which), intersected with the credentials this host actually holds, and\nranked by a deterministic formula whenever no decision provider is available\nto choose among them.\n\n| | Without catalog | With catalog.enabled |\n| -------------- | ------------------------ | ------------------------------------- |\n| Candidate pool | only the declared pool | declared pool plus registry matches |\n| Fallback pick | first pool member | tierScore()-ranked, tier-aware |\n| Cap | none needed | maxModels, default 120 |\n\nSee the model catalogue for how\ncandidates are filtered, rendered, and ranked.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"The model catalogue","lvl3":""}},
@@ -2843,8 +2845,8 @@
2843
2845
  {"objectID":"451d199b91f25696bed61dedb5ebc0465143dbf3bc350ca8a3943e622f9aedf8","title":"Tool / MCP routing","url":"/docs/features/decide-inference-type#tool-mcp-routing","content":"The shipped tool router asks a generative model for {servers: string[]} on\na 15-second budget — a shape that cannot express uncertainty. A decision\nmodel instead asks one yes/no question per server, answered with a probability,\nand a server is excluded only on a confident \"no\" (minDropConfidence default\n0.6), because dropping a needed server breaks the turn while keeping an unneeded\none only costs a few tokens.\n\nA measured wording change moved unrelated servers from a mean probability of\n0.31 (dropping 12 of 39 unneeded servers) to a mean of 0.03 (dropping 37 of\n39, with zero wrong drops) — seen in\ntool / MCP routing by decision model,\nwhich also covers the exact question shape and its size guards.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Tool / MCP routing","lvl3":""}},
2844
2846
  {"objectID":"b35e1ab2390a7b132037c293ecaf436f9ff9498591ccdd47b58ee0a6a95e7616","title":"RAG retrieval planning","url":"/docs/features/decide-inference-type#rag-retrieval-planning","content":"RAGPipelineConfig.decide lets each RAG query get its own topK/hybrid/\ngraph/rerank plan instead of one fixed configuration for every query. An\nexplicit per-call QueryOptions field always wins over the plan, and a\ncapability the pipeline wasn't configured with can never be switched on by\nit.\n\nSee per-query RAG retrieval planning\nfor the breadth rubric and why its confidence bar is deliberately lower than\ntool routing's or compaction's.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"RAG retrieval planning","lvl3":""}},
2845
2847
  {"objectID":"e399806055971566454170aeec7214ec9b5948c8e3a35731a9c64b4ee2be7c21","title":"One funnel for all five sites","url":"/docs/features/decide-inference-type#one-funnel-for-all-five-sites","content":"Every one of those calls is stamped with a site — routing, toolRouting,\ncontextRelevance, summaryGate or ragPlan (the DecisionSite union) — and\ngoes through one private path on NeuroLink, which is also where a bound\ntryDecide() lands. For the four sites that run inside a NeuroLink request —\nrouting, toolRouting, contextRelevance and summaryGate — that path\nforwards the outer generate() / stream() call's per-call credentials,\nabortSignal and requestId (the top-level option, else\ncontext.requestId) to the decision, so a request that carries\ncredentials: { laya: {...} } routes and compacts against that account\nrather than the instance's; for routing the per-call credentials also count\ntowards the auto strategy's activation, so a host that holds no decision key\nitself still routes a request that carries one. ragPlan runs inside a\nhost-wired RAGPipeline, outside any NeuroLink request, so it receives only\nwhat the host puts on the options it hands tryDecide (see \"Host-wired\nconsumers\" below). The path also adds decision.site and\ndecision.host_question_count to the model.decision span; emits\ndecision:before / decision:after on the instance emitter; and runs the\ndecisionHooks described next. Without hooks or listeners, the decision\npayload — state and questions — is unchanged; per-call credentials are still\nforwarded, so the full request on the wire (account, base URL) can differ from\nthe instance's.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"One funnel for all five sites","lvl3":""}},
2846
- {"objectID":"a472e427cc594fbcc2ea01a87a8ebf99f49ebb52d71a7d5e6ff8baa6667707d1","title":"Riding along: decisionHooks","url":"/docs/features/decide-inference-type#riding-along-decisionhooks","content":"Latency on a decision model is flat in question count, so a host with its own\nquestions about the same request pays nothing to ask them in the round trip\nNeuroLink is already making. decisionHooks on the constructor does exactly\nthat — one request per site, never a second:\n\nWhat the funnel guarantees:\n\n| Concern | Behaviour |\n| ----------------------- |","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Riding along: decisionHooks","lvl3":""}},
2847
- {"objectID":"fff45cefe577cc88ce4956ce2fbd51677da0f1d953c7c3330d96f4e26737da7e","title":"Reading the limits before you send: decisionLimits()","url":"/docs/features/decide-inference-type#reading-the-limits-before-you-send-decisionlimits","content":"neurolink.decisionLimits({ provider?, model?, credentials? }) returns the\nfigures the pre-flight check compares against, resolved for one model —\nprovider as decide() resolves it, model as model ?? the model decide() sends\n(the one registered for the provider: LAYA_MODEL / TYPESAFE_MODEL as read\nwhen providers were registered, else the default — a later change to the\nvariable moves neither), the per-model entry flattened over the base:\n\nmaxQuestions is present only for a provider that caps by question count\n(Laya, Cloudflare Clef, Perplexity, OpenAI Decisions); a reading without the\nkey has no count cap, and a host should test\nfor the key rather than compare against Infinity. media is present only\nfor a provider that reads images or video (XOR, Perplexity, Cloudflare Clef,\nOpenAI Decisions); its absence is what the media refusal for TypeSafe, Laya, SubQ\nand ScaleDown is based on.\n\nenforcedLocally says who refuses: NeuroLink itself, before any network call\n(Laya and XOR, whose servers would silently truncate or fail inside the\nengine), or the provider's server\n(TypeSafe and OpenAI Decisions, whose descriptor rows are marked advisory and are\nreported but never enforced here — a local refusal would turn a server ceiling\ninto a client one that drifts from it). estimateDecisionStateTokens is the very\nfunction the pre-flight refusal uses, so a state it sizes as fitting is sent\nand one it sizes as over is refused; pass the reading so non-ASCII text is\ncharged at the model's measured rate. A named provider is read whether or\nnot it is configured — its limits are facts about the model — while the\ndefault requires a configured provider, as tryDecide() does.\n\nLaya reads far less than Jev. About 768 tokens of state on\ntyped-decisions and multilingual, and 320 on english, auto or an\nunrecognised model name, against Jev's ~33,000. Laya's server does not refuse a\nlonger state; it answers from the first 1,024 tokens (512 on english).","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Reading the limits before you send: decisionLimits()","lvl3":""}},
2848
+ {"objectID":"a472e427cc594fbcc2ea01a87a8ebf99f49ebb52d71a7d5e6ff8baa6667707d1","title":"Riding along: decisionHooks","url":"/docs/features/decide-inference-type#riding-along-decisionhooks","content":"Latency on a decision model is flat in question count, so a host with its own\nquestions about the same request pays nothing to ask them in the round trip\nNeuroLink is already making. decisionHooks on the constructor does exactly\nthat — one request per site, never a second:\n\nWhat the funnel guarantees:\n\n| Concern | Behaviour |\n| ----------------------- |","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Riding along: decisionHooks","lvl3":""}},
2849
+ {"objectID":"fff45cefe577cc88ce4956ce2fbd51677da0f1d953c7c3330d96f4e26737da7e","title":"Reading the limits before you send: decisionLimits()","url":"/docs/features/decide-inference-type#reading-the-limits-before-you-send-decisionlimits","content":"neurolink.decisionLimits({ provider?, model?, credentials? }) returns the\nfigures the pre-flight check compares against, resolved for one model —\nprovider as decide() resolves it, model as model ?? the model decide() sends\n(the one registered for the provider: LAYA_MODEL / TYPESAFE_MODEL as read\nwhen providers were registered, else the default — a later change to the\nvariable moves neither), the per-model entry flattened over the base:\n\nmaxQuestions is present only for a provider that caps by question count\n(Laya, XOR, Cloudflare Clef, Perplexity, OpenAI Decisions); a reading without the\nkey has no count cap, and a host should test\nfor the key rather than compare against Infinity. media is present only\nfor a provider that reads images or video (XOR, Perplexity, Cloudflare Clef,\nOpenAI Decisions); its absence is what the media refusal for TypeSafe, Laya, SubQ\nand ScaleDown is based on.\n\nenforcedLocally says who refuses: NeuroLink itself, before any network call\n(Laya and XOR, whose servers would silently truncate or fail inside the\nengine), or the provider's server\n(TypeSafe and OpenAI Decisions, whose descriptor rows are marked advisory and are\nreported but never enforced here — a local refusal would turn a server ceiling\ninto a client one that drifts from it). estimateDecisionStateTokens is the very\nfunction the pre-flight refusal uses, so a state it sizes as fitting is sent\nand one it sizes as over is refused; pass the reading so non-ASCII text is\ncharged at the model's measured rate. A named provider is read whether or\nnot it is configured — its limits are facts about the model — while the\ndefault requires a configured provider, as tryDecide() does.\n\nLaya reads far less than Jev. About 768 tokens of state on\ntyped-decisions and multilingual, and 320 on english, auto or an\nunrecognised model name, against Jev's ~33,000. Laya's server does not refuse a\nlonger state; it answers from the first 1,024 tokens (512 on english).","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Reading the limits before you send: decisionLimits()","lvl3":""}},
2848
2850
  {"objectID":"f6543a204326fbf7f27ac5c61c2f10b76f0541f3acb61eec93291921404e1a37","title":"What it is bad at","url":"/docs/features/decide-inference-type#what-it-is-bad-at","content":"\"Cannot hallucinate\" is a claim about output shape, not answer\ncorrectness: a decision model cannot return malformed JSON or an option you\ndid not offer, but it can still be wrong. TypeSafe reports ~68% accuracy on its\nown 711-case benchmark, against ~73% for a frontier model. It wins cost and\nlatency on every row and loses accuracy on every row — so it is right for\ndecisions that are gated and reversible, and wrong for final answers.\nIt reads literally. It answers the question you wrote, not the one you\n meant. Split an ambiguous question into two and combine them in code.\nAsk about the act, not the subject. \"This task touches money\" scores high\n on ordinary code that merely concerns money. The risk question in\n classifyJev is worded to exclude writing and testing such code, precisely\n because the naive phrasing escalated everything.\nIt is not a calculator. Counting, arithmetic and date comparison are\n unreliable — dates are read as text, not ordered quantities. A score is for\n thresholding and ranking, not for reading an exact magnitude off.\nIrrelevant state costs accuracy. Filter before sending.\nIt never explains itself. No rationale field exists, which rules it out\n where a decision must be auditable.\nOption order can matter. Test with reordered criteria if a call is close.\nDon't invert criteria. A boolean whose true description means \"no\"\n performs measurably worse.\nUntrusted input. Any text in state can steer a verdict: a user prompt,\n a ticket, an earlier message, a retrieved document, tool output, a\n transcript, a third-party server's description. A decision model can only\n return an option you offered, but text written to push it can still pick the\n option the author wants. The built-in consumers are advisory and fail-open,\n and none of them feeds human-in-the-loop approval or a guardrail. Their reach\n is bounded, not zero.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"What it is bad at","lvl3":""}},
2849
2851
  {"objectID":"f51228c123e6f86e1889f9990b2592debd24cc90c1584ad7d35d1f9a7aac8b82","title":"Adding another decision provider","url":"/docs/features/decide-inference-type#adding-another-decision-provider","content":"The decide inference type is provider-neutral by construction. Another\ndecision model needs:\nAn AIProviderName member and a <Name>Models enum\n (src/lib/constants/enums.ts, outside the generated regions).\nA provider class extending SystemOneDecisionProvider\n (src/lib/providers/systemOneDecision.ts), which owns the request loop,\n retries, the auth circuit breaker and answer parsing, and supplies the\n throwing getAISDKModel() / executeStream() stubs, the same shape the\n embedding-only providers (voyage.ts, jina.ts) use. The class supplies\n its endpoint, headers, body, error parsing and messages.\nA descriptor with inferenceKinds: [\"decide\"], no auto-select ranks, and\n healthCheck: \"env-only\". That one field is what keeps a text-less model out\n of every generation fallback chain; nothing else needs to know the provider\n by name. A provider that reads images or video also declares\n decisionLimits.media; without it, a request that carries media is refused\n before any network call.\nA registration block, a credentials slice, a manifest, and the usual Tier-3\n onboarding artifacts — pnpm run verify:provider-onboarding enumerates them.\n\nThe Tier-2 catalog JSON path cannot be used: its schema pins tier: 2, accepts\nonly an 8-flag text-generation capability vocabulary, and requires\ndefaultMaxOutputTokens and pricingPerMTok.output — none of which a model\nthat emits no text can honestly supply.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Adding another decision provider","lvl3":""}},
2850
2852
  {"objectID":"31d00aee561a454dc5082ad8002f9dbc0e33f15463cfee1636497043c8f9ee55","title":"Testing","url":"/docs/features/decide-inference-type#testing","content":"The suite drives dist/index.js only. Live tests skip without\nTYPESAFE_API_KEY; the degradation and discriminator tests run unconditionally,\nbecause \"behaves correctly with no key\" and \"a text-less provider is unreachable\nfrom generation\" are the contracts that matter most.","hierarchy":{"lvl0":"Features","lvl1":"The `decide` inference type","lvl2":"Testing","lvl3":""}},
@@ -3064,9 +3066,9 @@
3064
3066
  {"objectID":"bd2ee9e750dd9d3654963a651002843a26cb8ced5537b398d7ea0ad1fb58519b","title":"3. Handle Both Text and Image","url":"/docs/features/image-generation#3-handle-both-text-and-image","content":"3. Handle Both Text and Image","hierarchy":{"lvl0":"Features","lvl1":"Image Generation Streaming Guide","lvl2":"3. Handle Both Text and Image","lvl3":""}},
3065
3067
  {"objectID":"d1846db6cdaa3b7fb7c00887f7a369a00267a56d10839b7a3ce2ccedef4f226d","title":"4. Use Analytics for Monitoring","url":"/docs/features/image-generation#4-use-analytics-for-monitoring","content":"4. Use Analytics for Monitoring","hierarchy":{"lvl0":"Features","lvl1":"Image Generation Streaming Guide","lvl2":"4. Use Analytics for Monitoring","lvl3":""}},
3066
3068
  {"objectID":"aa931e13d4db435aa9de30f72f599057c2977804e4316c3cb61f84d8e43fd97e","title":"Conclusion","url":"/docs/features/image-generation#conclusion","content":"NeuroLink's image generation streaming provides a unified interface for both text and image generation. The fake streaming approach ensures consistency while maintaining the benefits of streaming APIs. By following the patterns and examples in this guide, you can effectively integrate image generation into your applications.\n\nFor more information:\nAPI Reference\nProvider Comparison\nProvider Status Monitoring","hierarchy":{"lvl0":"Features","lvl1":"Image Generation Streaming Guide","lvl2":"Conclusion","lvl3":""}},
3067
- {"objectID":"e3366024d1a12d0c73fbcb1592927d2dcc0d92c12eaea661cf5164bb7217c32f","title":"Feature Guides","url":"/docs/features","content":"Feature Guides\n\nComprehensive guides for all NeuroLink features organized by category. Each guide includes setup, usage patterns, configuration, and troubleshooting.\n\nLatest Features (Q1 2026)\n\n| Feature | Description |\n| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| decide Inference Type | A third inference type alongside generate/stream: typed, calibrated boolean/choice/score judgments in one parallel pass — no text. ~400ms, ~$0.00002 per decision on TypeSafe; Perplexity adds about 65 ms for each further question, up to 128 (see its guide); Cloudflare Clef takes up to 64 questions and about 2,048 tokens of state (see its guide). Providers: TypeSafe Jev; Laya, open-weights and self-hosted; XOR, Juspay's open-weights model that also reads images and video; SubQ, a hosted API; Perplexity, a hosted API that also reads images; Cloudflare Clef, hosted on Workers AI, which also reads images; OpenAI Decisions, OpenAI's Decisions API (public beta), which also reads images; and ScaleDown, a hosted API that reads text only. Fail-open: a no-op when no decision provider is configured (the credentials of Perplexity, Cloudflare Clef and OpenAI Decisions are shared with their text providers; ScaleDown's is its own). |\n| Model Routing with a Decision Model | One round trip answers difficulty, capabilities, risk, context scope and the model pick, with asymmetric confidence bars (0.3 up / 0.6 down). ","hierarchy":{"lvl0":"Features","lvl1":"Feature Guides","lvl2":"","lvl3":""}},
3069
+ {"objectID":"e3366024d1a12d0c73fbcb1592927d2dcc0d92c12eaea661cf5164bb7217c32f","title":"Feature Guides","url":"/docs/features","content":"Feature Guides\n\nComprehensive guides for all NeuroLink features organized by category. Each guide includes setup, usage patterns, configuration, and troubleshooting.\n\nLatest Features (Q1 2026)\n\n| Feature | Description |\n| ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| decide Inference Type | A third inference type alongside generate/stream: typed, calibrated boolean/choice/score judgments in one parallel pass — no text. ~400ms, ~$0.00002 per decision on TypeSafe; Perplexity adds about 65 ms for each further question, up to 128 (see its guide); Cloudflare Clef takes up to 64 questions and about 2,048 tokens of state (see its guide). Providers: TypeSafe Jev; Laya, open-weights and self-hosted; XOR, Juspay's open-weights model that also reads an image and audio and extracts typed values; SubQ, a hosted API; Perplexity, a hosted API that also reads images; Cloudflare Clef, hosted on Workers AI, which also reads images; OpenAI Decisions, OpenAI's Decisions API (public beta), which also reads images; and ScaleDown, a hosted API that reads text only. Fail-open: a no-op when no decision provider is configured (the credentials of Perplexity, Cloudflare Clef and OpenAI Decisions are shared with their text providers; ScaleDown's is its own). |\n| Model Routing with a Decision Model | One round trip answers difficulty, capabilities, risk, context scope and the model pick, with asymmetric confidence bars (0.3 up / 0.6 down). ","hierarchy":{"lvl0":"Features","lvl1":"Feature Guides","lvl2":"","lvl3":""}},
3068
3070
  {"objectID":"a477c76a577a17e7aeaf89736289a0a41734b4bcfe479a523cdd9e2051bd0ba7","title":"Feature Guides","url":"/docs/features#feature-guides","content":"Comprehensive guides for all NeuroLink features organized by category. Each guide includes setup, usage patterns, configuration, and troubleshooting.","hierarchy":{"lvl0":"Features","lvl1":"Feature Guides","lvl2":"Feature Guides","lvl3":""}},
3069
- {"objectID":"a43e81fa4a6c594f8b6b4cfc216db1356d75b3e4ebb54279dd2da7f69001d28f","title":"Latest Features (Q1 2026)","url":"/docs/features#latest-features-q1-2026","content":"| Feature | Description |\n| ---------------------------------------------------------------------------------------- |","hierarchy":{"lvl0":"Features","lvl1":"Feature Guides","lvl2":"Latest Features (Q1 2026)","lvl3":""}},
3071
+ {"objectID":"a43e81fa4a6c594f8b6b4cfc216db1356d75b3e4ebb54279dd2da7f69001d28f","title":"Latest Features (Q1 2026)","url":"/docs/features#latest-features-q1-2026","content":"| Feature | Description |\n| ---------------------------------------------------------------------------------------- |","hierarchy":{"lvl0":"Features","lvl1":"Feature Guides","lvl2":"Latest Features (Q1 2026)","lvl3":""}},
3070
3072
  {"objectID":"84c604ce940f85fbe8aa8d94675e4c1dae80b22ede558d58102426598f6a7d06","title":"Core Features (shipped 2025)","url":"/docs/features#core-features-shipped-2025","content":"| Feature | Description |\n| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |\n| Image Generation | Generate images from text prompts using Gemini models via Vertex AI or Google AI Studio. |\n| Enterprise HITL | HITL design guide. Event-based tool approval works today; the rest is planned. |\n| Interactive CLI | AI development environment with loop mode, session variables, and conversation memory. |\n| MCP Tools Showcase | Complete guide to 6 built-in tools and connecting external MCP servers across 6 categories. |\n| Human-in-the-Loop (HITL) | Pause AI tool execution for user approval before risky operations like file deletion or API calls. |\n| Guardrails Middleware | Content filtering, PII detection, and safety checks for AI outputs with zero configuration. |\n| Redis Conversation Export | Export complete session history as JSON for analytics, debugging, and compliance auditing. |\n| Context Compaction | Automatic conversation compression for long-running sessions to stay within token limits. |\n| LiteLLM Integration | Access 100+ AI models across a broad range of AI providers through unified LiteLLM routing interface. |\n| SageMaker Integration | Deploy and use custom-trained models on AWS SageMaker infrastructure with full control. |","hierarchy":{"lvl0":"Features","lvl1":"Feature Guides","lvl2":"Core Features (shipped 2025)","lvl3":""}},
3071
3073
  {"objectID":"f64aba3e564a8f56064839c33b533971468da990a9cc95d86ceb1e9d13223478","title":"Earlier Core Features (shipped Q3 2025)","url":"/docs/features#earlier-core-features-shipped-q3-2025","content":"| Feature | Description |\n| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |\n| Multimodal Chat Experiences | Stream text and images together with automatic provider fallbacks and format conversion. |\n| CSV File Support | Process CSV files for data analysis with automatic format conversion. Works across supported providers. |\n| PDF File Support | Process PDF documents for visual analysis and content extraction. Native provider support. |\n| Office Documents | Process DOCX, PPTX, XLSX files for document analysis. Native Bedrock, Vertex, Anthropic support. |\n| Auto Evaluation Engine | Automated quality scoring and metrics export for AI response validation using LLM-as-judge. |\n| CLI Loop Sessions | Persistent interactive mode with conversation memory and session state for prompt engineering. |\n| Regional Streaming Controls | Region-specific model deployment and routing for compliance and latency optimization. |\n| Provider Orchestration Brain | Adaptive provider and model selection with intelligent fallbacks based on task classification. |","hierarchy":{"lvl0":"Features","lvl1":"Feature Guides","lvl2":"Earlier Core Features (shipped Q3 2025)","lvl3":""}},
3072
3074
  {"objectID":"fbd682fc148477639d38ab175c38363d003a1464834e84b2a48d88087d001b91","title":"Platform Capabilities at a Glance","url":"/docs/features#platform-capabilities-at-a-glance","content":"| Category | Features | Documentation |\n| ------------------------ | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |\n| Provider unification | Provider integrations with automatic failover, cost-aware routing, providerFallback policy, modelChain config | Provider Setup |\n| Multimodal pipeline | Stream images + CSV data + PDF documents + Office files across providers with auto-detection for mixed file types. | Multimodal Guide, CSV Support, PDF Support, Office Docs |\n| Voice pipeline | TTS (6 providers) + STT (4 providers) + realtime APIs (OpenAI Realtime, Gemini Live) | TTS Guide, STT Guide, Realtime Services |\n| Quality & governance | Auto-evaluation engine (14 scorers), guardrails middleware, HITL workflows, audit logging | Auto Evaluation, Guardrails, HITL |\n| Memory & context | Per-user condensed memory (S3/Redis/SQLite), Redis session export, 5-stage context compaction | Conversation Memory, Memory, Redis Export |\n| CLI tooling | Loop sessions, setup wizard, config validation, Redis auto-detect, JSON output, TTS/STT flags | CLI Loop, CLI Commands |\n| Enterprise ops | Claude proxy, OTLP observability, OpenObserve dashboard, regional routing,","hierarchy":{"lvl0":"Features","lvl1":"Feature Guides","lvl2":"Platform Capabilities at a Glance","lvl3":""}},
@@ -3490,29 +3492,34 @@
3490
3492
  {"objectID":"d6ae12d83a1d4ac329230b324df41139e12e69cc6b9716c9a488474f68a92e56","title":"Example 2: Document Comparison","url":"/docs/features/multimodal#example-2-document-comparison","content":"Compare two versions of a contract:","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Example 2: Document Comparison","lvl3":""}},
3491
3493
  {"objectID":"4efaf71953966b0390225a95bede8ba98ba13fc231e459aa3caff383bfe0d214","title":"Example 3: Data Visualization Analysis","url":"/docs/features/multimodal#example-3-data-visualization-analysis","content":"Analyze charts and underlying data together:","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Example 3: Data Visualization Analysis","lvl3":""}},
3492
3494
  {"objectID":"dc35015c48cf20bf4abe2a50399f0f6c0a87119e86f614f784bd9233318b779f","title":"Summary","url":"/docs/features/multimodal#summary","content":"NeuroLink's multimodal capabilities provide:\n\nāœ… Universal input support - Images, PDFs, CSV files\nāœ… Provider flexibility - Extensive provider compatibility matrix\nāœ… Automatic format detection - Smart file type recognition\nāœ… Accessibility features - Alt text support for images\nāœ… Production-ready - Battle-tested at enterprise scale\nāœ… Developer-friendly - Works seamlessly across CLI and SDK\n\nNext Steps:\nReview the provider support matrix to select the right provider\nTry the quick start examples with your use case\nExplore advanced recipes for complex scenarios\nCheck troubleshooting if you encounter issues","hierarchy":{"lvl0":"Features","lvl1":"Multimodal Capabilities Guide","lvl2":"Summary","lvl3":""}},
3493
- {"objectID":"c56b41cb0c1e8e9dfc542fc3c18cf1024985a68cd588c7616a5facff77a7613a","title":"Observability Guide","url":"/docs/features/observability","content":"Observability Guide\n\nNeuroLink exports every AI operation to Langfuse. Setup is environment variables only.\n\nArchitecture\n\nOne instrumentation feeds every backend:\n\nNeuroLink's own provider uses this pipeline, with LangfuseExporter registered when Langfuse is enabled. The Langfuse exporter queues spans without blocking the caller and posts them in batches to Langfuse's OTLP endpoint (POST /api/public/otel/v1/traces, OTLP/HTTP JSON, Basic auth with your keys). Spans that are not observations (health checks, auth, middleware) are skipped, and their children are re-parented onto the nearest exported ancestor so the tree stays connected.\n\nWarning: full input and output are captured by default. Prompts, system prompts, tool arguments, tool results and model output are sent to Langfuse as they are. If your data is sensitive, set mask before enabling tracing. See Masking sensitive data.\n\nWarning: tracing turns on automatically. If LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY are present in the environment, NeuroLink starts exporting with no code change. Upgrading with those variables already set starts sending data. Set observability: { langfuse: false } to opt out.\n\nSetup\n\nEnvironment variables\n\n| Variable | Purpose |\n| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| LANGFUSE_PUBLIC_KEY | Langfuse public key (required to auto-enable) |\n| LANGFUSE_SECRET_KEY | Langfuse secret key (required to auto-enable) |\n| LANGFUSE_BASE_URL | Langfuse host; defaults to Langfuse Cloud |\n| LANGFUSE_TRACING_ENVIRONMENT | Environment label on every trace |\n| LANGFUSE_RELEASE | Release label on every trace |\n| OTEL_EXPORTER_OTLP_ENDPOINT | Also export traces and metrics to an OTLP collector |\n| NEUROLINK_SPAN_ATTRIBUTE_MAX_CHARS | Largest serialized span input, output or tool payload in characters (default 40000, minimum 10000). Anything over it is trimmed to valid JSON, keeping the user's question, then this turn's newest messages, then history |\n\nDisable or override\n\nEvery field is optional and falls back to its LANGFUSE_* variable. The type is NeurolinkLangfuseConfig.\n\n| Field | Meaning |\n| --------------- | ------------------------------------------------------------ |\n| publicKey | Langfuse public key |\n| secretKey | Langfuse secret key |\n| baseUrl | Langfuse host |\n| environment | Environment label |\n| release | Release label |\n| mask | Function applied to input, output and metadata before export |\n| exportMode | \"batched\" (default) or \"immediate\" |\n| flushAt | Spans buffered before a batch is sent (default 15) |\n| flushInterval | Seconds between batch flushes (default 5) |\n\nPer-call context\n\nPass context on generate, stream and decide. The values are attached to every span the call creates, including tool calls and fallback attempts, so one call is one trace.\n\n| Field | Recorded as |\n| ----------------- | -------------------------------------------------------","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"","lvl3":""}},
3495
+ {"objectID":"c56b41cb0c1e8e9dfc542fc3c18cf1024985a68cd588c7616a5facff77a7613a","title":"Observability Guide","url":"/docs/features/observability","content":"Observability Guide\n\nNeuroLink exports every AI operation to Langfuse. Setup is environment variables only.\n\nArchitecture\n\nOne instrumentation feeds every backend:\n\nNeuroLink's own provider uses this pipeline, with LangfuseExporter registered when Langfuse is enabled. The Langfuse exporter queues spans without blocking the caller and posts them in batches to Langfuse's OTLP endpoint (POST /api/public/otel/v1/traces, OTLP/HTTP JSON, Basic auth with your keys). Every observation is exported: model calls, attempts, steps and tools, and the spans of the RAG, workflow, evaluation, processor and autoresearch features, which arrive as a plain span observation. Spans that are not observations (health checks, auth, middleware and NeuroLink's HTTP, MCP, memory and file helper spans), wrapper spans NeuroLink marks internal, and spans another library creates on a shared provider that do not describe a model call are skipped, and their children are re-parented onto the nearest exported ancestor so the tree stays connected.\n\nWarning: full input and output are captured by default. Prompts, system prompts, tool arguments, tool results and model output are sent to Langfuse as they are. If your data is sensitive, set mask (or redactIO) before enabling tracing; it applies to what is sent to Langfuse only. See Masking sensitive data.\n\nWarning: tracing turns on automatically. If LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY are present in the environment, NeuroLink starts exporting with no code change. Upgrading with those variables already set starts sending data. Set observability: { langfuse: false } to opt out.\n\nSetup\n\nEnvironment variables\n\n| Variable | Purpose |\n| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| LANGFUSE_PUBLIC_KEY | Langfuse public key (required to auto-enable) |\n| LANGFUSE_SECRET_KEY | Langfuse secret key (required to auto-enable) |\n| LANGFUSE_BASE_URL | Langfuse host; defaults to Langfuse Cloud |\n| LANGFUSE_TRACING_ENVIRONMENT | Environment label on every Langfuse trace. The OTLP resource's deployment.environment is the same value and falls back to LANGFUSE_ENVIRONMENT, then PUBLIC_APP_ENVIRONMENT; Langfuse traces do not read those two |\n| LANGFUSE_RELEASE | Release label on every Langfuse trace. The OTLP resource's service.version is the same value and falls back to PUBLIC_APP_VERSION; Langfuse traces do not read it |\n| LANGFUSE_ENABLED | false, 0, off or no keeps ","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"","lvl3":""}},
3494
3496
  {"objectID":"db6cc880d3b187c65a71c44dcdf928120d86ca14ee034da45f5dd218dcac59b5","title":"Observability Guide","url":"/docs/features/observability#observability-guide","content":"NeuroLink exports every AI operation to Langfuse. Setup is environment variables only.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Observability Guide","lvl3":""}},
3495
- {"objectID":"f66991b3ca536e199e647a4e425060cf8af3be12b320be25230f3fe79a3cf66c","title":"Architecture","url":"/docs/features/observability#architecture","content":"One instrumentation feeds every backend:\n\nNeuroLink's own provider uses this pipeline, with LangfuseExporter registered when Langfuse is enabled. The Langfuse exporter queues spans without blocking the caller and posts them in batches to Langfuse's OTLP endpoint (POST /api/public/otel/v1/traces, OTLP/HTTP JSON, Basic auth with your keys). Spans that are not observations (health checks, auth, middleware) are skipped, and their children are re-parented onto the nearest exported ancestor so the tree stays connected.\n\nWarning: full input and output are captured by default. Prompts, system prompts, tool arguments, tool results and model output are sent to Langfuse as they are. If your data is sensitive, set mask before enabling tracing. See Masking sensitive data.\n\nWarning: tracing turns on automatically. If LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY are present in the environment, NeuroLink starts exporting with no code change. Upgrading with those variables already set starts sending data. Set observability: { langfuse: false } to opt out.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Architecture","lvl3":""}},
3497
+ {"objectID":"f66991b3ca536e199e647a4e425060cf8af3be12b320be25230f3fe79a3cf66c","title":"Architecture","url":"/docs/features/observability#architecture","content":"One instrumentation feeds every backend:\n\nNeuroLink's own provider uses this pipeline, with LangfuseExporter registered when Langfuse is enabled. The Langfuse exporter queues spans without blocking the caller and posts them in batches to Langfuse's OTLP endpoint (POST /api/public/otel/v1/traces, OTLP/HTTP JSON, Basic auth with your keys). Every observation is exported: model calls, attempts, steps and tools, and the spans of the RAG, workflow, evaluation, processor and autoresearch features, which arrive as a plain span observation. Spans that are not observations (health checks, auth, middleware and NeuroLink's HTTP, MCP, memory and file helper spans), wrapper spans NeuroLink marks internal, and spans another library creates on a shared provider that do not describe a model call are skipped, and their children are re-parented onto the nearest exported ancestor so the tree stays connected.\n\nWarning: full input and output are captured by default. Prompts, system prompts, tool arguments, tool results and model output are sent to Langfuse as they are. If your data is sensitive, set mask (or redactIO) before enabling tracing; it applies to what is sent to Langfuse only. See Masking sensitive data.\n\nWarning: tracing turns on automatically. If LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY are present in the environment, NeuroLink starts exporting with no code change. Upgrading with those variables already set starts sending data. Set observability: { langfuse: false } to opt out.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Architecture","lvl3":""}},
3496
3498
  {"objectID":"c3139194cb90b179cd9656356b659b38ab57c2b36ff72a0ae85ba1d83fb78aee","title":"Setup","url":"/docs/features/observability#setup","content":"Setup","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Setup","lvl3":""}},
3497
- {"objectID":"f5b277d3ae78e90affc7f614bbe8f4ea0e076874c57d77d204bd0764d0952622","title":"Environment variables","url":"/docs/features/observability#environment-variables","content":"| Variable | Purpose |\n| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| LANGFUSE_PUBLIC_KEY | Langfuse public key (required to auto-enable) |\n| LANGFUSE_SECRET_KEY | Langfuse secret key (required to auto-enable) |\n| LANGFUSE_BASE_URL | Langfuse host; defaults to Langfuse Cloud |\n| LANGFUSE_TRACING_ENVIRONMENT | Environment label on every trace |\n| LANGFUSE_RELEASE | Release label on every trace |\n| OTEL_EXPORTER_OTLP_ENDPOINT | Also export traces and metrics to an OTLP collector","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Environment variables","lvl3":""}},
3498
- {"objectID":"304f3b3f1d3d11c91945e49e4ae3f60d2b9a826b87a3948f18fd137a16b38c45","title":"Disable or override","url":"/docs/features/observability#disable-or-override","content":"Every field is optional and falls back to its LANGFUSE_* variable. The type is NeurolinkLangfuseConfig.\n\n| Field | Meaning |\n| --------------- | ------------------------------------------------------------ |\n| publicKey | Langfuse public key |\n| secretKey | Langfuse secret key |\n| baseUrl | Langfuse host |\n| environment | Environment label |\n| release | Release label |\n| mask | Function applied to input, output and metadata before export |\n| exportMode | \"batched\" (default) or \"immediate\" |\n| flushAt | Spans buffered before a batch is sent (default 15) |\n| flushInterval | Seconds between batch flushes (default 5) |","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Disable or override","lvl3":""}},
3499
+ {"objectID":"f5b277d3ae78e90affc7f614bbe8f4ea0e076874c57d77d204bd0764d0952622","title":"Environment variables","url":"/docs/features/observability#environment-variables","content":"| Variable | Purpose |\n| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| LANGFUSE_PUBLIC_KEY | Langfuse public key (required to auto-enable) |\n| LANGFUSE_SECRET_KEY | Langfuse secret key (required to auto-enable) |\n| LANGFUSE_BASE_URL | Langfuse host; defaults to Langfuse Cloud","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Environment variables","lvl3":""}},
3500
+ {"objectID":"304f3b3f1d3d11c91945e49e4ae3f60d2b9a826b87a3948f18fd137a16b38c45","title":"Disable or override","url":"/docs/features/observability#disable-or-override","content":"Every field is optional. publicKey, secretKey, baseUrl, environment and release fall back to the variables above; the others have the defaults below. The type is NeurolinkLangfuseConfig.\n\n| Field | Meaning |\n| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| enabled | false turns Langfuse off although keys are set (from untyped config, the strings \"false\", \"0\", \"off\" and \"no\" do too); any other value, or leaving it out, leaves it on.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Disable or override","lvl3":""}},
3499
3501
  {"objectID":"d5c2e109ca689299bbc1e1068341dfd6ba1294b1ee3c11cebe6489977865892e","title":"Per-call context","url":"/docs/features/observability#per-call-context","content":"Pass context on generate, stream and decide. The values are attached to every span the call creates, including tool calls and fallback attempts, so one call is one trace.\n\n| Field | Recorded as |\n| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| userId | Trace user |\n| sessionId | Trace session |\n| tags | Trace tags |\n| traceName | Trace name |\n| conversationId | Metadata key conversationId |\n| requestId | Metadata key requestId |\n| metadata | Metadata; values up to 200 characters are on every observation, longer values and arrays are recorded in full on the trace only |\n| observationName | Name of the root observation of this call; default generate or stream; not propagated to child observations or used as the trace name |","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Per-call context","lvl3":""}},
3500
3502
  {"objectID":"d262ba0787c1b57c76e303845fc9c6f447a3a2f747a880270f15c61fcc1863bb","title":"setLangfuseContext","url":"/docs/features/observability#setlangfusecontext","content":"Wrap your own code to attach the same attributes to everything inside it, including spans you create yourself.\n\nsetLangfuseContext(params: NeurolinkTraceContext, fn) returns whatever fn returns. It is a thin wrapper over Langfuse's propagateAttributes.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"setLangfuseContext","lvl3":""}},
3501
- {"objectID":"542eff7388511fe18f61b3de966dff32a0a0ccfa5c95df170298790e0233739d","title":"Naming","url":"/docs/features/observability#naming","content":"No span name, attribute key or metadata key contains the product name. Names never carry per-execution values: the model and provider are fields, the step number is metadata.\n\n| Observation | Name | Langfuse type | Parent |\n| -------------------------- | ------------------------------------- | ------------- | ---------------------- |\n| generate / stream call | generate / stream | agent | none (trace root) |\n| Fallback attempt | generate.attempt / stream.attempt | span | root |\n| Agent loop step | step (step_index metadata) | span | the attempt span |\n| One model request | generate / stream | generation | its step |\n| Tool execution | the tool's name, e.g. get_weather | tool | the step that ran it |\n| decide | decide | generation | caller or root |\n\nA model request is named after the API the caller used (NeuroLink.generate or provider.generate gives generate, NeuroLink.stream or provider.stream gives stream). Its model is the observation's model field (the model the provider reported, falling back to the requested one) and the provider id is the provider metadata key.\n\nThe trace name is context.traceName when given, otherwise the operation name (generate, stream). An outer setLangfuseContext takes precedence over per-call context for user, session and trace name.\n\nEmbeddings, RAG, workflow, evaluation, transcribe, TTS and media tracing are not yet recorded. The root also sets trace-level input and output; a generate or stream nested inside another turn does not.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Naming","lvl3":""}},
3503
+ {"objectID":"542eff7388511fe18f61b3de966dff32a0a0ccfa5c95df170298790e0233739d","title":"Naming","url":"/docs/features/observability#naming","content":"The observations below (generate, stream, decide, attempts, steps and tools) carry no product name in their span names, attribute keys or metadata keys. Names never carry per-execution values: the model and provider are fields, the step number is metadata. The product name remains in the plain spans listed under Other spans and in the proxy root's neurolink.user_id, neurolink.session_id and neurolink.conversation_id attributes.\n\n| Observation | Name | Langfuse type | Parent |\n| -------------------------- | ------------------------------------- | ------------- | ---------------------- |\n| generate / stream call | generate / stream | agent | none (trace root) |\n| Fallback attempt | generate.attempt / stream.attempt | span | root |\n| Agent loop step | step (step_index metadata) | span | the attempt span |\n| One model request | generate / stream | generation | its step |\n| Tool execution | the tool's name, e.g. get_weather | tool | the step that ran it |\n| decide | decide | generation | caller or root |\n\nA model request is named after the API the caller used (NeuroLink.generate or provider.generate gives generate, NeuroLink.stream or provider.stream gives stream). Its model is the observation's model field (the model the provider reported, falling back to the requested one) and the provider id is the provider metadata key.\n\nThe trace name is context.traceName when given, otherwise the operation name (generate, stream). An outer setLangfuseContext takes precedence over per-call context for user, session and trace name.\n\nEmbeddings, transcribe, TTS and media tracing are not yet recorded. The root also sets trace-level input and output; a generate or stream nested inside another turn does not.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Naming","lvl3":""}},
3504
+ {"objectID":"bec7707961c02d5123e4dceed9e13f5f5311e2f47783dd81fc290b467f50ff7a","title":"Other spans","url":"/docs/features/observability#other-spans","content":"Spans from the RAG, workflow, file, memory, context and evaluation code are exported as plain span records with their name, timing, status and their own attributes. They keep their neurolink.* names: neurolink.rag.search, neurolink.rag.rerank, neurolink.rag.chunk, neurolink.workflow.* (run, run.streaming, response.condition, judge.score, ensemble.execute, layers.execute), neurolink.file.process, neurolink.file.video.process, neurolink.context.compact, neurolink.memory.summarize and neurolink.evaluation.ragas.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Other spans","lvl3":""}},
3502
3505
  {"objectID":"ceb0e84f71b6a9deb6a57e236efa009236e933eb892ad569d59e36ba873292f3","title":"Steps and chat format","url":"/docs/features/observability#steps-and-chat-format","content":"Each iteration of the agent loop gets one step span with its one-based index in the step_index metadata. It parents that iteration's generation and the tool observations the model requested in it, so a multi-tool turn reads as step 1 (call, tools), step 2 (call, tools), and so on.\n\nGeneration input is a chat message array with the system message first. Generation output is an assistant message array: content, plus reasoning and tool_calls ({ id, type: \"function\", function: { name, arguments } }) when present. Langfuse renders both as a conversation.\n\nGeneration details:\nUsage includes input, output, total, cache read, cache creation and reasoning tokens, whatever naming the provider uses.\nCost is computed from NeuroLink's pricing table and recorded as costDetails.total.\nModel parameters are temperature, max tokens, top-p and thinking level.\nTime to first token is recorded as the generation's completion start time for streams.\nLevel is WARNING for a length or content-filter finish, an aborted call, an early break out of a stream, or an empty output, and ERROR when the call throws.\nStreams end their spans when the stream finishes, is abandoned or is aborted.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Steps and chat format","lvl3":""}},
3503
- {"objectID":"7dbc08e9d130f6a8e970396c854cb6afb0be27558cdbaa21122d1df517e4006a","title":"Masking sensitive data","url":"/docs/features/observability#masking-sensitive-data","content":"mask runs on span input and output before export. It receives { data } and returns the value to send.\n\nmask also runs on trace and observation metadata. If it throws, the value is replaced by <fully masked>. Provider credentials are already redacted from recorded values. Everything else is captured as is.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Masking sensitive data","lvl3":""}},
3504
- {"objectID":"bc90fcd87977756303a1a998fc869c12bbd8ef1f1b88e4f17746e4bd4463d15f","title":"Langfuse exporter behaviour","url":"/docs/features/observability#langfuse-exporter-behaviour","content":"Endpoint. POST {baseUrl}/api/public/otel/v1/traces as OTLP/HTTP JSON. Requests are cut at 500 spans or 3.5 MB.\nObservation types. Agent, generation, tool, step, attempt and the other observation types reach Langfuse with their native type, model, parameters, usage, cost and first-token time.\nIdentity. User, session, trace name, tags and trace metadata are set on every observation, not only the root.\nEnvironment and release. The config field, then LANGFUSE_TRACING_ENVIRONMENT / LANGFUSE_RELEASE; they fill spans that do not carry them already.\nBatching. Spans are sent when flushAt spans are queued, every flushInterval seconds, on flushOpenTelemetry() and on shutdown. exportMode: \"immediate\" sends each span as it ends.\nFailures. A request that fails with 429, 5xx or a network error is retried with exponential backoff. Other 4xx responses are not retried. Spans Langfuse reports as rejected (partialSuccess) are logged once as a warning and not re-sent. A 401 or 403 logs one warning per process (\"Langfuse credentials rejected\") and is not retried. Delivery never throws into your code.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Langfuse exporter behaviour","lvl3":""}},
3505
- {"objectID":"ba32010e859f5101a6247151e93a3db0fdaa79d0afe2d47c1492c74a0e04f5f5","title":"External OpenTelemetry provider","url":"/docs/features/observability#external-opentelemetry-provider","content":"If your application registers its own global TracerProvider, NeuroLink creates no provider. Other backends: add that vendor's span processor to your provider, or set OTEL_EXPORTER_OTLP_ENDPOINT. For Langfuse choose one of two recipes.\n\n(a) Exporter pipeline (recommended). Add createExporterSpanProcessor() to your provider and give NeuroLink the Langfuse keys, either through new NeuroLink({ observability: { langfuse } }) or the LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY environment variables. NeuroLink registers its LangfuseExporter on the shared exporter registry, which the processor reads, so mask, batching, chunking and retry from the section above all apply. Register the processor before the first NeuroLink call; spans that end before the exporter is registered are not exported.\n\n(b) LangfuseSpanProcessor. Add new LangfuseSpanProcessor() from @langfuse/otel with its defaults; no custom shouldExportSpan filter is needed. Every observation span NeuroLink emits carries a gen_ai.observation.type attribute, which the default filter exports, while infrastructure spans are not exported. This path sends OTLP and keeps native Langfuse observation types. The processor takes its keys, mask, flushAt, flushInterval, exportMode, environment and release from its own constructor options and LANGFUSE_* variables. It does not read observability.langfuse from new NeuroLink(...), so those settings must be passed to the processor, and NeuroLink's exporter masking, 500-span / 3.5 MB chunking and retry policy do not apply to it.\n\nRegister your provider before the first NeuroLink call. NeuroLink never flushes or shuts down a provider it did not create. @langfuse/otel is not a dependency of NeuroLink; install it for recipe (b).","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"External OpenTelemetry provider","lvl3":""}},
3506
- {"objectID":"be39053eddf107cc2916c84cc2e34d629a925ef52f26864ff9dd628f798d49e1","title":"Initialisation","url":"/docs/features/observability#initialisation","content":"Telemetry starts synchronously and lazily, once per process, the first time any NeuroLink span is created. That includes direct provider calls with no NeuroLink instance, for example AIProviderFactory.createProvider(...).generate(...), which are traced the same way.\n\nAn explicit observability config passed to new NeuroLink() wins over LANGFUSE_* variables when the instance is constructed before any span is created. If telemetry has already started, the config is ignored and a debug message is logged once.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Initialisation","lvl3":""}},
3507
- {"objectID":"2f01481e1bd920ff04b7d26da5bc86efababd09cbd1f763a1f119fb7d1443582","title":"Serverless and short-lived processes","url":"/docs/features/observability#serverless-and-short-lived-processes","content":"Spans are batched. Flush before the process freezes or exits:\n\nOr send each span as it ends:\n\nshutdownOpenTelemetry() is terminal for the process: telemetry cannot restart afterwards. While other NeuroLink instances are still alive it only flushes.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Serverless and short-lived processes","lvl3":""}},
3506
+ {"objectID":"7dbc08e9d130f6a8e970396c854cb6afb0be27558cdbaa21122d1df517e4006a","title":"Masking sensitive data","url":"/docs/features/observability#masking-sensitive-data","content":"mask runs on the input, output and metadata sent to Langfuse. It receives { data } and returns the value to send.\n\nmask and redactIO act on what NeuroLink sends to Langfuse, and on nothing else. They cover the trace and observation input and output (langfuse.observation.input, langfuse.observation.output, langfuse.trace.input, langfuse.trace.output); redactIO: true leaves those out of the request, and mask rewrites them instead. mask also runs on trace and observation metadata, whether or not the metadata is an object. If it throws, the value is replaced by <fully masked>.\n\nEverything else goes to Langfuse as it was recorded: the status message, exception.message, error.message, a tool's tool.error.message and a provider retry's retry.error, tool-call attributes, the proxy.body attribute of proxy body events, event attributes, and the prompt and completion attributes another library's GenAI instrumentation writes on spans that are exported with NeuroLink's. The policy does not reach any other sink either: an exporter you register with registerExporter, the spans NeuroLink sends to OTEL_EXPORTER_OTLP_ENDPOINT, and getSpans() receive the values as they were recorded. If one of them must not see content, take it out where it is recorded (NEUROLINK_RECORD_INPUTS=false) or in that sink's own processing.\n\nNeuroLink adds no credential masking of its own to error text. A failed call's status message, error.message, tool.error.message, retry.error and exception text are recorded as the provider returned them, in every sink, as the released build did, so an error that echoes a key reaches the sinks with the key. Input, output and tool arguments and results keep the credential masking they always had. To keep a credential out of error text, filter it in a custom exporter or keep it out of the provider's error.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Masking sensitive data","lvl3":""}},
3507
+ {"objectID":"bc90fcd87977756303a1a998fc869c12bbd8ef1f1b88e4f17746e4bd4463d15f","title":"Langfuse exporter behaviour","url":"/docs/features/observability#langfuse-exporter-behaviour","content":"Endpoint. POST {baseUrl}/api/public/otel/v1/traces as OTLP/HTTP JSON. Requests are cut at 500 spans or 3.5 MB.\nObservation types. Agent, generation, tool, step, attempt and the other observation types reach Langfuse with their native type, model, parameters, usage, cost and first-token time.\nIdentity. User, session, trace name, tags and trace metadata are set on every observation, not only the root.\nEnvironment and release. The config field, then LANGFUSE_TRACING_ENVIRONMENT / LANGFUSE_RELEASE; they fill spans that do not carry them already. The OTLP resource's deployment.environment and service.version also read LANGFUSE_ENVIRONMENT, PUBLIC_APP_ENVIRONMENT and PUBLIC_APP_VERSION; Langfuse traces do not.\nBatching. Spans are sent when flushAt spans are queued, every flushInterval seconds, on flushOpenTelemetry() and on shutdown. exportMode: \"immediate\" sends each span as it ends.\nFailures. A request that fails with 429, 5xx or a network error is retried with exponential backoff, or after the Retry-After a 429 or 503 names, up to 60 seconds and never past a flush's budget. Other 4xx responses are not retried. Spans Langfuse reports as rejected (partialSuccess) are logged once as a warning and not re-sent; a success whose body is not an object, or whose rejected count is not a whole number between zero and the spans sent, counts as all delivered. At most 4 export requests are in flight at once. Spans that end meanwhile wait, up to 10,000 of them; when Langfuse stops answering, the oldest waiting spans are dropped, counted in failedSpans and in the errors of healthCheck(), and warned about once a minute. Spans that end during shutdown are sent within the flush budget or counted as dropped. A 401 or 403 logs one warning per process (\"Langfuse credentials rejected\") and is not retried. Delivery never throws into your code.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Langfuse exporter behaviour","lvl3":""}},
3508
+ {"objectID":"07d102a2518a59d4afc8618d87ee066c3c7e36fefe22ee00772e172d8055a720","title":"Metrics","url":"/docs/features/observability#metrics","content":"NeuroLink also records OpenTelemetry metrics, on one meter named neurolink-ai. They are recorded only when OTEL_EXPORTER_OTLP_ENDPOINT is set, when NEUROLINK_TELEMETRY_ENABLED=true, or when a global MeterProvider is registered (your application's, or NeuroLink's own); a global TracerProvider alone, NeuroLink's included, does not turn them on. They are written to the global MeterProvider. NeuroLink registers one, exporting to $OTEL_EXPORTER_OTLP_ENDPOINT/v1/metrics every 15 seconds, only when it owns the tracer provider and the endpoint is set; beside a host provider they go to the MeterProvider your application registered, and nowhere when it registered none.\n\n| Metric | Kind | Labels | Recorded for |\n| ----------------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| ai_requests_total | counter | provider, model | each generate() that finishes in BaseProvider.finalizeNativeGenerate (the native generate path of providers such as Anthropic, SageMaker and the OpenAI Chat Completions family), and each proxied request whose usage the proxy captured |\n| ai_request_duration_ms | histogram,","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Metrics","lvl3":""}},
3509
+ {"objectID":"ba32010e859f5101a6247151e93a3db0fdaa79d0afe2d47c1492c74a0e04f5f5","title":"External OpenTelemetry provider","url":"/docs/features/observability#external-opentelemetry-provider","content":"If your application registers its own global TracerProvider before tracing starts, NeuroLink creates no provider. OTEL_EXPORTER_OTLP_ENDPOINT is then ignored: NeuroLink logs one warning and exports no traces or metrics to it, so add an OTLP exporter, or any other vendor's span processor, to your own providers. neurolink.getTelemetryStatus().openTelemetry then reports usingExternalProvider: true (its enabled stays whether tracing is initialized, as before), and neurolink telemetry status and neurolink observability status print the endpoint as ignored. NeuroLink's metrics are recorded on the MeterProvider your application registers. For Langfuse choose one of two recipes.\n\nThe host provider must register a context manager, which NodeTracerProvider.register() and NodeSDK do. Without one OpenTelemetry cannot carry context between calls, so setLangfuseContext user, session and metadata never reach NeuroLink's spans and child spans lose their parents; NeuroLink logs a warning when it finds none. Shutting the host provider down shuts the shared exporters down, as createExporterSpanProcessor() always did. If the host rebuilds its provider, pass createExporterSpanProcessor(undefined, { shutdownExporters: false }): that processor only flushes the exporters on shutdown, so a rebuilt provider that adds one again keeps exporting.\n\n(a) Exporter pipeline (recommended). Add createExporterSpanProcessor() to your provider and give NeuroLink the Langfuse keys, either through new NeuroLink({ observability: { langfuse } }) or the LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY environment variables. NeuroLink registers its LangfuseExporter on the shared exporter registry, which the processor reads, so mask, batching, chunking and retry from the section above all apply. Register the processor before the first NeuroLink call; spans that end before the exporter is registered are not exported.\n\n(b) LangfuseSpanProcessor.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"External OpenTelemetry provider","lvl3":""}},
3510
+ {"objectID":"be39053eddf107cc2916c84cc2e34d629a925ef52f26864ff9dd628f798d49e1","title":"Initialisation","url":"/docs/features/observability#initialisation","content":"Tracing starts synchronously, once per process, at the first of these:\nnew NeuroLink({ observability }). A constructor without an observability block starts nothing.\nThe first generate, stream, decide or tryDecide call on any NeuroLink instance.\nThe first span NeuroLink's own code creates, on an entry that loads the start-up module: the main package entry, @juspay/neurolink/server and @juspay/neurolink/livekit, and the ./rag, ./workflow, ./evaluation, ./processors and ./autoresearch subpath entries. That includes direct provider calls with no NeuroLink instance, for example AIProviderFactory.createProvider(...).generate(...) imported from the main entry.\n\nThe lightweight @juspay/neurolink/client entry does not load the OpenTelemetry Node SDK and does not start tracing: its spans go to a TracerProvider your application registered, and are dropped when there is none. A Node application that relied on this entry alone to start tracing (the previous version did) adds import \"@juspay/neurolink\" once at start-up.\n\nTracing configuration is process-wide and the first start wins. An observability config passed to new NeuroLink() wins over the LANGFUSE_* variables only when that construction starts tracing. A later instance's config is ignored: if it differs from the first, NeuroLink logs a warning that names the options that differ, once per set of differing options; an identical config logs one debug line. A start before Langfuse or OTLP is configured sets nothing up, so a later config or environment can still start tracing, unless langfuse: false was passed. langfuse: true, or an options object, without both keys counts as Langfuse not configured: NeuroLink warns once that the keys are missing and, unless OTEL_EXPORTER_OTLP_ENDPOINT is set, starts nothing, so a later config with keys still starts tracing. To run beside your own OpenTelemetry setup, register your TracerProvider before tracing starts; see External OpenTelemetry provider.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Initialisation","lvl3":""}},
3511
+ {"objectID":"2f01481e1bd920ff04b7d26da5bc86efababd09cbd1f763a1f119fb7d1443582","title":"Serverless and short-lived processes","url":"/docs/features/observability#serverless-and-short-lived-processes","content":"Spans are batched. Flush before the process freezes or exits:\n\nOr send each span as it ends:\n\nA script that ends on its own flushes pending spans and metrics once the event loop is empty (beforeExit). An explicit process.exit(), a fatal signal or an uncaught exception skips that flush, so call await flushOpenTelemetry() before them. flushOpenTelemetry(), shutdownOpenTelemetry() and that exit flush wait at most the flush budget (observability.langfuse.flushBudgetMs, default 3000 ms) for the OTLP collector, and for Langfuse within the same budget. What did not complete is logged as a warning, and flushOpenTelemetry() then rejects with Flush failed (its cause lists the reasons), as it always did; the exit flush and shutdown() catch that themselves, so a collector or Langfuse that cannot be reached does not hold a script past the budget: NeuroLink gives its OTLP exporters the budget as their request timeout. When OTEL_EXPORTER_OTLP_TIMEOUT (or its _TRACES_ / _METRICS_ form) is set, that timeout applies instead, and a request still in flight can hold the process open until the exporters' timeouts expire. The exit flush is new: before it a script ended at once and delivered nothing it still held, and with a collector that cannot be reached it now ends up to the flush budget (3 s by default) after its last call. Set NEUROLINK_TELEMETRY_EXIT_FLUSH=false (also 0, off or no) to skip it and end as soon as there is no work.\n\nshutdownOpenTelemetry() is terminal for the process: telemetry cannot restart afterwards. While other NeuroLink instances are still alive it only flushes.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Serverless and short-lived processes","lvl3":""}},
3508
3512
  {"objectID":"721428a68bdcef28a5437443fe5a57565c6aa3b324a48ccc39728e5d7ff25e2d","title":"Health","url":"/docs/features/observability#health","content":"isHealthy and hasProcessor are true only while a Langfuse exporter is registered, whether NeuroLink owns the provider or runs beside a host provider. With a host provider, the exporter still needs createExporterSpanProcessor() on that provider to receive spans.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Health","lvl3":""}},
3509
- {"objectID":"68b0ab87bc1ff79e31454e9550e76c174e5a1a4628f3c9de0e74a7dc6e07bdd2","title":"Proxy","url":"/docs/features/observability#proxy","content":"The proxy records the pool account label (not an email address), sets userId and sessionId from x-neurolink-user-id and x-neurolink-session-id, names traces proxy:<model>, and records request and response bodies, usage, cost and time to first token. See the Telemetry Guide and Claude Proxy Observability.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Proxy","lvl3":""}},
3510
- {"objectID":"818b18185bdd4b8b7003f170da3113452c78895619ec762279322c8b2b587801","title":"Voice (LiveKit)","url":"/docs/features/observability#voice-livekit","content":"Pipeline mode traces through stream() and carries the voice tag. Realtime mode records one trace per conversation turn, with transcripts as input and output and tool calls as tool records, and only when voice memory is enabled. The realtime generation record carries the model id and the provider of the realtime backend (vertex for Gemini Live, xai for Grok Voice). Full transcripts are sent to Langfuse by default; use mask for sensitive data.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Voice (LiveKit)","lvl3":""}},
3513
+ {"objectID":"68b0ab87bc1ff79e31454e9550e76c174e5a1a4628f3c9de0e74a7dc6e07bdd2","title":"Proxy","url":"/docs/features/observability#proxy","content":"The proxy records the pool account label (not an email address), sets userId and sessionId from x-neurolink-user-id and x-neurolink-session-id (the session falls back to Claude Code's x-claude-code-session-id), names traces proxy:<model>, and records usage, cost and time to first token. The proxy.request root also carries neurolink.user_id, neurolink.session_id and neurolink.conversation_id as plain attributes for backends that index identity by key.\n\nRequest and response bodies are recorded on traces only when NEUROLINK_PROXY_TRACE_BODY_LOGGING=true, the same opt-in as before the Langfuse migration. With it on, the bodies become the input and output of the proxy observations, so mask and redactIO apply to them on the Langfuse request, and NEUROLINK_RECORD_INPUTS=false still drops the request body. The raw bodies are also attached as span events only when the same flag is on (proxy.client.request_body, proxy.upstream.request_body, proxy.upstream.response_body and proxy.stream.events); without it those events carry only sizes and counts. mask and redactIO do not cover those events, neither the proxy.body attribute nor proxy.stream.events, so leave the flag off for traffic whose content must not leave the process.\n\nUpstream error text is recorded without the flag, as it was before the migration: error.message and the status message (<error type>: <text>) hold the upstream text, capped, as received. mask and redactIO leave it as it was recorded, as they did before the migration. A translated request, which the proxy serves through a NeuroLink model call, follows the body opt-in: without the flag that call's records keep model, usage, cost, finish reason and timings but no input or output.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Proxy","lvl3":""}},
3514
+ {"objectID":"818b18185bdd4b8b7003f170da3113452c78895619ec762279322c8b2b587801","title":"Voice (LiveKit)","url":"/docs/features/observability#voice-livekit","content":"Pipeline mode traces through stream() and carries the voice tag. Realtime mode records one trace per conversation turn, with transcripts as input and output and tool calls as tool records. Every realtime turn is traced when tracing is on, with or without voice memory: a call without memory is traced through setUpRealtimeCallTracing, with the room name as the session and no user id. Each turn is traced as soon as it closes, never behind the memory store's writes, so a slow or stuck store loses no trace. At shutdown the call closes its open turn and delivers its traces while its pending turn writes finish, both within memory.flushBudgetMs (default 800 ms, because the worker is reaped about a second after shutdown starts); flushOpenTelemetry() itself is bounded by observability.langfuse.flushBudgetMs (default 3000 ms). The realtime generation record carries the model id and the provider of the realtime backend (vertex for Gemini Live, xai for Grok Voice). Full transcripts are sent to Langfuse by default; use mask for sensitive data.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Voice (LiveKit)","lvl3":""}},
3511
3515
  {"objectID":"6ddae5fd4f96e8c2a1c9d924054685440eb46353183984ff0b8d3167e12071b8","title":"Troubleshooting","url":"/docs/features/observability#troubleshooting","content":"| Symptom | Check |\n| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Nothing reaches Langfuse | Both keys set and langfuse not false; getLangfuseHealthStatus(); call flushOpenTelemetry() on exit |\n| Spans missing with own OTEL setup | Add createExporterSpanProcessor() to your provider and pass the Langfuse keys to NeuroLink (or set LANGFUSE_*), or use new LangfuseSpanProcessor() with its own keys; register the processor before the first NeuroLink call |\n| Wrong host | Variable is LANGFUSE_BASE_URL, not LANGFUSE_BASEURL |\n| Traces stop after shutdown | shutdownOpenTelemetry() cannot be undone in the same process; use flushOpenTelemetry() |","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Troubleshooting","lvl3":""}},
3512
3516
  {"objectID":"c26071897c848ba9cf00651234ebd459171008740460f6075451e866420e5881","title":"Migrating from the previous version","url":"/docs/features/observability#migrating-from-the-previous-version","content":"This release is breaking.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Migrating from the previous version","lvl3":""}},
3513
- {"objectID":"310e635468902de6eca8789d9ce5905c4ecb08a665b802119f33a6ae78a6f429","title":"Configuration fields","url":"/docs/features/observability#configuration-fields","content":"| Removed | Replacement |\n| ------------------------------------------------------------- | ----------------------------------------------------------------------- |\n| langfuse.enabled | Set the keys to enable; langfuse: false disables |\n| langfuse.userId, langfuse.sessionId | Per-call context.userId, context.sessionId, or setLangfuseContext |\n| useExternalTracerProvider, autoDetectExternalProvider | Automatic: a registered global provider is detected |\n| skipLangfuseSpanProcessor | Register your own provider; NeuroLink then adds nothing |\n| mediaUploadEnabled, LANGFUSE_MEDIA_UPLOAD_ENABLED | Removed; the exporter does not upload media |\n| autoDetectOperationName, operationName, traceNameFormat | context.traceName |\n| customAttributes | context.metadata |\n| conversationId, requestId on the config | context.conversationId, context.requestId (stored as metadata) |\n| NEUROLINK_TELEMETRY_ENABLED | Removed; the LANGFUSE_* keys enable tracing |\n| LANGFUSE_BASEURL hint | LANGFUSE_BASE_URL |","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Configuration fields","lvl3":""}},
3514
- {"objectID":"58496b05fc501a4521336522ee26a5eb64aa0e9d6b609b350cc0813f6d453feb","title":"Exports","url":"/docs/features/observability#exports","content":"| Removed | Replacement |\n| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |\n| initializeOpenTelemetry | Automatic on new NeuroLink() |\n| getLangfuseSpanProcessor | createExporterSpanProcessor(registry) or new LangfuseSpanProcessor() |\n| getTracerProvider, getTracer | trace.getTracer() from @opentelemetry/api |\n| getSpanProcessors | Add createExporterSpanProcessor() to your provider |\n| createContextEnricher | setLangfuseContext or per-call context |\n| isUsingExternalTracerProvider | getLangfuseHealthStatus().usingExternalProvider |\n| isLangfuseInternalSpan, langfuseShouldExportSpan | Removed; only observation spans are exported |\n| getLangfuseContext | Removed |\n| runWithCurrentLangfuseContext | context.bind(context.active(), fn) from @opentelemetry/api |\n| buildObservabilityConfigFromEnv | Removed; env keys are read automatically |\n| createMockLangfuseClient","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Exports","lvl3":""}},
3515
- {"objectID":"ddf06146284ab7554c88bfef97f75c42c4186a270ac952f6cb27eb9e3640217a","title":"CLI","url":"/docs/features/observability#cli","content":"| Removed | Replacement |\n| ------------------------------------ | -------------------------------------- |\n| neurolink telemetry configure | Set LANGFUSE_* environment variables |\n| neurolink telemetry list-exporters | neurolink telemetry status |\n| neurolink observability exporters | neurolink observability status |\n\nExporterRegistry, NoOpExporter, AlwaysSampler, NeverSampler and the vendor exporter classes are unchanged. NeuroLink wires only LangfuseExporter. LangfuseExporter, ExporterSpanProcessor and createExporterSpanProcessor are new root exports.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"CLI","lvl3":""}},
3517
+ {"objectID":"310e635468902de6eca8789d9ce5905c4ecb08a665b802119f33a6ae78a6f429","title":"Configuration fields","url":"/docs/features/observability#configuration-fields","content":"| Removed or changed | Replacement |\n| ------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| langfuse.enabled | Kept, and optional: Langfuse is on when keys are present unless enabled: false (or langfuse: false); enabled: true is no longer needed |\n| langfuse.userId, langfuse.sessionId | Per-call context.userId, context.sessionId, or setLangfuseContext","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Configuration fields","lvl3":""}},
3518
+ {"objectID":"58496b05fc501a4521336522ee26a5eb64aa0e9d6b609b350cc0813f6d453feb","title":"Exports","url":"/docs/features/observability#exports","content":"| Removed or changed | Replacement |\n| ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| initializeOpenTelemetry | Automatic; see Initialisation |\n| getLangfuseSpanProcessor | createExporterSpanProcessor(registry) or new LangfuseSpanProcessor() |\n| getTracerProvider, getTracer | trace.getTracer() from @opentelemetry/api |\n| getSpanProcessors | Add createExporterSpanProcessor() to","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Exports","lvl3":""}},
3519
+ {"objectID":"ddf06146284ab7554c88bfef97f75c42c4186a270ac952f6cb27eb9e3640217a","title":"CLI","url":"/docs/features/observability#cli","content":"| Removed | Replacement |\n| ------------------------------------ | -------------------------------------- |\n| neurolink telemetry configure | Set LANGFUSE_* environment variables |\n| neurolink telemetry list-exporters | neurolink telemetry status |\n| neurolink observability exporters | neurolink observability status |","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"CLI","lvl3":""}},
3520
+ {"objectID":"a53a05129c249c4b5998d14baf8ce8023a2e0039999f1f79520e3f0f58a92a54","title":"Types","url":"/docs/features/observability#types","content":"| Removed or changed | Replacement |\n| -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| LangfuseContext, TraceNameFormat | NeurolinkTraceContext (per-call context, setLangfuseContext); the trace name is context.traceName |\n| LangfuseConfig | NeurolinkLangfuseConfig |\n| ExporterName, ToolSpanCall, TelemetryConfigureArgs, TelemetryListExportersArgs, ObservabilityExportersArgs | Removed with the CLI commands and tool wrapper that used them |\n| AnthropicLoopAdapterConfig, GeminiLoopAdapterCoreConfig, NativeGenerateLoopArgs: provider | provider is now a required string, recorded on each step's generation and used to price it; GeminiLoopAdapterCoreConfig.buildRequest returns GeminiStepRequest instead of unknown |","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Types","lvl3":""}},
3521
+ {"objectID":"67b137f6a345340802aec49454b7f962e4ed3c240a395bc4c693dad84e4adf5e","title":"Dependencies","url":"/docs/features/observability#dependencies","content":"@opentelemetry/sdk-trace-node moved from a peer dependency to a dependency, so NeuroLink installs it itself.\n@langfuse/otel moved from an optional dependency to a devDependency of NeuroLink, so install it yourself for new LangfuseSpanProcessor().\n@langfuse/core and @langfuse/tracing are new dependencies.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Dependencies","lvl3":""}},
3522
+ {"objectID":"1bcd1955ce1f5ffdea68be64f3bfffc22035ae2006c1a19ca4bc41f0a5a74ef0","title":"Unchanged","url":"/docs/features/observability#unchanged","content":"ExporterRegistry, NoOpExporter, AlwaysSampler, NeverSampler, the vendor exporter classes, ExtendedObservabilityConfig and the *ExporterConfig types are unchanged (LangfuseExporterConfig configures new LangfuseExporter(...)). NeuroLink wires only LangfuseExporter. LangfuseExporter, ExporterSpanProcessor and createExporterSpanProcessor are new root exports.","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"Unchanged","lvl3":""}},
3516
3523
  {"objectID":"9a2c99612604332f4b2bc32fb673fe19412249aba74ad41f2ed2cfd84a8d1185","title":"See Also","url":"/docs/features/observability#see-also","content":"Telemetry Guide\nClaude Proxy Observability\nAnalytics Reference","hierarchy":{"lvl0":"Features","lvl1":"Observability Guide","lvl2":"See Also","lvl3":""}},
3517
3524
  {"objectID":"1b22ff57a6c6e2525e50e02118d6029f5ee3779ef701e9fde1b7b97d1a782760","title":"Office Documents Support","url":"/docs/features/office-documents","content":"Office Documents Support\n\nNeuroLink reads Word, Excel, and PowerPoint files itself and puts their content into the prompt as text. Attach them through the same input.files array (or --file flag) that carries every other file type; there is no separate Office input field and no provider restriction.\n\nOverview\n\nWhen a request carries an Office file, NeuroLink:\nDetects the format from the file's bytes, MIME type, and extension.\nExtracts the content in-process: Word through mammoth (converted to Markdown), Excel through exceljs, and PowerPoint by reading the slide XML inside the .pptx package.\nAppends the extracted text to the prompt under a heading that names the file (## Document: \"report.docx\", ## Spreadsheet: \"sales.xlsx\", ## Presentation: \"deck.pptx\").\nSends an ordinary text prompt to whichever provider you chose.\n\nBecause the model receives text, Office files work with every provider and model, including ones with no document API (OpenAI, Ollama, Mistral, and so on). The model sees what the extractors can read: text, tables, and speaker notes. It does not see images, charts, or shapes that carry no text.\n\nSupported File Types\n\n| Format | Extension | MIME Type | What the model receives |\n| --------------------- | --------- | --------------------------------------------------------------------------- | ------------------------------------------------------- |\n| Word Document | .docx | application/vnd.openxmlformats-officedocument.wordprocessingml.document | Markdown (headings, lists, tables) and a metrics line |\n| PowerPoint | .pptx | application/vnd.openxmlformats-officedocument.presentationml.presentation | Text of every slide, table rows, and each slide's notes |\n| Excel Spreadsheet | .xlsx | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | Every sheet (or one you name) in the format you choose |\n\nThe same three routes also carry OpenDocument text, spreadsheet and presentation files (.odt, .ods, .odp) and RTF (.rtf), which are read as plain text.\n\nLegacy binary formats (.doc, .xls, .ppt) are recognised but not parsed: the readers only understand the Office Open XML packages above. The request still succeeds, and the model receives a short note saying the file was attached but its content could not be extracted. Re-save such files as .docx, .xlsx, or .pptx first.\n\nQuick Start\n\nSDK Usage\n\nCLI Usage\n\nThe CLI has no flags for officeOptions; choosing a sheet or a spreadsheet format is an SDK feature (see below).\n\nWhat the Model Receives\n\nDOCX structure and metrics\n\nA .docx is converted to Markdown before it reaches the model, so headings, ordered and unordered lists, and tables survive as structure rather than being flattened into prose. Each document is prefixed with a metrics line:\n\nPPTX slides, tables, and speaker notes\n\nA .pptx is read slide by slide, in slide-number order. Each slide's text comes first, then its speaker notes on a **Speaker notes:** line:\nText boxes and titles come out one text run per line, with XML character references decoded (&amp; is shown as &, &lt; as <, &#169; as the copyright sign).\nA table row becomes one line with the cells joined by |. An empty cell keeps its separator, so the cells of a row can still be counted (Q3 | | has three, | Open | has three), and a cell with several paragraphs or line breaks is joined onto the one line with spaces. A row with no text at all is dropped. A literal | in cell text is not escaped, so it reads like a separator.\nSpeaker notes are always included. There is no option to turn them off.\nA slide with no text and no notes is left out. A deck of nothing but pictures therefore arrives as the Presentation: N slide(s) header alone.\nPictures, charts, SmartArt, and shapes that hold no text contribute nothing.\n\nSelecting and formatting spreadsheet data\n\nNaming a sheet that does not exist does not silently fall back to every sheet. The prompt reports the miss and lists the available sheet names.\n\nOptions\n\nofficeOptions is accepted by generate() and stream(). Two fields are read, both by the spreadsheet reader:\n\n| Option | Type | Default | Effect |\n| ------------- | ---------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| sheetName | string | all | Restrict the workbook to the named sheet. An unknown name is reported in the prompt together with the sheets that do exist. ","hierarchy":{"lvl0":"Features","lvl1":"Office Documents Support","lvl2":"","lvl3":""}},
3518
3525
  {"objectID":"20c3155d2c1f39db07b8ffb72304d589cb601d27d43fb6f6f81ec8e83b5af4f2","title":"Office Documents Support","url":"/docs/features/office-documents#office-documents-support","content":"NeuroLink reads Word, Excel, and PowerPoint files itself and puts their content into the prompt as text. Attach them through the same input.files array (or --file flag) that carries every other file type; there is no separate Office input field and no provider restriction.","hierarchy":{"lvl0":"Features","lvl1":"Office Documents Support","lvl2":"Office Documents Support","lvl3":""}},
@@ -3738,7 +3745,7 @@
3738
3745
  {"objectID":"439bf40cab58d82bc1488de91f2152f91d3ff65586aaa4616577f8bdae040075","title":"1. The shape of the problem","url":"/docs/features/proxy-cli-onboarding#1-the-shape-of-the-problem","content":"The proxy is a Hono app bound to 127.0.0.1:55669 by default (proxy.ts:3864,\n:4532, :5590; port default 55669 at :4536). It terminates a CLI's own OAuth\ntoken, swaps in one from a pooled account, forwards upstream, and relays SSE back.\n\nIt exposes three wire surfaces:\n\n| Door | Factory | Upstream |\n| ----------------------------------- | ------------------------------------------------------- | --------------------------------------- |\n| POST /v1/messages | createClaudeProxyRoutes (claudeProxyRoutes.ts:8316) | api.anthropic.com |\n| POST /v1/chat/completions | createOpenAIProxyRoutes (openaiProxyRoutes.ts:351) | translation engine / Anthropic loopback |\n| POST /backend-api/codex/responses | createCodexProxyRoutes (codexProxyRoutes.ts:568) | chatgpt.com/backend-api |\n\nDispatch is by URL path. A CLI is \"onboarded\" by writing _that CLI's own config\nfile_ so it points at the right door. Three such writers exist, hand-authored, sharing\nno abstraction:\n\n| CLI | Writer | Restore | Target |\n| ----------- | --------------------------------------------- | --------------------------------------- | ---------------------------------- |\n| Claude Code | setClaudeProxySettings proxy.ts:625-652 | clearClaudeProxySettings :654 | ~/.claude/settings.json (:248) |\n| OpenCode | setOpenCodeProxySettings proxy.ts:734-784 | clearOpenCodeProxySettings :786-843 | opencode.json (:722) |\n| Codex | setCodexProxySettings proxy.ts:922-994 | clearCodexProxySettings :996 | ~/.codex/config.toml (:857) |\n\n/v1/chat/completions is the important door and nothing is pointed at it.","hierarchy":{"lvl0":"Features","lvl1":"Onboarding a New AI Coding CLI onto the NeuroLink Proxy","lvl2":"1. The shape of the problem","lvl3":""}},
3739
3746
  {"objectID":"c1678ee68815daa3446f700aafd2ef5a7536ca51886ffbeb8e8989d38fbf0b1b","title":"Coverage, verified on this machine","url":"/docs/features/proxy-cli-onboarding#coverage-verified-on-this-machine","content":"| CLI | Installed | Verdict | Mechanism |\n| ------------ | ----------------------------- | ------------------------- | -------------------------------------------------------------- |\n| Claude Code | yes | live | ANTHROPIC_BASE_URL |\n| Codex | yes | live | config.toml base_url + wire_api=\"responses\" |\n| OpenCode | 1.3.13 | live | provider.neurolink in opencode.json (fixed: #1366/#1367) |\n| Qwen Code | @qwen-code/qwen-code@0.17.0 | live | security.auth.baseUrl → /v1/chat/completions |\n| Copilot CLI | @github/copilot@1.0.56 | live | COPILOT_PROVIDER_* via a sourceable env script |\n| Hermes Agent | no | easy (unverified on disk) | ANTHROPIC_BASE_URL / OPENAI_BASE_URL |\n| Gemini CLI | @google/gemini-cli@0.53.0 | moderate | GOOGLE_GEMINI_BASE_URL, API-key mode only |\n| Amp | 0.0.1780291930-gf6d536 | hard | AMP_URL honoured, but fronts a proprietary backend — see §7c |\n| Cursor | cursor-agent 2026.05.28 | refuted | env vars are dead code — proven by live test |\n| Antigravity | 1.107.0 | hard | proprietary Cascade protobuf |\n| Grok CLI | no | unconfirmed | not installed |\n| Kiro CLI | no | hard | fixed AWS hosts, OAuth device flow |\n\nNear-term: 2 live → 6 — five of those are now live (Claude Code, Codex,","hierarchy":{"lvl0":"Features","lvl1":"Onboarding a New AI Coding CLI onto the NeuroLink Proxy","lvl2":"Coverage, verified on this machine","lvl3":""}},
3740
3747
  {"objectID":"d002685bad412b4ea546cccc1ebd7441570e08708eb3fb3d8a6ead1898f5edb4","title":"2. Corrections to the v11.2.2 audit","url":"/docs/features/proxy-cli-onboarding#2-corrections-to-the-v1122-audit","content":"The earlier audit's structural claims hold. Five of its specific claims do not.","hierarchy":{"lvl0":"Features","lvl1":"Onboarding a New AI Coding CLI onto the NeuroLink Proxy","lvl2":"2. Corrections to the v11.2.2 audit","lvl3":""}},
3741
- {"objectID":"078d30f3c31c850d03257e95c48d42c113019b6fd26ccf46bb076eb1755cd92f","title":"2.1 The cost defect is attributed to the wrong route — and the failure mode is different","url":"/docs/features/proxy-cli-onboarding#21-the-cost-defect-is-attributed-to-the-wrong-route-and-the-failure-mode-is-different","content":"Claimed: proxyTracer.ts:497 and :826 hard-code calculateCost(\"anthropic\", …),\nso Codex traffic to chatgpt.com is costed against Anthropic's price table.\n\nActually:\nThere are four hard-coded \"anthropic\" sites, not two: proxyTracer.ts:497,\n :826, :874 (all calculateCost) and :883\n (the recordAIRequest metric call).\ncodexProxyRoutes.ts never imports ProxyTracer and never calls\n calculateCost. Its logRequest() object literal (:322-339) has no token keys\n at all, and it never parses usage out of the SSE stream. Codex traffic is not\n mis-priced — it is entirely unaccounted. Fixing it starts with parsing usage,\n not with the pricing call.\nThe hard-coded provider actually mis-prices /v1/chat/completions, which routes\n through ModelRouter to any provider.\nThe failure mode is usually $0, not a wrong number. findRates returns\n undefined when nothing matches, and calculateCost then returns 0\n (pricing.ts:755-758). The anthropic table (pricing.ts:26-161) has no\n _default sentinel — the first one is at :406. So findRates(\"anthropic\",\n\"gpt-4o\") → undefined → $0. Real mis-pricing only happens when a\n Claude-named model is routed elsewhere (e.g. a claude-* alias mapped to Vertex\n Gemini), which prices Gemini traffic at Sonnet rates.\nCompounding it: ProxyTracer.model is private readonly (proxyTracer.ts:248),\n fixed at construction to the model the client asked for.\n setModelSubstitution() only writes span attributes. So even with a correct\n provider string, cost is computed against the requested model, not the served one.\n\nGood news: pricing.ts already ships 18 provider tables including openai with a\n\"gpt-5.1-codex\" entry (:162+). For the /v1/chat/completions path this is a\nparameter change, not new pricing data.","hierarchy":{"lvl0":"Features","lvl1":"Onboarding a New AI Coding CLI onto the NeuroLink Proxy","lvl2":"2.1 The cost defect is attributed to the wrong route — and the failure mode is different","lvl3":""}},
3748
+ {"objectID":"078d30f3c31c850d03257e95c48d42c113019b6fd26ccf46bb076eb1755cd92f","title":"2.1 The cost defect is attributed to the wrong route — and the failure mode is different","url":"/docs/features/proxy-cli-onboarding#21-the-cost-defect-is-attributed-to-the-wrong-route-and-the-failure-mode-is-different","content":"Claimed: proxyTracer.ts:497 and :826 hard-code calculateCost(\"anthropic\", …),\nso Codex traffic to chatgpt.com is costed against Anthropic's price table.\n\nActually:\nThere are four hard-coded \"anthropic\" sites, not two: proxyTracer.ts:497,\n :826, :874 (all calculateCost) and :883\n (the recordAIRequest metric call, which feeds ai_requests_total; the proxy\n also counts requests with its own proxy_requests_total counter).\ncodexProxyRoutes.ts never imports ProxyTracer and never calls\n calculateCost. Its logRequest() object literal (:322-339) has no token keys\n at all, and it never parses usage out of the SSE stream. Codex traffic is not\n mis-priced — it is entirely unaccounted. Fixing it starts with parsing usage,\n not with the pricing call.\nThe hard-coded provider actually mis-prices /v1/chat/completions, which routes\n through ModelRouter to any provider.\nThe failure mode is usually $0, not a wrong number. findRates returns\n undefined when nothing matches, and calculateCost then returns 0\n (pricing.ts:755-758). The anthropic table (pricing.ts:26-161) has no\n _default sentinel — the first one is at :406. So findRates(\"anthropic\",\n\"gpt-4o\") → undefined → $0. Real mis-pricing only happens when a\n Claude-named model is routed elsewhere (e.g. a claude-* alias mapped to Vertex\n Gemini), which prices Gemini traffic at Sonnet rates.\nCompounding it: ProxyTracer.model is private readonly (proxyTracer.ts:248),\n fixed at construction to the model the client asked for.\n setModelSubstitution() only writes span attributes. So even with a correct\n provider string, cost is computed against the requested model, not the served one.\n\nGood news: pricing.ts already ships 18 provider tables including openai with a\n\"gpt-5.1-codex\" entry (:162+). For the /v1/chat/completions path this is a\nparameter change, not new pricing data.","hierarchy":{"lvl0":"Features","lvl1":"Onboarding a New AI Coding CLI onto the NeuroLink Proxy","lvl2":"2.1 The cost defect is attributed to the wrong route — and the failure mode is different","lvl3":""}},
3742
3749
  {"objectID":"0e958f52839eaf1faa4147d73bb91f8ed4ce2058629b7169b3ac3d59cae3dc30","title":"2.2 \"Nothing identifies the calling CLI at request time\" is false — and this is an onboarding hazard","url":"/docs/features/proxy-cli-onboarding#22-nothing-identifies-the-calling-cli-at-request-time-is-false-and-this-is-an-onboarding-hazard","content":"Claimed: detectClientApp() (proxyTracer.ts:226-235) is the only User-Agent\nsniff and it only labels trace spans.\n\nActually: there is a second sniff that drives real request behaviour.\nisLikelyClaudeClient() (claudeProxyRoutes.ts:2341-2350) tests\nheaders[\"user-agent\"]?.startsWith(\"claude-cli/\") (among other signals), and its\nresult gates:\nwhich OAuth beta header set is sent — CLAUDE_CODE_OAUTH_BETAS vs\n NON_CLAUDE_OAUTH_BETAS (:7221-7236);\nwhether polyfillOAuthBody() preserves the client's own system-prompt / agent\n identity blocks verbatim or strips and relocates them;\nmaybeRefreshClaudeSnapshot() (:2398).\n\nThis matters directly for CLI #4 and #5. Any non-Claude CLI pointed at\n/v1/messages via ANTHROPIC_BASE_URL — Hermes is exactly this case — takes the\nNON_CLAUDE_OAUTH_BETAS branch and a different system-prompt path. That is probably\ncorrect behaviour, but it is behaviour, and it must be tested rather than assumed.","hierarchy":{"lvl0":"Features","lvl1":"Onboarding a New AI Coding CLI onto the NeuroLink Proxy","lvl2":"2.2 \"Nothing identifies the calling CLI at request time\" is false — and this is an onboarding hazard","lvl3":""}},
3743
3750
  {"objectID":"8997754a7d1d3258d747dd7590b34765a6d34d8c36e642f970247618d869e78e","title":"2.3 \"314,116 requests and no ledger\" is overstated","url":"/docs/features/proxy-cli-onboarding#23-314116-requests-and-no-ledger-is-overstated","content":"usageStats.ts (1,478 lines, zero occurrences of inputTokens / outputTokens /\ntotalTokens / cost) is confirmed. But neurolink proxy analyze is a real, wired\ncommand (src/cli/parser.ts:260) and proxyAnalysis.ts:437-527 reads the same\nproxy-YYYY-MM-DD.jsonl files requestLogger.ts writes and sums inputTokens,\ncacheReadTokens and cacheCreationTokens per window.\n\nThe gap is narrower and more specific than \"no ledger\": outputTokens and cost\nare absent from proxyAnalysis.ts entirely, and nothing aggregates the Codex engine\nat all (§2.1).","hierarchy":{"lvl0":"Features","lvl1":"Onboarding a New AI Coding CLI onto the NeuroLink Proxy","lvl2":"2.3 \"314,116 requests and no ledger\" is overstated","lvl3":""}},
3744
3751
  {"objectID":"081076044aa5320358b560458b66d5725f4a5776d94e808e16b6961541789dcc","title":"2.4 OpenCode is two bugs, not a one-line fix","url":"/docs/features/proxy-cli-onboarding#24-opencode-is-two-bugs-not-a-one-line-fix","content":"The path bug is confirmed and is genuinely one line — but a path-only fix leaves a\nsecond, independent bug in place.\ngetOpenCodeConfigDir() (proxy.ts:711-720) returns\n ~/Library/Application Support/opencode on darwin. The installed OpenCode 1.3.13\n binary embeds the unmodified xdg-basedir npm package\n (XDG_CONFIG_HOME || join(homedir, \".config\")) with no platform branch at all.\n Empirically: ~/.config/opencode/opencode.json exists (2,640 bytes, holds a working\n custom provider that opencode models confirms is loaded);\n ~/Library/Application Support/opencode does not exist. Deleting the darwin\n branch is the whole fix.\nThe second bug survives that. setOpenCodeProxySettings returns Promise<void>\n (:734) and the call sites print āœ“ Auto-configured OpenCode settings\n unconditionally (:3491-3493, :5403-5405). setCodexProxySettings returns\n Promise<boolean> and its āœ“ is gated on it (:3502, :5413). So even after the\n path fix, a user without OpenCode installed still gets a success message for work\n that did not happen.\n\nOrigin of the mistake: the OpenCode binary does contain the literal\n/Library/Application Support/opencode — as systemManagedConfigDir(), an MDM /\nenterprise policy tier at the filesystem root (no $HOME), paired with\n/etc/opencode and %ProgramData%\\opencode. Someone found that string and read it as\nthe per-user path.\n\nIt is also baked into our own docs. docs/features/opencode-proxy-support.md:67\nasserts the macOS path is ~/Library/Application Support/opencode/opencode.json,\nwhile :313-323 of the same file shows resolution code with no darwin branch that\nwould compute ~/.config/opencode. The doc contradicts itself, and it is titled\n\"Implemented & Verified\".\n\nHow it escaped verification: §11's E2E playbook runs the dev proxy with --dev,\nwhich by its own option description performs \"no client auto-configuration\"\n(proxy.ts:3910-3917), and hand-copies a fixture to\n/tmp/opencode-proxy-test/opencode.json. The writer is never executed.","hierarchy":{"lvl0":"Features","lvl1":"Onboarding a New AI Coding CLI onto the NeuroLink Proxy","lvl2":"2.4 OpenCode is two bugs, not a one-line fix","lvl3":""}},
@@ -5644,7 +5651,7 @@
5644
5651
  {"objectID":"6337b6964b5ebadd9610f93633594d912ee39f49c2ef5e1683214dce45a1aee3","title":"🧠 Decision-Only Providers","url":"/docs/getting-started/providers#decision-only-providers","content":"The providers that serve decide rather than generate/stream:\nTypeSafe, Laya, XOR,\nSubQ, Perplexity, Cloudflare Clef,\nOpenAI Decisions and ScaleDown. Each returns typed boolean/choice/score\nanswers and emits no text, so none appears in generation fallback chains or the\nhealth sweep.","hierarchy":{"lvl0":"Getting Started","lvl1":"AI Provider Guides","lvl2":"🧠 Decision-Only Providers","lvl3":""}},
5645
5652
  {"objectID":"50860d1f7a27dbe3ad7452ad7079ea437658db372b97081d0a509429369cacc6","title":"[TypeSafe (Jev)](/docs/getting-started/providers/typesafe)","url":"/docs/getting-started/providers#typesafe-jevdocsgetting-startedproviderstypesafe","content":"Typed, calibrated judgments instead of text\nšŸŽÆ boolean / choice / score answers, each with a calibrated confidence\n⚔ Latency flat in question count — 1 question ~393 ms, 400 questions ~465 ms\nšŸ’° ~$0.00002 per decision (~$0.042/M input, output billed at zero)\nšŸ”Œ Two transports: TypeSafe direct, or the Vercel AI Gateway\nšŸ›”ļø Fails open — with no decision provider configured, every consumer behaves exactly as before\nšŸ”‘ API key from console.typesafe.ai/keys\nšŸ”„ Aliases: jev, typesafe-ai\n\nSetup Guide →","hierarchy":{"lvl0":"Getting Started","lvl1":"AI Provider Guides","lvl2":"[TypeSafe (Jev)](/docs/getting-started/providers/typesafe)","lvl3":""}},
5646
5653
  {"objectID":"43b838d60f2794ce46361196d6fc8a708b3a301398dd55f2c8015d567aa21c2c","title":"[Laya](/docs/getting-started/providers/laya)","url":"/docs/getting-started/providers#layadocsgetting-startedproviderslaya","content":"Open-weights decision provider — the same typed boolean/choice/score answers as Jev, from Convai Innovations' Apache-2.0 checkpoints, at a Laya server or LiteLLM proxy route you configure\n🧭 Serves decide only; built-in features use it when its key and base URL are set and neither TYPESAFE_API_KEY nor AI_GATEWAY_API_KEY is\nšŸ“ Refuses more than ~768 tokens of state (320 on english) before any network call, since its encoders read only 1,024 (512)\nšŸ”Œ No built-in endpoint: LAYA_BASE_URL (or credentials.laya.baseURL) names a Laya server or a LiteLLM pass-through route to one\nšŸ”‘ LAYA_API_KEY is the key that endpoint accepts; on LiteLLM, the route must be in the key's Allowed Routes\n\nSetup Guide →","hierarchy":{"lvl0":"Getting Started","lvl1":"AI Provider Guides","lvl2":"[Laya](/docs/getting-started/providers/laya)","lvl3":""}},
5647
- {"objectID":"613cbeca5e8fa0bbe765b8db9ec4ed2ff76a913dd45b8dab5e2de06a6e5f19ff","title":"[XOR](/docs/getting-started/providers/xor)","url":"/docs/getting-started/providers#xordocsgetting-startedprovidersxor","content":"Open-weights decision provider — the same typed boolean/choice/score answers as Jev, from xor-latest (text) and xor-omni (images and video), Juspay's Apache-2.0 model, at a deployment or LiteLLM proxy route you configure\n🧭 Serves decide only; built-in features use it when its key and base URL are set and no TypeSafe key, no AI_GATEWAY_API_KEY and no Laya key and base URL is\nšŸ–¼ļø Takes up to 8 images or one video with a decision on xor-omni, as a Buffer, a local file path or a data: URL; TypeSafe and Laya refuse media before any request, and Perplexity refuses a video\nšŸ“ Refuses more than about 200,000 estimated tokens of state before any network call\nšŸ”Œ No built-in endpoint: XOR_BASE_URL (or credentials.xor.baseURL) is the origin of a deployment or of a LiteLLM route to one; requests go to <base URL>/v1/decisions\nšŸ”‘ XOR_API_KEY is the key that endpoint accepts; on LiteLLM, the key's team must allow xor-latest (and xor-omni for media)\nšŸ“¦ Weights and setup: huggingface.co/juspay/xor\n\nSetup Guide →","hierarchy":{"lvl0":"Getting Started","lvl1":"AI Provider Guides","lvl2":"[XOR](/docs/getting-started/providers/xor)","lvl3":""}},
5654
+ {"objectID":"613cbeca5e8fa0bbe765b8db9ec4ed2ff76a913dd45b8dab5e2de06a6e5f19ff","title":"[XOR](/docs/getting-started/providers/xor)","url":"/docs/getting-started/providers#xordocsgetting-startedprovidersxor","content":"Open-weights decision provider — the same typed boolean/choice/score answers as Jev, plus value extraction, from xor-latest (text) and xor-omni (an image and audio), Juspay's Apache-2.0 model, at a deployment or LiteLLM proxy route you configure\n🧭 Serves decide only; built-in features use it when its key and base URL are set and no TypeSafe key, no AI_GATEWAY_API_KEY and no Laya key and base URL is\nšŸ–¼ļø Takes one image and one audio clip (the first 30 seconds are heard) with a decision on xor-omni, as a Buffer, a local file path or a data: URL; a video is refused, because XOR's gateway drops it\nšŸ“ Refuses more than 120,000 estimated tokens of state on xor-latest (28,000 on xor-omni) or more than 128 questions before any network call\nšŸ”Œ No built-in endpoint: XOR_BASE_URL (or credentials.xor.baseURL) is the origin of a deployment or of a LiteLLM route to one; requests go to <base URL>/v1/decisions\nšŸ”‘ XOR_API_KEY is the key that endpoint accepts; on LiteLLM, the key's team must allow xor-latest (and xor-omni for media)\nšŸ“¦ Weights and setup: huggingface.co/juspay/xor\n\nSetup Guide →","hierarchy":{"lvl0":"Getting Started","lvl1":"AI Provider Guides","lvl2":"[XOR](/docs/getting-started/providers/xor)","lvl3":""}},
5648
5655
  {"objectID":"9fd996aaa27fb32250d5434f0e2764d8bf348370af0e011f9e447a0fdf26e3a8","title":"[SubQ](/docs/getting-started/providers/subq)","url":"/docs/getting-started/providers#subqdocsgetting-startedproviderssubq","content":"Hosted decision provider — the same typed boolean/choice/score answers as Jev, from Subquadratic's subq-snap-preview (public preview), at https://api.subq.ai\n🧭 Serves decide only (provider id subq; the aliases subquadratic and subq-snap work too); built-in features use it when it is configured and none of TypeSafe, Laya or XOR is — it comes ahead of Perplexity and Cloudflare Clef, because its key is shared with no other provider\nšŸ”‘ SUBQ_API_KEY alone configures it (keys come from console.subq.ai, shown once); SUBQ_MODEL is optional\nšŸ–¼ļø No image or video input yet: SubQ's image format is not one the decide catalog can describe, so images are refused before any request\nšŸ“ NeuroLink reports an advisory state window (376,000 estimated tokens; SubQ's own 400 response states a maximum model length of 393,216) and enforces none; no question cap is declared\nšŸ”Œ Hosted endpoint, so no base URL is needed; SUBQ_BASE_URL (or credentials.subq.baseURL) can name another origin\n\nSetup Guide →","hierarchy":{"lvl0":"Getting Started","lvl1":"AI Provider Guides","lvl2":"[SubQ](/docs/getting-started/providers/subq)","lvl3":""}},
5649
5656
  {"objectID":"6ab8cdd8152a0d7c9844d54b6d4eaccc905b60e79f4b28508d78d52c3cfe9470","title":"[Perplexity Decisions](/docs/getting-started/providers/perplexity-decider)","url":"/docs/getting-started/providers#perplexity-decisionsdocsgetting-startedprovidersperplexity-decider","content":"Hosted decision provider — the same typed boolean/choice/score answers as Jev, from Perplexity's pplx-decider-v1-27b, at https://api.perplexity.ai\n🧭 Serves decide only (provider id perplexity-decider, not the Sonar text provider perplexity); built-in features use it when it is configured and no TypeSafe key, no AI_GATEWAY_API_KEY, no Laya key and base URL, no XOR key and base URL and no SUBQ_API_KEY is\nšŸ”‘ PERPLEXITY_API_KEY alone configures it, and it is the same key the Perplexity text provider reads, so a key set for Sonar also lets built-in features use it when none of TypeSafe, Laya, XOR or SubQ is configured; the guide lists what each consumer then sends\nšŸ–¼ļø Takes up to 8 PNG, JPEG or WebP images with a decision; no video\nšŸ’° $0.04 per million input tokens (image tokens included), output free, as Perplexity documents it\nšŸ“ Refuses more than about 100,000 estimated tokens of state (NeuroLink's own window; the server's ceiling is 262,144 input tokens), or more than 128 questions in a decide() call (tryDecide() splits them), before any network call. The 128-question cap, the server's token ceiling and the latency were measured on a real account in October 2026\nšŸ”Œ Hosted endpoint, so no base URL is needed; PERPLEXITY_DECIDER_BASE_URL (or credentials.perplexityDecider.baseURL) can name another origin\n\nSetup Guide →","hierarchy":{"lvl0":"Getting Started","lvl1":"AI Provider Guides","lvl2":"[Perplexity Decisions](/docs/getting-started/providers/perplexity-decider)","lvl3":""}},
5650
5657
  {"objectID":"94b24c75db64d3f04ba04b2ee0f04ec13bc0509de0aea1fa1b3e5516dea73ea9","title":"[Cloudflare Clef](/docs/getting-started/providers/cloudflare-clef)","url":"/docs/getting-started/providers#cloudflare-clefdocsgetting-startedproviderscloudflare-clef","content":"Hosted decision provider on Workers AI — the same typed boolean/choice/score answers as Jev, from Cloudflare's clef (27B) and clef-flash (9B), at https://api.cloudflare.com/client/v4\n🧭 Serves decide only (provider id cloudflare-clef, not the Workers AI text provider cloudflare); built-in features use it only when it is configured and none of TypeSafe, Laya, XOR, SubQ or Perplexity is — it comes after those five\nšŸ”‘ CLOUDFLARE_API_KEY (Workers AI permission) and CLOUDFLARE_ACCOUNT_ID configure it, and they are the same two variables the Cloudflare text provider reads, so a pair set for that provider also lets built-in features use it when none of TypeSafe, Laya, XOR, SubQ or Perplexity is configured\nšŸ–¼ļø Takes up to 4 PNG, JPEG or WebP images with a decision; no video\nšŸ’° $0.24 per million input tokens for clef, $0.09 for clef-flash; no output price is listed on Cloudflare's pricing page\nšŸ“ The state window changed in October 2026 (hosted service or model: unknown): until 2026-10-04 the endpoint ignored text past about 2,048 tokens without an error, and on 2026-10-07 it read states of at least 190,153 tokens (clef-flash) and 52,154 (clef); NeuroLink refuses more than 16,000 estimated tokens (digits count a token each, punctuation 0.75, emoji 3), more than 64 questions in a decide() call (tryDecide() splits them) and a request over 256,000 bytes, base64 image data included, before any network call. The state cut and the request ceiling were measured on a real account in October 2026, with the follow-up text cuts and API limits checked on both models; see the guide for the tested shapes and dates\nšŸ”Œ Hosted endpoint, so no base URL is needed; CLOUDFLARE_CLEF_BASE_URL (or credentials.cloudflareClef.baseURL) can name another base\n\nSetup Guide →","hierarchy":{"lvl0":"Getting Started","lvl1":"AI Provider Guides","lvl2":"[Cloudflare Clef](/docs/getting-started/providers/cloudflare-clef)","lvl3":""}},
@@ -6323,7 +6330,7 @@
6323
6330
  {"objectID":"bd7509f708e016023be201690ca4cc963bc77551d7152b34c636ae628ea5e497","title":"2. Configure","url":"/docs/getting-started/providers/perplexity-decider#2-configure","content":"Set the key 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:\n\ncredentials.perplexityDecider is its own slice. A key passed as\ncredentials.perplexity belongs to the text provider and does not configure\nthis one. PERPLEXITY_BASE_URL, which moves the text provider, is not read here;\nthe override for this provider is PERPLEXITY_DECIDER_BASE_URL, and a trailing\n/v1 on it is accepted. It must be https://; http:// is accepted only for\nlocalhost, 127.0.0.1 and [::1], because every request carries the key.","hierarchy":{"lvl0":"Getting Started","lvl1":"Perplexity Decisions Provider Guide","lvl2":"2. Configure","lvl3":""}},
6324
6331
  {"objectID":"c3dbe3c712926dfb07b0d907370532df8bc3c98a5e81f4b46784a037ac0f1e0b","title":"3. Use it","url":"/docs/getting-started/providers/perplexity-decider#3-use-it","content":"NeuroLink's boolean question is sent to the API as its noul type, and the\nanswer is read back as a boolean. A boolean answer carries a probability and\nno confidence of its own; a choice or score answer carries the confidence the\nAPI reports, which Perplexity describes as the model's own certainty estimate,\nnot the top probability. Perplexity's API reference and quickstart do not call\nthat confidence calibrated, and NeuroLink did not measure whether it is, so tune\na threshold on it against your own labelled data. (The Hugging Face model card\nfor the open-weights model describes its output as calibrated probabilities.)\nPerplexity also notes that identical requests occasionally differ in the second\ndecimal place. Repeating a request returned the same numbers in NeuroLink's\nprobe, but leave some margin when you set a threshold anyway.","hierarchy":{"lvl0":"Getting Started","lvl1":"Perplexity Decisions Provider Guide","lvl2":"3. Use it","lvl3":""}},
6325
6332
  {"objectID":"6f3f0a67d6c85ce65d1d7d95a7016987ad0423b545c9f849bcde853edd0dd20f","title":"From the CLI","url":"/docs/getting-started/providers/perplexity-decider#from-the-cli","content":"The CLI reads the same environment variables:\n\nThe text output ends with a Cost: line, priced from the input tokens the API\nreports.","hierarchy":{"lvl0":"Getting Started","lvl1":"Perplexity Decisions Provider Guide","lvl2":"From the CLI","lvl3":""}},
6326
- {"objectID":"e1b9161659de68a10277e69ce53077f2aed241892d783054f01471fb793024bd","title":"Images","url":"/docs/getting-started/providers/perplexity-decider#images","content":"Perplexity reads images alongside the state. TypeSafe, Laya,\nSubQ and ScaleDown do not read media. XOR reads images and a video; Perplexity reads images only, and so do\nCloudflare Clef, up to 4 of them, and\nOpenAI Decisions. In the probe of\nPerplexity's model it did read an image: asked red versus blue about one, it\nanswered correctly at about 0.98 confidence.\n\nFrom the CLI, pass --image <path>, repeated for several:\n\nOn the wire the images go inside state: NeuroLink sends state as an array,\nyour state first and then one OpenAI-style image_url part per image, each a\nbase64 data URL. The API has no separate images field and rejects unknown\nfields. An image can also be the whole input (an image-only state was\naccepted in the probe); from the SDK, pass an empty string as state with\nimages. The CLI always needs a state.\n\nThe rules:\nUp to 8 images per request. Measured: 8 images with three questions were\n accepted, and 9 images with one question were refused with a 400 whose message\n words the cap as \"per question\". NeuroLink applies the 8 to the request as a\n whole.\nPNG, JPEG or WebP only. The type comes from the bytes, not the file\n extension. A GIF, or a data: URL of another type, is refused before any\n request, as a non-retryable invalid_request. The API refuses a GIF with a 400\n too.\nEach is a Buffer, a local file path or a data: URL. An http(s) URL is\n refused locally and never fetched. The API does not fetch URLs either: an\n https image URL returned a 400 in the probe.\nEach image may be at most 2,048 tiles of 32 Ɨ 32 pixels. Round the width\n and the height to the nearest multiple of 32 and keep (width / 32) Ɨ\n (height / 32) at or under 2,048: by that count 1440 Ɨ 1440 (2,025 tiles) and\n 2048 Ɨ 1024 (2,048) fit, and 1600 Ɨ 1310 (2,050) does not. Perplexity documents\n this rule, and it was checked at its edge.","hierarchy":{"lvl0":"Getting Started","lvl1":"Perplexity Decisions Provider Guide","lvl2":"Images","lvl3":""}},
6333
+ {"objectID":"e1b9161659de68a10277e69ce53077f2aed241892d783054f01471fb793024bd","title":"Images","url":"/docs/getting-started/providers/perplexity-decider#images","content":"Perplexity reads images alongside the state. TypeSafe, Laya,\nSubQ and ScaleDown do not read media. XOR reads one image and audio; Perplexity reads images only, and so do\nCloudflare Clef, up to 4 of them, and\nOpenAI Decisions. In the probe of\nPerplexity's model it did read an image: asked red versus blue about one, it\nanswered correctly at about 0.98 confidence.\n\nFrom the CLI, pass --image <path>, repeated for several:\n\nOn the wire the images go inside state: NeuroLink sends state as an array,\nyour state first and then one OpenAI-style image_url part per image, each a\nbase64 data URL. The API has no separate images field and rejects unknown\nfields. An image can also be the whole input (an image-only state was\naccepted in the probe); from the SDK, pass an empty string as state with\nimages. The CLI always needs a state.\n\nThe rules:\nUp to 8 images per request. Measured: 8 images with three questions were\n accepted, and 9 images with one question were refused with a 400 whose message\n words the cap as \"per question\". NeuroLink applies the 8 to the request as a\n whole.\nPNG, JPEG or WebP only. The type comes from the bytes, not the file\n extension. A GIF, or a data: URL of another type, is refused before any\n request, as a non-retryable invalid_request. The API refuses a GIF with a 400\n too.\nEach is a Buffer, a local file path or a data: URL. An http(s) URL is\n refused locally and never fetched. The API does not fetch URLs either: an\n https image URL returned a 400 in the probe.\nEach image may be at most 2,048 tiles of 32 Ɨ 32 pixels. Round the width\n and the height to the nearest multiple of 32 and keep (width / 32) Ɨ\n (height / 32) at or under 2,048: by that count 1440 Ɨ 1440 (2,025 tiles) and\n 2048 Ɨ 1024 (2,048) fit, and 1600 Ɨ 1310 (2,050) does not. Perplexity documents\n this rule, and it was checked at its edge.","hierarchy":{"lvl0":"Getting Started","lvl1":"Perplexity Decisions Provider Guide","lvl2":"Images","lvl3":""}},
6327
6334
  {"objectID":"8ad813ca01099f441bbc2db40ee064d9a0294d269166c42a6f87fce2a1c1330f","title":"When NeuroLink uses it","url":"/docs/getting-started/providers/perplexity-decider#when-neurolink-uses-it","content":"Every built-in consumer of decide asks for the default decision provider. That\nis the first one that is configured, in the environment or in the credentials\npassed to the SDK, in the order TypeSafe, Laya, XOR, SubQ,\nPerplexity,\nCloudflare Clef,\nOpenAI Decisions, then ScaleDown. TypeSafe counts\nwith either of its keys, TYPESAFE_API_KEY or AI_GATEWAY_API_KEY. Laya and XOR\neach count only with both their key and their base URL. SubQ counts with its key\nalone, SUBQ_API_KEY. Perplexity counts with its key alone, a non-blank\nPERPLEXITY_API_KEY or credentials.perplexityDecider.apiKey. Cloudflare Clef\ncounts only with both CLOUDFLARE_API_KEY and CLOUDFLARE_ACCOUNT_ID. OpenAI Decisions counts with\nits key alone, OPENAI_API_KEY or credentials.openaiDecisions.apiKey.\nScaleDown counts with its key alone, SCALEDOWN_API_KEY or\ncredentials.scaledown.apiKey, which no other provider reads. A caller\ncan always name Perplexity with provider: \"perplexity-decider\". So:\nOnly a Perplexity key: built-in features use Perplexity.\nA Perplexity key, plus TypeSafe's key, or Laya's or XOR's key and base\n URL, or SUBQ_API_KEY: built-in features use TypeSafe, Laya, XOR or SubQ,\n in that order, whichever is configured. Perplexity runs only where a caller\n asks for provider: \"perplexity-decider\".\nA Perplexity key, plus CLOUDFLARE_API_KEY and CLOUDFLARE_ACCOUNT_ID:\n built-in features use Perplexity; Cloudflare Clef runs only where a caller\n asks for provider: \"cloudflare-clef\".\nA Perplexity key, plus SCALEDOWN_API_KEY: built-in features use\n Perplexity, which comes ahead of ScaleDown; ScaleDown runs only where a caller\n asks for provider: \"scaledown\".\nA key for the text provider only: the same as the first case. The key is\n shared, so the text provider's key also configures this one. See\n One key, two providers.\nNone of TypeSafe, Laya, XOR, SubQ, Perplexity, Cloudflare Clef, OpenAI Decisions\n or ScaleDown configured: everything behaves exactly as it did without a decision model.","hierarchy":{"lvl0":"Getting Started","lvl1":"Perplexity Decisions Provider Guide","lvl2":"When NeuroLink uses it","lvl3":""}},
6328
6335
  {"objectID":"9545ac1f3da9bfc59774adf1978baa598ba4fc65670b5d0d4c96f7c10e2a1c3c","title":"What is sent to Perplexity","url":"/docs/getting-started/providers/perplexity-decider#what-is-sent-to-perplexity","content":"Perplexity receives the state and the questions of every decision made on\nyour behalf. The questions are NeuroLink's fixed wording, plus text from your own\nconversation where a consumer quotes it; the state is where your text goes.\nThis is what each built-in consumer sends and what turns it on, read from the\ncode:\nModel routing. One request behind the router, the model catalogue and the\n per-request context budget.\nSends: the first 8,000 characters of the prompt of the call being routed,\n plus whether tools are available, whether the request includes images, the\n estimated input tokens, the requested thinking level, whether a session is\n bound and how many prior messages the caller passed in. When the pool has\n more than one model, the questions carry the ids and descriptions of the\n models in it. The images, the session id and the conversation itself are not\n sent.\nTurned on by: classifierRouter.enabled with a pool: an opt-in. With\n the default classifier: \"auto\", a configured decision provider selects the\n decision strategy. classifier: \"heuristic\" or \"llm\" never calls a\n decision provider. A call that pins both provider and model is not\n routed.\nRelevance compaction. Stage 0 of context compaction.\nSends: the current request (first 4,000 characters) and, for each earlier\n user or assistant message that is plain text (not a tool call or result, a\n summary or a pinned skill), its role and its first 1,200 characters. That\n text travels twice, once in the state and once in the message's question. By\n default the six most recent messages are never sent\n (contextRelevance.protectRecent), and at most 300 are asked about.\nTurned on by: nothing beyond a configured decision provider. It runs\n inside context compaction, which starts when a conversation outgrows its\n context budget.\nSummary-quality gate. Guards the summarization stage of compaction.","hierarchy":{"lvl0":"Getting Started","lvl1":"Perplexity Decisions Provider Guide","lvl2":"What is sent to Perplexity","lvl3":""}},
6329
6336
  {"objectID":"01342235e59d6796c886025eea6c3dbdefc5f4420f47123db9b23eacc22955d3","title":"One key, two providers","url":"/docs/getting-started/providers/perplexity-decider#one-key-two-providers","content":"PERPLEXITY_API_KEY is the variable the\nPerplexity text provider reads to serve generate() and\nstream(). It is also the variable that configures this provider. Nothing else\nneeds to be set, so a host that set the key only to use Sonar has also\nconfigured decide:\nWith none of TypeSafe, Laya, XOR or SubQ configured, the texts listed in\n What is sent to Perplexity go to Perplexity's\n Decisions API. Context compaction needs no other opt-in, so a host with long\n conversations starts sending conversation text the first time one outgrows its\n budget. Model routing, tool routing and RAG planning do so only where you have\n turned them on. No code changes: the key is the switch.\nIt is fail-open, and TypeSafe, Laya, XOR and SubQ take precedence. If you have\n configured any of them, the shared key changes nothing for the built-in\n consumers, and a Perplexity failure leaves behaviour as it was.\nWith no decision provider configured at all, nothing is sent.\n\nAn unedited copy of .env.example does not set the key, because its\nPERPLEXITY_API_KEY line is commented out.\n\nA key in a .env file counts as a key in the environment. Importing the SDK\nloads the .env in the current working directory, and the CLI does the same at\nstart; when DOTENV_CONFIG_PATH is set, that file is loaded instead. A key that\nsits in .env therefore configures decide exactly as one exported in the\nshell does.\n\nTo keep a key that was set for Sonar from activating decisions, use one of\nthese:\nHand the key to the text provider through the SDK, not the environment, and\n keep it out of .env.\n new NeuroLink({ credentials: { perplexity: { apiKey } } }) configures the\n text provider and not this one, because NeuroLink looks for this provider's\n key in PERPLEXITY_API_KEY and in credentials.perplexityDecider, never in\n credentials.perplexity.","hierarchy":{"lvl0":"Getting Started","lvl1":"Perplexity Decisions Provider Guide","lvl2":"One key, two providers","lvl3":""}},
@@ -6836,19 +6843,20 @@
6836
6843
  {"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":""}},
6837
6844
  {"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":""}},
6838
6845
  {"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":""}},
6839
- {"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, which also reads images and a video. It emits no\ntext at all.\n\nOverview\n\nXOR is Juspay's Apache-2.0 decision model, post-trained from Qwen3.6-35B-A3B.\nIt is asked for by one of two aliases: xor-latest, the text model, and\nxor-omni, the multimodal one, which also reads images and a video. You send one\nstate plus named, typed questions, and optionally images or a video. XOR\nanswers every question in a single batched pass and returns a typed answer for\neach; the result's model is the id that served the request (for example\nxor-text-1), not the alias you asked for. NeuroLink calls whatever base URL\nyou configure — a deployment of the model, or a LiteLLM proxy with a route to\none. There 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-latest by default, xor-omni for media; XOR_MODEL sets the name your proxy uses |\n| Media | xor-omni: 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 64 options |\n| Endpoint | <base URL>/v1/decisions; 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/decisions.\nThe base URL is the origin of the deployment or of the proxy route, and a\ntrailing /v1 is accepted. It must be https://; http:// is accepted only for\nlocalhost, 127.0.0.1 and [::1], because every request carries the key and the\nstate. A deployment reachable only over plain http inside a network needs an\nhttps:// front, or a port-forward to localhost. On a LiteLLM proxy the key's team must be allowed\nthe model you ask for (xor-latest by default, xor-omni for media); otherwise the proxy answers 403\nteam_model_access_denied. A proxy may serve XOR under a name of its own: set\nXOR_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, through the xor-omni\nmodel: name it in the request (model: \"xor-omni\", or --model xor-omni on the\nCLI), because the default, xor-latest, is the text model. TypeSafe, Laya,\nSubQ and ScaleDown do not read media, and Perplexity,\nCloudflare Clef and OpenAI Decisions\nread images but no video.\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. Perplexity, Cloudflare Clef and OpenAI\n Decisions refuse a video.\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, th","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"","lvl3":""}},
6840
- {"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, which also reads images and a video. It emits no\ntext at all.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"XOR Provider Guide","lvl3":""}},
6841
- {"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.\nIt is asked for by one of two aliases: xor-latest, the text model, and\nxor-omni, the multimodal one, which also reads images and a video. You send one\nstate plus named, typed questions, and optionally images or a video. XOR\nanswers every question in a single batched pass and returns a typed answer for\neach; the result's model is the id that served the request (for example\nxor-text-1), not the alias you asked for. NeuroLink calls whatever base URL\nyou configure — a deployment of the model, or a LiteLLM proxy with a route to\none. There 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":""}},
6842
- {"objectID":"2a68b1796658b1e42d1fb20868e2f28c9b9b13e9e83ebb2ac4411de95d15b8e3","title":"Key Facts","url":"/docs/getting-started/providers/xor#key-facts","content":"| | |\n| --------------------- | ---------------------------------------------------------------------------------------- |\n| Inference type | decide only |\n| Model | xor-latest by default, xor-omni for media; XOR_MODEL sets the name your proxy uses |\n| Media | xor-omni: 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 64 options |\n| Endpoint | <base URL>/v1/decisions; 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":""}},
6846
+ {"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, plus\nvalue extraction and a choice with an unknown option, from Juspay's\nopen-weights model, which also reads an image and an audio clip. It emits no\ntext at all.\n\nOverview\n\nXOR is Juspay's Apache-2.0 decision model, post-trained from Qwen3.6-35B-A3B.\nIt is asked for by one of two aliases: xor-latest, the text model, and\nxor-omni, the multimodal one, which also reads an image and audio. You send\none state plus named, typed questions, and optionally an image or a clip. XOR\nanswers every question in a single batched pass and returns a typed answer for\neach; the result's model is the id that served the request (for example\nxor-text-1), not the alias you asked for. NeuroLink calls whatever base URL\nyou configure — a deployment of the model, or a LiteLLM proxy with a route to\none. There 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-latest by default, xor-omni for media and value; XOR_MODEL sets your proxy's name |\n| Question types | boolean, choice (optionally with unknown), score and value |\n| Media | one image and one audio clip (first 30 seconds heard); no video; body at most 25 MB |\n| Input window | 120,000 estimated tokens of state on xor-latest, 28,000 on xor-omni |\n| Questions per request | 128 (tryDecide() splits more); a choice takes 2 to 64 options, a score 2 to 10 levels |\n| Endpoint | <base URL>/v1/decisions; 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/decisions.\nThe base URL is the origin of the deployment or of the proxy route, and a\ntrailing /v1 is accepted. It must be https://; http:// is accepted only for\nlocalhost, 127.0.0.1 and [::1], because every request carries the key and the\nstate. A deployment reachable only over plain http inside a network needs an\nhttps:// front, or a port-forward to localhost. On a LiteLLM proxy the key's team must be allowed\nthe model you ask for (xor-latest by default, xor-omni for media); otherwise the proxy answers 403\nteam_model_access_denied. A proxy may serve XOR under a name of its own: set\nXOR_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\nExtracting values\n\nA value question pulls one typed field out of the state. dtype is string\n(the default), int or float, and an optional pattern is a regular\nexpression the extracted text must match. A field the state does not hold comes\nback as value: null with unknown: true:\n\nxor-omni answers value. When nobody chose a model — the call names none and\nXOR_MODEL is unset — a request with a value question goes to xor-omni\ndirectly and is checked against its 28,000-token window, since xor-latest\ncannot answer it (a LiteLLM gateway would hand it to xor-omni behind your back,\npast a window NeuroLink never checked). A model you name is always the one sent:\nasked of xor-latest explicitly, a gateway with that fallback still answers it,\nand a deployment without one refuses it with a 422 that arrives as\ninvalid_request. Every other decision provider refuses a value question\nbefore any request.\n\nA choice with unknown: true adds an option named unknown, picked when none\nof the criteria fits; the answer then carries unknown: true. The 64-option cap\ncounts it, and no option may itself be named unknown.\n\nImages, audio and video\n\nXOR reads one image and one audio clip alongside the state, through the\nxor-omni model. A request carrying either goes there by itself when nobody\nchose a model, as a value question does; name it (model: \"xor-omni\", or\n--model xor-omni on the CLI) if XOR_MODEL points elsewhere. It does not\nread video: the gateway drops a\nvideo and the model answers without it (a solid red clip was answered exactly as\ngarbage bytes were), so NeuroLink refuses one before any request rather than\nreturn a blind answer. TypeSafe, Laya, SubQ and\nScaleDown do not read media, and Per","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"","lvl3":""}},
6847
+ {"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, plus\nvalue extraction and a choice with an unknown option, from Juspay's\nopen-weights model, which also reads an image and an audio clip. It emits no\ntext at all.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"XOR Provider Guide","lvl3":""}},
6848
+ {"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.\nIt is asked for by one of two aliases: xor-latest, the text model, and\nxor-omni, the multimodal one, which also reads an image and audio. You send\none state plus named, typed questions, and optionally an image or a clip. XOR\nanswers every question in a single batched pass and returns a typed answer for\neach; the result's model is the id that served the request (for example\nxor-text-1), not the alias you asked for. NeuroLink calls whatever base URL\nyou configure — a deployment of the model, or a LiteLLM proxy with a route to\none. There 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":""}},
6849
+ {"objectID":"2a68b1796658b1e42d1fb20868e2f28c9b9b13e9e83ebb2ac4411de95d15b8e3","title":"Key Facts","url":"/docs/getting-started/providers/xor#key-facts","content":"| | |\n| --------------------- | --------------------------------------------------------------------------------------------- |\n| Inference type | decide only |\n| Model | xor-latest by default, xor-omni for media and value; XOR_MODEL sets your proxy's name |\n| Question types | boolean, choice (optionally with unknown), score and value |\n| Media | one image and one audio clip (first 30 seconds heard); no video; body at most 25 MB |\n| Input window | 120,000 estimated tokens of state on xor-latest, 28,000 on xor-omni |\n| Questions per request | 128 (tryDecide() splits more); a choice takes 2 to 64 options, a score 2 to 10 levels |\n| Endpoint | <base URL>/v1/decisions; 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":""}},
6843
6850
  {"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/decisions.\nThe base URL is the origin of the deployment or of the proxy route, and a\ntrailing /v1 is accepted. It must be https://; http:// is accepted only for\nlocalhost, 127.0.0.1 and [::1], because every request carries the key and the\nstate. A deployment reachable only over plain http inside a network needs an\nhttps:// front, or a port-forward to localhost. On a LiteLLM proxy the key's team must be allowed\nthe model you ask for (xor-latest by default, xor-omni for media); otherwise the proxy answers 403\nteam_model_access_denied. A proxy may serve XOR under a name of its own: set\nXOR_MODEL to that name.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"1. Get an endpoint and a key","lvl3":""}},
6844
6851
  {"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":""}},
6845
6852
  {"objectID":"542b2f0ef274a614254639487a162dc7c476843897f5dac50693e39a7b613878","title":"3. Use it","url":"/docs/getting-started/providers/xor#3-use-it","content":"3. Use it","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"3. Use it","lvl3":""}},
6846
6853
  {"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":""}},
6847
- {"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, through the xor-omni\nmodel: name it in the request (model: \"xor-omni\", or --model xor-omni on the\nCLI), because the default, xor-latest, is the text model. TypeSafe, Laya,\nSubQ and ScaleDown do not read media, and Perplexity,\nCloudflare Clef and OpenAI Decisions\nread images but no video.\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. Perplexity, Cloudflare Clef and OpenAI\n Decisions refuse a video.\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.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Images and video","lvl3":""}},
6854
+ {"objectID":"430a39fdcacb0b1ba3bc87b9aaea581b39613a63a4f6e6b5f9e0a88138295ba1","title":"Extracting values","url":"/docs/getting-started/providers/xor#extracting-values","content":"A value question pulls one typed field out of the state. dtype is string\n(the default), int or float, and an optional pattern is a regular\nexpression the extracted text must match. A field the state does not hold comes\nback as value: null with unknown: true:\n\nxor-omni answers value. When nobody chose a model — the call names none and\nXOR_MODEL is unset — a request with a value question goes to xor-omni\ndirectly and is checked against its 28,000-token window, since xor-latest\ncannot answer it (a LiteLLM gateway would hand it to xor-omni behind your back,\npast a window NeuroLink never checked). A model you name is always the one sent:\nasked of xor-latest explicitly, a gateway with that fallback still answers it,\nand a deployment without one refuses it with a 422 that arrives as\ninvalid_request. Every other decision provider refuses a value question\nbefore any request.\n\nA choice with unknown: true adds an option named unknown, picked when none\nof the criteria fits; the answer then carries unknown: true. The 64-option cap\ncounts it, and no option may itself be named unknown.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Extracting values","lvl3":""}},
6855
+ {"objectID":"2e141e0bf5fba98d492094ba98639e4e6270fca0089c30ee0b64d074114d3af2","title":"Images, audio and video","url":"/docs/getting-started/providers/xor#images-audio-and-video","content":"XOR reads one image and one audio clip alongside the state, through the\nxor-omni model. A request carrying either goes there by itself when nobody\nchose a model, as a value question does; name it (model: \"xor-omni\", or\n--model xor-omni on the CLI) if XOR_MODEL points elsewhere. It does not\nread video: the gateway drops a\nvideo and the model answers without it (a solid red clip was answered exactly as\ngarbage bytes were), so NeuroLink refuses one before any request rather than\nreturn a blind answer. TypeSafe, Laya, SubQ and\nScaleDown do not read media, and Perplexity,\nCloudflare Clef and OpenAI Decisions\nread images only.\n\nFrom the CLI, pass --image <path> or --audio <path>; with --audio the state\nmay be left out:\n\nThe rules:\nOne image and one audio clip per request, in images and audio; a\n second image is refused before any request.\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 WAV, FLAC, Ogg (Vorbis or Opus) and MP3 audio, each of\n which was heard with speech in testing (English identified at 0.9999). An image\n goes to XOR as a data: URL and a clip as { data, format }. An image costs\n about 600 input tokens whatever its resolution.\nThe model hears the first 30 seconds of a clip, at about 25 input tokens a\n second, and drops the rest without a word. A WAV, FLAC or Ogg (Opus or Vorbis)\n clip that runs longer is refused before any request, its length read from the\n file; an MP3's length is not read, so only its first 30 seconds count.\n AAC/M4A is refused: it was\n measured decoding to nothing while the request was still answered. A WAV\n without a complete fmt and data chunk is refused as well.\nThe whole request body may be at most 25 MB (the server's own cap is\n 24 MiB). The limit applies to the encoded body, so the base64 form counts.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Images, audio and video","lvl3":""}},
6848
6856
  {"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,\nSubQ, Perplexity, Cloudflare Clef,\nOpenAI Decisions, then ScaleDown. TypeSafe counts with either of its keys,\nTYPESAFE_API_KEY or AI_GATEWAY_API_KEY. Laya and XOR each count only with\nboth their key and their base URL. SubQ counts with its key alone. Perplexity\ncounts with its key alone, which is shared with Perplexity's text provider. Cloudflare Clef counts only with both\nCLOUDFLARE_API_KEY and CLOUDFLARE_ACCOUNT_ID, the same two variables the\nWorkers AI text provider reads. OpenAI Decisions counts with its key alone,\nOPENAI_API_KEY, which the OpenAI chat provider reads too. ScaleDown counts with\nits key alone, SCALEDOWN_API_KEY, which no other provider reads. 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\".\nXOR's key and base URL, plus SUBQ_API_KEY: built-in features use XOR;\n SubQ runs only where a caller asks for provider: \"subq\".\nXOR's key and base URL, plus SCALEDOWN_API_KEY: built-in features use XOR;\n ScaleDown runs only where a caller asks for provider: \"scaledown\".\nXOR's key and base URL, plus a Perplexity key: built-in features use XOR;\n Perplexity runs only where a caller asks for\n provider: \"perplexity-decider\".\nXOR's key and base URL, plus CLOUDFLARE_API_KEY and\n CLOUDFLARE_ACCOUNT_ID: built-in features use XOR; Cloudflare Clef runs\n only where a caller asks for provider: \"cloudflare-clef\".\nOnly XOR's key and base URL: built-in features use XOR.\nAn XOR key with no base URL: XOR is not configured.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"When NeuroLink uses it","lvl3":""}},
6849
- {"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\nThe figures in this paragraph are measurements of one live LiteLLM route, not\nlimits of XOR: the model behind the route's name has not been confirmed as XOR,\nand its 262,144-token context length differs from the 250,000-token prefill\nabove. They were taken by sending requests to the route directly rather than\nthrough NeuroLink, which refuses a state of 1.2 million ASCII characters before\nany request, at about 315,000 estimated tokens. A state of about 215,000 tokens\n(1.2 million characters of English) was accepted, and one of about 430,000\ntokens was refused with a 500 whose text says the input is longer than the\nmodel's context length. The estimate is not exact in either direction. English\naveraged about 5.6 characters per token on the route, so the estimate\nover-counts it. Dense JSON averaged about 1.8, so the estimate under-counts it\nby more than half, and a dense state estimated under the limit can still be\nlonger than the server's context. A structured (non-string) state of 100,000\nChinese characters passed NeuroLink's local check and was refused by the\nserver, which counted it as 485,774 tokens.\n\nA state near the limit, or a lot of media, can therefore still be refused by the\nserver as too long. A 413, or a 5xx other than 503 whose message says the\ncontext was too long, arrives as max_tokens_exceeded and is not retried; every\nother reply is classified as in the Errors table below. Built-in consumers treat\na refusal as \"carry on as before\".\n\nA proxy may cap parallel requests.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Limits","lvl3":""}},
6850
- {"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.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Errors","lvl3":""}},
6851
- {"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:// for a server on this machine.\nThe XOR base URL must use https:// unless it points at this machine — the\n value is an http:// URL whose host is not localhost, 127.0.0.1 or [::1].\n A plain-http request would send the key and the state unencrypted. Put an\n https:// front on the deployment, or reach it through a port-forward to\n localhost.\nXOR answered with a redirect — the endpoint replied 301, 302, 307 or 308.\n The provider does not follow it and does not retry. Set the base URL to the\n address the redirect leads to.\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 the model you asked for (XOR_MODEL, or xor-latest by\n default; xor-omni for media). Add the model to the team; the instance works again without a\n 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.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Troubleshooting","lvl3":""}},
6857
+ {"objectID":"20a784de9dff5b3afa1f5ee7c0c60e268a7274b7e0c2a447aca431642f8fcabc","title":"Limits","url":"/docs/getting-started/providers/xor#limits","content":"The figures below were measured through a LiteLLM gateway in October 2026.\n\nEach model has its own window. xor-latest read a needle at the end of\n120,076 input tokens and refused 200,076 as longer than its 131,072-token\ncontext; xor-omni refuses past 32,768. NeuroLink allows 120,000 estimated\ntokens of state on xor-latest and 28,000 on xor-omni (and on any other model\nname), leaving room for the questions, an image and a clip, and refuses more\nbefore any network call, with max_tokens_exceeded. The size is an estimate,\nnot XOR's tokenizer: about four characters per token for ASCII text, and one\ntoken per character for other scripts. It over-counts English prose, and\nunder-counts dense JSON and digits, so a dense state estimated under the limit\ncan still be longer than the server's context.\n\nA state near the limit can therefore still be refused by the server as too long.\nA 413, or a 5xx other than 503 whose message says the context was too long,\narrives as max_tokens_exceeded and is not retried; every other reply is\nclassified as in the Errors table below. Built-in consumers treat a refusal as\n\"carry on as before\".\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. That order was observed; the rejected\nrequests were not confirmed as the cause. NeuroLink retries a 429 once.\n\n128 questions per request. The gateway refuses more, so decide() refuses\na longer map before any request, and tryDecide() (what every built-in consumer\nuses) splits it into batches of 128 and joins the answers. A choice takes 2 to\n64 options, counting unknown (64 plus unknown crashed the server with a 500),\nand a score 2 to 10 levels (the gateway refuses 11); past either cap the\nrequest is refused before any request, as invalid_request. Score levels must\nbe strings, objects or lists: numbers are refused by the gateway.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Limits","lvl3":""}},
6858
+ {"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| a 5xx wrapping the backend's 4xx (backend 400: …) | invalid_request — an image or clip it could not open | 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, or a question it cannot answer (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.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Errors","lvl3":""}},
6859
+ {"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:// for a server on this machine.\nThe XOR base URL must use https:// unless it points at this machine — the\n value is an http:// URL whose host is not localhost, 127.0.0.1 or [::1].\n A plain-http request would send the key and the state unencrypted. Put an\n https:// front on the deployment, or reach it through a port-forward to\n localhost.\nXOR answered with a redirect — the endpoint replied 301, 302, 307 or 308.\n The provider does not follow it and does not retry. Set the base URL to the\n address the redirect leads to.\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 the model you asked for (XOR_MODEL, or xor-latest by\n default; xor-omni for media). Add the model to the team; the instance works again without a\n restart.\nmax_tokens_exceeded — the state is over the model's window (120,000\n estimated tokens on xor-latest, 28,000 on xor-omni), the request has more\n than 128 questions, or the state, the questions and the media together are\n more than the model's context. Shorten the state, or use tryDecide() to split\n the questions.","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"Troubleshooting","lvl3":""}},
6852
6860
  {"objectID":"cdd6e7c83dfa23afa5cf0bb7d23081881600f72a5ac64ca56d383736c209f285","title":"See also","url":"/docs/getting-started/providers/xor#see-also","content":"TypeSafe (Jev) Provider Guide\nLaya Provider Guide\nPerplexity Decisions Provider Guide\nCloudflare Clef Provider Guide\nSubQ Provider Guide\nOpenAI Decisions Provider Guide\nScaleDown Provider Guide\nThe decide inference type\nXOR on Hugging Face","hierarchy":{"lvl0":"Getting Started","lvl1":"XOR Provider Guide","lvl2":"See also","lvl3":""}},
6853
6861
  {"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 ZAI_API_KE","hierarchy":{"lvl0":"Getting Started","lvl1":"Z.AI Provider Guide","lvl2":"","lvl3":""}},
6854
6862
  {"objectID":"ca9f10745365c71403f5bcba388684ac70aeb8f48c3d7b3fac85b832a4eead37","title":"Z.AI Provider Guide","url":"/docs/getting-started/providers/z-ai#zai-provider-guide","content":"Z.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).","hierarchy":{"lvl0":"Getting Started","lvl1":"Z.AI Provider Guide","lvl2":"Z.AI Provider Guide","lvl3":""}},
@@ -8556,19 +8564,19 @@
8556
8564
  {"objectID":"ce9a217a697d608230d795d2d3fdaf830ae8e403667bbab2280a9fd82c0c3702","title":"3. Authentication Failures","url":"/docs/observability/provider-status#3-authentication-failures","content":"Solution: Verify and update the API key environment variable (OPENAI_API_KEY in this case).","hierarchy":{"lvl0":"Observability","lvl1":"Provider Status Monitoring and Health Management","lvl2":"3. Authentication Failures","lvl3":""}},
8557
8565
  {"objectID":"baa7998e7800a556b1f9c93a93974de4b5796fdb60415881e25a1d033ca1706d","title":"Debugging Commands","url":"/docs/observability/provider-status#debugging-commands","content":"Debugging Commands","hierarchy":{"lvl0":"Observability","lvl1":"Provider Status Monitoring and Health Management","lvl2":"Debugging Commands","lvl3":""}},
8558
8566
  {"objectID":"e938db7055d0bdcf6a8aae0a76a7fc8aa1dc9496409d6c457136e5765a1cc5cc","title":"Conclusion","url":"/docs/observability/provider-status#conclusion","content":"NeuroLink's Provider Status Monitoring system provides enterprise-grade health management for AI provider infrastructure. With real-time monitoring, intelligent recommendations, and comprehensive analytics, it ensures optimal provider selection and proactive issue resolution.\n\nKey benefits include:\nProactive Issue Detection: Identify problems before they impact production\nIntelligent Provider Selection: Automatic optimization for performance and cost\nOperational Excellence: Complete visibility into AI infrastructure health\nDeveloper Productivity: Actionable recommendations reduce debugging time\n\nThis system transforms AI provider management from reactive troubleshooting to proactive optimization, ensuring reliable and efficient AI operations at enterprise scale.","hierarchy":{"lvl0":"Observability","lvl1":"Provider Status Monitoring and Health Management","lvl2":"Conclusion","lvl3":""}},
8559
- {"objectID":"3abc8984be035e0f7eaeeee65f8d8f2fab3f41ecc96355a2456d62170d25c333","title":"šŸ“Š Enterprise Telemetry Guide","url":"/docs/observability/telemetry","content":"šŸ“Š Enterprise Telemetry Guide\n\nAdvanced OpenTelemetry Integration for NeuroLink\n\nšŸ“‹ Overview\n\nNeuroLink includes optional OpenTelemetry integration for enterprise monitoring and observability. The telemetry system provides comprehensive insights into AI operations, performance metrics, and system health with zero overhead when disabled.\n\nšŸš€ Key Features\nāœ… Zero Overhead by Default - Telemetry disabled unless explicitly configured\nšŸ¤– AI Operation Tracking - Monitor text generation, token usage, costs, and response times\nšŸ”§ MCP Tool Monitoring - Track tool calls, execution time, and success rates\nšŸ“ˆ Performance Metrics - Response times, error rates, throughput monitoring\nšŸ” Distributed Tracing - Full request tracing across AI providers and services\nšŸ“Š Custom Dashboards - Grafana, Jaeger, and Prometheus integration\nšŸŽÆ Production Ready - Enterprise-grade monitoring for production deployments\n\nLangfuse Integration\n\nSet LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY and NeuroLink exports every AI operation to Langfuse. Pass observability: { langfuse: false } to disable. Full input and output are captured by default; use mask for sensitive data.\n\nIf your application already registers an OpenTelemetry TracerProvider, NeuroLink creates no provider but registers its Langfuse exporter when keys are configured; add createExporterSpanProcessor() to your provider (recommended), or add new LangfuseSpanProcessor() from @langfuse/otel with its own keys (no custom filter is needed because every NeuroLink observation span carries gen_ai.observation.type and infrastructure spans are not exported; NeuroLink's mask, chunking and retry do not apply to it). In serverless, call flushOpenTelemetry() before exit.\n\nThe full guide, including the migration table for removed fields and exports, is Observability.\n\nBasic Setup\n\nEnvironment Variables\n\n| Variable | Description | Default |\n| ----------------------------- | ---------------------------------------------------- | -------------- |\n| LANGFUSE_PUBLIC_KEY | Langfuse public key; with the secret key, enables it | - |\n| LANGFUSE_SECRET_KEY | Langfuse secret key | - |\n| LANGFUSE_BASE_URL | Langfuse host | Langfuse Cloud |\n| OTEL_EXPORTER_OTLP_ENDPOINT | OTLP collector endpoint for traces and metrics | - |\n| OTEL_SERVICE_NAME | Service name | neurolink-ai |\n\nšŸ”­ Proxy Telemetry (OTLP Triple-Signal Export)\n\nWhen running the NeuroLink proxy (neurolink proxy start), OpenTelemetry is automatically initialized. If OTEL_EXPORTER_OTLP_ENDPOINT is set, the proxy exports three signal types via OTLP HTTP:\n\n| Signal | Endpoint | What it captures |\n| ------- | ----------------------------------------- | ------------------------------------------------------------------ |\n| Traces | $OTEL_EXPORTER_OTLP_ENDPOINT/v1/traces | Per-request spans: receive → account selection → upstream → stream |\n| Metrics | $OTEL_EXPORTER_OTLP_ENDPOINT/v1/metrics | Request counters, latency histograms, token usage gauges |\n| Logs | $OTEL_EXPORTER_OTLP_ENDPOINT/v1/logs | Structured request log records with traceId/spanId correlation |\n\nConfiguration: Set OTEL_EXPORTER_OTLP_ENDPOINT (e.g., http://localhost:4318) before starting the proxy. The proxy defaults to service.name=neurolink-proxy.\n\nTrace correlation: Every JSONL request log entry includes traceId and spanId fields, enabling cross-signal correlation in backends like Jaeger, Grafana Tempo, or OpenObserve.\n\nCaller trace linkage: When a calling SDK already has an active trace, NeuroLink forwards W3C traceparent/tracestate headers and x-neurolink-session-id / x-neurolink-user-id / x-neurolink-conversation-id headers into the proxy so proxy spans can attach to the caller trace and preserve session-level attribution.\n\nProvider reuse: If a global TracerProvider is already registered (e.g., by the host application), NeuroLink registers nothing and uses it, avoiding \"already registered\" errors.\n\nOpenObserve dashboard: The maintained proxy dashboard definition lives in docs/assets/dashboards/neurolink-proxy-observability-dashboard.json. For how to read that dashboard and which streams it should use, see Claude Proxy Observability.\n\nLocal OpenObserve Setup For The Proxy\n\nFor a new local setup, use the repo-owned files in scripts/observability/ so the proxy dashboard does not depend on the Curator repo.\nOptional: copy scripts/observability/proxy-observability.env.example to scripts/observability/proxy-observability.env if the default ports or credentials clash with your machine.\nStart OpenObserve, start the OTEL collector, and import the dashboard:\nStart the proxy with the collector endpoint printed by the setup script. W","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"","lvl3":""}},
8567
+ {"objectID":"3abc8984be035e0f7eaeeee65f8d8f2fab3f41ecc96355a2456d62170d25c333","title":"šŸ“Š Enterprise Telemetry Guide","url":"/docs/observability/telemetry","content":"šŸ“Š Enterprise Telemetry Guide\n\nAdvanced OpenTelemetry Integration for NeuroLink\n\nšŸ“‹ Overview\n\nNeuroLink includes optional OpenTelemetry integration for enterprise monitoring and observability. The telemetry system provides insights into AI operations, performance metrics, and system health. Nothing is exported until Langfuse keys or an OTLP endpoint are configured, although importing the main package entry, or the ./rag, ./workflow, ./evaluation, ./processors or ./autoresearch entry, already loads the OpenTelemetry SDK (see Initialisation).\n\nšŸš€ Key Features\nāœ… Off by Default - Nothing is exported unless explicitly configured\nšŸ¤– AI Operation Tracking - Monitor text generation, token usage, costs, and response times\nšŸ”§ MCP Tool Monitoring - Track tool calls, execution time, and success rates\nšŸ“ˆ Performance Metrics - Response times and throughput; error counts for proxied traffic\nšŸ” Distributed Tracing - Full request tracing across AI providers and services\nšŸ“Š Custom Dashboards - Grafana, Jaeger, and Prometheus integration\nšŸŽÆ Production Ready - Enterprise-grade monitoring for production deployments\n\nLangfuse Integration\n\nSet LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY and NeuroLink exports every AI operation to Langfuse. Pass observability: { langfuse: false } to disable. Full input and output are captured by default; use mask for sensitive data.\n\nIf your application already registers an OpenTelemetry TracerProvider, NeuroLink creates no provider (and ignores OTEL_EXPORTER_OTLP_ENDPOINT, so add an OTLP exporter to your own providers) but registers its Langfuse exporter when keys are configured; add createExporterSpanProcessor() to your provider (recommended), or add new LangfuseSpanProcessor() from @langfuse/otel with its own keys (no custom filter is needed because every NeuroLink observation span carries gen_ai.observation.type and infrastructure spans are not exported; NeuroLink's mask, chunking and retry do not apply to it). In serverless, call flushOpenTelemetry() before exit.\n\nThe full guide, including the migration table for removed fields and exports, is Observability.\n\nBasic Setup\n\nEnvironment Variables\n\n| Variable | Description | Default |\n| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------- |\n| LANGFUSE_PUBLIC_KEY | Langfuse public key; with the secret key, enables it | - |\n| LANGFUSE_SECRET_KEY | Langfuse secret key | - |\n| LANGFUSE_BASE_URL | Langfuse host | Langfuse Cloud |\n| LANGFUSE_ENABLED | false, 0, off or no keeps Langfuse off while the keys are in the environment, in the proxy and for options that leave the keys there; langfuse: true, enabled: true or keys passed in code win over it | - |\n| OTEL_EXPORTER_OTLP_ENDPOINT | OTLP collector base URL for traces and metrics, only when NeuroLink owns the tracer provider (ignored beside a host provider) | - |\n| OTEL_SERVICE_NAME | Service name | ai-service (neurolink-proxy for the proxy) |\n| NEUROLINK_TELEMETRY_ENABLED | true records NeuroLink's metrics whatever else is set; a registered MeterProvider or an OTLP endpoint already turns them on | - |\n\nThe metric names, labels and when each one records are in Metrics.\n\nšŸ”­ Proxy Telemetry (OTLP Triple-Signal Export)\n\nWhen running the NeuroLink proxy (neurolink proxy start), OpenTelemetry is automatically initialized. If O","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"","lvl3":""}},
8560
8568
  {"objectID":"5bca8b67352628f2ead424686c839896c0c9191fbe9bc9ce9f2b4f1a34fda7b4","title":"šŸ“Š Enterprise Telemetry Guide","url":"/docs/observability/telemetry#-enterprise-telemetry-guide","content":"Advanced OpenTelemetry Integration for NeuroLink","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"šŸ“Š Enterprise Telemetry Guide","lvl3":""}},
8561
- {"objectID":"01dd892f6d8681fb916960f5512d67fc1772f13906d80f188160040a7d13352b","title":"šŸ“‹ Overview","url":"/docs/observability/telemetry#-overview","content":"NeuroLink includes optional OpenTelemetry integration for enterprise monitoring and observability. The telemetry system provides comprehensive insights into AI operations, performance metrics, and system health with zero overhead when disabled.","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"šŸ“‹ Overview","lvl3":""}},
8562
- {"objectID":"1560608dd386dd63e33cf859910b21ae292c6d2b3a5e5c20c6b758b509e91665","title":"šŸš€ Key Features","url":"/docs/observability/telemetry#-key-features","content":"āœ… Zero Overhead by Default - Telemetry disabled unless explicitly configured\nšŸ¤– AI Operation Tracking - Monitor text generation, token usage, costs, and response times\nšŸ”§ MCP Tool Monitoring - Track tool calls, execution time, and success rates\nšŸ“ˆ Performance Metrics - Response times, error rates, throughput monitoring\nšŸ” Distributed Tracing - Full request tracing across AI providers and services\nšŸ“Š Custom Dashboards - Grafana, Jaeger, and Prometheus integration\nšŸŽÆ Production Ready - Enterprise-grade monitoring for production deployments","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"šŸš€ Key Features","lvl3":""}},
8563
- {"objectID":"94eeb9415209595c93b61952b4d4a2bca198440a69ba4ac78dbba15557cfb2e9","title":"Langfuse Integration","url":"/docs/observability/telemetry#langfuse-integration","content":"Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY and NeuroLink exports every AI operation to Langfuse. Pass observability: { langfuse: false } to disable. Full input and output are captured by default; use mask for sensitive data.\n\nIf your application already registers an OpenTelemetry TracerProvider, NeuroLink creates no provider but registers its Langfuse exporter when keys are configured; add createExporterSpanProcessor() to your provider (recommended), or add new LangfuseSpanProcessor() from @langfuse/otel with its own keys (no custom filter is needed because every NeuroLink observation span carries gen_ai.observation.type and infrastructure spans are not exported; NeuroLink's mask, chunking and retry do not apply to it). In serverless, call flushOpenTelemetry() before exit.\n\nThe full guide, including the migration table for removed fields and exports, is Observability.","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"Langfuse Integration","lvl3":""}},
8564
- {"objectID":"3043ef1d5fa3c9cdc3f25bb1d2c4d5f3e650160b54e2b3af630105e58e61f560","title":"Environment Variables","url":"/docs/observability/telemetry#environment-variables","content":"| Variable | Description | Default |\n| ----------------------------- | ---------------------------------------------------- | -------------- |\n| LANGFUSE_PUBLIC_KEY | Langfuse public key; with the secret key, enables it | - |\n| LANGFUSE_SECRET_KEY | Langfuse secret key | - |\n| LANGFUSE_BASE_URL | Langfuse host | Langfuse Cloud |\n| OTEL_EXPORTER_OTLP_ENDPOINT | OTLP collector endpoint for traces and metrics | - |\n| OTEL_SERVICE_NAME | Service name | neurolink-ai |","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"Environment Variables","lvl3":""}},
8565
- {"objectID":"1196e9c4ad10ab88bcc4ee532ec941b2eebfe19b09eee44dafb9ee2d0b538e0b","title":"šŸ”­ Proxy Telemetry (OTLP Triple-Signal Export)","url":"/docs/observability/telemetry#-proxy-telemetry-otlp-triple-signal-export","content":"When running the NeuroLink proxy (neurolink proxy start), OpenTelemetry is automatically initialized. If OTEL_EXPORTER_OTLP_ENDPOINT is set, the proxy exports three signal types via OTLP HTTP:\n\n| Signal | Endpoint | What it captures |\n| ------- | ----------------------------------------- | ------------------------------------------------------------------ |\n| Traces | $OTEL_EXPORTER_OTLP_ENDPOINT/v1/traces | Per-request spans: receive → account selection → upstream → stream |\n| Metrics | $OTEL_EXPORTER_OTLP_ENDPOINT/v1/metrics | Request counters, latency histograms, token usage gauges |\n| Logs | $OTEL_EXPORTER_OTLP_ENDPOINT/v1/logs | Structured request log records with traceId/spanId correlation |\n\nConfiguration: Set OTEL_EXPORTER_OTLP_ENDPOINT (e.g., http://localhost:4318) before starting the proxy. The proxy defaults to service.name=neurolink-proxy.\n\nTrace correlation: Every JSONL request log entry includes traceId and spanId fields, enabling cross-signal correlation in backends like Jaeger, Grafana Tempo, or OpenObserve.\n\nCaller trace linkage: When a calling SDK already has an active trace, NeuroLink forwards W3C traceparent/tracestate headers and x-neurolink-session-id / x-neurolink-user-id / x-neurolink-conversation-id headers into the proxy so proxy spans can attach to the caller trace and preserve session-level attribution.\n\nProvider reuse: If a global TracerProvider is already registered (e.g., by the host application), NeuroLink registers nothing and uses it, avoiding \"already registered\" errors.\n\nOpenObserve dashboard: The maintained proxy dashboard definition lives in docs/assets/dashboards/neurolink-proxy-observability-dashboard.json. For how to read that dashboard and which streams it should use, see Claude Proxy Observability.","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"šŸ”­ Proxy Telemetry (OTLP Triple-Signal Export)","lvl3":""}},
8569
+ {"objectID":"01dd892f6d8681fb916960f5512d67fc1772f13906d80f188160040a7d13352b","title":"šŸ“‹ Overview","url":"/docs/observability/telemetry#-overview","content":"NeuroLink includes optional OpenTelemetry integration for enterprise monitoring and observability. The telemetry system provides insights into AI operations, performance metrics, and system health. Nothing is exported until Langfuse keys or an OTLP endpoint are configured, although importing the main package entry, or the ./rag, ./workflow, ./evaluation, ./processors or ./autoresearch entry, already loads the OpenTelemetry SDK (see Initialisation).","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"šŸ“‹ Overview","lvl3":""}},
8570
+ {"objectID":"1560608dd386dd63e33cf859910b21ae292c6d2b3a5e5c20c6b758b509e91665","title":"šŸš€ Key Features","url":"/docs/observability/telemetry#-key-features","content":"āœ… Off by Default - Nothing is exported unless explicitly configured\nšŸ¤– AI Operation Tracking - Monitor text generation, token usage, costs, and response times\nšŸ”§ MCP Tool Monitoring - Track tool calls, execution time, and success rates\nšŸ“ˆ Performance Metrics - Response times and throughput; error counts for proxied traffic\nšŸ” Distributed Tracing - Full request tracing across AI providers and services\nšŸ“Š Custom Dashboards - Grafana, Jaeger, and Prometheus integration\nšŸŽÆ Production Ready - Enterprise-grade monitoring for production deployments","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"šŸš€ Key Features","lvl3":""}},
8571
+ {"objectID":"94eeb9415209595c93b61952b4d4a2bca198440a69ba4ac78dbba15557cfb2e9","title":"Langfuse Integration","url":"/docs/observability/telemetry#langfuse-integration","content":"Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY and NeuroLink exports every AI operation to Langfuse. Pass observability: { langfuse: false } to disable. Full input and output are captured by default; use mask for sensitive data.\n\nIf your application already registers an OpenTelemetry TracerProvider, NeuroLink creates no provider (and ignores OTEL_EXPORTER_OTLP_ENDPOINT, so add an OTLP exporter to your own providers) but registers its Langfuse exporter when keys are configured; add createExporterSpanProcessor() to your provider (recommended), or add new LangfuseSpanProcessor() from @langfuse/otel with its own keys (no custom filter is needed because every NeuroLink observation span carries gen_ai.observation.type and infrastructure spans are not exported; NeuroLink's mask, chunking and retry do not apply to it). In serverless, call flushOpenTelemetry() before exit.\n\nThe full guide, including the migration table for removed fields and exports, is Observability.","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"Langfuse Integration","lvl3":""}},
8572
+ {"objectID":"3043ef1d5fa3c9cdc3f25bb1d2c4d5f3e650160b54e2b3af630105e58e61f560","title":"Environment Variables","url":"/docs/observability/telemetry#environment-variables","content":"| Variable | Description | Default |\n| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------- |\n| LANGFUSE_PUBLIC_KEY | Langfuse public key; with the secret key, enables it | - |\n| LANGFUSE_SECRET_KEY | Langfuse secret key | - |\n| LANGFUSE_BASE_URL | Langfuse host | Langfuse Cloud |\n| LANGFUSE_ENABLED | false, 0, off or no keeps Langfuse off while the keys are in the environment, in the proxy and for options that leave the keys there; langfuse: true, enabled: true or keys passed in code win over it | - |\n| OTEL_EXPORTER_OTLP_ENDPOINT | OTLP collector base URL for traces and metrics, only when NeuroLink owns the tracer provider (ignored beside a host provider) |","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"Environment Variables","lvl3":""}},
8573
+ {"objectID":"1196e9c4ad10ab88bcc4ee532ec941b2eebfe19b09eee44dafb9ee2d0b538e0b","title":"šŸ”­ Proxy Telemetry (OTLP Triple-Signal Export)","url":"/docs/observability/telemetry#-proxy-telemetry-otlp-triple-signal-export","content":"When running the NeuroLink proxy (neurolink proxy start), OpenTelemetry is automatically initialized. If OTEL_EXPORTER_OTLP_ENDPOINT is set, the proxy exports three signal types via OTLP HTTP:\n\n| Signal | Endpoint | What it captures |\n| ------- | ----------------------------------------- | ------------------------------------------------------------------ |\n| Traces | $OTEL_EXPORTER_OTLP_ENDPOINT/v1/traces | Per-request spans: receive → account selection → upstream → stream |\n| Metrics | $OTEL_EXPORTER_OTLP_ENDPOINT/v1/metrics | Request counters, latency histograms, token usage gauges |\n| Logs | $OTEL_EXPORTER_OTLP_ENDPOINT/v1/logs | Structured request log records with traceId/spanId correlation |\n\nConfiguration: Set OTEL_EXPORTER_OTLP_ENDPOINT (e.g., http://localhost:4318) before starting the proxy. The proxy defaults to service.name=neurolink-proxy.\n\nTrace correlation: Every JSONL request log entry includes traceId and spanId fields, enabling cross-signal correlation in backends like Jaeger, Grafana Tempo, or OpenObserve.\n\nCaller trace linkage: When a calling SDK already has an active trace, NeuroLink forwards W3C traceparent/tracestate headers and x-neurolink-session-id / x-neurolink-user-id / x-neurolink-conversation-id headers into the proxy so proxy spans can attach to the caller trace and preserve session-level attribution.\n\nProvider reuse: If a global TracerProvider is already registered (e.g., by the host application), NeuroLink registers nothing and uses it, avoiding \"already registered\" errors. OTEL_EXPORTER_OTLP_ENDPOINT then still receives the proxy's request logs, but not its traces or metrics, which go to the host's providers.\n\nOpenObserve dashboard: The maintained proxy dashboard definition lives in docs/assets/dashboards/neurolink-proxy-observability-dashboard.json.","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"šŸ”­ Proxy Telemetry (OTLP Triple-Signal Export)","lvl3":""}},
8566
8574
  {"objectID":"86edcd4d910e014c6ba3136e3beb5b3bac8acd20d33804cc1f315ce970a43458","title":"Local OpenObserve Setup For The Proxy","url":"/docs/observability/telemetry#local-openobserve-setup-for-the-proxy","content":"For a new local setup, use the repo-owned files in scripts/observability/ so the proxy dashboard does not depend on the Curator repo.\nOptional: copy scripts/observability/proxy-observability.env.example to scripts/observability/proxy-observability.env if the default ports or credentials clash with your machine.\nStart OpenObserve, start the OTEL collector, and import the dashboard:\nStart the proxy with the collector endpoint printed by the setup script. With the defaults, that is:\nOpen the UI at http://localhost:5080 and sign in with the configured OpenObserve credentials.\n\nUseful follow-up commands:\n\nWhen you are working from a local checkout instead of an installed CLI, pnpm run proxy:observability:* provides the same actions as repo shortcuts.\n\nWhat is machine-specific:\nOpenObserve URL, credentials, ports, container names, and volume names\nCompose project name if you intentionally run more than one local stack\nDashboard IDs and owners generated by OpenObserve when the dashboard is imported\n\nThe dashboard import helper strips dashboardId, owner, and created from the checked-in JSON before it calls the OpenObserve API, so those metadata fields do not need manual editing on a fresh machine.\n\nWhat is not machine-specific:\nThe dashboard query logic\nThe active streams neurolink_proxy and proxy_*\nThe proxy OTEL service name neurolink-proxy\nThe proxy log fields used for trace correlation and token analysis","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"Local OpenObserve Setup For The Proxy","lvl3":""}},
8567
8575
  {"objectID":"1d93cd70de084c5782b6189b3f415762ac33859e4cc82b99e48b659deed9bfc5","title":"Proxy Log Conventions In OpenObserve","url":"/docs/observability/telemetry#proxy-log-conventions-in-openobserve","content":"Final request-summary rows are exported to the neurolink_proxy log stream and are the rows dashboard request panels should use.\nRaw body captures share that same log stream with event.name=proxy.body_capture, so log-backed request panels should filter to request-summary rows, for example http_method IS NOT NULL.\nPer-upstream-attempt diagnostics stay local in ~/.neurolink/logs/proxy-attempts-*.jsonl; they are useful for debugging retries but are intentionally not part of the main dashboard counts.\nAdditional proxy OTEL metrics may appear when relevant traffic exists, including model-substitution counters and response-body histograms alongside the cache, request, retry, duration, and cost metrics already used by the dashboard.","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"Proxy Log Conventions In OpenObserve","lvl3":""}},
8568
- {"objectID":"a941e8b4933de87676af26c7c7b094af2f5fed8891ab5253411454ecaade5eb1","title":"Docker Compose with Jaeger","url":"/docs/observability/telemetry#docker-compose-with-jaeger","content":"Docker Compose with Jaeger","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"Docker Compose with Jaeger","lvl3":""}},
8569
- {"objectID":"7eb0866565462c132dd3cc9fa7c308f8ee6231bf6daf037b3dab796a9dd41d51","title":"AI Operation Metrics","url":"/docs/observability/telemetry#ai-operation-metrics","content":"Response Time: Time to generate AI responses\nToken Usage: Input/output tokens by provider and model\nCost Tracking: Estimated costs per operation\nError Rates: Failed AI requests by provider\nProvider Performance: Success rates and latency by provider","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"AI Operation Metrics","lvl3":""}},
8570
- {"objectID":"bd59daf6ceed52988c126b5f4b9791a9dd59dc3af55dd54cee5fadadbcfa0a41","title":"Sample Prometheus Queries","url":"/docs/observability/telemetry#sample-prometheus-queries","content":"Sample Prometheus Queries","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"Sample Prometheus Queries","lvl3":""}},
8571
- {"objectID":"1901741e79f4b3ed72eae0c8765fd89ceac268e0540844b0ec8367c4a4131dc1","title":"āœ… Quick Setup (5 minutes)","url":"/docs/observability/telemetry#-quick-setup-5-minutes","content":"Set the service name\nStart Jaeger (Local Development)\nConfigure Endpoint\nRun your app\n\n Tracing starts with the first new NeuroLink(); no initialization call is needed.\nView Traces\nOpen http://localhost:16686\nGenerate some AI requests\nSearch for traces in Jaeger UI","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"āœ… Quick Setup (5 minutes)","lvl3":""}},
8576
+ {"objectID":"a941e8b4933de87676af26c7c7b094af2f5fed8891ab5253411454ecaade5eb1","title":"Docker Compose with Jaeger","url":"/docs/observability/telemetry#docker-compose-with-jaeger","content":"OTEL_EXPORTER_OTLP_ENDPOINT is the collector's base URL: NeuroLink appends /v1/traces and /v1/metrics itself. Jaeger stores the traces only, so send the metrics to an OpenTelemetry Collector or another backend that accepts OTLP metrics.","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"Docker Compose with Jaeger","lvl3":""}},
8577
+ {"objectID":"7eb0866565462c132dd3cc9fa7c308f8ee6231bf6daf037b3dab796a9dd41d51","title":"AI Operation Metrics","url":"/docs/observability/telemetry#ai-operation-metrics","content":"Request volume: ai_requests_total, by provider and model\nResponse time: ai_request_duration_ms, a histogram in milliseconds\nToken usage: ai_tokens_used_total, total tokens per request\nCost tracking: ai_cost_usd_total, for models with known pricing\nMCP tool calls: mcp_tool_calls_total, by tool, success and duration bucket\n\nNeuroLink records no AI error-rate or connection metric: ai_provider_errors_total, connections_total and response_time_ms exist as instruments, but nothing in the package records them, and there is no active-connection gauge. For proxied traffic use the proxy's own metrics, such as proxy_errors_total; see Claude Proxy Observability.","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"AI Operation Metrics","lvl3":""}},
8578
+ {"objectID":"bd59daf6ceed52988c126b5f4b9791a9dd59dc3af55dd54cee5fadadbcfa0a41","title":"Sample Prometheus Queries","url":"/docs/observability/telemetry#sample-prometheus-queries","content":"The names below are the metric names NeuroLink creates; through an OTLP-to-Prometheus pipeline the histogram appears as _bucket, _sum and _count series.","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"Sample Prometheus Queries","lvl3":""}},
8579
+ {"objectID":"1901741e79f4b3ed72eae0c8765fd89ceac268e0540844b0ec8367c4a4131dc1","title":"āœ… Quick Setup (5 minutes)","url":"/docs/observability/telemetry#-quick-setup-5-minutes","content":"Set the service name\nStart Jaeger (Local Development)\nConfigure Endpoint (the base URL; NeuroLink appends /v1/traces and /v1/metrics)\nRun your app\n\n No initialization call is needed. Tracing starts at new NeuroLink({ observability }), at the first generate, stream or decide call, or at the first NeuroLink span; a plain new NeuroLink() starts nothing by itself. See Initialisation.\nView Traces\nOpen http://localhost:16686\nGenerate some AI requests\nSearch for traces in Jaeger UI","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"āœ… Quick Setup (5 minutes)","lvl3":""}},
8572
8580
  {"objectID":"790fed94c14846fea8574095e0237dcc2522ba7514f99d03d982c89d1ba6fbbf","title":"šŸ“š Additional Resources","url":"/docs/observability/telemetry#-additional-resources","content":"API Reference - Complete telemetry API documentation\nReal-time Services - WebSocket infrastructure guide\nPerformance Optimization - Optimization strategies\n\nReady for enterprise-grade AI monitoring with NeuroLink! šŸ“Š","hierarchy":{"lvl0":"Observability","lvl1":"šŸ“Š Enterprise Telemetry Guide","lvl2":"šŸ“š Additional Resources","lvl3":""}},
8573
8581
  {"objectID":"5122caad8c90eaa4c6454b2f4a4185aaee8933ab47a08806fad335486580d4df","title":"Migration Design: Remove AI SDK Google Dependencies","url":"/docs/plans/2026-01-01-remove-ai-sdk-google-design","content":"Migration Design: Remove AI SDK Google Dependencies\n\nDate: 2026-01-01\nBranch: feat/remove-ai-sdk-google\nStatus: Done — implemented in 076b9f4c3 (native @google/genai +\n@anthropic-ai/vertex-sdk). The Success Criteria below are all met.\n\nSummary\n\nRemove Vercel AI SDK wrappers (@ai-sdk/google, @ai-sdk/google-vertex) and migrate to the official unified Google SDK (@google/genai) for all Google AI Studio and Vertex AI Gemini models.\n\nMotivation\nReduce dependencies - Eliminate wrapper layer, use official SDK directly\nFuture-proof - @google-cloud/vertexai is deprecated (EOL June 2025); @google/genai is Google's recommended unified SDK\nConsistency - Native SDK path already exists for Gemini 3 with tools; extend pattern to all models\nBetter feature support - Direct access to thought_signature, extended thinking, and future Gemini features\n\nCurrent State Analysis\n\nDependencies to Remove\n\nDependencies to Keep\n\nProvider Files to Modify\n\n| File | Lines | Current Approach |\n| ------------------------------------- | ----- | ----------------------------------------- |\n| src/lib/providers/googleAiStudio.ts | ~1370 | AI SDK primary, native for Gemini 3+tools |\n| src/lib/providers/googleVertex.ts | ~3285 | AI SDK primary, native for Gemini 3+tools |\n\nArchitecture Design\n\nNew Unified Approach\n\nThe @google/genai SDK supports both authentication modes:\n\nStreaming Architecture\n\nReplace streamText() from AI SDK with native streaming:\n\nMessage Format Transformation\n\n| Vercel AI SDK Format | @google/genai Format |\n| ----------------------------------------------------- | ------------------------------------- |\n| { role: \"user\", content: [{ type: \"text\", text }] } | { role: \"user\", parts: [{ text }] } |\n| { type: \"image\", image: Buffer } | { inlineData: { mimeType, data } } |\n| { type: \"file\", data: Buffer } | { fileData: { mimeType, fileUri } } |\n\nTool Format Transformation\n\n| Vercel AI SDK Tool | @google/genai FunctionDeclaration |\n| -------------------------------------- | --------------------------------------------- |\n| { description, parameters, execute } | { name, description, parametersJsonSchema } |\n\nImplementation Plan\n\nPhase 1: Google AI Studio Provider (googleAiStudio.ts)\n\nStep 1.1: Remove AI SDK imports\n\nStep 1.2: Replace getAISDKModel() with native client\n\nStep 1.3: Convert message building\nAdd new convertToGoogleContents() method\nTransform CoreMessage[] to Content[] (Google format)\nHandle multimodal parts (text, images, PDFs)\n\nStep 1.4: Replace streaming implementation\nUse existing executeNativeGemini3Stream() as template\nRemove model detection logic (all models use native now)\nImplement unified streaming for all Gemini models\n\nStep 1.5: Replace generation implementation\nUse existing executeNativeGemini3Generate() as template\nExtend to all models\n\nPhase 2: Google Vertex Provider (googleVertex.ts)\n\nStep 2.1: Remove AI SDK imports\n\nNote: Anthropic Claude models via Vertex will still use @ai-sdk/google-vertex/anthropic since that's a separate API.\n\nStep 2.2: Replace Gemini model creation\n\nStep 2.3: Dual architecture\nKeep createVertexAnthropic() for Claude models\nUse @google/genai for all Gemini models\n\nStep 2.4: Unify streaming/generation paths\nReuse patterns from Google AI Studio\nHandle Vertex-specific authentication\n\nPhase 3: BaseProvider Updates\n\nStep 3.1: Update abstract methods\n\nStep 3.2: Make getAISDKModel() optional or provider-specific\nNon-Google providers still use AI SDK\nGoogle providers use native SDK directly\n\nPhase 4: Test Updates\n\nStep 4.1: Update test/setup.ts mocks\n\nStep 4.2: Update unit tests\ntest/unit/models/gemini-3-flash.test.ts\ntest/unit/thinking/thinking-configuration.test.ts\ntest/unit/providers/factory.test.ts\n\nStep 4.3: Run integration tests\n\nKey Transformations\nMessage Content Transformation\nTool Transformation\nThinking Configuration\n\nBackward Compatibility\n\nNo Breaking API Changes\nSDK public interface (neurolink.stream(), neurolink.generate()) unchanged\nCLI commands unchanged\nConfiguration unchanged\n\nInternal-Only Changes\nProvider implementation details\nSDK dependency swap\nTest mocks\n\nRisk Mitigation\n\n| Risk | Mitigation |\n| --------------------------- | ---------------------------------------------------------- |\n| Native SDK missing features | Existing Gemini 3 native implementation proves feasibility |\n| Test breakage | Comprehensive test suite with known patterns |\n| Authentication differences | @google/genai supports both API key and Vertex auth |\n| Performance regression | Native SDK eliminates wrapper overhead |\n\nTesting Strategy\nUnit Tests\nIntegration Tests\nEnd-to-End Tests\nManual Validation\n\nSuccess Criteria\nāœ… @ai-sdk/google and @ai-sdk/google-vertex removed from package.json\nāœ… Al","hierarchy":{"lvl0":"Plans","lvl1":"Migration Design: Remove AI SDK Google Dependencies","lvl2":"","lvl3":""}},
8574
8582
  {"objectID":"f22a2e266bd83093ebf4cc0b4fc75522e7ee728a0564d9d2f60d51bfbd6c3868","title":"Migration Design: Remove AI SDK Google Dependencies","url":"/docs/plans/2026-01-01-remove-ai-sdk-google-design#migration-design-remove-ai-sdk-google-dependencies","content":"Date: 2026-01-01\nBranch: feat/remove-ai-sdk-google\nStatus: Done — implemented in 076b9f4c3 (native @google/genai +\n@anthropic-ai/vertex-sdk). The Success Criteria below are all met.","hierarchy":{"lvl0":"Plans","lvl1":"Migration Design: Remove AI SDK Google Dependencies","lvl2":"Migration Design: Remove AI SDK Google Dependencies","lvl3":""}},