@juspay/neurolink 11.11.3 → 11.11.5
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 +1 -1
- package/dist/browser/neurolink.min.js +384 -384
- package/dist/cli/commands/proxy.d.ts +18 -0
- package/dist/cli/commands/proxy.js +38 -16
- package/dist/lib/providers/googleAiStudio/client.js +96 -86
- package/dist/providers/googleAiStudio/client.js +96 -86
- package/package.json +1 -1
|
@@ -58,6 +58,23 @@ export declare function isRollingHandoffCapable(state: ProxySupervisorState | nu
|
|
|
58
58
|
* confirm a mismatch" and falls through to the args-only result.
|
|
59
59
|
*/
|
|
60
60
|
export declare function processLooksLikeProxySupervisor(pid: number, expectedStartTimeIso?: string): Promise<boolean>;
|
|
61
|
+
declare function getOpenCodeConfigDir(): string;
|
|
62
|
+
declare function getOpenCodeConfigPath(): string;
|
|
63
|
+
declare function setOpenCodeProxySettings(baseUrl: string, proxyKey?: string): Promise<boolean>;
|
|
64
|
+
declare function clearOpenCodeProxySettings(expectedBaseUrl?: string): Promise<boolean>;
|
|
65
|
+
/**
|
|
66
|
+
* Test-only export (CLAUDE.md rule 15 determinism exception). The OpenCode
|
|
67
|
+
* client writers resolve paths from the environment and are only reachable
|
|
68
|
+
* from `proxy start` / `proxy setup`, neither of which can be driven against a
|
|
69
|
+
* throwaway HOME without starting a real server. Consumed by
|
|
70
|
+
* test/continuous-test-suite-proxy.ts.
|
|
71
|
+
*/
|
|
72
|
+
export declare const __openCodeTestHooks: {
|
|
73
|
+
getOpenCodeConfigDir: typeof getOpenCodeConfigDir;
|
|
74
|
+
getOpenCodeConfigPath: typeof getOpenCodeConfigPath;
|
|
75
|
+
setOpenCodeProxySettings: typeof setOpenCodeProxySettings;
|
|
76
|
+
clearOpenCodeProxySettings: typeof clearOpenCodeProxySettings;
|
|
77
|
+
};
|
|
61
78
|
export declare function probeProxyHealth(host: string, port: number, timeoutMs: number): Promise<ProxyHealthProbe>;
|
|
62
79
|
export declare function mapClaudeErrorTypeToStatus(errorType?: string): number;
|
|
63
80
|
export declare function createProxyStartApp(params: {
|
|
@@ -84,3 +101,4 @@ export declare const proxyGuardCommand: CommandModule<object, ProxyGuardArgs>;
|
|
|
84
101
|
export declare const proxySetupCommand: CommandModule;
|
|
85
102
|
export declare const proxyInstallCommand: CommandModule;
|
|
86
103
|
export declare const proxyUninstallCommand: CommandModule;
|
|
104
|
+
export {};
|
|
@@ -513,13 +513,17 @@ async function clearClaudeProxySettings(expectedBaseUrl) {
|
|
|
513
513
|
// OPENCODE AUTO-CONFIGURATION
|
|
514
514
|
// =============================================================================
|
|
515
515
|
function getOpenCodeConfigDir() {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
//
|
|
516
|
+
// OpenCode resolves this with the unmodified `xdg-basedir` package —
|
|
517
|
+
// `XDG_CONFIG_HOME || ~/.config` — on every platform, macOS included. There
|
|
518
|
+
// is deliberately no darwin branch here: `~/Library/Application Support/
|
|
519
|
+
// opencode` is not a path OpenCode reads. (The similar-looking literal in
|
|
520
|
+
// OpenCode's binary is `systemManagedConfigDir()`, an MDM policy directory
|
|
521
|
+
// at the filesystem root with no $HOME prefix.)
|
|
520
522
|
return join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "opencode");
|
|
521
523
|
}
|
|
522
|
-
|
|
524
|
+
function getOpenCodeConfigPath() {
|
|
525
|
+
return join(getOpenCodeConfigDir(), "opencode.json");
|
|
526
|
+
}
|
|
523
527
|
/**
|
|
524
528
|
* Key under which we persist the snapshot of the user's pre-existing
|
|
525
529
|
* `provider.neurolink` config inside `opencode.json` itself. Persisting (rather
|
|
@@ -536,12 +540,14 @@ async function setOpenCodeProxySettings(baseUrl, proxyKey) {
|
|
|
536
540
|
fs.accessSync(configDir);
|
|
537
541
|
}
|
|
538
542
|
catch {
|
|
539
|
-
// OpenCode not installed — config directory does not exist
|
|
540
|
-
|
|
543
|
+
// OpenCode not installed — config directory does not exist. Report the
|
|
544
|
+
// skip so the caller does not print a success message for work that did
|
|
545
|
+
// not happen.
|
|
546
|
+
return false;
|
|
541
547
|
}
|
|
542
548
|
let config;
|
|
543
549
|
try {
|
|
544
|
-
config = JSON.parse(fs.readFileSync(
|
|
550
|
+
config = JSON.parse(fs.readFileSync(getOpenCodeConfigPath(), "utf8"));
|
|
545
551
|
}
|
|
546
552
|
catch {
|
|
547
553
|
// file missing/invalid — create fresh config object
|
|
@@ -571,13 +577,14 @@ async function setOpenCodeProxySettings(baseUrl, proxyKey) {
|
|
|
571
577
|
},
|
|
572
578
|
};
|
|
573
579
|
config.provider = provider;
|
|
574
|
-
fs.writeFileSync(
|
|
580
|
+
fs.writeFileSync(getOpenCodeConfigPath(), JSON.stringify(config, null, 2));
|
|
581
|
+
return true;
|
|
575
582
|
}
|
|
576
583
|
async function clearOpenCodeProxySettings(expectedBaseUrl) {
|
|
577
584
|
const fs = await import("fs");
|
|
578
585
|
let config;
|
|
579
586
|
try {
|
|
580
|
-
config = JSON.parse(fs.readFileSync(
|
|
587
|
+
config = JSON.parse(fs.readFileSync(getOpenCodeConfigPath(), "utf8"));
|
|
581
588
|
}
|
|
582
589
|
catch {
|
|
583
590
|
return false;
|
|
@@ -622,9 +629,22 @@ async function clearOpenCodeProxySettings(expectedBaseUrl) {
|
|
|
622
629
|
return false;
|
|
623
630
|
}
|
|
624
631
|
config.provider = provider;
|
|
625
|
-
fs.writeFileSync(
|
|
632
|
+
fs.writeFileSync(getOpenCodeConfigPath(), JSON.stringify(config, null, 2));
|
|
626
633
|
return hadNeurolink;
|
|
627
634
|
}
|
|
635
|
+
/**
|
|
636
|
+
* Test-only export (CLAUDE.md rule 15 determinism exception). The OpenCode
|
|
637
|
+
* client writers resolve paths from the environment and are only reachable
|
|
638
|
+
* from `proxy start` / `proxy setup`, neither of which can be driven against a
|
|
639
|
+
* throwaway HOME without starting a real server. Consumed by
|
|
640
|
+
* test/continuous-test-suite-proxy.ts.
|
|
641
|
+
*/
|
|
642
|
+
export const __openCodeTestHooks = {
|
|
643
|
+
getOpenCodeConfigDir,
|
|
644
|
+
getOpenCodeConfigPath,
|
|
645
|
+
setOpenCodeProxySettings,
|
|
646
|
+
clearOpenCodeProxySettings,
|
|
647
|
+
};
|
|
628
648
|
// =============================================================================
|
|
629
649
|
// CODEX (ChatGPT) AUTO-CONFIGURATION
|
|
630
650
|
// =============================================================================
|
|
@@ -2709,9 +2729,10 @@ async function startProxyRuntime(params) {
|
|
|
2709
2729
|
(error instanceof Error ? error.message : String(error)));
|
|
2710
2730
|
}
|
|
2711
2731
|
try {
|
|
2712
|
-
await setOpenCodeProxySettings(`${url}/v1`)
|
|
2713
|
-
|
|
2714
|
-
|
|
2732
|
+
if (await setOpenCodeProxySettings(`${url}/v1`)) {
|
|
2733
|
+
logger.always(chalk.green(" ✓ Auto-configured OpenCode settings"));
|
|
2734
|
+
logger.always(chalk.dim(" Restart OpenCode to connect through proxy"));
|
|
2735
|
+
}
|
|
2715
2736
|
}
|
|
2716
2737
|
catch (error) {
|
|
2717
2738
|
logger.debug("[proxy] Failed to auto-configure OpenCode: " +
|
|
@@ -4155,8 +4176,9 @@ export const proxySetupCommand = {
|
|
|
4155
4176
|
console.info(chalk.yellow(` Set manually: ANTHROPIC_BASE_URL=${url}`));
|
|
4156
4177
|
}
|
|
4157
4178
|
try {
|
|
4158
|
-
await setOpenCodeProxySettings(`${url}/v1`)
|
|
4159
|
-
|
|
4179
|
+
if (await setOpenCodeProxySettings(`${url}/v1`)) {
|
|
4180
|
+
console.info(chalk.green(" ✓ OpenCode configured"));
|
|
4181
|
+
}
|
|
4160
4182
|
}
|
|
4161
4183
|
catch (e) {
|
|
4162
4184
|
console.info(chalk.yellow(` ⚠ Could not auto-configure OpenCode: ${e instanceof Error ? e.message : String(e)}`));
|
|
@@ -17,7 +17,7 @@ import { withTimeout } from "../../utils/async/index.js";
|
|
|
17
17
|
import { estimateTokens } from "../../utils/tokenEstimation.js";
|
|
18
18
|
import { transformToolExecutions } from "../../utils/transformationUtils.js";
|
|
19
19
|
import { resolveToolExecutionRecords } from "../../core/toolExecutionRecorder.js";
|
|
20
|
-
import { buildGeminiResponseSchema, buildNativeConfig,
|
|
20
|
+
import { buildGeminiResponseSchema, buildNativeConfig, computeMaxSteps, createContextGuard, buildUserPartsWithMultimodal, extractThoughtSignature, handleMaxStepsTermination, prependConversationMessages, } from "../googleNativeGemini3/index.js";
|
|
21
21
|
import { createStreamChannel } from "../../core/streamChannel.js";
|
|
22
22
|
import { toNativeToolDeclarations } from "../../core/nativeToolFormat.js";
|
|
23
23
|
import { createProxyFetch } from "../../proxy/proxyFetch.js";
|
|
@@ -1004,8 +1004,6 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
1004
1004
|
});
|
|
1005
1005
|
// Convert tools (a0269210: trust options.tools — already merged + filtered upstream)
|
|
1006
1006
|
let toolsConfig;
|
|
1007
|
-
let executeMap = new DedupExecuteMap();
|
|
1008
|
-
let originalNameMap = new Map();
|
|
1009
1007
|
let declarationsResult;
|
|
1010
1008
|
const shouldUseTools = !options.disableTools;
|
|
1011
1009
|
// Structured output (JSON format or schema) is incompatible with
|
|
@@ -1032,8 +1030,6 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
1032
1030
|
const result = toNativeToolDeclarations(tools, "functionDeclarations");
|
|
1033
1031
|
declarationsResult = result;
|
|
1034
1032
|
toolsConfig = result.toolsConfig;
|
|
1035
|
-
executeMap = result.executeMap;
|
|
1036
|
-
originalNameMap = result.originalNameMap;
|
|
1037
1033
|
logger.debug("[GoogleAIStudio] Converted tools for native SDK generate", {
|
|
1038
1034
|
toolCount: toolsConfig[0].functionDeclarations.length,
|
|
1039
1035
|
toolNames: toolsConfig[0].functionDeclarations.map((t) => t.name),
|
|
@@ -1066,114 +1062,128 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
1066
1062
|
const allToolCalls = [];
|
|
1067
1063
|
const toolExecutions = [];
|
|
1068
1064
|
let step = 0;
|
|
1069
|
-
const failedTools = new Map();
|
|
1070
1065
|
// Cheap reclaim trigger — see the stream twin.
|
|
1071
1066
|
const contextGuard = createContextGuard(getContextWindowSize("googleAiStudio", modelName));
|
|
1072
1067
|
// Agentic loop for tool calling
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1068
|
+
// Same shared engine as the streaming twin. This path has no
|
|
1069
|
+
// consumer channel — generate() returns one result rather than
|
|
1070
|
+
// streaming — so the engine's stream is drained and discarded, and
|
|
1071
|
+
// the turn's text comes from the result.
|
|
1072
|
+
const baseAdapter = createGeminiLoopAdapter({
|
|
1073
|
+
providerLabel: "GoogleAIStudio",
|
|
1074
|
+
maxSteps,
|
|
1075
|
+
toolFailureBreaker: { maxRetries: DEFAULT_TOOL_MAX_RETRIES },
|
|
1076
|
+
liveTools: options.tools ?? {},
|
|
1077
|
+
...(declarationsResult ? { declarations: declarationsResult } : {}),
|
|
1078
|
+
buildRequest: (contents) => ({
|
|
1079
|
+
model: modelName,
|
|
1080
|
+
contents,
|
|
1081
|
+
config,
|
|
1082
|
+
...(composedSignal
|
|
1083
|
+
? { httpOptions: { signal: composedSignal } }
|
|
1084
|
+
: {}),
|
|
1085
|
+
}),
|
|
1086
|
+
sendStep: async (request) => client.models.generateContentStream(request),
|
|
1087
|
+
noteUsage: (inputTokens, outputTokens) => {
|
|
1088
|
+
contextGuard.noteUsage(inputTokens, outputTokens);
|
|
1089
|
+
},
|
|
1090
|
+
planReclaim: (contents, stepIndex) => {
|
|
1091
|
+
if (stepIndex !== 0 && !contextGuard.shouldStop()) {
|
|
1092
|
+
return undefined;
|
|
1078
1093
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
? composedSignal.reason
|
|
1083
|
-
: new Error("Request aborted");
|
|
1084
|
-
}
|
|
1085
|
-
step++;
|
|
1086
|
-
// Mid-turn discovery sync — see the stream twin.
|
|
1087
|
-
if (declarationsResult) {
|
|
1088
|
-
refreshNativeToolDeclarations(options.tools, declarationsResult);
|
|
1089
|
-
}
|
|
1090
|
-
logger.debug(`[GoogleAIStudio] Native SDK generate step ${step}/${maxSteps}`);
|
|
1091
|
-
try {
|
|
1092
|
-
const stream = await client.models.generateContentStream({
|
|
1093
|
-
model: modelName,
|
|
1094
|
-
contents: currentContents,
|
|
1095
|
-
config,
|
|
1096
|
-
...(composedSignal
|
|
1097
|
-
? { httpOptions: { signal: composedSignal } }
|
|
1098
|
-
: {}),
|
|
1099
|
-
});
|
|
1100
|
-
const chunkResult = await collectStreamChunks(stream);
|
|
1101
|
-
totalInputTokens += chunkResult.inputTokens;
|
|
1102
|
-
totalOutputTokens += chunkResult.outputTokens;
|
|
1103
|
-
totalCacheReadTokens += chunkResult.cacheReadTokens ?? 0;
|
|
1104
|
-
totalReasoningTokens += chunkResult.reasoningTokens ?? 0;
|
|
1105
|
-
contextGuard.noteUsage(chunkResult.inputTokens, chunkResult.outputTokens);
|
|
1106
|
-
const stepText = extractTextFromParts(chunkResult.rawResponseParts);
|
|
1107
|
-
// If no function calls, we're done
|
|
1108
|
-
if (chunkResult.stepFunctionCalls.length === 0) {
|
|
1109
|
-
finalText = stepText;
|
|
1110
|
-
break;
|
|
1094
|
+
const working = [...contents];
|
|
1095
|
+
if (!reclaimAiStudioContext(working, modelName, contextGuard.projectedNextPromptTokens)) {
|
|
1096
|
+
return undefined;
|
|
1111
1097
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1098
|
+
contextGuard.resetAfterReclaim();
|
|
1099
|
+
return working;
|
|
1100
|
+
},
|
|
1101
|
+
});
|
|
1102
|
+
const adapter = {
|
|
1103
|
+
...baseAdapter,
|
|
1104
|
+
buildToolResultMessages: (contents, stepResult, toolResults) => {
|
|
1105
|
+
step++;
|
|
1106
|
+
for (const call of stepResult.toolCalls) {
|
|
1115
1107
|
span.addEvent("gen_ai.tool_call", {
|
|
1116
|
-
"tool.name":
|
|
1108
|
+
"tool.name": call.name,
|
|
1117
1109
|
"tool.step": step,
|
|
1118
1110
|
});
|
|
1111
|
+
allToolCalls.push({ toolName: call.name, args: call.args });
|
|
1119
1112
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
// Persist this step's tool calls/results into conversation memory.
|
|
1134
|
-
const stepToolCalls = allToolCalls.slice(toolCallsBefore);
|
|
1135
|
-
const stepToolExecs = toolExecutions.slice(toolExecsBefore);
|
|
1136
|
-
if (stepToolCalls.length > 0 || stepToolExecs.length > 0) {
|
|
1137
|
-
const stepThoughtSig = extractThoughtSignature(chunkResult.rawResponseParts);
|
|
1138
|
-
withTimeout(this.handleToolExecutionStorage(stepToolCalls.map((tc, i) => ({
|
|
1139
|
-
toolName: tc.toolName,
|
|
1140
|
-
args: tc.args,
|
|
1113
|
+
lastStepText = stepResult.text || lastStepText;
|
|
1114
|
+
for (const result of toolResults) {
|
|
1115
|
+
toolExecutions.push({
|
|
1116
|
+
name: result.name,
|
|
1117
|
+
input: result.args,
|
|
1118
|
+
output: result.output,
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
if (toolResults.length > 0) {
|
|
1122
|
+
const stepThoughtSig = extractThoughtSignature(stepResult.raw.rawResponseParts);
|
|
1123
|
+
withTimeout(this.handleToolExecutionStorage(stepResult.toolCalls.map((call, i) => ({
|
|
1124
|
+
toolName: call.name,
|
|
1125
|
+
args: call.args,
|
|
1141
1126
|
...(i === 0 && stepThoughtSig
|
|
1142
1127
|
? { thoughtSignature: stepThoughtSig }
|
|
1143
1128
|
: {}),
|
|
1144
1129
|
stepIndex: step,
|
|
1145
|
-
})),
|
|
1146
|
-
toolName:
|
|
1147
|
-
output:
|
|
1130
|
+
})), toolResults.map((result) => ({
|
|
1131
|
+
toolName: result.name,
|
|
1132
|
+
output: result.output,
|
|
1148
1133
|
stepIndex: step,
|
|
1149
1134
|
})), options, new Date()), TOOL_STORAGE_TIMEOUT_MS, "tool storage write timed out").catch((error) => {
|
|
1150
|
-
logger.warn("[GoogleAIStudio] Failed to store native
|
|
1135
|
+
logger.warn("[GoogleAIStudio] Failed to store native tool executions", {
|
|
1151
1136
|
error: error instanceof Error ? error.message : String(error),
|
|
1152
1137
|
});
|
|
1153
1138
|
});
|
|
1154
1139
|
}
|
|
1155
|
-
|
|
1156
|
-
// only accepts "user" and "model" as valid roles in contents.
|
|
1157
|
-
// Function/tool responses must use role: "user" (matching the
|
|
1158
|
-
// SDK's own automaticFunctionCalling implementation).
|
|
1159
|
-
currentContents.push({
|
|
1160
|
-
role: "user",
|
|
1161
|
-
parts: functionResponses,
|
|
1162
|
-
});
|
|
1163
|
-
// Project this step's growth: the appended tool results ride
|
|
1164
|
-
// the next prompt, which the provider has not reported on yet.
|
|
1140
|
+
const next = baseAdapter.buildToolResultMessages(contents, stepResult, toolResults);
|
|
1165
1141
|
try {
|
|
1166
|
-
|
|
1142
|
+
const appended = next[next.length - 1];
|
|
1143
|
+
contextGuard.noteAppendedChars(JSON.stringify(appended?.parts ?? []).length);
|
|
1167
1144
|
}
|
|
1168
1145
|
catch {
|
|
1169
1146
|
/* estimation is best-effort — never break the loop */
|
|
1170
1147
|
}
|
|
1148
|
+
return next;
|
|
1149
|
+
},
|
|
1150
|
+
};
|
|
1151
|
+
const engineTools = {};
|
|
1152
|
+
for (const [name, tool] of Object.entries(options.tools ?? {})) {
|
|
1153
|
+
const execute = tool?.execute;
|
|
1154
|
+
if (!execute) {
|
|
1155
|
+
continue;
|
|
1171
1156
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1157
|
+
engineTools[name] = {
|
|
1158
|
+
execute: async (args, opts) => execute(args, opts),
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
const { stream: engineStream, resultPromise } = runAgenticLoop(adapter, currentContents, {
|
|
1162
|
+
tools: engineTools,
|
|
1163
|
+
...(composedSignal ? { abortSignal: composedSignal } : {}),
|
|
1164
|
+
});
|
|
1165
|
+
// Drained, not consumed: nothing streams out of generate(), but an
|
|
1166
|
+
// undrained channel would stall the engine mid-turn.
|
|
1167
|
+
const drain = (async () => {
|
|
1168
|
+
for await (const chunk of engineStream) {
|
|
1169
|
+
void chunk;
|
|
1175
1170
|
}
|
|
1171
|
+
})();
|
|
1172
|
+
let engineResult;
|
|
1173
|
+
try {
|
|
1174
|
+
engineResult = await resultPromise;
|
|
1175
|
+
}
|
|
1176
|
+
catch (error) {
|
|
1177
|
+
await drain.catch(() => { });
|
|
1178
|
+
logger.error("[GoogleAIStudio] Native SDK generate error", error);
|
|
1179
|
+
throw this.handleProviderError(error);
|
|
1176
1180
|
}
|
|
1181
|
+
await drain;
|
|
1182
|
+
totalInputTokens += engineResult.usage.inputTokens;
|
|
1183
|
+
totalOutputTokens += engineResult.usage.outputTokens;
|
|
1184
|
+
totalCacheReadTokens += engineResult.usage.cacheReadTokens ?? 0;
|
|
1185
|
+
totalReasoningTokens += engineResult.usage.reasoningTokens ?? 0;
|
|
1186
|
+
finalText = engineResult.text;
|
|
1177
1187
|
finalText = handleMaxStepsTermination("[GoogleAIStudio]", step, maxSteps, finalText, lastStepText);
|
|
1178
1188
|
const responseTime = Date.now() - startTime;
|
|
1179
1189
|
// Set token usage and finish reason on the span
|
|
@@ -17,7 +17,7 @@ import { withTimeout } from "../../utils/async/index.js";
|
|
|
17
17
|
import { estimateTokens } from "../../utils/tokenEstimation.js";
|
|
18
18
|
import { transformToolExecutions } from "../../utils/transformationUtils.js";
|
|
19
19
|
import { resolveToolExecutionRecords } from "../../core/toolExecutionRecorder.js";
|
|
20
|
-
import { buildGeminiResponseSchema, buildNativeConfig,
|
|
20
|
+
import { buildGeminiResponseSchema, buildNativeConfig, computeMaxSteps, createContextGuard, buildUserPartsWithMultimodal, extractThoughtSignature, handleMaxStepsTermination, prependConversationMessages, } from "../googleNativeGemini3/index.js";
|
|
21
21
|
import { createStreamChannel } from "../../core/streamChannel.js";
|
|
22
22
|
import { toNativeToolDeclarations } from "../../core/nativeToolFormat.js";
|
|
23
23
|
import { createProxyFetch } from "../../proxy/proxyFetch.js";
|
|
@@ -1004,8 +1004,6 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
1004
1004
|
});
|
|
1005
1005
|
// Convert tools (a0269210: trust options.tools — already merged + filtered upstream)
|
|
1006
1006
|
let toolsConfig;
|
|
1007
|
-
let executeMap = new DedupExecuteMap();
|
|
1008
|
-
let originalNameMap = new Map();
|
|
1009
1007
|
let declarationsResult;
|
|
1010
1008
|
const shouldUseTools = !options.disableTools;
|
|
1011
1009
|
// Structured output (JSON format or schema) is incompatible with
|
|
@@ -1032,8 +1030,6 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
1032
1030
|
const result = toNativeToolDeclarations(tools, "functionDeclarations");
|
|
1033
1031
|
declarationsResult = result;
|
|
1034
1032
|
toolsConfig = result.toolsConfig;
|
|
1035
|
-
executeMap = result.executeMap;
|
|
1036
|
-
originalNameMap = result.originalNameMap;
|
|
1037
1033
|
logger.debug("[GoogleAIStudio] Converted tools for native SDK generate", {
|
|
1038
1034
|
toolCount: toolsConfig[0].functionDeclarations.length,
|
|
1039
1035
|
toolNames: toolsConfig[0].functionDeclarations.map((t) => t.name),
|
|
@@ -1066,114 +1062,128 @@ export class GoogleAIStudioProvider extends BaseProvider {
|
|
|
1066
1062
|
const allToolCalls = [];
|
|
1067
1063
|
const toolExecutions = [];
|
|
1068
1064
|
let step = 0;
|
|
1069
|
-
const failedTools = new Map();
|
|
1070
1065
|
// Cheap reclaim trigger — see the stream twin.
|
|
1071
1066
|
const contextGuard = createContextGuard(getContextWindowSize("googleAiStudio", modelName));
|
|
1072
1067
|
// Agentic loop for tool calling
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1068
|
+
// Same shared engine as the streaming twin. This path has no
|
|
1069
|
+
// consumer channel — generate() returns one result rather than
|
|
1070
|
+
// streaming — so the engine's stream is drained and discarded, and
|
|
1071
|
+
// the turn's text comes from the result.
|
|
1072
|
+
const baseAdapter = createGeminiLoopAdapter({
|
|
1073
|
+
providerLabel: "GoogleAIStudio",
|
|
1074
|
+
maxSteps,
|
|
1075
|
+
toolFailureBreaker: { maxRetries: DEFAULT_TOOL_MAX_RETRIES },
|
|
1076
|
+
liveTools: options.tools ?? {},
|
|
1077
|
+
...(declarationsResult ? { declarations: declarationsResult } : {}),
|
|
1078
|
+
buildRequest: (contents) => ({
|
|
1079
|
+
model: modelName,
|
|
1080
|
+
contents,
|
|
1081
|
+
config,
|
|
1082
|
+
...(composedSignal
|
|
1083
|
+
? { httpOptions: { signal: composedSignal } }
|
|
1084
|
+
: {}),
|
|
1085
|
+
}),
|
|
1086
|
+
sendStep: async (request) => client.models.generateContentStream(request),
|
|
1087
|
+
noteUsage: (inputTokens, outputTokens) => {
|
|
1088
|
+
contextGuard.noteUsage(inputTokens, outputTokens);
|
|
1089
|
+
},
|
|
1090
|
+
planReclaim: (contents, stepIndex) => {
|
|
1091
|
+
if (stepIndex !== 0 && !contextGuard.shouldStop()) {
|
|
1092
|
+
return undefined;
|
|
1078
1093
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
? composedSignal.reason
|
|
1083
|
-
: new Error("Request aborted");
|
|
1084
|
-
}
|
|
1085
|
-
step++;
|
|
1086
|
-
// Mid-turn discovery sync — see the stream twin.
|
|
1087
|
-
if (declarationsResult) {
|
|
1088
|
-
refreshNativeToolDeclarations(options.tools, declarationsResult);
|
|
1089
|
-
}
|
|
1090
|
-
logger.debug(`[GoogleAIStudio] Native SDK generate step ${step}/${maxSteps}`);
|
|
1091
|
-
try {
|
|
1092
|
-
const stream = await client.models.generateContentStream({
|
|
1093
|
-
model: modelName,
|
|
1094
|
-
contents: currentContents,
|
|
1095
|
-
config,
|
|
1096
|
-
...(composedSignal
|
|
1097
|
-
? { httpOptions: { signal: composedSignal } }
|
|
1098
|
-
: {}),
|
|
1099
|
-
});
|
|
1100
|
-
const chunkResult = await collectStreamChunks(stream);
|
|
1101
|
-
totalInputTokens += chunkResult.inputTokens;
|
|
1102
|
-
totalOutputTokens += chunkResult.outputTokens;
|
|
1103
|
-
totalCacheReadTokens += chunkResult.cacheReadTokens ?? 0;
|
|
1104
|
-
totalReasoningTokens += chunkResult.reasoningTokens ?? 0;
|
|
1105
|
-
contextGuard.noteUsage(chunkResult.inputTokens, chunkResult.outputTokens);
|
|
1106
|
-
const stepText = extractTextFromParts(chunkResult.rawResponseParts);
|
|
1107
|
-
// If no function calls, we're done
|
|
1108
|
-
if (chunkResult.stepFunctionCalls.length === 0) {
|
|
1109
|
-
finalText = stepText;
|
|
1110
|
-
break;
|
|
1094
|
+
const working = [...contents];
|
|
1095
|
+
if (!reclaimAiStudioContext(working, modelName, contextGuard.projectedNextPromptTokens)) {
|
|
1096
|
+
return undefined;
|
|
1111
1097
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1098
|
+
contextGuard.resetAfterReclaim();
|
|
1099
|
+
return working;
|
|
1100
|
+
},
|
|
1101
|
+
});
|
|
1102
|
+
const adapter = {
|
|
1103
|
+
...baseAdapter,
|
|
1104
|
+
buildToolResultMessages: (contents, stepResult, toolResults) => {
|
|
1105
|
+
step++;
|
|
1106
|
+
for (const call of stepResult.toolCalls) {
|
|
1115
1107
|
span.addEvent("gen_ai.tool_call", {
|
|
1116
|
-
"tool.name":
|
|
1108
|
+
"tool.name": call.name,
|
|
1117
1109
|
"tool.step": step,
|
|
1118
1110
|
});
|
|
1111
|
+
allToolCalls.push({ toolName: call.name, args: call.args });
|
|
1119
1112
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
// Persist this step's tool calls/results into conversation memory.
|
|
1134
|
-
const stepToolCalls = allToolCalls.slice(toolCallsBefore);
|
|
1135
|
-
const stepToolExecs = toolExecutions.slice(toolExecsBefore);
|
|
1136
|
-
if (stepToolCalls.length > 0 || stepToolExecs.length > 0) {
|
|
1137
|
-
const stepThoughtSig = extractThoughtSignature(chunkResult.rawResponseParts);
|
|
1138
|
-
withTimeout(this.handleToolExecutionStorage(stepToolCalls.map((tc, i) => ({
|
|
1139
|
-
toolName: tc.toolName,
|
|
1140
|
-
args: tc.args,
|
|
1113
|
+
lastStepText = stepResult.text || lastStepText;
|
|
1114
|
+
for (const result of toolResults) {
|
|
1115
|
+
toolExecutions.push({
|
|
1116
|
+
name: result.name,
|
|
1117
|
+
input: result.args,
|
|
1118
|
+
output: result.output,
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
if (toolResults.length > 0) {
|
|
1122
|
+
const stepThoughtSig = extractThoughtSignature(stepResult.raw.rawResponseParts);
|
|
1123
|
+
withTimeout(this.handleToolExecutionStorage(stepResult.toolCalls.map((call, i) => ({
|
|
1124
|
+
toolName: call.name,
|
|
1125
|
+
args: call.args,
|
|
1141
1126
|
...(i === 0 && stepThoughtSig
|
|
1142
1127
|
? { thoughtSignature: stepThoughtSig }
|
|
1143
1128
|
: {}),
|
|
1144
1129
|
stepIndex: step,
|
|
1145
|
-
})),
|
|
1146
|
-
toolName:
|
|
1147
|
-
output:
|
|
1130
|
+
})), toolResults.map((result) => ({
|
|
1131
|
+
toolName: result.name,
|
|
1132
|
+
output: result.output,
|
|
1148
1133
|
stepIndex: step,
|
|
1149
1134
|
})), options, new Date()), TOOL_STORAGE_TIMEOUT_MS, "tool storage write timed out").catch((error) => {
|
|
1150
|
-
logger.warn("[GoogleAIStudio] Failed to store native
|
|
1135
|
+
logger.warn("[GoogleAIStudio] Failed to store native tool executions", {
|
|
1151
1136
|
error: error instanceof Error ? error.message : String(error),
|
|
1152
1137
|
});
|
|
1153
1138
|
});
|
|
1154
1139
|
}
|
|
1155
|
-
|
|
1156
|
-
// only accepts "user" and "model" as valid roles in contents.
|
|
1157
|
-
// Function/tool responses must use role: "user" (matching the
|
|
1158
|
-
// SDK's own automaticFunctionCalling implementation).
|
|
1159
|
-
currentContents.push({
|
|
1160
|
-
role: "user",
|
|
1161
|
-
parts: functionResponses,
|
|
1162
|
-
});
|
|
1163
|
-
// Project this step's growth: the appended tool results ride
|
|
1164
|
-
// the next prompt, which the provider has not reported on yet.
|
|
1140
|
+
const next = baseAdapter.buildToolResultMessages(contents, stepResult, toolResults);
|
|
1165
1141
|
try {
|
|
1166
|
-
|
|
1142
|
+
const appended = next[next.length - 1];
|
|
1143
|
+
contextGuard.noteAppendedChars(JSON.stringify(appended?.parts ?? []).length);
|
|
1167
1144
|
}
|
|
1168
1145
|
catch {
|
|
1169
1146
|
/* estimation is best-effort — never break the loop */
|
|
1170
1147
|
}
|
|
1148
|
+
return next;
|
|
1149
|
+
},
|
|
1150
|
+
};
|
|
1151
|
+
const engineTools = {};
|
|
1152
|
+
for (const [name, tool] of Object.entries(options.tools ?? {})) {
|
|
1153
|
+
const execute = tool?.execute;
|
|
1154
|
+
if (!execute) {
|
|
1155
|
+
continue;
|
|
1171
1156
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1157
|
+
engineTools[name] = {
|
|
1158
|
+
execute: async (args, opts) => execute(args, opts),
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
const { stream: engineStream, resultPromise } = runAgenticLoop(adapter, currentContents, {
|
|
1162
|
+
tools: engineTools,
|
|
1163
|
+
...(composedSignal ? { abortSignal: composedSignal } : {}),
|
|
1164
|
+
});
|
|
1165
|
+
// Drained, not consumed: nothing streams out of generate(), but an
|
|
1166
|
+
// undrained channel would stall the engine mid-turn.
|
|
1167
|
+
const drain = (async () => {
|
|
1168
|
+
for await (const chunk of engineStream) {
|
|
1169
|
+
void chunk;
|
|
1175
1170
|
}
|
|
1171
|
+
})();
|
|
1172
|
+
let engineResult;
|
|
1173
|
+
try {
|
|
1174
|
+
engineResult = await resultPromise;
|
|
1175
|
+
}
|
|
1176
|
+
catch (error) {
|
|
1177
|
+
await drain.catch(() => { });
|
|
1178
|
+
logger.error("[GoogleAIStudio] Native SDK generate error", error);
|
|
1179
|
+
throw this.handleProviderError(error);
|
|
1176
1180
|
}
|
|
1181
|
+
await drain;
|
|
1182
|
+
totalInputTokens += engineResult.usage.inputTokens;
|
|
1183
|
+
totalOutputTokens += engineResult.usage.outputTokens;
|
|
1184
|
+
totalCacheReadTokens += engineResult.usage.cacheReadTokens ?? 0;
|
|
1185
|
+
totalReasoningTokens += engineResult.usage.reasoningTokens ?? 0;
|
|
1186
|
+
finalText = engineResult.text;
|
|
1177
1187
|
finalText = handleMaxStepsTermination("[GoogleAIStudio]", step, maxSteps, finalText, lastStepText);
|
|
1178
1188
|
const responseTime = Date.now() - startTime;
|
|
1179
1189
|
// Set token usage and finish reason on the span
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "11.11.
|
|
3
|
+
"version": "11.11.5",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
|
|
6
6
|
"author": {
|