@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.
Files changed (241) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +15 -15
  3. package/dist/adapters/video/videoAnalyzer.d.ts +1 -1
  4. package/dist/adapters/video/videoAnalyzer.js +10 -8
  5. package/dist/auth/anthropicOAuth.d.ts +377 -0
  6. package/dist/auth/anthropicOAuth.js +914 -0
  7. package/dist/auth/index.d.ts +20 -0
  8. package/dist/auth/index.js +29 -0
  9. package/dist/auth/tokenStore.d.ts +225 -0
  10. package/dist/auth/tokenStore.js +521 -0
  11. package/dist/cli/commands/auth.d.ts +50 -0
  12. package/dist/cli/commands/auth.js +1115 -0
  13. package/dist/cli/commands/setup-anthropic.js +1 -14
  14. package/dist/cli/commands/setup-azure.js +1 -12
  15. package/dist/cli/commands/setup-bedrock.js +1 -9
  16. package/dist/cli/commands/setup-google-ai.js +1 -12
  17. package/dist/cli/commands/setup-openai.js +1 -14
  18. package/dist/cli/commands/workflow.d.ts +27 -0
  19. package/dist/cli/commands/workflow.js +216 -0
  20. package/dist/cli/factories/authCommandFactory.d.ts +52 -0
  21. package/dist/cli/factories/authCommandFactory.js +146 -0
  22. package/dist/cli/factories/commandFactory.d.ts +6 -0
  23. package/dist/cli/factories/commandFactory.js +171 -22
  24. package/dist/cli/index.js +0 -1
  25. package/dist/cli/parser.js +14 -2
  26. package/dist/cli/utils/maskCredential.d.ts +11 -0
  27. package/dist/cli/utils/maskCredential.js +23 -0
  28. package/dist/constants/contextWindows.js +107 -16
  29. package/dist/constants/enums.d.ts +119 -15
  30. package/dist/constants/enums.js +182 -22
  31. package/dist/constants/index.d.ts +3 -1
  32. package/dist/constants/index.js +11 -1
  33. package/dist/context/budgetChecker.js +1 -1
  34. package/dist/context/contextCompactor.js +31 -4
  35. package/dist/context/emergencyTruncation.d.ts +21 -0
  36. package/dist/context/emergencyTruncation.js +88 -0
  37. package/dist/context/errorDetection.d.ts +16 -0
  38. package/dist/context/errorDetection.js +48 -1
  39. package/dist/context/errors.d.ts +19 -0
  40. package/dist/context/errors.js +21 -0
  41. package/dist/context/stages/slidingWindowTruncator.d.ts +6 -0
  42. package/dist/context/stages/slidingWindowTruncator.js +159 -24
  43. package/dist/core/baseProvider.js +306 -200
  44. package/dist/core/conversationMemoryManager.js +104 -61
  45. package/dist/core/evaluationProviders.js +16 -33
  46. package/dist/core/factory.js +237 -164
  47. package/dist/core/modules/GenerationHandler.js +175 -116
  48. package/dist/core/modules/MessageBuilder.js +222 -170
  49. package/dist/core/modules/StreamHandler.d.ts +1 -0
  50. package/dist/core/modules/StreamHandler.js +95 -27
  51. package/dist/core/modules/TelemetryHandler.d.ts +10 -1
  52. package/dist/core/modules/TelemetryHandler.js +25 -7
  53. package/dist/core/modules/ToolsManager.js +115 -191
  54. package/dist/core/redisConversationMemoryManager.js +418 -282
  55. package/dist/factories/providerRegistry.d.ts +5 -0
  56. package/dist/factories/providerRegistry.js +20 -2
  57. package/dist/index.d.ts +3 -3
  58. package/dist/index.js +4 -2
  59. package/dist/lib/adapters/video/videoAnalyzer.d.ts +1 -1
  60. package/dist/lib/adapters/video/videoAnalyzer.js +10 -8
  61. package/dist/lib/auth/anthropicOAuth.d.ts +377 -0
  62. package/dist/lib/auth/anthropicOAuth.js +915 -0
  63. package/dist/lib/auth/index.d.ts +20 -0
  64. package/dist/lib/auth/index.js +30 -0
  65. package/dist/lib/auth/tokenStore.d.ts +225 -0
  66. package/dist/lib/auth/tokenStore.js +522 -0
  67. package/dist/lib/constants/contextWindows.js +107 -16
  68. package/dist/lib/constants/enums.d.ts +119 -15
  69. package/dist/lib/constants/enums.js +182 -22
  70. package/dist/lib/constants/index.d.ts +3 -1
  71. package/dist/lib/constants/index.js +11 -1
  72. package/dist/lib/context/budgetChecker.js +1 -1
  73. package/dist/lib/context/contextCompactor.js +31 -4
  74. package/dist/lib/context/emergencyTruncation.d.ts +21 -0
  75. package/dist/lib/context/emergencyTruncation.js +89 -0
  76. package/dist/lib/context/errorDetection.d.ts +16 -0
  77. package/dist/lib/context/errorDetection.js +48 -1
  78. package/dist/lib/context/errors.d.ts +19 -0
  79. package/dist/lib/context/errors.js +22 -0
  80. package/dist/lib/context/stages/slidingWindowTruncator.d.ts +6 -0
  81. package/dist/lib/context/stages/slidingWindowTruncator.js +159 -24
  82. package/dist/lib/core/baseProvider.js +306 -200
  83. package/dist/lib/core/conversationMemoryManager.js +104 -61
  84. package/dist/lib/core/evaluationProviders.js +16 -33
  85. package/dist/lib/core/factory.js +237 -164
  86. package/dist/lib/core/modules/GenerationHandler.js +175 -116
  87. package/dist/lib/core/modules/MessageBuilder.js +222 -170
  88. package/dist/lib/core/modules/StreamHandler.d.ts +1 -0
  89. package/dist/lib/core/modules/StreamHandler.js +95 -27
  90. package/dist/lib/core/modules/TelemetryHandler.d.ts +10 -1
  91. package/dist/lib/core/modules/TelemetryHandler.js +25 -7
  92. package/dist/lib/core/modules/ToolsManager.js +115 -191
  93. package/dist/lib/core/redisConversationMemoryManager.js +418 -282
  94. package/dist/lib/factories/providerRegistry.d.ts +5 -0
  95. package/dist/lib/factories/providerRegistry.js +20 -2
  96. package/dist/lib/index.d.ts +3 -3
  97. package/dist/lib/index.js +4 -2
  98. package/dist/lib/mcp/externalServerManager.js +66 -0
  99. package/dist/lib/mcp/mcpCircuitBreaker.js +24 -0
  100. package/dist/lib/mcp/mcpClientFactory.js +16 -0
  101. package/dist/lib/mcp/toolDiscoveryService.js +32 -6
  102. package/dist/lib/mcp/toolRegistry.js +193 -123
  103. package/dist/lib/models/anthropicModels.d.ts +267 -0
  104. package/dist/lib/models/anthropicModels.js +528 -0
  105. package/dist/lib/neurolink.d.ts +6 -0
  106. package/dist/lib/neurolink.js +1162 -646
  107. package/dist/lib/providers/amazonBedrock.d.ts +1 -1
  108. package/dist/lib/providers/amazonBedrock.js +521 -319
  109. package/dist/lib/providers/anthropic.d.ts +123 -2
  110. package/dist/lib/providers/anthropic.js +873 -27
  111. package/dist/lib/providers/anthropicBaseProvider.js +77 -17
  112. package/dist/lib/providers/googleAiStudio.d.ts +1 -1
  113. package/dist/lib/providers/googleAiStudio.js +292 -227
  114. package/dist/lib/providers/googleVertex.d.ts +36 -1
  115. package/dist/lib/providers/googleVertex.js +553 -260
  116. package/dist/lib/providers/ollama.js +329 -278
  117. package/dist/lib/providers/openAI.js +77 -19
  118. package/dist/lib/providers/sagemaker/parsers.js +3 -3
  119. package/dist/lib/providers/sagemaker/streaming.js +3 -3
  120. package/dist/lib/proxy/proxyFetch.js +81 -48
  121. package/dist/lib/rag/ChunkerFactory.js +1 -1
  122. package/dist/lib/rag/chunkers/MarkdownChunker.d.ts +22 -0
  123. package/dist/lib/rag/chunkers/MarkdownChunker.js +213 -9
  124. package/dist/lib/rag/chunking/markdownChunker.d.ts +16 -0
  125. package/dist/lib/rag/chunking/markdownChunker.js +174 -2
  126. package/dist/lib/rag/pipeline/contextAssembly.js +2 -1
  127. package/dist/lib/rag/ragIntegration.d.ts +18 -1
  128. package/dist/lib/rag/ragIntegration.js +94 -14
  129. package/dist/lib/rag/retrieval/vectorQueryTool.js +21 -4
  130. package/dist/lib/server/abstract/baseServerAdapter.js +4 -1
  131. package/dist/lib/server/adapters/fastifyAdapter.js +35 -30
  132. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +32 -0
  133. package/dist/lib/services/server/ai/observability/instrumentation.js +39 -0
  134. package/dist/lib/telemetry/attributes.d.ts +52 -0
  135. package/dist/lib/telemetry/attributes.js +61 -0
  136. package/dist/lib/telemetry/index.d.ts +3 -0
  137. package/dist/lib/telemetry/index.js +3 -0
  138. package/dist/lib/telemetry/telemetryService.d.ts +6 -0
  139. package/dist/lib/telemetry/telemetryService.js +6 -0
  140. package/dist/lib/telemetry/tracers.d.ts +15 -0
  141. package/dist/lib/telemetry/tracers.js +17 -0
  142. package/dist/lib/telemetry/withSpan.d.ts +9 -0
  143. package/dist/lib/telemetry/withSpan.js +35 -0
  144. package/dist/lib/types/contextTypes.d.ts +10 -0
  145. package/dist/lib/types/errors.d.ts +62 -0
  146. package/dist/lib/types/errors.js +107 -0
  147. package/dist/lib/types/index.d.ts +2 -1
  148. package/dist/lib/types/index.js +2 -0
  149. package/dist/lib/types/providers.d.ts +107 -0
  150. package/dist/lib/types/providers.js +69 -0
  151. package/dist/lib/types/streamTypes.d.ts +14 -0
  152. package/dist/lib/types/subscriptionTypes.d.ts +893 -0
  153. package/dist/lib/types/subscriptionTypes.js +8 -0
  154. package/dist/lib/utils/conversationMemory.js +121 -82
  155. package/dist/lib/utils/logger.d.ts +5 -0
  156. package/dist/lib/utils/logger.js +50 -2
  157. package/dist/lib/utils/messageBuilder.js +22 -42
  158. package/dist/lib/utils/modelDetection.js +3 -3
  159. package/dist/lib/utils/providerConfig.d.ts +167 -0
  160. package/dist/lib/utils/providerConfig.js +619 -9
  161. package/dist/lib/utils/providerRetry.d.ts +41 -0
  162. package/dist/lib/utils/providerRetry.js +114 -0
  163. package/dist/lib/utils/retryability.d.ts +14 -0
  164. package/dist/lib/utils/retryability.js +23 -0
  165. package/dist/lib/utils/sanitizers/svg.js +4 -5
  166. package/dist/lib/utils/tokenEstimation.d.ts +11 -1
  167. package/dist/lib/utils/tokenEstimation.js +19 -4
  168. package/dist/lib/utils/videoAnalysisProcessor.js +7 -3
  169. package/dist/mcp/externalServerManager.js +66 -0
  170. package/dist/mcp/mcpCircuitBreaker.js +24 -0
  171. package/dist/mcp/mcpClientFactory.js +16 -0
  172. package/dist/mcp/toolDiscoveryService.js +32 -6
  173. package/dist/mcp/toolRegistry.js +193 -123
  174. package/dist/models/anthropicModels.d.ts +267 -0
  175. package/dist/models/anthropicModels.js +527 -0
  176. package/dist/neurolink.d.ts +6 -0
  177. package/dist/neurolink.js +1162 -646
  178. package/dist/providers/amazonBedrock.d.ts +1 -1
  179. package/dist/providers/amazonBedrock.js +521 -319
  180. package/dist/providers/anthropic.d.ts +123 -2
  181. package/dist/providers/anthropic.js +873 -27
  182. package/dist/providers/anthropicBaseProvider.js +77 -17
  183. package/dist/providers/googleAiStudio.d.ts +1 -1
  184. package/dist/providers/googleAiStudio.js +292 -227
  185. package/dist/providers/googleVertex.d.ts +36 -1
  186. package/dist/providers/googleVertex.js +553 -260
  187. package/dist/providers/ollama.js +329 -278
  188. package/dist/providers/openAI.js +77 -19
  189. package/dist/providers/sagemaker/parsers.js +3 -3
  190. package/dist/providers/sagemaker/streaming.js +3 -3
  191. package/dist/proxy/proxyFetch.js +81 -48
  192. package/dist/rag/ChunkerFactory.js +1 -1
  193. package/dist/rag/chunkers/MarkdownChunker.d.ts +22 -0
  194. package/dist/rag/chunkers/MarkdownChunker.js +213 -9
  195. package/dist/rag/chunking/markdownChunker.d.ts +16 -0
  196. package/dist/rag/chunking/markdownChunker.js +174 -2
  197. package/dist/rag/pipeline/contextAssembly.js +2 -1
  198. package/dist/rag/ragIntegration.d.ts +18 -1
  199. package/dist/rag/ragIntegration.js +94 -14
  200. package/dist/rag/retrieval/vectorQueryTool.js +21 -4
  201. package/dist/server/abstract/baseServerAdapter.js +4 -1
  202. package/dist/server/adapters/fastifyAdapter.js +35 -30
  203. package/dist/services/server/ai/observability/instrumentation.d.ts +32 -0
  204. package/dist/services/server/ai/observability/instrumentation.js +39 -0
  205. package/dist/telemetry/attributes.d.ts +52 -0
  206. package/dist/telemetry/attributes.js +60 -0
  207. package/dist/telemetry/index.d.ts +3 -0
  208. package/dist/telemetry/index.js +3 -0
  209. package/dist/telemetry/telemetryService.d.ts +6 -0
  210. package/dist/telemetry/telemetryService.js +6 -0
  211. package/dist/telemetry/tracers.d.ts +15 -0
  212. package/dist/telemetry/tracers.js +16 -0
  213. package/dist/telemetry/withSpan.d.ts +9 -0
  214. package/dist/telemetry/withSpan.js +34 -0
  215. package/dist/types/contextTypes.d.ts +10 -0
  216. package/dist/types/errors.d.ts +62 -0
  217. package/dist/types/errors.js +107 -0
  218. package/dist/types/index.d.ts +2 -1
  219. package/dist/types/index.js +2 -0
  220. package/dist/types/providers.d.ts +107 -0
  221. package/dist/types/providers.js +69 -0
  222. package/dist/types/streamTypes.d.ts +14 -0
  223. package/dist/types/subscriptionTypes.d.ts +893 -0
  224. package/dist/types/subscriptionTypes.js +7 -0
  225. package/dist/utils/conversationMemory.js +121 -82
  226. package/dist/utils/logger.d.ts +5 -0
  227. package/dist/utils/logger.js +50 -2
  228. package/dist/utils/messageBuilder.js +22 -42
  229. package/dist/utils/modelDetection.js +3 -3
  230. package/dist/utils/providerConfig.d.ts +167 -0
  231. package/dist/utils/providerConfig.js +619 -9
  232. package/dist/utils/providerRetry.d.ts +41 -0
  233. package/dist/utils/providerRetry.js +113 -0
  234. package/dist/utils/retryability.d.ts +14 -0
  235. package/dist/utils/retryability.js +22 -0
  236. package/dist/utils/sanitizers/svg.js +4 -5
  237. package/dist/utils/tokenEstimation.d.ts +11 -1
  238. package/dist/utils/tokenEstimation.js +19 -4
  239. package/dist/utils/videoAnalysisProcessor.js +7 -3
  240. package/dist/workflow/config.d.ts +26 -26
  241. 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
- logger.info(`🚀 [AmazonBedrockProvider] Starting Bedrock API call at ${new Date().toISOString()}`);
188
- try {
189
- // Pre-call validation and logging
190
- const region = typeof this.bedrockClient.config.region === "function"
191
- ? await this.bedrockClient.config.region()
192
- : this.bedrockClient.config.region;
193
- logger.info(`🔧 [AmazonBedrockProvider] Client region: ${region}`);
194
- logger.info(`🔧 [AmazonBedrockProvider] Model: ${this.modelName || this.getDefaultModel()}`);
195
- logger.info(`🔧 [AmazonBedrockProvider] Conversation history length: ${this.conversationHistory.length}`);
196
- // Get all available tools
197
- const aiTools = await this.getAllTools();
198
- const allTools = this.convertAISDKToolsToToolDefinitions(aiTools);
199
- const toolConfig = this.formatToolsForBedrock(allTools);
200
- const commandInput = {
201
- modelId: this.modelName || this.getDefaultModel(),
202
- messages: this.convertToAWSMessages(this.conversationHistory),
203
- system: [
204
- {
205
- text: options.systemPrompt ||
206
- "You are a helpful assistant with access to external tools. Use tools when necessary to provide accurate information.",
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
- inferenceConfig: {
210
- maxTokens: options.maxTokens, // No default limit - unlimited unless specified
211
- temperature: options.temperature || 0.7,
212
- },
213
- };
214
- if (toolConfig) {
215
- commandInput.toolConfig = toolConfig;
216
- logger.info(`🛠️ [AmazonBedrockProvider] Tools configured: ${toolConfig.tools?.length || 0}`);
217
- }
218
- // Log command details for debugging
219
- logger.info(`📋 [AmazonBedrockProvider] Command input summary:`);
220
- logger.info(` - Model ID: ${commandInput.modelId}`);
221
- logger.info(` - Messages count: ${commandInput.messages?.length || 0}`);
222
- logger.info(` - System prompts: ${commandInput.system?.length || 0}`);
223
- logger.info(` - Max tokens: ${commandInput.inferenceConfig?.maxTokens}`);
224
- logger.info(` - Temperature: ${commandInput.inferenceConfig?.temperature}`);
225
- logger.debug(`[AmazonBedrockProvider] Calling Bedrock with ${this.conversationHistory.length} messages and ${toolConfig?.tools?.length || 0} tools`);
226
- // Create command and attempt API call
227
- const command = new ConverseCommand(commandInput);
228
- logger.debug("[Observability] Bedrock API request", {
229
- model: commandInput.modelId,
230
- region: region,
231
- messageCount: commandInput.messages?.length || 0,
232
- toolCount: commandInput.toolConfig?.tools?.length || 0,
233
- maxTokens: commandInput.inferenceConfig?.maxTokens,
234
- });
235
- const apiCallStartTime = Date.now();
236
- const response = await this.bedrockClient.send(command);
237
- const apiCallDuration = Date.now() - apiCallStartTime;
238
- logger.debug("[Observability] Bedrock API response", {
239
- model: commandInput.modelId,
240
- durationMs: apiCallDuration,
241
- hasContent: !!response.output?.message?.content?.length,
242
- stopReason: response.stopReason,
243
- usage: response.usage
244
- ? {
245
- inputTokens: response.usage.inputTokens,
246
- outputTokens: response.usage.outputTokens,
247
- totalTokens: (response.usage.inputTokens || 0) +
248
- (response.usage.outputTokens || 0),
249
- }
250
- : undefined,
251
- });
252
- logger.info(`[AmazonBedrockProvider] Bedrock API call successful`);
253
- logger.info(`[AmazonBedrockProvider] API call duration: ${apiCallDuration}ms`);
254
- const totalDuration = Date.now() - startTime;
255
- logger.info(`[AmazonBedrockProvider] Total callBedrock duration: ${totalDuration}ms`);
256
- return response;
257
- }
258
- catch (error) {
259
- const errorDuration = Date.now() - startTime;
260
- // Extract AWS metadata for structured logging
261
- const awsError = error && typeof error === "object"
262
- ? error
263
- : null;
264
- const metadata = awsError?.$metadata && typeof awsError.$metadata === "object"
265
- ? awsError.$metadata
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
- if (metadata) {
281
- logger.error(`[AmazonBedrockProvider] AWS SDK metadata`, {
282
- httpStatus: metadata.httpStatusCode,
283
- requestId: metadata.requestId,
284
- attempts: metadata.attempts,
285
- totalRetryDelay: metadata.totalRetryDelay,
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
- throw error;
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
- // Handle max tokens by continuing conversation
387
- const userMessage = {
388
- role: "user",
389
- content: [{ text: "Please continue." }],
390
- };
391
- this.conversationHistory.push(userMessage);
392
- return { shouldContinue: true };
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
- logger.debug(`[AmazonBedrockProvider] Executing single tool: ${toolName}`, {
442
- args,
443
- });
444
- try {
445
- // Use BaseProvider's tool execution mechanism
446
- const aiTools = await this.getAllTools();
447
- const tools = this.convertAISDKToolsToToolDefinitions(aiTools);
448
- if (!tools[toolName]) {
449
- throw new Error(`Tool not found: ${toolName}`);
450
- }
451
- const tool = tools[toolName];
452
- if (!tool || !tool.execute) {
453
- throw new Error(`Tool ${toolName} does not have execute method`);
454
- }
455
- // Apply robust parameter handling like Bedrock-MCP-Connector
456
- // Bedrock toolUse.input already contains the correct parameter structure
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
- const result = await tool.execute(toolArgs);
477
- logger.debug(`[AmazonBedrockProvider] Tool execution result:`, {
478
- toolName,
479
- result,
480
- });
481
- // Handle ToolResult type
482
- if (result && typeof result === "object" && "success" in result) {
483
- if (result.success && result.data !== undefined) {
484
- if (typeof result.data === "string") {
485
- return result.data;
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
- else if (typeof result.data === "object") {
488
- return JSON.stringify(result.data, null, 2);
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
- return String(result.data);
546
+ finalResult = "";
492
547
  }
493
548
  }
494
- else if (result.error) {
495
- const errorMessage = typeof result.error === "string"
496
- ? result.error
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
- // Fallback for non-ToolResult return types
502
- if (typeof result === "string") {
503
- return result;
504
- }
505
- else if (typeof result === "object") {
506
- return JSON.stringify(result, null, 2);
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
- else {
509
- return String(result);
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
- try {
683
- logger.debug("🟢 [TRACE] executeStream TRY block - about to call streamingConversationLoop");
684
- // Clear conversation history for new streaming session
685
- this.conversationHistory = [];
686
- // Check for multimodal input (images, PDFs, CSVs, files)
687
- const hasMultimodalInput = !!(options.input?.images?.length ||
688
- options.input?.content?.length ||
689
- options.input?.files?.length ||
690
- options.input?.csvFiles?.length ||
691
- options.input?.pdfFiles?.length);
692
- if (hasMultimodalInput) {
693
- logger.debug(`[AmazonBedrockProvider] Detected multimodal input, using multimodal message builder`, {
694
- hasImages: !!options.input?.images?.length,
695
- imageCount: options.input?.images?.length || 0,
696
- hasContent: !!options.input?.content?.length,
697
- contentCount: options.input?.content?.length || 0,
698
- hasFiles: !!options.input?.files?.length,
699
- fileCount: options.input?.files?.length || 0,
700
- hasCSVFiles: !!options.input?.csvFiles?.length,
701
- csvFileCount: options.input?.csvFiles?.length || 0,
702
- hasPDFFiles: !!options.input?.pdfFiles?.length,
703
- pdfFileCount: options.input?.pdfFiles?.length || 0,
704
- });
705
- const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
706
- const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
707
- // Convert to Bedrock format
708
- this.conversationHistory =
709
- this.convertToBedrockMessages(multimodalMessages);
710
- }
711
- else {
712
- logger.debug(`[AmazonBedrockProvider] Text-only input, using simple message builder`);
713
- // Add user message to conversation - simple text-only case
714
- const userMessage = {
715
- role: "user",
716
- content: [{ text: options.input.text }],
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
- // Convert generate result to streaming format
755
- const stream = new ReadableStream({
756
- start(controller) {
757
- // Split the response into chunks for pseudo-streaming
758
- const responseText = generateResult.content || "";
759
- const chunks = responseText.split(" ");
760
- chunks.forEach((word, _index) => {
761
- controller.enqueue({ content: word + " " });
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
- controller.enqueue({ content: "" });
764
- controller.close();
765
- },
766
- });
767
- // Convert ReadableStream to AsyncIterable like streamingConversationLoop does
768
- const asyncIterable = {
769
- async *[Symbol.asyncIterator]() {
770
- const reader = stream.getReader();
771
- try {
772
- while (true) {
773
- const { done, value } = await reader.read();
774
- if (done) {
775
- break;
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
- finally {
781
- reader.releaseLock();
782
- }
783
- },
784
- };
785
- return {
786
- stream: asyncIterable,
787
- usage: { total: 0, input: 0, output: 0 },
788
- model: this.modelName || this.getDefaultModel(),
789
- provider: this.getProviderName(),
790
- metadata: {
791
- fallback: true,
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
- // Re-throw non-permission errors
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
- controller.enqueue({
832
- content: chunk.contentBlockDelta.delta.text,
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
- controller.close();
837
- return;
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.info(`🔍 [AmazonBedrockProvider] BEFORE conversion - conversationHistory length: ${this.conversationHistory.length}`);
907
- this.conversationHistory.forEach((msg, index) => {
908
- logger.info(`🔍 [AmazonBedrockProvider] Message ${index}: role=${msg.role}, content=${JSON.stringify(msg.content)}`);
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.info(`🔍 [AmazonBedrockProvider] AFTER conversion - messages length: ${convertedMessages.length}`);
918
- convertedMessages.forEach((msg, index) => {
919
- logger.info(`🔍 [AmazonBedrockProvider] Converted Message ${index}: role=${msg.role}, content=${JSON.stringify(msg.content)}`);
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
- // Use robust parameter merging like Bedrock-MCP-Connector
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.toolUse.input = { value: deltaInput };
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
- // Ensure both objects are properly typed before spreading
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
- ...newInput,
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
- // Handle max tokens by continuing conversation
1057
- const userMessage = {
1058
- role: "user",
1059
- content: [{ text: "Please continue." }],
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)) {