@juspay/neurolink 12.47.0 → 12.47.2
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.
- package/CHANGELOG.md +3 -3
- package/dist/adapters/video/ffmpegAdapter.d.ts +3 -1
- package/dist/adapters/video/ffmpegAdapter.js +13 -2
- package/dist/browser/neurolink.min.js +635 -634
- package/dist/cli/commands/setup.js +6 -5
- package/dist/cli/proxy-clients/copilot.js +55 -1
- package/dist/constants/contextWindows.js +12 -0
- package/dist/core/toolExecutionGuards.d.ts +1 -9
- package/dist/core/toolExecutionGuards.js +1 -9
- package/dist/factories/providerFactory.js +16 -1
- package/dist/files/fileReferenceRegistry.js +3 -3
- package/dist/neurolink.js +13 -2
- package/dist/processors/media/VideoProcessor.js +16 -4
- package/dist/providers/googleAiStudio/client.js +52 -11
- package/dist/providers/googleNativeGemini3/utils.d.ts +9 -0
- package/dist/providers/googleNativeGemini3/utils.js +9 -0
- package/dist/providers/googleVertex/client.js +7 -8
- package/dist/proxy/proxyFetch.d.ts +16 -1
- package/dist/proxy/proxyFetch.js +12 -24
- package/docs-site/static/search-index.json +2 -2
- package/package.json +4 -2
|
@@ -19,6 +19,7 @@ import { handleBedrockSetup } from "./setup-bedrock.js";
|
|
|
19
19
|
import { handleGCPSetup } from "./setup-gcp.js";
|
|
20
20
|
import { handleHuggingFaceSetup } from "./setup-huggingface.js";
|
|
21
21
|
import { handleMistralSetup } from "./setup-mistral.js";
|
|
22
|
+
import { OpenRouterModels, } from "../../constants/enums.js";
|
|
22
23
|
import { PROVIDER_DESCRIPTORS_BY_NAME } from "../../factories/providerDescriptors.js";
|
|
23
24
|
import { createCohereConfig, createDeepSeekConfig, createIdeogramConfig, createJinaConfig, createNvidiaNimConfig, createOpenAICompatibleConfig, createRecraftConfig, createReplicateConfig, createStabilityConfig, createVoyageConfig, createTypeSafeConfig, createLayaConfig, createXorConfig, createPerplexityDeciderConfig, createCloudflareClefConfig, satisfiesFallbacks, } from "../../utils/providerConfig.js";
|
|
24
25
|
import { getCatalogJsonEntries, buildCatalogConfigOptions, } from "../../providers/catalog/loader.js";
|
|
@@ -571,13 +572,13 @@ async function handleOpenRouterSetup() {
|
|
|
571
572
|
logger.always(chalk.cyan(" export OPENROUTER_API_KEY=your_api_key_here"));
|
|
572
573
|
logger.always("");
|
|
573
574
|
logger.always(chalk.yellow("Step 3: Test the configuration"));
|
|
574
|
-
logger.always(chalk.cyan(
|
|
575
|
+
logger.always(chalk.cyan(` neurolink generate "Hello!" --provider openrouter --model ${OpenRouterModels.GEMINI_2_5_FLASH}`));
|
|
575
576
|
logger.always("");
|
|
576
577
|
logger.always(chalk.green("Available models include:"));
|
|
577
|
-
logger.always(
|
|
578
|
-
logger.always(
|
|
579
|
-
logger.always(
|
|
580
|
-
logger.always(
|
|
578
|
+
logger.always(` • ${OpenRouterModels.CLAUDE_SONNET_4_6} - Best for analysis`);
|
|
579
|
+
logger.always(` • ${OpenRouterModels.GPT_4O} - Industry standard`);
|
|
580
|
+
logger.always(` • ${OpenRouterModels.GEMINI_2_5_FLASH} - Fast and efficient`);
|
|
581
|
+
logger.always(` • ${OpenRouterModels.LLAMA_3_1_70B} - Open source`);
|
|
581
582
|
logger.always("");
|
|
582
583
|
logger.always(chalk.gray("See all models at: https://openrouter.ai/models"));
|
|
583
584
|
}
|
|
@@ -46,12 +46,66 @@ function getCopilotEnvPath() {
|
|
|
46
46
|
function getProfileCandidates() {
|
|
47
47
|
return [".zshrc", ".zprofile", ".bashrc", ".bash_profile", ".profile"].map((name) => join(homedir(), name));
|
|
48
48
|
}
|
|
49
|
+
/** A command that is `source`/`.` of a path ending in exactly `copilot-env.sh`, optionally after `then`/`else`/`do`. */
|
|
50
|
+
const ENV_SCRIPT_SOURCE_COMMAND = /^\s*(?:(?:then|else|do)\s+)?(?:source|\.)\s+(["']?)(?:[^\s"']*\/)?copilot-env\.sh\1(?:\s|$)/;
|
|
51
|
+
/**
|
|
52
|
+
* One profile line as the commands it runs, comment dropped. Quote-aware, so a
|
|
53
|
+
* `;` or `#` inside quotes neither splits nor starts a comment. Heredocs,
|
|
54
|
+
* multi-line strings and line continuations are not modelled, so a source line
|
|
55
|
+
* inside one is still counted, and a source built with `eval` is missed. Both
|
|
56
|
+
* are rare; this guards against the common mentions (a comment, an `echo`, a
|
|
57
|
+
* file-existence test, a `.bak` name) that suppressed the note before.
|
|
58
|
+
*/
|
|
59
|
+
function splitProfileCommands(line) {
|
|
60
|
+
const commands = [];
|
|
61
|
+
let current = "";
|
|
62
|
+
let quote = null;
|
|
63
|
+
for (let i = 0; i < line.length; i++) {
|
|
64
|
+
const char = line[i];
|
|
65
|
+
if (quote) {
|
|
66
|
+
current += char;
|
|
67
|
+
if (quote === '"' && char === "\\") {
|
|
68
|
+
current += line[i + 1] ?? "";
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
else if (char === quote) {
|
|
72
|
+
quote = null;
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (char === "\\") {
|
|
77
|
+
current += char + (line[i + 1] ?? "");
|
|
78
|
+
i++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (char === '"' || char === "'") {
|
|
82
|
+
quote = char;
|
|
83
|
+
current += char;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (char === "#" &&
|
|
87
|
+
(current === "" || /\s/.test(current[current.length - 1]))) {
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
if (char === ";" || char === "&" || char === "|") {
|
|
91
|
+
commands.push(current);
|
|
92
|
+
current = "";
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
current += char;
|
|
96
|
+
}
|
|
97
|
+
commands.push(current);
|
|
98
|
+
return commands;
|
|
99
|
+
}
|
|
49
100
|
/** Whether any shell profile already sources the generated script. */
|
|
50
101
|
async function isEnvScriptSourced() {
|
|
51
102
|
const fs = await import("fs");
|
|
52
103
|
for (const profile of getProfileCandidates()) {
|
|
53
104
|
try {
|
|
54
|
-
if (fs
|
|
105
|
+
if (fs
|
|
106
|
+
.readFileSync(profile, "utf8")
|
|
107
|
+
.split(/\r?\n/)
|
|
108
|
+
.some((line) => splitProfileCommands(line).some((command) => ENV_SCRIPT_SOURCE_COMMAND.test(command)))) {
|
|
55
109
|
return true;
|
|
56
110
|
}
|
|
57
111
|
}
|
|
@@ -122,6 +122,11 @@ export const MODEL_CONTEXT_WINDOWS = {
|
|
|
122
122
|
"claude-fable-5-1": 1_000_000,
|
|
123
123
|
// Claude 5 (mid 2026) — 1M context window
|
|
124
124
|
"claude-sonnet-5": 1_000_000,
|
|
125
|
+
"claude-opus-5": 1_000_000,
|
|
126
|
+
"claude-fable-5": 1_000_000,
|
|
127
|
+
// Claude 4.7 / 4.8 — 1M context window (platform.claude.com/docs/en/models)
|
|
128
|
+
"claude-opus-4-8": 1_000_000,
|
|
129
|
+
"claude-opus-4-7": 1_000_000,
|
|
125
130
|
// Claude 4.6 (Feb 2026) — 1M context window
|
|
126
131
|
"claude-opus-4-6": 1_000_000,
|
|
127
132
|
"claude-sonnet-4-6": 1_000_000,
|
|
@@ -220,6 +225,13 @@ export const MODEL_CONTEXT_WINDOWS = {
|
|
|
220
225
|
"claude-sonnet-5-5": 1_000_000,
|
|
221
226
|
"claude-fable-5-1": 1_000_000,
|
|
222
227
|
"claude-sonnet-5": 1_000_000,
|
|
228
|
+
// Placed before the 200K `claude-opus-4` prefix key below: the lookup takes
|
|
229
|
+
// the first prefix that matches, so a dated `claude-opus-4-8@...` id would
|
|
230
|
+
// otherwise land on 200K.
|
|
231
|
+
"claude-opus-5": 1_000_000,
|
|
232
|
+
"claude-fable-5": 1_000_000,
|
|
233
|
+
"claude-opus-4-8": 1_000_000,
|
|
234
|
+
"claude-opus-4-7": 1_000_000,
|
|
223
235
|
"claude-opus-4-6": 1_000_000,
|
|
224
236
|
"claude-sonnet-4-6": 1_000_000,
|
|
225
237
|
"claude-sonnet-4-5": 200_000,
|
|
@@ -13,15 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { Tool, ToolExecutionGuards } from "../types/index.js";
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
* via buildNativeToolDeclarations. `search_tools` (tools.discovery) hydrates
|
|
18
|
-
* discovered tools into the live record between steps; without this refresh
|
|
19
|
-
* they stay invisible to the rest of the turn and every call dies as
|
|
20
|
-
* TOOL_NOT_FOUND. Mutates the snapshot in place — the request config holds
|
|
21
|
-
* `toolsConfig` by reference — and returns true when anything was added.
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* Everything a native Gemini loop wraps around a tool call that the shared
|
|
16
|
+
* Everything a native provider loop wraps around a tool call that the shared
|
|
25
17
|
* engine does not do itself.
|
|
26
18
|
*
|
|
27
19
|
* Order matters. `raceWithAbort` sits INSIDE `withTimeout` so a turn-level
|
|
@@ -13,15 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { raceWithAbort, withTimeout } from "../utils/async/index.js";
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
* via buildNativeToolDeclarations. `search_tools` (tools.discovery) hydrates
|
|
18
|
-
* discovered tools into the live record between steps; without this refresh
|
|
19
|
-
* they stay invisible to the rest of the turn and every call dies as
|
|
20
|
-
* TOOL_NOT_FOUND. Mutates the snapshot in place — the request config holds
|
|
21
|
-
* `toolsConfig` by reference — and returns true when anything was added.
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* Everything a native Gemini loop wraps around a tool call that the shared
|
|
16
|
+
* Everything a native provider loop wraps around a tool call that the shared
|
|
25
17
|
* engine does not do itself.
|
|
26
18
|
*
|
|
27
19
|
* Order matters. `raceWithAbort` sits INSIDE `withTimeout` so a turn-level
|
|
@@ -22,6 +22,17 @@ export function resolveCredentialKey(providerName) {
|
|
|
22
22
|
const normalized = providerName.toLowerCase();
|
|
23
23
|
return (ProviderFactory.getDescriptor(normalized)?.credentialsKey ?? normalized);
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* registerProvider() takes a class as well as a factory (ProviderConstructor),
|
|
27
|
+
* and calling a class without `new` throws. A prototype test cannot tell them
|
|
28
|
+
* apart, because every non-arrow function's prototype points back at itself,
|
|
29
|
+
* so the source text decides. A factory is therefore never constructed and
|
|
30
|
+
* never retried after it throws.
|
|
31
|
+
*/
|
|
32
|
+
function isClassConstructor(candidate) {
|
|
33
|
+
return (typeof candidate === "function" &&
|
|
34
|
+
/^class[\s{]/.test(Function.prototype.toString.call(candidate)));
|
|
35
|
+
}
|
|
25
36
|
/**
|
|
26
37
|
* True Factory Pattern implementation for AI Providers
|
|
27
38
|
* Uses registration-based approach to eliminate switch statements
|
|
@@ -113,7 +124,11 @@ export class ProviderFactory {
|
|
|
113
124
|
if (typeof registration.constructor !== "function") {
|
|
114
125
|
throw new Error(`Invalid constructor for provider ${providerName}: not a function`);
|
|
115
126
|
}
|
|
116
|
-
|
|
127
|
+
// A factory keeps being called as a method of the registration, as it
|
|
128
|
+
// always was; only a class is constructed.
|
|
129
|
+
const factoryResult = isClassConstructor(registration.constructor)
|
|
130
|
+
? new registration.constructor(model, resolvedProviderName, sdk, region, scopedCredentials)
|
|
131
|
+
: registration.constructor(model, resolvedProviderName, sdk, region, scopedCredentials);
|
|
117
132
|
const result = factoryResult instanceof Promise ? await factoryResult : factoryResult;
|
|
118
133
|
return result;
|
|
119
134
|
}
|
|
@@ -827,7 +827,7 @@ export class FileReferenceRegistry {
|
|
|
827
827
|
extractedText = await this.extractWordText(buffer, ref);
|
|
828
828
|
break;
|
|
829
829
|
case "pptx":
|
|
830
|
-
extractedText = await this.extractPptxText(buffer);
|
|
830
|
+
extractedText = await this.extractPptxText(buffer, ref.filename);
|
|
831
831
|
break;
|
|
832
832
|
case "video":
|
|
833
833
|
extractedText = await this.extractVideoContent(buffer, ref);
|
|
@@ -992,13 +992,13 @@ export class FileReferenceRegistry {
|
|
|
992
992
|
/**
|
|
993
993
|
* Extract text from a PowerPoint file using PptxProcessor.
|
|
994
994
|
*/
|
|
995
|
-
async extractPptxText(buffer) {
|
|
995
|
+
async extractPptxText(buffer, filename) {
|
|
996
996
|
try {
|
|
997
997
|
const { PptxProcessor } = await import("../processors/document/PptxProcessor.js");
|
|
998
998
|
return await PptxProcessor.extractText(buffer);
|
|
999
999
|
}
|
|
1000
1000
|
catch (err) {
|
|
1001
|
-
logger.warn(`[FileReferenceRegistry] PPTX extraction failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1001
|
+
logger.warn(`[FileReferenceRegistry] PPTX extraction failed for "${filename}": ${err instanceof Error ? err.message : String(err)}`);
|
|
1002
1002
|
return null;
|
|
1003
1003
|
}
|
|
1004
1004
|
}
|
package/dist/neurolink.js
CHANGED
|
@@ -8640,11 +8640,22 @@ Current user's request: ${currentInput}`;
|
|
|
8640
8640
|
}
|
|
8641
8641
|
yield fallbackChunk;
|
|
8642
8642
|
}
|
|
8643
|
+
// Read after the drain: a background-loop provider (native Vertex+Claude)
|
|
8644
|
+
// fills these while the stream is consumed, so the values taken before
|
|
8645
|
+
// the loop are still empty for it. Without this, a fallback that ran
|
|
8646
|
+
// tools reports none on the result and in the end-of-turn events, and
|
|
8647
|
+
// the 0-output gate below counts tool calls that had not appeared yet.
|
|
8648
|
+
const finalToolCalls = fallbackResult.toolCalls ?? [];
|
|
8649
|
+
const finalToolResults = fallbackResult.toolResults ?? [];
|
|
8643
8650
|
if (fallbackRealOutputChunks === 0 &&
|
|
8644
|
-
|
|
8645
|
-
|
|
8651
|
+
finalToolCalls.length === 0 &&
|
|
8652
|
+
finalToolResults.length === 0) {
|
|
8646
8653
|
throw new Error(`Fallback provider ${fallbackRoute.provider} also returned 0 real output chunks (chunkCount=${fallbackChunkCount}, sentinel-only or empty)`);
|
|
8647
8654
|
}
|
|
8655
|
+
streamState.toolCalls = finalToolCalls;
|
|
8656
|
+
streamState.toolResults = finalToolResults;
|
|
8657
|
+
streamState.finishReason =
|
|
8658
|
+
fallbackResult.finishReason ?? streamState.finishReason;
|
|
8648
8659
|
// Fallback succeeded - likely guardrails blocked primary
|
|
8649
8660
|
metadata.fallbackProvider = fallbackRoute.provider;
|
|
8650
8661
|
metadata.fallbackModel = fallbackRoute.model;
|
|
@@ -660,6 +660,7 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
660
660
|
*/
|
|
661
661
|
async probeVideoWithFfmpeg(filePath) {
|
|
662
662
|
let report;
|
|
663
|
+
let exitFailure;
|
|
663
664
|
try {
|
|
664
665
|
const { stderr } = await runFfmpeg(["-hide_banner", "-i", filePath, "-t", "0", "-f", "null", "-"], { timeoutMs: VIDEO_CONFIG.FFPROBE_TIMEOUT_MS });
|
|
665
666
|
report = stderr;
|
|
@@ -668,18 +669,29 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
668
669
|
// A non-zero exit still carries the stream report when the failure came
|
|
669
670
|
// after the input was opened.
|
|
670
671
|
const stderr = error.stderr;
|
|
672
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
671
673
|
if (typeof stderr !== "string") {
|
|
672
674
|
return {
|
|
673
675
|
success: false,
|
|
674
|
-
error: `ffmpeg probe failed: ${
|
|
676
|
+
error: `ffmpeg probe failed: ${message}`,
|
|
675
677
|
};
|
|
676
678
|
}
|
|
677
679
|
report = stderr;
|
|
680
|
+
exitFailure = message;
|
|
678
681
|
}
|
|
679
682
|
const data = this.parseFfmpegInputReport(report);
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
+
if (data) {
|
|
684
|
+
return { success: true, data };
|
|
685
|
+
}
|
|
686
|
+
// Keep ffmpeg's own words when it failed as well as found no duration:
|
|
687
|
+
// a file it cannot read at all is the common case, and the reason is the
|
|
688
|
+
// only thing the warning that follows can tell the reader.
|
|
689
|
+
return {
|
|
690
|
+
success: false,
|
|
691
|
+
error: exitFailure
|
|
692
|
+
? `ffmpeg reported no duration: ${exitFailure}`
|
|
693
|
+
: "ffmpeg reported no duration",
|
|
694
|
+
};
|
|
683
695
|
}
|
|
684
696
|
/**
|
|
685
697
|
* Parse the input section of an ffmpeg banner: `Duration:`, `bitrate:` and
|
|
@@ -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 {
|
|
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
|
|
149
|
-
//
|
|
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
|
-
|
|
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
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
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,
|
|
@@ -52,6 +52,15 @@ export declare function buildNativeToolDeclarations(tools: Record<string, Tool>,
|
|
|
52
52
|
* duplicate side effects the moment the model repeats itself.
|
|
53
53
|
*/
|
|
54
54
|
export declare function buildDedupedEngineTools(declarations: NativeToolDeclarationsResult | undefined, tools: Record<string, Tool> | undefined, guards?: ToolExecutionGuards): NonNullable<AgenticLoopOptions["tools"]>;
|
|
55
|
+
/**
|
|
56
|
+
* Mid-turn tool sync for the native Gemini loops that build their snapshot
|
|
57
|
+
* via buildNativeToolDeclarations. `search_tools` (tools.discovery) hydrates
|
|
58
|
+
* discovered tools into the live record between steps; without this refresh
|
|
59
|
+
* they stay invisible to the rest of the turn and every call dies as
|
|
60
|
+
* TOOL_NOT_FOUND. Mutates the snapshot in place — the request config holds
|
|
61
|
+
* `toolsConfig` by reference — and returns the original names of the tools it
|
|
62
|
+
* added (empty when nothing was added).
|
|
63
|
+
*/
|
|
55
64
|
export declare function refreshNativeToolDeclarations(liveTools: Record<string, Tool> | undefined, current: NativeToolDeclarationsResult): string[];
|
|
56
65
|
/**
|
|
57
66
|
* Build the native @google/genai config object shared by stream and generate.
|
|
@@ -381,6 +381,15 @@ export function buildDedupedEngineTools(declarations, tools, guards) {
|
|
|
381
381
|
}
|
|
382
382
|
return engineTools;
|
|
383
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Mid-turn tool sync for the native Gemini loops that build their snapshot
|
|
386
|
+
* via buildNativeToolDeclarations. `search_tools` (tools.discovery) hydrates
|
|
387
|
+
* discovered tools into the live record between steps; without this refresh
|
|
388
|
+
* they stay invisible to the rest of the turn and every call dies as
|
|
389
|
+
* TOOL_NOT_FOUND. Mutates the snapshot in place — the request config holds
|
|
390
|
+
* `toolsConfig` by reference — and returns the original names of the tools it
|
|
391
|
+
* added (empty when nothing was added).
|
|
392
|
+
*/
|
|
384
393
|
export function refreshNativeToolDeclarations(liveTools, current) {
|
|
385
394
|
if (!liveTools) {
|
|
386
395
|
return [];
|
|
@@ -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 {
|
|
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
|
|
1151
|
-
//
|
|
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
|
-
//
|
|
1155
|
-
// whenever httpOptions.baseUrl is undefined, so omitting the key
|
|
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
|
-
|
|
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;
|
package/dist/proxy/proxyFetch.js
CHANGED
|
@@ -698,31 +698,19 @@ export function getProxyStatus() {
|
|
|
698
698
|
};
|
|
699
699
|
}
|
|
700
700
|
/**
|
|
701
|
-
*
|
|
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
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
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
|
-
*
|
|
713
|
-
*
|
|
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
|
-
|
|
722
|
-
|
|
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
|
}
|
|
@@ -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.
|
|
3
|
+
"version": "12.47.2",
|
|
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": "^
|
|
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",
|