@juspay/neurolink 9.65.0 → 9.65.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 +8 -0
- package/dist/adapters/video/videoAnalyzer.d.ts +1 -1
- package/dist/agent/directTools.d.ts +9 -17
- package/dist/agent/directTools.js +12 -8
- package/dist/autoresearch/tools.d.ts +2 -214
- package/dist/autoresearch/tools.js +1 -1
- package/dist/browser/neurolink.min.js +362 -368
- package/dist/client/reactHooks.js +16 -8
- package/dist/client/reactHooks.tsx +24 -9
- package/dist/core/baseProvider.d.ts +1 -6
- package/dist/core/baseProvider.js +1 -1
- package/dist/core/constants.d.ts +1 -0
- package/dist/core/constants.js +3 -0
- package/dist/core/modules/GenerationHandler.d.ts +2 -2
- package/dist/core/modules/GenerationHandler.js +3 -1
- package/dist/core/modules/MessageBuilder.d.ts +1 -15
- package/dist/core/modules/MessageBuilder.js +0 -14
- package/dist/core/modules/StreamHandler.js +1 -1
- package/dist/core/modules/ToolsManager.d.ts +1 -17
- package/dist/core/modules/ToolsManager.js +1 -17
- package/dist/core/redisConversationMemoryManager.js +0 -6
- package/dist/core/streamAnalytics.js +1 -1
- package/dist/evaluation/contextBuilder.d.ts +1 -4
- package/dist/evaluation/contextBuilder.js +0 -3
- package/dist/evaluation/index.d.ts +1 -4
- package/dist/evaluation/index.js +0 -3
- package/dist/files/fileTools.d.ts +2 -18
- package/dist/files/fileTools.js +3 -19
- package/dist/lib/adapters/video/videoAnalyzer.d.ts +1 -1
- package/dist/lib/agent/directTools.d.ts +9 -17
- package/dist/lib/agent/directTools.js +12 -8
- package/dist/lib/autoresearch/tools.d.ts +2 -214
- package/dist/lib/autoresearch/tools.js +1 -1
- package/dist/lib/client/reactHooks.js +16 -8
- package/dist/lib/core/baseProvider.d.ts +1 -6
- package/dist/lib/core/baseProvider.js +1 -1
- package/dist/lib/core/constants.d.ts +1 -0
- package/dist/lib/core/constants.js +3 -0
- package/dist/lib/core/modules/GenerationHandler.d.ts +2 -2
- package/dist/lib/core/modules/GenerationHandler.js +3 -1
- package/dist/lib/core/modules/MessageBuilder.d.ts +1 -15
- package/dist/lib/core/modules/MessageBuilder.js +0 -14
- package/dist/lib/core/modules/StreamHandler.js +1 -1
- package/dist/lib/core/modules/ToolsManager.d.ts +1 -17
- package/dist/lib/core/modules/ToolsManager.js +1 -17
- package/dist/lib/core/redisConversationMemoryManager.js +0 -6
- package/dist/lib/core/streamAnalytics.js +1 -1
- package/dist/lib/evaluation/contextBuilder.d.ts +1 -4
- package/dist/lib/evaluation/contextBuilder.js +0 -3
- package/dist/lib/evaluation/index.d.ts +1 -4
- package/dist/lib/evaluation/index.js +0 -3
- package/dist/lib/files/fileTools.d.ts +2 -18
- package/dist/lib/files/fileTools.js +3 -19
- package/dist/lib/memory/memoryRetrievalTools.d.ts +2 -126
- package/dist/lib/memory/memoryRetrievalTools.js +1 -9
- package/dist/lib/middleware/builtin/autoEvaluation.d.ts +0 -3
- package/dist/lib/middleware/builtin/autoEvaluation.js +0 -3
- package/dist/lib/middleware/builtin/guardrails.js +1 -1
- package/dist/lib/middleware/builtin/lifecycle.d.ts +0 -9
- package/dist/lib/middleware/builtin/lifecycle.js +0 -9
- package/dist/lib/middleware/factory.d.ts +1 -1
- package/dist/lib/middleware/factory.js +1 -1
- package/dist/lib/middleware/registry.d.ts +1 -1
- package/dist/lib/neurolink.d.ts +14 -2
- package/dist/lib/neurolink.js +46 -18
- package/dist/lib/processors/media/AudioProcessor.js +8 -3
- package/dist/lib/providers/amazonBedrock.js +1 -2
- package/dist/lib/providers/amazonSagemaker.d.ts +1 -7
- package/dist/lib/providers/amazonSagemaker.js +0 -6
- package/dist/lib/providers/anthropic.d.ts +1 -1
- package/dist/lib/providers/anthropic.js +2 -1
- package/dist/lib/providers/anthropicBaseProvider.d.ts +1 -1
- package/dist/lib/providers/anthropicBaseProvider.js +2 -1
- package/dist/lib/providers/azureOpenai.d.ts +1 -1
- package/dist/lib/providers/azureOpenai.js +2 -1
- package/dist/lib/providers/cloudflare.d.ts +1 -1
- package/dist/lib/providers/cloudflare.js +2 -1
- package/dist/lib/providers/cohere.d.ts +1 -1
- package/dist/lib/providers/cohere.js +2 -1
- package/dist/lib/providers/deepseek.d.ts +1 -1
- package/dist/lib/providers/deepseek.js +2 -1
- package/dist/lib/providers/fireworks.d.ts +1 -1
- package/dist/lib/providers/fireworks.js +2 -1
- package/dist/lib/providers/googleAiStudio.d.ts +1 -1
- package/dist/lib/providers/googleAiStudio.js +82 -6
- package/dist/lib/providers/googleNativeGemini3.d.ts +3 -6
- package/dist/lib/providers/googleNativeGemini3.js +104 -9
- package/dist/lib/providers/googleVertex.d.ts +1 -1
- package/dist/lib/providers/googleVertex.js +466 -165
- package/dist/lib/providers/groq.d.ts +1 -1
- package/dist/lib/providers/groq.js +2 -1
- package/dist/lib/providers/huggingFace.d.ts +1 -1
- package/dist/lib/providers/huggingFace.js +3 -1
- package/dist/lib/providers/ideogram.d.ts +1 -1
- package/dist/lib/providers/jina.d.ts +1 -1
- package/dist/lib/providers/litellm.d.ts +1 -1
- package/dist/lib/providers/litellm.js +12 -6
- package/dist/lib/providers/llamaCpp.d.ts +1 -1
- package/dist/lib/providers/llamaCpp.js +2 -1
- package/dist/lib/providers/lmStudio.d.ts +1 -1
- package/dist/lib/providers/lmStudio.js +2 -1
- package/dist/lib/providers/mistral.d.ts +1 -1
- package/dist/lib/providers/mistral.js +2 -1
- package/dist/lib/providers/nvidiaNim.d.ts +1 -1
- package/dist/lib/providers/nvidiaNim.js +2 -1
- package/dist/lib/providers/ollama.d.ts +1 -1
- package/dist/lib/providers/ollama.js +1 -2
- package/dist/lib/providers/openAI.d.ts +1 -1
- package/dist/lib/providers/openAI.js +3 -1
- package/dist/lib/providers/openRouter.d.ts +1 -1
- package/dist/lib/providers/openRouter.js +3 -1
- package/dist/lib/providers/openaiCompatible.d.ts +1 -1
- package/dist/lib/providers/openaiCompatible.js +3 -1
- package/dist/lib/providers/perplexity.d.ts +1 -1
- package/dist/lib/providers/perplexity.js +2 -1
- package/dist/lib/providers/providerTypeUtils.d.ts +2 -7
- package/dist/lib/providers/providerTypeUtils.js +0 -6
- package/dist/lib/providers/recraft.d.ts +1 -1
- package/dist/lib/providers/replicate.d.ts +1 -1
- package/dist/lib/providers/stability.d.ts +1 -1
- package/dist/lib/providers/togetherAi.d.ts +1 -1
- package/dist/lib/providers/togetherAi.js +2 -1
- package/dist/lib/providers/voyage.d.ts +1 -1
- package/dist/lib/providers/xai.d.ts +1 -1
- package/dist/lib/providers/xai.js +2 -1
- package/dist/lib/proxy/claudeFormat.d.ts +0 -15
- package/dist/lib/proxy/claudeFormat.js +1 -11
- package/dist/lib/rag/ragIntegration.d.ts +1 -12
- package/dist/lib/rag/ragIntegration.js +0 -8
- package/dist/lib/tasks/tools/taskTools.d.ts +2 -117
- package/dist/lib/tasks/tools/taskTools.js +1 -10
- package/dist/lib/types/aliases.d.ts +1 -1
- package/dist/lib/types/conversation.d.ts +17 -0
- package/dist/lib/types/evaluation.d.ts +1 -5
- package/dist/lib/types/evaluation.js +0 -4
- package/dist/lib/types/generate.d.ts +2 -22
- package/dist/lib/types/guardrails.d.ts +1 -1
- package/dist/lib/types/middleware.d.ts +8 -3
- package/dist/lib/types/providers.d.ts +2 -1
- package/dist/lib/types/rag.d.ts +1 -1
- package/dist/lib/types/rag.js +0 -6
- package/dist/lib/types/stream.d.ts +2 -11
- package/dist/lib/types/tools.d.ts +2 -1
- package/dist/lib/utils/generation.d.ts +8 -0
- package/dist/lib/utils/generation.js +9 -0
- package/dist/lib/utils/generationErrors.d.ts +10 -0
- package/dist/lib/utils/generationErrors.js +11 -0
- package/dist/lib/utils/messageBuilder.d.ts +1 -6
- package/dist/lib/utils/messageBuilder.js +0 -5
- package/dist/lib/utils/noOutputSentinel.d.ts +0 -13
- package/dist/lib/utils/noOutputSentinel.js +1 -14
- package/dist/lib/utils/providerRetry.js +1 -1
- package/dist/lib/utils/tool.d.ts +8 -0
- package/dist/lib/utils/tool.js +9 -0
- package/dist/lib/utils/toolCallRepair.d.ts +1 -16
- package/dist/lib/utils/toolCallRepair.js +1 -16
- package/dist/lib/utils/toolChoice.d.ts +1 -1
- package/dist/lib/utils/videoAnalysisProcessor.d.ts +1 -8
- package/dist/lib/utils/videoAnalysisProcessor.js +0 -7
- package/dist/memory/memoryRetrievalTools.d.ts +2 -126
- package/dist/memory/memoryRetrievalTools.js +1 -9
- package/dist/middleware/builtin/autoEvaluation.d.ts +0 -3
- package/dist/middleware/builtin/autoEvaluation.js +0 -3
- package/dist/middleware/builtin/guardrails.js +1 -1
- package/dist/middleware/builtin/lifecycle.d.ts +0 -9
- package/dist/middleware/builtin/lifecycle.js +0 -9
- package/dist/middleware/factory.d.ts +1 -1
- package/dist/middleware/factory.js +1 -1
- package/dist/middleware/registry.d.ts +1 -1
- package/dist/neurolink.d.ts +14 -2
- package/dist/neurolink.js +46 -18
- package/dist/processors/media/AudioProcessor.js +8 -3
- package/dist/providers/amazonBedrock.js +1 -2
- package/dist/providers/amazonSagemaker.d.ts +1 -7
- package/dist/providers/amazonSagemaker.js +0 -6
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +2 -1
- package/dist/providers/anthropicBaseProvider.d.ts +1 -1
- package/dist/providers/anthropicBaseProvider.js +2 -1
- package/dist/providers/azureOpenai.d.ts +1 -1
- package/dist/providers/azureOpenai.js +2 -1
- package/dist/providers/cloudflare.d.ts +1 -1
- package/dist/providers/cloudflare.js +2 -1
- package/dist/providers/cohere.d.ts +1 -1
- package/dist/providers/cohere.js +2 -1
- package/dist/providers/deepseek.d.ts +1 -1
- package/dist/providers/deepseek.js +2 -1
- package/dist/providers/fireworks.d.ts +1 -1
- package/dist/providers/fireworks.js +2 -1
- package/dist/providers/googleAiStudio.d.ts +1 -1
- package/dist/providers/googleAiStudio.js +82 -5
- package/dist/providers/googleNativeGemini3.d.ts +3 -6
- package/dist/providers/googleNativeGemini3.js +104 -9
- package/dist/providers/googleVertex.d.ts +1 -1
- package/dist/providers/googleVertex.js +466 -164
- package/dist/providers/groq.d.ts +1 -1
- package/dist/providers/groq.js +2 -1
- package/dist/providers/huggingFace.d.ts +1 -1
- package/dist/providers/huggingFace.js +3 -1
- package/dist/providers/ideogram.d.ts +1 -1
- package/dist/providers/jina.d.ts +1 -1
- package/dist/providers/litellm.d.ts +1 -1
- package/dist/providers/litellm.js +12 -6
- package/dist/providers/llamaCpp.d.ts +1 -1
- package/dist/providers/llamaCpp.js +2 -1
- package/dist/providers/lmStudio.d.ts +1 -1
- package/dist/providers/lmStudio.js +2 -1
- package/dist/providers/mistral.d.ts +1 -1
- package/dist/providers/mistral.js +2 -1
- package/dist/providers/nvidiaNim.d.ts +1 -1
- package/dist/providers/nvidiaNim.js +2 -1
- package/dist/providers/ollama.d.ts +1 -1
- package/dist/providers/ollama.js +1 -2
- package/dist/providers/openAI.d.ts +1 -1
- package/dist/providers/openAI.js +3 -1
- package/dist/providers/openRouter.d.ts +1 -1
- package/dist/providers/openRouter.js +3 -1
- package/dist/providers/openaiCompatible.d.ts +1 -1
- package/dist/providers/openaiCompatible.js +3 -1
- package/dist/providers/perplexity.d.ts +1 -1
- package/dist/providers/perplexity.js +2 -1
- package/dist/providers/providerTypeUtils.d.ts +2 -7
- package/dist/providers/providerTypeUtils.js +0 -6
- package/dist/providers/recraft.d.ts +1 -1
- package/dist/providers/replicate.d.ts +1 -1
- package/dist/providers/stability.d.ts +1 -1
- package/dist/providers/togetherAi.d.ts +1 -1
- package/dist/providers/togetherAi.js +2 -1
- package/dist/providers/voyage.d.ts +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +2 -1
- package/dist/proxy/claudeFormat.d.ts +0 -15
- package/dist/proxy/claudeFormat.js +1 -11
- package/dist/rag/ragIntegration.d.ts +1 -12
- package/dist/rag/ragIntegration.js +0 -8
- package/dist/tasks/tools/taskTools.d.ts +2 -117
- package/dist/tasks/tools/taskTools.js +1 -10
- package/dist/types/aliases.d.ts +1 -1
- package/dist/types/conversation.d.ts +17 -0
- package/dist/types/evaluation.d.ts +1 -5
- package/dist/types/evaluation.js +0 -4
- package/dist/types/generate.d.ts +2 -22
- package/dist/types/guardrails.d.ts +1 -1
- package/dist/types/middleware.d.ts +8 -3
- package/dist/types/providers.d.ts +2 -1
- package/dist/types/rag.d.ts +1 -1
- package/dist/types/rag.js +0 -6
- package/dist/types/stream.d.ts +2 -11
- package/dist/types/tools.d.ts +2 -1
- package/dist/utils/generation.d.ts +8 -0
- package/dist/utils/generation.js +8 -0
- package/dist/utils/generationErrors.d.ts +10 -0
- package/dist/utils/generationErrors.js +10 -0
- package/dist/utils/messageBuilder.d.ts +1 -6
- package/dist/utils/messageBuilder.js +0 -5
- package/dist/utils/noOutputSentinel.d.ts +0 -13
- package/dist/utils/noOutputSentinel.js +1 -14
- package/dist/utils/providerRetry.js +1 -1
- package/dist/utils/tool.d.ts +8 -0
- package/dist/utils/tool.js +8 -0
- package/dist/utils/toolCallRepair.d.ts +1 -16
- package/dist/utils/toolCallRepair.js +1 -16
- package/dist/utils/toolChoice.d.ts +1 -1
- package/dist/utils/videoAnalysisProcessor.d.ts +1 -8
- package/dist/utils/videoAnalysisProcessor.js +0 -7
- package/package.json +2 -3
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
/* eslint-disable max-lines-per-function */
|
|
1
2
|
// Native SDK imports - no more @ai-sdk/google-vertex dependency
|
|
2
3
|
import fs from "fs";
|
|
3
4
|
import path from "path";
|
|
4
5
|
import os from "os";
|
|
5
|
-
import {} from "ai";
|
|
6
6
|
import { AIProviderName, ErrorCategory, ErrorSeverity, } from "../constants/enums.js";
|
|
7
7
|
import { BaseProvider } from "../core/baseProvider.js";
|
|
8
|
-
import { DEFAULT_MAX_STEPS, DEFAULT_TOOL_MAX_RETRIES, GLOBAL_LOCATION_MODELS, IMAGE_GENERATION_MODELS, } from "../core/constants.js";
|
|
8
|
+
import { DEFAULT_MAX_STEPS, DEFAULT_TOOL_MAX_RETRIES, GLOBAL_LOCATION_MODELS, IMAGE_GENERATION_MODELS, TOOL_STORAGE_TIMEOUT_MS, } from "../core/constants.js";
|
|
9
9
|
import { ModelConfigurationManager } from "../core/modelConfiguration.js";
|
|
10
10
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
11
11
|
import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
|
|
@@ -17,10 +17,12 @@ import { hasRestrictedOutputLimit, RESTRICTED_OUTPUT_TOKEN_LIMIT, } from "../uti
|
|
|
17
17
|
import { validateApiKey, createVertexProjectConfig, createGoogleAuthConfig, } from "../utils/providerConfig.js";
|
|
18
18
|
import { convertZodToJsonSchema, inlineJsonSchema, ensureNestedSchemaTypes, } from "../utils/schemaConversion.js";
|
|
19
19
|
import { createNativeThinkingConfig } from "../utils/thinkingConfig.js";
|
|
20
|
-
import { TimeoutError } from "../utils/async/index.js";
|
|
21
|
-
import {
|
|
20
|
+
import { TimeoutError, withTimeout } from "../utils/async/index.js";
|
|
21
|
+
import { parseTimeout } from "../utils/timeout.js";
|
|
22
|
+
import { createTextChannel, extractThoughtSignature, prependConversationMessages, } from "./googleNativeGemini3.js";
|
|
22
23
|
import { ATTR, tracers, withClientSpan, withClientStreamSpan, withSpan, } from "../telemetry/index.js";
|
|
23
24
|
import { calculateCost } from "../utils/pricing.js";
|
|
25
|
+
import { transformToolExecutions } from "../utils/transformationUtils.js";
|
|
24
26
|
// Import proper types for multimodal message handling
|
|
25
27
|
// Dynamic import helper for native Anthropic Vertex SDK
|
|
26
28
|
let anthropicVertexModule = null;
|
|
@@ -1126,6 +1128,11 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1126
1128
|
let finalText = "";
|
|
1127
1129
|
let lastStepText = ""; // Track text from last step for maxSteps termination
|
|
1128
1130
|
const allToolCalls = [];
|
|
1131
|
+
// Mirrors the generate-path shape so StreamResult.toolExecutions can be
|
|
1132
|
+
// populated (parity with AI-SDK-driven providers) and so the storage
|
|
1133
|
+
// hook can persist actual tool outputs rather than the placeholder
|
|
1134
|
+
// "success" string used by flushPendingToolData's default fallback.
|
|
1135
|
+
const toolExecutions = [];
|
|
1129
1136
|
let step = 0;
|
|
1130
1137
|
// Track structured output from final_result tool (when using final_result pattern)
|
|
1131
1138
|
let finalResultStructuredOutput;
|
|
@@ -1232,22 +1239,38 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1232
1239
|
});
|
|
1233
1240
|
// Execute each function and collect responses
|
|
1234
1241
|
const functionResponses = [];
|
|
1242
|
+
// Per-step bookkeeping for conversation-memory storage.
|
|
1243
|
+
const stepStorageCalls = [];
|
|
1244
|
+
const stepStorageResults = [];
|
|
1245
|
+
// Note: tool:start / tool:end events are emitted by ToolsManager's
|
|
1246
|
+
// wrapped `execute` (see ToolsManager.ts:355) — no inline emit needed.
|
|
1235
1247
|
for (const call of stepFunctionCalls) {
|
|
1236
1248
|
allToolCalls.push({ toolName: call.name, args: call.args });
|
|
1249
|
+
stepStorageCalls.push({ toolName: call.name, args: call.args });
|
|
1237
1250
|
// Check if this tool has already exceeded retry limit
|
|
1238
1251
|
const failedInfo = failedTools.get(call.name);
|
|
1239
1252
|
if (failedInfo && failedInfo.count >= DEFAULT_TOOL_MAX_RETRIES) {
|
|
1240
1253
|
logger.warn(`[GoogleVertex] Tool "${call.name}" has exceeded retry limit (${DEFAULT_TOOL_MAX_RETRIES}), skipping execution`);
|
|
1254
|
+
const errorPayload = {
|
|
1255
|
+
error: `TOOL_PERMANENTLY_FAILED: The tool "${call.name}" has failed ${failedInfo.count} times and will not be retried. Last error: ${failedInfo.lastError}. Please proceed without using this tool or inform the user that this functionality is unavailable.`,
|
|
1256
|
+
status: "permanently_failed",
|
|
1257
|
+
do_not_retry: true,
|
|
1258
|
+
};
|
|
1241
1259
|
functionResponses.push({
|
|
1242
1260
|
functionResponse: {
|
|
1243
1261
|
name: call.name,
|
|
1244
|
-
response:
|
|
1245
|
-
error: `TOOL_PERMANENTLY_FAILED: The tool "${call.name}" has failed ${failedInfo.count} times and will not be retried. Last error: ${failedInfo.lastError}. Please proceed without using this tool or inform the user that this functionality is unavailable.`,
|
|
1246
|
-
status: "permanently_failed",
|
|
1247
|
-
do_not_retry: true,
|
|
1248
|
-
},
|
|
1262
|
+
response: errorPayload,
|
|
1249
1263
|
},
|
|
1250
1264
|
});
|
|
1265
|
+
toolExecutions.push({
|
|
1266
|
+
name: call.name,
|
|
1267
|
+
input: call.args,
|
|
1268
|
+
output: errorPayload,
|
|
1269
|
+
});
|
|
1270
|
+
stepStorageResults.push({
|
|
1271
|
+
toolName: call.name,
|
|
1272
|
+
output: errorPayload,
|
|
1273
|
+
});
|
|
1251
1274
|
continue;
|
|
1252
1275
|
}
|
|
1253
1276
|
const execute = executeMap.get(call.name);
|
|
@@ -1260,9 +1283,18 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1260
1283
|
abortSignal: undefined,
|
|
1261
1284
|
};
|
|
1262
1285
|
const result = await execute(call.args, toolOptions);
|
|
1286
|
+
toolExecutions.push({
|
|
1287
|
+
name: call.name,
|
|
1288
|
+
input: call.args,
|
|
1289
|
+
output: result,
|
|
1290
|
+
});
|
|
1263
1291
|
functionResponses.push({
|
|
1264
1292
|
functionResponse: { name: call.name, response: { result } },
|
|
1265
1293
|
});
|
|
1294
|
+
stepStorageResults.push({
|
|
1295
|
+
toolName: call.name,
|
|
1296
|
+
output: result,
|
|
1297
|
+
});
|
|
1266
1298
|
}
|
|
1267
1299
|
catch (error) {
|
|
1268
1300
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -1277,38 +1309,77 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1277
1309
|
logger.warn(`[GoogleVertex] Tool "${call.name}" failed (attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}): ${errorMessage}`);
|
|
1278
1310
|
// Determine if this is a permanent failure
|
|
1279
1311
|
const isPermanentFailure = currentFailInfo.count >= DEFAULT_TOOL_MAX_RETRIES;
|
|
1312
|
+
const errorPayload = {
|
|
1313
|
+
error: isPermanentFailure
|
|
1314
|
+
? `TOOL_PERMANENTLY_FAILED: The tool "${call.name}" has failed ${currentFailInfo.count} times with error: ${errorMessage}. This tool will not be retried. Please proceed without using this tool or inform the user that this functionality is unavailable.`
|
|
1315
|
+
: `TOOL_EXECUTION_ERROR: ${errorMessage}. Retry attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}.`,
|
|
1316
|
+
status: isPermanentFailure ? "permanently_failed" : "failed",
|
|
1317
|
+
do_not_retry: isPermanentFailure,
|
|
1318
|
+
retry_count: currentFailInfo.count,
|
|
1319
|
+
max_retries: DEFAULT_TOOL_MAX_RETRIES,
|
|
1320
|
+
};
|
|
1280
1321
|
functionResponses.push({
|
|
1281
1322
|
functionResponse: {
|
|
1282
1323
|
name: call.name,
|
|
1283
|
-
response:
|
|
1284
|
-
error: isPermanentFailure
|
|
1285
|
-
? `TOOL_PERMANENTLY_FAILED: The tool "${call.name}" has failed ${currentFailInfo.count} times with error: ${errorMessage}. This tool will not be retried. Please proceed without using this tool or inform the user that this functionality is unavailable.`
|
|
1286
|
-
: `TOOL_EXECUTION_ERROR: ${errorMessage}. Retry attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}.`,
|
|
1287
|
-
status: isPermanentFailure
|
|
1288
|
-
? "permanently_failed"
|
|
1289
|
-
: "failed",
|
|
1290
|
-
do_not_retry: isPermanentFailure,
|
|
1291
|
-
retry_count: currentFailInfo.count,
|
|
1292
|
-
max_retries: DEFAULT_TOOL_MAX_RETRIES,
|
|
1293
|
-
},
|
|
1324
|
+
response: errorPayload,
|
|
1294
1325
|
},
|
|
1295
1326
|
});
|
|
1327
|
+
toolExecutions.push({
|
|
1328
|
+
name: call.name,
|
|
1329
|
+
input: call.args,
|
|
1330
|
+
output: errorPayload,
|
|
1331
|
+
});
|
|
1332
|
+
stepStorageResults.push({
|
|
1333
|
+
toolName: call.name,
|
|
1334
|
+
output: errorPayload,
|
|
1335
|
+
});
|
|
1296
1336
|
}
|
|
1297
1337
|
}
|
|
1298
1338
|
else {
|
|
1299
1339
|
// Tool not found is a permanent error
|
|
1340
|
+
const errorPayload = {
|
|
1341
|
+
error: `TOOL_NOT_FOUND: The tool "${call.name}" does not exist. Do not attempt to call this tool again.`,
|
|
1342
|
+
status: "permanently_failed",
|
|
1343
|
+
do_not_retry: true,
|
|
1344
|
+
};
|
|
1300
1345
|
functionResponses.push({
|
|
1301
1346
|
functionResponse: {
|
|
1302
1347
|
name: call.name,
|
|
1303
|
-
response:
|
|
1304
|
-
error: `TOOL_NOT_FOUND: The tool "${call.name}" does not exist. Do not attempt to call this tool again.`,
|
|
1305
|
-
status: "permanently_failed",
|
|
1306
|
-
do_not_retry: true,
|
|
1307
|
-
},
|
|
1348
|
+
response: errorPayload,
|
|
1308
1349
|
},
|
|
1309
1350
|
});
|
|
1351
|
+
toolExecutions.push({
|
|
1352
|
+
name: call.name,
|
|
1353
|
+
input: call.args,
|
|
1354
|
+
output: errorPayload,
|
|
1355
|
+
});
|
|
1356
|
+
stepStorageResults.push({
|
|
1357
|
+
toolName: call.name,
|
|
1358
|
+
output: errorPayload,
|
|
1359
|
+
});
|
|
1310
1360
|
}
|
|
1311
1361
|
}
|
|
1362
|
+
// Persist this step's tool calls/results into conversation memory.
|
|
1363
|
+
// Without this, tool_call / tool_result rows never reach Redis and
|
|
1364
|
+
// the chat-history UI loses every tool invocation.
|
|
1365
|
+
//
|
|
1366
|
+
// `thoughtSignature` rides as a sibling on the first call of the
|
|
1367
|
+
// step — Gemini 3 needs it to match thinking patterns when the
|
|
1368
|
+
// conversation is replayed on the next turn.
|
|
1369
|
+
if (stepStorageCalls.length > 0 || stepStorageResults.length > 0) {
|
|
1370
|
+
const stepThoughtSig = extractThoughtSignature(rawResponseParts);
|
|
1371
|
+
withTimeout(this.handleToolExecutionStorage(stepStorageCalls.map((c, i) => ({
|
|
1372
|
+
...c,
|
|
1373
|
+
...(i === 0 && stepThoughtSig
|
|
1374
|
+
? { thoughtSignature: stepThoughtSig }
|
|
1375
|
+
: {}),
|
|
1376
|
+
stepIndex: step,
|
|
1377
|
+
})), stepStorageResults.map((r) => ({ ...r, stepIndex: step })), options, new Date()), TOOL_STORAGE_TIMEOUT_MS, "tool storage write timed out").catch((error) => {
|
|
1378
|
+
logger.warn("[GoogleVertex] Failed to store native Gemini stream tool executions", {
|
|
1379
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1380
|
+
});
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1312
1383
|
// The @google/genai SDK only accepts "user" and "model" as valid
|
|
1313
1384
|
// roles in contents — function/tool responses must use role: "user"
|
|
1314
1385
|
// (matching the SDK's automaticFunctionCalling implementation and
|
|
@@ -1354,6 +1425,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1354
1425
|
}
|
|
1355
1426
|
// Filter out final_result from tool calls as it's an internal pattern
|
|
1356
1427
|
const externalToolCalls = allToolCalls.filter((tc) => tc.toolName !== "final_result");
|
|
1428
|
+
const externalToolExecutions = toolExecutions.filter((te) => te.name !== "final_result");
|
|
1357
1429
|
const result = {
|
|
1358
1430
|
stream: createTextStream(),
|
|
1359
1431
|
provider: this.providerName,
|
|
@@ -1367,6 +1439,12 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1367
1439
|
toolName: tc.toolName,
|
|
1368
1440
|
args: tc.args,
|
|
1369
1441
|
})),
|
|
1442
|
+
// Surface tools-used + execution summary so `hasToolActivity` in
|
|
1443
|
+
// conversationMemory.ts evaluates true for tool-only stream turns
|
|
1444
|
+
// (assistant text empty but tools ran) and downstream consumers see
|
|
1445
|
+
// the same shape AI-SDK-driven providers expose.
|
|
1446
|
+
toolsUsed: externalToolCalls.map((tc) => tc.toolName),
|
|
1447
|
+
toolExecutions: transformToolExecutions(externalToolExecutions),
|
|
1370
1448
|
metadata: {
|
|
1371
1449
|
streamId: `native-vertex-${Date.now()}`,
|
|
1372
1450
|
startTime,
|
|
@@ -1768,6 +1846,10 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1768
1846
|
});
|
|
1769
1847
|
// Execute each function and collect responses
|
|
1770
1848
|
const functionResponses = [];
|
|
1849
|
+
const toolCallsBefore = allToolCalls.length;
|
|
1850
|
+
const toolExecsBefore = toolExecutions.length;
|
|
1851
|
+
// Note: tool:start / tool:end events are emitted by ToolsManager's
|
|
1852
|
+
// wrapped `execute` (see ToolsManager.ts:355) — no inline emit needed.
|
|
1771
1853
|
for (const call of stepFunctionCalls) {
|
|
1772
1854
|
allToolCalls.push({ toolName: call.name, args: call.args });
|
|
1773
1855
|
// Check if this tool has already exceeded retry limit
|
|
@@ -1870,6 +1952,32 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1870
1952
|
});
|
|
1871
1953
|
}
|
|
1872
1954
|
}
|
|
1955
|
+
// Persist this step's tool calls/results into conversation memory.
|
|
1956
|
+
// Without this, tool_call / tool_result rows never reach Redis and
|
|
1957
|
+
// the chat-history UI loses every tool invocation. The first call
|
|
1958
|
+
// of the step carries the step's `thoughtSignature` so Gemini 3 can
|
|
1959
|
+
// match thinking patterns on replay.
|
|
1960
|
+
const stepToolCalls = allToolCalls.slice(toolCallsBefore);
|
|
1961
|
+
const stepToolExecs = toolExecutions.slice(toolExecsBefore);
|
|
1962
|
+
if (stepToolCalls.length > 0 || stepToolExecs.length > 0) {
|
|
1963
|
+
const stepThoughtSig = extractThoughtSignature(rawResponseParts);
|
|
1964
|
+
withTimeout(this.handleToolExecutionStorage(stepToolCalls.map((tc, i) => ({
|
|
1965
|
+
toolName: tc.toolName,
|
|
1966
|
+
args: tc.args,
|
|
1967
|
+
...(i === 0 && stepThoughtSig
|
|
1968
|
+
? { thoughtSignature: stepThoughtSig }
|
|
1969
|
+
: {}),
|
|
1970
|
+
stepIndex: step,
|
|
1971
|
+
})), stepToolExecs.map((te) => ({
|
|
1972
|
+
toolName: te.name,
|
|
1973
|
+
output: te.output,
|
|
1974
|
+
stepIndex: step,
|
|
1975
|
+
})), options, new Date()), TOOL_STORAGE_TIMEOUT_MS, "tool storage write timed out").catch((error) => {
|
|
1976
|
+
logger.warn("[GoogleVertex] Failed to store native Gemini generate tool executions", {
|
|
1977
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1978
|
+
});
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1873
1981
|
// The @google/genai SDK only accepts "user" and "model" as valid
|
|
1874
1982
|
// roles in contents — function/tool responses must use role: "user"
|
|
1875
1983
|
// (matching the SDK's automaticFunctionCalling implementation and
|
|
@@ -1908,7 +2016,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1908
2016
|
},
|
|
1909
2017
|
responseTime,
|
|
1910
2018
|
toolsUsed: externalToolCalls.map((tc) => tc.toolName),
|
|
1911
|
-
toolExecutions: externalToolExecutions,
|
|
2019
|
+
toolExecutions: transformToolExecutions(externalToolExecutions),
|
|
1912
2020
|
enhancedWithTools: externalToolCalls.length > 0,
|
|
1913
2021
|
};
|
|
1914
2022
|
// Add structured output if final_result tool was used
|
|
@@ -1944,7 +2052,15 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1944
2052
|
});
|
|
1945
2053
|
// Build messages from input
|
|
1946
2054
|
const messages = [];
|
|
1947
|
-
// Add conversation history if present
|
|
2055
|
+
// Add conversation history if present.
|
|
2056
|
+
//
|
|
2057
|
+
// Intentionally text-only. Anthropic's API rejects messages where a
|
|
2058
|
+
// tool_use_id reference appears without its matching tool_use in the
|
|
2059
|
+
// same turn — so synthesising tool_use / tool_result blocks from
|
|
2060
|
+
// stored ChatMessages risks emitting orphaned references that fail
|
|
2061
|
+
// validation. Tool rows are still persisted to Redis (chat-history
|
|
2062
|
+
// UI renders them) but they don't re-enter the model's context on
|
|
2063
|
+
// subsequent turns.
|
|
1948
2064
|
if (options.conversationMessages &&
|
|
1949
2065
|
options.conversationMessages.length > 0) {
|
|
1950
2066
|
for (const msg of options.conversationMessages) {
|
|
@@ -2175,157 +2291,270 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2175
2291
|
stop_sequences: options.stopSequences,
|
|
2176
2292
|
}),
|
|
2177
2293
|
};
|
|
2178
|
-
//
|
|
2294
|
+
// ── Real-time streaming via stream.on('text', ...) ────────────────────
|
|
2295
|
+
//
|
|
2296
|
+
// The Anthropic SDK exposes per-delta streaming through `stream.on('text', listener)`:
|
|
2297
|
+
// each content_block_delta SSE event fires the listener synchronously
|
|
2298
|
+
// with that token's text — typically ~10 chars per delta, ~26ms apart
|
|
2299
|
+
// on Claude Haiku. Awaiting `stream.finalMessage()` here would buffer
|
|
2300
|
+
// the entire response before yielding anything; the listener pattern
|
|
2301
|
+
// keeps the wire and the consumer in lockstep instead.
|
|
2302
|
+
//
|
|
2303
|
+
// Structure: push-channel + background agentic loop, returning the
|
|
2304
|
+
// StreamResult immediately so callers can iterate `channel.iterable`
|
|
2305
|
+
// while generation is still in progress. Mirrors the executeStream
|
|
2306
|
+
// pattern in googleAiStudio.ts.
|
|
2179
2307
|
const maxSteps = options.maxSteps || DEFAULT_MAX_STEPS;
|
|
2180
|
-
let step = 0;
|
|
2181
|
-
let finalText = "";
|
|
2182
|
-
let structuredOutput;
|
|
2183
2308
|
const allToolCalls = [];
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
//
|
|
2187
|
-
//
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2309
|
+
const toolExecutions = [];
|
|
2310
|
+
const channel = createTextChannel();
|
|
2311
|
+
// Mutable holders the StreamResult references. Background loop updates
|
|
2312
|
+
// these as state progresses; consumer reads them after iterating the
|
|
2313
|
+
// stream to completion (channel.close() is called AFTER mutations).
|
|
2314
|
+
const usage = { input: 0, output: 0, total: 0 };
|
|
2315
|
+
const metadata = {
|
|
2316
|
+
streamId: `native-anthropic-vertex-${Date.now()}`,
|
|
2317
|
+
startTime,
|
|
2318
|
+
responseTime: 0,
|
|
2319
|
+
totalToolExecutions: 0,
|
|
2320
|
+
};
|
|
2321
|
+
const toolsUsedRef = [];
|
|
2322
|
+
const structuredOutputRef = {};
|
|
2323
|
+
// Track the active Anthropic stream so options.abortSignal can cancel it
|
|
2324
|
+
// mid-flight (pre-rewrite code had no abort handling — fixed for free).
|
|
2325
|
+
let activeStream;
|
|
2326
|
+
const abortHandler = () => {
|
|
2194
2327
|
try {
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
// Extract text from response
|
|
2219
|
-
const textBlocks = response.content.filter((block) => block.type === "text");
|
|
2220
|
-
const responseText = textBlocks.map((b) => b.text).join("");
|
|
2221
|
-
// Preserve each Anthropic text block separately so the
|
|
2222
|
-
// consumer-visible stream yields multiple chunks (one per block).
|
|
2223
|
-
for (const tb of textBlocks) {
|
|
2224
|
-
if (tb.text.length > 0) {
|
|
2225
|
-
allTextBlocks.push(tb.text);
|
|
2328
|
+
activeStream?.controller.abort();
|
|
2329
|
+
}
|
|
2330
|
+
catch {
|
|
2331
|
+
/* ignore — stream may already be finalized */
|
|
2332
|
+
}
|
|
2333
|
+
};
|
|
2334
|
+
options.abortSignal?.addEventListener("abort", abortHandler);
|
|
2335
|
+
// Defensive upper bound: if neither the caller nor the SDK ever fires,
|
|
2336
|
+
// abort the stream after the configured timeout so a stalled
|
|
2337
|
+
// Vertex/Anthropic endpoint can't hang forever. options.timeout wins
|
|
2338
|
+
// if set; otherwise 5 min — generous for tool-heavy turns.
|
|
2339
|
+
const streamTimeoutMs = parseTimeout(options.timeout) ?? 300_000;
|
|
2340
|
+
const streamTimeoutHandle = setTimeout(() => {
|
|
2341
|
+
logger.warn(`[GoogleVertex] Anthropic stream exceeded ${streamTimeoutMs}ms — aborting`);
|
|
2342
|
+
abortHandler();
|
|
2343
|
+
}, streamTimeoutMs);
|
|
2344
|
+
const loopPromise = (async () => {
|
|
2345
|
+
let step = 0;
|
|
2346
|
+
const currentMessages = [...messages];
|
|
2347
|
+
try {
|
|
2348
|
+
while (step < maxSteps) {
|
|
2349
|
+
if (options.abortSignal?.aborted) {
|
|
2350
|
+
throw new Error("Stream aborted by caller");
|
|
2226
2351
|
}
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
break;
|
|
2232
|
-
}
|
|
2233
|
-
// Handle tool calls
|
|
2234
|
-
const toolResults = [];
|
|
2235
|
-
for (const toolUse of toolUseBlocks) {
|
|
2236
|
-
allToolCalls.push({
|
|
2237
|
-
toolName: toolUse.name,
|
|
2238
|
-
args: toolUse.input,
|
|
2352
|
+
step++;
|
|
2353
|
+
const stream = await client.messages.stream({
|
|
2354
|
+
...requestParams,
|
|
2355
|
+
messages: currentMessages,
|
|
2239
2356
|
});
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
});
|
|
2357
|
+
activeStream = stream;
|
|
2358
|
+
// Forward each text delta to the consumer as it arrives. The
|
|
2359
|
+
// Anthropic SDK fires this listener synchronously for every
|
|
2360
|
+
// content_block_delta SSE event, so the channel sees bytes at
|
|
2361
|
+
// the same cadence the wire delivers them.
|
|
2362
|
+
stream.on("text", (delta) => {
|
|
2363
|
+
if (delta.length > 0) {
|
|
2364
|
+
channel.push(delta);
|
|
2249
2365
|
}
|
|
2250
|
-
|
|
2366
|
+
});
|
|
2367
|
+
// finalMessage() resolves AFTER message_stop. By then the listener
|
|
2368
|
+
// has already fired for every delta — awaiting here doesn't block
|
|
2369
|
+
// visible streaming, it just gives us the structured response
|
|
2370
|
+
// shape needed for tool_use block extraction.
|
|
2371
|
+
const response = await stream.finalMessage();
|
|
2372
|
+
activeStream = undefined;
|
|
2373
|
+
usage.input += response.usage?.input_tokens || 0;
|
|
2374
|
+
usage.output += response.usage?.output_tokens || 0;
|
|
2375
|
+
usage.total = usage.input + usage.output;
|
|
2376
|
+
const toolUseBlocks = response.content.filter((block) => block.type === "tool_use");
|
|
2377
|
+
// Structured-output pattern: when the model returns the
|
|
2378
|
+
// final_result tool call, push its arguments as JSON and stop.
|
|
2379
|
+
// Single-shot yield so callers consuming the stream still see
|
|
2380
|
+
// the structured value.
|
|
2381
|
+
if (useFinalResultTool) {
|
|
2382
|
+
const finalResultCall = toolUseBlocks.find((block) => block.name === "final_result");
|
|
2383
|
+
if (finalResultCall) {
|
|
2384
|
+
structuredOutputRef.value = finalResultCall.input;
|
|
2385
|
+
channel.push(JSON.stringify(finalResultCall.input));
|
|
2386
|
+
logger.debug("[GoogleVertex] Extracted structured output from final_result tool (stream)", { keys: Object.keys(finalResultCall.input) });
|
|
2387
|
+
break;
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
// No tools — pure text turn. Listener already pushed all deltas;
|
|
2391
|
+
// loop terminates and channel.close() flushes the consumer.
|
|
2392
|
+
if (toolUseBlocks.length === 0) {
|
|
2393
|
+
break;
|
|
2394
|
+
}
|
|
2395
|
+
// Tool execution loop. tool:start / tool:end events fire from
|
|
2396
|
+
// ToolsManager's wrapped execute (ToolsManager.ts:355) — no inline
|
|
2397
|
+
// emit needed.
|
|
2398
|
+
const toolResults = [];
|
|
2399
|
+
// Per-step bookkeeping for conversation-memory storage.
|
|
2400
|
+
const stepStorageCalls = [];
|
|
2401
|
+
const stepStorageResults = [];
|
|
2402
|
+
// Note: tool:start / tool:end events are emitted by ToolsManager's
|
|
2403
|
+
// wrapped `execute` (see ToolsManager.ts:355) — no inline emit needed.
|
|
2404
|
+
for (const toolUse of toolUseBlocks) {
|
|
2405
|
+
allToolCalls.push({
|
|
2406
|
+
toolName: toolUse.name,
|
|
2407
|
+
args: toolUse.input,
|
|
2408
|
+
});
|
|
2409
|
+
toolsUsedRef.push(toolUse.name);
|
|
2410
|
+
stepStorageCalls.push({
|
|
2411
|
+
toolCallId: toolUse.id,
|
|
2412
|
+
toolName: toolUse.name,
|
|
2413
|
+
args: toolUse.input,
|
|
2414
|
+
});
|
|
2415
|
+
const execute = executeMap.get(toolUse.name);
|
|
2416
|
+
if (execute) {
|
|
2417
|
+
try {
|
|
2418
|
+
const toolOptions = {
|
|
2419
|
+
toolCallId: toolUse.id,
|
|
2420
|
+
messages: [],
|
|
2421
|
+
abortSignal: options.abortSignal,
|
|
2422
|
+
};
|
|
2423
|
+
const result = await execute(toolUse.input, toolOptions);
|
|
2424
|
+
toolExecutions.push({
|
|
2425
|
+
name: toolUse.name,
|
|
2426
|
+
input: toolUse.input,
|
|
2427
|
+
output: result,
|
|
2428
|
+
});
|
|
2429
|
+
// Anthropic requires tool_result.content to be a string.
|
|
2430
|
+
// JSON.stringify returns undefined for undefined/function/symbol,
|
|
2431
|
+
// so coerce defensively to keep the follow-up turn valid.
|
|
2432
|
+
const resultContent = typeof result === "string"
|
|
2433
|
+
? result
|
|
2434
|
+
: (JSON.stringify(result ?? null) ?? String(result));
|
|
2435
|
+
toolResults.push({
|
|
2436
|
+
type: "tool_result",
|
|
2437
|
+
tool_use_id: toolUse.id,
|
|
2438
|
+
content: resultContent,
|
|
2439
|
+
});
|
|
2440
|
+
stepStorageResults.push({
|
|
2441
|
+
toolCallId: toolUse.id,
|
|
2442
|
+
toolName: toolUse.name,
|
|
2443
|
+
output: result,
|
|
2444
|
+
});
|
|
2445
|
+
}
|
|
2446
|
+
catch (err) {
|
|
2447
|
+
const errMsg = `Error executing tool "${toolUse.name}": ${err instanceof Error ? err.message : String(err)}`;
|
|
2448
|
+
const errorPayload = { error: errMsg };
|
|
2449
|
+
toolExecutions.push({
|
|
2450
|
+
name: toolUse.name,
|
|
2451
|
+
input: toolUse.input,
|
|
2452
|
+
output: errorPayload,
|
|
2453
|
+
});
|
|
2454
|
+
toolResults.push({
|
|
2455
|
+
type: "tool_result",
|
|
2456
|
+
tool_use_id: toolUse.id,
|
|
2457
|
+
content: errMsg,
|
|
2458
|
+
});
|
|
2459
|
+
stepStorageResults.push({
|
|
2460
|
+
toolCallId: toolUse.id,
|
|
2461
|
+
toolName: toolUse.name,
|
|
2462
|
+
output: errorPayload,
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
else {
|
|
2467
|
+
const errMsg = `TOOL_NOT_FOUND: The tool "${toolUse.name}" does not exist.`;
|
|
2468
|
+
const errorPayload = { error: errMsg };
|
|
2469
|
+
toolExecutions.push({
|
|
2470
|
+
name: toolUse.name,
|
|
2471
|
+
input: toolUse.input,
|
|
2472
|
+
output: errorPayload,
|
|
2473
|
+
});
|
|
2251
2474
|
toolResults.push({
|
|
2252
2475
|
type: "tool_result",
|
|
2253
2476
|
tool_use_id: toolUse.id,
|
|
2254
|
-
content:
|
|
2477
|
+
content: errMsg,
|
|
2478
|
+
});
|
|
2479
|
+
stepStorageResults.push({
|
|
2480
|
+
toolCallId: toolUse.id,
|
|
2481
|
+
toolName: toolUse.name,
|
|
2482
|
+
output: errorPayload,
|
|
2255
2483
|
});
|
|
2256
2484
|
}
|
|
2257
2485
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2486
|
+
// Persist this step's tool calls/results into conversation memory.
|
|
2487
|
+
// Without this hook, tool rows never land in Redis and the
|
|
2488
|
+
// chat-history UI loses every tool invocation.
|
|
2489
|
+
if (stepStorageCalls.length > 0 || stepStorageResults.length > 0) {
|
|
2490
|
+
withTimeout(this.handleToolExecutionStorage(stepStorageCalls.map((c) => ({ ...c, stepIndex: step })), stepStorageResults.map((r) => ({ ...r, stepIndex: step })), options, new Date()), TOOL_STORAGE_TIMEOUT_MS, "tool storage write timed out").catch((error) => {
|
|
2491
|
+
logger.warn("[GoogleVertex] Failed to store native Anthropic stream tool executions", {
|
|
2492
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2493
|
+
});
|
|
2263
2494
|
});
|
|
2264
2495
|
}
|
|
2496
|
+
// Continue the loop: assistant turn + tool_result user turn.
|
|
2497
|
+
// Filter server_tool_use blocks (Anthropic API rejects them in
|
|
2498
|
+
// subsequent message turns).
|
|
2499
|
+
const assistantContent = response.content.filter((block) => block.type !== "server_tool_use");
|
|
2500
|
+
currentMessages.push({
|
|
2501
|
+
role: "assistant",
|
|
2502
|
+
content: assistantContent,
|
|
2503
|
+
});
|
|
2504
|
+
currentMessages.push({
|
|
2505
|
+
role: "user",
|
|
2506
|
+
content: toolResults,
|
|
2507
|
+
});
|
|
2265
2508
|
}
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
currentMessages.push({
|
|
2270
|
-
role: "assistant",
|
|
2271
|
-
content: assistantContent,
|
|
2272
|
-
});
|
|
2273
|
-
currentMessages.push({
|
|
2274
|
-
role: "user",
|
|
2275
|
-
content: toolResults,
|
|
2276
|
-
});
|
|
2277
|
-
// Store last text in case we hit max steps
|
|
2278
|
-
if (responseText) {
|
|
2279
|
-
finalText = responseText;
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
catch (error) {
|
|
2283
|
-
logger.error("[GoogleVertex] Native Anthropic SDK stream error", error);
|
|
2284
|
-
throw this.handleProviderError(error);
|
|
2509
|
+
metadata.responseTime = Date.now() - startTime;
|
|
2510
|
+
metadata.totalToolExecutions = allToolCalls.filter((tc) => tc.toolName !== "final_result").length;
|
|
2511
|
+
channel.close();
|
|
2285
2512
|
}
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
// stream chunks instead of a single coalesced buffer. The Anthropic
|
|
2290
|
-
// SDK gives us discrete text blocks; collapsing them into one chunk
|
|
2291
|
-
// breaks the chunk-count smoke test even though the upstream
|
|
2292
|
-
// streaming is real.
|
|
2293
|
-
const finalContentBlocks = (() => {
|
|
2294
|
-
if (structuredOutput) {
|
|
2295
|
-
return [finalText];
|
|
2513
|
+
catch (err) {
|
|
2514
|
+
logger.error("[GoogleVertex] Native Anthropic SDK stream error", err);
|
|
2515
|
+
channel.error(this.handleProviderError(err));
|
|
2296
2516
|
}
|
|
2297
|
-
|
|
2298
|
-
|
|
2517
|
+
finally {
|
|
2518
|
+
options.abortSignal?.removeEventListener("abort", abortHandler);
|
|
2519
|
+
clearTimeout(streamTimeoutHandle);
|
|
2299
2520
|
}
|
|
2300
|
-
return finalText ? [finalText] : [];
|
|
2301
2521
|
})();
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2522
|
+
// Suppress unhandled-rejection: errors funnel through channel.error()
|
|
2523
|
+
// and surface when the consumer iterates the stream.
|
|
2524
|
+
loopPromise.catch(() => undefined);
|
|
2525
|
+
// Return StreamResult IMMEDIATELY — caller's for-await can begin
|
|
2526
|
+
// iterating channel.iterable while the background loop is still
|
|
2527
|
+
// generating. usage / metadata / toolCalls / toolExecutions are mutable
|
|
2528
|
+
// references that the loop fills in over time; the consumer reads them
|
|
2529
|
+
// after iteration completes (after channel.close() has fired).
|
|
2530
|
+
const result = {
|
|
2531
|
+
stream: channel.iterable,
|
|
2311
2532
|
provider: this.providerName,
|
|
2312
2533
|
model: modelName,
|
|
2313
|
-
usage
|
|
2314
|
-
|
|
2315
|
-
output: totalOutputTokens,
|
|
2316
|
-
total: totalInputTokens + totalOutputTokens,
|
|
2317
|
-
},
|
|
2318
|
-
toolCalls: allToolCalls.map((tc) => ({
|
|
2319
|
-
toolName: tc.toolName,
|
|
2320
|
-
args: tc.args,
|
|
2321
|
-
})),
|
|
2322
|
-
metadata: {
|
|
2323
|
-
streamId: `native-anthropic-vertex-${Date.now()}`,
|
|
2324
|
-
startTime,
|
|
2325
|
-
responseTime,
|
|
2326
|
-
totalToolExecutions: allToolCalls.length,
|
|
2327
|
-
},
|
|
2534
|
+
usage,
|
|
2535
|
+
metadata,
|
|
2328
2536
|
};
|
|
2537
|
+
Object.defineProperty(result, "toolCalls", {
|
|
2538
|
+
enumerable: true,
|
|
2539
|
+
configurable: true,
|
|
2540
|
+
get: () => allToolCalls.filter((tc) => tc.toolName !== "final_result"),
|
|
2541
|
+
});
|
|
2542
|
+
Object.defineProperty(result, "toolsUsed", {
|
|
2543
|
+
enumerable: true,
|
|
2544
|
+
configurable: true,
|
|
2545
|
+
get: () => toolsUsedRef.filter((name) => name !== "final_result"),
|
|
2546
|
+
});
|
|
2547
|
+
Object.defineProperty(result, "toolExecutions", {
|
|
2548
|
+
enumerable: true,
|
|
2549
|
+
configurable: true,
|
|
2550
|
+
get: () => transformToolExecutions(toolExecutions.filter((te) => te.name !== "final_result")),
|
|
2551
|
+
});
|
|
2552
|
+
Object.defineProperty(result, "structuredOutput", {
|
|
2553
|
+
enumerable: true,
|
|
2554
|
+
configurable: true,
|
|
2555
|
+
get: () => structuredOutputRef.value,
|
|
2556
|
+
});
|
|
2557
|
+
return result;
|
|
2329
2558
|
}
|
|
2330
2559
|
/**
|
|
2331
2560
|
* Execute generate using native @anthropic-ai/vertex-sdk for Claude models on Vertex AI
|
|
@@ -2348,6 +2577,9 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2348
2577
|
// the older surface. The Vertex Claude STREAM path already follows this
|
|
2349
2578
|
// priority — keeping the GENERATE path on `conversationHistory` only
|
|
2350
2579
|
// would silently drop multi-turn context for memory/loop sessions.
|
|
2580
|
+
// Intentionally text-only: see the stream sibling for the rationale —
|
|
2581
|
+
// synthesising tool_use / tool_result blocks from stored ChatMessages
|
|
2582
|
+
// risks emitting orphaned references that Anthropic's API rejects.
|
|
2351
2583
|
const historyMessages = options.conversationMessages && options.conversationMessages.length > 0
|
|
2352
2584
|
? options.conversationMessages
|
|
2353
2585
|
: options.conversationHistory;
|
|
@@ -2591,10 +2823,14 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2591
2823
|
while (step < maxSteps) {
|
|
2592
2824
|
step++;
|
|
2593
2825
|
try {
|
|
2594
|
-
|
|
2826
|
+
// Bound the SDK wait so a stalled Vertex/Anthropic call can't hang
|
|
2827
|
+
// generate forever. options.timeout wins if set, otherwise default
|
|
2828
|
+
// to 5 min — generous for tool-heavy turns.
|
|
2829
|
+
const generateTimeoutMs = parseTimeout(options.timeout) ?? 300_000;
|
|
2830
|
+
const response = await withTimeout(client.messages.create({
|
|
2595
2831
|
...requestParams,
|
|
2596
2832
|
messages: currentMessages,
|
|
2597
|
-
});
|
|
2833
|
+
}), generateTimeoutMs, "Anthropic generate timed out");
|
|
2598
2834
|
// Update token counts
|
|
2599
2835
|
totalInputTokens += response.usage?.input_tokens || 0;
|
|
2600
2836
|
totalOutputTokens += response.usage?.output_tokens || 0;
|
|
@@ -2621,42 +2857,105 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2621
2857
|
}
|
|
2622
2858
|
// Handle tool calls
|
|
2623
2859
|
const toolResults = [];
|
|
2860
|
+
// Per-step bookkeeping for conversation-memory storage. Tracks calls
|
|
2861
|
+
// and results for ONLY the tools fired in this step so the storage
|
|
2862
|
+
// hook can tag them with the current stepIndex.
|
|
2863
|
+
const stepStorageCalls = [];
|
|
2864
|
+
const stepStorageResults = [];
|
|
2865
|
+
// Note: tool:start / tool:end events are emitted by ToolsManager's
|
|
2866
|
+
// wrapped `execute` (see ToolsManager.ts:355) — no inline emit needed.
|
|
2624
2867
|
for (const toolUse of toolUseBlocks) {
|
|
2625
2868
|
allToolCalls.push({
|
|
2626
2869
|
toolName: toolUse.name,
|
|
2627
2870
|
args: toolUse.input,
|
|
2628
2871
|
});
|
|
2872
|
+
stepStorageCalls.push({
|
|
2873
|
+
toolCallId: toolUse.id,
|
|
2874
|
+
toolName: toolUse.name,
|
|
2875
|
+
args: toolUse.input,
|
|
2876
|
+
});
|
|
2629
2877
|
const execute = executeMap.get(toolUse.name);
|
|
2630
2878
|
if (execute) {
|
|
2631
2879
|
try {
|
|
2632
|
-
const
|
|
2880
|
+
const toolOptions = {
|
|
2881
|
+
toolCallId: toolUse.id,
|
|
2882
|
+
messages: [],
|
|
2883
|
+
abortSignal: options.abortSignal,
|
|
2884
|
+
};
|
|
2885
|
+
const result = await execute(toolUse.input, toolOptions);
|
|
2633
2886
|
toolExecutions.push({
|
|
2634
2887
|
name: toolUse.name,
|
|
2635
2888
|
input: toolUse.input,
|
|
2636
2889
|
output: result,
|
|
2637
2890
|
});
|
|
2891
|
+
// Anthropic requires tool_result.content to be a string.
|
|
2892
|
+
// JSON.stringify returns undefined for undefined/function/symbol,
|
|
2893
|
+
// so coerce defensively to keep the follow-up turn valid.
|
|
2894
|
+
const resultContent = typeof result === "string"
|
|
2895
|
+
? result
|
|
2896
|
+
: (JSON.stringify(result ?? null) ?? String(result));
|
|
2638
2897
|
toolResults.push({
|
|
2639
2898
|
type: "tool_result",
|
|
2640
2899
|
tool_use_id: toolUse.id,
|
|
2641
|
-
content:
|
|
2900
|
+
content: resultContent,
|
|
2901
|
+
});
|
|
2902
|
+
stepStorageResults.push({
|
|
2903
|
+
toolCallId: toolUse.id,
|
|
2904
|
+
toolName: toolUse.name,
|
|
2905
|
+
output: result,
|
|
2642
2906
|
});
|
|
2643
2907
|
}
|
|
2644
2908
|
catch (err) {
|
|
2909
|
+
const errMsg = `Error executing tool "${toolUse.name}": ${err instanceof Error ? err.message : String(err)}`;
|
|
2910
|
+
const errorPayload = { error: errMsg };
|
|
2911
|
+
toolExecutions.push({
|
|
2912
|
+
name: toolUse.name,
|
|
2913
|
+
input: toolUse.input,
|
|
2914
|
+
output: errorPayload,
|
|
2915
|
+
});
|
|
2645
2916
|
toolResults.push({
|
|
2646
2917
|
type: "tool_result",
|
|
2647
2918
|
tool_use_id: toolUse.id,
|
|
2648
|
-
content:
|
|
2919
|
+
content: errMsg,
|
|
2920
|
+
});
|
|
2921
|
+
stepStorageResults.push({
|
|
2922
|
+
toolCallId: toolUse.id,
|
|
2923
|
+
toolName: toolUse.name,
|
|
2924
|
+
output: errorPayload,
|
|
2649
2925
|
});
|
|
2650
2926
|
}
|
|
2651
2927
|
}
|
|
2652
2928
|
else {
|
|
2929
|
+
const errMsg = `TOOL_NOT_FOUND: The tool "${toolUse.name}" does not exist.`;
|
|
2930
|
+
const errorPayload = { error: errMsg };
|
|
2931
|
+
toolExecutions.push({
|
|
2932
|
+
name: toolUse.name,
|
|
2933
|
+
input: toolUse.input,
|
|
2934
|
+
output: errorPayload,
|
|
2935
|
+
});
|
|
2653
2936
|
toolResults.push({
|
|
2654
2937
|
type: "tool_result",
|
|
2655
2938
|
tool_use_id: toolUse.id,
|
|
2656
|
-
content:
|
|
2939
|
+
content: errMsg,
|
|
2940
|
+
});
|
|
2941
|
+
stepStorageResults.push({
|
|
2942
|
+
toolCallId: toolUse.id,
|
|
2943
|
+
toolName: toolUse.name,
|
|
2944
|
+
output: errorPayload,
|
|
2657
2945
|
});
|
|
2658
2946
|
}
|
|
2659
2947
|
}
|
|
2948
|
+
// Persist this step's tool calls/results into conversation memory.
|
|
2949
|
+
// Without this, tool_call / tool_result rows never reach Redis and
|
|
2950
|
+
// the chat-history UI loses every tool invocation.
|
|
2951
|
+
// Fire-and-forget — storage failures must not break generation.
|
|
2952
|
+
if (stepStorageCalls.length > 0 || stepStorageResults.length > 0) {
|
|
2953
|
+
withTimeout(this.handleToolExecutionStorage(stepStorageCalls.map((c) => ({ ...c, stepIndex: step })), stepStorageResults.map((r) => ({ ...r, stepIndex: step })), options, new Date()), TOOL_STORAGE_TIMEOUT_MS, "tool storage write timed out").catch((error) => {
|
|
2954
|
+
logger.warn("[GoogleVertex] Failed to store native Anthropic generate tool executions", {
|
|
2955
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2956
|
+
});
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2660
2959
|
// Add assistant message and tool results to continue the loop
|
|
2661
2960
|
// Filter out server_tool_use blocks that the Anthropic API doesn't accept in messages
|
|
2662
2961
|
const assistantContent = response.content.filter((block) => block.type !== "server_tool_use");
|
|
@@ -2679,6 +2978,8 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2679
2978
|
}
|
|
2680
2979
|
}
|
|
2681
2980
|
const responseTime = Date.now() - startTime;
|
|
2981
|
+
const externalToolCalls = allToolCalls.filter((tc) => tc.toolName !== "final_result");
|
|
2982
|
+
const externalToolExecutions = toolExecutions.filter((te) => te.name !== "final_result");
|
|
2682
2983
|
const result = {
|
|
2683
2984
|
content: finalText,
|
|
2684
2985
|
provider: this.providerName,
|
|
@@ -2689,9 +2990,9 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2689
2990
|
total: totalInputTokens + totalOutputTokens,
|
|
2690
2991
|
},
|
|
2691
2992
|
responseTime,
|
|
2692
|
-
toolsUsed:
|
|
2693
|
-
toolExecutions,
|
|
2694
|
-
enhancedWithTools:
|
|
2993
|
+
toolsUsed: externalToolCalls.map((tc) => tc.toolName),
|
|
2994
|
+
toolExecutions: transformToolExecutions(externalToolExecutions),
|
|
2995
|
+
enhancedWithTools: externalToolCalls.length > 0,
|
|
2695
2996
|
};
|
|
2696
2997
|
// Route through enhanceResult so analytics/evaluation/tracing are picked
|
|
2697
2998
|
// up the same way the BaseProvider.generate() path picks them up. The
|