@juspay/neurolink 7.30.0 ā 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.
- package/CHANGELOG.md +6 -0
- package/dist/core/conversationMemoryManager.js +15 -9
- package/dist/factories/providerRegistry.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/lib/core/conversationMemoryManager.js +15 -9
- package/dist/lib/factories/providerRegistry.js +1 -1
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +2 -0
- package/dist/lib/providers/amazonBedrock.d.ts +28 -59
- package/dist/lib/providers/amazonBedrock.js +913 -330
- package/dist/lib/utils/conversationMemoryUtils.js +1 -1
- package/dist/lib/utils/logger.d.ts +164 -4
- package/dist/lib/utils/logger.js +163 -10
- package/dist/lib/utils/providerUtils.js +9 -6
- package/dist/providers/amazonBedrock.d.ts +28 -59
- package/dist/providers/amazonBedrock.js +913 -330
- package/dist/utils/conversationMemoryUtils.js +1 -1
- package/dist/utils/logger.d.ts +164 -4
- package/dist/utils/logger.js +163 -10
- package/dist/utils/providerUtils.js +9 -6
- package/package.json +2 -3
- package/dist/lib/providers/aws/credentialProvider.d.ts +0 -58
- package/dist/lib/providers/aws/credentialProvider.js +0 -267
- package/dist/lib/providers/aws/credentialTester.d.ts +0 -49
- package/dist/lib/providers/aws/credentialTester.js +0 -394
- package/dist/providers/aws/credentialProvider.d.ts +0 -58
- package/dist/providers/aws/credentialProvider.js +0 -267
- package/dist/providers/aws/credentialTester.d.ts +0 -49
- package/dist/providers/aws/credentialTester.js +0 -394
|
@@ -1,390 +1,973 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
constructor(modelName, credentialConfig, neurolink) {
|
|
8
|
+
conversationHistory = [];
|
|
9
|
+
constructor(modelName, neurolink) {
|
|
50
10
|
super(modelName, "bedrock", neurolink);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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(
|
|
97
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
197
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
254
|
-
|
|
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
|
-
|
|
257
|
-
const
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
//
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
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
|
-
|
|
328
|
-
|
|
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
|
-
|
|
344
|
-
|
|
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
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
673
|
+
logger.debug("š“ [TRACE] streamingConversationLoop - first streaming call FAILED, throwing");
|
|
674
|
+
throw error; // This will be caught by executeStream
|
|
360
675
|
}
|
|
361
676
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
382
|
-
|
|
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 (
|
|
385
|
-
return new Error(
|
|
968
|
+
if (message.includes("ValidationException")) {
|
|
969
|
+
return new Error(`AWS Bedrock validation error: ${message}`);
|
|
386
970
|
}
|
|
387
|
-
return new Error(
|
|
971
|
+
return new Error(`AWS Bedrock error: ${message}`);
|
|
388
972
|
}
|
|
389
973
|
}
|
|
390
|
-
export default AmazonBedrockProvider;
|