@juspay/neurolink 7.29.3 → 7.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/config/conversationMemoryConfig.js +5 -0
  3. package/dist/core/conversationMemoryManager.d.ts +9 -15
  4. package/dist/core/conversationMemoryManager.js +103 -56
  5. package/dist/core/types.d.ts +3 -1
  6. package/dist/factories/providerRegistry.js +1 -1
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +2 -0
  9. package/dist/lib/config/conversationMemoryConfig.js +5 -0
  10. package/dist/lib/core/conversationMemoryManager.d.ts +9 -15
  11. package/dist/lib/core/conversationMemoryManager.js +103 -56
  12. package/dist/lib/core/types.d.ts +3 -1
  13. package/dist/lib/factories/providerRegistry.js +1 -1
  14. package/dist/lib/index.d.ts +2 -0
  15. package/dist/lib/index.js +2 -0
  16. package/dist/lib/neurolink.d.ts +0 -9
  17. package/dist/lib/neurolink.js +7 -39
  18. package/dist/lib/providers/amazonBedrock.d.ts +28 -59
  19. package/dist/lib/providers/amazonBedrock.js +913 -330
  20. package/dist/lib/types/conversationTypes.d.ts +10 -0
  21. package/dist/lib/types/generateTypes.d.ts +1 -2
  22. package/dist/lib/utils/conversationMemoryUtils.d.ts +1 -2
  23. package/dist/lib/utils/conversationMemoryUtils.js +5 -6
  24. package/dist/lib/utils/logger.d.ts +164 -4
  25. package/dist/lib/utils/logger.js +163 -10
  26. package/dist/lib/utils/providerUtils.js +9 -6
  27. package/dist/neurolink.d.ts +0 -9
  28. package/dist/neurolink.js +7 -39
  29. package/dist/providers/amazonBedrock.d.ts +28 -59
  30. package/dist/providers/amazonBedrock.js +913 -330
  31. package/dist/types/conversationTypes.d.ts +10 -0
  32. package/dist/types/generateTypes.d.ts +1 -2
  33. package/dist/utils/conversationMemoryUtils.d.ts +1 -2
  34. package/dist/utils/conversationMemoryUtils.js +5 -6
  35. package/dist/utils/logger.d.ts +164 -4
  36. package/dist/utils/logger.js +163 -10
  37. package/dist/utils/providerUtils.js +9 -6
  38. package/package.json +2 -3
  39. package/dist/context/ContextManager.d.ts +0 -28
  40. package/dist/context/ContextManager.js +0 -113
  41. package/dist/context/config.d.ts +0 -5
  42. package/dist/context/config.js +0 -42
  43. package/dist/context/types.d.ts +0 -20
  44. package/dist/context/types.js +0 -1
  45. package/dist/context/utils.d.ts +0 -7
  46. package/dist/context/utils.js +0 -8
  47. package/dist/lib/context/ContextManager.d.ts +0 -28
  48. package/dist/lib/context/ContextManager.js +0 -113
  49. package/dist/lib/context/config.d.ts +0 -5
  50. package/dist/lib/context/config.js +0 -42
  51. package/dist/lib/context/types.d.ts +0 -20
  52. package/dist/lib/context/types.js +0 -1
  53. package/dist/lib/context/utils.d.ts +0 -7
  54. package/dist/lib/context/utils.js +0 -8
  55. package/dist/lib/providers/aws/credentialProvider.d.ts +0 -58
  56. package/dist/lib/providers/aws/credentialProvider.js +0 -267
  57. package/dist/lib/providers/aws/credentialTester.d.ts +0 -49
  58. package/dist/lib/providers/aws/credentialTester.js +0 -394
  59. package/dist/providers/aws/credentialProvider.d.ts +0 -58
  60. package/dist/providers/aws/credentialProvider.js +0 -267
  61. package/dist/providers/aws/credentialTester.d.ts +0 -49
  62. package/dist/providers/aws/credentialTester.js +0 -394
@@ -1,390 +1,973 @@
1
- import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock";
2
- import { streamText } from "ai";
1
+ import { BedrockRuntimeClient, ConverseCommand, ConverseStreamCommand, } from "@aws-sdk/client-bedrock-runtime";
2
+ import { BedrockClient, ListFoundationModelsCommand, } from "@aws-sdk/client-bedrock";
3
3
  import { BaseProvider } from "../core/baseProvider.js";
4
4
  import { logger } from "../utils/logger.js";
5
- import { createTimeoutController, TimeoutError } from "../utils/timeout.js";
6
- import { DEFAULT_MAX_TOKENS, DEFAULT_MAX_STEPS } from "../core/constants.js";
7
- import { validateApiKey, createAWSAccessKeyConfig, createAWSSecretConfig, getAWSRegion, getAWSSessionToken, } from "../utils/providerConfig.js";
8
- import { buildMessagesArray } from "../utils/messageBuilder.js";
9
- import { createProxyFetch } from "../proxy/proxyFetch.js";
10
- import { AWSCredentialProvider } from "./aws/credentialProvider.js";
11
- import { BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime";
12
- // Configuration helpers
13
- const getBedrockModelId = () => {
14
- const model = process.env.BEDROCK_MODEL || process.env.BEDROCK_MODEL_ID;
15
- if (!model) {
16
- throw new Error("BEDROCK_MODEL (or BEDROCK_MODEL_ID) is required. Example: 'anthropic.claude-3-haiku-20240307-v1:0' or a valid inference profile ARN.");
17
- }
18
- return model;
19
- };
20
- // Configuration helpers - now using consolidated utility
21
- const getAWSAccessKeyId = () => {
22
- return validateApiKey(createAWSAccessKeyConfig());
23
- };
24
- const getAWSSecretAccessKey = () => {
25
- return validateApiKey(createAWSSecretConfig());
26
- };
27
- // Note: getAWSRegion and getAWSSessionToken are now directly imported from consolidated utility
28
- const getAppEnvironment = () => {
29
- return process.env.PUBLIC_APP_ENVIRONMENT || "production";
30
- };
31
- /**
32
- * Amazon Bedrock Provider v3 - Enhanced Authentication Implementation
33
- *
34
- * BEDROCK-MCP-CONNECTOR COMPATIBILITY: Complete AWS SDK credential chain support
35
- *
36
- * Features:
37
- * - Extends BaseProvider for shared functionality
38
- * - AWS SDK v3 defaultProvider credential chain (9 sources)
39
- * - Dual access: AI SDK + Direct AWS SDK BedrockRuntimeClient
40
- * - Full backward compatibility with existing configurations
41
- * - Enhanced error handling with setup guidance
42
- * - Bedrock-MCP-Connector compatible authentication patterns
43
- */
5
+ import { zodToJsonSchema } from "zod-to-json-schema";
44
6
  export class AmazonBedrockProvider extends BaseProvider {
45
- awsCredentialProvider;
46
7
  bedrockClient;
47
- bedrock;
48
- model;
49
- constructor(modelName, credentialConfig, neurolink) {
8
+ conversationHistory = [];
9
+ constructor(modelName, neurolink) {
50
10
  super(modelName, "bedrock", neurolink);
51
- // Debug: Bedrock initialization started
52
- logger.debug("[Bedrock] Provider initialization started", {
53
- requestedModel: modelName || "default",
54
- environment: getAppEnvironment(),
55
- });
56
- // Initialize AWS credential provider with full credential chain support
57
- const defaultCredentialConfig = {
58
- region: getAWSRegion(),
59
- enableDebugLogging: getAppEnvironment() === "dev",
60
- ...credentialConfig,
61
- };
62
- // Debug: AWS configuration
63
- logger.debug("[Bedrock] AWS configuration resolved", {
64
- region: defaultCredentialConfig.region,
65
- enableDebugLogging: defaultCredentialConfig.enableDebugLogging,
66
- credentialConfigProvided: !!credentialConfig,
67
- });
68
- this.awsCredentialProvider = new AWSCredentialProvider(defaultCredentialConfig);
69
- // Debug: AWS credential detection status
70
- logger.debug("[Bedrock] AWS credential detection status", {
71
- hasAccessKey: !!process.env.AWS_ACCESS_KEY_ID,
72
- hasSecretKey: !!process.env.AWS_SECRET_ACCESS_KEY,
73
- hasSessionToken: !!process.env.AWS_SESSION_TOKEN,
74
- hasProfile: !!process.env.AWS_PROFILE,
75
- credentialChainEnabled: true,
76
- });
77
- // Create AWS SDK v3 Bedrock client for direct access (Bedrock-MCP-Connector compatibility)
78
- // Proxy support will be injected lazily when needed
79
- this.bedrockClient = new BedrockRuntimeClient({
80
- region: defaultCredentialConfig.region,
81
- credentials: this.awsCredentialProvider.getCredentialProvider(),
82
- });
83
- // Debug: AWS region and service endpoint
84
- logger.debug("[Bedrock] AWS service configuration", {
85
- region: defaultCredentialConfig.region,
86
- serviceEndpoint: `https://bedrock-runtime.${defaultCredentialConfig.region}.amazonaws.com`,
87
- credentialProviderType: "AWS SDK v3 defaultProvider chain",
88
- });
89
- // For now, use legacy configuration as AI SDK may not support credential providers directly
90
- // TODO: Update when @ai-sdk/amazon-bedrock supports credential providers
91
- const legacyAwsConfig = this.createLegacyAWSConfig();
11
+ logger.debug("[AmazonBedrockProvider] Starting constructor with extensive logging for debugging");
12
+ // Log environment variables for debugging
13
+ logger.debug(`[AmazonBedrockProvider] Environment check: AWS_REGION=${process.env.AWS_REGION || "undefined"}, AWS_ACCESS_KEY_ID=${process.env.AWS_ACCESS_KEY_ID ? "SET" : "undefined"}, AWS_SECRET_ACCESS_KEY=${process.env.AWS_SECRET_ACCESS_KEY ? "SET" : "undefined"}`);
92
14
  try {
93
- this.bedrock = createAmazonBedrock(legacyAwsConfig);
15
+ // Create BedrockRuntimeClient with clean configuration like working Bedrock-MCP-Connector
16
+ // Absolutely no proxy interference - let AWS SDK handle everything natively
17
+ logger.debug("[AmazonBedrockProvider] Creating BedrockRuntimeClient with clean configuration");
18
+ this.bedrockClient = new BedrockRuntimeClient({
19
+ region: process.env.AWS_REGION || "us-east-1",
20
+ // Clean configuration - AWS SDK will handle credentials via:
21
+ // 1. IAM roles (preferred in production)
22
+ // 2. Environment variables
23
+ // 3. AWS config files
24
+ // 4. Instance metadata
25
+ });
26
+ logger.debug(`[AmazonBedrockProvider] Successfully created BedrockRuntimeClient with model: ${this.modelName}, region: ${process.env.AWS_REGION || "us-east-1"}`);
27
+ // Immediate health check to catch credential issues early
28
+ this.performInitialHealthCheck();
94
29
  }
95
30
  catch (error) {
96
- logger.error("Failed to create AI SDK provider", {
97
- error: error instanceof Error ? error.message : String(error),
98
- });
99
- throw new Error(`Failed to initialize Amazon Bedrock AI SDK: ${error instanceof Error ? error.message : String(error)}`);
100
- }
101
- // Pre-initialize model for efficiency
102
- const resolvedModelId = this.modelName || getBedrockModelId();
103
- // Debug: Bedrock model validation process
104
- logger.debug("[Bedrock] Model validation and ARN processing", {
105
- requestedModel: this.modelName || "from environment",
106
- resolvedModelId: resolvedModelId,
107
- isInferenceProfile: resolvedModelId.includes(":inference-profile/"),
108
- isFoundationModel: resolvedModelId.startsWith("anthropic.") ||
109
- resolvedModelId.startsWith("amazon.") ||
110
- resolvedModelId.startsWith("meta."),
111
- modelARNValidation: resolvedModelId.includes("arn:aws:bedrock:")
112
- ? "Full ARN provided"
113
- : "Model ID provided",
114
- });
115
- this.model = this.bedrock(resolvedModelId);
116
- logger.debug("Amazon Bedrock Provider v3 initialized", {
117
- modelName: this.modelName,
118
- region: defaultCredentialConfig.region,
119
- credentialProvider: "AWS SDK v3 defaultProvider",
120
- hasDualAccess: true,
121
- provider: this.providerName,
122
- });
31
+ logger.error(`[AmazonBedrockProvider] CRITICAL: Failed to initialize BedrockRuntimeClient:`, error);
32
+ throw error;
33
+ }
123
34
  }
124
35
  /**
125
- * Legacy AWS configuration for backward compatibility
36
+ * Perform initial health check to catch credential/connectivity issues early
37
+ * This prevents the health check failure we saw in production logs
126
38
  */
127
- createLegacyAWSConfig() {
128
- const awsConfig = {
129
- accessKeyId: getAWSAccessKeyId(),
130
- secretAccessKey: getAWSSecretAccessKey(),
131
- region: getAWSRegion(),
132
- fetch: createProxyFetch(),
133
- };
134
- // Add session token for development environment
135
- if (getAppEnvironment() === "dev") {
136
- const sessionToken = getAWSSessionToken();
137
- if (sessionToken) {
138
- awsConfig.sessionToken = sessionToken;
39
+ async performInitialHealthCheck() {
40
+ const bedrockClient = new BedrockClient({
41
+ region: process.env.AWS_REGION || "us-east-1",
42
+ });
43
+ try {
44
+ logger.debug("[AmazonBedrockProvider] Starting initial health check to validate credentials and connectivity");
45
+ // Try to list foundation models as a lightweight health check
46
+ const command = new ListFoundationModelsCommand({});
47
+ const startTime = Date.now();
48
+ await bedrockClient.send(command);
49
+ const responseTime = Date.now() - startTime;
50
+ logger.debug(`[AmazonBedrockProvider] Health check PASSED - credentials valid, connectivity good, responseTime: ${responseTime}ms`);
51
+ }
52
+ catch (error) {
53
+ const errorMessage = error instanceof Error ? error.message : String(error);
54
+ logger.error(`[AmazonBedrockProvider] Health check FAILED - this will cause production failures:`, {
55
+ error: errorMessage,
56
+ errorType: error instanceof Error ? error.constructor.name : "Unknown",
57
+ region: process.env.AWS_REGION || "us-east-1",
58
+ hasAccessKey: !!process.env.AWS_ACCESS_KEY_ID,
59
+ hasSecretKey: !!process.env.AWS_SECRET_ACCESS_KEY,
60
+ });
61
+ // Don't throw here - let the actual usage fail with better context
62
+ }
63
+ finally {
64
+ try {
65
+ bedrockClient.destroy();
66
+ }
67
+ catch {
68
+ // Ignore destroy errors during cleanup
139
69
  }
140
70
  }
141
- return awsConfig;
71
+ }
72
+ // Not using AI SDK approach in conversation management
73
+ getAISDKModel() {
74
+ throw new Error("AmazonBedrockProvider does not use AI SDK models");
142
75
  }
143
76
  getProviderName() {
144
77
  return "bedrock";
145
78
  }
146
79
  getDefaultModel() {
147
- return getBedrockModelId();
148
- }
149
- /**
150
- * Returns the Vercel AI SDK model instance for AWS Bedrock
151
- */
152
- getAISDKModel() {
153
- return this.model;
154
- }
155
- /**
156
- * Get AWS SDK BedrockRuntimeClient for direct access (Bedrock-MCP-Connector compatibility)
157
- * This provides the same direct AWS SDK access that Bedrock-MCP-Connector uses
158
- */
159
- getBedrockClient() {
160
- // Note: For synchronous access, proxy support is configured lazily
161
- // If proxy support is critical, use getBedrockClientWithProxy() instead
162
- return this.bedrockClient;
80
+ return (process.env.BEDROCK_MODEL || "anthropic.claude-3-sonnet-20240229-v1:0");
163
81
  }
164
- /**
165
- * Get AWS SDK BedrockRuntimeClient with proxy support ensured
166
- * Use this method when proxy support is critical for the operation
167
- */
168
- async getBedrockClientWithProxy() {
169
- await this.ensureProxySupport();
170
- return this.bedrockClient;
82
+ // Override the main generate method to implement conversation management
83
+ async generate(optionsOrPrompt) {
84
+ logger.debug("[AmazonBedrockProvider] generate() called with conversation management");
85
+ const options = typeof optionsOrPrompt === "string"
86
+ ? { prompt: optionsOrPrompt }
87
+ : optionsOrPrompt;
88
+ // Clear conversation history for new generation
89
+ this.conversationHistory = [];
90
+ // Add user message to conversation
91
+ const userMessage = {
92
+ role: "user",
93
+ content: [{ text: options.prompt }],
94
+ };
95
+ this.conversationHistory.push(userMessage);
96
+ logger.debug(`[AmazonBedrockProvider] Starting conversation with prompt: ${options.prompt}`);
97
+ // Start conversation loop and return enhanced result
98
+ const text = await this.conversationLoop(options);
99
+ return {
100
+ content: text, // CLI expects 'content' not 'text'
101
+ usage: { total: 0, input: 0, output: 0 },
102
+ model: this.modelName || this.getDefaultModel(),
103
+ provider: this.getProviderName(),
104
+ };
171
105
  }
172
- /**
173
- * Get AWS credential provider for advanced credential management
174
- */
175
- getCredentialProvider() {
176
- return this.awsCredentialProvider;
106
+ async conversationLoop(options) {
107
+ const maxIterations = 10; // Prevent infinite loops
108
+ let iteration = 0;
109
+ while (iteration < maxIterations) {
110
+ iteration++;
111
+ logger.debug(`[AmazonBedrockProvider] Conversation iteration ${iteration}`);
112
+ try {
113
+ logger.debug(`[AmazonBedrockProvider] About to call Bedrock API`);
114
+ const response = await this.callBedrock(options);
115
+ logger.debug(`[AmazonBedrockProvider] Received Bedrock response`, JSON.stringify(response, null, 2));
116
+ const result = await this.handleBedrockResponse(response);
117
+ logger.debug(`[AmazonBedrockProvider] Handle response result:`, result);
118
+ if (result.shouldContinue) {
119
+ logger.debug(`[AmazonBedrockProvider] Continuing conversation loop...`);
120
+ continue;
121
+ }
122
+ else {
123
+ logger.debug(`[AmazonBedrockProvider] Conversation completed with final text`);
124
+ logger.debug(`[AmazonBedrockProvider] Returning final text: "${result.text}"`);
125
+ return result.text || "";
126
+ }
127
+ }
128
+ catch (error) {
129
+ logger.error(`[AmazonBedrockProvider] Error in conversation loop:`, error);
130
+ throw this.handleProviderError(error);
131
+ }
132
+ }
133
+ throw new Error("Conversation loop exceeded maximum iterations");
177
134
  }
178
- /**
179
- * Ensure proxy support is configured for AWS SDK client if needed
180
- */
181
- async ensureProxySupport() {
135
+ async callBedrock(options) {
136
+ const startTime = Date.now();
137
+ logger.info(`šŸš€ [AmazonBedrockProvider] Starting Bedrock API call at ${new Date().toISOString()}`);
182
138
  try {
183
- const { createAWSProxyHandler } = await import("../proxy/awsProxyIntegration.js");
184
- const proxyHandler = await createAWSProxyHandler();
185
- if (proxyHandler) {
186
- logger.debug("[Bedrock] Reinitializing client with proxy support");
187
- // Recreate the client with proxy handler
188
- this.bedrockClient = new BedrockRuntimeClient({
189
- region: this.awsCredentialProvider.getConfig().region,
190
- credentials: this.awsCredentialProvider.getCredentialProvider(),
191
- requestHandler: proxyHandler,
192
- });
139
+ // Pre-call validation and logging
140
+ const region = typeof this.bedrockClient.config.region === "function"
141
+ ? await this.bedrockClient.config.region()
142
+ : this.bedrockClient.config.region;
143
+ logger.info(`šŸ”§ [AmazonBedrockProvider] Client region: ${region}`);
144
+ logger.info(`šŸ”§ [AmazonBedrockProvider] Model: ${this.modelName || this.getDefaultModel()}`);
145
+ logger.info(`šŸ”§ [AmazonBedrockProvider] Conversation history length: ${this.conversationHistory.length}`);
146
+ // Get all available tools
147
+ const aiTools = await this.getAllTools();
148
+ const allTools = this.convertAISDKToolsToToolDefinitions(aiTools);
149
+ const toolConfig = this.formatToolsForBedrock(allTools);
150
+ const commandInput = {
151
+ modelId: this.modelName || this.getDefaultModel(),
152
+ messages: this.convertToAWSMessages(this.conversationHistory),
153
+ system: [
154
+ {
155
+ text: options.systemPrompt ||
156
+ "You are a helpful assistant with access to external tools. Use tools when necessary to provide accurate information.",
157
+ },
158
+ ],
159
+ inferenceConfig: {
160
+ maxTokens: options.maxTokens || 4096,
161
+ temperature: options.temperature || 0.7,
162
+ },
163
+ };
164
+ if (toolConfig) {
165
+ commandInput.toolConfig = toolConfig;
166
+ logger.info(`šŸ› ļø [AmazonBedrockProvider] Tools configured: ${toolConfig.tools?.length || 0}`);
193
167
  }
168
+ // Log command details for debugging
169
+ logger.info(`šŸ“‹ [AmazonBedrockProvider] Command input summary:`);
170
+ logger.info(` - Model ID: ${commandInput.modelId}`);
171
+ logger.info(` - Messages count: ${commandInput.messages?.length || 0}`);
172
+ logger.info(` - System prompts: ${commandInput.system?.length || 0}`);
173
+ logger.info(` - Max tokens: ${commandInput.inferenceConfig?.maxTokens}`);
174
+ logger.info(` - Temperature: ${commandInput.inferenceConfig?.temperature}`);
175
+ logger.debug(`[AmazonBedrockProvider] Calling Bedrock with ${this.conversationHistory.length} messages and ${toolConfig?.tools?.length || 0} tools`);
176
+ // Create command and attempt API call
177
+ const command = new ConverseCommand(commandInput);
178
+ logger.info(`ā³ [AmazonBedrockProvider] Sending ConverseCommand to Bedrock...`);
179
+ const apiCallStartTime = Date.now();
180
+ const response = await this.bedrockClient.send(command);
181
+ const apiCallDuration = Date.now() - apiCallStartTime;
182
+ logger.info(`āœ… [AmazonBedrockProvider] Bedrock API call successful!`);
183
+ logger.info(`ā±ļø [AmazonBedrockProvider] API call duration: ${apiCallDuration}ms`);
184
+ logger.info(`šŸ“Š [AmazonBedrockProvider] Response metadata:`);
185
+ logger.info(` - Stop reason: ${response.stopReason}`);
186
+ logger.info(` - Usage tokens: ${JSON.stringify(response.usage || {})}`);
187
+ logger.info(` - Metrics: ${JSON.stringify(response.metrics || {})}`);
188
+ const totalDuration = Date.now() - startTime;
189
+ logger.info(`šŸŽÆ [AmazonBedrockProvider] Total callBedrock duration: ${totalDuration}ms`);
190
+ return response;
194
191
  }
195
192
  catch (error) {
196
- logger.warn("[Bedrock] Failed to configure proxy support", { error });
197
- // Continue without proxy support
193
+ const errorDuration = Date.now() - startTime;
194
+ logger.error(`āŒ [AmazonBedrockProvider] Bedrock API call failed after ${errorDuration}ms`);
195
+ logger.error(`šŸ” [AmazonBedrockProvider] Error details:`);
196
+ if (error instanceof Error) {
197
+ logger.error(` - Error name: ${error.name}`);
198
+ logger.error(` - Error message: ${error.message}`);
199
+ logger.error(` - Error stack: ${error.stack}`);
200
+ }
201
+ // Log AWS SDK specific error details
202
+ if (error && typeof error === "object") {
203
+ const awsError = error;
204
+ if (awsError.$metadata && typeof awsError.$metadata === "object") {
205
+ const metadata = awsError.$metadata;
206
+ logger.error(`šŸ­ [AmazonBedrockProvider] AWS SDK metadata:`);
207
+ logger.error(` - HTTP status: ${metadata.httpStatusCode}`);
208
+ logger.error(` - Request ID: ${metadata.requestId}`);
209
+ logger.error(` - Attempts: ${metadata.attempts}`);
210
+ logger.error(` - Total retry delay: ${metadata.totalRetryDelay}`);
211
+ }
212
+ if (awsError.Code) {
213
+ logger.error(` - AWS Error Code: ${awsError.Code}`);
214
+ }
215
+ if (awsError.Type) {
216
+ logger.error(` - AWS Error Type: ${awsError.Type}`);
217
+ }
218
+ if (awsError.Fault) {
219
+ logger.error(` - AWS Fault: ${awsError.Fault}`);
220
+ }
221
+ }
222
+ // Log environment details for debugging
223
+ logger.error(`šŸŒ [AmazonBedrockProvider] Environment diagnostics:`);
224
+ logger.error(` - AWS_REGION: ${process.env.AWS_REGION || "not set"}`);
225
+ logger.error(` - AWS_PROFILE: ${process.env.AWS_PROFILE || "not set"}`);
226
+ logger.error(` - AWS_ACCESS_KEY_ID: ${process.env.AWS_ACCESS_KEY_ID ? "set" : "not set"}`);
227
+ logger.error(` - AWS_SECRET_ACCESS_KEY: ${process.env.AWS_SECRET_ACCESS_KEY ? "set" : "not set"}`);
228
+ logger.error(` - AWS_SESSION_TOKEN: ${process.env.AWS_SESSION_TOKEN ? "set" : "not set"}`);
229
+ throw error;
198
230
  }
199
231
  }
200
- /**
201
- * Test AWS credentials and Bedrock connectivity
202
- * Useful for debugging authentication issues
203
- */
204
- async testConnectivity() {
205
- const startTime = Date.now();
206
- try {
207
- // Ensure proxy support is configured before testing
208
- await this.ensureProxySupport();
209
- const { CredentialTester } = await import("./aws/credentialTester.js");
210
- // Add timeout protection using AbortController
211
- const timeout = 15000; // 15 second timeout
212
- const abortController = new AbortController();
213
- const timeoutId = setTimeout(() => {
214
- abortController.abort();
215
- }, timeout);
216
- try {
217
- const [credentialResult, connectivityResult] = await Promise.race([
218
- Promise.all([
219
- CredentialTester.validateCredentials(this.awsCredentialProvider),
220
- CredentialTester.testBedrockConnectivity(this.awsCredentialProvider),
221
- ]),
222
- new Promise((_, reject) => {
223
- abortController.signal.addEventListener("abort", () => {
224
- reject(new Error("Connectivity test timeout"));
232
+ async handleBedrockResponse(response) {
233
+ logger.debug(`[AmazonBedrockProvider] Received response with stopReason: ${response.stopReason}`);
234
+ if (!response.output || !response.output.message) {
235
+ throw new Error("Invalid response structure from Bedrock API");
236
+ }
237
+ const assistantMessage = response.output.message;
238
+ const stopReason = response.stopReason;
239
+ // Add assistant message to conversation history
240
+ const bedrockAssistantMessage = {
241
+ role: "assistant",
242
+ content: (assistantMessage.content || []).map((item) => {
243
+ const bedrockItem = {};
244
+ if ("text" in item && item.text) {
245
+ bedrockItem.text = item.text;
246
+ }
247
+ if ("toolUse" in item && item.toolUse) {
248
+ bedrockItem.toolUse = {
249
+ toolUseId: item.toolUse.toolUseId || "",
250
+ name: item.toolUse.name || "",
251
+ input: item.toolUse.input || {},
252
+ };
253
+ }
254
+ if ("toolResult" in item && item.toolResult) {
255
+ bedrockItem.toolResult = {
256
+ toolUseId: item.toolResult.toolUseId || "",
257
+ content: (item.toolResult.content || []).map((c) => ({
258
+ text: typeof c === "object" && "text" in c
259
+ ? c.text || ""
260
+ : "",
261
+ })),
262
+ status: item.toolResult.status || "unknown",
263
+ };
264
+ }
265
+ return bedrockItem;
266
+ }),
267
+ };
268
+ this.conversationHistory.push(bedrockAssistantMessage);
269
+ if (stopReason === "end_turn" || stopReason === "stop_sequence") {
270
+ // Extract text from assistant message
271
+ const textContent = bedrockAssistantMessage.content
272
+ .filter((item) => item.text)
273
+ .map((item) => item.text)
274
+ .join(" ");
275
+ return { shouldContinue: false, text: textContent };
276
+ }
277
+ else if (stopReason === "tool_use") {
278
+ logger.debug(`[AmazonBedrockProvider] Tool use detected - executing tools immediately`);
279
+ // Execute all tool uses in the message
280
+ const toolResults = [];
281
+ for (const contentItem of bedrockAssistantMessage.content) {
282
+ if (contentItem.toolUse) {
283
+ logger.debug(`[AmazonBedrockProvider] Executing tool: ${contentItem.toolUse.name}`);
284
+ try {
285
+ // Execute tool using BaseProvider's tool execution
286
+ logger.debug(`[AmazonBedrockProvider] Debug toolUse.input:`, JSON.stringify(contentItem.toolUse.input, null, 2));
287
+ const toolResult = await this.executeSingleTool(contentItem.toolUse.name, contentItem.toolUse.input || {}, contentItem.toolUse.toolUseId);
288
+ logger.debug(`[AmazonBedrockProvider] Tool execution successful: ${contentItem.toolUse.name}`);
289
+ toolResults.push({
290
+ toolResult: {
291
+ toolUseId: contentItem.toolUse.toolUseId,
292
+ content: [{ text: String(toolResult) }],
293
+ status: "success",
294
+ },
225
295
  });
226
- }),
227
- ]);
228
- clearTimeout(timeoutId);
229
- return {
230
- credentialsValid: credentialResult.isValid,
231
- bedrockAccessible: connectivityResult.bedrockAccessible,
232
- credentialSource: credentialResult.credentialSource,
233
- error: credentialResult.error || connectivityResult.error,
234
- responseTime: Date.now() - startTime,
296
+ }
297
+ catch (error) {
298
+ logger.error(`[AmazonBedrockProvider] Tool execution failed: ${contentItem.toolUse.name}`, error);
299
+ const errorMessage = error instanceof Error ? error.message : String(error);
300
+ // Still create toolResult for failed tools to maintain 1:1 mapping with toolUse blocks
301
+ toolResults.push({
302
+ toolResult: {
303
+ toolUseId: contentItem.toolUse.toolUseId,
304
+ content: [
305
+ {
306
+ text: `Error executing tool ${contentItem.toolUse.name}: ${errorMessage}`,
307
+ },
308
+ ],
309
+ status: "error",
310
+ },
311
+ });
312
+ }
313
+ }
314
+ }
315
+ // Add tool results as user message
316
+ if (toolResults.length > 0) {
317
+ const userMessageWithToolResults = {
318
+ role: "user",
319
+ content: toolResults,
235
320
  };
321
+ this.conversationHistory.push(userMessageWithToolResults);
322
+ logger.debug(`[AmazonBedrockProvider] Added ${toolResults.length} tool results to conversation`);
236
323
  }
237
- catch (timeoutError) {
238
- clearTimeout(timeoutId);
239
- throw timeoutError;
324
+ return { shouldContinue: true };
325
+ }
326
+ else if (stopReason === "max_tokens") {
327
+ // Handle max tokens by continuing conversation
328
+ const userMessage = {
329
+ role: "user",
330
+ content: [{ text: "Please continue." }],
331
+ };
332
+ this.conversationHistory.push(userMessage);
333
+ return { shouldContinue: true };
334
+ }
335
+ else {
336
+ logger.warn(`[AmazonBedrockProvider] Unrecognized stop reason "${stopReason}", ending conversation.`);
337
+ return { shouldContinue: false, text: "" };
338
+ }
339
+ }
340
+ convertToAWSMessages(bedrockMessages) {
341
+ return bedrockMessages.map((msg) => ({
342
+ role: msg.role,
343
+ content: msg.content.map((item) => {
344
+ if (item.text) {
345
+ return {
346
+ text: item.text,
347
+ };
348
+ }
349
+ if (item.toolUse) {
350
+ return {
351
+ toolUse: {
352
+ toolUseId: item.toolUse.toolUseId,
353
+ name: item.toolUse.name,
354
+ input: item.toolUse.input,
355
+ },
356
+ };
357
+ }
358
+ if (item.toolResult) {
359
+ return {
360
+ toolResult: {
361
+ toolUseId: item.toolResult.toolUseId,
362
+ content: item.toolResult.content,
363
+ status: item.toolResult.status,
364
+ },
365
+ };
366
+ }
367
+ return { text: "" };
368
+ }),
369
+ }));
370
+ }
371
+ async executeSingleTool(toolName, args, _toolUseId) {
372
+ logger.debug(`[AmazonBedrockProvider] Executing single tool: ${toolName}`, {
373
+ args,
374
+ });
375
+ try {
376
+ // Use BaseProvider's tool execution mechanism
377
+ const aiTools = await this.getAllTools();
378
+ const tools = this.convertAISDKToolsToToolDefinitions(aiTools);
379
+ if (!tools[toolName]) {
380
+ throw new Error(`Tool not found: ${toolName}`);
381
+ }
382
+ const tool = tools[toolName];
383
+ if (!tool || !tool.execute) {
384
+ throw new Error(`Tool ${toolName} does not have execute method`);
385
+ }
386
+ // Apply robust parameter handling like Bedrock-MCP-Connector
387
+ // Bedrock toolUse.input already contains the correct parameter structure
388
+ const toolInput = args || {};
389
+ // Add default parameters for common tools that Claude might call without required params
390
+ if (toolName === "list_directory" && !toolInput.path) {
391
+ toolInput.path = ".";
392
+ logger.debug(`[AmazonBedrockProvider] Added default path '.' for list_directory tool`);
393
+ }
394
+ logger.debug(`[AmazonBedrockProvider] Tool input parameters:`, toolInput);
395
+ // Convert Record<string, unknown> to ToolArgs by filtering out non-JsonValue types
396
+ const toolArgs = {};
397
+ for (const [key, value] of Object.entries(toolInput)) {
398
+ // Only include values that are JsonValue compatible
399
+ if (value === null ||
400
+ typeof value === "string" ||
401
+ typeof value === "number" ||
402
+ typeof value === "boolean" ||
403
+ (typeof value === "object" && value !== null)) {
404
+ toolArgs[key] = value;
405
+ }
406
+ }
407
+ const result = await tool.execute(toolArgs);
408
+ logger.debug(`[AmazonBedrockProvider] Tool execution result:`, {
409
+ toolName,
410
+ result,
411
+ });
412
+ // Handle ToolResult type
413
+ if (result && typeof result === "object" && "success" in result) {
414
+ if (result.success && result.data !== undefined) {
415
+ if (typeof result.data === "string") {
416
+ return result.data;
417
+ }
418
+ else if (typeof result.data === "object") {
419
+ return JSON.stringify(result.data, null, 2);
420
+ }
421
+ else {
422
+ return String(result.data);
423
+ }
424
+ }
425
+ else if (result.error) {
426
+ throw new Error(result.error.message || "Tool execution failed");
427
+ }
428
+ }
429
+ // Fallback for non-ToolResult return types
430
+ if (typeof result === "string") {
431
+ return result;
432
+ }
433
+ else if (typeof result === "object") {
434
+ return JSON.stringify(result, null, 2);
435
+ }
436
+ else {
437
+ return String(result);
240
438
  }
241
439
  }
242
440
  catch (error) {
243
- const errorMessage = error instanceof Error ? error.message : String(error);
441
+ logger.error(`[AmazonBedrockProvider] Tool execution error:`, {
442
+ toolName,
443
+ error,
444
+ });
445
+ throw error;
446
+ }
447
+ }
448
+ convertAISDKToolsToToolDefinitions(aiTools) {
449
+ const result = {};
450
+ for (const [name, tool] of Object.entries(aiTools)) {
451
+ if ("description" in tool && tool.description) {
452
+ result[name] = {
453
+ description: tool.description,
454
+ parameters: "parameters" in tool ? tool.parameters : undefined,
455
+ execute: async (params) => {
456
+ if ("execute" in tool && tool.execute) {
457
+ const result = await tool.execute(params, {
458
+ toolCallId: `tool_${Date.now()}`,
459
+ messages: [],
460
+ });
461
+ return {
462
+ success: true,
463
+ data: result,
464
+ };
465
+ }
466
+ throw new Error(`Tool ${name} has no execute method`);
467
+ },
468
+ };
469
+ }
470
+ }
471
+ return result;
472
+ }
473
+ formatToolsForBedrock(tools) {
474
+ if (!tools || Object.keys(tools).length === 0) {
475
+ return null;
476
+ }
477
+ const bedrockTools = Object.entries(tools).map(([name, tool]) => {
478
+ // Handle Zod schema or plain object schema
479
+ let schema;
480
+ if (tool.parameters && typeof tool.parameters === "object") {
481
+ // Check if it's a Zod schema
482
+ if ("_def" in tool.parameters) {
483
+ // It's a Zod schema, convert to JSON schema
484
+ schema = zodToJsonSchema(tool.parameters);
485
+ }
486
+ else {
487
+ // It's already a plain object schema
488
+ schema = tool.parameters;
489
+ }
490
+ }
491
+ else {
492
+ schema = {
493
+ type: "object",
494
+ properties: {},
495
+ required: [],
496
+ };
497
+ }
498
+ // Ensure the schema always has type: "object" at the root level
499
+ if (!schema.type || schema.type !== "object") {
500
+ schema = {
501
+ type: "object",
502
+ properties: schema.properties || {},
503
+ required: schema.required || [],
504
+ };
505
+ }
506
+ const toolSpec = {
507
+ name,
508
+ description: tool.description,
509
+ inputSchema: {
510
+ json: schema,
511
+ },
512
+ };
244
513
  return {
245
- credentialsValid: false,
246
- bedrockAccessible: false,
247
- credentialSource: "unknown",
248
- error: errorMessage,
249
- responseTime: Date.now() - startTime,
514
+ toolSpec,
515
+ };
516
+ });
517
+ logger.debug(`[AmazonBedrockProvider] Formatted ${bedrockTools.length} tools for Bedrock`);
518
+ return { tools: bedrockTools };
519
+ }
520
+ // Bedrock-MCP-Connector compatibility
521
+ getBedrockClient() {
522
+ return this.bedrockClient;
523
+ }
524
+ async executeStream(options) {
525
+ logger.debug("🟢 [TRACE] executeStream ENTRY - starting streaming attempt");
526
+ logger.info("šŸš€ [AmazonBedrockProvider] Attempting real streaming with ConverseStreamCommand");
527
+ try {
528
+ logger.debug("🟢 [TRACE] executeStream TRY block - about to call streamingConversationLoop");
529
+ // CRITICAL FIX: Initialize conversation history like generate() does
530
+ // Clear conversation history for new streaming session
531
+ this.conversationHistory = [];
532
+ // Add user message to conversation - exactly like generate() does
533
+ const userMessage = {
534
+ role: "user",
535
+ content: [{ text: options.input.text }],
250
536
  };
537
+ this.conversationHistory.push(userMessage);
538
+ logger.debug(`[AmazonBedrockProvider] Starting streaming conversation with prompt: ${options.input.text}`);
539
+ // Call the actual streaming implementation that already exists
540
+ logger.debug("🟢 [TRACE] executeStream - calling streamingConversationLoop NOW");
541
+ const result = await this.streamingConversationLoop(options);
542
+ logger.debug("🟢 [TRACE] executeStream - streamingConversationLoop SUCCESS, returning result");
543
+ return result;
544
+ }
545
+ catch (error) {
546
+ logger.debug("šŸ”“ [TRACE] executeStream CATCH - error caught from streamingConversationLoop");
547
+ const errorObj = error;
548
+ // Check if error is related to streaming permissions
549
+ const isPermissionError = errorObj?.name ===
550
+ "AccessDeniedException" ||
551
+ errorObj?.name ===
552
+ "UnauthorizedOperation" ||
553
+ errorObj?.message?.includes("bedrock:InvokeModelWithResponseStream") ||
554
+ errorObj?.message?.includes("streaming") ||
555
+ errorObj?.message?.includes("ConverseStream");
556
+ logger.debug("šŸ”“ [TRACE] executeStream CATCH - checking if permission error");
557
+ logger.debug(`šŸ”“ [TRACE] executeStream CATCH - isPermissionError=${isPermissionError}`);
558
+ if (isPermissionError) {
559
+ logger.debug("🟔 [TRACE] executeStream CATCH - PERMISSION ERROR DETECTED, starting fallback");
560
+ logger.warn(`[AmazonBedrockProvider] Streaming permissions not available, falling back to generate method: ${errorObj.message}`);
561
+ // Fallback to generate method and convert to streaming format
562
+ const generateResult = await this.generate({
563
+ prompt: options.input.text,
564
+ });
565
+ if (!generateResult) {
566
+ throw new Error("Generate method returned null result");
567
+ }
568
+ // Convert generate result to streaming format
569
+ const stream = new ReadableStream({
570
+ start(controller) {
571
+ // Split the response into chunks for pseudo-streaming
572
+ const responseText = generateResult.content || "";
573
+ const chunks = responseText.split(" ");
574
+ chunks.forEach((word, _index) => {
575
+ controller.enqueue({ content: word + " " });
576
+ });
577
+ controller.enqueue({ content: "" });
578
+ controller.close();
579
+ },
580
+ });
581
+ // Convert ReadableStream to AsyncIterable like streamingConversationLoop does
582
+ const asyncIterable = {
583
+ async *[Symbol.asyncIterator]() {
584
+ const reader = stream.getReader();
585
+ try {
586
+ while (true) {
587
+ const { done, value } = await reader.read();
588
+ if (done) {
589
+ break;
590
+ }
591
+ yield value;
592
+ }
593
+ }
594
+ finally {
595
+ reader.releaseLock();
596
+ }
597
+ },
598
+ };
599
+ return {
600
+ stream: asyncIterable,
601
+ usage: { total: 0, input: 0, output: 0 },
602
+ model: this.modelName || this.getDefaultModel(),
603
+ provider: this.getProviderName(),
604
+ metadata: {
605
+ fallback: true,
606
+ },
607
+ };
608
+ }
609
+ // Re-throw non-permission errors
610
+ throw error;
251
611
  }
252
612
  }
253
- // executeGenerate removed - BaseProvider handles all generation with tools
254
- async executeStream(options, _analysisSchema) {
613
+ async streamingConversationLoop(options) {
614
+ logger.debug("🟦 [TRACE] streamingConversationLoop ENTRY");
615
+ const maxIterations = 10;
616
+ let iteration = 0;
617
+ // The REAL issue: ReadableStream errors don't bubble up to the caller
618
+ // So we need to make the first streaming call synchronously to test permissions
255
619
  try {
256
- this.validateStreamOptions(options);
257
- const timeout = this.getTimeout(options);
258
- const timeoutController = createTimeoutController(timeout, this.providerName, "stream");
259
- // Get tools consistently with generate method (now supports streaming with tools)
260
- const shouldUseTools = !options.disableTools && this.supportsTools();
261
- const tools = shouldUseTools ? await this.getAllTools() : {};
262
- // Build message array from options
263
- const messages = buildMessagesArray(options);
264
- const result = streamText({
265
- model: this.model,
266
- messages: messages,
267
- tools,
268
- maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
269
- toolChoice: shouldUseTools ? "auto" : "none",
270
- maxTokens: options.maxTokens || DEFAULT_MAX_TOKENS,
271
- temperature: options.temperature,
272
- abortSignal: timeoutController?.controller.signal,
273
- });
274
- const streamResult = {
275
- stream: (async function* (self) {
276
- let chunkCount = 0;
277
- let streamStarted = false;
278
- let timeoutId = null;
620
+ logger.debug("🟦 [TRACE] streamingConversationLoop - testing first streaming call");
621
+ const commandInput = await this.prepareStreamCommand(options);
622
+ const command = new ConverseStreamCommand(commandInput);
623
+ const response = await this.bedrockClient.send(command);
624
+ logger.debug("🟦 [TRACE] streamingConversationLoop - first streaming call SUCCESS");
625
+ // Process the first response immediately to avoid waste
626
+ const stream = new ReadableStream({
627
+ start: async (controller) => {
628
+ logger.debug("🟦 [TRACE] streamingConversationLoop - ReadableStream start() called");
279
629
  try {
280
- // Create timeout promise for first chunk with proper cleanup
281
- const timeoutPromise = new Promise((_, reject) => {
282
- timeoutId = setTimeout(() => {
283
- if (!streamStarted && chunkCount === 0) {
284
- reject(new Error("āŒ Amazon Bedrock Streaming Timeout\n\n" +
285
- "Stream failed to produce any content within 5 seconds.\n\n" +
286
- "šŸ”§ Common Causes:\n" +
287
- "1. Expired AWS credentials - run: aws sts get-caller-identity\n" +
288
- "2. Missing Bedrock permissions - need: bedrock:InvokeModelWithResponseStream\n" +
289
- "3. Model not available in your region\n" +
290
- "4. Network connectivity issues\n\n" +
291
- 'šŸ’” Try: neurolink generate "test" --provider bedrock\n' +
292
- " (Generate mode provides more detailed error messages)"));
630
+ // Process the first response we already have
631
+ if (response.stream) {
632
+ for await (const chunk of response.stream) {
633
+ if (chunk.contentBlockDelta?.delta?.text) {
634
+ controller.enqueue({
635
+ content: chunk.contentBlockDelta.delta.text,
636
+ });
637
+ }
638
+ if (chunk.messageStop) {
639
+ controller.close();
640
+ return;
293
641
  }
294
- }, 5000);
295
- });
296
- // Process stream with timeout handling
297
- const streamIterator = result.textStream[Symbol.asyncIterator]();
298
- let timeoutActive = true;
299
- while (true) {
300
- let nextResult;
301
- if (timeoutActive) {
302
- // Race between next chunk and timeout for first chunk only
303
- nextResult = await Promise.race([
304
- streamIterator.next(),
305
- timeoutPromise,
306
- ]);
307
- }
308
- else {
309
- // No timeout for subsequent chunks
310
- nextResult = await streamIterator.next();
311
642
  }
312
- if (nextResult.done) {
643
+ }
644
+ // Continue with normal iterations if needed
645
+ while (iteration < maxIterations) {
646
+ iteration++;
647
+ logger.debug(`[AmazonBedrockProvider] Streaming iteration ${iteration}`);
648
+ const commandInput = await this.prepareStreamCommand(options);
649
+ const { stopReason, assistantMessage } = await this.processStreamResponse(commandInput, controller);
650
+ const shouldContinue = await this.handleStreamStopReason(stopReason, assistantMessage, controller);
651
+ if (!shouldContinue) {
313
652
  break;
314
653
  }
315
- if (!streamStarted) {
316
- streamStarted = true;
317
- timeoutActive = false;
318
- // Clear the timeout now that we have content
319
- if (timeoutId) {
320
- clearTimeout(timeoutId);
321
- timeoutId = null;
322
- }
323
- }
324
- chunkCount++;
325
- yield { content: nextResult.value };
326
654
  }
327
- // If no chunks received, likely an authentication error
328
- if (chunkCount === 0) {
329
- throw new Error("āŒ Amazon Bedrock Streaming Error\n\n" +
330
- "Stream completed with no content.\n\n" +
331
- "šŸ”§ Most Likely Causes:\n" +
332
- "1. AWS credentials are expired or invalid\n" +
333
- "2. Insufficient Bedrock permissions\n" +
334
- "3. Model access not enabled in AWS console\n" +
335
- "4. Region mismatch\n\n" +
336
- "šŸ” Debug Steps:\n" +
337
- "1. Check credentials: aws sts get-caller-identity\n" +
338
- '2. Test generate mode: neurolink generate "test" --provider bedrock\n' +
339
- '3. Verify region: AWS_REGION=us-east-1 neurolink stream "test" --provider bedrock');
655
+ if (iteration >= maxIterations) {
656
+ controller.error(new Error("Streaming conversation exceeded maximum iterations"));
340
657
  }
341
658
  }
342
659
  catch (error) {
343
- // Clean up timeout on error
344
- if (timeoutId) {
345
- clearTimeout(timeoutId);
346
- }
347
- throw self.handleStreamError
348
- ? self.handleStreamError(error)
349
- : error;
660
+ logger.debug("šŸ”“ [TRACE] streamingConversationLoop - CATCH block hit in ReadableStream");
661
+ controller.error(error);
350
662
  }
351
- })(this),
352
- provider: this.providerName,
353
- model: this.modelName,
663
+ },
664
+ });
665
+ return {
666
+ stream: this.convertToAsyncIterable(stream),
667
+ usage: { total: 0, input: 0, output: 0 },
668
+ model: this.modelName || this.getDefaultModel(),
669
+ provider: this.getProviderName(),
354
670
  };
355
- timeoutController?.cleanup();
356
- return streamResult;
357
671
  }
358
672
  catch (error) {
359
- throw this.handleProviderError(error);
673
+ logger.debug("šŸ”“ [TRACE] streamingConversationLoop - first streaming call FAILED, throwing");
674
+ throw error; // This will be caught by executeStream
360
675
  }
361
676
  }
362
- handleStreamError(error) {
363
- const errorMessage = error instanceof Error ? error.message : String(error);
364
- // Stream-specific error handling
365
- if (errorMessage.includes("no content") ||
366
- errorMessage.includes("Streaming Timeout") ||
367
- errorMessage.includes("Stream failed")) {
368
- return new Error(errorMessage); // Already formatted in stream logic
369
- }
370
- // For other errors, use standard provider error handling
371
- return this.handleProviderError(error);
677
+ convertToAsyncIterable(stream) {
678
+ return {
679
+ async *[Symbol.asyncIterator]() {
680
+ const reader = stream.getReader();
681
+ try {
682
+ while (true) {
683
+ const { done, value } = await reader.read();
684
+ if (done) {
685
+ break;
686
+ }
687
+ yield value;
688
+ }
689
+ }
690
+ finally {
691
+ reader.releaseLock();
692
+ }
693
+ },
694
+ };
372
695
  }
373
- handleProviderError(error) {
374
- if (error instanceof Error && error.name === "TimeoutError") {
375
- return new TimeoutError(`Amazon Bedrock request timed out. Consider increasing timeout or using a lighter model.`, this.defaultTimeout);
696
+ async prepareStreamCommand(options) {
697
+ // CRITICAL DEBUG: Log conversation history before conversion
698
+ logger.info(`šŸ” [AmazonBedrockProvider] BEFORE conversion - conversationHistory length: ${this.conversationHistory.length}`);
699
+ this.conversationHistory.forEach((msg, index) => {
700
+ logger.info(`šŸ” [AmazonBedrockProvider] Message ${index}: role=${msg.role}, content=${JSON.stringify(msg.content)}`);
701
+ });
702
+ // Get all available tools
703
+ const aiTools = await this.getAllTools();
704
+ const allTools = this.convertAISDKToolsToToolDefinitions(aiTools);
705
+ const toolConfig = this.formatToolsForBedrock(allTools);
706
+ const convertedMessages = this.convertToAWSMessages(this.conversationHistory);
707
+ logger.info(`šŸ” [AmazonBedrockProvider] AFTER conversion - messages length: ${convertedMessages.length}`);
708
+ convertedMessages.forEach((msg, index) => {
709
+ logger.info(`šŸ” [AmazonBedrockProvider] Converted Message ${index}: role=${msg.role}, content=${JSON.stringify(msg.content)}`);
710
+ });
711
+ const commandInput = {
712
+ modelId: this.modelName || this.getDefaultModel(),
713
+ messages: convertedMessages,
714
+ system: [
715
+ {
716
+ text: options.systemPrompt ||
717
+ "You are a helpful assistant with access to external tools. Use tools when necessary to provide accurate information.",
718
+ },
719
+ ],
720
+ inferenceConfig: {
721
+ maxTokens: options.maxTokens || 4096,
722
+ temperature: options.temperature || 0.7,
723
+ },
724
+ };
725
+ if (toolConfig) {
726
+ commandInput.toolConfig = toolConfig;
727
+ }
728
+ logger.debug(`[AmazonBedrockProvider] Calling Bedrock streaming with ${this.conversationHistory.length} messages`);
729
+ // DEBUG: Log exact conversation structure being sent to Bedrock
730
+ logger.debug(`[AmazonBedrockProvider] DEBUG - Conversation structure:`);
731
+ this.conversationHistory.forEach((msg, index) => {
732
+ logger.debug(` Message ${index} (${msg.role}): ${msg.content.length} content items`);
733
+ msg.content.forEach((item, itemIndex) => {
734
+ const keys = Object.keys(item);
735
+ logger.debug(` Content ${itemIndex}: ${keys.join(", ")}`);
736
+ });
737
+ });
738
+ return commandInput;
739
+ }
740
+ async processStreamResponse(commandInput, controller) {
741
+ const command = new ConverseStreamCommand(commandInput);
742
+ const response = await this.bedrockClient.send(command);
743
+ if (!response.stream) {
744
+ throw new Error("No stream returned from Bedrock");
745
+ }
746
+ const currentMessageContent = [];
747
+ let stopReason = "";
748
+ let currentText = "";
749
+ // Process streaming chunks
750
+ for await (const chunk of response.stream) {
751
+ if (chunk.contentBlockStart) {
752
+ // Starting a new content block
753
+ currentMessageContent.push({});
754
+ }
755
+ if (chunk.contentBlockDelta?.delta?.text) {
756
+ // Text delta - stream it to user
757
+ const textDelta = chunk.contentBlockDelta.delta.text;
758
+ currentText += textDelta;
759
+ controller.enqueue({
760
+ content: textDelta,
761
+ });
762
+ }
763
+ if (chunk.contentBlockStart?.start?.toolUse) {
764
+ // Tool use block starting - initialize tool information
765
+ const currentBlock = currentMessageContent[currentMessageContent.length - 1];
766
+ currentBlock.toolUse = {
767
+ name: chunk.contentBlockStart.start.toolUse.name || "",
768
+ input: {}, // Initialize empty - will be populated by delta chunks
769
+ toolUseId: chunk.contentBlockStart.start.toolUse.toolUseId ||
770
+ `tool_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
771
+ };
772
+ }
773
+ if (chunk.contentBlockDelta?.delta?.toolUse) {
774
+ // Tool use delta - accumulate tool information
775
+ const currentBlock = currentMessageContent[currentMessageContent.length - 1];
776
+ if (!currentBlock.toolUse) {
777
+ currentBlock.toolUse = {
778
+ name: "",
779
+ input: {},
780
+ toolUseId: `tool_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
781
+ };
782
+ }
783
+ // Use robust parameter merging like Bedrock-MCP-Connector
784
+ if (chunk.contentBlockDelta.delta.toolUse.input) {
785
+ // Merge parameters more robustly to avoid missing required parameters
786
+ const deltaInput = chunk.contentBlockDelta.delta.toolUse.input;
787
+ if (typeof deltaInput === "string") {
788
+ currentBlock.toolUse.input = { value: deltaInput };
789
+ }
790
+ else if (deltaInput &&
791
+ typeof deltaInput === "object" &&
792
+ !Array.isArray(deltaInput)) {
793
+ // Ensure both objects are properly typed before spreading
794
+ const currentInput = currentBlock.toolUse.input || {};
795
+ const newInput = deltaInput;
796
+ currentBlock.toolUse.input = {
797
+ ...currentInput,
798
+ ...newInput,
799
+ };
800
+ }
801
+ }
802
+ }
803
+ if (chunk.contentBlockStop) {
804
+ // Content block completed
805
+ const currentBlock = currentMessageContent[currentMessageContent.length - 1];
806
+ if (currentText && currentBlock && !currentBlock.toolUse) {
807
+ // Only add text to blocks that don't have toolUse
808
+ currentBlock.text = currentText;
809
+ }
810
+ currentText = "";
811
+ }
812
+ if (chunk.messageStop) {
813
+ stopReason = chunk.messageStop.stopReason || "end_turn";
814
+ break;
815
+ }
816
+ }
817
+ // Add assistant message to conversation history
818
+ const assistantMessage = {
819
+ role: "assistant",
820
+ content: currentMessageContent,
821
+ };
822
+ this.conversationHistory.push(assistantMessage);
823
+ return { stopReason, assistantMessage };
824
+ }
825
+ async handleStreamStopReason(stopReason, assistantMessage, controller) {
826
+ if (stopReason === "end_turn" || stopReason === "stop_sequence") {
827
+ // Conversation completed
828
+ controller.close();
829
+ return false;
830
+ }
831
+ else if (stopReason === "tool_use") {
832
+ logger.debug(`šŸ› ļø [AmazonBedrockProvider] Tool use detected in streaming - executing tools`);
833
+ await this.executeStreamTools(assistantMessage.content);
834
+ return true; // Continue conversation loop
376
835
  }
377
- const errorMessage = error instanceof Error ? error.message : String(error);
378
- if (errorMessage.includes("InvalidRequestException")) {
379
- return new Error(`āŒ Amazon Bedrock Request Error\n\nThe request was invalid: ${errorMessage}\n\nšŸ”§ Common Solutions:\n1. Check your model ID format\n2. Verify your request parameters\n3. Ensure your AWS account has Bedrock access`);
836
+ else if (stopReason === "max_tokens") {
837
+ // Handle max tokens by continuing conversation
838
+ const userMessage = {
839
+ role: "user",
840
+ content: [{ text: "Please continue." }],
841
+ };
842
+ this.conversationHistory.push(userMessage);
843
+ return true; // Continue conversation loop
844
+ }
845
+ else {
846
+ // Unknown stop reason - end conversation
847
+ controller.close();
848
+ return false;
849
+ }
850
+ }
851
+ async executeStreamTools(messageContent) {
852
+ // Execute all tool uses in the message - ensure 1:1 mapping like Bedrock-MCP-Connector
853
+ const toolResults = [];
854
+ let toolUseCount = 0;
855
+ // Count toolUse blocks first to ensure 1:1 mapping
856
+ for (const contentItem of messageContent) {
857
+ if (contentItem.toolUse) {
858
+ toolUseCount++;
859
+ }
380
860
  }
381
- if (errorMessage.includes("AccessDeniedException")) {
382
- return new Error(`āŒ Amazon Bedrock Access Denied\n\nYour AWS credentials don't have permission to access Bedrock.\n\nšŸ”§ Required Steps:\n1. Ensure your IAM user has bedrock:InvokeModel permission\n2. Check if Bedrock is available in your region\n3. Verify model access is enabled in Bedrock console`);
861
+ logger.debug(`šŸ” [AmazonBedrockProvider] Found ${toolUseCount} toolUse blocks in assistant message`);
862
+ for (const contentItem of messageContent) {
863
+ if (contentItem.toolUse) {
864
+ logger.debug(`šŸ”§ [AmazonBedrockProvider] Executing tool: ${contentItem.toolUse.name}`);
865
+ try {
866
+ const toolResult = await this.executeSingleTool(contentItem.toolUse.name, contentItem.toolUse.input || {}, contentItem.toolUse.toolUseId);
867
+ logger.debug(`āœ… [AmazonBedrockProvider] Tool execution successful: ${contentItem.toolUse.name}`);
868
+ // Ensure exact structure matching Bedrock-MCP-Connector
869
+ toolResults.push({
870
+ toolResult: {
871
+ toolUseId: contentItem.toolUse.toolUseId,
872
+ content: [{ text: String(toolResult) }],
873
+ status: "success",
874
+ },
875
+ });
876
+ }
877
+ catch (error) {
878
+ logger.error(`āŒ [AmazonBedrockProvider] Tool execution failed: ${contentItem.toolUse.name}`, error);
879
+ const errorMessage = error instanceof Error ? error.message : String(error);
880
+ toolResults.push({
881
+ toolResult: {
882
+ toolUseId: contentItem.toolUse.toolUseId,
883
+ content: [
884
+ {
885
+ text: `Error executing tool ${contentItem.toolUse.name}: ${errorMessage}`,
886
+ },
887
+ ],
888
+ status: "error",
889
+ },
890
+ });
891
+ }
892
+ }
893
+ }
894
+ logger.debug(`šŸ“Š [AmazonBedrockProvider] Created ${toolResults.length} toolResult blocks for ${toolUseCount} toolUse blocks`);
895
+ // Validate 1:1 mapping before adding to conversation
896
+ if (toolResults.length !== toolUseCount) {
897
+ logger.error(`āŒ [AmazonBedrockProvider] Mismatch: ${toolResults.length} toolResults vs ${toolUseCount} toolUse blocks`);
898
+ throw new Error(`Tool mapping mismatch: ${toolResults.length} toolResults for ${toolUseCount} toolUse blocks`);
899
+ }
900
+ // Add tool results as user message - exact structure like Bedrock-MCP-Connector
901
+ if (toolResults.length > 0) {
902
+ const userMessageWithToolResults = {
903
+ role: "user",
904
+ content: toolResults,
905
+ };
906
+ this.conversationHistory.push(userMessageWithToolResults);
907
+ logger.debug(`šŸ“¤ [AmazonBedrockProvider] Added ${toolResults.length} tool results to conversation (1:1 mapping validated)`);
908
+ }
909
+ }
910
+ /**
911
+ * Health check for Amazon Bedrock service
912
+ * Uses ListFoundationModels API to validate connectivity and permissions
913
+ */
914
+ async checkBedrockHealth() {
915
+ const controller = new AbortController();
916
+ const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
917
+ // Create a separate BedrockClient for health checks (not BedrockRuntimeClient)
918
+ // Use simple configuration like working example - no custom proxy handler
919
+ const healthCheckClient = new BedrockClient({
920
+ region: process.env.AWS_REGION || "us-east-1",
921
+ });
922
+ try {
923
+ logger.debug("šŸ” [AmazonBedrockProvider] Starting health check...");
924
+ const command = new ListFoundationModelsCommand({});
925
+ const response = await healthCheckClient.send(command, {
926
+ abortSignal: controller.signal,
927
+ });
928
+ const models = response.modelSummaries || [];
929
+ const activeModels = models.filter((model) => model.modelLifecycle?.status === "ACTIVE");
930
+ logger.debug(`āœ… [AmazonBedrockProvider] Health check passed - Found ${activeModels.length} active models out of ${models.length} total models`);
931
+ if (activeModels.length === 0) {
932
+ throw new Error("No active foundation models available in the region");
933
+ }
934
+ }
935
+ catch (error) {
936
+ clearTimeout(timeoutId);
937
+ const errorObj = error;
938
+ if (errorObj.name === "AbortError") {
939
+ throw new Error("Bedrock health check timed out after 10 seconds");
940
+ }
941
+ const errorMessage = typeof errorObj.message === "string" ? errorObj.message : "";
942
+ if (errorMessage.includes("UnauthorizedOperation") ||
943
+ errorMessage.includes("AccessDenied")) {
944
+ throw new Error("Bedrock access denied. Check your AWS credentials and IAM permissions for bedrock:ListFoundationModels");
945
+ }
946
+ if (errorObj.code === "ECONNREFUSED" || errorObj.code === "ENOTFOUND") {
947
+ throw new Error("Unable to connect to Bedrock service. Check your network connectivity and AWS region configuration");
948
+ }
949
+ logger.error("āŒ [AmazonBedrockProvider] Health check failed:", error);
950
+ throw new Error(`Bedrock health check failed: ${errorMessage || "Unknown error"}`);
951
+ }
952
+ finally {
953
+ clearTimeout(timeoutId);
954
+ try {
955
+ healthCheckClient.destroy();
956
+ }
957
+ catch {
958
+ // Ignore destroy errors during cleanup
959
+ }
960
+ }
961
+ }
962
+ handleProviderError(error) {
963
+ // Handle AWS SDK specific errors
964
+ const message = error instanceof Error ? error.message : String(error);
965
+ if (message.includes("AccessDeniedException")) {
966
+ return new Error("AWS Bedrock access denied. Check your credentials and permissions.");
383
967
  }
384
- if (errorMessage.includes("ValidationException")) {
385
- return new Error(`āŒ Amazon Bedrock Validation Error\n\n${errorMessage}\n\nšŸ”§ Check:\n1. Model ID format (should be ARN or model identifier)\n2. Request parameters are within limits\n3. Region configuration is correct`);
968
+ if (message.includes("ValidationException")) {
969
+ return new Error(`AWS Bedrock validation error: ${message}`);
386
970
  }
387
- return new Error(`āŒ Amazon Bedrock Provider Error\n\n${errorMessage || "Unknown error occurred"}\n\nšŸ”§ Troubleshooting:\n1. Check AWS credentials and permissions\n2. Verify model availability\n3. Check network connectivity`);
971
+ return new Error(`AWS Bedrock error: ${message}`);
388
972
  }
389
973
  }
390
- export default AmazonBedrockProvider;