@juspay/neurolink 9.64.0 → 9.65.1
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 +12 -0
- package/README.md +18 -17
- package/dist/adapters/providerImageAdapter.js +29 -1
- package/dist/adapters/replicate/auth.d.ts +19 -0
- package/dist/adapters/replicate/auth.js +32 -0
- package/dist/adapters/replicate/predictionLifecycle.d.ts +46 -0
- package/dist/adapters/replicate/predictionLifecycle.js +283 -0
- package/dist/adapters/video/klingVideoHandler.d.ts +37 -0
- package/dist/adapters/video/klingVideoHandler.js +305 -0
- package/dist/adapters/video/replicateVideoHandler.d.ts +29 -0
- package/dist/adapters/video/replicateVideoHandler.js +157 -0
- package/dist/adapters/video/runwayVideoHandler.d.ts +32 -0
- package/dist/adapters/video/runwayVideoHandler.js +316 -0
- package/dist/adapters/video/vertexVideoHandler.d.ts +19 -1
- package/dist/adapters/video/vertexVideoHandler.js +33 -9
- package/dist/agent/directTools.js +11 -3
- package/dist/autoresearch/runner.js +8 -2
- package/dist/avatar/index.d.ts +13 -0
- package/dist/avatar/index.js +13 -0
- package/dist/avatar/providers/DIDAvatar.d.ts +49 -0
- package/dist/avatar/providers/DIDAvatar.js +501 -0
- package/dist/avatar/providers/HeyGenAvatar.d.ts +30 -0
- package/dist/avatar/providers/HeyGenAvatar.js +337 -0
- package/dist/avatar/providers/ReplicateAvatar.d.ts +36 -0
- package/dist/avatar/providers/ReplicateAvatar.js +267 -0
- package/dist/browser/neurolink.min.js +624 -601
- package/dist/cli/commands/mcp.js +29 -0
- package/dist/cli/commands/proxy.js +24 -5
- package/dist/cli/factories/commandFactory.d.ts +11 -1
- package/dist/cli/factories/commandFactory.js +291 -38
- package/dist/constants/contextWindows.js +101 -0
- package/dist/constants/enums.d.ts +273 -2
- package/dist/constants/enums.js +290 -1
- package/dist/constants/videoErrors.d.ts +4 -0
- package/dist/constants/videoErrors.js +4 -0
- package/dist/core/baseProvider.d.ts +22 -2
- package/dist/core/baseProvider.js +217 -11
- package/dist/core/constants.d.ts +12 -0
- package/dist/core/constants.js +72 -1
- package/dist/evaluation/index.d.ts +2 -0
- package/dist/evaluation/index.js +4 -0
- package/dist/factories/providerFactory.js +7 -1
- package/dist/factories/providerRegistry.js +202 -5
- package/dist/features/ppt/contentPlanner.js +42 -14
- package/dist/index.d.ts +9 -1
- package/dist/index.js +16 -1
- package/dist/lib/adapters/providerImageAdapter.js +29 -1
- package/dist/lib/adapters/replicate/auth.d.ts +19 -0
- package/dist/lib/adapters/replicate/auth.js +33 -0
- package/dist/lib/adapters/replicate/predictionLifecycle.d.ts +46 -0
- package/dist/lib/adapters/replicate/predictionLifecycle.js +284 -0
- package/dist/lib/adapters/video/klingVideoHandler.d.ts +37 -0
- package/dist/lib/adapters/video/klingVideoHandler.js +306 -0
- package/dist/lib/adapters/video/replicateVideoHandler.d.ts +29 -0
- package/dist/lib/adapters/video/replicateVideoHandler.js +158 -0
- package/dist/lib/adapters/video/runwayVideoHandler.d.ts +32 -0
- package/dist/lib/adapters/video/runwayVideoHandler.js +317 -0
- package/dist/lib/adapters/video/vertexVideoHandler.d.ts +19 -1
- package/dist/lib/adapters/video/vertexVideoHandler.js +33 -9
- package/dist/lib/agent/directTools.js +11 -3
- package/dist/lib/autoresearch/runner.js +8 -2
- package/dist/lib/avatar/index.d.ts +13 -0
- package/dist/lib/avatar/index.js +14 -0
- package/dist/lib/avatar/providers/DIDAvatar.d.ts +49 -0
- package/dist/lib/avatar/providers/DIDAvatar.js +502 -0
- package/dist/lib/avatar/providers/HeyGenAvatar.d.ts +30 -0
- package/dist/lib/avatar/providers/HeyGenAvatar.js +338 -0
- package/dist/lib/avatar/providers/ReplicateAvatar.d.ts +36 -0
- package/dist/lib/avatar/providers/ReplicateAvatar.js +268 -0
- package/dist/lib/constants/contextWindows.js +101 -0
- package/dist/lib/constants/enums.d.ts +273 -2
- package/dist/lib/constants/enums.js +290 -1
- package/dist/lib/constants/videoErrors.d.ts +4 -0
- package/dist/lib/constants/videoErrors.js +4 -0
- package/dist/lib/core/baseProvider.d.ts +22 -2
- package/dist/lib/core/baseProvider.js +217 -11
- package/dist/lib/core/constants.d.ts +12 -0
- package/dist/lib/core/constants.js +72 -1
- package/dist/lib/evaluation/index.d.ts +2 -0
- package/dist/lib/evaluation/index.js +4 -0
- package/dist/lib/factories/providerFactory.js +7 -1
- package/dist/lib/factories/providerRegistry.js +202 -5
- package/dist/lib/features/ppt/contentPlanner.js +42 -14
- package/dist/lib/index.d.ts +9 -1
- package/dist/lib/index.js +16 -1
- package/dist/lib/middleware/builtin/lifecycle.js +39 -9
- package/dist/lib/music/index.d.ts +13 -0
- package/dist/lib/music/index.js +14 -0
- package/dist/lib/music/providers/BeatovenMusic.d.ts +31 -0
- package/dist/lib/music/providers/BeatovenMusic.js +334 -0
- package/dist/lib/music/providers/ElevenLabsMusic.d.ts +30 -0
- package/dist/lib/music/providers/ElevenLabsMusic.js +169 -0
- package/dist/lib/music/providers/LyriaMusic.d.ts +29 -0
- package/dist/lib/music/providers/LyriaMusic.js +173 -0
- package/dist/lib/music/providers/ReplicateMusic.d.ts +31 -0
- package/dist/lib/music/providers/ReplicateMusic.js +262 -0
- package/dist/lib/neurolink.d.ts +30 -0
- package/dist/lib/neurolink.js +323 -77
- package/dist/lib/providers/amazonBedrock.d.ts +10 -0
- package/dist/lib/providers/amazonBedrock.js +94 -39
- package/dist/lib/providers/anthropic.js +55 -7
- package/dist/lib/providers/anthropicBaseProvider.js +1 -1
- package/dist/lib/providers/azureOpenai.js +66 -17
- package/dist/lib/providers/cloudflare.d.ts +35 -0
- package/dist/lib/providers/cloudflare.js +174 -0
- package/dist/lib/providers/cohere.d.ts +52 -0
- package/dist/lib/providers/cohere.js +253 -0
- package/dist/lib/providers/deepseek.js +72 -17
- package/dist/lib/providers/fireworks.d.ts +33 -0
- package/dist/lib/providers/fireworks.js +164 -0
- package/dist/lib/providers/googleAiStudio.js +126 -10
- package/dist/lib/providers/googleNativeGemini3.d.ts +26 -6
- package/dist/lib/providers/googleNativeGemini3.js +276 -29
- package/dist/lib/providers/googleVertex.js +639 -181
- package/dist/lib/providers/groq.d.ts +33 -0
- package/dist/lib/providers/groq.js +181 -0
- package/dist/lib/providers/huggingFace.js +9 -8
- package/dist/lib/providers/ideogram.d.ts +34 -0
- package/dist/lib/providers/ideogram.js +184 -0
- package/dist/lib/providers/index.d.ts +13 -0
- package/dist/lib/providers/index.js +13 -0
- package/dist/lib/providers/jina.d.ts +59 -0
- package/dist/lib/providers/jina.js +218 -0
- package/dist/lib/providers/llamaCpp.js +14 -46
- package/dist/lib/providers/lmStudio.js +14 -47
- package/dist/lib/providers/mistral.js +7 -7
- package/dist/lib/providers/nvidiaNim.js +160 -19
- package/dist/lib/providers/ollama.js +7 -7
- package/dist/lib/providers/openAI.d.ts +22 -1
- package/dist/lib/providers/openAI.js +181 -0
- package/dist/lib/providers/openRouter.js +35 -23
- package/dist/lib/providers/openaiCompatible.js +9 -8
- package/dist/lib/providers/perplexity.d.ts +33 -0
- package/dist/lib/providers/perplexity.js +179 -0
- package/dist/lib/providers/recraft.d.ts +34 -0
- package/dist/lib/providers/recraft.js +197 -0
- package/dist/lib/providers/replicate.d.ts +75 -0
- package/dist/lib/providers/replicate.js +403 -0
- package/dist/lib/providers/stability.d.ts +37 -0
- package/dist/lib/providers/stability.js +191 -0
- package/dist/lib/providers/togetherAi.d.ts +33 -0
- package/dist/lib/providers/togetherAi.js +176 -0
- package/dist/lib/providers/voyage.d.ts +47 -0
- package/dist/lib/providers/voyage.js +177 -0
- package/dist/lib/providers/xai.d.ts +33 -0
- package/dist/lib/providers/xai.js +172 -0
- package/dist/lib/telemetry/index.d.ts +1 -1
- package/dist/lib/telemetry/index.js +1 -1
- package/dist/lib/telemetry/tracers.d.ts +19 -0
- package/dist/lib/telemetry/tracers.js +19 -0
- package/dist/lib/telemetry/withSpan.d.ts +35 -0
- package/dist/lib/telemetry/withSpan.js +103 -0
- package/dist/lib/types/avatar.d.ts +143 -0
- package/dist/lib/types/avatar.js +20 -0
- package/dist/lib/types/cli.d.ts +6 -0
- package/dist/lib/types/conversation.d.ts +16 -0
- package/dist/lib/types/generate.d.ts +62 -5
- package/dist/lib/types/index.d.ts +5 -0
- package/dist/lib/types/index.js +7 -0
- package/dist/lib/types/middleware.d.ts +27 -0
- package/dist/lib/types/multimodal.d.ts +35 -2
- package/dist/lib/types/music.d.ts +165 -0
- package/dist/lib/types/music.js +21 -0
- package/dist/lib/types/providers.d.ts +144 -1
- package/dist/lib/types/replicate.d.ts +67 -0
- package/dist/lib/types/replicate.js +10 -0
- package/dist/lib/types/safeFetch.d.ts +15 -0
- package/dist/lib/types/safeFetch.js +7 -0
- package/dist/lib/types/stream.d.ts +2 -1
- package/dist/lib/types/tools.d.ts +13 -0
- package/dist/lib/types/video.d.ts +89 -0
- package/dist/lib/types/video.js +15 -0
- package/dist/lib/utils/avatarProcessor.d.ts +68 -0
- package/dist/lib/utils/avatarProcessor.js +172 -0
- package/dist/lib/utils/cloneOptions.d.ts +36 -0
- package/dist/lib/utils/cloneOptions.js +62 -0
- package/dist/lib/utils/lifecycleCallbacks.d.ts +51 -8
- package/dist/lib/utils/lifecycleCallbacks.js +82 -26
- package/dist/lib/utils/lifecycleTimeout.d.ts +25 -0
- package/dist/lib/utils/lifecycleTimeout.js +39 -0
- package/dist/lib/utils/logSanitize.d.ts +49 -0
- package/dist/lib/utils/logSanitize.js +170 -0
- package/dist/lib/utils/loggingFetch.d.ts +29 -0
- package/dist/lib/utils/loggingFetch.js +60 -0
- package/dist/lib/utils/messageBuilder.js +43 -25
- package/dist/lib/utils/modelChoices.js +236 -3
- package/dist/lib/utils/musicProcessor.d.ts +67 -0
- package/dist/lib/utils/musicProcessor.js +189 -0
- package/dist/lib/utils/optionsConversion.js +3 -2
- package/dist/lib/utils/parameterValidation.js +14 -4
- package/dist/lib/utils/pricing.js +193 -0
- package/dist/lib/utils/providerConfig.d.ts +55 -0
- package/dist/lib/utils/providerConfig.js +224 -0
- package/dist/lib/utils/safeFetch.d.ts +26 -0
- package/dist/lib/utils/safeFetch.js +83 -0
- package/dist/lib/utils/sizeGuard.d.ts +34 -0
- package/dist/lib/utils/sizeGuard.js +45 -0
- package/dist/lib/utils/ssrfGuard.d.ts +52 -0
- package/dist/lib/utils/ssrfGuard.js +411 -0
- package/dist/lib/utils/videoProcessor.d.ts +60 -0
- package/dist/lib/utils/videoProcessor.js +201 -0
- package/dist/lib/voice/providers/FishAudioTTS.d.ts +27 -0
- package/dist/lib/voice/providers/FishAudioTTS.js +183 -0
- package/dist/lib/workflow/core/ensembleExecutor.js +26 -9
- package/dist/middleware/builtin/lifecycle.js +39 -9
- package/dist/music/index.d.ts +13 -0
- package/dist/music/index.js +13 -0
- package/dist/music/providers/BeatovenMusic.d.ts +31 -0
- package/dist/music/providers/BeatovenMusic.js +333 -0
- package/dist/music/providers/ElevenLabsMusic.d.ts +30 -0
- package/dist/music/providers/ElevenLabsMusic.js +168 -0
- package/dist/music/providers/LyriaMusic.d.ts +29 -0
- package/dist/music/providers/LyriaMusic.js +172 -0
- package/dist/music/providers/ReplicateMusic.d.ts +31 -0
- package/dist/music/providers/ReplicateMusic.js +261 -0
- package/dist/neurolink.d.ts +30 -0
- package/dist/neurolink.js +323 -77
- package/dist/providers/amazonBedrock.d.ts +10 -0
- package/dist/providers/amazonBedrock.js +94 -39
- package/dist/providers/anthropic.js +55 -7
- package/dist/providers/anthropicBaseProvider.js +1 -1
- package/dist/providers/azureOpenai.js +66 -17
- package/dist/providers/cloudflare.d.ts +35 -0
- package/dist/providers/cloudflare.js +173 -0
- package/dist/providers/cohere.d.ts +52 -0
- package/dist/providers/cohere.js +252 -0
- package/dist/providers/deepseek.js +72 -17
- package/dist/providers/fireworks.d.ts +33 -0
- package/dist/providers/fireworks.js +163 -0
- package/dist/providers/googleAiStudio.js +126 -10
- package/dist/providers/googleNativeGemini3.d.ts +26 -6
- package/dist/providers/googleNativeGemini3.js +276 -29
- package/dist/providers/googleVertex.js +639 -181
- package/dist/providers/groq.d.ts +33 -0
- package/dist/providers/groq.js +180 -0
- package/dist/providers/huggingFace.js +9 -8
- package/dist/providers/ideogram.d.ts +34 -0
- package/dist/providers/ideogram.js +183 -0
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.js +13 -0
- package/dist/providers/jina.d.ts +59 -0
- package/dist/providers/jina.js +217 -0
- package/dist/providers/llamaCpp.js +14 -46
- package/dist/providers/lmStudio.js +14 -47
- package/dist/providers/mistral.js +7 -7
- package/dist/providers/nvidiaNim.js +160 -19
- package/dist/providers/ollama.js +7 -7
- package/dist/providers/openAI.d.ts +22 -1
- package/dist/providers/openAI.js +181 -0
- package/dist/providers/openRouter.js +35 -23
- package/dist/providers/openaiCompatible.js +9 -8
- package/dist/providers/perplexity.d.ts +33 -0
- package/dist/providers/perplexity.js +178 -0
- package/dist/providers/recraft.d.ts +34 -0
- package/dist/providers/recraft.js +196 -0
- package/dist/providers/replicate.d.ts +75 -0
- package/dist/providers/replicate.js +402 -0
- package/dist/providers/stability.d.ts +37 -0
- package/dist/providers/stability.js +190 -0
- package/dist/providers/togetherAi.d.ts +33 -0
- package/dist/providers/togetherAi.js +175 -0
- package/dist/providers/voyage.d.ts +47 -0
- package/dist/providers/voyage.js +176 -0
- package/dist/providers/xai.d.ts +33 -0
- package/dist/providers/xai.js +171 -0
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/telemetry/index.js +1 -1
- package/dist/telemetry/tracers.d.ts +19 -0
- package/dist/telemetry/tracers.js +19 -0
- package/dist/telemetry/withSpan.d.ts +35 -0
- package/dist/telemetry/withSpan.js +103 -0
- package/dist/types/avatar.d.ts +143 -0
- package/dist/types/avatar.js +19 -0
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/conversation.d.ts +16 -0
- package/dist/types/generate.d.ts +62 -5
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +7 -0
- package/dist/types/middleware.d.ts +27 -0
- package/dist/types/multimodal.d.ts +35 -2
- package/dist/types/music.d.ts +165 -0
- package/dist/types/music.js +20 -0
- package/dist/types/providers.d.ts +144 -1
- package/dist/types/replicate.d.ts +67 -0
- package/dist/types/replicate.js +9 -0
- package/dist/types/safeFetch.d.ts +15 -0
- package/dist/types/safeFetch.js +6 -0
- package/dist/types/stream.d.ts +2 -1
- package/dist/types/tools.d.ts +13 -0
- package/dist/types/video.d.ts +89 -0
- package/dist/types/video.js +14 -0
- package/dist/utils/avatarProcessor.d.ts +68 -0
- package/dist/utils/avatarProcessor.js +171 -0
- package/dist/utils/cloneOptions.d.ts +36 -0
- package/dist/utils/cloneOptions.js +61 -0
- package/dist/utils/lifecycleCallbacks.d.ts +51 -8
- package/dist/utils/lifecycleCallbacks.js +82 -26
- package/dist/utils/lifecycleTimeout.d.ts +25 -0
- package/dist/utils/lifecycleTimeout.js +38 -0
- package/dist/utils/logSanitize.d.ts +49 -0
- package/dist/utils/logSanitize.js +169 -0
- package/dist/utils/loggingFetch.d.ts +29 -0
- package/dist/utils/loggingFetch.js +59 -0
- package/dist/utils/messageBuilder.js +43 -25
- package/dist/utils/modelChoices.js +236 -3
- package/dist/utils/musicProcessor.d.ts +67 -0
- package/dist/utils/musicProcessor.js +188 -0
- package/dist/utils/optionsConversion.js +3 -2
- package/dist/utils/parameterValidation.js +14 -4
- package/dist/utils/pricing.js +193 -0
- package/dist/utils/providerConfig.d.ts +55 -0
- package/dist/utils/providerConfig.js +224 -0
- package/dist/utils/safeFetch.d.ts +26 -0
- package/dist/utils/safeFetch.js +82 -0
- package/dist/utils/sizeGuard.d.ts +34 -0
- package/dist/utils/sizeGuard.js +44 -0
- package/dist/utils/ssrfGuard.d.ts +52 -0
- package/dist/utils/ssrfGuard.js +410 -0
- package/dist/utils/videoProcessor.d.ts +60 -0
- package/dist/utils/videoProcessor.js +200 -0
- package/dist/voice/providers/FishAudioTTS.d.ts +27 -0
- package/dist/voice/providers/FishAudioTTS.js +182 -0
- package/dist/workflow/core/ensembleExecutor.js +26 -9
- package/package.json +32 -5
|
@@ -1,3 +1,4 @@
|
|
|
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";
|
|
@@ -5,7 +6,7 @@ import os from "os";
|
|
|
5
6
|
import {} from "ai";
|
|
6
7
|
import { AIProviderName, ErrorCategory, ErrorSeverity, } from "../constants/enums.js";
|
|
7
8
|
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";
|
|
9
|
+
import { DEFAULT_MAX_STEPS, DEFAULT_TOOL_MAX_RETRIES, GLOBAL_LOCATION_MODELS, IMAGE_GENERATION_MODELS, TOOL_STORAGE_TIMEOUT_MS, } from "../core/constants.js";
|
|
9
10
|
import { ModelConfigurationManager } from "../core/modelConfiguration.js";
|
|
10
11
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
11
12
|
import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
|
|
@@ -17,10 +18,12 @@ import { hasRestrictedOutputLimit, RESTRICTED_OUTPUT_TOKEN_LIMIT, } from "../uti
|
|
|
17
18
|
import { validateApiKey, createVertexProjectConfig, createGoogleAuthConfig, } from "../utils/providerConfig.js";
|
|
18
19
|
import { convertZodToJsonSchema, inlineJsonSchema, ensureNestedSchemaTypes, } from "../utils/schemaConversion.js";
|
|
19
20
|
import { createNativeThinkingConfig } from "../utils/thinkingConfig.js";
|
|
20
|
-
import { TimeoutError } from "../utils/async/index.js";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
21
|
+
import { TimeoutError, withTimeout } from "../utils/async/index.js";
|
|
22
|
+
import { parseTimeout } from "../utils/timeout.js";
|
|
23
|
+
import { createTextChannel, extractThoughtSignature, prependConversationMessages, } from "./googleNativeGemini3.js";
|
|
24
|
+
import { ATTR, tracers, withClientSpan, withClientStreamSpan, withSpan, } from "../telemetry/index.js";
|
|
23
25
|
import { calculateCost } from "../utils/pricing.js";
|
|
26
|
+
import { transformToolExecutions } from "../utils/transformationUtils.js";
|
|
24
27
|
// Import proper types for multimodal message handling
|
|
25
28
|
// Dynamic import helper for native Anthropic Vertex SDK
|
|
26
29
|
let anthropicVertexModule = null;
|
|
@@ -36,6 +39,110 @@ const hasAnthropicSupport = () => {
|
|
|
36
39
|
// Actual availability is checked at runtime when creating the client
|
|
37
40
|
return true;
|
|
38
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* Recursively strip JSON-schema fields that Vertex Gemini's function-call
|
|
44
|
+
* validator rejects with 400 INVALID_ARGUMENT. Vertex implements OpenAPI 3.0
|
|
45
|
+
* Schema strictly and rejects extension fields that the broader JSON Schema
|
|
46
|
+
* spec allows. The fields stripped here have no semantic meaning for the
|
|
47
|
+
* model, so removing them is safe for every caller.
|
|
48
|
+
*
|
|
49
|
+
* Fields removed:
|
|
50
|
+
* - `additionalProperties` — extension; Vertex rejects on any nested object.
|
|
51
|
+
* - `default` — Vertex rejects defaults on object/array-typed properties and
|
|
52
|
+
* on properties that are also marked `required`. Safest to strip globally
|
|
53
|
+
* because the model never inspects them.
|
|
54
|
+
* - `$schema`, `$id`, `$ref`, `definitions`, `$defs` — JSON-Schema-meta
|
|
55
|
+
* fields that Vertex doesn't recognise.
|
|
56
|
+
* - `examples` — accepted by some Gemini variants but not 2.5-flash; strip
|
|
57
|
+
* to avoid the model rejecting tool schemas under that path.
|
|
58
|
+
*/
|
|
59
|
+
function stripAdditionalPropertiesDeep(schema) {
|
|
60
|
+
if (!schema || typeof schema !== "object") {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const FIELDS_TO_STRIP = [
|
|
64
|
+
"additionalProperties",
|
|
65
|
+
"default",
|
|
66
|
+
"$schema",
|
|
67
|
+
"$id",
|
|
68
|
+
"$ref",
|
|
69
|
+
"definitions",
|
|
70
|
+
"$defs",
|
|
71
|
+
"examples",
|
|
72
|
+
];
|
|
73
|
+
for (const field of FIELDS_TO_STRIP) {
|
|
74
|
+
if (field in schema) {
|
|
75
|
+
delete schema[field];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// JSON Schema Draft-4 `exclusiveMinimum: true` / `exclusiveMaximum: true`
|
|
79
|
+
// (boolean form) is rejected by Vertex's OpenAPI 3.0 validator, which
|
|
80
|
+
// expects a numeric bound. zod-to-json-schema's openApi3 target still
|
|
81
|
+
// emits the Draft-4 form for `z.number().positive()` etc. Translate the
|
|
82
|
+
// boolean form into the numeric form when paired with `minimum` /
|
|
83
|
+
// `maximum`; otherwise drop it (the model doesn't validate, so the
|
|
84
|
+
// constraint is informational only).
|
|
85
|
+
if (typeof schema.exclusiveMinimum === "boolean") {
|
|
86
|
+
if (schema.exclusiveMinimum === true &&
|
|
87
|
+
typeof schema.minimum === "number") {
|
|
88
|
+
schema.exclusiveMinimum = schema.minimum;
|
|
89
|
+
delete schema.minimum;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
delete schema.exclusiveMinimum;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (typeof schema.exclusiveMaximum === "boolean") {
|
|
96
|
+
if (schema.exclusiveMaximum === true &&
|
|
97
|
+
typeof schema.maximum === "number") {
|
|
98
|
+
schema.exclusiveMaximum = schema.maximum;
|
|
99
|
+
delete schema.maximum;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
delete schema.exclusiveMaximum;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Strip `maximum` values that exceed int32 range — Vertex's protobuf
|
|
106
|
+
// serializer treats `type: "integer"` as int32 and rejects bounds beyond
|
|
107
|
+
// 2^31. zod's `.positive().int()` emits Number.MAX_SAFE_INTEGER as the
|
|
108
|
+
// upper bound (8.9e15), which trips this. The constraint is informational
|
|
109
|
+
// for the model anyway, so dropping it is safe.
|
|
110
|
+
const INT32_MAX = 2147483647;
|
|
111
|
+
if (typeof schema.maximum === "number" && schema.maximum > INT32_MAX) {
|
|
112
|
+
delete schema.maximum;
|
|
113
|
+
}
|
|
114
|
+
if (typeof schema.minimum === "number" && schema.minimum < -INT32_MAX) {
|
|
115
|
+
delete schema.minimum;
|
|
116
|
+
}
|
|
117
|
+
if (schema.properties && typeof schema.properties === "object") {
|
|
118
|
+
for (const child of Object.values(schema.properties)) {
|
|
119
|
+
if (child && typeof child === "object") {
|
|
120
|
+
stripAdditionalPropertiesDeep(child);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (schema.items && typeof schema.items === "object") {
|
|
125
|
+
if (Array.isArray(schema.items)) {
|
|
126
|
+
for (const item of schema.items) {
|
|
127
|
+
if (item && typeof item === "object") {
|
|
128
|
+
stripAdditionalPropertiesDeep(item);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
stripAdditionalPropertiesDeep(schema.items);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
for (const key of ["allOf", "anyOf", "oneOf"]) {
|
|
137
|
+
if (Array.isArray(schema[key])) {
|
|
138
|
+
for (const branch of schema[key]) {
|
|
139
|
+
if (branch && typeof branch === "object") {
|
|
140
|
+
stripAdditionalPropertiesDeep(branch);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
39
146
|
// Configuration helpers - now using consolidated utility
|
|
40
147
|
const getVertexProjectId = () => {
|
|
41
148
|
return validateApiKey(createVertexProjectConfig());
|
|
@@ -605,7 +712,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
605
712
|
// the test:tracing observability harness sees the same span hierarchy
|
|
606
713
|
// it sees for AI Studio. BaseProvider.stream does NOT emit this span
|
|
607
714
|
// for any provider — each native provider has to add it itself.
|
|
608
|
-
return
|
|
715
|
+
return withClientStreamSpan({
|
|
609
716
|
name: "neurolink.provider.stream",
|
|
610
717
|
tracer: tracers.provider,
|
|
611
718
|
attributes: {
|
|
@@ -672,7 +779,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
672
779
|
this.emitStreamEnd(modelName, streamStartTime, false, error);
|
|
673
780
|
throw error;
|
|
674
781
|
}
|
|
675
|
-
});
|
|
782
|
+
}, (r) => r.stream, (r, wrapped) => ({ ...r, stream: wrapped }));
|
|
676
783
|
}
|
|
677
784
|
/**
|
|
678
785
|
* Emit `stream:end` so the Pipeline B observability listener creates a
|
|
@@ -747,8 +854,11 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
747
854
|
});
|
|
748
855
|
// Build contents from input with multimodal support
|
|
749
856
|
const contents = [];
|
|
750
|
-
// Build user message parts - start with text
|
|
751
|
-
|
|
857
|
+
// Build user message parts - start with text.
|
|
858
|
+
// `options.input.text` is `string | undefined` in strict mode; the
|
|
859
|
+
// VertexNativePart `text` field requires `string`, so coerce to "" if
|
|
860
|
+
// unset (the multimodal-only path still appends other parts below).
|
|
861
|
+
const userParts = [{ text: options.input.text ?? "" }];
|
|
752
862
|
// Add PDF files as inlineData parts if present
|
|
753
863
|
// Cast input to access multimodal properties that may exist at runtime
|
|
754
864
|
const multimodalInput = options.input;
|
|
@@ -887,6 +997,12 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
887
997
|
// ensureNestedSchemaTypes recursively adds missing type fields to tool schemas
|
|
888
998
|
// Note: convertZodToJsonSchema now uses openApi3 target which produces nullable: true
|
|
889
999
|
const typedSchema = ensureNestedSchemaTypes(inlinedSchema);
|
|
1000
|
+
// Strip `additionalProperties` recursively — Vertex Gemini's
|
|
1001
|
+
// function-call validator rejects it on object schemas (returns
|
|
1002
|
+
// 400 INVALID_ARGUMENT) even though it's valid OpenAPI 3. The
|
|
1003
|
+
// field has no semantic meaning to the model, so dropping it
|
|
1004
|
+
// before send is safe for every caller.
|
|
1005
|
+
stripAdditionalPropertiesDeep(typedSchema);
|
|
890
1006
|
decl.parametersJsonSchema = typedSchema;
|
|
891
1007
|
}
|
|
892
1008
|
functionDeclarations.push(decl);
|
|
@@ -1013,6 +1129,11 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1013
1129
|
let finalText = "";
|
|
1014
1130
|
let lastStepText = ""; // Track text from last step for maxSteps termination
|
|
1015
1131
|
const allToolCalls = [];
|
|
1132
|
+
// Mirrors the generate-path shape so StreamResult.toolExecutions can be
|
|
1133
|
+
// populated (parity with AI-SDK-driven providers) and so the storage
|
|
1134
|
+
// hook can persist actual tool outputs rather than the placeholder
|
|
1135
|
+
// "success" string used by flushPendingToolData's default fallback.
|
|
1136
|
+
const toolExecutions = [];
|
|
1016
1137
|
let step = 0;
|
|
1017
1138
|
// Track structured output from final_result tool (when using final_result pattern)
|
|
1018
1139
|
let finalResultStructuredOutput;
|
|
@@ -1119,22 +1240,38 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1119
1240
|
});
|
|
1120
1241
|
// Execute each function and collect responses
|
|
1121
1242
|
const functionResponses = [];
|
|
1243
|
+
// Per-step bookkeeping for conversation-memory storage.
|
|
1244
|
+
const stepStorageCalls = [];
|
|
1245
|
+
const stepStorageResults = [];
|
|
1246
|
+
// Note: tool:start / tool:end events are emitted by ToolsManager's
|
|
1247
|
+
// wrapped `execute` (see ToolsManager.ts:355) — no inline emit needed.
|
|
1122
1248
|
for (const call of stepFunctionCalls) {
|
|
1123
1249
|
allToolCalls.push({ toolName: call.name, args: call.args });
|
|
1250
|
+
stepStorageCalls.push({ toolName: call.name, args: call.args });
|
|
1124
1251
|
// Check if this tool has already exceeded retry limit
|
|
1125
1252
|
const failedInfo = failedTools.get(call.name);
|
|
1126
1253
|
if (failedInfo && failedInfo.count >= DEFAULT_TOOL_MAX_RETRIES) {
|
|
1127
1254
|
logger.warn(`[GoogleVertex] Tool "${call.name}" has exceeded retry limit (${DEFAULT_TOOL_MAX_RETRIES}), skipping execution`);
|
|
1255
|
+
const errorPayload = {
|
|
1256
|
+
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.`,
|
|
1257
|
+
status: "permanently_failed",
|
|
1258
|
+
do_not_retry: true,
|
|
1259
|
+
};
|
|
1128
1260
|
functionResponses.push({
|
|
1129
1261
|
functionResponse: {
|
|
1130
1262
|
name: call.name,
|
|
1131
|
-
response:
|
|
1132
|
-
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.`,
|
|
1133
|
-
status: "permanently_failed",
|
|
1134
|
-
do_not_retry: true,
|
|
1135
|
-
},
|
|
1263
|
+
response: errorPayload,
|
|
1136
1264
|
},
|
|
1137
1265
|
});
|
|
1266
|
+
toolExecutions.push({
|
|
1267
|
+
name: call.name,
|
|
1268
|
+
input: call.args,
|
|
1269
|
+
output: errorPayload,
|
|
1270
|
+
});
|
|
1271
|
+
stepStorageResults.push({
|
|
1272
|
+
toolName: call.name,
|
|
1273
|
+
output: errorPayload,
|
|
1274
|
+
});
|
|
1138
1275
|
continue;
|
|
1139
1276
|
}
|
|
1140
1277
|
const execute = executeMap.get(call.name);
|
|
@@ -1147,9 +1284,18 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1147
1284
|
abortSignal: undefined,
|
|
1148
1285
|
};
|
|
1149
1286
|
const result = await execute(call.args, toolOptions);
|
|
1287
|
+
toolExecutions.push({
|
|
1288
|
+
name: call.name,
|
|
1289
|
+
input: call.args,
|
|
1290
|
+
output: result,
|
|
1291
|
+
});
|
|
1150
1292
|
functionResponses.push({
|
|
1151
1293
|
functionResponse: { name: call.name, response: { result } },
|
|
1152
1294
|
});
|
|
1295
|
+
stepStorageResults.push({
|
|
1296
|
+
toolName: call.name,
|
|
1297
|
+
output: result,
|
|
1298
|
+
});
|
|
1153
1299
|
}
|
|
1154
1300
|
catch (error) {
|
|
1155
1301
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -1164,38 +1310,77 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1164
1310
|
logger.warn(`[GoogleVertex] Tool "${call.name}" failed (attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}): ${errorMessage}`);
|
|
1165
1311
|
// Determine if this is a permanent failure
|
|
1166
1312
|
const isPermanentFailure = currentFailInfo.count >= DEFAULT_TOOL_MAX_RETRIES;
|
|
1313
|
+
const errorPayload = {
|
|
1314
|
+
error: isPermanentFailure
|
|
1315
|
+
? `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.`
|
|
1316
|
+
: `TOOL_EXECUTION_ERROR: ${errorMessage}. Retry attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}.`,
|
|
1317
|
+
status: isPermanentFailure ? "permanently_failed" : "failed",
|
|
1318
|
+
do_not_retry: isPermanentFailure,
|
|
1319
|
+
retry_count: currentFailInfo.count,
|
|
1320
|
+
max_retries: DEFAULT_TOOL_MAX_RETRIES,
|
|
1321
|
+
};
|
|
1167
1322
|
functionResponses.push({
|
|
1168
1323
|
functionResponse: {
|
|
1169
1324
|
name: call.name,
|
|
1170
|
-
response:
|
|
1171
|
-
error: isPermanentFailure
|
|
1172
|
-
? `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.`
|
|
1173
|
-
: `TOOL_EXECUTION_ERROR: ${errorMessage}. Retry attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}.`,
|
|
1174
|
-
status: isPermanentFailure
|
|
1175
|
-
? "permanently_failed"
|
|
1176
|
-
: "failed",
|
|
1177
|
-
do_not_retry: isPermanentFailure,
|
|
1178
|
-
retry_count: currentFailInfo.count,
|
|
1179
|
-
max_retries: DEFAULT_TOOL_MAX_RETRIES,
|
|
1180
|
-
},
|
|
1325
|
+
response: errorPayload,
|
|
1181
1326
|
},
|
|
1182
1327
|
});
|
|
1328
|
+
toolExecutions.push({
|
|
1329
|
+
name: call.name,
|
|
1330
|
+
input: call.args,
|
|
1331
|
+
output: errorPayload,
|
|
1332
|
+
});
|
|
1333
|
+
stepStorageResults.push({
|
|
1334
|
+
toolName: call.name,
|
|
1335
|
+
output: errorPayload,
|
|
1336
|
+
});
|
|
1183
1337
|
}
|
|
1184
1338
|
}
|
|
1185
1339
|
else {
|
|
1186
1340
|
// Tool not found is a permanent error
|
|
1341
|
+
const errorPayload = {
|
|
1342
|
+
error: `TOOL_NOT_FOUND: The tool "${call.name}" does not exist. Do not attempt to call this tool again.`,
|
|
1343
|
+
status: "permanently_failed",
|
|
1344
|
+
do_not_retry: true,
|
|
1345
|
+
};
|
|
1187
1346
|
functionResponses.push({
|
|
1188
1347
|
functionResponse: {
|
|
1189
1348
|
name: call.name,
|
|
1190
|
-
response:
|
|
1191
|
-
error: `TOOL_NOT_FOUND: The tool "${call.name}" does not exist. Do not attempt to call this tool again.`,
|
|
1192
|
-
status: "permanently_failed",
|
|
1193
|
-
do_not_retry: true,
|
|
1194
|
-
},
|
|
1349
|
+
response: errorPayload,
|
|
1195
1350
|
},
|
|
1196
1351
|
});
|
|
1352
|
+
toolExecutions.push({
|
|
1353
|
+
name: call.name,
|
|
1354
|
+
input: call.args,
|
|
1355
|
+
output: errorPayload,
|
|
1356
|
+
});
|
|
1357
|
+
stepStorageResults.push({
|
|
1358
|
+
toolName: call.name,
|
|
1359
|
+
output: errorPayload,
|
|
1360
|
+
});
|
|
1197
1361
|
}
|
|
1198
1362
|
}
|
|
1363
|
+
// Persist this step's tool calls/results into conversation memory.
|
|
1364
|
+
// Without this, tool_call / tool_result rows never reach Redis and
|
|
1365
|
+
// the chat-history UI loses every tool invocation.
|
|
1366
|
+
//
|
|
1367
|
+
// `thoughtSignature` rides as a sibling on the first call of the
|
|
1368
|
+
// step — Gemini 3 needs it to match thinking patterns when the
|
|
1369
|
+
// conversation is replayed on the next turn.
|
|
1370
|
+
if (stepStorageCalls.length > 0 || stepStorageResults.length > 0) {
|
|
1371
|
+
const stepThoughtSig = extractThoughtSignature(rawResponseParts);
|
|
1372
|
+
withTimeout(this.handleToolExecutionStorage(stepStorageCalls.map((c, i) => ({
|
|
1373
|
+
...c,
|
|
1374
|
+
...(i === 0 && stepThoughtSig
|
|
1375
|
+
? { thoughtSignature: stepThoughtSig }
|
|
1376
|
+
: {}),
|
|
1377
|
+
stepIndex: step,
|
|
1378
|
+
})), stepStorageResults.map((r) => ({ ...r, stepIndex: step })), options, new Date()), TOOL_STORAGE_TIMEOUT_MS, "tool storage write timed out").catch((error) => {
|
|
1379
|
+
logger.warn("[GoogleVertex] Failed to store native Gemini stream tool executions", {
|
|
1380
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1381
|
+
});
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1199
1384
|
// The @google/genai SDK only accepts "user" and "model" as valid
|
|
1200
1385
|
// roles in contents — function/tool responses must use role: "user"
|
|
1201
1386
|
// (matching the SDK's automaticFunctionCalling implementation and
|
|
@@ -1241,6 +1426,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1241
1426
|
}
|
|
1242
1427
|
// Filter out final_result from tool calls as it's an internal pattern
|
|
1243
1428
|
const externalToolCalls = allToolCalls.filter((tc) => tc.toolName !== "final_result");
|
|
1429
|
+
const externalToolExecutions = toolExecutions.filter((te) => te.name !== "final_result");
|
|
1244
1430
|
const result = {
|
|
1245
1431
|
stream: createTextStream(),
|
|
1246
1432
|
provider: this.providerName,
|
|
@@ -1254,6 +1440,12 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1254
1440
|
toolName: tc.toolName,
|
|
1255
1441
|
args: tc.args,
|
|
1256
1442
|
})),
|
|
1443
|
+
// Surface tools-used + execution summary so `hasToolActivity` in
|
|
1444
|
+
// conversationMemory.ts evaluates true for tool-only stream turns
|
|
1445
|
+
// (assistant text empty but tools ran) and downstream consumers see
|
|
1446
|
+
// the same shape AI-SDK-driven providers expose.
|
|
1447
|
+
toolsUsed: externalToolCalls.map((tc) => tc.toolName),
|
|
1448
|
+
toolExecutions: transformToolExecutions(externalToolExecutions),
|
|
1257
1449
|
metadata: {
|
|
1258
1450
|
streamId: `native-vertex-${Date.now()}`,
|
|
1259
1451
|
startTime,
|
|
@@ -1427,6 +1619,12 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1427
1619
|
// ensureNestedSchemaTypes recursively adds missing type fields to tool schemas
|
|
1428
1620
|
// Note: convertZodToJsonSchema now uses openApi3 target which produces nullable: true
|
|
1429
1621
|
const typedSchema = ensureNestedSchemaTypes(inlinedSchema);
|
|
1622
|
+
// Strip `additionalProperties` recursively — Vertex Gemini's
|
|
1623
|
+
// function-call validator rejects it on object schemas (returns
|
|
1624
|
+
// 400 INVALID_ARGUMENT) even though it's valid OpenAPI 3. The
|
|
1625
|
+
// field has no semantic meaning to the model, so dropping it
|
|
1626
|
+
// before send is safe for every caller.
|
|
1627
|
+
stripAdditionalPropertiesDeep(typedSchema);
|
|
1430
1628
|
decl.parametersJsonSchema = typedSchema;
|
|
1431
1629
|
}
|
|
1432
1630
|
functionDeclarations.push(decl);
|
|
@@ -1649,6 +1847,10 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1649
1847
|
});
|
|
1650
1848
|
// Execute each function and collect responses
|
|
1651
1849
|
const functionResponses = [];
|
|
1850
|
+
const toolCallsBefore = allToolCalls.length;
|
|
1851
|
+
const toolExecsBefore = toolExecutions.length;
|
|
1852
|
+
// Note: tool:start / tool:end events are emitted by ToolsManager's
|
|
1853
|
+
// wrapped `execute` (see ToolsManager.ts:355) — no inline emit needed.
|
|
1652
1854
|
for (const call of stepFunctionCalls) {
|
|
1653
1855
|
allToolCalls.push({ toolName: call.name, args: call.args });
|
|
1654
1856
|
// Check if this tool has already exceeded retry limit
|
|
@@ -1751,6 +1953,32 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1751
1953
|
});
|
|
1752
1954
|
}
|
|
1753
1955
|
}
|
|
1956
|
+
// Persist this step's tool calls/results into conversation memory.
|
|
1957
|
+
// Without this, tool_call / tool_result rows never reach Redis and
|
|
1958
|
+
// the chat-history UI loses every tool invocation. The first call
|
|
1959
|
+
// of the step carries the step's `thoughtSignature` so Gemini 3 can
|
|
1960
|
+
// match thinking patterns on replay.
|
|
1961
|
+
const stepToolCalls = allToolCalls.slice(toolCallsBefore);
|
|
1962
|
+
const stepToolExecs = toolExecutions.slice(toolExecsBefore);
|
|
1963
|
+
if (stepToolCalls.length > 0 || stepToolExecs.length > 0) {
|
|
1964
|
+
const stepThoughtSig = extractThoughtSignature(rawResponseParts);
|
|
1965
|
+
withTimeout(this.handleToolExecutionStorage(stepToolCalls.map((tc, i) => ({
|
|
1966
|
+
toolName: tc.toolName,
|
|
1967
|
+
args: tc.args,
|
|
1968
|
+
...(i === 0 && stepThoughtSig
|
|
1969
|
+
? { thoughtSignature: stepThoughtSig }
|
|
1970
|
+
: {}),
|
|
1971
|
+
stepIndex: step,
|
|
1972
|
+
})), stepToolExecs.map((te) => ({
|
|
1973
|
+
toolName: te.name,
|
|
1974
|
+
output: te.output,
|
|
1975
|
+
stepIndex: step,
|
|
1976
|
+
})), options, new Date()), TOOL_STORAGE_TIMEOUT_MS, "tool storage write timed out").catch((error) => {
|
|
1977
|
+
logger.warn("[GoogleVertex] Failed to store native Gemini generate tool executions", {
|
|
1978
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1979
|
+
});
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1754
1982
|
// The @google/genai SDK only accepts "user" and "model" as valid
|
|
1755
1983
|
// roles in contents — function/tool responses must use role: "user"
|
|
1756
1984
|
// (matching the SDK's automaticFunctionCalling implementation and
|
|
@@ -1789,7 +2017,7 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1789
2017
|
},
|
|
1790
2018
|
responseTime,
|
|
1791
2019
|
toolsUsed: externalToolCalls.map((tc) => tc.toolName),
|
|
1792
|
-
toolExecutions: externalToolExecutions,
|
|
2020
|
+
toolExecutions: transformToolExecutions(externalToolExecutions),
|
|
1793
2021
|
enhancedWithTools: externalToolCalls.length > 0,
|
|
1794
2022
|
};
|
|
1795
2023
|
// Add structured output if final_result tool was used
|
|
@@ -1825,7 +2053,15 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1825
2053
|
});
|
|
1826
2054
|
// Build messages from input
|
|
1827
2055
|
const messages = [];
|
|
1828
|
-
// Add conversation history if present
|
|
2056
|
+
// Add conversation history if present.
|
|
2057
|
+
//
|
|
2058
|
+
// Intentionally text-only. Anthropic's API rejects messages where a
|
|
2059
|
+
// tool_use_id reference appears without its matching tool_use in the
|
|
2060
|
+
// same turn — so synthesising tool_use / tool_result blocks from
|
|
2061
|
+
// stored ChatMessages risks emitting orphaned references that fail
|
|
2062
|
+
// validation. Tool rows are still persisted to Redis (chat-history
|
|
2063
|
+
// UI renders them) but they don't re-enter the model's context on
|
|
2064
|
+
// subsequent turns.
|
|
1829
2065
|
if (options.conversationMessages &&
|
|
1830
2066
|
options.conversationMessages.length > 0) {
|
|
1831
2067
|
for (const msg of options.conversationMessages) {
|
|
@@ -2056,157 +2292,270 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2056
2292
|
stop_sequences: options.stopSequences,
|
|
2057
2293
|
}),
|
|
2058
2294
|
};
|
|
2059
|
-
//
|
|
2295
|
+
// ── Real-time streaming via stream.on('text', ...) ────────────────────
|
|
2296
|
+
//
|
|
2297
|
+
// The Anthropic SDK exposes per-delta streaming through `stream.on('text', listener)`:
|
|
2298
|
+
// each content_block_delta SSE event fires the listener synchronously
|
|
2299
|
+
// with that token's text — typically ~10 chars per delta, ~26ms apart
|
|
2300
|
+
// on Claude Haiku. Awaiting `stream.finalMessage()` here would buffer
|
|
2301
|
+
// the entire response before yielding anything; the listener pattern
|
|
2302
|
+
// keeps the wire and the consumer in lockstep instead.
|
|
2303
|
+
//
|
|
2304
|
+
// Structure: push-channel + background agentic loop, returning the
|
|
2305
|
+
// StreamResult immediately so callers can iterate `channel.iterable`
|
|
2306
|
+
// while generation is still in progress. Mirrors the executeStream
|
|
2307
|
+
// pattern in googleAiStudio.ts.
|
|
2060
2308
|
const maxSteps = options.maxSteps || DEFAULT_MAX_STEPS;
|
|
2061
|
-
let step = 0;
|
|
2062
|
-
let finalText = "";
|
|
2063
|
-
let structuredOutput;
|
|
2064
2309
|
const allToolCalls = [];
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
//
|
|
2068
|
-
//
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2310
|
+
const toolExecutions = [];
|
|
2311
|
+
const channel = createTextChannel();
|
|
2312
|
+
// Mutable holders the StreamResult references. Background loop updates
|
|
2313
|
+
// these as state progresses; consumer reads them after iterating the
|
|
2314
|
+
// stream to completion (channel.close() is called AFTER mutations).
|
|
2315
|
+
const usage = { input: 0, output: 0, total: 0 };
|
|
2316
|
+
const metadata = {
|
|
2317
|
+
streamId: `native-anthropic-vertex-${Date.now()}`,
|
|
2318
|
+
startTime,
|
|
2319
|
+
responseTime: 0,
|
|
2320
|
+
totalToolExecutions: 0,
|
|
2321
|
+
};
|
|
2322
|
+
const toolsUsedRef = [];
|
|
2323
|
+
const structuredOutputRef = {};
|
|
2324
|
+
// Track the active Anthropic stream so options.abortSignal can cancel it
|
|
2325
|
+
// mid-flight (pre-rewrite code had no abort handling — fixed for free).
|
|
2326
|
+
let activeStream;
|
|
2327
|
+
const abortHandler = () => {
|
|
2075
2328
|
try {
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
// Extract text from response
|
|
2100
|
-
const textBlocks = response.content.filter((block) => block.type === "text");
|
|
2101
|
-
const responseText = textBlocks.map((b) => b.text).join("");
|
|
2102
|
-
// Preserve each Anthropic text block separately so the
|
|
2103
|
-
// consumer-visible stream yields multiple chunks (one per block).
|
|
2104
|
-
for (const tb of textBlocks) {
|
|
2105
|
-
if (tb.text.length > 0) {
|
|
2106
|
-
allTextBlocks.push(tb.text);
|
|
2329
|
+
activeStream?.controller.abort();
|
|
2330
|
+
}
|
|
2331
|
+
catch {
|
|
2332
|
+
/* ignore — stream may already be finalized */
|
|
2333
|
+
}
|
|
2334
|
+
};
|
|
2335
|
+
options.abortSignal?.addEventListener("abort", abortHandler);
|
|
2336
|
+
// Defensive upper bound: if neither the caller nor the SDK ever fires,
|
|
2337
|
+
// abort the stream after the configured timeout so a stalled
|
|
2338
|
+
// Vertex/Anthropic endpoint can't hang forever. options.timeout wins
|
|
2339
|
+
// if set; otherwise 5 min — generous for tool-heavy turns.
|
|
2340
|
+
const streamTimeoutMs = parseTimeout(options.timeout) ?? 300_000;
|
|
2341
|
+
const streamTimeoutHandle = setTimeout(() => {
|
|
2342
|
+
logger.warn(`[GoogleVertex] Anthropic stream exceeded ${streamTimeoutMs}ms — aborting`);
|
|
2343
|
+
abortHandler();
|
|
2344
|
+
}, streamTimeoutMs);
|
|
2345
|
+
const loopPromise = (async () => {
|
|
2346
|
+
let step = 0;
|
|
2347
|
+
const currentMessages = [...messages];
|
|
2348
|
+
try {
|
|
2349
|
+
while (step < maxSteps) {
|
|
2350
|
+
if (options.abortSignal?.aborted) {
|
|
2351
|
+
throw new Error("Stream aborted by caller");
|
|
2107
2352
|
}
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
break;
|
|
2113
|
-
}
|
|
2114
|
-
// Handle tool calls
|
|
2115
|
-
const toolResults = [];
|
|
2116
|
-
for (const toolUse of toolUseBlocks) {
|
|
2117
|
-
allToolCalls.push({
|
|
2118
|
-
toolName: toolUse.name,
|
|
2119
|
-
args: toolUse.input,
|
|
2353
|
+
step++;
|
|
2354
|
+
const stream = await client.messages.stream({
|
|
2355
|
+
...requestParams,
|
|
2356
|
+
messages: currentMessages,
|
|
2120
2357
|
});
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
});
|
|
2358
|
+
activeStream = stream;
|
|
2359
|
+
// Forward each text delta to the consumer as it arrives. The
|
|
2360
|
+
// Anthropic SDK fires this listener synchronously for every
|
|
2361
|
+
// content_block_delta SSE event, so the channel sees bytes at
|
|
2362
|
+
// the same cadence the wire delivers them.
|
|
2363
|
+
stream.on("text", (delta) => {
|
|
2364
|
+
if (delta.length > 0) {
|
|
2365
|
+
channel.push(delta);
|
|
2130
2366
|
}
|
|
2131
|
-
|
|
2367
|
+
});
|
|
2368
|
+
// finalMessage() resolves AFTER message_stop. By then the listener
|
|
2369
|
+
// has already fired for every delta — awaiting here doesn't block
|
|
2370
|
+
// visible streaming, it just gives us the structured response
|
|
2371
|
+
// shape needed for tool_use block extraction.
|
|
2372
|
+
const response = await stream.finalMessage();
|
|
2373
|
+
activeStream = undefined;
|
|
2374
|
+
usage.input += response.usage?.input_tokens || 0;
|
|
2375
|
+
usage.output += response.usage?.output_tokens || 0;
|
|
2376
|
+
usage.total = usage.input + usage.output;
|
|
2377
|
+
const toolUseBlocks = response.content.filter((block) => block.type === "tool_use");
|
|
2378
|
+
// Structured-output pattern: when the model returns the
|
|
2379
|
+
// final_result tool call, push its arguments as JSON and stop.
|
|
2380
|
+
// Single-shot yield so callers consuming the stream still see
|
|
2381
|
+
// the structured value.
|
|
2382
|
+
if (useFinalResultTool) {
|
|
2383
|
+
const finalResultCall = toolUseBlocks.find((block) => block.name === "final_result");
|
|
2384
|
+
if (finalResultCall) {
|
|
2385
|
+
structuredOutputRef.value = finalResultCall.input;
|
|
2386
|
+
channel.push(JSON.stringify(finalResultCall.input));
|
|
2387
|
+
logger.debug("[GoogleVertex] Extracted structured output from final_result tool (stream)", { keys: Object.keys(finalResultCall.input) });
|
|
2388
|
+
break;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
// No tools — pure text turn. Listener already pushed all deltas;
|
|
2392
|
+
// loop terminates and channel.close() flushes the consumer.
|
|
2393
|
+
if (toolUseBlocks.length === 0) {
|
|
2394
|
+
break;
|
|
2395
|
+
}
|
|
2396
|
+
// Tool execution loop. tool:start / tool:end events fire from
|
|
2397
|
+
// ToolsManager's wrapped execute (ToolsManager.ts:355) — no inline
|
|
2398
|
+
// emit needed.
|
|
2399
|
+
const toolResults = [];
|
|
2400
|
+
// Per-step bookkeeping for conversation-memory storage.
|
|
2401
|
+
const stepStorageCalls = [];
|
|
2402
|
+
const stepStorageResults = [];
|
|
2403
|
+
// Note: tool:start / tool:end events are emitted by ToolsManager's
|
|
2404
|
+
// wrapped `execute` (see ToolsManager.ts:355) — no inline emit needed.
|
|
2405
|
+
for (const toolUse of toolUseBlocks) {
|
|
2406
|
+
allToolCalls.push({
|
|
2407
|
+
toolName: toolUse.name,
|
|
2408
|
+
args: toolUse.input,
|
|
2409
|
+
});
|
|
2410
|
+
toolsUsedRef.push(toolUse.name);
|
|
2411
|
+
stepStorageCalls.push({
|
|
2412
|
+
toolCallId: toolUse.id,
|
|
2413
|
+
toolName: toolUse.name,
|
|
2414
|
+
args: toolUse.input,
|
|
2415
|
+
});
|
|
2416
|
+
const execute = executeMap.get(toolUse.name);
|
|
2417
|
+
if (execute) {
|
|
2418
|
+
try {
|
|
2419
|
+
const toolOptions = {
|
|
2420
|
+
toolCallId: toolUse.id,
|
|
2421
|
+
messages: [],
|
|
2422
|
+
abortSignal: options.abortSignal,
|
|
2423
|
+
};
|
|
2424
|
+
const result = await execute(toolUse.input, toolOptions);
|
|
2425
|
+
toolExecutions.push({
|
|
2426
|
+
name: toolUse.name,
|
|
2427
|
+
input: toolUse.input,
|
|
2428
|
+
output: result,
|
|
2429
|
+
});
|
|
2430
|
+
// Anthropic requires tool_result.content to be a string.
|
|
2431
|
+
// JSON.stringify returns undefined for undefined/function/symbol,
|
|
2432
|
+
// so coerce defensively to keep the follow-up turn valid.
|
|
2433
|
+
const resultContent = typeof result === "string"
|
|
2434
|
+
? result
|
|
2435
|
+
: (JSON.stringify(result ?? null) ?? String(result));
|
|
2436
|
+
toolResults.push({
|
|
2437
|
+
type: "tool_result",
|
|
2438
|
+
tool_use_id: toolUse.id,
|
|
2439
|
+
content: resultContent,
|
|
2440
|
+
});
|
|
2441
|
+
stepStorageResults.push({
|
|
2442
|
+
toolCallId: toolUse.id,
|
|
2443
|
+
toolName: toolUse.name,
|
|
2444
|
+
output: result,
|
|
2445
|
+
});
|
|
2446
|
+
}
|
|
2447
|
+
catch (err) {
|
|
2448
|
+
const errMsg = `Error executing tool "${toolUse.name}": ${err instanceof Error ? err.message : String(err)}`;
|
|
2449
|
+
const errorPayload = { error: errMsg };
|
|
2450
|
+
toolExecutions.push({
|
|
2451
|
+
name: toolUse.name,
|
|
2452
|
+
input: toolUse.input,
|
|
2453
|
+
output: errorPayload,
|
|
2454
|
+
});
|
|
2455
|
+
toolResults.push({
|
|
2456
|
+
type: "tool_result",
|
|
2457
|
+
tool_use_id: toolUse.id,
|
|
2458
|
+
content: errMsg,
|
|
2459
|
+
});
|
|
2460
|
+
stepStorageResults.push({
|
|
2461
|
+
toolCallId: toolUse.id,
|
|
2462
|
+
toolName: toolUse.name,
|
|
2463
|
+
output: errorPayload,
|
|
2464
|
+
});
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
else {
|
|
2468
|
+
const errMsg = `TOOL_NOT_FOUND: The tool "${toolUse.name}" does not exist.`;
|
|
2469
|
+
const errorPayload = { error: errMsg };
|
|
2470
|
+
toolExecutions.push({
|
|
2471
|
+
name: toolUse.name,
|
|
2472
|
+
input: toolUse.input,
|
|
2473
|
+
output: errorPayload,
|
|
2474
|
+
});
|
|
2132
2475
|
toolResults.push({
|
|
2133
2476
|
type: "tool_result",
|
|
2134
2477
|
tool_use_id: toolUse.id,
|
|
2135
|
-
content:
|
|
2478
|
+
content: errMsg,
|
|
2479
|
+
});
|
|
2480
|
+
stepStorageResults.push({
|
|
2481
|
+
toolCallId: toolUse.id,
|
|
2482
|
+
toolName: toolUse.name,
|
|
2483
|
+
output: errorPayload,
|
|
2136
2484
|
});
|
|
2137
2485
|
}
|
|
2138
2486
|
}
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2487
|
+
// Persist this step's tool calls/results into conversation memory.
|
|
2488
|
+
// Without this hook, tool rows never land in Redis and the
|
|
2489
|
+
// chat-history UI loses every tool invocation.
|
|
2490
|
+
if (stepStorageCalls.length > 0 || stepStorageResults.length > 0) {
|
|
2491
|
+
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) => {
|
|
2492
|
+
logger.warn("[GoogleVertex] Failed to store native Anthropic stream tool executions", {
|
|
2493
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2494
|
+
});
|
|
2144
2495
|
});
|
|
2145
2496
|
}
|
|
2497
|
+
// Continue the loop: assistant turn + tool_result user turn.
|
|
2498
|
+
// Filter server_tool_use blocks (Anthropic API rejects them in
|
|
2499
|
+
// subsequent message turns).
|
|
2500
|
+
const assistantContent = response.content.filter((block) => block.type !== "server_tool_use");
|
|
2501
|
+
currentMessages.push({
|
|
2502
|
+
role: "assistant",
|
|
2503
|
+
content: assistantContent,
|
|
2504
|
+
});
|
|
2505
|
+
currentMessages.push({
|
|
2506
|
+
role: "user",
|
|
2507
|
+
content: toolResults,
|
|
2508
|
+
});
|
|
2146
2509
|
}
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
currentMessages.push({
|
|
2151
|
-
role: "assistant",
|
|
2152
|
-
content: assistantContent,
|
|
2153
|
-
});
|
|
2154
|
-
currentMessages.push({
|
|
2155
|
-
role: "user",
|
|
2156
|
-
content: toolResults,
|
|
2157
|
-
});
|
|
2158
|
-
// Store last text in case we hit max steps
|
|
2159
|
-
if (responseText) {
|
|
2160
|
-
finalText = responseText;
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
catch (error) {
|
|
2164
|
-
logger.error("[GoogleVertex] Native Anthropic SDK stream error", error);
|
|
2165
|
-
throw this.handleProviderError(error);
|
|
2510
|
+
metadata.responseTime = Date.now() - startTime;
|
|
2511
|
+
metadata.totalToolExecutions = allToolCalls.filter((tc) => tc.toolName !== "final_result").length;
|
|
2512
|
+
channel.close();
|
|
2166
2513
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
// stream chunks instead of a single coalesced buffer. The Anthropic
|
|
2171
|
-
// SDK gives us discrete text blocks; collapsing them into one chunk
|
|
2172
|
-
// breaks the chunk-count smoke test even though the upstream
|
|
2173
|
-
// streaming is real.
|
|
2174
|
-
const finalContentBlocks = (() => {
|
|
2175
|
-
if (structuredOutput) {
|
|
2176
|
-
return [finalText];
|
|
2514
|
+
catch (err) {
|
|
2515
|
+
logger.error("[GoogleVertex] Native Anthropic SDK stream error", err);
|
|
2516
|
+
channel.error(this.handleProviderError(err));
|
|
2177
2517
|
}
|
|
2178
|
-
|
|
2179
|
-
|
|
2518
|
+
finally {
|
|
2519
|
+
options.abortSignal?.removeEventListener("abort", abortHandler);
|
|
2520
|
+
clearTimeout(streamTimeoutHandle);
|
|
2180
2521
|
}
|
|
2181
|
-
return finalText ? [finalText] : [];
|
|
2182
2522
|
})();
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2523
|
+
// Suppress unhandled-rejection: errors funnel through channel.error()
|
|
2524
|
+
// and surface when the consumer iterates the stream.
|
|
2525
|
+
loopPromise.catch(() => undefined);
|
|
2526
|
+
// Return StreamResult IMMEDIATELY — caller's for-await can begin
|
|
2527
|
+
// iterating channel.iterable while the background loop is still
|
|
2528
|
+
// generating. usage / metadata / toolCalls / toolExecutions are mutable
|
|
2529
|
+
// references that the loop fills in over time; the consumer reads them
|
|
2530
|
+
// after iteration completes (after channel.close() has fired).
|
|
2531
|
+
const result = {
|
|
2532
|
+
stream: channel.iterable,
|
|
2192
2533
|
provider: this.providerName,
|
|
2193
2534
|
model: modelName,
|
|
2194
|
-
usage
|
|
2195
|
-
|
|
2196
|
-
output: totalOutputTokens,
|
|
2197
|
-
total: totalInputTokens + totalOutputTokens,
|
|
2198
|
-
},
|
|
2199
|
-
toolCalls: allToolCalls.map((tc) => ({
|
|
2200
|
-
toolName: tc.toolName,
|
|
2201
|
-
args: tc.args,
|
|
2202
|
-
})),
|
|
2203
|
-
metadata: {
|
|
2204
|
-
streamId: `native-anthropic-vertex-${Date.now()}`,
|
|
2205
|
-
startTime,
|
|
2206
|
-
responseTime,
|
|
2207
|
-
totalToolExecutions: allToolCalls.length,
|
|
2208
|
-
},
|
|
2535
|
+
usage,
|
|
2536
|
+
metadata,
|
|
2209
2537
|
};
|
|
2538
|
+
Object.defineProperty(result, "toolCalls", {
|
|
2539
|
+
enumerable: true,
|
|
2540
|
+
configurable: true,
|
|
2541
|
+
get: () => allToolCalls.filter((tc) => tc.toolName !== "final_result"),
|
|
2542
|
+
});
|
|
2543
|
+
Object.defineProperty(result, "toolsUsed", {
|
|
2544
|
+
enumerable: true,
|
|
2545
|
+
configurable: true,
|
|
2546
|
+
get: () => toolsUsedRef.filter((name) => name !== "final_result"),
|
|
2547
|
+
});
|
|
2548
|
+
Object.defineProperty(result, "toolExecutions", {
|
|
2549
|
+
enumerable: true,
|
|
2550
|
+
configurable: true,
|
|
2551
|
+
get: () => transformToolExecutions(toolExecutions.filter((te) => te.name !== "final_result")),
|
|
2552
|
+
});
|
|
2553
|
+
Object.defineProperty(result, "structuredOutput", {
|
|
2554
|
+
enumerable: true,
|
|
2555
|
+
configurable: true,
|
|
2556
|
+
get: () => structuredOutputRef.value,
|
|
2557
|
+
});
|
|
2558
|
+
return result;
|
|
2210
2559
|
}
|
|
2211
2560
|
/**
|
|
2212
2561
|
* Execute generate using native @anthropic-ai/vertex-sdk for Claude models on Vertex AI
|
|
@@ -2229,6 +2578,9 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2229
2578
|
// the older surface. The Vertex Claude STREAM path already follows this
|
|
2230
2579
|
// priority — keeping the GENERATE path on `conversationHistory` only
|
|
2231
2580
|
// would silently drop multi-turn context for memory/loop sessions.
|
|
2581
|
+
// Intentionally text-only: see the stream sibling for the rationale —
|
|
2582
|
+
// synthesising tool_use / tool_result blocks from stored ChatMessages
|
|
2583
|
+
// risks emitting orphaned references that Anthropic's API rejects.
|
|
2232
2584
|
const historyMessages = options.conversationMessages && options.conversationMessages.length > 0
|
|
2233
2585
|
? options.conversationMessages
|
|
2234
2586
|
: options.conversationHistory;
|
|
@@ -2472,10 +2824,14 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2472
2824
|
while (step < maxSteps) {
|
|
2473
2825
|
step++;
|
|
2474
2826
|
try {
|
|
2475
|
-
|
|
2827
|
+
// Bound the SDK wait so a stalled Vertex/Anthropic call can't hang
|
|
2828
|
+
// generate forever. options.timeout wins if set, otherwise default
|
|
2829
|
+
// to 5 min — generous for tool-heavy turns.
|
|
2830
|
+
const generateTimeoutMs = parseTimeout(options.timeout) ?? 300_000;
|
|
2831
|
+
const response = await withTimeout(client.messages.create({
|
|
2476
2832
|
...requestParams,
|
|
2477
2833
|
messages: currentMessages,
|
|
2478
|
-
});
|
|
2834
|
+
}), generateTimeoutMs, "Anthropic generate timed out");
|
|
2479
2835
|
// Update token counts
|
|
2480
2836
|
totalInputTokens += response.usage?.input_tokens || 0;
|
|
2481
2837
|
totalOutputTokens += response.usage?.output_tokens || 0;
|
|
@@ -2502,42 +2858,105 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2502
2858
|
}
|
|
2503
2859
|
// Handle tool calls
|
|
2504
2860
|
const toolResults = [];
|
|
2861
|
+
// Per-step bookkeeping for conversation-memory storage. Tracks calls
|
|
2862
|
+
// and results for ONLY the tools fired in this step so the storage
|
|
2863
|
+
// hook can tag them with the current stepIndex.
|
|
2864
|
+
const stepStorageCalls = [];
|
|
2865
|
+
const stepStorageResults = [];
|
|
2866
|
+
// Note: tool:start / tool:end events are emitted by ToolsManager's
|
|
2867
|
+
// wrapped `execute` (see ToolsManager.ts:355) — no inline emit needed.
|
|
2505
2868
|
for (const toolUse of toolUseBlocks) {
|
|
2506
2869
|
allToolCalls.push({
|
|
2507
2870
|
toolName: toolUse.name,
|
|
2508
2871
|
args: toolUse.input,
|
|
2509
2872
|
});
|
|
2873
|
+
stepStorageCalls.push({
|
|
2874
|
+
toolCallId: toolUse.id,
|
|
2875
|
+
toolName: toolUse.name,
|
|
2876
|
+
args: toolUse.input,
|
|
2877
|
+
});
|
|
2510
2878
|
const execute = executeMap.get(toolUse.name);
|
|
2511
2879
|
if (execute) {
|
|
2512
2880
|
try {
|
|
2513
|
-
const
|
|
2881
|
+
const toolOptions = {
|
|
2882
|
+
toolCallId: toolUse.id,
|
|
2883
|
+
messages: [],
|
|
2884
|
+
abortSignal: options.abortSignal,
|
|
2885
|
+
};
|
|
2886
|
+
const result = await execute(toolUse.input, toolOptions);
|
|
2514
2887
|
toolExecutions.push({
|
|
2515
2888
|
name: toolUse.name,
|
|
2516
2889
|
input: toolUse.input,
|
|
2517
2890
|
output: result,
|
|
2518
2891
|
});
|
|
2892
|
+
// Anthropic requires tool_result.content to be a string.
|
|
2893
|
+
// JSON.stringify returns undefined for undefined/function/symbol,
|
|
2894
|
+
// so coerce defensively to keep the follow-up turn valid.
|
|
2895
|
+
const resultContent = typeof result === "string"
|
|
2896
|
+
? result
|
|
2897
|
+
: (JSON.stringify(result ?? null) ?? String(result));
|
|
2519
2898
|
toolResults.push({
|
|
2520
2899
|
type: "tool_result",
|
|
2521
2900
|
tool_use_id: toolUse.id,
|
|
2522
|
-
content:
|
|
2901
|
+
content: resultContent,
|
|
2902
|
+
});
|
|
2903
|
+
stepStorageResults.push({
|
|
2904
|
+
toolCallId: toolUse.id,
|
|
2905
|
+
toolName: toolUse.name,
|
|
2906
|
+
output: result,
|
|
2523
2907
|
});
|
|
2524
2908
|
}
|
|
2525
2909
|
catch (err) {
|
|
2910
|
+
const errMsg = `Error executing tool "${toolUse.name}": ${err instanceof Error ? err.message : String(err)}`;
|
|
2911
|
+
const errorPayload = { error: errMsg };
|
|
2912
|
+
toolExecutions.push({
|
|
2913
|
+
name: toolUse.name,
|
|
2914
|
+
input: toolUse.input,
|
|
2915
|
+
output: errorPayload,
|
|
2916
|
+
});
|
|
2526
2917
|
toolResults.push({
|
|
2527
2918
|
type: "tool_result",
|
|
2528
2919
|
tool_use_id: toolUse.id,
|
|
2529
|
-
content:
|
|
2920
|
+
content: errMsg,
|
|
2921
|
+
});
|
|
2922
|
+
stepStorageResults.push({
|
|
2923
|
+
toolCallId: toolUse.id,
|
|
2924
|
+
toolName: toolUse.name,
|
|
2925
|
+
output: errorPayload,
|
|
2530
2926
|
});
|
|
2531
2927
|
}
|
|
2532
2928
|
}
|
|
2533
2929
|
else {
|
|
2930
|
+
const errMsg = `TOOL_NOT_FOUND: The tool "${toolUse.name}" does not exist.`;
|
|
2931
|
+
const errorPayload = { error: errMsg };
|
|
2932
|
+
toolExecutions.push({
|
|
2933
|
+
name: toolUse.name,
|
|
2934
|
+
input: toolUse.input,
|
|
2935
|
+
output: errorPayload,
|
|
2936
|
+
});
|
|
2534
2937
|
toolResults.push({
|
|
2535
2938
|
type: "tool_result",
|
|
2536
2939
|
tool_use_id: toolUse.id,
|
|
2537
|
-
content:
|
|
2940
|
+
content: errMsg,
|
|
2941
|
+
});
|
|
2942
|
+
stepStorageResults.push({
|
|
2943
|
+
toolCallId: toolUse.id,
|
|
2944
|
+
toolName: toolUse.name,
|
|
2945
|
+
output: errorPayload,
|
|
2538
2946
|
});
|
|
2539
2947
|
}
|
|
2540
2948
|
}
|
|
2949
|
+
// Persist this step's tool calls/results into conversation memory.
|
|
2950
|
+
// Without this, tool_call / tool_result rows never reach Redis and
|
|
2951
|
+
// the chat-history UI loses every tool invocation.
|
|
2952
|
+
// Fire-and-forget — storage failures must not break generation.
|
|
2953
|
+
if (stepStorageCalls.length > 0 || stepStorageResults.length > 0) {
|
|
2954
|
+
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) => {
|
|
2955
|
+
logger.warn("[GoogleVertex] Failed to store native Anthropic generate tool executions", {
|
|
2956
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2957
|
+
});
|
|
2958
|
+
});
|
|
2959
|
+
}
|
|
2541
2960
|
// Add assistant message and tool results to continue the loop
|
|
2542
2961
|
// Filter out server_tool_use blocks that the Anthropic API doesn't accept in messages
|
|
2543
2962
|
const assistantContent = response.content.filter((block) => block.type !== "server_tool_use");
|
|
@@ -2560,6 +2979,8 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2560
2979
|
}
|
|
2561
2980
|
}
|
|
2562
2981
|
const responseTime = Date.now() - startTime;
|
|
2982
|
+
const externalToolCalls = allToolCalls.filter((tc) => tc.toolName !== "final_result");
|
|
2983
|
+
const externalToolExecutions = toolExecutions.filter((te) => te.name !== "final_result");
|
|
2563
2984
|
const result = {
|
|
2564
2985
|
content: finalText,
|
|
2565
2986
|
provider: this.providerName,
|
|
@@ -2570,9 +2991,9 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2570
2991
|
total: totalInputTokens + totalOutputTokens,
|
|
2571
2992
|
},
|
|
2572
2993
|
responseTime,
|
|
2573
|
-
toolsUsed:
|
|
2574
|
-
toolExecutions,
|
|
2575
|
-
enhancedWithTools:
|
|
2994
|
+
toolsUsed: externalToolCalls.map((tc) => tc.toolName),
|
|
2995
|
+
toolExecutions: transformToolExecutions(externalToolExecutions),
|
|
2996
|
+
enhancedWithTools: externalToolCalls.length > 0,
|
|
2576
2997
|
};
|
|
2577
2998
|
// Route through enhanceResult so analytics/evaluation/tracing are picked
|
|
2578
2999
|
// up the same way the BaseProvider.generate() path picks them up. The
|
|
@@ -2737,6 +3158,17 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2737
3158
|
this.emitGenerationEnd(modelName, videoResult, generateStartTime, true);
|
|
2738
3159
|
return videoResult;
|
|
2739
3160
|
}
|
|
3161
|
+
// TTS direct-synthesis mode: when caller passes `tts.enabled` without
|
|
3162
|
+
// `tts.useAiResponse`, route to the shared `handleDirectTTSSynthesis`
|
|
3163
|
+
// (synthesise the input text directly; no LLM call). BaseProvider's
|
|
3164
|
+
// standard generate() does the same dispatch — we replicate it here
|
|
3165
|
+
// because Vertex's override bypasses that path.
|
|
3166
|
+
if (options.tts?.enabled && !options.tts?.useAiResponse) {
|
|
3167
|
+
logger.info("[GoogleVertex] Routing TTS direct-synthesis to handleDirectTTSSynthesis", { model: modelName });
|
|
3168
|
+
const ttsResult = await this.handleDirectTTSSynthesis(options, generateStartTime);
|
|
3169
|
+
this.emitGenerationEnd(modelName, ttsResult, generateStartTime, true);
|
|
3170
|
+
return ttsResult;
|
|
3171
|
+
}
|
|
2740
3172
|
// Check if this is an image generation model - route to executeImageGeneration without tools
|
|
2741
3173
|
const isImageModel = IMAGE_GENERATION_MODELS.some((m) => modelName.toLowerCase().startsWith(m.toLowerCase()));
|
|
2742
3174
|
if (isImageModel) {
|
|
@@ -2797,23 +3229,41 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
2797
3229
|
"";
|
|
2798
3230
|
try {
|
|
2799
3231
|
let result;
|
|
2800
|
-
//
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
3232
|
+
// Wrap the actual native generate call in `neurolink.executeGeneration`
|
|
3233
|
+
// so the observability span chain (tested by
|
|
3234
|
+
// "Tracing: Generate Span Chain") sees a third inner span on the
|
|
3235
|
+
// native @google/genai / @anthropic-ai/vertex-sdk path — Pipeline A
|
|
3236
|
+
// gets this for free from GenerationHandler.executeGeneration.
|
|
3237
|
+
result = await withSpan({
|
|
3238
|
+
name: "neurolink.executeGeneration",
|
|
3239
|
+
tracer: tracers.provider,
|
|
3240
|
+
attributes: {
|
|
3241
|
+
[ATTR.GEN_AI_SYSTEM]: this.providerName,
|
|
3242
|
+
[ATTR.GEN_AI_MODEL]: modelName,
|
|
3243
|
+
"neurolink.path": isAnthropicModel(modelName)
|
|
3244
|
+
? "native.anthropic"
|
|
3245
|
+
: "native.google-genai",
|
|
3246
|
+
},
|
|
3247
|
+
}, async () => {
|
|
3248
|
+
if (isAnthropicModel(modelName)) {
|
|
3249
|
+
logger.info("[GoogleVertex] Routing Claude generate to native @anthropic-ai/vertex-sdk", {
|
|
3250
|
+
model: modelName,
|
|
3251
|
+
totalToolCount: Object.keys(mergedOptions.tools).length,
|
|
3252
|
+
});
|
|
3253
|
+
return this.executeNativeAnthropicGenerate(mergedOptions);
|
|
3254
|
+
}
|
|
2810
3255
|
logger.info("[GoogleVertex] Routing Gemini generate to native @google/genai", {
|
|
2811
3256
|
model: modelName,
|
|
2812
3257
|
totalToolCount: Object.keys(mergedOptions.tools).length,
|
|
2813
3258
|
});
|
|
2814
|
-
|
|
2815
|
-
}
|
|
3259
|
+
return this.executeNativeGemini3Generate(mergedOptions);
|
|
3260
|
+
});
|
|
2816
3261
|
this.attachUsageAndCostAttributes(generateSpan, modelName, result?.usage);
|
|
3262
|
+
// Pipe through TTS-of-AI-response when caller asks for it. The
|
|
3263
|
+
// shared `synthesizeAIResponseIfNeeded` no-ops when tts is not
|
|
3264
|
+
// enabled / useAiResponse is false, so the cost is zero on
|
|
3265
|
+
// non-TTS paths.
|
|
3266
|
+
result = await this.synthesizeAIResponseIfNeeded(result, options);
|
|
2817
3267
|
// Fire onFinish lifecycle callback for the native generate path.
|
|
2818
3268
|
// Pipeline A providers get this for free via the AI SDK middleware
|
|
2819
3269
|
// wrapper (LifecycleMiddleware); native @google/genai bypasses
|
|
@@ -3053,6 +3503,14 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
3053
3503
|
? { error: error instanceof Error ? error.message : String(error) }
|
|
3054
3504
|
: {}),
|
|
3055
3505
|
});
|
|
3506
|
+
// Mark on the result so the SDK-level runStandardGenerateRequest knows
|
|
3507
|
+
// this provider already emitted `generation:end` itself and skips its
|
|
3508
|
+
// own duplicate emission. Without this flag the public event listener
|
|
3509
|
+
// (and the observability test) would see two events per generate call.
|
|
3510
|
+
if (result && typeof result === "object") {
|
|
3511
|
+
result._generationEndEmitted =
|
|
3512
|
+
true;
|
|
3513
|
+
}
|
|
3056
3514
|
}
|
|
3057
3515
|
formatProviderError(error) {
|
|
3058
3516
|
const errorRecord = error;
|