@juspay/neurolink 9.14.0 → 9.16.0
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 +15 -15
- package/dist/adapters/video/videoAnalyzer.d.ts +1 -1
- package/dist/adapters/video/videoAnalyzer.js +10 -8
- package/dist/auth/anthropicOAuth.d.ts +377 -0
- package/dist/auth/anthropicOAuth.js +914 -0
- package/dist/auth/index.d.ts +20 -0
- package/dist/auth/index.js +29 -0
- package/dist/auth/tokenStore.d.ts +225 -0
- package/dist/auth/tokenStore.js +521 -0
- package/dist/cli/commands/auth.d.ts +50 -0
- package/dist/cli/commands/auth.js +1115 -0
- package/dist/cli/commands/setup-anthropic.js +1 -14
- package/dist/cli/commands/setup-azure.js +1 -12
- package/dist/cli/commands/setup-bedrock.js +1 -9
- package/dist/cli/commands/setup-google-ai.js +1 -12
- package/dist/cli/commands/setup-openai.js +1 -14
- package/dist/cli/commands/workflow.d.ts +27 -0
- package/dist/cli/commands/workflow.js +216 -0
- package/dist/cli/factories/authCommandFactory.d.ts +52 -0
- package/dist/cli/factories/authCommandFactory.js +146 -0
- package/dist/cli/factories/commandFactory.d.ts +6 -0
- package/dist/cli/factories/commandFactory.js +171 -22
- package/dist/cli/index.js +0 -1
- package/dist/cli/parser.js +14 -2
- package/dist/cli/utils/maskCredential.d.ts +11 -0
- package/dist/cli/utils/maskCredential.js +23 -0
- package/dist/constants/contextWindows.js +107 -16
- package/dist/constants/enums.d.ts +119 -15
- package/dist/constants/enums.js +182 -22
- package/dist/constants/index.d.ts +3 -1
- package/dist/constants/index.js +11 -1
- package/dist/context/budgetChecker.js +1 -1
- package/dist/context/contextCompactor.js +31 -4
- package/dist/context/emergencyTruncation.d.ts +21 -0
- package/dist/context/emergencyTruncation.js +88 -0
- package/dist/context/errorDetection.d.ts +16 -0
- package/dist/context/errorDetection.js +48 -1
- package/dist/context/errors.d.ts +19 -0
- package/dist/context/errors.js +21 -0
- package/dist/context/stages/slidingWindowTruncator.d.ts +6 -0
- package/dist/context/stages/slidingWindowTruncator.js +159 -24
- package/dist/core/baseProvider.js +306 -200
- package/dist/core/conversationMemoryManager.js +104 -61
- package/dist/core/evaluationProviders.js +16 -33
- package/dist/core/factory.js +237 -164
- package/dist/core/modules/GenerationHandler.js +175 -116
- package/dist/core/modules/MessageBuilder.js +222 -170
- package/dist/core/modules/StreamHandler.d.ts +1 -0
- package/dist/core/modules/StreamHandler.js +95 -27
- package/dist/core/modules/TelemetryHandler.d.ts +10 -1
- package/dist/core/modules/TelemetryHandler.js +25 -7
- package/dist/core/modules/ToolsManager.js +115 -191
- package/dist/core/redisConversationMemoryManager.js +418 -282
- package/dist/factories/providerRegistry.d.ts +5 -0
- package/dist/factories/providerRegistry.js +20 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -2
- package/dist/lib/adapters/video/videoAnalyzer.d.ts +1 -1
- package/dist/lib/adapters/video/videoAnalyzer.js +10 -8
- package/dist/lib/auth/anthropicOAuth.d.ts +377 -0
- package/dist/lib/auth/anthropicOAuth.js +915 -0
- package/dist/lib/auth/index.d.ts +20 -0
- package/dist/lib/auth/index.js +30 -0
- package/dist/lib/auth/tokenStore.d.ts +225 -0
- package/dist/lib/auth/tokenStore.js +522 -0
- package/dist/lib/constants/contextWindows.js +107 -16
- package/dist/lib/constants/enums.d.ts +119 -15
- package/dist/lib/constants/enums.js +182 -22
- package/dist/lib/constants/index.d.ts +3 -1
- package/dist/lib/constants/index.js +11 -1
- package/dist/lib/context/budgetChecker.js +1 -1
- package/dist/lib/context/contextCompactor.js +31 -4
- package/dist/lib/context/emergencyTruncation.d.ts +21 -0
- package/dist/lib/context/emergencyTruncation.js +89 -0
- package/dist/lib/context/errorDetection.d.ts +16 -0
- package/dist/lib/context/errorDetection.js +48 -1
- package/dist/lib/context/errors.d.ts +19 -0
- package/dist/lib/context/errors.js +22 -0
- package/dist/lib/context/stages/slidingWindowTruncator.d.ts +6 -0
- package/dist/lib/context/stages/slidingWindowTruncator.js +159 -24
- package/dist/lib/core/baseProvider.js +306 -200
- package/dist/lib/core/conversationMemoryManager.js +104 -61
- package/dist/lib/core/evaluationProviders.js +16 -33
- package/dist/lib/core/factory.js +237 -164
- package/dist/lib/core/modules/GenerationHandler.js +175 -116
- package/dist/lib/core/modules/MessageBuilder.js +222 -170
- package/dist/lib/core/modules/StreamHandler.d.ts +1 -0
- package/dist/lib/core/modules/StreamHandler.js +95 -27
- package/dist/lib/core/modules/TelemetryHandler.d.ts +10 -1
- package/dist/lib/core/modules/TelemetryHandler.js +25 -7
- package/dist/lib/core/modules/ToolsManager.js +115 -191
- package/dist/lib/core/redisConversationMemoryManager.js +418 -282
- package/dist/lib/factories/providerRegistry.d.ts +5 -0
- package/dist/lib/factories/providerRegistry.js +20 -2
- package/dist/lib/index.d.ts +3 -3
- package/dist/lib/index.js +4 -2
- package/dist/lib/mcp/externalServerManager.js +66 -0
- package/dist/lib/mcp/mcpCircuitBreaker.js +24 -0
- package/dist/lib/mcp/mcpClientFactory.js +16 -0
- package/dist/lib/mcp/toolDiscoveryService.js +32 -6
- package/dist/lib/mcp/toolRegistry.js +193 -123
- package/dist/lib/models/anthropicModels.d.ts +267 -0
- package/dist/lib/models/anthropicModels.js +528 -0
- package/dist/lib/neurolink.d.ts +6 -0
- package/dist/lib/neurolink.js +1162 -646
- package/dist/lib/providers/amazonBedrock.d.ts +1 -1
- package/dist/lib/providers/amazonBedrock.js +521 -319
- package/dist/lib/providers/anthropic.d.ts +123 -2
- package/dist/lib/providers/anthropic.js +873 -27
- package/dist/lib/providers/anthropicBaseProvider.js +77 -17
- package/dist/lib/providers/googleAiStudio.d.ts +1 -1
- package/dist/lib/providers/googleAiStudio.js +292 -227
- package/dist/lib/providers/googleVertex.d.ts +36 -1
- package/dist/lib/providers/googleVertex.js +553 -260
- package/dist/lib/providers/ollama.js +329 -278
- package/dist/lib/providers/openAI.js +77 -19
- package/dist/lib/providers/sagemaker/parsers.js +3 -3
- package/dist/lib/providers/sagemaker/streaming.js +3 -3
- package/dist/lib/proxy/proxyFetch.js +81 -48
- package/dist/lib/rag/ChunkerFactory.js +1 -1
- package/dist/lib/rag/chunkers/MarkdownChunker.d.ts +22 -0
- package/dist/lib/rag/chunkers/MarkdownChunker.js +213 -9
- package/dist/lib/rag/chunking/markdownChunker.d.ts +16 -0
- package/dist/lib/rag/chunking/markdownChunker.js +174 -2
- package/dist/lib/rag/pipeline/contextAssembly.js +2 -1
- package/dist/lib/rag/ragIntegration.d.ts +18 -1
- package/dist/lib/rag/ragIntegration.js +94 -14
- package/dist/lib/rag/retrieval/vectorQueryTool.js +21 -4
- package/dist/lib/server/abstract/baseServerAdapter.js +4 -1
- package/dist/lib/server/adapters/fastifyAdapter.js +35 -30
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +32 -0
- package/dist/lib/services/server/ai/observability/instrumentation.js +39 -0
- package/dist/lib/telemetry/attributes.d.ts +52 -0
- package/dist/lib/telemetry/attributes.js +61 -0
- package/dist/lib/telemetry/index.d.ts +3 -0
- package/dist/lib/telemetry/index.js +3 -0
- package/dist/lib/telemetry/telemetryService.d.ts +6 -0
- package/dist/lib/telemetry/telemetryService.js +6 -0
- package/dist/lib/telemetry/tracers.d.ts +15 -0
- package/dist/lib/telemetry/tracers.js +17 -0
- package/dist/lib/telemetry/withSpan.d.ts +9 -0
- package/dist/lib/telemetry/withSpan.js +35 -0
- package/dist/lib/types/contextTypes.d.ts +10 -0
- package/dist/lib/types/errors.d.ts +62 -0
- package/dist/lib/types/errors.js +107 -0
- package/dist/lib/types/index.d.ts +2 -1
- package/dist/lib/types/index.js +2 -0
- package/dist/lib/types/providers.d.ts +107 -0
- package/dist/lib/types/providers.js +69 -0
- package/dist/lib/types/streamTypes.d.ts +14 -0
- package/dist/lib/types/subscriptionTypes.d.ts +893 -0
- package/dist/lib/types/subscriptionTypes.js +8 -0
- package/dist/lib/utils/conversationMemory.js +121 -82
- package/dist/lib/utils/logger.d.ts +5 -0
- package/dist/lib/utils/logger.js +50 -2
- package/dist/lib/utils/messageBuilder.js +22 -42
- package/dist/lib/utils/modelDetection.js +3 -3
- package/dist/lib/utils/providerConfig.d.ts +167 -0
- package/dist/lib/utils/providerConfig.js +619 -9
- package/dist/lib/utils/providerRetry.d.ts +41 -0
- package/dist/lib/utils/providerRetry.js +114 -0
- package/dist/lib/utils/retryability.d.ts +14 -0
- package/dist/lib/utils/retryability.js +23 -0
- package/dist/lib/utils/sanitizers/svg.js +4 -5
- package/dist/lib/utils/tokenEstimation.d.ts +11 -1
- package/dist/lib/utils/tokenEstimation.js +19 -4
- package/dist/lib/utils/videoAnalysisProcessor.js +7 -3
- package/dist/mcp/externalServerManager.js +66 -0
- package/dist/mcp/mcpCircuitBreaker.js +24 -0
- package/dist/mcp/mcpClientFactory.js +16 -0
- package/dist/mcp/toolDiscoveryService.js +32 -6
- package/dist/mcp/toolRegistry.js +193 -123
- package/dist/models/anthropicModels.d.ts +267 -0
- package/dist/models/anthropicModels.js +527 -0
- package/dist/neurolink.d.ts +6 -0
- package/dist/neurolink.js +1162 -646
- package/dist/providers/amazonBedrock.d.ts +1 -1
- package/dist/providers/amazonBedrock.js +521 -319
- package/dist/providers/anthropic.d.ts +123 -2
- package/dist/providers/anthropic.js +873 -27
- package/dist/providers/anthropicBaseProvider.js +77 -17
- package/dist/providers/googleAiStudio.d.ts +1 -1
- package/dist/providers/googleAiStudio.js +292 -227
- package/dist/providers/googleVertex.d.ts +36 -1
- package/dist/providers/googleVertex.js +553 -260
- package/dist/providers/ollama.js +329 -278
- package/dist/providers/openAI.js +77 -19
- package/dist/providers/sagemaker/parsers.js +3 -3
- package/dist/providers/sagemaker/streaming.js +3 -3
- package/dist/proxy/proxyFetch.js +81 -48
- package/dist/rag/ChunkerFactory.js +1 -1
- package/dist/rag/chunkers/MarkdownChunker.d.ts +22 -0
- package/dist/rag/chunkers/MarkdownChunker.js +213 -9
- package/dist/rag/chunking/markdownChunker.d.ts +16 -0
- package/dist/rag/chunking/markdownChunker.js +174 -2
- package/dist/rag/pipeline/contextAssembly.js +2 -1
- package/dist/rag/ragIntegration.d.ts +18 -1
- package/dist/rag/ragIntegration.js +94 -14
- package/dist/rag/retrieval/vectorQueryTool.js +21 -4
- package/dist/server/abstract/baseServerAdapter.js +4 -1
- package/dist/server/adapters/fastifyAdapter.js +35 -30
- package/dist/services/server/ai/observability/instrumentation.d.ts +32 -0
- package/dist/services/server/ai/observability/instrumentation.js +39 -0
- package/dist/telemetry/attributes.d.ts +52 -0
- package/dist/telemetry/attributes.js +60 -0
- package/dist/telemetry/index.d.ts +3 -0
- package/dist/telemetry/index.js +3 -0
- package/dist/telemetry/telemetryService.d.ts +6 -0
- package/dist/telemetry/telemetryService.js +6 -0
- package/dist/telemetry/tracers.d.ts +15 -0
- package/dist/telemetry/tracers.js +16 -0
- package/dist/telemetry/withSpan.d.ts +9 -0
- package/dist/telemetry/withSpan.js +34 -0
- package/dist/types/contextTypes.d.ts +10 -0
- package/dist/types/errors.d.ts +62 -0
- package/dist/types/errors.js +107 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.js +2 -0
- package/dist/types/providers.d.ts +107 -0
- package/dist/types/providers.js +69 -0
- package/dist/types/streamTypes.d.ts +14 -0
- package/dist/types/subscriptionTypes.d.ts +893 -0
- package/dist/types/subscriptionTypes.js +7 -0
- package/dist/utils/conversationMemory.js +121 -82
- package/dist/utils/logger.d.ts +5 -0
- package/dist/utils/logger.js +50 -2
- package/dist/utils/messageBuilder.js +22 -42
- package/dist/utils/modelDetection.js +3 -3
- package/dist/utils/providerConfig.d.ts +167 -0
- package/dist/utils/providerConfig.js +619 -9
- package/dist/utils/providerRetry.d.ts +41 -0
- package/dist/utils/providerRetry.js +113 -0
- package/dist/utils/retryability.d.ts +14 -0
- package/dist/utils/retryability.js +22 -0
- package/dist/utils/sanitizers/svg.js +4 -5
- package/dist/utils/tokenEstimation.d.ts +11 -1
- package/dist/utils/tokenEstimation.js +19 -4
- package/dist/utils/videoAnalysisProcessor.js +7 -3
- package/dist/workflow/config.d.ts +26 -26
- package/package.json +2 -1
|
@@ -5,11 +5,14 @@ import { createAnalytics } from "../core/analytics.js";
|
|
|
5
5
|
import { BaseProvider } from "../core/baseProvider.js";
|
|
6
6
|
import { DEFAULT_MAX_STEPS } from "../core/constants.js";
|
|
7
7
|
import { AuthenticationError, ProviderError } from "../types/errors.js";
|
|
8
|
-
import { isAbortError } from "../utils/errorHandling.js";
|
|
8
|
+
import { isAbortError, withTimeout } from "../utils/errorHandling.js";
|
|
9
9
|
import { logger } from "../utils/logger.js";
|
|
10
|
+
import { calculateCost } from "../utils/pricing.js";
|
|
10
11
|
import { buildMultimodalMessagesArray } from "../utils/messageBuilder.js";
|
|
11
12
|
import { buildMultimodalOptions } from "../utils/multimodalOptionsBuilder.js";
|
|
12
13
|
import { convertZodToJsonSchema } from "../utils/schemaConversion.js";
|
|
14
|
+
import { trace, SpanKind, SpanStatusCode } from "@opentelemetry/api";
|
|
15
|
+
const bedrockTracer = trace.getTracer("neurolink.bedrock");
|
|
13
16
|
// Bedrock-specific types now imported from ../types/providerSpecific.js
|
|
14
17
|
export class AmazonBedrockProvider extends BaseProvider {
|
|
15
18
|
bedrockClient;
|
|
@@ -34,8 +37,6 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
34
37
|
// 4. Instance metadata
|
|
35
38
|
});
|
|
36
39
|
logger.debug(`[AmazonBedrockProvider] Successfully created BedrockRuntimeClient with model: ${this.modelName}, region: ${this.region}`);
|
|
37
|
-
// Immediate health check to catch credential issues early
|
|
38
|
-
this.performInitialHealthCheck();
|
|
39
40
|
}
|
|
40
41
|
catch (error) {
|
|
41
42
|
logger.error(`[AmazonBedrockProvider] CRITICAL: Failed to initialize BedrockRuntimeClient:`, error);
|
|
@@ -184,109 +185,145 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
184
185
|
}
|
|
185
186
|
async callBedrock(options) {
|
|
186
187
|
const startTime = Date.now();
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
logger.info(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
188
|
+
return bedrockTracer.startActiveSpan("bedrock.generate", {
|
|
189
|
+
kind: SpanKind.CLIENT,
|
|
190
|
+
attributes: {
|
|
191
|
+
"gen_ai.system": "aws.bedrock",
|
|
192
|
+
"gen_ai.request.model": this.modelName || this.getDefaultModel(),
|
|
193
|
+
"gen_ai.operation.name": "chat",
|
|
194
|
+
},
|
|
195
|
+
}, async (generateSpan) => {
|
|
196
|
+
logger.info(`🚀 [AmazonBedrockProvider] Starting Bedrock API call at ${new Date().toISOString()}`);
|
|
197
|
+
try {
|
|
198
|
+
// Pre-call validation and logging
|
|
199
|
+
let region = "unknown";
|
|
200
|
+
try {
|
|
201
|
+
region =
|
|
202
|
+
typeof this.bedrockClient.config.region === "function"
|
|
203
|
+
? await this.bedrockClient.config.region()
|
|
204
|
+
: (this.bedrockClient.config.region ?? "unknown");
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// Region lookup failed — not critical, only used for logging
|
|
208
|
+
}
|
|
209
|
+
logger.info(`🔧 [AmazonBedrockProvider] Client region: ${region}`);
|
|
210
|
+
logger.info(`🔧 [AmazonBedrockProvider] Model: ${this.modelName || this.getDefaultModel()}`);
|
|
211
|
+
logger.info(`🔧 [AmazonBedrockProvider] Conversation history length: ${this.conversationHistory.length}`);
|
|
212
|
+
// Get all available tools
|
|
213
|
+
const aiTools = await this.getAllTools();
|
|
214
|
+
const allTools = this.convertAISDKToolsToToolDefinitions(aiTools);
|
|
215
|
+
const toolConfig = this.formatToolsForBedrock(allTools);
|
|
216
|
+
const commandInput = {
|
|
217
|
+
modelId: this.modelName || this.getDefaultModel(),
|
|
218
|
+
messages: this.convertToAWSMessages(this.conversationHistory),
|
|
219
|
+
system: [
|
|
220
|
+
{
|
|
221
|
+
text: options.systemPrompt ||
|
|
222
|
+
"You are a helpful assistant with access to external tools. Use tools when necessary to provide accurate information.",
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
inferenceConfig: {
|
|
226
|
+
maxTokens: options.maxTokens, // No default limit - unlimited unless specified
|
|
227
|
+
temperature: options.temperature || 0.7,
|
|
207
228
|
},
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
logger.info(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
:
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
: null;
|
|
267
|
-
logger.debug("[Observability] Bedrock API request failed", {
|
|
268
|
-
model: this.modelName || this.getDefaultModel(),
|
|
269
|
-
durationMs: errorDuration,
|
|
270
|
-
error: error instanceof Error ? error.message : String(error),
|
|
271
|
-
errorName: error instanceof Error ? error.name : undefined,
|
|
272
|
-
httpStatus: metadata?.httpStatusCode,
|
|
273
|
-
awsRequestId: metadata?.requestId,
|
|
274
|
-
awsErrorCode: awsError?.Code,
|
|
275
|
-
});
|
|
276
|
-
logger.error(`[AmazonBedrockProvider] Bedrock API call failed after ${errorDuration}ms`);
|
|
277
|
-
if (error instanceof Error) {
|
|
278
|
-
logger.error(`[AmazonBedrockProvider] Error: ${error.name} - ${error.message}`);
|
|
229
|
+
};
|
|
230
|
+
if (toolConfig) {
|
|
231
|
+
commandInput.toolConfig = toolConfig;
|
|
232
|
+
logger.info(`🛠️ [AmazonBedrockProvider] Tools configured: ${toolConfig.tools?.length || 0}`);
|
|
233
|
+
}
|
|
234
|
+
// Log command details for debugging
|
|
235
|
+
logger.info(`📋 [AmazonBedrockProvider] Command input summary:`);
|
|
236
|
+
logger.info(` - Model ID: ${commandInput.modelId}`);
|
|
237
|
+
logger.info(` - Messages count: ${commandInput.messages?.length || 0}`);
|
|
238
|
+
logger.info(` - System prompts: ${commandInput.system?.length || 0}`);
|
|
239
|
+
logger.info(` - Max tokens: ${commandInput.inferenceConfig?.maxTokens}`);
|
|
240
|
+
logger.info(` - Temperature: ${commandInput.inferenceConfig?.temperature}`);
|
|
241
|
+
logger.debug(`[AmazonBedrockProvider] Calling Bedrock with ${this.conversationHistory.length} messages and ${toolConfig?.tools?.length || 0} tools`);
|
|
242
|
+
// Create command and attempt API call
|
|
243
|
+
const command = new ConverseCommand(commandInput);
|
|
244
|
+
logger.debug("[Observability] Bedrock API request", {
|
|
245
|
+
model: commandInput.modelId,
|
|
246
|
+
region: region,
|
|
247
|
+
messageCount: commandInput.messages?.length || 0,
|
|
248
|
+
toolCount: commandInput.toolConfig?.tools?.length || 0,
|
|
249
|
+
maxTokens: commandInput.inferenceConfig?.maxTokens,
|
|
250
|
+
});
|
|
251
|
+
const apiCallStartTime = Date.now();
|
|
252
|
+
const response = await withTimeout(this.bedrockClient.send(command), 120_000, new Error("Bedrock API call timed out"));
|
|
253
|
+
const apiCallDuration = Date.now() - apiCallStartTime;
|
|
254
|
+
logger.debug("[Observability] Bedrock API response", {
|
|
255
|
+
model: commandInput.modelId,
|
|
256
|
+
durationMs: apiCallDuration,
|
|
257
|
+
hasContent: !!response.output?.message?.content?.length,
|
|
258
|
+
stopReason: response.stopReason,
|
|
259
|
+
usage: response.usage
|
|
260
|
+
? {
|
|
261
|
+
inputTokens: response.usage.inputTokens,
|
|
262
|
+
outputTokens: response.usage.outputTokens,
|
|
263
|
+
totalTokens: (response.usage.inputTokens || 0) +
|
|
264
|
+
(response.usage.outputTokens || 0),
|
|
265
|
+
}
|
|
266
|
+
: undefined,
|
|
267
|
+
});
|
|
268
|
+
logger.info(`[AmazonBedrockProvider] Bedrock API call successful`);
|
|
269
|
+
logger.info(`[AmazonBedrockProvider] API call duration: ${apiCallDuration}ms`);
|
|
270
|
+
const totalDuration = Date.now() - startTime;
|
|
271
|
+
logger.info(`[AmazonBedrockProvider] Total callBedrock duration: ${totalDuration}ms`);
|
|
272
|
+
generateSpan.setAttribute("gen_ai.response.stop_reason", response.stopReason ?? "");
|
|
273
|
+
generateSpan.setAttribute("gen_ai.usage.input_tokens", response.usage?.inputTokens ?? 0);
|
|
274
|
+
generateSpan.setAttribute("gen_ai.usage.output_tokens", response.usage?.outputTokens ?? 0);
|
|
275
|
+
const cost = calculateCost(this.providerName, this.modelName, {
|
|
276
|
+
input: response.usage?.inputTokens ?? 0,
|
|
277
|
+
output: response.usage?.outputTokens ?? 0,
|
|
278
|
+
total: (response.usage?.inputTokens ?? 0) +
|
|
279
|
+
(response.usage?.outputTokens ?? 0),
|
|
280
|
+
});
|
|
281
|
+
if (cost && cost > 0) {
|
|
282
|
+
generateSpan.setAttribute("neurolink.cost", cost);
|
|
283
|
+
}
|
|
284
|
+
generateSpan.setStatus({ code: SpanStatusCode.OK });
|
|
285
|
+
generateSpan.end();
|
|
286
|
+
return response;
|
|
279
287
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
288
|
+
catch (error) {
|
|
289
|
+
const errorDuration = Date.now() - startTime;
|
|
290
|
+
// Extract AWS metadata for structured logging
|
|
291
|
+
const awsError = error && typeof error === "object"
|
|
292
|
+
? error
|
|
293
|
+
: null;
|
|
294
|
+
const metadata = awsError?.$metadata && typeof awsError.$metadata === "object"
|
|
295
|
+
? awsError.$metadata
|
|
296
|
+
: null;
|
|
297
|
+
logger.debug("[Observability] Bedrock API request failed", {
|
|
298
|
+
model: this.modelName || this.getDefaultModel(),
|
|
299
|
+
durationMs: errorDuration,
|
|
300
|
+
error: error instanceof Error ? error.message : String(error),
|
|
301
|
+
errorName: error instanceof Error ? error.name : undefined,
|
|
302
|
+
httpStatus: metadata?.httpStatusCode,
|
|
303
|
+
awsRequestId: metadata?.requestId,
|
|
304
|
+
awsErrorCode: awsError?.Code,
|
|
305
|
+
});
|
|
306
|
+
logger.error(`[AmazonBedrockProvider] Bedrock API call failed after ${errorDuration}ms`);
|
|
307
|
+
if (error instanceof Error) {
|
|
308
|
+
logger.error(`[AmazonBedrockProvider] Error: ${error.name} - ${error.message}`);
|
|
309
|
+
}
|
|
310
|
+
if (metadata) {
|
|
311
|
+
logger.error(`[AmazonBedrockProvider] AWS SDK metadata`, {
|
|
312
|
+
httpStatus: metadata.httpStatusCode,
|
|
313
|
+
requestId: metadata.requestId,
|
|
314
|
+
attempts: metadata.attempts,
|
|
315
|
+
totalRetryDelay: metadata.totalRetryDelay,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
generateSpan.setStatus({
|
|
319
|
+
code: SpanStatusCode.ERROR,
|
|
320
|
+
message: error instanceof Error ? error.message : String(error),
|
|
286
321
|
});
|
|
322
|
+
generateSpan.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
323
|
+
generateSpan.end();
|
|
324
|
+
throw error;
|
|
287
325
|
}
|
|
288
|
-
|
|
289
|
-
}
|
|
326
|
+
}); // end bedrockTracer.startActiveSpan('bedrock.generate')
|
|
290
327
|
}
|
|
291
328
|
async handleBedrockResponse(response) {
|
|
292
329
|
logger.debug(`[AmazonBedrockProvider] Received response with stopReason: ${response.stopReason}`);
|
|
@@ -383,13 +420,13 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
383
420
|
return { shouldContinue: true };
|
|
384
421
|
}
|
|
385
422
|
else if (stopReason === "max_tokens") {
|
|
386
|
-
//
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
return { shouldContinue:
|
|
423
|
+
// Max tokens reached — return what we have rather than continuing,
|
|
424
|
+
// since the model hit the configured limit.
|
|
425
|
+
const textContent = bedrockAssistantMessage.content
|
|
426
|
+
.filter((item) => item.text)
|
|
427
|
+
.map((item) => item.text)
|
|
428
|
+
.join(" ");
|
|
429
|
+
return { shouldContinue: false, text: textContent };
|
|
393
430
|
}
|
|
394
431
|
else {
|
|
395
432
|
logger.warn(`[AmazonBedrockProvider] Unrecognized stop reason "${stopReason}", ending conversation.`);
|
|
@@ -438,84 +475,106 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
438
475
|
}));
|
|
439
476
|
}
|
|
440
477
|
async executeSingleTool(toolName, args, _toolUseId) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const toolInput = args || {};
|
|
458
|
-
// Add default parameters for common tools that Claude might call without required params
|
|
459
|
-
if (toolName === "list_directory" && !toolInput.path) {
|
|
460
|
-
toolInput.path = ".";
|
|
461
|
-
logger.debug(`[AmazonBedrockProvider] Added default path '.' for list_directory tool`);
|
|
462
|
-
}
|
|
463
|
-
logger.debug(`[AmazonBedrockProvider] Tool input parameters:`, toolInput);
|
|
464
|
-
// Convert Record<string, unknown> to ToolArgs by filtering out non-JsonValue types
|
|
465
|
-
const toolArgs = {};
|
|
466
|
-
for (const [key, value] of Object.entries(toolInput)) {
|
|
467
|
-
// Only include values that are JsonValue compatible
|
|
468
|
-
if (value === null ||
|
|
469
|
-
typeof value === "string" ||
|
|
470
|
-
typeof value === "number" ||
|
|
471
|
-
typeof value === "boolean" ||
|
|
472
|
-
(typeof value === "object" && value !== null)) {
|
|
473
|
-
toolArgs[key] = value;
|
|
478
|
+
return bedrockTracer.startActiveSpan("bedrock.tool.execute", {
|
|
479
|
+
kind: SpanKind.CLIENT,
|
|
480
|
+
attributes: {
|
|
481
|
+
"gen_ai.tool.name": toolName,
|
|
482
|
+
"gen_ai.system": "aws.bedrock",
|
|
483
|
+
},
|
|
484
|
+
}, async (span) => {
|
|
485
|
+
try {
|
|
486
|
+
logger.debug(`[AmazonBedrockProvider] Executing single tool: ${toolName}`, {
|
|
487
|
+
args,
|
|
488
|
+
});
|
|
489
|
+
// Use BaseProvider's tool execution mechanism
|
|
490
|
+
const aiTools = await this.getAllTools();
|
|
491
|
+
const tools = this.convertAISDKToolsToToolDefinitions(aiTools);
|
|
492
|
+
if (!tools[toolName]) {
|
|
493
|
+
throw new Error(`Tool not found: ${toolName}`);
|
|
474
494
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
if (
|
|
484
|
-
|
|
485
|
-
|
|
495
|
+
const tool = tools[toolName];
|
|
496
|
+
if (!tool || !tool.execute) {
|
|
497
|
+
throw new Error(`Tool ${toolName} does not have execute method`);
|
|
498
|
+
}
|
|
499
|
+
// Apply robust parameter handling like Bedrock-MCP-Connector
|
|
500
|
+
// Bedrock toolUse.input already contains the correct parameter structure
|
|
501
|
+
const toolInput = args || {};
|
|
502
|
+
// Add default parameters for common tools that Claude might call without required params
|
|
503
|
+
if (toolName === "list_directory" && !toolInput.path) {
|
|
504
|
+
toolInput.path = ".";
|
|
505
|
+
logger.debug(`[AmazonBedrockProvider] Added default path '.' for list_directory tool`);
|
|
506
|
+
}
|
|
507
|
+
logger.debug(`[AmazonBedrockProvider] Tool input parameters:`, toolInput);
|
|
508
|
+
// Convert Record<string, unknown> to ToolArgs by filtering out non-JsonValue types
|
|
509
|
+
const toolArgs = {};
|
|
510
|
+
for (const [key, value] of Object.entries(toolInput)) {
|
|
511
|
+
// Only include values that are JsonValue compatible
|
|
512
|
+
if (value === null ||
|
|
513
|
+
typeof value === "string" ||
|
|
514
|
+
typeof value === "number" ||
|
|
515
|
+
typeof value === "boolean" ||
|
|
516
|
+
(typeof value === "object" && value !== null)) {
|
|
517
|
+
toolArgs[key] = value;
|
|
486
518
|
}
|
|
487
|
-
|
|
488
|
-
|
|
519
|
+
}
|
|
520
|
+
const result = await tool.execute(toolArgs);
|
|
521
|
+
logger.debug(`[AmazonBedrockProvider] Tool execution result:`, {
|
|
522
|
+
toolName,
|
|
523
|
+
result,
|
|
524
|
+
});
|
|
525
|
+
// Handle ToolResult type
|
|
526
|
+
let finalResult;
|
|
527
|
+
if (result && typeof result === "object" && "success" in result) {
|
|
528
|
+
if (result.success && result.data !== undefined) {
|
|
529
|
+
if (typeof result.data === "string") {
|
|
530
|
+
finalResult = result.data;
|
|
531
|
+
}
|
|
532
|
+
else if (typeof result.data === "object") {
|
|
533
|
+
finalResult = JSON.stringify(result.data, null, 2);
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
finalResult = String(result.data);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
else if (result.error) {
|
|
540
|
+
const errorMessage = typeof result.error === "string"
|
|
541
|
+
? result.error
|
|
542
|
+
: result.error.message || "Tool execution failed";
|
|
543
|
+
throw new Error(errorMessage);
|
|
489
544
|
}
|
|
490
545
|
else {
|
|
491
|
-
|
|
546
|
+
finalResult = "";
|
|
492
547
|
}
|
|
493
548
|
}
|
|
494
|
-
else if (result
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
: result.error.message || "Tool execution failed";
|
|
498
|
-
throw new Error(errorMessage);
|
|
549
|
+
else if (typeof result === "string") {
|
|
550
|
+
// Fallback for non-ToolResult return types
|
|
551
|
+
finalResult = result;
|
|
499
552
|
}
|
|
553
|
+
else if (typeof result === "object") {
|
|
554
|
+
finalResult = JSON.stringify(result, null, 2);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
finalResult = String(result);
|
|
558
|
+
}
|
|
559
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
560
|
+
return finalResult;
|
|
500
561
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
562
|
+
catch (error) {
|
|
563
|
+
logger.error(`[AmazonBedrockProvider] Tool execution error:`, {
|
|
564
|
+
toolName,
|
|
565
|
+
error,
|
|
566
|
+
});
|
|
567
|
+
span.setStatus({
|
|
568
|
+
code: SpanStatusCode.ERROR,
|
|
569
|
+
message: error.message,
|
|
570
|
+
});
|
|
571
|
+
span.recordException(error);
|
|
572
|
+
throw error;
|
|
507
573
|
}
|
|
508
|
-
|
|
509
|
-
|
|
574
|
+
finally {
|
|
575
|
+
span.end();
|
|
510
576
|
}
|
|
511
|
-
}
|
|
512
|
-
catch (error) {
|
|
513
|
-
logger.error(`[AmazonBedrockProvider] Tool execution error:`, {
|
|
514
|
-
toolName,
|
|
515
|
-
error,
|
|
516
|
-
});
|
|
517
|
-
throw error;
|
|
518
|
-
}
|
|
577
|
+
});
|
|
519
578
|
}
|
|
520
579
|
convertAISDKToolsToToolDefinitions(aiTools) {
|
|
521
580
|
const result = {};
|
|
@@ -679,124 +738,152 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
679
738
|
async executeStream(options) {
|
|
680
739
|
logger.debug("🟢 [TRACE] executeStream ENTRY - starting streaming attempt");
|
|
681
740
|
logger.info("🚀 [AmazonBedrockProvider] Attempting real streaming with ConverseStreamCommand");
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
this.conversationHistory.push(userMessage);
|
|
719
|
-
}
|
|
720
|
-
logger.debug(`[AmazonBedrockProvider] Starting streaming conversation with ${this.conversationHistory.length} message(s)`);
|
|
721
|
-
// Call the actual streaming implementation that already exists
|
|
722
|
-
logger.debug("🟢 [TRACE] executeStream - calling streamingConversationLoop NOW");
|
|
723
|
-
const result = await this.streamingConversationLoop(options);
|
|
724
|
-
logger.debug("🟢 [TRACE] executeStream - streamingConversationLoop SUCCESS, returning result");
|
|
725
|
-
return result;
|
|
726
|
-
}
|
|
727
|
-
catch (error) {
|
|
728
|
-
logger.debug("🔴 [TRACE] executeStream CATCH - error caught from streamingConversationLoop");
|
|
729
|
-
const errorObj = error;
|
|
730
|
-
// Check if error is related to streaming permissions
|
|
731
|
-
const isPermissionError = errorObj?.name ===
|
|
732
|
-
"AccessDeniedException" ||
|
|
733
|
-
errorObj?.name ===
|
|
734
|
-
"UnauthorizedOperation" ||
|
|
735
|
-
errorObj?.message?.includes("bedrock:InvokeModelWithResponseStream") ||
|
|
736
|
-
errorObj?.message?.includes("streaming") ||
|
|
737
|
-
errorObj?.message?.includes("ConverseStream");
|
|
738
|
-
logger.debug("🔴 [TRACE] executeStream CATCH - checking if permission error");
|
|
739
|
-
logger.debug(`🔴 [TRACE] executeStream CATCH - isPermissionError=${isPermissionError}`);
|
|
740
|
-
if (isPermissionError) {
|
|
741
|
-
logger.debug("🟡 [TRACE] executeStream CATCH - PERMISSION ERROR DETECTED, starting fallback");
|
|
742
|
-
logger.warn(`[AmazonBedrockProvider] Streaming permissions not available, falling back to generate method: ${errorObj.message}`);
|
|
743
|
-
// Fallback to generate method and convert to streaming format
|
|
744
|
-
const generateResult = await this.generate({
|
|
745
|
-
prompt: options.input.text,
|
|
746
|
-
input: options.input,
|
|
747
|
-
maxTokens: options.maxTokens,
|
|
748
|
-
temperature: options.temperature,
|
|
749
|
-
systemPrompt: options.systemPrompt,
|
|
750
|
-
});
|
|
751
|
-
if (!generateResult) {
|
|
752
|
-
throw new Error("Generate method returned null result");
|
|
741
|
+
return bedrockTracer.startActiveSpan("bedrock.stream", {
|
|
742
|
+
kind: SpanKind.CLIENT,
|
|
743
|
+
attributes: {
|
|
744
|
+
"gen_ai.system": "aws.bedrock",
|
|
745
|
+
"gen_ai.request.model": this.modelName || this.getDefaultModel(),
|
|
746
|
+
"gen_ai.operation.name": "stream",
|
|
747
|
+
},
|
|
748
|
+
}, async (streamSpan) => {
|
|
749
|
+
try {
|
|
750
|
+
logger.debug("🟢 [TRACE] executeStream TRY block - about to call streamingConversationLoop");
|
|
751
|
+
// Clear conversation history for new streaming session
|
|
752
|
+
this.conversationHistory = [];
|
|
753
|
+
// Check for multimodal input (images, PDFs, CSVs, files)
|
|
754
|
+
const hasMultimodalInput = !!(options.input?.images?.length ||
|
|
755
|
+
options.input?.content?.length ||
|
|
756
|
+
options.input?.files?.length ||
|
|
757
|
+
options.input?.csvFiles?.length ||
|
|
758
|
+
options.input?.pdfFiles?.length);
|
|
759
|
+
if (hasMultimodalInput) {
|
|
760
|
+
logger.debug(`[AmazonBedrockProvider] Detected multimodal input, using multimodal message builder`, {
|
|
761
|
+
hasImages: !!options.input?.images?.length,
|
|
762
|
+
imageCount: options.input?.images?.length || 0,
|
|
763
|
+
hasContent: !!options.input?.content?.length,
|
|
764
|
+
contentCount: options.input?.content?.length || 0,
|
|
765
|
+
hasFiles: !!options.input?.files?.length,
|
|
766
|
+
fileCount: options.input?.files?.length || 0,
|
|
767
|
+
hasCSVFiles: !!options.input?.csvFiles?.length,
|
|
768
|
+
csvFileCount: options.input?.csvFiles?.length || 0,
|
|
769
|
+
hasPDFFiles: !!options.input?.pdfFiles?.length,
|
|
770
|
+
pdfFileCount: options.input?.pdfFiles?.length || 0,
|
|
771
|
+
});
|
|
772
|
+
const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
|
|
773
|
+
const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
|
|
774
|
+
// Convert to Bedrock format
|
|
775
|
+
this.conversationHistory =
|
|
776
|
+
this.convertToBedrockMessages(multimodalMessages);
|
|
753
777
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
778
|
+
else {
|
|
779
|
+
logger.debug(`[AmazonBedrockProvider] Text-only input, using simple message builder`);
|
|
780
|
+
// Add user message to conversation - simple text-only case
|
|
781
|
+
const userMessage = {
|
|
782
|
+
role: "user",
|
|
783
|
+
content: [{ text: options.input.text }],
|
|
784
|
+
};
|
|
785
|
+
this.conversationHistory.push(userMessage);
|
|
786
|
+
}
|
|
787
|
+
logger.debug(`[AmazonBedrockProvider] Starting streaming conversation with ${this.conversationHistory.length} message(s)`);
|
|
788
|
+
// Call the actual streaming implementation that already exists
|
|
789
|
+
logger.debug("🟢 [TRACE] executeStream - calling streamingConversationLoop NOW");
|
|
790
|
+
const result = await this.streamingConversationLoop(options, streamSpan);
|
|
791
|
+
logger.debug("🟢 [TRACE] executeStream - streamingConversationLoop SUCCESS, returning result");
|
|
792
|
+
streamSpan.setStatus({ code: SpanStatusCode.OK });
|
|
793
|
+
streamSpan.end();
|
|
794
|
+
return result;
|
|
795
|
+
}
|
|
796
|
+
catch (error) {
|
|
797
|
+
logger.debug("🔴 [TRACE] executeStream CATCH - error caught from streamingConversationLoop");
|
|
798
|
+
const errorObj = error;
|
|
799
|
+
// Check if error is related to streaming permissions
|
|
800
|
+
const isPermissionError = errorObj?.name ===
|
|
801
|
+
"AccessDeniedException" ||
|
|
802
|
+
errorObj?.name ===
|
|
803
|
+
"UnauthorizedOperation" ||
|
|
804
|
+
errorObj?.message?.includes("bedrock:InvokeModelWithResponseStream") ||
|
|
805
|
+
errorObj?.message?.includes("streaming") ||
|
|
806
|
+
errorObj?.message?.includes("ConverseStream");
|
|
807
|
+
logger.debug("🔴 [TRACE] executeStream CATCH - checking if permission error");
|
|
808
|
+
logger.debug(`🔴 [TRACE] executeStream CATCH - isPermissionError=${isPermissionError}`);
|
|
809
|
+
if (isPermissionError) {
|
|
810
|
+
logger.debug("🟡 [TRACE] executeStream CATCH - PERMISSION ERROR DETECTED, starting fallback");
|
|
811
|
+
logger.warn(`[AmazonBedrockProvider] Streaming permissions not available, falling back to generate method: ${errorObj.message}`);
|
|
812
|
+
streamSpan.addEvent("stream.fallback_to_generate", {
|
|
813
|
+
reason: errorObj.message,
|
|
814
|
+
});
|
|
815
|
+
// Fallback to generate method and convert to streaming format
|
|
816
|
+
const generateResult = await this.generate({
|
|
817
|
+
prompt: options.input.text,
|
|
818
|
+
input: options.input,
|
|
819
|
+
maxTokens: options.maxTokens,
|
|
820
|
+
temperature: options.temperature,
|
|
821
|
+
systemPrompt: options.systemPrompt,
|
|
822
|
+
});
|
|
823
|
+
if (!generateResult) {
|
|
824
|
+
streamSpan.setStatus({
|
|
825
|
+
code: SpanStatusCode.ERROR,
|
|
826
|
+
message: "Generate method returned null result",
|
|
762
827
|
});
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
828
|
+
streamSpan.end();
|
|
829
|
+
throw new Error("Generate method returned null result");
|
|
830
|
+
}
|
|
831
|
+
streamSpan.setAttribute("gen_ai.response.stop_reason", "fallback_end_turn");
|
|
832
|
+
streamSpan.setStatus({ code: SpanStatusCode.OK });
|
|
833
|
+
streamSpan.end();
|
|
834
|
+
// Convert generate result to streaming format
|
|
835
|
+
const stream = new ReadableStream({
|
|
836
|
+
start(controller) {
|
|
837
|
+
// Split the response into chunks for pseudo-streaming
|
|
838
|
+
const responseText = generateResult.content || "";
|
|
839
|
+
const chunks = responseText.split(" ");
|
|
840
|
+
chunks.forEach((word, _index) => {
|
|
841
|
+
controller.enqueue({ content: word + " " });
|
|
842
|
+
});
|
|
843
|
+
controller.enqueue({ content: "" });
|
|
844
|
+
controller.close();
|
|
845
|
+
},
|
|
846
|
+
});
|
|
847
|
+
// Convert ReadableStream to AsyncIterable like streamingConversationLoop does
|
|
848
|
+
const asyncIterable = {
|
|
849
|
+
async *[Symbol.asyncIterator]() {
|
|
850
|
+
const reader = stream.getReader();
|
|
851
|
+
try {
|
|
852
|
+
while (true) {
|
|
853
|
+
const { done, value } = await reader.read();
|
|
854
|
+
if (done) {
|
|
855
|
+
break;
|
|
856
|
+
}
|
|
857
|
+
yield value;
|
|
776
858
|
}
|
|
777
|
-
yield value;
|
|
778
859
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
}
|
|
793
|
-
}
|
|
860
|
+
finally {
|
|
861
|
+
reader.releaseLock();
|
|
862
|
+
}
|
|
863
|
+
},
|
|
864
|
+
};
|
|
865
|
+
return {
|
|
866
|
+
stream: asyncIterable,
|
|
867
|
+
usage: { total: 0, input: 0, output: 0 },
|
|
868
|
+
model: this.modelName || this.getDefaultModel(),
|
|
869
|
+
provider: this.getProviderName(),
|
|
870
|
+
metadata: {
|
|
871
|
+
fallback: true,
|
|
872
|
+
},
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
// Re-throw non-permission errors
|
|
876
|
+
streamSpan.setStatus({
|
|
877
|
+
code: SpanStatusCode.ERROR,
|
|
878
|
+
message: errorObj instanceof Error ? errorObj.message : String(errorObj),
|
|
879
|
+
});
|
|
880
|
+
streamSpan.recordException(errorObj instanceof Error ? errorObj : new Error(String(errorObj)));
|
|
881
|
+
streamSpan.end();
|
|
882
|
+
throw error;
|
|
794
883
|
}
|
|
795
|
-
|
|
796
|
-
throw error;
|
|
797
|
-
}
|
|
884
|
+
});
|
|
798
885
|
}
|
|
799
|
-
async streamingConversationLoop(options) {
|
|
886
|
+
async streamingConversationLoop(options, streamSpan) {
|
|
800
887
|
logger.debug("🟦 [TRACE] streamingConversationLoop ENTRY");
|
|
801
888
|
const startTime = Date.now();
|
|
802
889
|
const maxIterations = options.maxSteps || DEFAULT_MAX_STEPS;
|
|
@@ -812,8 +899,12 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
812
899
|
messageCount: commandInput.messages?.length || 0,
|
|
813
900
|
toolCount: commandInput.toolConfig?.tools?.length || 0,
|
|
814
901
|
});
|
|
902
|
+
streamSpan.addEvent("stream.api_call", {
|
|
903
|
+
"bedrock.message_count": commandInput.messages?.length || 0,
|
|
904
|
+
"bedrock.tool_count": commandInput.toolConfig?.tools?.length || 0,
|
|
905
|
+
});
|
|
815
906
|
const streamStartTime = Date.now();
|
|
816
|
-
const response = await this.bedrockClient.send(command);
|
|
907
|
+
const response = await withTimeout(this.bedrockClient.send(command), 120_000, new Error("Bedrock streaming API call timed out"));
|
|
817
908
|
logger.debug("[Observability] Bedrock streaming API connection established", {
|
|
818
909
|
model: commandInput.modelId,
|
|
819
910
|
durationMs: Date.now() - streamStartTime,
|
|
@@ -824,19 +915,102 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
824
915
|
start: async (controller) => {
|
|
825
916
|
logger.debug("🟦 [TRACE] streamingConversationLoop - ReadableStream start() called");
|
|
826
917
|
try {
|
|
827
|
-
// Process the first response we already have
|
|
918
|
+
// Process the first response we already have, tracking all event types
|
|
919
|
+
let firstStopReason = "";
|
|
828
920
|
if (response.stream) {
|
|
921
|
+
const firstMessageContent = [];
|
|
922
|
+
let firstText = "";
|
|
829
923
|
for await (const chunk of response.stream) {
|
|
924
|
+
if (chunk.contentBlockStart) {
|
|
925
|
+
firstMessageContent.push({});
|
|
926
|
+
}
|
|
830
927
|
if (chunk.contentBlockDelta?.delta?.text) {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
});
|
|
928
|
+
const textDelta = chunk.contentBlockDelta.delta.text;
|
|
929
|
+
firstText += textDelta;
|
|
930
|
+
controller.enqueue({ content: textDelta });
|
|
931
|
+
}
|
|
932
|
+
if (chunk.contentBlockStart?.start?.toolUse) {
|
|
933
|
+
const currentBlock = firstMessageContent[firstMessageContent.length - 1];
|
|
934
|
+
currentBlock.toolUse = {
|
|
935
|
+
name: chunk.contentBlockStart.start.toolUse.name || "",
|
|
936
|
+
input: {},
|
|
937
|
+
toolUseId: chunk.contentBlockStart.start.toolUse.toolUseId ||
|
|
938
|
+
`tool_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
if (chunk.contentBlockDelta?.delta?.toolUse) {
|
|
942
|
+
const currentBlock = firstMessageContent[firstMessageContent.length - 1];
|
|
943
|
+
if (!currentBlock.toolUse) {
|
|
944
|
+
currentBlock.toolUse = {
|
|
945
|
+
name: "",
|
|
946
|
+
input: {},
|
|
947
|
+
toolUseId: `tool_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
const deltaInput = chunk.contentBlockDelta.delta.toolUse.input;
|
|
951
|
+
if (!deltaInput) {
|
|
952
|
+
// no input delta
|
|
953
|
+
}
|
|
954
|
+
else if (typeof deltaInput === "string") {
|
|
955
|
+
currentBlock._inputBuffer =
|
|
956
|
+
(currentBlock._inputBuffer || "") + deltaInput;
|
|
957
|
+
}
|
|
958
|
+
else if (typeof deltaInput === "object" &&
|
|
959
|
+
!Array.isArray(deltaInput)) {
|
|
960
|
+
const currentInput = currentBlock.toolUse.input || {};
|
|
961
|
+
currentBlock.toolUse.input = {
|
|
962
|
+
...currentInput,
|
|
963
|
+
...deltaInput,
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
if (chunk.contentBlockStop) {
|
|
968
|
+
const currentBlock = firstMessageContent[firstMessageContent.length - 1];
|
|
969
|
+
if (currentBlock?.toolUse && currentBlock._inputBuffer) {
|
|
970
|
+
try {
|
|
971
|
+
currentBlock.toolUse.input = JSON.parse(currentBlock._inputBuffer);
|
|
972
|
+
}
|
|
973
|
+
catch {
|
|
974
|
+
currentBlock.toolUse.input = {};
|
|
975
|
+
}
|
|
976
|
+
delete currentBlock._inputBuffer;
|
|
977
|
+
}
|
|
978
|
+
if (firstText && currentBlock && !currentBlock.toolUse) {
|
|
979
|
+
currentBlock.text = firstText;
|
|
980
|
+
}
|
|
981
|
+
firstText = "";
|
|
834
982
|
}
|
|
835
983
|
if (chunk.messageStop) {
|
|
836
|
-
|
|
837
|
-
|
|
984
|
+
firstStopReason = chunk.messageStop.stopReason || "end_turn";
|
|
985
|
+
break;
|
|
838
986
|
}
|
|
839
987
|
}
|
|
988
|
+
// Add first assistant message to conversation history
|
|
989
|
+
const firstAssistantMessage = {
|
|
990
|
+
role: "assistant",
|
|
991
|
+
content: firstMessageContent,
|
|
992
|
+
};
|
|
993
|
+
this.conversationHistory.push(firstAssistantMessage);
|
|
994
|
+
streamSpan.addEvent("stream.turn_complete", {
|
|
995
|
+
iteration: 0,
|
|
996
|
+
stop_reason: firstStopReason,
|
|
997
|
+
});
|
|
998
|
+
if (firstStopReason === "tool_use") {
|
|
999
|
+
const toolNames = firstMessageContent
|
|
1000
|
+
.filter((b) => b.toolUse?.name)
|
|
1001
|
+
.map((b) => b.toolUse.name)
|
|
1002
|
+
.join(", ");
|
|
1003
|
+
streamSpan.addEvent("stream.tool_use", {
|
|
1004
|
+
iteration: 0,
|
|
1005
|
+
tool_names: toolNames,
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
// Handle the stop reason from the first response
|
|
1009
|
+
const shouldContinue = await this.handleStreamStopReason(firstStopReason, firstAssistantMessage, controller, options);
|
|
1010
|
+
if (!shouldContinue) {
|
|
1011
|
+
streamSpan.setAttribute("gen_ai.response.stop_reason", firstStopReason);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
840
1014
|
}
|
|
841
1015
|
// Continue with normal iterations if needed
|
|
842
1016
|
while (iteration < maxIterations) {
|
|
@@ -844,12 +1018,28 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
844
1018
|
logger.debug(`[AmazonBedrockProvider] Streaming iteration ${iteration}`);
|
|
845
1019
|
const commandInput = await this.prepareStreamCommand(options);
|
|
846
1020
|
const { stopReason, assistantMessage } = await this.processStreamResponse(commandInput, controller);
|
|
1021
|
+
streamSpan.addEvent("stream.turn_complete", {
|
|
1022
|
+
iteration,
|
|
1023
|
+
stop_reason: stopReason,
|
|
1024
|
+
});
|
|
1025
|
+
if (stopReason === "tool_use") {
|
|
1026
|
+
const toolNames = assistantMessage.content
|
|
1027
|
+
.filter((b) => b.toolUse?.name)
|
|
1028
|
+
.map((b) => b.toolUse.name)
|
|
1029
|
+
.join(", ");
|
|
1030
|
+
streamSpan.addEvent("stream.tool_use", {
|
|
1031
|
+
iteration,
|
|
1032
|
+
tool_names: toolNames,
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
847
1035
|
const shouldContinue = await this.handleStreamStopReason(stopReason, assistantMessage, controller, options);
|
|
848
1036
|
if (!shouldContinue) {
|
|
1037
|
+
streamSpan.setAttribute("gen_ai.response.stop_reason", stopReason);
|
|
849
1038
|
break;
|
|
850
1039
|
}
|
|
851
1040
|
}
|
|
852
1041
|
if (iteration >= maxIterations) {
|
|
1042
|
+
streamSpan.setAttribute("gen_ai.response.stop_reason", "max_iterations");
|
|
853
1043
|
controller.error(new Error("Streaming conversation exceeded maximum iterations"));
|
|
854
1044
|
}
|
|
855
1045
|
}
|
|
@@ -903,10 +1093,12 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
903
1093
|
}
|
|
904
1094
|
async prepareStreamCommand(options) {
|
|
905
1095
|
// CRITICAL DEBUG: Log conversation history before conversion
|
|
906
|
-
logger.
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1096
|
+
if (logger.shouldLog("debug")) {
|
|
1097
|
+
logger.debug(`[AmazonBedrockProvider] BEFORE conversion - conversationHistory length: ${this.conversationHistory.length}`);
|
|
1098
|
+
this.conversationHistory.forEach((msg, index) => {
|
|
1099
|
+
logger.debug(`[AmazonBedrockProvider] Message ${index}: role=${msg.role}, content=${JSON.stringify(msg.content)}`);
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
910
1102
|
// Get all available tools
|
|
911
1103
|
// BaseProvider.stream() pre-merges base tools + external tools into options.tools
|
|
912
1104
|
const aiTools = options.tools ||
|
|
@@ -914,10 +1106,12 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
914
1106
|
const allTools = this.convertAISDKToolsToToolDefinitions(aiTools);
|
|
915
1107
|
const toolConfig = this.formatToolsForBedrock(allTools);
|
|
916
1108
|
const convertedMessages = this.convertToAWSMessages(this.conversationHistory);
|
|
917
|
-
logger.
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1109
|
+
if (logger.shouldLog("debug")) {
|
|
1110
|
+
logger.debug(`[AmazonBedrockProvider] AFTER conversion - messages length: ${convertedMessages.length}`);
|
|
1111
|
+
convertedMessages.forEach((msg, index) => {
|
|
1112
|
+
logger.debug(`[AmazonBedrockProvider] Converted Message ${index}: role=${msg.role}, content=${JSON.stringify(msg.content)}`);
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
921
1115
|
const commandInput = {
|
|
922
1116
|
modelId: this.modelName || this.getDefaultModel(),
|
|
923
1117
|
messages: convertedMessages,
|
|
@@ -954,7 +1148,7 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
954
1148
|
messageCount: commandInput.messages?.length || 0,
|
|
955
1149
|
});
|
|
956
1150
|
const iterationStartTime = Date.now();
|
|
957
|
-
const response = await this.bedrockClient.send(command);
|
|
1151
|
+
const response = await withTimeout(this.bedrockClient.send(command), 120_000, new Error("Bedrock streaming API call timed out"));
|
|
958
1152
|
logger.debug("[Observability] Bedrock streaming API connection established (continuation)", {
|
|
959
1153
|
model: commandInput.modelId,
|
|
960
1154
|
durationMs: Date.now() - iterationStartTime,
|
|
@@ -999,22 +1193,23 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
999
1193
|
toolUseId: `tool_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
1000
1194
|
};
|
|
1001
1195
|
}
|
|
1002
|
-
//
|
|
1196
|
+
// Accumulate JSON string fragments into _inputBuffer.
|
|
1197
|
+
// Bedrock sends toolUse.input as incremental JSON string fragments,
|
|
1198
|
+
// not pre-parsed objects. We buffer them and parse at contentBlockStop.
|
|
1003
1199
|
if (chunk.contentBlockDelta.delta.toolUse.input) {
|
|
1004
|
-
// Merge parameters more robustly to avoid missing required parameters
|
|
1005
1200
|
const deltaInput = chunk.contentBlockDelta.delta.toolUse.input;
|
|
1006
1201
|
if (typeof deltaInput === "string") {
|
|
1007
|
-
currentBlock.
|
|
1202
|
+
currentBlock._inputBuffer =
|
|
1203
|
+
(currentBlock._inputBuffer || "") + deltaInput;
|
|
1008
1204
|
}
|
|
1009
1205
|
else if (deltaInput &&
|
|
1010
1206
|
typeof deltaInput === "object" &&
|
|
1011
1207
|
!Array.isArray(deltaInput)) {
|
|
1012
|
-
//
|
|
1208
|
+
// Some SDK versions may deliver pre-parsed objects; merge directly
|
|
1013
1209
|
const currentInput = currentBlock.toolUse.input || {};
|
|
1014
|
-
const newInput = deltaInput;
|
|
1015
1210
|
currentBlock.toolUse.input = {
|
|
1016
1211
|
...currentInput,
|
|
1017
|
-
...
|
|
1212
|
+
...deltaInput,
|
|
1018
1213
|
};
|
|
1019
1214
|
}
|
|
1020
1215
|
}
|
|
@@ -1022,6 +1217,16 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
1022
1217
|
if (chunk.contentBlockStop) {
|
|
1023
1218
|
// Content block completed
|
|
1024
1219
|
const currentBlock = currentMessageContent[currentMessageContent.length - 1];
|
|
1220
|
+
// Parse accumulated JSON input buffer for tool-use blocks
|
|
1221
|
+
if (currentBlock?.toolUse && currentBlock._inputBuffer) {
|
|
1222
|
+
try {
|
|
1223
|
+
currentBlock.toolUse.input = JSON.parse(currentBlock._inputBuffer);
|
|
1224
|
+
}
|
|
1225
|
+
catch {
|
|
1226
|
+
currentBlock.toolUse.input = {};
|
|
1227
|
+
}
|
|
1228
|
+
delete currentBlock._inputBuffer;
|
|
1229
|
+
}
|
|
1025
1230
|
if (currentText && currentBlock && !currentBlock.toolUse) {
|
|
1026
1231
|
// Only add text to blocks that don't have toolUse
|
|
1027
1232
|
currentBlock.text = currentText;
|
|
@@ -1053,13 +1258,10 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
1053
1258
|
return true; // Continue conversation loop
|
|
1054
1259
|
}
|
|
1055
1260
|
else if (stopReason === "max_tokens") {
|
|
1056
|
-
//
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
};
|
|
1061
|
-
this.conversationHistory.push(userMessage);
|
|
1062
|
-
return true; // Continue conversation loop
|
|
1261
|
+
// Max tokens reached — close the stream rather than continuing,
|
|
1262
|
+
// since the model hit the configured limit.
|
|
1263
|
+
controller.close();
|
|
1264
|
+
return false;
|
|
1063
1265
|
}
|
|
1064
1266
|
else {
|
|
1065
1267
|
// Unknown stop reason - end conversation
|
|
@@ -1246,7 +1448,7 @@ export class AmazonBedrockProvider extends BaseProvider {
|
|
|
1246
1448
|
accept: "application/json",
|
|
1247
1449
|
body: requestBody,
|
|
1248
1450
|
});
|
|
1249
|
-
const response = await this.bedrockClient.send(command);
|
|
1451
|
+
const response = await withTimeout(this.bedrockClient.send(command), 60_000, new Error("Bedrock embedding API call timed out"));
|
|
1250
1452
|
// Parse the response
|
|
1251
1453
|
const responseBody = JSON.parse(new TextDecoder().decode(response.body));
|
|
1252
1454
|
if (!responseBody.embedding || !Array.isArray(responseBody.embedding)) {
|