@juspay/neurolink 13.4.7 → 13.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2396,6 +2396,7 @@
2396
2396
  {"objectID":"6653ad7d7db43c9524583b9dadbf6368e9f153396ef5489c318c688f0fe95d24","title":"9. Streaming response shows as raw bytes","url":"/docs/features/claude-proxy-troubleshooting#9-streaming-response-shows-as-raw-bytes","content":"Cause: An earlier version of the Hono handler re-encoded the ReadableStream as a byte array instead of passing it through as a raw SSE stream.\n\nFix: This is fixed in the current version. The proxy returns a raw Response object for streaming requests, preserving the SSE format. If you encounter this, rebuild:","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy Troubleshooting","lvl2":"9. Streaming response shows as raw bytes","lvl3":""}},
2397
2397
  {"objectID":"c11829787189c82bd28cebb8f491b004b2778739e15705556ffaa098ed95dd7a","title":"10. Tools not working / \"0 chunks\"","url":"/docs/features/claude-proxy-troubleshooting#10-tools-not-working-0-chunks","content":"Cause: An earlier architecture had NeuroLink merging 68+ MCP tools with the client's tools, causing tool name conflicts and prefixing issues. Tool definitions were being modified or dropped in the merge.\n\nFix: This is fixed in the current version. The proxy uses passthrough mode for Claude-to-Claude requests: the raw request body is forwarded directly to Anthropic without any parsing, tool merging, or reconstruction. Tool definitions pass through exactly as Claude Code sent them.\n\nIf you are seeing tool issues:\nVerify the proxy is in passthrough mode (check logs for [proxy] POST /v1/messages -- passthrough requests do not show \"translation\" in the log).\nEnsure you are targeting a Claude model (passthrough is only for claude-* models).\nCheck that NEUROLINK_SKIP_MCP=true is set (prevents MCP tool injection).","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy Troubleshooting","lvl2":"10. Tools not working / \"0 chunks\"","lvl3":""}},
2398
2398
  {"objectID":"f5e4bc93c3248b16f666fc22de0855f134d5dde56d25280c5a7c2ae9abddc8ee","title":"11. Account not rotating on 429","url":"/docs/features/claude-proxy-troubleshooting#11-account-not-rotating-on-429","content":"Cause: The proxy uses fill-first routing by design. It keeps sending requests to one account until that account is rate-limited, then switches to the next.\n\nThis is expected behavior. Fill-first is optimal for Anthropic because:\nAnthropic's prompt caching is tied to the account/session. Spreading requests across accounts reduces cache hit rates.\nFill-first maximizes the benefit of each account's rate-limit window before moving on.\n\nOn a 429, the proxy applies exponential backoff to the current account (1s, 2s, 4s, 8s, ... up to 10 minutes) and immediately tries the next non-cooling account.\n\nTo verify rotation is working:","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy Troubleshooting","lvl2":"11. Account not rotating on 429","lvl3":""}},
2399
+ {"objectID":"a344f10565b8cb427dd5b903be65d7445973de5f1fbfdc7eeb523cae2a8b673f","title":"12. \"400 invalid_image_reference\" when sending an image","url":"/docs/features/claude-proxy-troubleshooting#12-400-invalid_image_reference-when-sending-an-image","content":"Cause: A request to a translated route (/v1/chat/completions, Gemini generateContent, or /v1/messages routed to a non-Anthropic provider) named its image as a local file path, an http:// URL or another scheme, or (Gemini) sent raw base64 in inlineData.data without an image/<subtype> mimeType, or with a length or = padding that is not valid base64. The proxy resolves image references before the SDK sees them and accepts only data: URIs and public https:// URLs. The answer is identical whether or not the file exists, and it never echoes the path.\n\nFix: Send the image inline as a data: URI (a Claude base64 source is converted for you), or host it at a public https:// URL that answers without a redirect. A Gemini client may send inlineData.data as raw base64 (standard or URL-safe) next to a mimeType such as image/png: the proxy converts it to a data: URI. If that request gets this answer, check inlineData.mimeType: it must be an image/<subtype> type with no space, comma or semicolon, and audio, PDF and untyped parts are not translated. Then check data itself: a length one character over a multiple of four, or = padding that does not match the length, is refused rather than forwarded. image_url_unfetchable means an https:// URL was refused (a private or non-public address, a redirect, an error status, or larger than the size limit while downloading); image_input_too_large means more than 20 images, a data: image over 50 MB, or data: images together over 100 MB. See Image references on translated requests.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy Troubleshooting","lvl2":"12. \"400 invalid_image_reference\" when sending an image","lvl3":""}},
2399
2400
  {"objectID":"f3a39e51eb140c1d4a2d4a3ca65428fa316193a4e5e8fa94ff94167e1c46fff2","title":"Enable debug logging","url":"/docs/features/claude-proxy-troubleshooting#enable-debug-logging","content":"This outputs detailed information for every request:","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy Troubleshooting","lvl2":"Enable debug logging","lvl3":""}},
2400
2401
  {"objectID":"787ea2a89999f69958b0dd99b8ae0410812df77cd2198c4e2b3d056c2147cdcd","title":"Check request logs","url":"/docs/features/claude-proxy-troubleshooting#check-request-logs","content":"The proxy writes structured JSONL logs to ~/.neurolink/logs/:\n\nEach log entry includes: timestamp, request ID, method, path, model, account label, response status, response time (ms), token usage, and OTel correlation fields (traceId, spanId).","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy Troubleshooting","lvl2":"Check request logs","lvl3":""}},
2401
2402
  {"objectID":"1df2cf3c6943377703033901df111cb673599219d7af3451c0e1b03cc7258d60","title":"Correlate logs with traces","url":"/docs/features/claude-proxy-troubleshooting#correlate-logs-with-traces","content":"When OTEL_EXPORTER_OTLP_ENDPOINT is set, every request log entry includes traceId and spanId fields. Use these to find the corresponding trace in your observability backend (Jaeger, Grafana Tempo, OpenObserve):","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy Troubleshooting","lvl2":"Correlate logs with traces","lvl3":""}},
@@ -2462,6 +2463,7 @@
2462
2463
  {"objectID":"7a743b6bb13366c925cd238d4853826a08f0b161659bcb0575d5b2e83709a2e5","title":"429 Rate Limit","url":"/docs/features/claude-proxy#429-rate-limit","content":"Treat top-level anthropic-ratelimit-unified-status: rejected as authoritative, even if sub-windows still say allowed.\nPrefer the rejected 5-hour or 7-day reset and otherwise use Retry-After.\nPersist the cooldown and rotate immediately for authoritative exhaustion.\nReturn the earliest recovery timestamp without another upstream request when all accounts are cooling.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"429 Rate Limit","lvl3":""}},
2463
2464
  {"objectID":"1522c22c8467bae6a5a51e9e4c4dca5792a7b45c457b0628ef5536d068d642b3","title":"401/402/403 Authentication Errors","url":"/docs/features/claude-proxy#401402403-authentication-errors","content":"OAuth accounts with refresh token: Serialize refresh, persist the new rotating token, and retry. A rejected refresh credential disables the account until re-authentication; transient refresh infrastructure errors cool and rotate.\nOAuth accounts without refresh token: Disable until re-authentication and rotate.\nAPI key accounts: Rotate after authentication failure.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"401/402/403 Authentication Errors","lvl3":""}},
2464
2465
  {"objectID":"b8f1c40c30e2fe7c3e03ca8b9857986dbce8dc17c382f69e1575e3c9b8e22c11","title":"400/422 Request Shape Error","url":"/docs/features/claude-proxy#400422-request-shape-error","content":"Detected via HTTP 422 status or invalid_request_error error type in the response body.\nNo retry or failover. These are client-side errors (malformed request, invalid parameters).\nReturn the error body directly to Claude Code.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"400/422 Request Shape Error","lvl3":""}},
2466
+ {"objectID":"afa3c85f9ffa8a82a4379702baa0f7270668e8c8cd1792431947aadd4e3ed0d1","title":"400 Refused Image Reference","url":"/docs/features/claude-proxy#400-refused-image-reference","content":"A translated request (OpenAI, Gemini, or Claude routed to another provider) named an image the proxy will not resolve: a local path, an http:// URL, another scheme, an https:// URL that could not be fetched safely, or (Gemini) raw base64 whose mimeType is not an image/<subtype> type.\nAnswered locally before any provider is contacted, with code invalid_image_reference, image_url_unfetchable or image_input_too_large. No retry and no fallback.\nSee Image references on translated requests.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"400 Refused Image Reference","lvl3":""}},
2465
2467
  {"objectID":"e1f4a02d90c3d80f462b4ab9d516b9be9e53d7f954539837078c9e19088d01fb","title":"404 Not Found","url":"/docs/features/claude-proxy#404-not-found","content":"Typically means the model is not available for this account.\nNo cooldown applied.\nReturn the error body immediately to the client (no failover to next account).","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"404 Not Found","lvl3":""}},
2466
2468
  {"objectID":"8ea0d60b8e33eeebfb825eb12ad9fcd0cdad610ee262ac5486db5d7fa3b8406b","title":"5xx / Transient Server Error","url":"/docs/features/claude-proxy#5xx-transient-server-error","content":"Transient errors (408, 500, 502, 503, 504, and Cloudflare 520-526/529).\nAlso matches 400 responses with api_error or overloaded_error types that wrap transient HTML content (e.g., Cloudflare error pages).\nApply bounded same-account retries, then rotate to the next account.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"5xx / Transient Server Error","lvl3":""}},
2467
2469
  {"objectID":"195dae3c0ea28fdcfeb0fbf39c456fd580652b07fa5f2ce81b341d4279621cf8","title":"All Accounts Exhausted","url":"/docs/features/claude-proxy#all-accounts-exhausted","content":"When every account is in a cooling state:\nWalk the fallback chain (if configured).\nEach fallback uses NeuroLink's stream() pipeline with the specified provider/model.\nIf all fallbacks also fail, return a 429 with Retry-After set to the earliest account recovery time.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"All Accounts Exhausted","lvl3":""}},
@@ -2473,6 +2475,7 @@
2473
2475
  {"objectID":"08067260c522d0f7b196b054e71ab3a4c733c6cedc590cfc5ff95f96e5d5d527","title":"Passthrough mode (Claude to Claude)","url":"/docs/features/claude-proxy#passthrough-mode-claude-to-claude","content":"When the target provider is anthropic (the default for any claude-* model), the proxy operates in passthrough mode:\nLoad allowed, enabled TokenStore accounts. Consider legacy or environment credentials only when no Anthropic TokenStore entries exist and the source is allowed.\nSelect the first non-cooling account according to the active routing strategy. With the default fill-first strategy, this is always the current primary account until it cools down.\nAuto-refresh the token if expiring within 5 minutes, sharing one in-flight refresh for concurrent requests.\nForward the raw request body via plain fetch() to https://api.anthropic.com/v1/messages?beta=true.\nSet authentication headers (Authorization: Bearer for OAuth, x-api-key for API keys).\nForward client headers as-is, preserving Claude Code's own request shape, then merge in required OAuth betas and trace headers when absent. The proxy extracts incoming traceparent and x-neurolink-* headers and injects outbound trace context plus x-claude-code-session-id when needed.\nFor streaming: verify the first chunk (bootstrap retry), then forward the stream. For non-streaming: return JSON.\n\nThis mode preserves the exact request format that Claude Code expects, including thinking blocks, cache control headers, and multi-turn tool use conversations. Rate-limit headers from Anthropic are passed through to the client — see Limit response headers below.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"Passthrough mode (Claude to Claude)","lvl3":""}},
2474
2476
  {"objectID":"fbc67516d545a8892da4fcf8e8d8f06fe29e480e22303b430eec8a70262aafa6","title":"Limit response headers","url":"/docs/features/claude-proxy#limit-response-headers","content":"Every proxy response — streaming, non-streaming, and errors including 429 — carries the account's limit state. There are two layers.\n\nVerbatim passthrough. Anthropic's own anthropic-ratelimit-* headers and retry-after are forwarded unchanged. This is deliberate: a proxied response looks identical to a direct one, so a client needs only a single parser for both. Which family is present depends on the serving account:\n\n| Account type | Headers | Semantics |\n| -------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |\n| OAuth / subscription | anthropic-ratelimit-unified-5h-*, -7d-*, -status, -overage-status | Utilization (0.0-1.0 of capacity used) plus a reset epoch. Anthropic publishes no absolute remaining count for subscription windows. |\n| API key | anthropic-ratelimit-requests-remaining/-limit, anthropic-ratelimit-tokens-remaining/-limit | Absolute remaining counts.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"Limit response headers","lvl3":""}},
2475
2477
  {"objectID":"dc6518f4c6b67f9a5231d413df258d79141e42dcc772f807732d7627f3502931","title":"Translation mode (Claude to other provider)","url":"/docs/features/claude-proxy#translation-mode-claude-to-other-provider","content":"When model routing directs to a non-Anthropic provider:\nParse the Claude request using parseClaudeRequest() -- extracts prompt, system prompt, images, tools, thinking config, and conversation history. The thinking type field is handled adaptively: both \"enabled\" (fixed budget) and \"adaptive\" (auto budget, mapped to thinkingLevel: \"medium\") are supported.\nCall neurolink.stream() with the target provider and model. Tools and conversation messages from the original request are passed through (not disabled).\nFor streaming: use ClaudeStreamSerializer to emit Claude-compatible SSE events (message_start, content_block_start, content_block_delta, content_block_stop, message_delta, message_stop).\nFor non-streaming: collect all text from the stream and call serializeClaudeResponse() to build a Claude Messages API response.\n\nIf the translated response model differs from the requested model, the proxy records that as a model-substitution metric (proxy_model_substitution_total) and adds the requested vs actual model attributes to the trace.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"Translation mode (Claude to other provider)","lvl3":""}},
2478
+ {"objectID":"230e1aa96cd18fcbcdb4072e0aa5815cc298b7e55dcdf7e805181551efa5c413","title":"Image references on translated requests","url":"/docs/features/claude-proxy#image-references-on-translated-requests","content":"Translation mode serves three request formats, and each carries images in its own slot:\n\n| Format | Endpoint | Image slot |\n| ------ | ----------------------------------------------------------------------- | --------------------------------------------------------- |\n| OpenAI | POST /v1/chat/completions | image_url.url of the latest user turn |\n| Gemini | POST /v1beta/models/<model>:generateContent, :streamGenerateContent | inlineData of the latest user turn (data, mimeType) |\n| Claude | POST /v1/messages (routed or falling back to another provider) | source.url of an image block (source.type: \"url\") |\n\nThe SDK resolves an image string by itself (an http(s) URL is fetched, anything else naming an existing file is read from disk), which is right for a program naming its own files and wrong for a request that arrived over a socket. So the proxy resolves every reference first and hands the SDK nothing but bytes it vetted:\n\n| Reference | What the proxy does |\n| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| data: URI | Accepted and passed through unchanged. Its length counts against the limits below.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"Image references on translated requests","lvl3":""}},
2476
2479
  {"objectID":"8a2ed66111704403b1e9a7c68b3214184ddc57877a63dc50ad6a25461d24c778","title":"OAuth cloaking","url":"/docs/features/claude-proxy#oauth-cloaking","content":"For OAuth-authenticated requests, the proxy applies transformations to make requests appear as standard Claude CLI traffic:\nUser-Agent: claude-cli/2.1.87 (external, sdk-cli)\nBeta headers: oauth-2025-04-20, claude-code-20250219, interleaved-thinking-2025-05-14, context-management-2025-06-27, prompt-caching-scope-2026-01-05, advanced-tool-use-2025-11-20, effort-2025-11-24\nIdentity headers: x-app: cli, anthropic-dangerous-direct-browser-access: true\nStainless SDK headers: x-stainless-runtime, x-stainless-lang, x-stainless-os, etc.\nBilling header: Injected into the system prompt as a deterministic Claude-Code-shaped billing block so prompt caching stays stable across requests\nUser ID: metadata.user_id is a JSON string with device_id, account_uuid, and session_id, cached per account/token seed and reused across requests\nTrace linkage: outbound requests include W3C trace headers and a stable x-claude-code-session-id when the proxy owns the request shape\n\nThe CloakingPipeline supports three modes:\n\n| Mode | Behavior |\n| -------- | ------------------------------------------------ |\n| auto | Apply cloaking only for OAuth accounts (default) |\n| always | Apply cloaking for all accounts |\n| never | Skip all cloaking |","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"OAuth cloaking","lvl3":""}},
2477
2480
  {"objectID":"95865db245f6ad8bafa53c81d03c73e6b1486683b1d545cae1670fc33b6c049f","title":"Cloaking plugins","url":"/docs/features/claude-proxy#cloaking-plugins","content":"The pipeline runs plugins in order field order:\nHeaderScrubber -- Removes or modifies headers that reveal proxy usage\nSessionIdentity -- Generates Claude-Code-shaped identity metadata with stable device_id and account_uuid\nSystemPromptInjector -- Adds billing and agent block to system prompts\nTlsFingerprint -- TLS fingerprint matching\nWordObfuscator -- Obfuscates identifiable patterns","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"Cloaking plugins","lvl3":""}},
2478
2481
  {"objectID":"8c04eba9378c5ec10f74e502b58467103bdefcde924c940884e1c6fe9d39d8c2","title":"Request logging","url":"/docs/features/claude-proxy#request-logging","content":"The proxy writes four complementary log families under ~/.neurolink/logs/:\nproxy-YYYY-MM-DD.jsonl -- final request summaries used for request counts, status trends, token totals, and dashboard panels\nproxy-attempts-YYYY-MM-DD.jsonl -- per-upstream-attempt diagnostics for retries, failover, and rate-limit debugging\nproxy-debug-YYYY-MM-DD.jsonl -- redacted body-capture index rows with phase, headers, file path, and response metadata\nbodies/YYYY-MM-DD/<request-id>/*.json.gz -- the corresponding redacted request and response body artifacts, stored compressed with 0o600 permissions\n\nFinal request summaries include request ID, method, path, model, account label, response status, response time, token usage, and traceId / spanId for trace correlation. Debug body captures are also emitted to OTLP logs as event.name=proxy.body_capture.\n\nRedaction: Sensitive headers and common JSON secret keys (authorization, access_token, refresh_token, api_key, etc.) are redacted before debug artifacts are written locally or emitted to OTLP.","hierarchy":{"lvl0":"Features","lvl1":"Claude Proxy","lvl2":"Request logging","lvl3":""}},
@@ -3123,7 +3126,8 @@
3123
3126
  {"objectID":"040d22ab2a366728173274f46d65882eb25dfb04fb45e61415a51e713b462248","title":"Tool latency","url":"/docs/features/livekit-voice-agent#tool-latency","content":"While a tool runs inside stream(), no audio is produced. To avoid dead air, instruct the model to speak a brief acknowledgment before tool use and/or emit a status event over a LiveKit data channel for the UI.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"Tool latency","lvl3":""}},
3124
3127
  {"objectID":"1a1d08cbc77d73fc2a6a758ad8c99929b2472265e02f4b3f41f595762f6fbda7","title":"Normal Turn","url":"/docs/features/livekit-voice-agent#normal-turn","content":"Browser publishes microphone audio to the room (WebRTC).\nLiveKit Agents detects the end of the user's turn (VAD + turn detection).\nSTT produces the transcript.\nllmNode calls neurolink.stream({ conversationId, input }).\nNeuroLink generates (running any tool calls internally) and streams tokens.\nTTS converts tokens to audio; LiveKit plays it back in the room.\nNeuroLink persists the turn to memory under conversationId.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"Normal Turn","lvl3":""}},
3125
3128
  {"objectID":"787c1375763b84292f38ba63c0c55c800c7a03c6ebf5cb75025fb37c70813b1e","title":"Barge-In / Abort","url":"/docs/features/livekit-voice-agent#barge-in-abort","content":"The assistant is speaking.\nLiveKit detects user speech and cancels the current llmNode.\nThe abort signal cancels the in-flight neurolink.stream() (and any active tool).\nThe session yields to the user.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"Barge-In / Abort","lvl3":""}},
3126
- {"objectID":"b2370bac0ed242ca8b2cf6a2ffa048e65c37f6ee5a8b1f79b966111c73e4228c","title":"Usage Example","url":"/docs/features/livekit-voice-agent#usage-example","content":"The integration is exposed under @juspay/neurolink/livekit. LiveKit dependencies are optional/peer dependencies and are only required when the voice agent is used.\n\nLiveKit runs each call as a Job in its own child process and re-imports the\nagent entry file there. Because a live object cannot cross that process\nboundary, the NeuroLink instance is built inside each job process via a\ncreateNeuroLink factory — not passed in from a parent. This is split into two\nfiles: the agent entry file (the default export) and a small launcher.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"Usage Example","lvl3":""}},
3129
+ {"objectID":"b2370bac0ed242ca8b2cf6a2ffa048e65c37f6ee5a8b1f79b966111c73e4228c","title":"Usage Example","url":"/docs/features/livekit-voice-agent#usage-example","content":"The integration is exposed under @juspay/neurolink/livekit.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"Usage Example","lvl3":""}},
3130
+ {"objectID":"72f3af68c3a6b88f11b359431bdd0ff2a331eb4bf5dcda592a2ccab6e7148624","title":"Requirements","url":"/docs/features/livekit-voice-agent#requirements","content":"The @juspay/neurolink/livekit entry requires @livekit/agents. Its\nfunctions take LiveKit's JobContext and run inside a worker where the SDK is\ninstalled, so its type declarations import types from @livekit/agents.\n@livekit/agents, the @livekit/agents-plugin-* packages and\n@livekit/rtc-node are optionalDependencies of @juspay/neurolink: a default\nnpm install installs them, and npm install --omit=optional does not.\nInstalled without optional dependencies. Type-checking a file that\n imports @juspay/neurolink/livekit fails with\n TS2307: Cannot find module '@livekit/agents'. Install @livekit/agents in\n the project that uses this entry, or do not import the entry. The other\n entries of the package do not need it.\n@types/node range. With strict on and skipLibCheck off, the LiveKit\n packages' own declarations compile cleanly on @types/node 22 and 24.19.2,\n the versions this was measured on. On @types/node 25 (25.9.9) and 26\n (26.6.5) two third-party declarations fail:\n node_modules/@livekit/agents/dist/worker.d.ts (TS2344) and\n node_modules/@livekit/rtc-node/dist/audio_stream.d.ts (TS2416). They are\n not NeuroLink's files and NeuroLink cannot fix them; set skipLibCheck: true,\n or stay on @types/node 22 or 24.19.2, in the project that imports this\n entry.\n\nLiveKit runs each call as a Job in its own child process and re-imports the\nagent entry file there. Because a live object cannot cross that process\nboundary, the NeuroLink instance is built inside each job process via a\ncreateNeuroLink factory — not passed in from a parent. This is split into two\nfiles: the agent entry file (the default export) and a small launcher.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"Requirements","lvl3":""}},
3127
3131
  {"objectID":"5a2b0572c54cfcb080ae9a71830313aa04f0d9c024a0a54cd89569d44cec0f26","title":"1a. Agent entry file (default export)","url":"/docs/features/livekit-voice-agent#1a-agent-entry-file-default-export","content":"defineVoiceAgent overrides the agent's llmNode so every turn calls\nneurolink.stream() with a per-room conversationId (NeuroLink owns history\nand tools), and wires abort-on-interrupt: when LiveKit cancels a turn the\nin-flight stream is aborted.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"1a. Agent entry file (default export)","lvl3":""}},
3128
3132
  {"objectID":"15e24c4996eb497e26a8f0c91708e965e904cf730e7d4745db49acd782888275","title":"1b. Launcher","url":"/docs/features/livekit-voice-agent#1b-launcher","content":"startVoiceAgentWorker resolves LiveKit connection settings from the\nenvironment (LIVEKIT_URL/LIVEKIT_API_KEY/LIVEKIT_API_SECRET) and registers\nthe worker; LiveKit dispatches one Job per room.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"1b. Launcher","lvl3":""}},
3129
3133
  {"objectID":"a046ce03438e9b67de497496afa3281477b8127e0c7402dbd588967a974c761d","title":"2. Mint a join token (host application, plain HTTP)","url":"/docs/features/livekit-voice-agent#2-mint-a-join-token-host-application-plain-http","content":"2. Mint a join token (host application, plain HTTP)","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"2. Mint a join token (host application, plain HTTP)","lvl3":""}},
@@ -3149,7 +3153,7 @@
3149
3153
  {"objectID":"a890643483526cae888fdc083535c83d5313ddef29c4a24f6d2771e94dcf3f71","title":"What the xai backend does","url":"/docs/features/livekit-voice-agent#what-the-xai-backend-does","content":"The LiveKit xAI plugin extends the OpenAI realtime model. Against a stub it\nconnects to <base URL>/realtime?model=<id> with Authorization: Bearer\n<XAI_API_KEY> (the key is never put in the URL) and its first event is a\nsession.update that carries the model, the voice, output_modalities:\n[\"audio\"], server-side voice-activity detection (threshold 0.5, 300 ms prefix\npadding, 200 ms silence, response creation and interruption on) and an input\ntranscription setting. The plugin's default endpoint is wss://api.x.ai/v1.\n\nTwo things in that list are the plugin's and are not verified against xAI:\nthe plugin sends gpt-4o-mini-transcribe, an OpenAI model name, as the input\n transcription model;\nthe plugin's own default model is grok-voice-think-fast-1.0. NeuroLink\n defaults to grok-voice-3.0, the flagship id xAI's documentation lists\n (together with the alias grok-voice-latest and a deprecated\n grok-voice-2.0). The plugin passes the id through; whether the service\n accepts it through the plugin has not been tested.\n\nA missing key stops the call before it connects. If XAI_API_KEY is unset\nthe plugin falls back to OPENAI_API_KEY, and a stub server showed that\nfallback sending the OpenAI key as the Bearer token to the configured xAI URL.\n(An empty value does not fall back: the plugin throws its own OpenAI API key is\nrequired error. A whitespace-only value is sent as the key.) NeuroLink\ntherefore refuses an unset, empty or whitespace-only key: it throws\nXAI_API_KEY is not set ... when the agent starts and again in\ncreateRealtimeLlm, and no connection is made.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"What the xai backend does","lvl3":""}},
3150
3154
  {"objectID":"b4b0a14667e072deb333f9771c062361a617e33da9a0142087c0ba9502d3e4f7","title":"How each part of the agent behaves on xai","url":"/docs/features/livekit-voice-agent#how-each-part-of-the-agent-behaves-on-xai","content":"| Part | On xai |\n| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Vertex ADC and GEMINI_API_KEY clearing | Not run. The xai backend never touches the Gemini environment. |\n| Data-channel event bridge, HITL, room context | Unchanged. They work on the LiveKit room and the agent session, not on a model. |\n| Session events (user transcript, assistant text, turn done, usage) | Unchanged code. Against a stub, OpenAI-style transcript and usage events reach the shapes the agent reads. |\n| MCP tool bridge and schema sanitizer | Unchanged. The sanitizer produces plain JSON Schema (the OpenAI-style API takes full JSON Schema), and the plugin forwards it as is. Keeping it is the safer choice: it drops $ref and unions that xAI has not been tried with. |\n| Conversation memory (recording and seeding) | Recording is shared by both backends.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"How each part of the agent behaves on xai","lvl3":""}},
3151
3155
  {"objectID":"f2c4f00431a110b4ec6b67653e157a21d79cc3d52751253260a13758eec90e66","title":"Still unknown without a real key","url":"/docs/features/livekit-voice-agent#still-unknown-without-a-real-key","content":"Whether xAI accepts the model ids above through the plugin; whether it accepts\nthe input transcription setting; whether it accepts the sanitized tool schemas\nand underscore tool names; the event names and ordering xAI really sends (the\nplugin line pinned here, 1.4.6, predates later xAI-specific handling in newer\nplugin releases); whether the seeded history is accepted; and how interruption,\ntruncation and tool calls behave. A live smoke test with a key supplied by the\nowner is the missing step.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"Still unknown without a real key","lvl3":""}},
3152
- {"objectID":"9eb38592a55bd3da33ee8a936b4648d18645a49909579f1fccb19844c8263ac7","title":"Troubleshooting","url":"/docs/features/livekit-voice-agent#troubleshooting","content":"VOICE_REALTIME_BACKEND must be \"gemini\" or \"xai\": fix the value, or unset it\n for gemini.\nXAI_API_KEY is not set: set it in the worker's environment. The check also\n rejects a whitespace-only value.\nXAI_REALTIME_BASE_URL must be a ws:// or wss:// URL: use a WebSocket URL,\n not an https:// one.\nXAI_REALTIME_BASE_URL is a ws:// URL to a host other than localhost, ... (or\n the same for xaiBaseURL): the URL would send XAI_API_KEY unencrypted to a\n host that is not loopback. Use a wss:// URL. Only if the path to that host is\n one you trust, set XAI_REALTIME_ALLOW_INSECURE=true, knowing the key then\n travels in cleartext.\nA call that will not start on the xai backend: confirm that VOICE_LIVE_MODEL\n and VOICE_S2S_VOICE hold xAI values and not leftover Gemini ones, since both\n variables are shared.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"Troubleshooting","lvl3":""}},
3156
+ {"objectID":"9eb38592a55bd3da33ee8a936b4648d18645a49909579f1fccb19844c8263ac7","title":"Troubleshooting","url":"/docs/features/livekit-voice-agent#troubleshooting","content":"VOICE_REALTIME_BACKEND must be \"gemini\" or \"xai\": fix the value, or unset it\n for gemini.\nXAI_API_KEY is not set: set it in the worker's environment. The check also\n rejects a whitespace-only value.\nXAI_REALTIME_BASE_URL must be a ws:// or wss:// URL (or the same for\n xaiBaseURL): use a WebSocket URL, not an https:// one.\nXAI_REALTIME_BASE_URL is a ws:// URL to a host other than localhost, ... (or\n the same for xaiBaseURL): the URL would send XAI_API_KEY unencrypted to a\n host that is not loopback. Use a wss:// URL. Only if the path to that host is\n one you trust, set XAI_REALTIME_ALLOW_INSECURE=true, knowing the key then\n travels in cleartext.\nA call that will not start on the xai backend: confirm that VOICE_LIVE_MODEL\n and VOICE_S2S_VOICE hold xAI values and not leftover Gemini ones, since both\n variables are shared.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"Troubleshooting","lvl3":""}},
3153
3157
  {"objectID":"7ffe7e32a7c2baea6c491d743617448eb70eeb4afe439960050efdc3ead18c48","title":"Implementation Plan","url":"/docs/features/livekit-voice-agent#implementation-plan","content":"The integration is built and validated in phases. Each phase is independently testable.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"Implementation Plan","lvl3":""}},
3154
3158
  {"objectID":"d4c9885eea3272e250fa1a0dbc1af9503b1a24f0217a82dd0c30d93bf6c5df31","title":"Phase 0 — Console-mode spike (no infrastructure)","url":"/docs/features/livekit-voice-agent#phase-0-console-mode-spike-no-infrastructure","content":"Build a minimal agent (Silero VAD + Deepgram STT + ElevenLabs TTS + llmNode → neurolink.stream()) and run it in console mode using the host machine's mic/speakers. Validates the NeuroLink brain loop, conversationId history, and a tool call — with no LiveKit server and no browser. Requires only STT/TTS and LLM credentials.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"Phase 0 — Console-mode spike (no infrastructure)","lvl3":""}},
3155
3159
  {"objectID":"9dddf98f952af643b5357bdefbcb5554a3ff2d9c33a16b0c432243fd68db5569","title":"Phase 1 — NeuroLink LiveKit module","url":"/docs/features/livekit-voice-agent#phase-1-neurolink-livekit-module","content":"Implement brain.ts, voiceAgent.ts, voiceAgentWorker.ts, tokens.ts, config.ts; add the @juspay/neurolink/livekit export and optional/peer dependencies. The worker factory accepts an external NeuroLink instance so a host application's registered tools are available. Wire abort-on-interrupt. Verify build, type-check, and lint.","hierarchy":{"lvl0":"Features","lvl1":"LiveKit Voice Agent — Real-Time Voice over WebRTC","lvl2":"Phase 1 — NeuroLink LiveKit module","lvl3":""}},
@@ -3785,7 +3789,7 @@
3785
3789
  {"objectID":"6e5f9a49dd3a55b0389b7f2a9e847f8480f4c1cd772c3da591e0e20e917ea5ed","title":"The share listener","url":"/docs/features/proxy-peer-sharing#the-share-listener","content":"A proxy that lends anything runs two listeners:\n\n| Port | Who it is for | Untokened request |\n| ---------------------- | ----------------------- | ----------------- |\n| Main (--port) | Your own client | Served, as always |\n| Share (--share-port) | Peers, through a tunnel | Refused |\n\nIt appears on its own the moment you issue the first grant and goes away when\nthe last one is revoked — no restart on either edge — and share create tells\nyou the port. Expose that one.\n\nThe split exists because the gate refuses untokened requests and your own client\nsends none. An address check could not stand in for it: cloudflared and every\nreverse proxy connect from 127.0.0.1, so tunnelled traffic is\nindistinguishable from local traffic by origin. The listener a connection was\naccepted on is not something a client can influence, which is why the decision\nis made there.\n\nNEUROLINK_PROXY_REQUIRE_GRANT=1 still exists and now means something narrower:\ngate the main port as well. Reach for it only when you bind 0.0.0.0 with\nnothing in front of it — it refuses your own client too, which is what made it\nawkward in the first place.","hierarchy":{"lvl0":"Features","lvl1":"Proxy Peer Sharing","lvl2":"The share listener","lvl3":""}},
3786
3790
  {"objectID":"92e5e510e92a4cf4d45a90d208c65ad705c1da31d34031543140474c70f7954f","title":"Exposure","url":"/docs/features/proxy-peer-sharing#exposure","content":"Any address a borrower can reach works — a domain you already own behind nginx or\nCaddy, a permanent named tunnel, a VPN hostname, a plain DNS record. Nothing in\nthe sharing path knows or cares how you got one.\n\nRecord it once and every link is minted against it:\n\nPoint that address at the share port, not the main one.\n\nshare url also probes that address and warns if it answers a request carrying\nno share token — the check matters more when you front the proxy yourself, since\nnothing else in your stack knows the gate is supposed to be on.\n\nneurolink proxy expose is a convenience for people who have no address yet: it\nwraps cloudflared, picks the share listener automatically, and refuses to open\na tunnel to a port that serves untokened requests. If you already have a domain,\nskip it entirely.","hierarchy":{"lvl0":"Features","lvl1":"Proxy Peer Sharing","lvl2":"Exposure","lvl3":""}},
3787
3791
  {"objectID":"dc8fe9cc83072284aacc3c7ec009908282feb617037b43920cf216e3f9671eaa","title":"Exposure with cloudflared","url":"/docs/features/proxy-peer-sharing#exposure-with-cloudflared","content":"neurolink proxy expose wraps cloudflared, and refuses to open a tunnel to a\nproxy that serves untokened requests — it checks by asking the proxy, not by\nreading configuration. --force overrides that, and should only be used when\nsomething in front of the tunnel already authenticates every request.\n\nQuick tunnels get a new URL on every restart, which rots every peer's\nconfiguration. Use a named tunnel (--named <tunnel>) for a peer you expect to\nkeep.","hierarchy":{"lvl0":"Features","lvl1":"Proxy Peer Sharing","lvl2":"Exposure with cloudflared","lvl3":""}},
3788
- {"objectID":"e4334c89b485facc220fc359dcd520ec9e8464dff64701db51837e14c83a4ec2","title":"What an exposed proxy still reveals","url":"/docs/features/proxy-peer-sharing#what-an-exposed-proxy-still-reveals","content":"On the share listener — or on the main port with\nNEUROLINK_PROXY_REQUIRE_GRANT=1 — /v1/messages and the Codex and\nOpenAI-compatible routes all require a share token. Two endpoints stay open on\npurpose, because a tunnel and a peer both need a liveness probe:\n/health — status, readiness, version, uptime. No account data.\n/status — counters, health and routing state, with account identity\n redacted: labels become account-1, account-2, and the primary-account\n block is blanked. A caller holding the update-control token sees the real\n values.\n\nA loopback allowlist would not have worked here: cloudflared runs on the same\nmachine and connects to 127.0.0.1, so tunnelled traffic arrives from loopback\nexactly like the operator's own CLI does. Separating the two by listener is\nwhat makes the distinction real.","hierarchy":{"lvl0":"Features","lvl1":"Proxy Peer Sharing","lvl2":"What an exposed proxy still reveals","lvl3":""}},
3792
+ {"objectID":"e4334c89b485facc220fc359dcd520ec9e8464dff64701db51837e14c83a4ec2","title":"What an exposed proxy still reveals","url":"/docs/features/proxy-peer-sharing#what-an-exposed-proxy-still-reveals","content":"On the share listener — or on the main port with\nNEUROLINK_PROXY_REQUIRE_GRANT=1 — /v1/messages and the Codex and\nOpenAI-compatible routes all require a share token. Two endpoints stay open on\npurpose, because a tunnel and a peer both need a liveness probe:\n/health — status, readiness, version, uptime. No account data.\n/status — counters, health and routing state, with account identity\n redacted: labels become account-1, account-2, and the primary-account\n block is blanked. A caller holding the update-control token sees the real\n values.\n\nA borrower's request reaches the same routes as yours, so image references are\nvetted for it too: on the OpenAI-compatible and Gemini routes (and on /v1/messages\nwhen a model is routed to another provider) a borrower can send data: URIs and\npublic https:// URLs, but a local path, an http:// URL or any other scheme is\nrefused with a 400 before anything is read or fetched. A borrower cannot make\nyour proxy read a file on your disk or call a service on your network.\n\nA loopback allowlist would not have worked here: cloudflared runs on the same\nmachine and connects to 127.0.0.1, so tunnelled traffic arrives from loopback\nexactly like the operator's own CLI does. Separating the two by listener is\nwhat makes the distinction real.","hierarchy":{"lvl0":"Features","lvl1":"Proxy Peer Sharing","lvl2":"What an exposed proxy still reveals","lvl3":""}},
3789
3793
  {"objectID":"752a4365e2038f8539a1c2e53cc1e34d7d1754ae660fec39639e158f92f4e347","title":"Files","url":"/docs/features/proxy-peer-sharing#files","content":"| Path | Owner | Contents |\n| -------------------------------------------- | -------- | ------------------------------------------------------------ |\n| ~/.neurolink/proxy-grants.json | lender | Grants, hashed tokens, policy, state, this node's public URL |\n| ~/.neurolink/proxy-share-ledger.json | lender | Coin spend and per-window buckets |\n| ~/.neurolink/proxy-share-audit.json | lender | Drift observations, streak, auto-pause marker |\n| ~/.neurolink/proxy-share-provisioning.json | lender | Outstanding split-PKCE challenges and single-use codes |\n| ~/.neurolink/proxy-peers.json | borrower | Peers, tokens, priorities, cooldowns, pending verifier |\n| ~/.neurolink/proxy-resident-grants.json | borrower | Leases governing credentials a lender provisioned here |\n| ~/.neurolink/proxy-share-receipts.json | lender | Signed receipts per grant, and the cumulative netted total |\n| ~/.neurolink/proxy-share-notes.json | issuer | Every coin note minted, and which have been redeemed |\n\nAll eight are 0o600 and written by atomic rename. Field-level detail is in the\nconfig reference.\n\nTokens are stored hashed on the lender's side. The raw token exists once, in\nthe output of share create — which is why share link cannot reprint one and\ntells you to rotate instead.","hierarchy":{"lvl0":"Features","lvl1":"Proxy Peer Sharing","lvl2":"Files","lvl3":""}},
3790
3794
  {"objectID":"c1a53699b19ffabe2eb7e0e20f27164c8900d82e292cf71b22843ae047c74382","title":"Scope","url":"/docs/features/proxy-peer-sharing#scope","content":"The gate covers every inbound proxy route, including the Codex and\nOpenAI-compatible surfaces. Account-level gates (reserve floor, window slice) and\ncoin settlement are implemented for the Anthropic engine; a borrowed request on\nanother engine is admitted or refused by the grant's request-level gates only.","hierarchy":{"lvl0":"Features","lvl1":"Proxy Peer Sharing","lvl2":"Scope","lvl3":""}},
3791
3795
  {"objectID":"f34fbae8a587159df344af5151d04a122347f2fa0e0c0592548b0e9b6ea10529","title":"Per-query RAG retrieval planning","url":"/docs/features/rag-retrieval-planning","content":"Per-query RAG retrieval planning\n\nRAGPipeline.query() has always resolved four knobs — topK, hybrid,\ngraph, rerank — from config, with an optional per-call override on each.\nNothing inspected the query itself: \"what is the refund window?\" (one precise\npassage, worth matching by an exact phrase) and \"how does billing relate to\nentitlements?\" (many passages, relationships across documents) got the same\nplan. RAGPipelineConfig.decide lets a\ndecision model answer all four for\nthe query in hand, in one request (~400ms on TypeSafe; on Perplexity each further\nquestion adds about 65 ms, up to 128, see\nits guide; on\nCloudflare Clef a small request took 0.3 to 1.0 s on 2026-10-03, see\nits guide).\n\nThe degradation contract. config.decide is optional and defaults to\nunset. Without it, query() behaves exactly as before — the configured\ndefaults and any explicit QueryOptions are all that determine topK,\nhybrid, graph and rerank. Setting plan: false on a call skips\nplanning for that one call even when decide is configured, without\ntouching anything else.\n\n⚠️ This is opt-in wiring, not automatic. Per-query planning lives on\nRAGPipeline. The rag: { files } shortcut on generate() / stream() does\nnot construct one, so that path keeps its fixed topK/hybrid/graph/\nrerank settings. To get planning you build the pipeline yourself and pass a\ndecide function, as below.\n\nWhat gets asked\n\nOne request always asks breadth — a score question rather than a raw\nnumber, because a decision model places a query on an ordered scale\nreliably and reads a digit string as text, not as a quantity to reason with:\n\n| Level | Criterion | topK multiplier |\n| ----- | ----------------------------------------------------------------------------------- | --------------- |\n| 0 | One specific fact, definition or value. A single passage answers it completely. | 0.5× |\n| 1 | A handful of related points — a procedure, a short comparison, one topic explained. | 1× |\n| 2 | Several distinct areas that each need their own supporting passage. | 1.5× |\n| 3 | A broad survey that needs evidence from across the whole corpus. | 2.5× |\n\nThe multiplier is applied to the pipeline's own configured defaultTopK and\nclamped to between 1 and 50. Below 0.5 confidence the breadth\nreading is dropped entirely and the configured topK stands untouched.\n\nhybrid, graph and rerank are each a plain boolean — and each is asked\nonly when the pipeline was actually configured with that capability.\nAsking about a knob nobody can act on would cost input tokens for nothing\nand invite the mistake of acting on it anyway:\nhybrid: \"This question contains exact terms that must be matched\n literally — an identifier, error code, file name, version number, API\n name, or a quoted phrase — rather than only a topic to match by meaning.\"\ngraph: \"Answering this requires connecting information that lives in\n separate documents, such as how two things relate, what depends on what,\n or tracing a chain across sources.\"\nrerank: \"This question is specific enough that the ORDER of the\n retrieved passages matters — a nearly-right passage would produce a wrong\n answer, so precision is worth an extra ranking pass.\"\n\nA deliberately lower bar than tool routing or compaction\n\nhybrid, graph and rerank are each read with the plain library default —\n0.5 probability, 0.4 confidence — not the stricter 0.6 confidence override\nthat tool routing and\nrelevance compaction both apply. This\nis a deliberate asymmetry, not an oversight: a wrong guess here is cheap (an\nextra ranking pass that didn't help, or a missed lexical match on an\notherwise-fine semantic result), where a wrong guess on a dropped tool\nserver or a dropped conversation message breaks the turn outright. The bar\nmatches the cost of being wrong.\n\nPrecedence: explicit always wins, capability is a hard ceiling\n\nAn explicit QueryOptions field always wins over the plan, per field —\nsetting hybrid: true on one call while letting topK be planned works\nexactly as written. And the plan can never turn on a capability the pipeline\nitself was not configured with: canHybrid/canGraph/canRerank gate\nwhether the question is even asked, so graph: true cannot appear in a plan\nfor a pipeline with no graph index. This is the same \"suggestion, not an\noverride of capability\" contract every other consumer of decide in this\ncodebase follows.\n\nWhat this is bad at\nBreadth is a rubric, not a real answer-length estimate. A level-3\n reading multiplies topK by 2.5× regardless of how large the corpus\n actually is — for a small collection that can mean requesting more\n passages than exist.\nThe three capability booleans don't interact. hybrid and rerank\n are decided independently even though a rerank pass changes how much a\n lexical-match boost from hybrid search actually matters; there's no joint\n reasoning about t","hierarchy":{"lvl0":"Features","lvl1":"Per-query RAG retrieval planning","lvl2":"","lvl3":""}},
@@ -8805,9 +8809,9 @@
8805
8809
  {"objectID":"5b2a78cbbf35a37b330aeb67341477591ea8ca5a9e287906d133647035620d85","title":"SDK: exercise both modes","url":"/docs/provider-integration/05-llamacpp#sdk-exercise-both-modes","content":"Use an available model ID for your account or loaded local model. The example\nbelow calls the two public surfaces independently; it does not pass a Vercel\nmodel adapter into NeuroLink.\n\nPer-call credentials.llamacpp accepts apiKey and baseURL; it overrides\ninstance/environment defaults. For local providers, omit the example's model\nto test discovery instead of specifying a fallback label.","hierarchy":{"lvl0":"Provider Integration","lvl1":"llama.cpp Native Provider","lvl2":"SDK: exercise both modes","lvl3":""}},
8806
8810
  {"objectID":"a78143ddc1b1dbcbd85a5471d486613aa59d4e38619f6005ba1cdd96619c1b27","title":"Tools and schemas","url":"/docs/provider-integration/05-llamacpp#tools-and-schemas","content":"Register tools with sdk.registerTool() or pass the package's tool() helper.\nUse the top-level schema option for structured generate() output; a prose\nanswer alone is not proof that the schema or a tool was honored. Tests should\nassert an executed tool result and validate structuredData, not merely check\nthat content is nonempty. Check stream tool execution independently.","hierarchy":{"lvl0":"Provider Integration","lvl1":"llama.cpp Native Provider","lvl2":"Tools and schemas","lvl3":""}},
8807
8811
  {"objectID":"21a6ad996293118682882c0f2287e77762f33598c4368bdc9f44d48f2f459621","title":"Verification and limitations","url":"/docs/provider-integration/05-llamacpp#verification-and-limitations","content":"Live checks require credentials or the relevant local server. Use deterministic\nHTTP stand-ins to verify wire bodies, retries, and cancellation even when live\naccess is unavailable. Record blocked/skipped live cells explicitly.\nNative architecture\nNew-provider test coverage","hierarchy":{"lvl0":"Provider Integration","lvl1":"llama.cpp Native Provider","lvl2":"Verification and limitations","lvl3":""}},
8808
- {"objectID":"b8cb066ddce0b63e9280d8b21b027dcbad45747eef4dc730e981e9d01397a780","title":"06 · Testing Strategy","url":"/docs/provider-integration/06-testing","content":"06 · Testing Strategy\n\nTest runner facts\nAll tests use tsx directly — there is no vitest/jest runner despite vitest.config.ts existing\nEach suite is a standalone test/continuous-test-suite-<name>.ts script; it logs pass/fail and exits with code 0/1\nThe orchestrator is test/continuous-test-suite.ts (run via pnpm test)\nAll tests read env vars; missing-credential is treated as skip (not fail) for provider tests\n\nFiles to edit\n\nA. test/continuous-test-suite-providers.ts\n\nUpdated 2026-08-15: the ALL_PROVIDERS array described below no longer\nexists — continuous-test-suite-providers.ts deleted it once provider\ncoverage moved to two more targeted places:\nStructural completeness (zero API keys, runs in CI on every commit):\n test/continuous-test-suite-provider-structure.ts\n (pnpm run test:provider-structure) asserts every value in the\n canonical AIProviderName enum resolves via ProviderFactory, and every\n src/lib/providers/*.ts module has exactly one dynamic import in\n providerRegistry.ts.\nLive per-provider generate/stream sweep (needs API keys, runs\n nightly via .github/workflows/live-matrix.yml, not a PR gate):\n test/continuous-test-suite-provider-matrix.ts (pnpm run test:matrix)\n iterates test/helpers/providerMatrix.ts's PROVIDERS map — see that\n file's header comment for the current provider count and coverage gaps.\n\nIf you're adding a new provider, add it to providerMatrix.ts's\nPROVIDERS map so test:matrix picks it up automatically;\ntest:provider-structure needs no edits — it derives its expectations from\nthe AIProviderName enum and the filesystem, not a hand-maintained list.\n\nB. test/continuous-test-suite-credentials.ts\n\nFor each new provider, add a per-call credential-override test block. Use the existing Mistral block as the template (search for \"mistral\" in the file):\n\nC. test/continuous-test-suite.ts (orchestrator)\n\nLikely no changes needed — it invokes per-domain suites which are already wired.\n\nD. package.json\n\nThe canonical entrypoint for the four new providers is pnpm run test:new-providers, which runs the dedicated suite test/continuous-test-suite-new-providers.ts (full feature surface per provider — generate, stream, tools, structured, reasoning, vision-where-supported, abort, timeout, per-call creds, telemetry, error formatting). The existing test:providers and test:credentials are still useful for cross-provider checks but the new suite is the primary coverage for the integration.\n\nNVIDIA NIM-specific test (the only one that needs custom assertions)\n\nNIM has unique behavior (extra-body params, retry-on-400). Add a focused test inside test/continuous-test-suite-providers.ts:\n\nSmoke test scripts\n\nAdd (optional) to test/:\n\nValidation pipeline\n\nAfter implementing each provider:\n\nFor the all-provider loop to actually exercise the new provider (vs skip), set the relevant env vars in your local .env. Local providers (LM Studio, llama.cpp) need their servers running; cloud providers (DeepSeek, NVIDIA NIM) need API keys.\n\nCI considerations\n\nCloud-provider tests with real API calls cost money. Two options:\nSkip in CI by default — current pattern. Tests only run if env vars are set; CI can set them as secrets for nightly runs.\nMock the AI SDK — adds complexity; not recommended for v1.\n\nLocal-provider tests are fine in CI ONLY if the runner has the local server installed and pre-loaded. For now, expect CI to skip LM Studio and llama.cpp tests.\n\nManual matrix (run before merging)\n\n| Provider | Test |\n| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| DeepSeek | --provider deepseek text gen, then --model deepseek-reasoner to verify reasoning |\n| NVIDIA NIM | --provider nvidia-nim default model, then --thinking-level high on a Nemotron model, then a model that does NOT support reasoning_budget (verify retry) |\n| LM Studio | Start server with a small Llama model, run text gen + tool gen (\"write me a 3-line poem\") |\n| LM Studio | Stop server, run again — verify error message is the friendly \"Open LM Studio app...\" form |\n| llama.cpp | Start server with --jinja, run text gen and tool gen |\n| llama.cpp | Stop server, run again — verify error message instructs ./llama-server -m ... |\n| All | Run pnpm run test:credentials — all four per-call override tests should pass or skip cleanly |","hierarchy":{"lvl0":"Provider Integration","lvl1":"06 · Testing Strategy","lvl2":"","lvl3":""}},
8809
- {"objectID":"1873fafe24c8b6683cf1684c1a5b60681950290d7320ddb36867773eff0c22ca","title":"Test runner facts","url":"/docs/provider-integration/06-testing#test-runner-facts","content":"All tests use tsx directly — there is no vitest/jest runner despite vitest.config.ts existing\nEach suite is a standalone test/continuous-test-suite-<name>.ts script; it logs pass/fail and exits with code 0/1\nThe orchestrator is test/continuous-test-suite.ts (run via pnpm test)\nAll tests read env vars; missing-credential is treated as skip (not fail) for provider tests","hierarchy":{"lvl0":"Provider Integration","lvl1":"06 · Testing Strategy","lvl2":"Test runner facts","lvl3":""}},
8810
- {"objectID":"23638e0388c9ea637242bcb2b1517c68de4b1eb0cccdb2c46632ebebf2f1aa7a","title":"A. test/continuous-test-suite-providers.ts","url":"/docs/provider-integration/06-testing#a-testcontinuous-test-suite-providersts","content":"Updated 2026-08-15: the ALL_PROVIDERS array described below no longer\nexists — continuous-test-suite-providers.ts deleted it once provider\ncoverage moved to two more targeted places:\nStructural completeness (zero API keys, runs in CI on every commit):\n test/continuous-test-suite-provider-structure.ts\n (pnpm run test:provider-structure) asserts every value in the\n canonical AIProviderName enum resolves via ProviderFactory, and every\n src/lib/providers/*.ts module has exactly one dynamic import in\n providerRegistry.ts.\nLive per-provider generate/stream sweep (needs API keys, runs\n nightly via .github/workflows/live-matrix.yml, not a PR gate):\n test/continuous-test-suite-provider-matrix.ts (pnpm run test:matrix)\n iterates test/helpers/providerMatrix.ts's PROVIDERS map — see that\n file's header comment for the current provider count and coverage gaps.\n\nIf you're adding a new provider, add it to providerMatrix.ts's\nPROVIDERS map so test:matrix picks it up automatically;\ntest:provider-structure needs no edits — it derives its expectations from\nthe AIProviderName enum and the filesystem, not a hand-maintained list.","hierarchy":{"lvl0":"Provider Integration","lvl1":"06 · Testing Strategy","lvl2":"A. test/continuous-test-suite-providers.ts","lvl3":""}},
8812
+ {"objectID":"b8cb066ddce0b63e9280d8b21b027dcbad45747eef4dc730e981e9d01397a780","title":"06 · Testing Strategy","url":"/docs/provider-integration/06-testing","content":"06 · Testing Strategy\n\nTest runner facts\nAll tests use tsx directly — there is no vitest/jest runner despite vitest.config.ts existing\nEach suite is a standalone test/continuous-test-suite-<name>.ts script; it logs pass/fail and exits with code 0/1\nThe orchestrator is test/continuous-test-suite.ts (run via pnpm test)\nAll tests read env vars; missing-credential is treated as skip (not fail) for provider tests. The nightly live matrix is the exception for a provider that HAS its secret: see \"The live gate\" below\n\nFiles to edit\n\nA. test/continuous-test-suite-providers.ts\n\nUpdated 2026-08-15: the ALL_PROVIDERS array described below no longer\nexists — continuous-test-suite-providers.ts deleted it once provider\ncoverage moved to two more targeted places:\nStructural completeness (zero API keys, runs in CI on every commit):\n test/continuous-test-suite-provider-structure.ts\n (pnpm run test:provider-structure) asserts every value in the\n canonical AIProviderName enum resolves via ProviderFactory, and every\n src/lib/providers/*.ts module has exactly one dynamic import in\n providerRegistry.ts.\nLive per-provider generate/stream sweep (needs API keys, runs\n nightly via .github/workflows/live-matrix.yml, not a PR gate):\n test/continuous-test-suite-provider-matrix.ts (pnpm run test:matrix)\n iterates test/helpers/providerMatrix.ts's PROVIDERS map — see that\n file's header comment for the current provider count and coverage gaps.\n\nIf you're adding a new provider, add it to providerMatrix.ts's\nPROVIDERS map so test:matrix picks it up automatically;\ntest:provider-structure needs no edits — it derives its expectations from\nthe AIProviderName enum and the filesystem, not a hand-maintained list.\n\nThe live gate. A provider that has its secret (or whose catalog JSON\nrecords a liveMatrix run) is expected to answer, so in test:matrix and\ntest:matrix:cli a credential error (401, 403, invalid key), an account error\n(402, billing, a tier gate) or a rate limit (429) fails its cell instead of\nskipping. The status is read from the error object (statusCode, then its\ncause chain) because the SDK often drops it from the message. A known-broken\nprovider is excused only by a dated entry in LIVE_GATE_ALLOWLIST\n(test/helpers/liveGate.ts): each entry has a reason and an expiry at most 14\ndays after it was added, and an expired entry excuses nothing. A provider with\nno secret and no recorded run is skipped in one quiet line without calling the\nSDK. The nightly sweep prints a coverage table and fails when fewer than\nMIN_PASSED_PROVIDERS providers passed (LIVE_MATRIX_ENFORCE_FLOOR=1, set by\nthe workflow). The gate throws HardFail (test/helpers/harness.ts), which\ntest() never downgrades: the failure message necessarily contains \"401\" or\n\"rate limit\", which isExpectedProviderError() would turn into a skip.\npnpm run test:live-gate shows all of this against a local server that answers\n200, 401, 402, 429 and 503.\n\nB. test/continuous-test-suite-credentials.ts\n\nFor each new provider, add a per-call credential-override test block. Use the existing Mistral block as the template (search for \"mistral\" in the file):\n\nC. test/continuous-test-suite.ts (orchestrator)\n\nLikely no changes needed — it invokes per-domain suites which are already wired.\n\nD. package.json\n\nThe canonical entrypoint for the four new providers is pnpm run test:new-providers, which runs the dedicated suite test/continuous-test-suite-new-providers.ts (full feature surface per provider — generate, stream, tools, structured, reasoning, vision-where-supported, abort, timeout, per-call creds, telemetry, error formatting). The existing test:providers and test:credentials are still useful for cross-provider checks but the new suite is the primary coverage for the integration.\n\nNVIDIA NIM-specific test (the only one that needs custom assertions)\n\nNIM has unique behavior (extra-body params, retry-on-400). Add a focused test inside test/continuous-test-suite-providers.ts:\n\nSmoke test scripts\n\nAdd (optional) to test/:\n\nValidation pipeline\n\nAfter implementing each provider:\n\nFor the all-provider loop to actually exercise the new provider (vs skip), set the relevant env vars in your local .env. Local providers (LM Studio, llama.cpp) need their servers running; cloud providers (DeepSeek, NVIDIA NIM) need API keys.\n\nCI considerations\n\nCloud-provider tests with real API calls cost money. Two options:\nSkip in CI by default — current pattern. Tests only run if env vars are set; CI can set them as secrets for nightly runs.\nMock the AI SDK — adds complexity; not recommended for v1.\n\nLocal-provider tests are fine in CI ONLY if the runner has the local server installed and pre-loaded. For now, expect CI to skip LM Studio and llama.cpp tests.\n\nManual matrix (run before merging)\n\n| Provider | Test |\n| ---------- | -----------------------------------","hierarchy":{"lvl0":"Provider Integration","lvl1":"06 · Testing Strategy","lvl2":"","lvl3":""}},
8813
+ {"objectID":"1873fafe24c8b6683cf1684c1a5b60681950290d7320ddb36867773eff0c22ca","title":"Test runner facts","url":"/docs/provider-integration/06-testing#test-runner-facts","content":"All tests use tsx directly — there is no vitest/jest runner despite vitest.config.ts existing\nEach suite is a standalone test/continuous-test-suite-<name>.ts script; it logs pass/fail and exits with code 0/1\nThe orchestrator is test/continuous-test-suite.ts (run via pnpm test)\nAll tests read env vars; missing-credential is treated as skip (not fail) for provider tests. The nightly live matrix is the exception for a provider that HAS its secret: see \"The live gate\" below","hierarchy":{"lvl0":"Provider Integration","lvl1":"06 · Testing Strategy","lvl2":"Test runner facts","lvl3":""}},
8814
+ {"objectID":"23638e0388c9ea637242bcb2b1517c68de4b1eb0cccdb2c46632ebebf2f1aa7a","title":"A. test/continuous-test-suite-providers.ts","url":"/docs/provider-integration/06-testing#a-testcontinuous-test-suite-providersts","content":"Updated 2026-08-15: the ALL_PROVIDERS array described below no longer\nexists — continuous-test-suite-providers.ts deleted it once provider\ncoverage moved to two more targeted places:\nStructural completeness (zero API keys, runs in CI on every commit):\n test/continuous-test-suite-provider-structure.ts\n (pnpm run test:provider-structure) asserts every value in the\n canonical AIProviderName enum resolves via ProviderFactory, and every\n src/lib/providers/*.ts module has exactly one dynamic import in\n providerRegistry.ts.\nLive per-provider generate/stream sweep (needs API keys, runs\n nightly via .github/workflows/live-matrix.yml, not a PR gate):\n test/continuous-test-suite-provider-matrix.ts (pnpm run test:matrix)\n iterates test/helpers/providerMatrix.ts's PROVIDERS map — see that\n file's header comment for the current provider count and coverage gaps.\n\nIf you're adding a new provider, add it to providerMatrix.ts's\nPROVIDERS map so test:matrix picks it up automatically;\ntest:provider-structure needs no edits — it derives its expectations from\nthe AIProviderName enum and the filesystem, not a hand-maintained list.\n\nThe live gate. A provider that has its secret (or whose catalog JSON\nrecords a liveMatrix run) is expected to answer, so in test:matrix and\ntest:matrix:cli a credential error (401, 403, invalid key), an account error\n(402, billing, a tier gate) or a rate limit (429) fails its cell instead of\nskipping. The status is read from the error object (statusCode, then its\ncause chain) because the SDK often drops it from the message. A known-broken\nprovider is excused only by a dated entry in LIVE_GATE_ALLOWLIST\n(test/helpers/liveGate.ts): each entry has a reason and an expiry at most 14\ndays after it was added, and an expired entry excuses nothing. A provider with\nno secret and no recorded run is skipped in one quiet line without calling the\nSDK.","hierarchy":{"lvl0":"Provider Integration","lvl1":"06 · Testing Strategy","lvl2":"A. test/continuous-test-suite-providers.ts","lvl3":""}},
8811
8815
  {"objectID":"d803c12157f219816b953463b178cb5cf252ef77512a5b07209b656d48dc0cc0","title":"B. test/continuous-test-suite-credentials.ts","url":"/docs/provider-integration/06-testing#b-testcontinuous-test-suite-credentialsts","content":"For each new provider, add a per-call credential-override test block. Use the existing Mistral block as the template (search for \"mistral\" in the file):","hierarchy":{"lvl0":"Provider Integration","lvl1":"06 · Testing Strategy","lvl2":"B. test/continuous-test-suite-credentials.ts","lvl3":""}},
8812
8816
  {"objectID":"8b4752157a293b3334713fce56b05b685fd51aa93bdc0c6399af30b7d5f02345","title":"C. test/continuous-test-suite.ts (orchestrator)","url":"/docs/provider-integration/06-testing#c-testcontinuous-test-suitets-orchestrator","content":"Likely no changes needed — it invokes per-domain suites which are already wired.","hierarchy":{"lvl0":"Provider Integration","lvl1":"06 · Testing Strategy","lvl2":"C. test/continuous-test-suite.ts (orchestrator)","lvl3":""}},
8813
8817
  {"objectID":"f08e02ae4ccb888a927974d615dfd84fe71e7dfac002262f0a519926f76b57ee","title":"D. package.json","url":"/docs/provider-integration/06-testing#d-packagejson","content":"The canonical entrypoint for the four new providers is pnpm run test:new-providers, which runs the dedicated suite test/continuous-test-suite-new-providers.ts (full feature surface per provider — generate, stream, tools, structured, reasoning, vision-where-supported, abort, timeout, per-call creds, telemetry, error formatting). The existing test:providers and test:credentials are still useful for cross-provider checks but the new suite is the primary coverage for the integration.","hierarchy":{"lvl0":"Provider Integration","lvl1":"06 · Testing Strategy","lvl2":"D. package.json","lvl3":""}},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "13.4.7",
3
+ "version": "13.4.8",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
6
6
  "author": {
@@ -138,6 +138,7 @@
138
138
  "test:new-providers": "pnpm exec tsx test/continuous-test-suite-new-providers.ts",
139
139
  "test:matrix": "pnpm exec tsx test/continuous-test-suite-provider-matrix.ts",
140
140
  "test:matrix:cli": "pnpm exec tsx test/continuous-test-suite-provider-matrix-cli.ts",
141
+ "test:live-gate": "pnpm exec tsx test/continuous-test-suite-live-gate.ts",
141
142
  "test:thinking-config": "pnpm exec tsx test/continuous-test-suite-sdk-thinking-config.ts",
142
143
  "test:gemini-thinking-level": "pnpm exec tsx test/continuous-test-suite-gemini-thinking-level.ts",
143
144
  "test:gemini-thinking-level-ai-studio": "pnpm exec tsx test/continuous-test-suite-gemini-thinking-level-ai-studio.ts",
@@ -214,6 +215,7 @@
214
215
  "test:proxy-reliability": "node scripts/run-proxy-reliability.mjs",
215
216
  "test:proxy-telemetry": "pnpm exec tsx test/continuous-test-suite-proxy-telemetry.ts",
216
217
  "test:proxy-restart": "pnpm exec tsx test/continuous-test-suite-proxy-restart.ts",
218
+ "test:proxy-image-input": "pnpm exec tsx test/continuous-test-suite-proxy-image-input.ts",
217
219
  "test:codex": "pnpm exec tsx test/continuous-test-suite-codex.ts",
218
220
  "test:bugfixes": "pnpm exec tsx test/continuous-test-suite-bugfixes.ts",
219
221
  "test:json-e2e": "pnpm exec tsx test/continuous-test-suite-json-e2e.ts",