@juspay/neurolink 12.47.1 → 12.47.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,15 @@ export declare class AnthropicProvider extends BaseProvider {
11
11
  private readonly authMethod;
12
12
  private readonly subscriptionTier;
13
13
  private readonly enableBetaFeatures;
14
+ /**
15
+ * Where requests go when not to `api.anthropic.com`: the credentials'
16
+ * `baseURL`, else the config's, else `ANTHROPIC_BASE_URL` — normalized once
17
+ * (no trailing slash, no `/vN` suffix: the SDK appends `/v1` itself).
18
+ * Undefined means the vendor's own endpoint; every "proxy in use" decision
19
+ * reads this, never the environment directly, so a per-instance gateway is
20
+ * honoured the same way the environment one always was.
21
+ */
22
+ private readonly baseURL;
14
23
  private oauthToken;
15
24
  private lastResponseMetadata;
16
25
  private usageInfo;
@@ -25,6 +34,7 @@ export declare class AnthropicProvider extends BaseProvider {
25
34
  constructor(modelName?: string, sdk?: unknown, config?: AnthropicProviderConfig, credentials?: {
26
35
  apiKey?: string;
27
36
  oauthToken?: string;
37
+ baseURL?: string;
28
38
  });
29
39
  /**
30
40
  * Get authentication headers based on current auth method and configuration.
@@ -58,6 +58,31 @@ const getAnthropicApiKey = () => {
58
58
  const getDefaultAnthropicModel = () => {
59
59
  return getProviderModel("ANTHROPIC_MODEL", AnthropicModels.CLAUDE_SONNET_4_6);
60
60
  };
61
+ /**
62
+ * The official Anthropic SDK builds `${baseURL}/v1/messages` itself, so a
63
+ * version-suffixed base URL — the form the previous @ai-sdk/anthropic
64
+ * implementation REQUIRED (`https://api.anthropic.com/v1`) — would double up
65
+ * as `/v1/v1/messages`. Normalize the inverse way: strip trailing slashes and
66
+ * a trailing `/vN` segment, so both historical forms keep working whether the
67
+ * URL comes from the credentials, the config or `ANTHROPIC_BASE_URL`. Blank
68
+ * means "the vendor's endpoint".
69
+ */
70
+ function normalizeAnthropicBaseURL(raw) {
71
+ const value = raw?.trim();
72
+ if (!value) {
73
+ return undefined;
74
+ }
75
+ const trimmed = value.replace(/\/+$/, "");
76
+ const stripped = trimmed.replace(/\/v\d+$/, "");
77
+ if (stripped !== trimmed) {
78
+ logger.debug("[AnthropicProvider] Stripping the version suffix from the base URL — " +
79
+ "the official Anthropic SDK appends /v1 to the base URL itself.", {
80
+ baseURL: redactUrlCredentials(value),
81
+ rewrittenTo: redactUrlCredentials(stripped),
82
+ });
83
+ }
84
+ return stripped;
85
+ }
61
86
  const streamTracer = trace.getTracer("neurolink.provider.anthropic");
62
87
  /**
63
88
  * Get OAuth token from stored credentials file or environment.
@@ -476,6 +501,15 @@ export class AnthropicProvider extends BaseProvider {
476
501
  authMethod;
477
502
  subscriptionTier;
478
503
  enableBetaFeatures;
504
+ /**
505
+ * Where requests go when not to `api.anthropic.com`: the credentials'
506
+ * `baseURL`, else the config's, else `ANTHROPIC_BASE_URL` — normalized once
507
+ * (no trailing slash, no `/vN` suffix: the SDK appends `/v1` itself).
508
+ * Undefined means the vendor's own endpoint; every "proxy in use" decision
509
+ * reads this, never the environment directly, so a per-instance gateway is
510
+ * honoured the same way the environment one always was.
511
+ */
512
+ baseURL;
479
513
  oauthToken;
480
514
  lastResponseMetadata = null;
481
515
  usageInfo = null;
@@ -511,12 +545,16 @@ export class AnthropicProvider extends BaseProvider {
511
545
  const subscriptionTier = config?.subscriptionTier ??
512
546
  (authMethod === "oauth" ? detectSubscriptionTier(oauthToken) : "api");
513
547
  const targetModel = modelName || getDefaultAnthropicModel();
548
+ // Resolved before super(): the tier check below needs it, and the
549
+ // credentials win over the config, which wins over the environment —
550
+ // the same precedence `apiKey` has.
551
+ const baseURL = normalizeAnthropicBaseURL(credentials?.baseURL ?? config?.baseURL ?? process.env.ANTHROPIC_BASE_URL);
514
552
  // Determine effective model based on tier access.
515
- // Skip tier validation when a proxy is in use (ANTHROPIC_BASE_URL is set)
516
- // — the proxy handles model access and auth, so the SDK should pass
517
- // the requested model through without downgrading.
553
+ // Skip tier validation when a proxy is in use (a base URL is set) — the
554
+ // proxy handles model access and auth, so the SDK should pass the
555
+ // requested model through without downgrading.
518
556
  let effectiveModel = targetModel;
519
- const usingProxy = !!process.env.ANTHROPIC_BASE_URL;
557
+ const usingProxy = baseURL !== undefined;
520
558
  if (!usingProxy &&
521
559
  subscriptionTier !== "api" &&
522
560
  !isModelAvailableForTier(targetModel, subscriptionTier)) {
@@ -533,6 +571,7 @@ export class AnthropicProvider extends BaseProvider {
533
571
  // Store computed values
534
572
  this.oauthToken = oauthToken;
535
573
  this.subscriptionTier = subscriptionTier;
574
+ this.baseURL = baseURL;
536
575
  // Use the auth method already resolved above (before tier computation)
537
576
  this.authMethod = authMethod;
538
577
  // Build headers based on auth method and subscription tier
@@ -570,6 +609,9 @@ export class AnthropicProvider extends BaseProvider {
570
609
  // The claude-code-20250219 beta header triggers "credential only for Claude Code" error
571
610
  client = new Anthropic({
572
611
  apiKey: "oauth-authenticated", // Placeholder, actual auth is in fetch wrapper
612
+ // The same gateway the API-key branch honours: without it, OAuth
613
+ // credentials that also name a base URL would still reach the vendor.
614
+ ...(this.baseURL && { baseURL: this.baseURL }),
573
615
  // Note: No headers passed - fetch wrapper sets oauth-2025-04-20 beta header
574
616
  // Limit capture wraps the OAuth fetch so subscription quota headers
575
617
  // (anthropic-ratelimit-unified-*) are recorded on every request —
@@ -599,33 +641,10 @@ export class AnthropicProvider extends BaseProvider {
599
641
  else {
600
642
  // Traditional API key authentication
601
643
  const apiKeyToUse = credentials?.apiKey ?? config?.apiKey ?? getAnthropicApiKey();
602
- // The official Anthropic SDK builds `${baseURL}/v1/messages` itself, so
603
- // a version-suffixed base URL — the form the previous @ai-sdk/anthropic
604
- // implementation REQUIRED (`https://api.anthropic.com/v1`) — would
605
- // double up as `/v1/v1/messages`. Normalize the inverse way now: strip
606
- // a trailing `/vN` segment when present so both historical forms of
607
- // ANTHROPIC_BASE_URL keep working.
608
- const normalizedBaseURL = (() => {
609
- const raw = process.env.ANTHROPIC_BASE_URL;
610
- if (!raw) {
611
- return undefined;
612
- }
613
- const trimmed = raw.replace(/\/+$/, "");
614
- const stripped = trimmed.replace(/\/v\d+$/, "");
615
- if (stripped !== trimmed) {
616
- logger.debug("[AnthropicProvider] Stripping the version suffix from " +
617
- "ANTHROPIC_BASE_URL — the official Anthropic SDK appends /v1 " +
618
- "to the base URL itself.", {
619
- baseURL: redactUrlCredentials(raw),
620
- rewrittenTo: redactUrlCredentials(stripped),
621
- });
622
- }
623
- return stripped;
624
- })();
625
644
  client = new Anthropic({
626
645
  apiKey: apiKeyToUse,
627
646
  defaultHeaders: headers,
628
- ...(normalizedBaseURL && { baseURL: normalizedBaseURL }),
647
+ ...(this.baseURL && { baseURL: this.baseURL }),
629
648
  // Same capture as the OAuth branch: works for direct API-key traffic
630
649
  // (legacy requests/tokens counters) and for the NeuroLink Claude proxy
631
650
  // (verbatim unified quota plus x-neurolink-* account/pool state).
@@ -676,10 +695,11 @@ export class AnthropicProvider extends BaseProvider {
676
695
  */
677
696
  getAuthHeaders() {
678
697
  const headers = {};
679
- // When routing through proxy (ANTHROPIC_BASE_URL set), use the full
680
- // OAuth beta set so the proxy forwards them upstream. Without these,
681
- // Anthropic treats the request with tighter non-subscription rate limits.
682
- const usingProxy = !!process.env.ANTHROPIC_BASE_URL;
698
+ // When routing through a proxy (a base URL is set — per instance or
699
+ // ANTHROPIC_BASE_URL), use the full OAuth beta set so the proxy forwards
700
+ // them upstream. Without these, Anthropic treats the request with tighter
701
+ // non-subscription rate limits.
702
+ const usingProxy = this.baseURL !== undefined;
683
703
  if (this.enableBetaFeatures) {
684
704
  if (usingProxy) {
685
705
  // The 1M-context beta requires a plan upgrade on most accounts;
@@ -705,9 +725,9 @@ export class AnthropicProvider extends BaseProvider {
705
725
  }
706
726
  }
707
727
  if (usingProxy) {
708
- // WAFs in front of ANTHROPIC_BASE_URL proxies commonly block the bare
709
- // SDK UA ("Anthropic/JS x.y.z"); send the claude-cli UA the OAuth path
710
- // already uses. Direct-to-Anthropic traffic keeps the honest SDK UA.
728
+ // WAFs in front of Anthropic proxies commonly block the bare SDK UA
729
+ // ("Anthropic/JS x.y.z"); send the claude-cli UA the OAuth path already
730
+ // uses. Direct-to-Anthropic traffic keeps the honest SDK UA.
711
731
  headers["User-Agent"] = CLAUDE_CLI_USER_AGENT;
712
732
  }
713
733
  // Add subscription-specific headers if applicable
@@ -736,8 +756,8 @@ export class AnthropicProvider extends BaseProvider {
736
756
  // Proxy mode: bypass tier validation entirely — the proxy handles model
737
757
  // access. Log at debug level so users can tell why an unknown model name
738
758
  // "validated" when their proxy may not actually expose it.
739
- if (process.env.ANTHROPIC_BASE_URL) {
740
- logger.debug("[validateModelAccess] Bypassing tier check (ANTHROPIC_BASE_URL set — proxy enforces access)", { model });
759
+ if (this.baseURL !== undefined) {
760
+ logger.debug("[validateModelAccess] Bypassing tier check (a base URL is set — the proxy enforces access)", { model });
741
761
  return true;
742
762
  }
743
763
  // API tier has access to all models
@@ -21,7 +21,7 @@ import { resolveToolExecutionRecords } from "../../core/toolExecutionRecorder.js
21
21
  import { buildDedupedEngineTools, buildGeminiResponseSchema, buildLoopExitMessage, buildNativeConfig, buildWrapupNudgeText, computeMaxSteps, createContextGuard, createTurnClock, buildUserPartsWithMultimodal, extractThoughtSignature, geminiContentsToV3Prompt, handleMaxStepsTermination, mapGeminiFinishReason, prependConversationMessages, resolveTurnStopReason, v3PromptToGeminiContents, } from "../googleNativeGemini3/index.js";
22
22
  import { createStreamChannel } from "../../core/streamChannel.js";
23
23
  import { toNativeToolDeclarations } from "../../core/nativeToolFormat.js";
24
- import { warnGoogleSdkIgnoresProxy } from "../../proxy/proxyFetch.js";
24
+ import { googleSdkProxyHttpOptions } from "../../proxy/proxyFetch.js";
25
25
  // ── Model middleware stream/generate bridges ──
26
26
  //
27
27
  // Caller model middleware (transformParams / wrapGenerate / wrapStream) is
@@ -145,20 +145,19 @@ async function createGoogleGenAIClient(apiKey, baseURL) {
145
145
  });
146
146
  }
147
147
  const Ctor = ctor;
148
- // httpOptions carries the endpoint override and nothing else. It used to
149
- // also pass a proxy fetch, which the SDK silently ignored — see
150
- // warnGoogleSdkIgnoresProxy for why that is not fixable here.
148
+ // httpOptions carries the endpoint override and, when a proxy is configured,
149
+ // the proxy-aware fetch the SDK sends its requests through.
151
150
  //
152
151
  // baseUrl is only included when resolved — verified against
153
152
  // @google/genai's ApiClient (dist/node/index.cjs) that it falls back to
154
153
  // its own default whenever httpOptions.baseUrl is undefined, so omitting
155
154
  // the key and passing `baseUrl: undefined` behave identically; the key is
156
155
  // still omitted outright for a cleaner outbound config object.
157
- warnGoogleSdkIgnoresProxy("GoogleAIStudio");
158
156
  return new Ctor({
159
157
  apiKey,
160
158
  httpOptions: {
161
159
  ...(baseURL ? { baseUrl: baseURL } : {}),
160
+ ...googleSdkProxyHttpOptions(),
162
161
  },
163
162
  });
164
163
  }
@@ -2202,7 +2201,17 @@ export class GoogleAIStudioProvider extends BaseProvider {
2202
2201
  }
2203
2202
  queue.push(item);
2204
2203
  };
2205
- const session = await client.live.connect({
2204
+ // @google/genai 2.x waits inside connect() for the server's setupComplete
2205
+ // message and does not reject when the socket errors or closes first, so a
2206
+ // refused key or model would leave this await pending forever. Fail on the
2207
+ // first error or close that arrives before the session is up. Once it is
2208
+ // up, the callbacks below end the stream as they always did.
2209
+ let sessionOpen = false;
2210
+ let failConnect = () => undefined;
2211
+ const connectFailed = new Promise((_resolve, reject) => {
2212
+ failConnect = reject;
2213
+ });
2214
+ const connecting = client.live.connect({
2206
2215
  model,
2207
2216
  callbacks: {
2208
2217
  onopen: () => {
@@ -2230,9 +2239,15 @@ export class GoogleAIStudioProvider extends BaseProvider {
2230
2239
  }
2231
2240
  },
2232
2241
  onerror: (e) => {
2242
+ if (!sessionOpen) {
2243
+ failConnect(new Error("Gemini Live connection failed before setup completed"));
2244
+ }
2233
2245
  push({ type: "error", error: e });
2234
2246
  },
2235
2247
  onclose: (_e) => {
2248
+ if (!sessionOpen) {
2249
+ failConnect(new Error("Gemini Live connection closed before setup completed"));
2250
+ }
2236
2251
  push({ type: "end" });
2237
2252
  },
2238
2253
  },
@@ -2243,6 +2258,8 @@ export class GoogleAIStudioProvider extends BaseProvider {
2243
2258
  },
2244
2259
  },
2245
2260
  });
2261
+ const session = await Promise.race([connecting, connectFailed]);
2262
+ sessionOpen = true;
2246
2263
  // Feed upstream audio frames concurrently
2247
2264
  (async () => {
2248
2265
  try {
@@ -2411,11 +2428,35 @@ export class GoogleAIStudioProvider extends BaseProvider {
2411
2428
  try {
2412
2429
  const apiKey = this.getApiKey();
2413
2430
  const client = await createGoogleGenAIClient(apiKey, this.getBaseURL());
2414
- const result = await client.models.embedContent({
2415
- model: embeddingModelName,
2416
- contents: texts,
2417
- });
2418
- const embeddings = (result.embeddings || []).map((e) => e.values || []);
2431
+ const embeddings = [];
2432
+ if (embeddingModelName.includes("gemini-embedding-2")) {
2433
+ // @google/genai 2.x reads an array of strings for these models as ONE
2434
+ // content with several parts, so a batch would come back as a single
2435
+ // vector. Embed each text on its own, a few at a time, in order.
2436
+ const concurrency = 8;
2437
+ for (let start = 0; start < texts.length; start += concurrency) {
2438
+ const group = texts.slice(start, start + concurrency);
2439
+ const results = await Promise.all(group.map((text) => client.models.embedContent({
2440
+ model: embeddingModelName,
2441
+ contents: [text],
2442
+ })));
2443
+ for (const result of results) {
2444
+ embeddings.push(result.embeddings?.[0]?.values || []);
2445
+ }
2446
+ }
2447
+ }
2448
+ else {
2449
+ const result = await client.models.embedContent({
2450
+ model: embeddingModelName,
2451
+ contents: texts,
2452
+ });
2453
+ embeddings.push(...(result.embeddings || []).map((e) => e.values || []));
2454
+ }
2455
+ // One vector per text, or fail: a silent mismatch would pair texts with
2456
+ // the wrong vectors downstream.
2457
+ if (embeddings.length !== texts.length) {
2458
+ throw new Error(`Embedding response held ${embeddings.length} vectors for ${texts.length} texts`);
2459
+ }
2419
2460
  logger.debug("Batch embeddings generated successfully", {
2420
2461
  provider: this.providerName,
2421
2462
  model: embeddingModelName,
@@ -13,7 +13,7 @@ import { resolveRequestKind } from "../../core/resolveRequestKind.js";
13
13
  import { ModelConfigurationManager } from "../../core/modelConfiguration.js";
14
14
  import { isSchemaComplexityError } from "../../core/modules/structuredOutputPolicy.js";
15
15
  import { redactUrlForError, stringifyContentSafe, } from "../../utils/logSanitize.js";
16
- import { warnGoogleSdkIgnoresProxy } from "../../proxy/proxyFetch.js";
16
+ import { googleSdkProxyHttpOptions } from "../../proxy/proxyFetch.js";
17
17
  import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../../types/index.js";
18
18
  import { classifyProviderError } from "../../utils/errorClassifier.js";
19
19
  import { ERROR_CODES, NeuroLinkError } from "../../utils/errorHandling.js";
@@ -1122,7 +1122,6 @@ export class GoogleVertexProvider extends BaseProvider {
1122
1122
  * Create @google/genai client configured for Vertex AI
1123
1123
  */
1124
1124
  async createVertexGenAIClient(regionOverride) {
1125
- warnGoogleSdkIgnoresProxy("GoogleVertex");
1126
1125
  const expressApiKey = this.resolveExpressApiKey();
1127
1126
  // Resolved only on the ADC path, and from the per-instance projectId the
1128
1127
  // constructor already settled (credentials.projectId if supplied, else
@@ -1147,14 +1146,14 @@ export class GoogleVertexProvider extends BaseProvider {
1147
1146
  const Ctor = ctor;
1148
1147
  const baseUrl = this.resolveBaseURL();
1149
1148
  const httpOptions = {
1150
- // The endpoint override and nothing else. This object used to also pass
1151
- // a proxy fetch, which the SDK silently ignored — see
1152
- // warnGoogleSdkIgnoresProxy for why that is not fixable here.
1149
+ // The endpoint override and, when a proxy is configured, the
1150
+ // proxy-aware fetch the SDK sends its requests through.
1153
1151
  //
1154
- // Only set when resolved: the SDK falls back to its own default
1155
- // whenever httpOptions.baseUrl is undefined, so omitting the key and
1156
- // passing undefined behave identically.
1152
+ // baseUrl is only set when resolved: the SDK falls back to its own
1153
+ // default whenever httpOptions.baseUrl is undefined, so omitting the key
1154
+ // and passing undefined behave identically.
1157
1155
  ...(baseUrl ? { baseUrl } : {}),
1156
+ ...googleSdkProxyHttpOptions(),
1158
1157
  };
1159
1158
  if (expressApiKey) {
1160
1159
  // Express Mode: an API key replaces project/location entirely. Passing
@@ -3,6 +3,7 @@
3
3
  * Supports HTTP/HTTPS, SOCKS4/5, authentication, and NO_PROXY bypass
4
4
  * Lightweight implementation extracted from research of major proxy packages
5
5
  */
6
+ import type { GoogleGenAIHttpOptions } from "../types/index.js";
6
7
  /**
7
8
  * Classify a fetch failure as a transient network error worth retrying.
8
9
  *
@@ -47,4 +48,18 @@ export declare function getProxyStatus(): {
47
48
  method: string;
48
49
  capabilities: string[];
49
50
  };
50
- export declare function warnGoogleSdkIgnoresProxy(providerLabel: string): void;
51
+ /**
52
+ * `httpOptions` fragment that routes the @google/genai SDK through the
53
+ * configured proxy, or nothing when none is configured.
54
+ *
55
+ * The SDK calls global `fetch` unless `HttpOptions.fetch` is set (declared and
56
+ * used from 2.23.0), and global `fetch` does not read HTTP_PROXY / HTTPS_PROXY
57
+ * unless Node itself was started with env-proxy support. The proxy-aware fetch
58
+ * is passed only when a proxy is configured: with none, the SDK keeps its own
59
+ * request path instead of gaining this module's trace headers and retries.
60
+ *
61
+ * Covers the SDK's `generateContent`, `generateContentStream` and
62
+ * `embedContent` requests. It does not reach Gemini Live websockets or the
63
+ * Application Default Credentials token requests.
64
+ */
65
+ export declare function googleSdkProxyHttpOptions(): GoogleGenAIHttpOptions;
@@ -698,31 +698,19 @@ export function getProxyStatus() {
698
698
  };
699
699
  }
700
700
  /**
701
- * One-time warning that the @google/genai SDK cannot honour a configured
702
- * proxy.
701
+ * `httpOptions` fragment that routes the @google/genai SDK through the
702
+ * configured proxy, or nothing when none is configured.
703
703
  *
704
- * Both Google providers passed `httpOptions: { fetch: createProxyFetch() }`,
705
- * which does nothing. `HttpOptions` in @google/genai 1.46.0 declares only
706
- * baseUrl, baseUrlResourceScope, apiVersion, headers, timeout, extraBody and
707
- * retryOptions — there is no `fetch` on it. That property belongs to a
708
- * different interface (`ClientOptions`), which `GoogleGenAIOptions` does not
709
- * accept, and the SDK's request path calls global `fetch`. The option was
710
- * silently dropped and requests went direct.
704
+ * The SDK calls global `fetch` unless `HttpOptions.fetch` is set (declared and
705
+ * used from 2.23.0), and global `fetch` does not read HTTP_PROXY / HTTPS_PROXY
706
+ * unless Node itself was started with env-proxy support. The proxy-aware fetch
707
+ * is passed only when a proxy is configured: with none, the SDK keeps its own
708
+ * request path instead of gaining this module's trace headers and retries.
711
709
  *
712
- * It type-checked only because those constructors are reached through a
713
- * loosely-typed local alias, which turns off excess-property checking.
714
- *
715
- * This SDK version offers no supported injection point, so rather than keep a
716
- * line that reads like working proxy support, the situation is reported once
717
- * per process — and only to someone who actually configured a proxy. Silent
718
- * bypass is the worst outcome available: a corporate user believes their
719
- * traffic is proxied when it is not.
710
+ * Covers the SDK's `generateContent`, `generateContentStream` and
711
+ * `embedContent` requests. It does not reach Gemini Live websockets or the
712
+ * Application Default Credentials token requests.
720
713
  */
721
- let proxyUnsupportedWarned = false;
722
- export function warnGoogleSdkIgnoresProxy(providerLabel) {
723
- if (proxyUnsupportedWarned || !getProxyStatus().enabled) {
724
- return;
725
- }
726
- proxyUnsupportedWarned = true;
727
- logger.warn(`[${providerLabel}] A proxy is configured, but the @google/genai SDK provides no way to route its requests through it (HttpOptions has no 'fetch', and GoogleGenAIOptions accepts none). Requests from this provider go direct.`);
714
+ export function googleSdkProxyHttpOptions() {
715
+ return getProxyStatus().enabled ? { fetch: createProxyFetch() } : {};
728
716
  }
@@ -105,9 +105,16 @@ export type NeurolinkCredentials = {
105
105
  apiKey?: string;
106
106
  baseURL?: string;
107
107
  };
108
+ /**
109
+ * Anthropic. `baseURL` points the official SDK client at a gateway or proxy
110
+ * instead of `api.anthropic.com` (with or without a trailing `/v1`); it
111
+ * takes precedence over `ANTHROPIC_BASE_URL`, exactly as `apiKey` does over
112
+ * `ANTHROPIC_API_KEY`.
113
+ */
108
114
  anthropic?: {
109
115
  apiKey?: string;
110
116
  oauthToken?: string;
117
+ baseURL?: string;
111
118
  };
112
119
  googleAiStudio?: {
113
120
  apiKey?: string;
@@ -4131,15 +4131,15 @@
4131
4131
  {"objectID":"1d8625a67cba7bc640e454fe15f76d86e2be4fbe79e9496fc085e5a6df496086","title":"Future Enhancements","url":"/docs/features/pdf-support#future-enhancements","content":"Planned features for PDF support:\nOCR Integration: Extract text from scanned PDFs\nPage Selection: Analyze specific pages only\nPDF Generation: Create PDFs from AI responses\nForm Filling: Extract and populate PDF forms","hierarchy":{"lvl0":"Features","lvl1":"PDF File Support","lvl2":"Future Enhancements","lvl3":""}},
4132
4132
  {"objectID":"20bb5119cb74199a04bebd17c621d8c6a0438c0833f12103b9f3914c5f026ceb","title":"Feedback and Support","url":"/docs/features/pdf-support#feedback-and-support","content":"Found a bug or have a feature request? Please:\nCheck existing issues on GitHub\nCreate a new issue with:\nProvider used\nPDF file details (size, pages)\nError message or unexpected behavior\nSample code (if possible)","hierarchy":{"lvl0":"Features","lvl1":"PDF File Support","lvl2":"Feedback and Support","lvl3":""}},
4133
4133
  {"objectID":"7c47c75b9023d438078ad3ed696c167cb9b5beaa369fad844635f7f32cce74a5","title":"Version 9.2.0 (Current)","url":"/docs/features/pdf-support#version-920-current","content":"✅ Initial PDF support for Vertex AI, Anthropic, Bedrock, AI Studio\n✅ Auto-detection via --file flag\n✅ Multiple PDF processing\n✅ Size and page limit validation\n✅ Comprehensive error messages\n✅ CLI and SDK integration\n✅ Streaming support\n✅ Mixed multimodal inputs (PDF + CSV + images)\n\nNext: Multimodal Chat Guide | CSV Support","hierarchy":{"lvl0":"Features","lvl1":"PDF File Support","lvl2":"Version 9.2.0 (Current)","lvl3":""}},
4134
- {"objectID":"2a46dad649fcc5c7b389d9db519211899183703302b4127f0a4a2285018e1362","title":"Per-Request Credentials","url":"/docs/features/per-request-credentials","content":"Per-Request Credentials\n\nStatus: Stable | Availability: SDK only\n\nOverview\n\nNeuroLink allows provider credentials to be supplied at two levels below the environment-variable default: on the NeuroLink constructor (instance level) and on individual generate() / stream() calls (per-call level). Credentials are resolved in the following order of precedence:\n\nThis enables multi-tenant architectures where different users or tenants supply their own provider API keys (bring-your-own-key, BYOK), without requiring separate NeuroLink instances per user or touching the process environment.\n\nTypical use cases:\nMulti-tenant SaaS — each API request carries the calling user's provider key; no shared key leakage between tenants\nBYOK products — end users paste their OpenAI / Anthropic keys in settings; you forward them to NeuroLink per call\nTesting and CI — inject credentials programmatically without setting environment variables\nProvider switching — override only the active provider's credentials while leaving others to fall through to env vars\n\nQuick Start\n\nInstance-Level vs Per-Call\n\nInstance-Level Credentials\n\nSet credentials in the NeuroLink constructor. These apply as the default for every generate() and stream() call made on that instance. Useful when serving a single tenant or when you have a known key for the duration of the instance lifecycle.\n\nPer-Call Credentials\n\nSet credentials directly on generate() or stream(). These override the instance-level credentials for that single call only. Only the providers you explicitly set are overridden — others continue falling through to instance credentials and then environment variables.\n\nPrecedence Rules\n\n| Level | Scope | Set on |\n| ----------- | --------------------- | ----------------------------------------------------------------------------------- |\n| Per-call | Single request only | generate({ credentials }), stream({ credentials }) or decide({ credentials }) |\n| Instance | All calls on instance | new NeuroLink({ credentials }) |\n| Environment | Process-wide fallback | OPENAI_API_KEY, ANTHROPIC_API_KEY, … |\n\nUnset providers at any level fall through to the next. You never need to repeat a credential at the per-call level if the instance default is correct.\n\nProvider Credential Reference\n\nAll fields are optional — omit any field you want to fall through to a lower-precedence level.\n\n| Provider | Key | Fields |\n| -------------------- | ------------------- | ---------------------------------------------------------------------------------------------------- |\n| OpenAI | openai | apiKey, baseURL |\n| Anthropic | anthropic | apiKey, oauthToken |\n| Google AI Studio | googleAiStudio | apiKey, baseURL |\n| Google Vertex AI | vertex | projectId, location, apiKey (Express Mode), serviceAccountKey, clientEmail, privateKey |\n| Amazon Bedrock | bedrock | accessKeyId, secretAccessKey, sessionToken, region |\n| Amazon SageMaker | sagemaker | accessKeyId, secretAccessKey, sessionToken, region, endpoint |\n| Azure OpenAI | azure | apiKey, resourceName, deploymentName, apiVersion |\n| Mistral | mistral | apiKey |\n| Hugging Face | huggingFace | apiKey, baseURL |\n| OpenRouter | openrouter | apiKey, baseURL |\n| LiteLLM | litellm | apiKey, baseURL |\n| OpenAI-Compatible | openaiCompatible | apiKey, baseURL |\n| Cerebras | cerebras | apiKey, baseURL |\n| SambaNova | sambanova | apiKey, baseURL |\n| Ollama | ollama | baseURL ","hierarchy":{"lvl0":"Features","lvl1":"Per-Request Credentials","lvl2":"","lvl3":""}},
4134
+ {"objectID":"2a46dad649fcc5c7b389d9db519211899183703302b4127f0a4a2285018e1362","title":"Per-Request Credentials","url":"/docs/features/per-request-credentials","content":"Per-Request Credentials\n\nStatus: Stable | Availability: SDK only\n\nOverview\n\nNeuroLink allows provider credentials to be supplied at two levels below the environment-variable default: on the NeuroLink constructor (instance level) and on individual generate() / stream() calls (per-call level). Credentials are resolved in the following order of precedence:\n\nThis enables multi-tenant architectures where different users or tenants supply their own provider API keys (bring-your-own-key, BYOK), without requiring separate NeuroLink instances per user or touching the process environment.\n\nTypical use cases:\nMulti-tenant SaaS — each API request carries the calling user's provider key; no shared key leakage between tenants\nBYOK products — end users paste their OpenAI / Anthropic keys in settings; you forward them to NeuroLink per call\nTesting and CI — inject credentials programmatically without setting environment variables\nProvider switching — override only the active provider's credentials while leaving others to fall through to env vars\n\nQuick Start\n\nInstance-Level vs Per-Call\n\nInstance-Level Credentials\n\nSet credentials in the NeuroLink constructor. These apply as the default for every generate() and stream() call made on that instance. Useful when serving a single tenant or when you have a known key for the duration of the instance lifecycle.\n\nPer-Call Credentials\n\nSet credentials directly on generate() or stream(). These override the instance-level credentials for that single call only. Only the providers you explicitly set are overridden — others continue falling through to instance credentials and then environment variables.\n\nPrecedence Rules\n\n| Level | Scope | Set on |\n| ----------- | --------------------- | ----------------------------------------------------------------------------------- |\n| Per-call | Single request only | generate({ credentials }), stream({ credentials }) or decide({ credentials }) |\n| Instance | All calls on instance | new NeuroLink({ credentials }) |\n| Environment | Process-wide fallback | OPENAI_API_KEY, ANTHROPIC_API_KEY, … |\n\nUnset providers at any level fall through to the next. You never need to repeat a credential at the per-call level if the instance default is correct.\n\nProvider Credential Reference\n\nAll fields are optional — omit any field you want to fall through to a lower-precedence level.\n\n| Provider | Key | Fields |\n| -------------------- | ------------------- | ---------------------------------------------------------------------------------------------------- |\n| OpenAI | openai | apiKey, baseURL |\n| Anthropic | anthropic | apiKey, oauthToken, baseURL |\n| Google AI Studio | googleAiStudio | apiKey, baseURL |\n| Google Vertex AI | vertex | projectId, location, apiKey (Express Mode), serviceAccountKey, clientEmail, privateKey |\n| Amazon Bedrock | bedrock | accessKeyId, secretAccessKey, sessionToken, region |\n| Amazon SageMaker | sagemaker | accessKeyId, secretAccessKey, sessionToken, region, endpoint |\n| Azure OpenAI | azure | apiKey, resourceName, deploymentName, apiVersion |\n| Mistral | mistral | apiKey |\n| Hugging Face | huggingFace | apiKey, baseURL |\n| OpenRouter | openrouter | apiKey, baseURL |\n| LiteLLM | litellm | apiKey, baseURL |\n| OpenAI-Compatible | openaiCompatible | apiKey, baseURL |\n| Cerebras | cerebras | apiKey, baseURL |\n| SambaNova | sambanova | apiKey, baseURL |\n| Ollama | ollama | baseURL ","hierarchy":{"lvl0":"Features","lvl1":"Per-Request Credentials","lvl2":"","lvl3":""}},
4135
4135
  {"objectID":"12291afcfaaf4af65b01b07e93a9eac08280ff7c61394f6051a86c1b152a7b4a","title":"Per-Request Credentials","url":"/docs/features/per-request-credentials#per-request-credentials","content":"Status: Stable | Availability: SDK only","hierarchy":{"lvl0":"Features","lvl1":"Per-Request Credentials","lvl2":"Per-Request Credentials","lvl3":""}},
4136
4136
  {"objectID":"6c6756b0806344240b3c86d6b0063587db50f2c4ccf936a32e87d4377b77014c","title":"Overview","url":"/docs/features/per-request-credentials#overview","content":"NeuroLink allows provider credentials to be supplied at two levels below the environment-variable default: on the NeuroLink constructor (instance level) and on individual generate() / stream() calls (per-call level). Credentials are resolved in the following order of precedence:\n\nThis enables multi-tenant architectures where different users or tenants supply their own provider API keys (bring-your-own-key, BYOK), without requiring separate NeuroLink instances per user or touching the process environment.\n\nTypical use cases:\nMulti-tenant SaaS — each API request carries the calling user's provider key; no shared key leakage between tenants\nBYOK products — end users paste their OpenAI / Anthropic keys in settings; you forward them to NeuroLink per call\nTesting and CI — inject credentials programmatically without setting environment variables\nProvider switching — override only the active provider's credentials while leaving others to fall through to env vars","hierarchy":{"lvl0":"Features","lvl1":"Per-Request Credentials","lvl2":"Overview","lvl3":""}},
4137
4137
  {"objectID":"d4e86918319aa78b4e4461dbf29a60ebe90935e74fc70617f59bf524ba3e1eae","title":"Instance-Level Credentials","url":"/docs/features/per-request-credentials#instance-level-credentials","content":"Set credentials in the NeuroLink constructor. These apply as the default for every generate() and stream() call made on that instance. Useful when serving a single tenant or when you have a known key for the duration of the instance lifecycle.","hierarchy":{"lvl0":"Features","lvl1":"Per-Request Credentials","lvl2":"Instance-Level Credentials","lvl3":""}},
4138
4138
  {"objectID":"250b1dd11e018f5f252b27ce408e3dade21eff1cf26eed4aaf81126fad6ab691","title":"Per-Call Credentials","url":"/docs/features/per-request-credentials#per-call-credentials","content":"Set credentials directly on generate() or stream(). These override the instance-level credentials for that single call only. Only the providers you explicitly set are overridden — others continue falling through to instance credentials and then environment variables.","hierarchy":{"lvl0":"Features","lvl1":"Per-Request Credentials","lvl2":"Per-Call Credentials","lvl3":""}},
4139
4139
  {"objectID":"4166cbaf6e6263ac98e24d7ede9950b44aade18e787b57e71138d2fc2deaaa07","title":"Precedence Rules","url":"/docs/features/per-request-credentials#precedence-rules","content":"| Level | Scope | Set on |\n| ----------- | --------------------- | ----------------------------------------------------------------------------------- |\n| Per-call | Single request only | generate({ credentials }), stream({ credentials }) or decide({ credentials }) |\n| Instance | All calls on instance | new NeuroLink({ credentials }) |\n| Environment | Process-wide fallback | OPENAI_API_KEY, ANTHROPIC_API_KEY, … |\n\nUnset providers at any level fall through to the next. You never need to repeat a credential at the per-call level if the instance default is correct.","hierarchy":{"lvl0":"Features","lvl1":"Per-Request Credentials","lvl2":"Precedence Rules","lvl3":""}},
4140
- {"objectID":"b739fcf7819dd6f055ebe3bcdb161ff839067dc172235f9d0072f9cf79d7bcd3","title":"Provider Credential Reference","url":"/docs/features/per-request-credentials#provider-credential-reference","content":"All fields are optional — omit any field you want to fall through to a lower-precedence level.\n\n| Provider | Key | Fields |\n| -------------------- | ------------------- | ---------------------------------------------------------------------------------------------------- |\n| OpenAI | openai | apiKey, baseURL |\n| Anthropic | anthropic | apiKey, oauthToken |\n| Google AI Studio | googleAiStudio | apiKey, baseURL |\n| Google Vertex AI | vertex | projectId, location, apiKey (Express Mode), serviceAccountKey, clientEmail, privateKey |\n| Amazon Bedrock | bedrock | accessKeyId, secretAccessKey, sessionToken, region |\n| Amazon SageMaker | sagemaker | accessKeyId, secretAccessKey, sessionToken, region, endpoint |\n| Azure OpenAI | azure | apiKey, resourceName, deploymentName, apiVersion |\n| Mistral | mistral | apiKey |\n| Hugging Face | huggingFace | apiKey, baseURL |\n| OpenRouter | openrouter | apiKey, baseURL |\n| LiteLLM | litellm | apiKey, baseURL |\n| OpenAI-Compatible |","hierarchy":{"lvl0":"Features","lvl1":"Per-Request Credentials","lvl2":"Provider Credential Reference","lvl3":""}},
4140
+ {"objectID":"b739fcf7819dd6f055ebe3bcdb161ff839067dc172235f9d0072f9cf79d7bcd3","title":"Provider Credential Reference","url":"/docs/features/per-request-credentials#provider-credential-reference","content":"All fields are optional — omit any field you want to fall through to a lower-precedence level.\n\n| Provider | Key | Fields |\n| -------------------- | ------------------- | ---------------------------------------------------------------------------------------------------- |\n| OpenAI | openai | apiKey, baseURL |\n| Anthropic | anthropic | apiKey, oauthToken, baseURL |\n| Google AI Studio | googleAiStudio | apiKey, baseURL |\n| Google Vertex AI | vertex | projectId, location, apiKey (Express Mode), serviceAccountKey, clientEmail, privateKey |\n| Amazon Bedrock | bedrock | accessKeyId, secretAccessKey, sessionToken, region |\n| Amazon SageMaker | sagemaker | accessKeyId, secretAccessKey, sessionToken, region, endpoint |\n| Azure OpenAI | azure | apiKey, resourceName, deploymentName, apiVersion |\n| Mistral | mistral | apiKey |\n| Hugging Face | huggingFace | apiKey, baseURL |\n| OpenRouter | openrouter | apiKey, baseURL |\n| LiteLLM | litellm | apiKey, baseURL |\n| OpenAI-Compatible | openaiCompatible","hierarchy":{"lvl0":"Features","lvl1":"Per-Request Credentials","lvl2":"Provider Credential Reference","lvl3":""}},
4141
4141
  {"objectID":"575e2e50c5fd00e53fce66deee556264f3df8962e2188ac446e6e7d4ad29b2ed","title":"OpenAI","url":"/docs/features/per-request-credentials#openai","content":"Custom base URL for OpenAI-compatible proxies:","hierarchy":{"lvl0":"Features","lvl1":"Per-Request Credentials","lvl2":"OpenAI","lvl3":""}},
4142
- {"objectID":"499e7c1d012d337ad54e06db35770d5b3471afb0f9e3f471564642e8ba7e5c6b","title":"Anthropic","url":"/docs/features/per-request-credentials#anthropic","content":"OAuth token (Anthropic Claude subscription):","hierarchy":{"lvl0":"Features","lvl1":"Per-Request Credentials","lvl2":"Anthropic","lvl3":""}},
4142
+ {"objectID":"499e7c1d012d337ad54e06db35770d5b3471afb0f9e3f471564642e8ba7e5c6b","title":"Anthropic","url":"/docs/features/per-request-credentials#anthropic","content":"OAuth token (Anthropic Claude subscription):\n\nA gateway or proxy in front of Anthropic (baseURL wins over ANTHROPIC_BASE_URL, with or without a trailing /v1; a gateway that does its own auth accepts any apiKey):","hierarchy":{"lvl0":"Features","lvl1":"Per-Request Credentials","lvl2":"Anthropic","lvl3":""}},
4143
4143
  {"objectID":"3f5ea871d2750f36fca227c33f109de53f5d0bc4c184ea956d0b8c3736babe29","title":"Vertex AI — Express Mode","url":"/docs/features/per-request-credentials#vertex-ai-express-mode","content":"Express Mode uses a simple API key instead of service-account credentials, making it suitable for per-request BYOK flows:\n\nFull service-account credentials (server-side only — keep private keys out of client code):","hierarchy":{"lvl0":"Features","lvl1":"Per-Request Credentials","lvl2":"Vertex AI — Express Mode","lvl3":""}},
4144
4144
  {"objectID":"af5efdaa7ba1811d537c1c7aca8a0143cd8496be1e9db86526d34003cf427dbb","title":"Streaming with Credentials","url":"/docs/features/per-request-credentials#streaming-with-credentials","content":"credentials works identically on stream(). Note that neurolink.stream()\nreturns a StreamResult wrapper — iterate over its .stream property:","hierarchy":{"lvl0":"Features","lvl1":"Per-Request Credentials","lvl2":"Streaming with Credentials","lvl3":""}},
4145
4145
  {"objectID":"ea70e911923bd079e70af1c36bda14c16bdd9bfcec49f236a9d0550d6602502a","title":"Multi-Tenant Request Handler","url":"/docs/features/per-request-credentials#multi-tenant-request-handler","content":"A typical pattern for a multi-tenant API endpoint. Note the provider-name →\ncredential-key mapping: the registered provider names (google-ai,\nopenai-compatible, huggingface) differ from their NeurolinkCredentials\nslot keys (googleAiStudio, openaiCompatible, huggingFace), so map\nexplicitly to avoid runtime surprises.\n\nNote: This example assumes API-key authentication. Providers like Bedrock\n(which use accessKeyId/secretAccessKey) and Ollama (which use baseURL)\nrequire different credential shapes — see the Provider Credential Reference above.","hierarchy":{"lvl0":"Features","lvl1":"Per-Request Credentials","lvl2":"Multi-Tenant Request Handler","lvl3":""}},
@@ -9687,8 +9687,8 @@
9687
9687
  {"objectID":"a5276d835ceba15da6ec36261ab78001e17f8b78ba9c8cb1c5e1282aa2f377e3","title":"Error-message fidelity","url":"/docs/provider-integration/openai-compat-catalog#error-message-fidelity","content":"Each entry's errorRules is a direct, order-preserving translation of its\noriginal subclass's formatProviderError if/else ladder into rule data\n(status code and/or case-insensitive pattern), classified via\nclassifyProviderError()\n(src/lib/utils/errorClassifier.ts). Every bespoke message string is\npreserved verbatim — including xAI's \"top up your account\" quota URL and\nGroq's decommissioned-vs-not-found distinction — via each rule's own\nmessage field (string | ((ctx) => string)), with model-name\ninterpolation carried through ctx.modelName. There is no message-wording\nregression here. Timeout classification is likewise unchanged: every catalog\nprovider except Groq maps TimeoutError to NetworkError (the classifier's\ndefault), and Groq alone maps it to ProviderError. Groq's subclass override is\npreserved verbatim via the JSON's quirks.timeoutErrorClass, so no\nprovider's timeout class changed during migration.","hierarchy":{"lvl0":"Provider Integration","lvl1":"OpenAI-Compatible Provider Catalog","lvl2":"Error-message fidelity","lvl3":""}},
9688
9688
  {"objectID":"2a22fddc1a2c383a5e75a53e2d512d764f01ad66ab03bfe6edf12acb5d204814","title":"Known pre-existing quirk this migration preserved (not fixed)","url":"/docs/provider-integration/openai-compat-catalog#known-pre-existing-quirk-this-migration-preserved-not-fixed","content":"Mistral's provider registration passes a defaultModel value to\nProviderFactory.registerProvider() that does not check MISTRAL_MODEL\n(MistralModels.MISTRAL_LARGE_LATEST, a bare literal), while\nConfiguredOpenAICompatProvider.getDefaultModel() for Mistral does\ncheck MISTRAL_MODEL (falling back to MistralModels.MISTRAL_SMALL_2506).\nEvery other catalog provider's registry default and class default agree.\nThis is expressed via the JSON's quirks.registryDefaultIgnoresModelEnvVar\n(true only for Mistral). The JSON migration did reconcile one half of it:\ngetDefaultModel(MISTRAL) now returns the real generation default\n(mistral-small-2506) rather than the registry literal — a disclosed\nbug-fix-grade delta, since the two disagreed before.","hierarchy":{"lvl0":"Provider Integration","lvl1":"OpenAI-Compatible Provider Catalog","lvl2":"Known pre-existing quirk this migration preserved (not fixed)","lvl3":""}},
9689
9689
  {"objectID":"1d94ab5116c71ed28e2428889216922df96e1d6d4d5fdc955ee65a4171c483e3","title":"What comes next","url":"/docs/provider-integration/openai-compat-catalog#what-comes-next","content":"The credential-free growth queue in docs/provider-integration/growth-queue.json lists every\nverified candidate vendor by wave (W1 = hosted OpenAI-compatible, the catalog path). Entries\nonboarded from it carry evidence.liveMatrix: null until a live run with a real key.","hierarchy":{"lvl0":"Provider Integration","lvl1":"OpenAI-Compatible Provider Catalog","lvl2":"What comes next","lvl3":""}},
9690
- {"objectID":"bf3e1f8f1233a9b3d12aedbdbec4bf8cd196d18bf56b7bb104b34e31e90b61e0","title":"Provider Onboarding Tiers","url":"/docs/provider-integration/tiers/README","content":"Provider Onboarding Tiers\n\nFour tiers, ordered by effort. Always pick the lowest tier that's\nactually true for the provider you're adding — a provider that's\nOpenAI-wire-compatible but gets built as a bespoke Tier 3 subclass \"to be\nsafe\" is exactly the copy-pasted-boilerplate problem this redesign\nexists to eliminate (see ../adr/0002-catalog-over-subclass-default.md).\n\n| Tier | Example | Code required | Time |\n| -------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------- | --------------------------------- |\n| 1 — Aggregator passthrough | A new model id on an existing LiteLLM/OpenRouter route | None | Minutes |\n| 2 — Catalog entry | A new zero-quirk OpenAI-compatible vendor (the Groq/xAI/Together shape) | One data row + one mocked-test section | ~1 hour |\n| 3 — Adapter-based native | A vendor with its own SDK/wire format but a normal request/response HTTP lifecycle | One provider class | Days |\n| 4 — Full custom | SageMaker-class: non-HTTP protocol, SDK-signed auth, bespoke lifecycle | Custom executeStream/doGenerate, possibly own CLI subcommands | Days, needs written justification |\n\nTier 1 adds zero provider coverage — it is a usage change against an\nalready-counted aggregator, never reflected in\ndocs/reference/provider-comparison.md or the README's provider count.\n\nEvery tier that adds a new AIProviderName member (Tier 2 and above) ends\nthe same way: a manifest at\ndocs/provider-integration/manifests/<provider>.json\n(see ../manifests/README.md) and a green run\nof pnpm run verify:provider-onboarding (see\n../../../tools/verify-provider-onboarding.ts).\nTier 1 needs no manifest and no gate — see\ntier-1-aggregator-passthrough.md.\n\nUse ../../../tools/scaffold-provider.ts\n(pnpm run scaffold:provider) to generate the starting-point snippets for\nTiers 2–4 instead of copy-pasting from an existing provider by hand. Both\ntools ship in the tree; there is no manual-fallback era anymore — a PR\nthat skips the gate locally just fails it in CI.","hierarchy":{"lvl0":"Provider Integration","lvl1":"Provider Onboarding Tiers","lvl2":"","lvl3":""}},
9691
- {"objectID":"de01fcaf93bee302e2fca5e18f85cba5def099685383ec01b61d18489dbca5aa","title":"Provider Onboarding Tiers","url":"/docs/provider-integration/tiers/README#provider-onboarding-tiers","content":"Four tiers, ordered by effort. Always pick the lowest tier that's\nactually true for the provider you're adding — a provider that's\nOpenAI-wire-compatible but gets built as a bespoke Tier 3 subclass \"to be\nsafe\" is exactly the copy-pasted-boilerplate problem this redesign\nexists to eliminate (see ../adr/0002-catalog-over-subclass-default.md).\n\n| Tier | Example | Code required | Time |\n| -------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------- | --------------------------------- |\n| 1 — Aggregator passthrough | A new model id on an existing LiteLLM/OpenRouter route | None | Minutes |\n| 2 — Catalog entry | A new zero-quirk OpenAI-compatible vendor (the Groq/xAI/Together shape) | One data row + one mocked-test section | ~1 hour |\n| 3 — Adapter-based native | A vendor with its own SDK/wire format but a normal request/response HTTP lifecycle | One provider class | Days |\n| 4 — Full custom | SageMaker-class: non-HTTP protocol, SDK-signed auth, bespoke lifecycle | Custom executeStream/doGenerate, possibly own CLI subcommands | Days, needs written justification |\n\nTier 1 adds zero provider coverage — it is a usage change against an\nalready-counted aggregator, never reflected in\ndocs/reference/provider-comparison.md or the README's provider count.","hierarchy":{"lvl0":"Provider Integration","lvl1":"Provider Onboarding Tiers","lvl2":"Provider Onboarding Tiers","lvl3":""}},
9690
+ {"objectID":"bf3e1f8f1233a9b3d12aedbdbec4bf8cd196d18bf56b7bb104b34e31e90b61e0","title":"Provider Onboarding Tiers","url":"/docs/provider-integration/tiers/README","content":"Provider Onboarding Tiers\n\nFour tiers, ordered by effort. Always pick the lowest tier that's\nactually true for the provider you're adding — a provider that's\nOpenAI-wire-compatible but gets built as a bespoke Tier 3 subclass \"to be\nsafe\" is exactly the copy-pasted-boilerplate problem this redesign\nexists to eliminate (see ../adr/0002-catalog-over-subclass-default.md).\n\nProvider Integration index\n\n| Tier | Example | Code required | Time |\n| -------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------- | --------------------------------- |\n| 1 — Aggregator passthrough | A new model id on an existing LiteLLM/OpenRouter route | None | Minutes |\n| 2 — Catalog entry | A new zero-quirk OpenAI-compatible vendor (the Groq/xAI/Together shape) | One data row + one mocked-test section | ~1 hour |\n| 3 — Adapter-based native | A vendor with its own SDK/wire format but a normal request/response HTTP lifecycle | One provider class | Days |\n| 4 — Full custom | SageMaker-class: non-HTTP protocol, SDK-signed auth, bespoke lifecycle | Custom executeStream/doGenerate, possibly own CLI subcommands | Days, needs written justification |\n\nTier 1 adds zero provider coverage — it is a usage change against an\nalready-counted aggregator, never reflected in\ndocs/reference/provider-comparison.md or the README's provider count.\n\nEvery tier that adds a new AIProviderName member (Tier 2 and above) ends\nthe same way: a manifest at\ndocs/provider-integration/manifests/<provider>.json\n(see ../manifests/README.md) and a green run\nof pnpm run verify:provider-onboarding (see\n../../../tools/verify-provider-onboarding.ts).\nTier 1 needs no manifest and no gate — see\ntier-1-aggregator-passthrough.md.\n\nUse ../../../tools/scaffold-provider.ts\n(pnpm run scaffold:provider) to generate the starting-point snippets for\nTiers 2–4 instead of copy-pasting from an existing provider by hand. Both\ntools ship in the tree; there is no manual-fallback era anymore — a PR\nthat skips the gate locally just fails it in CI.","hierarchy":{"lvl0":"Provider Integration","lvl1":"Provider Onboarding Tiers","lvl2":"","lvl3":""}},
9691
+ {"objectID":"de01fcaf93bee302e2fca5e18f85cba5def099685383ec01b61d18489dbca5aa","title":"Provider Onboarding Tiers","url":"/docs/provider-integration/tiers/README#provider-onboarding-tiers","content":"Four tiers, ordered by effort. Always pick the lowest tier that's\nactually true for the provider you're adding — a provider that's\nOpenAI-wire-compatible but gets built as a bespoke Tier 3 subclass \"to be\nsafe\" is exactly the copy-pasted-boilerplate problem this redesign\nexists to eliminate (see ../adr/0002-catalog-over-subclass-default.md).\n\nProvider Integration index\n\n| Tier | Example | Code required | Time |\n| -------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------- | --------------------------------- |\n| 1 — Aggregator passthrough | A new model id on an existing LiteLLM/OpenRouter route | None | Minutes |\n| 2 — Catalog entry | A new zero-quirk OpenAI-compatible vendor (the Groq/xAI/Together shape) | One data row + one mocked-test section | ~1 hour |\n| 3 — Adapter-based native | A vendor with its own SDK/wire format but a normal request/response HTTP lifecycle | One provider class | Days |\n| 4 — Full custom | SageMaker-class: non-HTTP protocol, SDK-signed auth, bespoke lifecycle | Custom executeStream/doGenerate, possibly own CLI subcommands | Days, needs written justification |\n\nTier 1 adds zero provider coverage — it is a usage change against an\nalready-counted aggregator, never reflected in\ndocs/reference/provider-comparison.md or the README's provider count.","hierarchy":{"lvl0":"Provider Integration","lvl1":"Provider Onboarding Tiers","lvl2":"Provider Onboarding Tiers","lvl3":""}},
9692
9692
  {"objectID":"a9753e41ed762a57c15471c49e70dc9917e8388eca055e3dc1c4ce90db6804bb","title":"Tier 1 — Aggregator Passthrough","url":"/docs/provider-integration/tiers/tier-1-aggregator-passthrough","content":"Tier 1 — Aggregator Passthrough\n\nWhen this applies: the model you want is already reachable through a\nprovider NeuroLink already registers as a pass-through aggregator —\ntoday that's litellm (any backend the user's LiteLLM proxy exposes) or\nopenrouter (any model in OpenRouter's catalog). No new\nAIProviderName member, no new provider class, no new catalog row.\n\nWhat you're actually doing: picking a model id string and confirming\nit works — this is a usage change, not an integration change.\n\nChecklist\n[ ] Confirm the aggregator actually serves the model. For LiteLLM,\n check the proxy's /v1/models (or its config.yaml) for the\n model's model_name. For OpenRouter, check\n https://openrouter.ai/models for the exact vendor/model-id\n slug.\n[ ] No AIProviderName enum change. No PROVIDER_DESCRIPTORS change.\n No OPENAI_COMPAT_CATALOG change. If you find yourself editing any\n of those three for a \"Tier 1\" provider, it isn't Tier 1 — restart\n from ../tiers/README.md's decision tree.\n[ ] Optional: if the model needs a friendlier default alias, add it to\n MODEL_ALIASES / DEFAULT_MODEL_ALIASES in\n src/lib/models/modelRegistry.ts. Not required for the model to\n work.\n[ ] Optional: if the model needs a documented env var (e.g., a\n dedicated LiteLLM route), document it in\n docs/getting-started/environment-variables.md.\n[ ] Manually smoke-test the model end-to-end.\n[ ] No manifest file is required — tools/verify-provider-onboarding.ts\n (see ../../../tools/verify-provider-onboarding.ts) only gates\n new AIProviderName members, and Tier 1 never adds one.\n[ ] Confirmed: no edit to docs/reference/provider-comparison.md or\n README provider count for this change.\n\nVerification commands\n\nBoth should return a normal GenerateResult with non-empty content. If\neither 400s with an \"unknown model\" style error, the aggregator doesn't\nactually serve that model yet — fix the aggregator-side config, not\nNeuroLink.","hierarchy":{"lvl0":"Provider Integration","lvl1":"Tier 1 — Aggregator Passthrough","lvl2":"","lvl3":""}},
9693
9693
  {"objectID":"f04f6a19785cf4ff03d1ed0808f35ddbedb0508a51ca0590d1a57ff6703b40eb","title":"Tier 1 — Aggregator Passthrough","url":"/docs/provider-integration/tiers/tier-1-aggregator-passthrough#tier-1-aggregator-passthrough","content":"When this applies: the model you want is already reachable through a\nprovider NeuroLink already registers as a pass-through aggregator —\ntoday that's litellm (any backend the user's LiteLLM proxy exposes) or\nopenrouter (any model in OpenRouter's catalog). No new\nAIProviderName member, no new provider class, no new catalog row.\n\nWhat you're actually doing: picking a model id string and confirming\nit works — this is a usage change, not an integration change.","hierarchy":{"lvl0":"Provider Integration","lvl1":"Tier 1 — Aggregator Passthrough","lvl2":"Tier 1 — Aggregator Passthrough","lvl3":""}},
9694
9694
  {"objectID":"209d4e31f0601ea928a348869f63ad32c049e6624269f06d4fe795f15d30d311","title":"Checklist","url":"/docs/provider-integration/tiers/tier-1-aggregator-passthrough#checklist","content":"[ ] Confirm the aggregator actually serves the model. For LiteLLM,\n check the proxy's /v1/models (or its config.yaml) for the\n model's model_name. For OpenRouter, check\n https://openrouter.ai/models for the exact vendor/model-id\n slug.\n[ ] No AIProviderName enum change. No PROVIDER_DESCRIPTORS change.\n No OPENAI_COMPAT_CATALOG change. If you find yourself editing any\n of those three for a \"Tier 1\" provider, it isn't Tier 1 — restart\n from ../tiers/README.md's decision tree.\n[ ] Optional: if the model needs a friendlier default alias, add it to\n MODEL_ALIASES / DEFAULT_MODEL_ALIASES in\n src/lib/models/modelRegistry.ts. Not required for the model to\n work.\n[ ] Optional: if the model needs a documented env var (e.g., a\n dedicated LiteLLM route), document it in\n docs/getting-started/environment-variables.md.\n[ ] Manually smoke-test the model end-to-end.\n[ ] No manifest file is required — tools/verify-provider-onboarding.ts\n (see ../../../tools/verify-provider-onboarding.ts) only gates\n new AIProviderName members, and Tier 1 never adds one.\n[ ] Confirmed: no edit to docs/reference/provider-comparison.md or\n README provider count for this change.","hierarchy":{"lvl0":"Provider Integration","lvl1":"Tier 1 — Aggregator Passthrough","lvl2":"Checklist","lvl3":""}},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "12.47.1",
3
+ "version": "12.47.3",
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": {
@@ -280,6 +280,8 @@
280
280
  "test:anthropic-loop-characterization": "tsx test/continuous-test-suite-anthropic-loop-characterization.ts",
281
281
  "test:anthropic-execution-control": "tsx test/continuous-test-suite-anthropic-execution-control.ts",
282
282
  "test:aistudio-loop-characterization": "tsx test/continuous-test-suite-aistudio-loop-characterization.ts",
283
+ "test:google-genai-proxy": "tsx test/continuous-test-suite-google-genai-proxy.ts",
284
+ "test:google-genai-sdk": "tsx test/continuous-test-suite-google-genai-sdk.ts",
283
285
  "test:docs-mcp": "pnpm exec tsx test/continuous-test-suite-docs-mcp.ts",
284
286
  "test:docs-search-index": "pnpm exec tsx test/continuous-test-suite-docs-search-index.ts",
285
287
  "test:docs-snippets": "pnpm exec tsx test/continuous-test-suite-docs-snippets.ts",
@@ -430,7 +432,7 @@
430
432
  "@anthropic-ai/vertex-sdk": "^0.16.0",
431
433
  "@aws-sdk/types": "^3.862.0",
432
434
  "@cfworker/json-schema": "^4.1.1",
433
- "@google/genai": "^1.43.0",
435
+ "@google/genai": "^2.23.0",
434
436
  "@modelcontextprotocol/sdk": "^1.27.1",
435
437
  "@opentelemetry/api-logs": "^0.214.0",
436
438
  "@opentelemetry/context-async-hooks": "^2.6.1",