From 2fbbcbdd4056d6db01c22dfe8592142c03597575 Mon Sep 17 00:00:00 2001 From: Sachin Sharma Date: Mon, 1 Sep 2025 00:30:38 +0530 Subject: [PATCH] fix(bedrock): migrate from ai-sdk to native AWS SDK implementation - Replace @ai-sdk/amazon-bedrock with direct @aws-sdk/client-bedrock-runtime integration - Remove custom AWS credential provider and authentication logic - Delete redundant AWS credential testing and authentication modules - Implement native Bedrock Converse API with streaming support and permission fallback - Add comprehensive error handling and logging for AWS SDK operations - Remove obsolete MCP connector analysis documentation files - Simplify provider architecture by using AWS SDK's built-in credential handling --- BEDROCK_MCP_CONNECTOR_COMPLETE_ANALYSIS.md | 2931 ----------------- COMPREHENSIVE_COMPATIBILITY_MATRIX.md | 196 -- NEUROLINK_BEDROCK_COMPATIBILITY_ANALYSIS.md | 2050 ------------ docs/CONTEXT-SUMMARIZATION.md | 1 + docs/DYNAMIC-MODELS.md | 2 + docs/MCP-CONFIGURATION-LOCATIONS.md | 2 + docs/PERFORMANCE-OPTIMIZATION.md | 2 + docs/TESTING.md | 3 + docs/TROUBLESHOOTING.md | 1 + docs/advanced/dynamic-models.md | 8 +- .../MASTER_NEUROLINK_COMPLETE_ANALYSIS.md | 4 + docs/analysis/VERIFICATION_RESULTS.md | 17 + docs/demos/interactive.md | 8 + docs/demos/screenshots.md | 4 + .../cli-factory-impact-assessment.md | 8 + docs/development/package-overrides.md | 2 + docs/development/testing.md | 3 + docs/getting-started/environment-variables.md | 5 + docs/reference/troubleshooting.md | 2 + .../phase-1-2-completion-report.md | 3 + ...al-content-documentation-update-summary.md | 1 + docs/tracking/CLI_OPTIMIZATION_TRACKING.md | 2 + docs/tracking/IMMEDIATE_WORK_PLAN.md | 8 + .../phase-1-2-visual-content-achievement.md | 6 + .../phase-1-2-workflow-tools-plan.md | 4 + examples/sagemaker/README.md | 3 + neurolink-demo/package.json | 1 - package.json | 3 +- pnpm-lock.yaml | 140 +- src/lib/core/conversationMemoryManager.ts | 81 +- src/lib/core/types.ts | 5 +- src/lib/factories/providerRegistry.ts | 1 - src/lib/index.ts | 4 + src/lib/providers/amazonBedrock.ts | 1688 +++++++--- src/lib/providers/aws/credentialProvider.ts | 303 -- src/lib/providers/aws/credentialTester.ts | 554 ---- src/lib/types/generateTypes.ts | 5 +- src/lib/utils/conversationMemoryUtils.ts | 5 +- src/lib/utils/logger.ts | 4 +- src/lib/utils/providerUtils.ts | 19 +- test/providers/aws/authentication.test.ts | 411 --- test/providers/aws/credentialSources.test.ts | 433 --- tools/testing/providerValidator.js | 1 - 43 files changed, 1465 insertions(+), 7469 deletions(-) delete mode 100644 BEDROCK_MCP_CONNECTOR_COMPLETE_ANALYSIS.md delete mode 100644 COMPREHENSIVE_COMPATIBILITY_MATRIX.md delete mode 100644 NEUROLINK_BEDROCK_COMPATIBILITY_ANALYSIS.md delete mode 100644 src/lib/providers/aws/credentialProvider.ts delete mode 100644 src/lib/providers/aws/credentialTester.ts delete mode 100644 test/providers/aws/authentication.test.ts delete mode 100644 test/providers/aws/credentialSources.test.ts diff --git a/BEDROCK_MCP_CONNECTOR_COMPLETE_ANALYSIS.md b/BEDROCK_MCP_CONNECTOR_COMPLETE_ANALYSIS.md deleted file mode 100644 index c8f5fa9f6..000000000 --- a/BEDROCK_MCP_CONNECTOR_COMPLETE_ANALYSIS.md +++ /dev/null @@ -1,2931 +0,0 @@ -# BEDROCK-MCP-CONNECTOR: COMPLETE IN-DEPTH ANALYSIS - -## 100% Feature Replication Blueprint - ---- - -## EXECUTIVE SUMMARY - -This document provides an exhaustive, line-by-line analysis of the Bedrock-MCP-Connector library (`@juspay/bedrock-mcp-connector` v1.1.0) to enable 100% feature-complete replacement implementation. Every component, configuration, behavior, and implementation detail has been documented to ensure perfect replication. - -**Project Overview:** - -- **Name**: @juspay/bedrock-mcp-connector -- **Version**: 1.1.0 -- **Type**: ES Module TypeScript Library -- **Purpose**: AWS Bedrock + MCP Server Integration Client -- **Architecture**: Event-driven, modular, storage-agnostic - ---- - -## SECTION 1: PROJECT ARCHITECTURE DEEP DIVE - -### 1.1 Directory Structure Analysis - -The project follows a strict modular architecture with specific file organization: - -``` -ROOT/ -├── package.json # Primary package configuration -├── package-lock.json # NPM dependency lock (exact versions) -├── pnpm-lock.yaml # PNPM lock file (package manager preference) -├── tsconfig.json # TypeScript compilation configuration -├── README.md # User documentation -├── TESTING.md # Testing instructions and setup -├── test-package.js # Integration test for package functionality -├── test-storage.js # Storage system validation test -└── src/ # Source code directory - ├── index.ts # Main library entry point - ALL exports - ├── bin.ts # CLI executable entry point - ├── types.ts # Core type definitions and interfaces - ├── cli/ - │ └── index.ts # Complete CLI implementation - ├── client/ - │ ├── index.ts # Client module exports - │ └── BedrockMCPClient.ts # Main client class implementation - ├── core/ - │ ├── index.ts # Core module exports - │ ├── ConverseAgent.ts # AWS Bedrock integration agent - │ └── ToolManager.ts # Tool registration and execution - ├── storage/ - │ ├── index.ts # Storage module exports - │ ├── types.ts # Storage-specific type definitions - │ ├── MessageStorage.ts # Abstract storage interface - │ ├── InMemoryMessageStorage.ts # In-memory storage implementation - │ └── RedisMessageStorage.ts # Redis storage implementation - ├── utils/ - │ ├── index.ts # Utility module exports - │ └── logging.ts # Logging system implementation - └── examples/ - ├── basic.ts # Basic usage demonstration - └── interact.js # Interactive example (JavaScript) -``` - -### 1.2 Module Dependency Graph - -The library follows a hierarchical dependency structure: - -``` -index.ts (ROOT) -├── client/BedrockMCPClient.ts (MAIN CLIENT) -│ ├── core/ConverseAgent.ts (AWS INTEGRATION) -│ │ ├── @aws-sdk/client-bedrock-runtime -│ │ ├── utils/logging.ts -│ │ ├── storage/MessageStorage.ts -│ │ └── core/ToolManager.ts -│ ├── core/ToolManager.ts (TOOL MANAGEMENT) -│ │ ├── utils/logging.ts -│ │ └── types.ts -│ ├── storage/InMemoryMessageStorage.ts -│ ├── storage/RedisMessageStorage.ts -│ │ ├── redis (external dependency) -│ │ └── utils/logging.ts -│ ├── utils/logging.ts -│ ├── events (Node.js built-in) -│ └── mcp-client (external dependency) -├── cli/index.ts (CLI INTERFACE) -│ ├── readline (Node.js built-in) -│ ├── client/BedrockMCPClient.ts -│ └── utils/logging.ts -└── types.ts (TYPE DEFINITIONS) -``` - -### 1.3 Build and Distribution Strategy - -**TypeScript Configuration:** - -- Target: ES2020 -- Module: NodeNext (modern ESM) -- Output: dist/ directory -- Declaration files: Generated for TypeScript consumers -- Strict mode: Enabled - -**Package Distribution:** - -- Type: "module" (pure ESM) -- Main entry: dist/index.js -- Types entry: dist/index.d.ts -- Binary: dist/bin.js -- Included files: dist/, README.md only - ---- - -## SECTION 2: DEPENDENCY ANALYSIS & EXTERNAL INTEGRATIONS - -### 2.1 Production Dependencies - -#### @aws-sdk/client-bedrock-runtime (^3.0.0) - -**Purpose**: Official AWS SDK for Bedrock Runtime API -**Usage**: Direct integration for Converse API calls -**Key Components Used**: - -- `BedrockRuntimeClient`: Main service client -- `ConverseCommand`: Command for conversation API - **Configuration**: Region-based initialization only - **Authentication**: Uses AWS SDK default credential chain - -#### events (^3.3.0) - -**Purpose**: Node.js EventEmitter for client events -**Usage**: Event-driven architecture implementation -**Key Components Used**: - -- `EventEmitter`: Base class for typed event emission - **Events Emitted**: message, error, tool:start, tool:end, response:start/chunk/end, connected, disconnected - -#### mcp-client (^1.12.0) - -**Purpose**: Model Context Protocol client implementation -**Usage**: Connection to MCP servers for tool discovery -**Key Components Used**: - -- `MCPClient`: Main client for MCP server communication - **Connection Type**: Server-Sent Events (SSE) - **Features**: Tool discovery, tool execution, connection management - -#### redis (^5.8.2) - -**Purpose**: Redis client for persistent storage -**Usage**: Optional storage backend for conversation history -**Key Components Used**: - -- `createClient`: Redis client factory -- `RedisClientType`: TypeScript types - **Features**: Connection management, TTL support, health checks - -### 2.2 Development Dependencies - -#### typescript (^5.8.2) - -**Purpose**: TypeScript compiler and type checking -**Configuration**: Strict mode, ES2020 target, NodeNext modules - -#### @types/node (^22.13.13) - -**Purpose**: Node.js type definitions -**Usage**: Readline, process, buffer type support - -#### @types/events (^3.0.3) - -**Purpose**: Events module type definitions -**Usage**: Enhanced EventEmitter typing - -### 2.3 Node.js Version Requirements - -**Minimum**: Node.js 18.0.0 -**Reason**: ES modules, modern async/await, recent Node.js APIs -**Package Manager**: PNPM 10.0.0+ (preferred, but npm compatible) - ---- - -## SECTION 3: TYPE SYSTEM COMPREHENSIVE SPECIFICATION - -### 3.1 Core Configuration Types (src/types.ts) - -```typescript -export interface BedrockMCPClientConfig { - /** REQUIRED: AWS Bedrock model ID */ - modelId: string; - - /** OPTIONAL: AWS region (default: 'us-east-1') */ - region?: string; - - /** OPTIONAL: System prompt for model context */ - systemPrompt?: string; - - /** OPTIONAL: MCP server URL for tool integration */ - mcpServerUrl?: string; - - /** OPTIONAL: Client identification name */ - clientName?: string; - - /** OPTIONAL: Client version string */ - clientVersion?: string; - - /** OPTIONAL: Max tokens per response (default: 2000) */ - maxTokens?: number; - - /** OPTIONAL: Temperature 0.0-1.0 (default: 0.7) */ - temperature?: number; - - /** OPTIONAL: Response extraction tags [start, end] */ - responseOutputTags?: [string, string]; - - /** OPTIONAL: Storage configuration (default: in-memory) */ - storage?: StorageConfig; - - /** OPTIONAL: Session ID (auto-generated if not provided) */ - sessionId?: string; - - /** OPTIONAL: User ID for multi-user scenarios */ - userId?: string; -} -``` - -### 3.2 Message Content Type System - -The library uses a sophisticated union type system for message content: - -```typescript -// Base text content -export interface TextContent { - text: string; -} - -// Tool use request content -export interface ToolUseContent { - toolUse: { - toolUseId: string; // Unique identifier for this tool use - name: string; // Tool name to execute - input?: Record; // Tool parameters - }; -} - -// Tool execution result content -export interface ToolResultContent { - toolResult: { - toolUseId: string; // Matches the toolUse ID - content: Array<{ text: string }>; // Tool output - status: "success" | "error"; // Execution status - }; -} - -// Union type for all content types -export type MessageContent = TextContent | ToolUseContent | ToolResultContent; - -// Complete message structure -export interface Message { - role: "user" | "assistant" | "system"; - content: MessageContent[]; // Array of content items -} -``` - -### 3.3 Tool System Type Definitions - -```typescript -// Tool specification for Bedrock API -export interface ToolSpec { - name: string; - description?: string; - inputSchema?: { - json: Record; // JSON Schema for tool input - }; -} - -// Tool configuration for Bedrock API -export interface ToolConfig { - tools: Array<{ - toolSpec: ToolSpec; - }>; -} - -// Tool execution request -export interface ToolRequest { - toolUseId: string; - name: string; - input?: Record; -} - -// Tool execution response -export interface ToolResponse { - toolUseId: string; - content: Array<{ text: string }>; - status: "success" | "error"; -} - -// Tool handler function signature -export type ToolHandler = ( - name: string, - input: Record, -) => Promise; -``` - -### 3.4 Event System Type Definitions - -```typescript -export interface BedrockMCPClientEvents { - message: (message: string) => void; - error: (error: Error) => void; - "tool:start": (toolName: string, input: Record) => void; - "tool:end": (toolName: string, result: any) => void; - "response:start": () => void; - "response:chunk": (chunk: string) => void; - "response:end": (fullResponse: string) => void; - connected: () => void; - disconnected: () => void; -} - -// Typed EventEmitter with method overrides -export type BedrockMCPClientEmitter = EventEmitter & { - on( - event: E, - listener: BedrockMCPClientEvents[E], - ): BedrockMCPClientEmitter; - - emit( - event: E, - ...args: Parameters - ): boolean; -}; -``` - -### 3.5 Storage System Type Definitions - -```typescript -// Redis storage configuration -export interface RedisStorageConfig { - host?: string; // Default: 'localhost' - port?: number; // Default: 6379 - password?: string; // Optional authentication - db?: number; // Default: 0 - keyPrefix?: string; // Default: 'bedrock-mcp:conversation:' - ttl?: number; // Default: 86400 (24 hours) - connectionOptions?: { - // Redis client options - connectTimeout?: number; - lazyConnect?: boolean; - retryDelayOnFailover?: number; - maxRetriesPerRequest?: number; - [key: string]: any; - }; -} - -// Storage configuration union type -export type StorageConfig = - | { type: "memory" } - | { type: "redis"; config: RedisStorageConfig }; - -// Session identification -export interface SessionIdentifier { - sessionId: string; // Required: Unique session ID - userId?: string; // Optional: User identification -} -``` - ---- - -## SECTION 4: CORE COMPONENT IMPLEMENTATION DETAILS - -### 4.1 BedrockMCPClient - Main Client Class - -**Location**: `src/client/BedrockMCPClient.ts` - -#### 4.1.1 Class Properties and State Management - -```typescript -export class BedrockMCPClient { - // Core components - private agent: ConverseAgent; // AWS Bedrock integration - private toolManager: ToolManager; // Tool registration/execution - private messageStorage: MessageStorage; // Conversation persistence - - // MCP integration - private mcpClient: MCPClient | null = null; // MCP server connection - private mcpServerUrl: string | null = null; // MCP server endpoint - private isConnected: boolean = false; // Connection state - - // Client identification - private clientName: string; // Client name for MCP - private clientVersion: string; // Client version for MCP - - // Event system - private emitter: BedrockMCPClientEmitter; // Typed event emitter -} -``` - -#### 4.1.2 Constructor Implementation Logic - -**CRITICAL**: The constructor follows a specific initialization sequence: - -1. **Tool Manager Initialization** - -```typescript -this.toolManager = new ToolManager(); -``` - -2. **Storage Factory Pattern** - -```typescript -this.messageStorage = this.createStorage(config); - -private createStorage(config: BedrockMCPClientConfig): MessageStorage { - if (!config.storage || config.storage.type === 'memory') { - return new InMemoryMessageStorage(); - } else if (config.storage.type === 'redis') { - return new RedisMessageStorage(config.storage.config); - } else { - throw new Error(`Unsupported storage type: ${(config.storage as any).type}`); - } -} -``` - -3. **Session Management** - -```typescript -const session: SessionIdentifier = { - sessionId: config.sessionId || this.generateSessionId(), - userId: config.userId -}; - -private generateSessionId(): string { - return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; -} -``` - -4. **Agent Configuration** - -```typescript -this.agent = new ConverseAgent(config.modelId, { - region: config.region, - systemPrompt: config.systemPrompt, - toolManager: this.toolManager, - responseOutputTags: config.responseOutputTags, - maxTokens: config.maxTokens, - temperature: config.temperature, - messageStorage: this.messageStorage, - session: session, -}); -``` - -5. **MCP Configuration Storage** - -```typescript -this.mcpServerUrl = config.mcpServerUrl || null; -this.clientName = config.clientName || "BedrockMCPClient"; -this.clientVersion = config.clientVersion || "1.0.0"; -``` - -6. **Event System Setup** - -```typescript -this.emitter = new EventEmitter() as BedrockMCPClientEmitter; -``` - -7. **Storage Initialization** - -```typescript -this.initializeStorage(); // Async initialization - -private async initializeStorage(): Promise { - try { - await this.messageStorage.initialize(); - } catch (error) { - this.emitter.emit("error", new Error(`Failed to initialize storage: ${error instanceof Error ? error.message : String(error)}`)); - } -} -``` - -#### 4.1.3 MCP Connection Implementation - -**CRITICAL**: MCP connection follows exact sequence with specific error handling: - -```typescript -async connect(): Promise { - // Validation - if (!this.mcpServerUrl) { - throw new Error("MCP server URL is required to connect"); - } - - try { - // Event notification - this.emitter.emit("message", "Connecting to MCP server..."); - - // MCP client initialization - this.mcpClient = new MCPClient({ - name: this.clientName, - version: this.clientVersion, - }); - - // Connection with specific type - await this.mcpClient.connect({ - type: "sse", // REQUIRED: Server-Sent Events - url: this.mcpServerUrl - }); - - // Success events - this.emitter.emit("message", "Connected to MCP server"); - this.emitter.emit("connected"); - this.isConnected = true; - - // Tool discovery and registration - await this.registerMCPTools(); - - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - this.emitter.emit("error", new Error(`Error connecting to MCP server: ${errorMessage}`)); - throw error; - } -} -``` - -#### 4.1.4 Tool Registration System - -**Custom Tool Registration with Event Wrapping:** - -```typescript -registerTool( - name: string, - handler: ToolHandler, - description?: string, - inputSchema?: Record -): void { - // Event wrapping for all tool executions - const wrappedHandler: ToolHandler = async (name, input) => { - try { - this.emitter.emit("tool:start", name, input); - const result = await handler(name, input); - this.emitter.emit("tool:end", name, result); - return result; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - this.emitter.emit("error", new Error(`Error executing tool ${name}: ${errorMessage}`)); - throw error; - } - }; - - this.toolManager.registerTool(name, wrappedHandler, description, inputSchema); -} -``` - -**MCP Tool Discovery and Registration:** - -```typescript -private async registerMCPTools(): Promise { - if (!this.mcpClient) return; - - try { - // Tool discovery - const tools = await this.mcpClient.getAllTools(); - - if (tools.length === 0) { - this.emitter.emit("message", "No tools available from MCP server"); - return; - } - - // Register each discovered tool - for (const tool of tools) { - if (!tool.name) { - this.emitter.emit("message", `Skipping tool with missing name: ${JSON.stringify(tool)}`); - continue; - } - - try { - // Create wrapper function for MCP tool calls - const toolFunction: ToolHandler = async (name, input) => { - try { - const formattedInput = input || {}; - const callToolParams = { - name: name, - arguments: formattedInput - }; - - return await this.mcpClient!.callTool(callToolParams); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - this.emitter.emit("error", new Error(`Error calling MCP tool ${name}: ${errorMessage}`)); - throw error; - } - }; - - // Register with default schema if none provided - this.registerTool( - tool.name, - toolFunction, - tool.description || "", - tool.inputSchema || { type: "object", properties: {}, required: [] } - ); - - this.emitter.emit("message", `Registered MCP tool: ${tool.name}`); - - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - this.emitter.emit("error", new Error(`Error registering tool ${tool.name}: ${errorMessage}`)); - } - } - - this.emitter.emit("message", `Registered ${tools.length} MCP tools`); - - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - this.emitter.emit("error", new Error(`Error getting tools from MCP client: ${errorMessage}`)); - } -} -``` - -#### 4.1.5 Conversation Management - -**Prompt Handling with Event Flow:** - -```typescript -async sendPrompt(prompt: string): Promise { - try { - this.emitter.emit("response:start"); - this.emitter.emit("message", `Sending prompt to ${this.agent.constructor.name}...`); - - const response = await this.agent.invokeWithPrompt(prompt); - - this.emitter.emit("response:chunk", response); - this.emitter.emit("response:end", response); - - return response; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - this.emitter.emit("error", new Error(`Error sending prompt: ${errorMessage}`)); - throw error; - } -} -``` - -**Connection Management:** - -```typescript -async disconnect(): Promise { - if (this.mcpClient) { - try { - // Note: MCPClient doesn't have a disconnect method in this version - this.mcpClient = null; - this.emitter.emit("message", "Disconnected from MCP server"); - this.emitter.emit("disconnected"); - this.isConnected = false; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - this.emitter.emit("error", new Error(`Error disconnecting from MCP server: ${errorMessage}`)); - throw error; - } - } - - // Close storage connection - try { - await this.messageStorage.close(); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - this.emitter.emit("error", new Error(`Error closing storage: ${errorMessage}`)); - } -} -``` - -### 4.2 ConverseAgent - AWS Bedrock Integration - -**Location**: `src/core/ConverseAgent.ts` - -#### 4.2.1 Agent Configuration and Initialization - -```typescript -export class ConverseAgent { - // AWS Bedrock configuration - private modelId: string; // Bedrock model identifier - private region: string; // AWS region - private bedrockClient: BedrockRuntimeClient; // AWS SDK client - - // Conversation management - private systemPrompt: string; // Model context prompt - private messageStorage: MessageStorage; // Conversation persistence - private session: SessionIdentifier; // Session tracking - - // Tool integration - private toolManager: ToolManager | null; // Tool execution manager - - // Response configuration - private responseOutputTags: [string, string] | []; // Content extraction tags - private maxTokens: number; // Response length limit - private temperature: number; // Response randomness - - // System management - private logger: Logger; // Logging instance - private accumulatedToolResults: { name: string; text: string }[] = []; // Tool result cache -} -``` - -#### 4.2.2 Default System Prompt (EXACT IMPLEMENTATION REQUIRED) - -**CRITICAL**: The default system prompt must match exactly: - -```typescript -this.systemPrompt = - options.systemPrompt || - `You are a helpful assistant with access to external tools. - - Use available tools **only when necessary** to provide accurate or up-to-date information. - - If a question can be answered based on your knowledge, respond directly **without using tools**. - - If a tool is required: - 1. **Check if all necessary parameters are available.** If they are, use the tool directly. - 2. **If any parameters are missing, do not proceed.** Instead, ask the user for the required information, explaining why it is needed. - 3. **Wait for the user's response before using the tool.** - - If the user asks multiple questions, **handle them one by one**. - - If some questions require tools and others don't, **answer what you can immediately**, then use tools as needed. - - After using a tool, continue answering any remaining questions. - `; -``` - -#### 4.2.3 AWS Bedrock Client Configuration - -```typescript -constructor(modelId: string, options: ConverseAgentOptions = {}) { - this.modelId = modelId; - this.region = options.region || 'us-east-1'; - - // AWS SDK client initialization - NO additional configuration - this.bedrockClient = new BedrockRuntimeClient({ region: this.region }); - - // Default configuration values - this.maxTokens = options.maxTokens || 2000; - this.temperature = options.temperature || 0.7; - this.responseOutputTags = options.responseOutputTags || []; - - // Storage and session setup - this.messageStorage = options.messageStorage || new InMemoryMessageStorage(); - this.session = options.session || { sessionId: this.generateSessionId() }; - - // Component initialization - this.toolManager = options.toolManager || null; - this.logger = createDefaultLogger('ConverseAgent'); - this.logger.setLevel(LogLevel.INFO); -} -``` - -#### 4.2.4 Message Processing Logic (CRITICAL IMPLEMENTATION) - -**Core Invoke Method - Handles Tool Results and User Messages:** - -```typescript -async invoke(content: any[]): Promise { - // Determine message type - const isToolResult = content.length > 0 && content[0].toolResult !== undefined; - - if (!isToolResult) { - // Clear accumulated tool results for new user queries - this.clearAccumulatedToolResults(); - } - - if (isToolResult) { - this.logger.debug("Detected tool result in invoke:", JSON.stringify(content, null, 2)); - - // Find the last assistant message to update with tool results - const messages = await this.messageStorage.getMessages(this.session); - let assistantMessageIndex = -1; - - for (let i = messages.length - 1; i >= 0; i--) { - if (messages[i].role === "assistant") { - assistantMessageIndex = i; - break; - } - } - - if (assistantMessageIndex >= 0) { - // Update existing assistant message with tool results - const updatedMessage: Message = { - role: "assistant", - content: content - }; - await this.messageStorage.updateMessage(this.session, assistantMessageIndex, updatedMessage); - this.logger.debug("Updated assistant message with tool results"); - } else { - // Create new assistant message if none found - const newMessage: Message = { - role: "assistant", - content: content - }; - await this.messageStorage.addMessage(this.session, newMessage); - this.logger.debug("Added new assistant message with tool results"); - } - } else { - // Regular user message - const userMessage: Message = { - role: "user", - content: content, - }; - await this.messageStorage.addMessage(this.session, userMessage); - this.logger.debug("Added user message"); - } - - // Get response from Bedrock and process - const messages = await this.messageStorage.getMessages(this.session); - this.logger.debug("Sending message to model:", JSON.stringify(messages[messages.length - 1], null, 2)); - const response = await this._getConverseResponse(); - return await this._handleResponse(response); -} -``` - -#### 4.2.5 AWS Bedrock API Integration (EXACT IMPLEMENTATION) - -```typescript -private async _getConverseResponse() { - // Get current conversation history - const messages = await this.messageStorage.getMessages(this.session); - - // Build API command input - const commandInput: any = { - modelId: this.modelId, - messages: messages, - system: [{ text: this.systemPrompt }], - inferenceConfig: { - maxTokens: this.maxTokens, - temperature: this.temperature, - }, - }; - - // Add tool configuration if tools are available - if (this.toolManager) { - const toolConfig = this.toolManager.getToolConfig(); - if (toolConfig) { - commandInput.toolConfig = toolConfig; - } - } - - // Debug logging for API calls - this.logger.debug("Full conversation history:", JSON.stringify(messages, null, 2)); - this.logger.debug("CONVERSE API PAYLOAD:", JSON.stringify(commandInput, null, 2)); - - // Message structure debugging - this.logger.debug("Message structure breakdown:"); - messages.forEach((msg: Message, index: number) => { - this.logger.debug(`Message ${index} (${msg.role}):`); - if (Array.isArray(msg.content)) { - msg.content.forEach((contentItem: any, contentIndex: number) => { - this.logger.debug(` Content item ${contentIndex} type: ${Object.keys(contentItem).join(', ')}`); - }); - } else { - this.logger.debug(` Content is not an array: ${typeof msg.content}`); - } - }); - - // Execute API call - const command = new ConverseCommand(commandInput); - return await this.bedrockClient.send(command); -} -``` - -#### 4.2.6 Response Processing (CRITICAL TOOL USE LOGIC) - -```typescript -private async _handleResponse(response: any): Promise { - this.logger.debug("Received response from Bedrock:", JSON.stringify(response, null, 2)); - - // Validate response structure - if (!response.output || !response.output.message) { - this.logger.error("Invalid response structure, missing output.message"); - this.logger.error("Response:", response); - throw new Error("Invalid response structure from Bedrock API"); - } - - this.logger.debug("Message content before pushing to history:", - JSON.stringify(response.output.message.content, null, 2)); - - const stopReason = response.stopReason; - this.logger.debug("Stop reason:", stopReason); - - if (stopReason === 'end_turn' || stopReason === 'stop_sequence') { - // Standard text response handling - await this.messageStorage.addMessage(this.session, response.output.message); - - try { - const message = response.output.message; - const content = message.content; - - if (!content || content.length === 0) { - this.logger.error("No content in message"); - return ''; - } - - this.logger.debug("Content:", JSON.stringify(content, null, 2)); - let text = (content[0] && content[0].text) || ''; - this.logger.debug("Extracted text:", text ? text.substring(0, 100) + "..." : "(empty)"); - - // Apply response output tags if configured - if (this.responseOutputTags.length === 2) { - const [startTag, endTag] = this.responseOutputTags; - const pattern = new RegExp(`${startTag}(.*?)${endTag}`, 's'); - const match = text.match(pattern); - if (match) { - text = match[1]; - } - } - - return text; - } catch (err) { - this.logger.error("Error extracting text from response:", err); - return ''; - } - - } else if (stopReason === 'tool_use') { - // Tool use response handling - if (!this.toolManager) { - throw new Error("Tool use requested but no tool manager is set"); - } - - try { - // Add assistant message with tool use requests - await this.messageStorage.addMessage(this.session, response.output.message); - - // Process each tool use request - const toolResults = []; - let combinedText = ""; - - this.logger.debug("Processing tool use blocks..."); - for (const contentItem of response.output.message.content) { - if (contentItem.toolUse) { - const toolRequest: ToolRequest = { - toolUseId: contentItem.toolUse.toolUseId, - name: contentItem.toolUse.name, - input: contentItem.toolUse.input || {}, - }; - - this.logger.info(`Gathering data using tool: ${toolRequest.name} ...`); - this.logger.debug(`Tool request: ${JSON.stringify(toolRequest, null, 2)}`); - - // Execute tool - const toolResult = await this.toolManager.executeTool(toolRequest); - this.logger.info("Analyzing data ..."); - this.logger.debug(`Tool result: ${JSON.stringify(toolResult, null, 2)}`); - - // Collect tool result - toolResults.push({ - toolResult: { - toolUseId: toolRequest.toolUseId, - content: toolResult.content || [], - status: toolResult.status || 'success' - } - }); - - // Extract text from tool result - if (toolResult && toolResult.content) { - for (const item of toolResult.content) { - if (item.text) { - combinedText += item.text + " "; - } - } - } - } - } - - // Add user message with tool results and get next response - if (toolResults.length > 0) { - const userMessageWithToolResults: Message = { - role: "user", - content: toolResults - }; - await this.messageStorage.addMessage(this.session, userMessageWithToolResults); - - // Recursive call for continued conversation - const nextResponse = await this._getConverseResponse(); - return await this._handleResponse(nextResponse); - } - - return combinedText.trim(); - - } catch (e) { - this.logger.error("Error executing tool:", e); - throw new Error(`Missing required tool use field: ${e instanceof Error ? e.message : String(e)}`); - } - - } else if (stopReason === 'max_tokens') { - // Token limit reached - continue conversation - await this.messageStorage.addMessage(this.session, response.output.message); - return await this.invokeWithPrompt('Please continue.'); - - } else { - throw new Error(`Unknown stop reason: ${stopReason}`); - } -} -``` - -### 4.3 ToolManager - Tool Registration and Execution - -**Location**: `src/core/ToolManager.ts` - -#### 4.3.1 Tool Storage and Management - -```typescript -export class ToolManager { - private tools: Record< - string, - { - handler: ToolHandler; - description?: string; - inputSchema?: Record; - } - > = {}; - private logger: Logger; - - constructor() { - this.logger = createDefaultLogger("ToolManager"); - this.logger.setLevel(LogLevel.INFO); - } -} -``` - -#### 4.3.2 Tool Registration System - -```typescript -registerTool( - name: string, - handler: ToolHandler, - description?: string, - inputSchema?: Record -): void { - this.tools[name] = { handler, description, inputSchema }; -} -``` - -#### 4.3.3 Bedrock Tool Configuration Generation - -```typescript -getToolConfig(): ToolConfig | null { - const toolsList = Object.entries(this.tools).map(([name, tool]) => ({ - toolSpec: { - name, - description: tool.description || "Tool description", - inputSchema: { - json: tool.inputSchema || { type: "object", properties: {}, required: [] } - } - } as ToolSpec - })); - - if (toolsList.length === 0) { - return null; - } - - return { - tools: toolsList - }; -} -``` - -#### 4.3.4 Tool Execution Logic - -```typescript -async executeTool(request: ToolRequest): Promise { - const { toolUseId, name, input } = request; - - if (!this.tools[name]) { - throw new Error(`Unknown tool: ${name}`); - } - - // Input normalization - const toolInput = typeof input === 'string' ? { value: input } : input || {}; - - try { - const result = await this.tools[name].handler(name, toolInput); - - // Handle structured vs. unstructured results - if (result && typeof result === 'object' && result.content) { - return { - toolUseId, - content: result.content, - status: 'success' - }; - } else { - // Convert result to text format - let textResult; - try { - if (typeof result === 'object') { - textResult = JSON.stringify(result); - } else { - textResult = String(result); - } - } catch (e) { - textResult = `Error stringifying result: ${(e as Error).message}`; - } - - return { - toolUseId, - content: [{ text: textResult }], - status: 'success' - }; - } - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - this.logger.error(`Error executing tool ${name}:`, error); - - return { - toolUseId, - content: [{ text: `Error executing tool ${name}: ${errorMessage}` }], - status: 'error' - }; - } -} -``` - ---- - -## SECTION 5: STORAGE SYSTEM DETAILED IMPLEMENTATION - -### 5.1 MessageStorage Interface Specification - -**Location**: `src/storage/MessageStorage.ts` - -```typescript -export interface MessageStorage { - // Connection management - initialize(): Promise; - close(): Promise; - isHealthy(): Promise; - - // Message operations - storeMessages(session: SessionIdentifier, messages: Message[]): Promise; - getMessages(session: SessionIdentifier): Promise; - addMessage(session: SessionIdentifier, message: Message): Promise; - updateMessage( - session: SessionIdentifier, - messageIndex: number, - message: Message, - ): Promise; - clearMessages(session: SessionIdentifier): Promise; - - // Utility operations - getMessageCount(session: SessionIdentifier): Promise; -} -``` - -### 5.2 InMemoryMessageStorage Implementation - -**Location**: `src/storage/InMemoryMessageStorage.ts` - -#### 5.2.1 Storage Structure and Key Generation - -```typescript -export class InMemoryMessageStorage implements MessageStorage { - private messages: Map = new Map(); - - private getStorageKey(session: SessionIdentifier): string { - return session.userId - ? `${session.userId}:${session.sessionId}` - : session.sessionId; - } -} -``` - -#### 5.2.2 Core Operations Implementation - -```typescript -// No-op initialization for in-memory storage -async initialize(): Promise {} -async close(): Promise {} - -// Message storage with array copying for immutability -async storeMessages(session: SessionIdentifier, messages: Message[]): Promise { - const key = this.getStorageKey(session); - this.messages.set(key, [...messages]); -} - -async getMessages(session: SessionIdentifier): Promise { - const key = this.getStorageKey(session); - const messages = this.messages.get(key); - return messages ? [...messages] : []; -} - -// Atomic operations using existing methods -async addMessage(session: SessionIdentifier, message: Message): Promise { - const messages = await this.getMessages(session); - messages.push(message); - await this.storeMessages(session, messages); -} - -async updateMessage(session: SessionIdentifier, messageIndex: number, message: Message): Promise { - const messages = await this.getMessages(session); - - if (messageIndex >= 0 && messageIndex < messages.length) { - messages[messageIndex] = message; - await this.storeMessages(session, messages); - } else { - throw new Error(`Message index ${messageIndex} out of bounds for session ${session.sessionId}`); - } -} - -async clearMessages(session: SessionIdentifier): Promise { - const key = this.getStorageKey(session); - this.messages.delete(key); -} - -async getMessageCount(session: SessionIdentifier): Promise { - const messages = await this.getMessages(session); - return messages.length; -} - -async isHealthy(): Promise { - return true; // Always healthy for in-memory storage -} -``` - -### 5.3 RedisMessageStorage Implementation - -**Location**: `src/storage/RedisMessageStorage.ts` - -#### 5.3.1 Configuration and Connection Management - -```typescript -export class RedisMessageStorage implements MessageStorage { - private client: RedisClientType | null = null; - private config: Required; - private logger: Logger; - private isInitialized: boolean = false; - - constructor(config: RedisStorageConfig = {}) { - // Default configuration with all required fields - this.config = { - host: config.host || "localhost", - port: config.port || 6379, - password: config.password || "", - db: config.db || 0, - keyPrefix: config.keyPrefix || "bedrock-mcp:conversation:", - ttl: config.ttl || 86400, // 24 hours - connectionOptions: config.connectionOptions || {}, - }; - - this.logger = createDefaultLogger("RedisMessageStorage"); - this.logger.setLevel(LogLevel.INFO); - } -} -``` - -#### 5.3.2 Redis Key Management - -```typescript -private getRedisKey(session: SessionIdentifier): string { - const sessionKey = session.userId ? `${session.userId}:${session.sessionId}` : session.sessionId; - return `${this.config.keyPrefix}${sessionKey}`; -} -``` - -#### 5.3.3 Connection Initialization and Management - -```typescript -async initialize(): Promise { - if (this.isInitialized && this.client?.isOpen) { - return; - } - - try { - // Build Redis URL with authentication - const redisUrl = this.config.password - ? `redis://:${this.config.password}@${this.config.host}:${this.config.port}/${this.config.db}` - : `redis://${this.config.host}:${this.config.port}/${this.config.db}`; - - // Create client with configuration - this.client = createClient({ - url: redisUrl, - ...this.config.connectionOptions - }); - - // Event listeners for connection monitoring - this.client.on('error', (err) => { - this.logger.error('Redis client error:', err); - }); - - this.client.on('connect', () => { - this.logger.info('Connected to Redis'); - }); - - this.client.on('disconnect', () => { - this.logger.warn('Disconnected from Redis'); - }); - - await this.client.connect(); - this.isInitialized = true; - this.logger.info(`Redis storage initialized at ${this.config.host}:${this.config.port}`); - - } catch (error) { - this.logger.error('Failed to initialize Redis storage:', error); - throw new Error(`Redis initialization failed: ${error instanceof Error ? error.message : String(error)}`); - } -} - -async close(): Promise { - if (this.client?.isOpen) { - await this.client.disconnect(); - this.logger.info('Redis connection closed'); - } - this.isInitialized = false; -} - -private async ensureConnected(): Promise { - if (!this.isInitialized || !this.client?.isOpen) { - await this.initialize(); - } -} -``` - -#### 5.3.4 Message Operations with TTL Support - -```typescript -async storeMessages(session: SessionIdentifier, messages: Message[]): Promise { - await this.ensureConnected(); - - const key = this.getRedisKey(session); - const serializedMessages = JSON.stringify(messages); - - try { - await this.client!.setEx(key, this.config.ttl, serializedMessages); - this.logger.debug(`Stored ${messages.length} messages for session ${session.sessionId}`); - } catch (error) { - this.logger.error(`Failed to store messages for session ${session.sessionId}:`, error); - throw new Error(`Redis store operation failed: ${error instanceof Error ? error.message : String(error)}`); - } -} - -async getMessages(session: SessionIdentifier): Promise { - await this.ensureConnected(); - - const key = this.getRedisKey(session); - - try { - const serializedMessages = await this.client!.get(key); - - if (!serializedMessages) { - this.logger.debug(`No messages found for session ${session.sessionId}`); - return []; - } - - const messages = JSON.parse(serializedMessages) as Message[]; - this.logger.debug(`Retrieved ${messages.length} messages for session ${session.sessionId}`); - return messages; - } catch (error) { - this.logger.error(`Failed to retrieve messages for session ${session.sessionId}:`, error); - throw new Error(`Redis get operation failed: ${error instanceof Error ? error.message : String(error)}`); - } -} - -// Atomic operations using read-modify-write pattern -async addMessage(session: SessionIdentifier, message: Message): Promise { - const messages = await this.getMessages(session); - messages.push(message); - await this.storeMessages(session, messages); -} - -async updateMessage(session: SessionIdentifier, messageIndex: number, message: Message): Promise { - const messages = await this.getMessages(session); - - if (messageIndex >= 0 && messageIndex < messages.length) { - messages[messageIndex] = message; - await this.storeMessages(session, messages); - } else { - throw new Error(`Message index ${messageIndex} out of bounds for session ${session.sessionId}`); - } -} - -async clearMessages(session: SessionIdentifier): Promise { - await this.ensureConnected(); - - const key = this.getRedisKey(session); - - try { - await this.client!.del(key); - this.logger.debug(`Cleared messages for session ${session.sessionId}`); - } catch (error) { - this.logger.error(`Failed to clear messages for session ${session.sessionId}:`, error); - throw new Error(`Redis delete operation failed: ${error instanceof Error ? error.message : String(error)}`); - } -} - -async isHealthy(): Promise { - try { - await this.ensureConnected(); - await this.client!.ping(); - return true; - } catch (error) { - this.logger.error('Redis health check failed:', error); - return false; - } -} -``` - -#### 5.3.5 Advanced Redis Operations - -```typescript -// TTL management for individual sessions -async setSessionTTL(session: SessionIdentifier, ttlSeconds: number): Promise { - await this.ensureConnected(); - - const key = this.getRedisKey(session); - - try { - await this.client!.expire(key, ttlSeconds); - this.logger.debug(`Set TTL of ${ttlSeconds}s for session ${session.sessionId}`); - } catch (error) { - this.logger.error(`Failed to set TTL for session ${session.sessionId}:`, error); - throw new Error(`Redis expire operation failed: ${error instanceof Error ? error.message : String(error)}`); - } -} - -// Session discovery for management/debugging -async getActiveSessions(): Promise { - await this.ensureConnected(); - - try { - const keys = await this.client!.keys(`${this.config.keyPrefix}*`); - return keys.map(key => key.replace(this.config.keyPrefix, '')); - } catch (error) { - this.logger.error('Failed to get active sessions:', error); - throw new Error(`Redis keys operation failed: ${error instanceof Error ? error.message : String(error)}`); - } -} - -// Redis server information -async getRedisInfo(): Promise { - await this.ensureConnected(); - - try { - return await this.client!.info(); - } catch (error) { - this.logger.error('Failed to get Redis info:', error); - throw new Error(`Redis info operation failed: ${error instanceof Error ? error.message : String(error)}`); - } -} - -// Dynamic log level configuration -setLogLevel(level: LogLevel): void { - this.logger.setLevel(level); -} -``` - ---- - -## SECTION 6: LOGGING SYSTEM IMPLEMENTATION - -### 6.1 LogLevel Enumeration - -**Location**: `src/utils/logging.ts` - -```typescript -export enum LogLevel { - DEBUG = 0, // Detailed debugging information - INFO = 1, // General information messages - WARN = 2, // Warning messages - ERROR = 3, // Error messages - NONE = 4, // No logging -} -``` - -### 6.2 Logger Configuration Interface - -```typescript -export interface LoggerConfig { - level: LogLevel; // Minimum log level to output - prefix?: string; // Optional prefix for all messages - enableTimestamps?: boolean; // Whether to include timestamps -} -``` - -### 6.3 Logger Implementation - -```typescript -export class Logger { - private level: LogLevel; - private prefix: string; - private enableTimestamps: boolean; - - constructor(config: LoggerConfig) { - this.level = config.level; - this.prefix = config.prefix || ""; - this.enableTimestamps = config.enableTimestamps || false; - } - - // Public logging methods - debug(message: string, ...args: any[]): void { - this.log(LogLevel.DEBUG, message, ...args); - } - - info(message: string, ...args: any[]): void { - this.log(LogLevel.INFO, message, ...args); - } - - warn(message: string, ...args: any[]): void { - this.log(LogLevel.WARN, message, ...args); - } - - error(message: string, ...args: any[]): void { - this.log(LogLevel.ERROR, message, ...args); - } - - // Dynamic level configuration - setLevel(level: LogLevel): void { - this.level = level; - } - - // Core logging implementation - private log(level: LogLevel, message: string, ...args: any[]): void { - if (level < this.level) { - return; - } - - let prefix = this.prefix ? `[${this.prefix}] ` : ""; - - if (this.enableTimestamps) { - const timestamp = new Date().toISOString(); - prefix = `[${timestamp}] ${prefix}`; - } - - const levelPrefix = this.getLevelPrefix(level); - const formattedMessage = `${prefix}${levelPrefix}${message}`; - - // Route to appropriate console method - switch (level) { - case LogLevel.DEBUG: - console.debug(formattedMessage, ...args); - break; - case LogLevel.INFO: - console.info(formattedMessage, ...args); - break; - case LogLevel.WARN: - console.warn(formattedMessage, ...args); - break; - case LogLevel.ERROR: - console.error(formattedMessage, ...args); - break; - } - } - - private getLevelPrefix(level: LogLevel): string { - switch (level) { - case LogLevel.DEBUG: - return "[DEBUG] "; - case LogLevel.INFO: - return "[INFO] "; - case LogLevel.WARN: - return "[WARN] "; - case LogLevel.ERROR: - return "[ERROR] "; - default: - return ""; - } - } -} -``` - -### 6.4 Default Logger Factory - -```typescript -export function createDefaultLogger(prefix?: string): Logger { - return new Logger({ - level: LogLevel.INFO, - prefix, - enableTimestamps: true, - }); -} -``` - ---- - -## SECTION 7: CLI IMPLEMENTATION COMPREHENSIVE SPECIFICATION - -### 7.1 CLI Architecture Overview - -**Location**: `src/cli/index.ts` - -The CLI implements a full REPL (Read-Eval-Print Loop) interface with: - -- Command-line argument parsing -- Interactive session management -- Event-driven response handling -- Built-in commands and help system - -### 7.2 CLI Options Interface - -```typescript -interface CLIOptions { - modelId: string; // Required: Bedrock model ID - region?: string; // Optional: AWS region - systemPrompt?: string; // Optional: Custom system prompt - mcpServerUrl?: string; // Optional: MCP server URL - clientName?: string; // Optional: Client identification - clientVersion?: string; // Optional: Client version -} -``` - -### 7.3 Argument Parsing Implementation - -```typescript -function parseArgs(): CLIOptions { - const args = process.argv.slice(2); - const options: CLIOptions = { - modelId: "anthropic.claude-3-sonnet-20240229-v1:0", // Default model - }; - - for (let i = 0; i < args.length; i++) { - const arg = args[i]; - - switch (arg) { - case "--model": - case "-m": - options.modelId = args[++i]; - break; - case "--region": - case "-r": - options.region = args[++i]; - break; - case "--system-prompt": - case "-s": - options.systemPrompt = args[++i]; - break; - case "--mcp-url": - case "-u": - options.mcpServerUrl = args[++i]; - break; - case "--name": - case "-n": - options.clientName = args[++i]; - break; - case "--version": - case "-v": - options.clientVersion = args[++i]; - break; - case "--help": - case "-h": - printHelp(); - process.exit(0); - break; - } - } - - return options; -} -``` - -### 7.4 Help System Implementation - -**EXACT TEXT REQUIRED:** - -```typescript -function printHelp(): void { - console.log(` -Bedrock MCP Connector CLI - -Usage: @juspay/bedrock-mcp-connector [options] - -Options: - -m, --model AWS Bedrock model ID (default: anthropic.claude-3-sonnet-20240229-v1:0) - -r, --region AWS region (default: us-east-1) - -s, --system-prompt System prompt for the model - -u, --mcp-url MCP server URL - -n, --name Client name - -v, --version Client version - -h, --help Show this help message -`); -} -``` - -### 7.5 Main CLI Run Function - -```typescript -export async function runCLI(): Promise { - try { - const options = parseArgs(); - - // Create and configure client - const client = new BedrockMCPClient({ - modelId: options.modelId, - region: options.region, - systemPrompt: options.systemPrompt, - mcpServerUrl: options.mcpServerUrl, - clientName: options.clientName, - clientVersion: options.clientVersion, - }); - - // Set up event listeners - const emitter = client.getEmitter(); - - emitter.on("message", (message) => { - logger.info(message); - }); - - emitter.on("error", (error) => { - logger.error(error.message); - }); - - emitter.on("tool:start", (toolName, input) => { - logger.info(`Executing tool: ${toolName}`); - }); - - emitter.on("tool:end", (toolName, result) => { - logger.info(`Tool ${toolName} execution completed`); - }); - - // Optional MCP connection - if (options.mcpServerUrl) { - try { - await client.connect(); - logger.info(`Connected to MCP server at ${options.mcpServerUrl}`); - - const tools = client.getTools(); - if (tools.length > 0) { - logger.info( - `Available tools: ${tools.map((t) => t.name).join(", ")}`, - ); - } else { - logger.info("No tools available"); - } - } catch (error) { - logger.warn( - `Failed to connect to MCP server: ${error instanceof Error ? error.message : String(error)}`, - ); - logger.info("Continuing without MCP tools"); - } - } - - // Create readline interface - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - const askQuestion = (query: string): Promise => - new Promise((resolve) => rl.question(query, resolve)); - - // Welcome banner (EXACT FORMATTING REQUIRED) - console.log(` -╔════════════════════════════════════════════════════╗ -║ ║ -║ @juspay/Bedrock MCP Connector CLI ║ -║ ║ -╚════════════════════════════════════════════════════╝ - -Model: ${options.modelId} -Type 'quit', 'exit', or 'q' to exit -Type 'help' for available commands -`); - - // Main REPL loop - while (true) { - const userPrompt = await askQuestion("> "); - const command = userPrompt.trim().toLowerCase(); - - // Exit commands - if (["quit", "exit", "q"].includes(command)) { - break; - } - - // Help command - if (command === "help") { - console.log(` -Available commands: - help Show this help message - tools List available tools - clear Clear the conversation history - quit, exit, q Exit the CLI -`); - continue; - } - - // Tools command - if (command === "tools") { - const tools = client.getTools(); - if (tools.length > 0) { - console.log("\nAvailable tools:"); - tools.forEach((tool) => { - console.log( - ` - ${tool.name}${tool.description ? `: ${tool.description}` : ""}`, - ); - }); - console.log(""); - } else { - console.log("\nNo tools available\n"); - } - continue; - } - - // Clear command - if (command === "clear") { - client.clearConversationHistory(); - console.log("\nConversation history cleared\n"); - continue; - } - - // Process user input - if (userPrompt.trim()) { - try { - console.log("\nThinking..."); - const response = await client.sendPrompt(userPrompt); - console.log(`\n${response}\n`); - } catch (error) { - console.error( - `\nError: ${error instanceof Error ? error.message : String(error)}\n`, - ); - } - } - } - - // Cleanup - rl.close(); - if (client.isConnectedToMCP()) { - await client.disconnect(); - } - - console.log("\nGoodbye!\n"); - } catch (error) { - console.error( - `Error: ${error instanceof Error ? error.message : String(error)}`, - ); - process.exit(1); - } -} -``` - -### 7.6 CLI Module Detection for Direct Execution - -```typescript -// ES Module main module detection -const isMainModule = import.meta.url.endsWith( - process.argv[1].replace("file://", ""), -); -if (isMainModule) { - runCLI().catch(console.error); -} -``` - ---- - -## SECTION 8: EXAMPLE IMPLEMENTATIONS - -### 8.1 Basic Example (src/examples/basic.ts) - -**COMPLETE IMPLEMENTATION WITH ALL FEATURES:** - -```typescript -import { BedrockMCPClient, LogLevel, createDefaultLogger } from "../index.js"; - -// Logger setup -const logger = createDefaultLogger("Example"); -logger.setLevel(LogLevel.DEBUG); - -// Configuration -const config = { - modelId: "anthropic.claude-3-sonnet-20240229-v1:0", - region: "us-east-1", - systemPrompt: - "You are a helpful assistant that provides concise and accurate information.", - mcpServerUrl: "http://localhost:5713/sse", // Optional MCP server - clientName: "Example Client", - clientVersion: "1.0.0", -}; - -async function runExample() { - logger.info("Starting example..."); - - // Create client - const client = new BedrockMCPClient(config); - - // Complete event listener setup - const emitter = client.getEmitter(); - - emitter.on("message", (message) => { - logger.info(`Message: ${message}`); - }); - - emitter.on("error", (error) => { - logger.error(`Error: ${error.message}`); - }); - - emitter.on("tool:start", (toolName, input) => { - logger.info( - `Tool started: ${toolName} with input: ${JSON.stringify(input)}`, - ); - }); - - emitter.on("tool:end", (toolName, result) => { - logger.info(`Tool completed: ${toolName}`); - }); - - emitter.on("response:start", () => { - logger.info("Response started"); - }); - - emitter.on("response:chunk", (chunk) => { - logger.debug(`Response chunk: ${chunk.substring(0, 50)}...`); - }); - - emitter.on("response:end", (fullResponse) => { - logger.info("Response completed"); - }); - - try { - // Optional MCP connection - if (config.mcpServerUrl) { - try { - await client.connect(); - logger.info(`Connected to MCP server at ${config.mcpServerUrl}`); - - const tools = client.getTools(); - if (tools.length > 0) { - logger.info( - `Available tools: ${tools.map((t) => t.name).join(", ")}`, - ); - } else { - logger.info("No tools available"); - } - } catch (error) { - logger.warn( - `Failed to connect to MCP server: ${error instanceof Error ? error.message : String(error)}`, - ); - logger.info("Continuing without MCP tools"); - } - } - - // Custom tool registration - client.registerTool( - "getCurrentTime", - async (name, input) => { - const timezone = input.timezone || "UTC"; - const date = new Date().toLocaleString("en-US", { timeZone: timezone }); - return { - content: [{ text: `The current time is ${date} in ${timezone}` }], - }; - }, - "Get the current time in the specified timezone", - { - type: "object", - properties: { - timezone: { - type: "string", - description: - "The timezone to get the time for (e.g., UTC, America/New_York)", - }, - }, - required: [], - }, - ); - - // Send example prompt - logger.info("Sending prompt..."); - const response = await client.sendPrompt( - "What is the capital of France? Also, what time is it now?", - ); - - logger.info("Response:"); - console.log("\n" + response + "\n"); - - // Cleanup - if (client.isConnectedToMCP()) { - await client.disconnect(); - logger.info("Disconnected from MCP server"); - } - } catch (error) { - logger.error( - `Error: ${error instanceof Error ? error.message : String(error)}`, - ); - } -} - -// Execute example -runExample().catch((error) => { - console.error( - `Fatal error: ${error instanceof Error ? error.message : String(error)}`, - ); - process.exit(1); -}); -``` - -### 8.2 Interactive Example (src/examples/interact.js) - -**NOTE**: This file is JavaScript (.js) not TypeScript, demonstrating JavaScript compatibility: - -```javascript -#!/usr/bin/env node - -// Import the BedrockMCPClient -import { BedrockMCPClient, LogLevel } from "../dist/index.js"; - -// Parse command line arguments -function parseArgs() { - const args = {}; - for (let i = 2; i < process.argv.length; i += 2) { - const key = process.argv[i].replace("--", ""); - const value = process.argv[i + 1]; - args[key] = value; - } - return args; -} - -// Print usage information -function printUsage() { - console.log(` -Usage: node interact.js [options] - -Options: - --model Specify the model ID (default: anthropic.claude-3-sonnet-20240229-v1:0) - --region Specify the AWS region (default: us-east-1) - --max-tokens Specify the maximum tokens (default: 2000) - --temperature Specify the temperature (default: 0.7) - --mcp-url Specify the MCP server URL - --help Show this help message -`); -} - -async function main() { - const args = parseArgs(); - - if (args.help) { - printUsage(); - process.exit(0); - } - - // Configuration with environment variable fallbacks - const config = { - modelId: - args.model || - process.env.BEDROCK_MODEL_ID || - "anthropic.claude-3-sonnet-20240229-v1:0", - region: args.region || process.env.AWS_REGION || "us-east-1", - maxTokens: parseInt(args["max-tokens"] || process.env.MAX_TOKENS || "2000"), - temperature: parseFloat( - args.temperature || process.env.TEMPERATURE || "0.7", - ), - mcpServerUrl: args["mcp-url"] || process.env.MCP_SERVER_URL, - clientName: "Interactive Example", - clientVersion: "1.0.0", - }; - - console.log(` -Interactive Bedrock MCP Connector Example -======================================== - • Model: ${config.modelId} - • Region: ${config.region} - • Max Tokens: ${config.maxTokens} - • Temperature: ${config.temperature} - • MCP URL: ${config.mcpServerUrl || "Not configured"} - -Starting interactive session... -`); - - try { - // Create client - const client = new BedrockMCPClient(config); - - // Set debug logging - client.setLogLevel(LogLevel.DEBUG); - - // Set up event listeners - const emitter = client.getEmitter(); - - emitter.on("message", (message) => { - console.log(`[CLIENT] ${message}`); - }); - - emitter.on("error", (error) => { - console.error(`[ERROR] ${error.message}`); - }); - - // Connect to MCP if configured - if (config.mcpServerUrl) { - try { - await client.connect(); - console.log(`[SUCCESS] Connected to MCP server`); - - const tools = client.getTools(); - if (tools.length > 0) { - console.log( - `[TOOLS] Available: ${tools.map((t) => t.name).join(", ")}`, - ); - } - } catch (error) { - console.warn(`[WARNING] MCP connection failed: ${error.message}`); - } - } - - // Register example tools - client.registerTool( - "getSystemInfo", - async (name, input) => { - return { - content: [ - { - text: `System Info: -- Node.js Version: ${process.version} -- Platform: ${process.platform} -- Architecture: ${process.arch} -- Memory Usage: ${JSON.stringify(process.memoryUsage(), null, 2)}`, - }, - ], - }; - }, - "Get system information about the current Node.js process", - ); - - // Interactive loop - const readline = await import("readline"); - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - const question = (prompt) => - new Promise((resolve) => rl.question(prompt, resolve)); - - console.log( - '\nType "exit" to quit, "tools" to list tools, or enter a message:\n', - ); - - while (true) { - const input = await question("> "); - - if (input.toLowerCase().trim() === "exit") { - break; - } - - if (input.toLowerCase().trim() === "tools") { - const tools = client.getTools(); - console.log( - `\nAvailable tools: ${tools.map((t) => t.name).join(", ")}\n`, - ); - continue; - } - - if (input.trim()) { - try { - const response = await client.sendPrompt(input); - console.log(`\n[RESPONSE] ${response}\n`); - } catch (error) { - console.error(`\n[ERROR] ${error.message}\n`); - } - } - } - - rl.close(); - - // Cleanup - if (client.isConnectedToMCP()) { - await client.disconnect(); - } - - console.log("\nGoodbye!"); - } catch (error) { - console.error(`Fatal error: ${error.message}`); - process.exit(1); - } -} - -main(); -``` - ---- - -## SECTION 9: TESTING INFRASTRUCTURE SPECIFICATION - -### 9.1 Testing Documentation (TESTING.md) - -**EXACT CONTENT REQUIRED:** - -````markdown -# Testing Guide - -## Prerequisites - -1. AWS credentials configured with access to AWS Bedrock -2. Node.js 18+ installed -3. Redis server (for Redis storage tests) - -## Running Tests - -### Package Tests - -```bash -node test-package.js -``` -```` - -### Storage Tests - -```bash -node test-storage.js -``` - -### AWS Credentials - -Make sure your AWS credentials are properly configured: - -```bash -# Check if AWS credentials are configured -aws sts get-caller-identity -``` - -The client needs: - -- AWS Access Key ID -- AWS Secret Access Key -- Default region (matching the region in your config) - -## Troubleshooting - -1. **Authentication Error**: Check your AWS credentials -2. **Model Error**: Ensure you have access to the Bedrock model -3. **Region Error**: Make sure the region in your config matches your AWS credentials -4. **Redis Error**: Ensure Redis server is running for storage tests - -```` - -### 9.2 Package Integration Test (test-package.js) - -**COMPLETE IMPLEMENTATION:** - -```javascript -#!/usr/bin/env node - -// Test the package functionality without external dependencies -console.log('Testing @juspay/bedrock-mcp-connector package...\n'); - -async function testPackage() { - try { - // Test 1: Import the package - console.log('1. Testing package imports...'); - const { - BedrockMCPClient, - ConverseAgent, - ToolManager, - Logger, - LogLevel, - createDefaultLogger, - InMemoryMessageStorage - } = await import('./dist/index.js'); - - console.log(' ✓ Package imports successful'); - - // Test 2: Create logger - console.log('2. Testing logger functionality...'); - const logger = createDefaultLogger('Test'); - logger.setLevel(LogLevel.DEBUG); - logger.info('Test log message'); - console.log(' ✓ Logger functionality works'); - - // Test 3: Create in-memory storage - console.log('3. Testing in-memory storage...'); - const storage = new InMemoryMessageStorage(); - await storage.initialize(); - - const session = { sessionId: 'test-session' }; - const testMessage = { - role: 'user', - content: [{ text: 'Hello, world!' }] - }; - - await storage.addMessage(session, testMessage); - const messages = await storage.getMessages(session); - - if (messages.length === 1 && messages[0].content[0].text === 'Hello, world!') { - console.log(' ✓ In-memory storage works'); - } else { - throw new Error('Storage test failed'); - } - - // Test 4: Tool manager - console.log('4. Testing tool manager...'); - const toolManager = new ToolManager(); - - toolManager.registerTool( - 'testTool', - async (name, input) => { - return `Tool ${name} received: ${JSON.stringify(input)}`; - }, - 'A test tool', - { type: 'object', properties: {} } - ); - - const tools = toolManager.getTools(); - if (tools.length === 1 && tools[0].name === 'testTool') { - console.log(' ✓ Tool manager works'); - } else { - throw new Error('Tool manager test failed'); - } - - // Test 5: Client creation (without AWS calls) - console.log('5. Testing client creation...'); - const client = new BedrockMCPClient({ - modelId: 'anthropic.claude-3-sonnet-20240229-v1:0', - region: 'us-east-1', - }); - - // Test event emitter - const emitter = client.getEmitter(); - let eventReceived = false; - emitter.on('message', () => { eventReceived = true; }); - emitter.emit('message', 'test'); - - if (eventReceived) { - console.log(' ✓ Client creation and event system works'); - } else { - throw new Error('Event system test failed'); - } - - // Test 6: Custom tool registration - console.log('6. Testing custom tool registration...'); - client.registerTool( - 'getCurrentTime', - async (name, input) => { - const timezone = input.timezone || 'UTC'; - const date = new Date().toLocaleString('en-US', { timeZone: timezone }); - return { content: [{ text: `The current time is ${date} in ${timezone}` }] }; - }, - 'Get the current time in the specified timezone', - { - type: 'object', - properties: { - timezone: { - type: 'string', - description: 'The timezone to get the time for (e.g., UTC, America/New_York)', - }, - }, - required: [], - } - ); - - const clientTools = client.getTools(); - if (clientTools.some(tool => tool.name === 'getCurrentTime')) { - console.log(' ✓ Custom tool registration works'); - } else { - throw new Error('Tool registration test failed'); - } - - console.log('\n✅ All package tests passed successfully!\n'); - - console.log('Package is ready for use. To test with AWS Bedrock:'); - console.log('1. Configure your AWS credentials'); - console.log('2. Run: node test-storage.js'); - console.log('3. Or try the interactive example: node src/examples/interact.js\n'); - - } catch (error) { - console.error(`\n❌ Package test failed: ${error.message}\n`); - process.exit(1); - } -} - -testPackage(); -```` - -### 9.3 Storage System Test (test-storage.js) - -**COMPLETE IMPLEMENTATION:** - -```javascript -#!/usr/bin/env node - -// Test storage systems and basic AWS connectivity -console.log("Testing Bedrock MCP Connector storage systems...\n"); - -async function testStorage() { - try { - // Import the package - const { - BedrockMCPClient, - InMemoryMessageStorage, - RedisMessageStorage, - LogLevel, - } = await import("./dist/index.js"); - - // Test 1: In-Memory Storage - console.log("1. Testing In-Memory Storage..."); - await testInMemoryStorage(InMemoryMessageStorage); - - // Test 2: Redis Storage (if available) - console.log("2. Testing Redis Storage..."); - await testRedisStorage(RedisMessageStorage); - - // Test 3: Basic client functionality with storage - console.log("3. Testing client with different storage backends..."); - await testClientWithStorage(BedrockMCPClient); - - console.log("\n✅ All storage tests passed!\n"); - } catch (error) { - console.error(`\n❌ Storage test failed: ${error.message}\n`); - process.exit(1); - } -} - -async function testInMemoryStorage(InMemoryMessageStorage) { - const storage = new InMemoryMessageStorage(); - await storage.initialize(); - - const session1 = { sessionId: "session1", userId: "user1" }; - const session2 = { sessionId: "session2", userId: "user2" }; - - // Test basic operations - const message1 = { - role: "user", - content: [{ text: "Hello from session 1" }], - }; - const message2 = { - role: "user", - content: [{ text: "Hello from session 2" }], - }; - - await storage.addMessage(session1, message1); - await storage.addMessage(session2, message2); - - const messages1 = await storage.getMessages(session1); - const messages2 = await storage.getMessages(session2); - - if (messages1.length !== 1 || messages2.length !== 1) { - throw new Error("Session isolation failed"); - } - - // Test message updates - const updatedMessage = { - role: "assistant", - content: [{ text: "Updated message" }], - }; - await storage.updateMessage(session1, 0, updatedMessage); - - const updatedMessages = await storage.getMessages(session1); - if (updatedMessages[0].role !== "assistant") { - throw new Error("Message update failed"); - } - - // Test health check - const isHealthy = await storage.isHealthy(); - if (!isHealthy) { - throw new Error("Health check failed"); - } - - await storage.close(); - console.log(" ✓ In-memory storage passed all tests"); -} - -async function testRedisStorage(RedisMessageStorage) { - try { - const storage = new RedisMessageStorage({ - host: "localhost", - port: 6379, - db: 1, // Use different DB for testing - keyPrefix: "test:bedrock-mcp:", - ttl: 300, // 5 minutes for testing - }); - - await storage.initialize(); - - const session = { sessionId: "redis-test-session" }; - const message = { role: "user", content: [{ text: "Redis test message" }] }; - - await storage.addMessage(session, message); - const messages = await storage.getMessages(session); - - if ( - messages.length !== 1 || - messages[0].content[0].text !== "Redis test message" - ) { - throw new Error("Redis storage operation failed"); - } - - // Test health check - const isHealthy = await storage.isHealthy(); - if (!isHealthy) { - throw new Error("Redis health check failed"); - } - - // Cleanup - await storage.clearMessages(session); - await storage.close(); - - console.log(" ✓ Redis storage passed all tests"); - } catch (error) { - if ( - error.message.includes("Redis") || - error.message.includes("ECONNREFUSED") - ) { - console.log(" ⚠ Redis not available, skipping Redis tests"); - console.log(" To test Redis: Start Redis server and run again"); - } else { - throw error; - } - } -} - -async function testClientWithStorage(BedrockMCPClient) { - // Test with in-memory storage - const client1 = new BedrockMCPClient({ - modelId: "anthropic.claude-3-sonnet-20240229-v1:0", - region: "us-east-1", - storage: { type: "memory" }, - }); - - // Test storage info - const storageInfo = client1.getStorageInfo(); - if (storageInfo.type !== "memory") { - throw new Error("Storage type detection failed"); - } - - // Test with Redis storage (if available) - try { - const client2 = new BedrockMCPClient({ - modelId: "anthropic.claude-3-sonnet-20240229-v1:0", - region: "us-east-1", - storage: { - type: "redis", - config: { - host: "localhost", - port: 6379, - db: 2, - keyPrefix: "test-client:", - }, - }, - }); - - const redisStorageInfo = client2.getStorageInfo(); - if (redisStorageInfo.type !== "redis") { - throw new Error("Redis storage type detection failed"); - } - - console.log(" ✓ Client storage configuration works"); - } catch (error) { - if ( - error.message.includes("Redis") || - error.message.includes("ECONNREFUSED") - ) { - console.log(" ⚠ Redis client test skipped (Redis not available)"); - } else { - throw error; - } - } -} - -// Mock AWS test (without making actual calls) -async function testAWSConnection() { - console.log("4. Testing AWS SDK initialization..."); - - try { - const { BedrockMCPClient } = await import("./dist/index.js"); - - const client = new BedrockMCPClient({ - modelId: "anthropic.claude-3-sonnet-20240229-v1:0", - region: "us-east-1", - }); - - // Test that client was created successfully - const agent = client.getAgent(); - const bedrockClient = agent.getBedrockClient(); - - if ( - bedrockClient && - bedrockClient.constructor.name === "BedrockRuntimeClient" - ) { - console.log(" ✓ AWS Bedrock client initialization successful"); - } else { - throw new Error("Bedrock client not properly initialized"); - } - } catch (error) { - console.log(` ⚠ AWS test skipped: ${error.message}`); - console.log(" This is normal if AWS credentials are not configured"); - } -} - -testStorage().then(() => testAWSConnection()); -``` - ---- - -## SECTION 10: BUILD AND DISTRIBUTION CONFIGURATION - -### 10.1 TypeScript Configuration (tsconfig.json) - -**EXACT CONFIGURATION REQUIRED:** - -```json -{ - "compilerOptions": { - "target": "ES2020", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "skipLibCheck": true, - "declaration": true, - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} -``` - -**Key Configuration Details:** - -- **Target ES2020**: Modern JavaScript features -- **Module NodeNext**: Latest Node.js ES module support -- **Strict Mode**: Full TypeScript strictness -- **Declaration Files**: Generate .d.ts files for TypeScript consumers -- **Output/Input**: dist/ and src/ directories - -### 10.2 Package Configuration (package.json) - -**COMPLETE CONFIGURATION:** - -```json -{ - "name": "@juspay/bedrock-mcp-connector", - "version": "1.1.0", - "description": "A client for interacting with AWS Bedrock and MCP servers", - "type": "module", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "bin": { - "@juspay/bedrock-mcp-connector": "dist/bin.js" - }, - "files": ["dist", "README.md"], - "scripts": { - "build": "tsc", - "start": "node dist/bin.js", - "dev": "tsc --watch", - "prepublishOnly": "npm run build", - "example": "node dist/examples/basic.js" - }, - "keywords": [ - "aws", - "bedrock", - "claude", - "mcp", - "client", - "ai", - "llm", - "tools", - "model-context-protocol" - ], - "author": "Swaroop Varma", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/juspay/Bedrock-MCP-Connector" - }, - "bugs": { - "url": "https://github.com/juspay/Bedrock-MCP-Connector/issues" - }, - "homepage": "https://github.com/juspay/Bedrock-MCP-Connector#readme", - "dependencies": { - "@aws-sdk/client-bedrock-runtime": "^3.0.0", - "events": "^3.3.0", - "mcp-client": "^1.12.0", - "redis": "^5.8.2" - }, - "packageManager": "pnpm@10.0.0+sha512.b8fef5494bd3fe4cbd4edabd0745df2ee5be3e4b0b8b08fa643aa3e4c6702ccc0f00d68fa8a8c9858a735a0032485a44990ed2810526c875e416f001b17df12b", - "devDependencies": { - "@types/events": "^3.0.3", - "@types/node": "^22.13.13", - "typescript": "^5.8.2" - }, - "engines": { - "node": ">=18.0.0" - } -} -``` - -**Critical Package Details:** - -- **Type: "module"**: Pure ES modules -- **Main/Types**: Dual entry points for JS/TS -- **Binary**: CLI executable configuration -- **Files**: Limited distribution (dist + README only) -- **Scripts**: Standard build/dev workflow -- **Package Manager**: PNPM preference indicated - -### 10.3 Build Process - -1. **Development**: `npm run dev` (watch mode) -2. **Production**: `npm run build` (compile to dist/) -3. **Testing**: `node test-package.js` and `node test-storage.js` -4. **Distribution**: `npm publish` (with prepublishOnly hook) - -### 10.4 Module Export Strategy - -Each module has an index.ts file that exports its public API: - -**src/index.ts** (Main entry): - -```typescript -export { BedrockMCPClient } from "./client/index.js"; -export { ConverseAgent, ToolManager } from "./core/index.js"; -export { Logger, LogLevel, createDefaultLogger } from "./utils/index.js"; -export { - MessageStorage, - InMemoryMessageStorage, - RedisMessageStorage, - StorageConfig, - RedisStorageConfig, - SessionIdentifier, -} from "./storage/index.js"; -export { - BedrockMCPClientConfig, - BedrockMCPClientEvents, - BedrockMCPClientEmitter, - Message, - MessageContent, - TextContent, - ToolUseContent, - ToolResultContent, - ToolRequest, - ToolResponse, - ToolHandler, - ToolSpec, - ToolConfig, -} from "./types.js"; -export { runCLI } from "./cli/index.js"; -``` - ---- - -## SECTION 11: CRITICAL IMPLEMENTATION REQUIREMENTS - -### 11.1 Session ID Generation Algorithm - -**EXACT IMPLEMENTATION REQUIRED:** - -```typescript -private generateSessionId(): string { - return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; -} -``` - -**Format**: `session_[timestamp]_[random-9-chars]` -**Example**: `session_1640995200000_k2j3h4g5f` - -### 11.2 Default System Prompt (EXACT TEXT) - -```typescript -const defaultSystemPrompt = `You are a helpful assistant with access to external tools. - - Use available tools **only when necessary** to provide accurate or up-to-date information. - - If a question can be answered based on your knowledge, respond directly **without using tools**. - - If a tool is required: - 1. **Check if all necessary parameters are available.** If they are, use the tool directly. - 2. **If any parameters are missing, do not proceed.** Instead, ask the user for the required information, explaining why it is needed. - 3. **Wait for the user's response before using the tool.** - - If the user asks multiple questions, **handle them one by one**. - - If some questions require tools and others don't, **answer what you can immediately**, then use tools as needed. - - After using a tool, continue answering any remaining questions. - `; -``` - -### 11.3 Redis Key Format - -**Pattern**: `${keyPrefix}${userId}:${sessionId}` or `${keyPrefix}${sessionId}` -**Default Prefix**: `bedrock-mcp:conversation:` -**Examples**: - -- With user: `bedrock-mcp:conversation:user123:session_1640995200000_k2j3h4g5f` -- Without user: `bedrock-mcp:conversation:session_1640995200000_k2j3h4g5f` - -### 11.4 Event Emission Points - -**CRITICAL**: Events must be emitted at exact points in the code: - -1. **MCP Connection Events**: - - `message`: "Connecting to MCP server..." - - `message`: "Connected to MCP server" - - `connected`: (no parameters) - -2. **Tool Execution Events**: - - `tool:start`: (toolName, input) - - `tool:end`: (toolName, result) - -3. **Response Events**: - - `response:start`: (no parameters) - - `response:chunk`: (chunk) - - `response:end`: (fullResponse) - -4. **Error Events**: - - `error`: (Error object) - -### 11.5 CLI Banner Format (EXACT ASCII ART) - -``` -╔════════════════════════════════════════════════════╗ -║ ║ -║ @juspay/Bedrock MCP Connector CLI ║ -║ ║ -╚════════════════════════════════════════════════════╝ -``` - -### 11.6 Default Configuration Values - -**CRITICAL DEFAULTS:** - -```typescript -const DEFAULT_VALUES = { - region: "us-east-1", - maxTokens: 2000, - temperature: 0.7, - clientName: "BedrockMCPClient", - clientVersion: "1.0.0", - logLevel: LogLevel.INFO, - enableTimestamps: true, - redis: { - host: "localhost", - port: 6379, - db: 0, - keyPrefix: "bedrock-mcp:conversation:", - ttl: 86400, // 24 hours - }, -}; -``` - -### 11.7 Tool Use Flow Sequence - -**CRITICAL**: Tool use must follow exact sequence: - -1. **Bedrock returns tool_use stop reason** -2. **Add assistant message with toolUse content to storage** -3. **Execute each tool sequentially** -4. **Collect all tool results** -5. **Add user message with toolResult content to storage** -6. **Call Bedrock API again for continued response** -7. **Process the continued response** - -### 11.8 Error Message Patterns - -**Standard Error Formats:** - -```typescript -// MCP Connection Errors -throw new Error(`Error connecting to MCP server: ${errorMessage}`); - -// Tool Execution Errors -throw new Error(`Error executing tool ${name}: ${errorMessage}`); - -// Storage Errors -throw new Error(`Redis store operation failed: ${errorMessage}`); - -// Validation Errors -throw new Error( - `Message index ${messageIndex} out of bounds for session ${sessionId}`, -); -``` - ---- - -## SECTION 12: IMPLEMENTATION VERIFICATION CHECKLIST - -### 12.1 Core Functionality Verification - -- [ ] **BedrockMCPClient constructor** follows exact initialization sequence -- [ ] **Session ID generation** uses exact algorithm -- [ ] **Storage factory pattern** handles memory/redis configuration correctly -- [ ] **MCP connection** uses SSE type with exact error handling -- [ ] **Tool registration** wraps handlers with event emission -- [ ] **Event system** emits all required events at correct points - -### 12.2 AWS Integration Verification - -- [ ] **BedrockRuntimeClient** initialized with region only -- [ ] **ConverseCommand** built with exact structure -- [ ] **Tool configuration** matches Bedrock API requirements -- [ ] **Response handling** processes all stop reasons correctly -- [ ] **Tool use flow** follows exact sequence -- [ ] **Conversation history** maintained correctly in storage - -### 12.3 Storage System Verification - -- [ ] **MessageStorage interface** implements all required methods -- [ ] **InMemoryMessageStorage** provides session isolation -- [ ] **RedisMessageStorage** handles TTL and connection management -- [ ] **Key generation** follows exact patterns -- [ ] **Error handling** uses standard error formats -- [ ] **Health checks** implemented for both storage types - -### 12.4 CLI Verification - -- [ ] **Argument parsing** handles all command-line options -- [ ] **Help text** matches exact format -- [ ] **Banner** uses exact ASCII art -- [ ] **REPL commands** (help, tools, clear, quit) work correctly -- [ ] **Event listeners** display appropriate messages -- [ ] **Cleanup** properly disconnects and closes resources - -### 12.5 Type System Verification - -- [ ] **All interfaces** match exact specifications -- [ ] **Event emitter typing** provides type safety -- [ ] **Union types** for message content work correctly -- [ ] **Storage configuration** supports memory/redis types -- [ ] **Tool handler signatures** match requirements -- [ ] **Export structure** provides all public APIs - -### 12.6 Build System Verification - -- [ ] **TypeScript configuration** compiles to correct target -- [ ] **Package.json** includes all required fields -- [ ] **Module exports** work correctly as ES modules -- [ ] **CLI binary** executable after build -- [ ] **Declaration files** generated for TypeScript consumers -- [ ] **Distribution files** limited to dist/ and README.md - ---- - -## SECTION 13: CONCLUSION - -This document provides a complete, line-by-line specification for implementing a 100% feature-compatible replacement for the Bedrock-MCP-Connector library. Every critical implementation detail, from session ID generation algorithms to exact error message formats, has been documented. - -The implementation must follow this specification exactly to ensure: - -1. **Perfect API compatibility** -2. **Identical behavior patterns** -3. **Same event emission patterns** -4. **Compatible storage formats** -5. **Matching CLI experience** -6. **Consistent error handling** -7. **Proper AWS integration** -8. **Correct MCP protocol implementation** - -**Key Success Criteria:** - -- All existing code using the original library continues to work unchanged -- CLI behavior is identical to the original -- Storage formats are compatible -- Event sequences match exactly -- Error conditions are handled identically -- Performance characteristics are similar or better - -This specification serves as the complete blueprint for a replacement implementation that will be indistinguishable from the original library in terms of functionality and behavior. diff --git a/COMPREHENSIVE_COMPATIBILITY_MATRIX.md b/COMPREHENSIVE_COMPATIBILITY_MATRIX.md deleted file mode 100644 index 56a873ec5..000000000 --- a/COMPREHENSIVE_COMPATIBILITY_MATRIX.md +++ /dev/null @@ -1,196 +0,0 @@ -# NeuroLink vs Bedrock-MCP-Connector: Comprehensive Functional Compatibility Matrix - -## Executive Summary - -After extensive practical testing, code analysis, and functional verification, **NeuroLink and Bedrock-MCP-Connector are functionally compatible at the application level**. The key insight is that while they use different architectural approaches, both systems achieve similar results through different implementation strategies. - -### Key Finding: Architectural Differences ≠ Functional Incompatibility - -- **NeuroLink**: Uses AI SDK abstraction layer for unified provider access + comprehensive MCP tool integration -- **Bedrock-MCP-Connector**: Uses direct AWS SDK integration + custom message handling + dedicated MCP client - -**Both approaches work effectively for their intended use cases.** - -## Detailed Compatibility Analysis - -### ✅ **FULLY COMPATIBLE AREAS** - -#### 1. **Tool Registration & Execution** - -| Feature | NeuroLink | Bedrock-MCP-Connector | Compatibility | -| ----------------- | ------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------ | -| Tool Registration | `registerTool(name, MCPExecutableTool)` | `registerTool(name, handler, description, schema)` | ✅ **Compatible** - Different APIs, same functionality | -| Tool Execution | Automatic via AI SDK + manual via `executeTool()` | Via ToolManager + ConverseAgent | ✅ **Compatible** - Both support tool calling | -| Error Handling | Circuit breaker + retry + timeout | Error wrapping + logging | ✅ **Compatible** - Both have robust error handling | -| Schema Validation | Zod + JSON Schema support | JSON Schema support | ✅ **Compatible** - Both support parameter validation | - -**Practical Test Results:** - -``` -✅ Tool registration: PASSED -✅ Tool execution: PASSED -✅ Error handling: PASSED -✅ Timeout handling: PASSED -✅ Parameter validation: PASSED -``` - -#### 2. **AWS Bedrock Integration** - -| Feature | NeuroLink | Bedrock-MCP-Connector | Compatibility | -| ---------------- | ---------------------------------- | -------------------------------- | --------------------------------------------------- | -| Authentication | AWS credential chain + env vars | Direct AWS credentials + regions | ✅ **Compatible** - Both support standard AWS auth | -| Model Support | Via @ai-sdk/amazon-bedrock | Direct BedrockRuntimeClient | ✅ **Compatible** - Both access same models | -| Streaming | AI SDK streamText() + tool support | ConverseCommand streaming | ✅ **Compatible** - Both support streaming | -| Tool Integration | AI SDK tools parameter | toolConfig in ConverseCommand | ✅ **Compatible** - Both support tools in streaming | - -**Key Fix Applied:** Added tool support to NeuroLink's Bedrock streaming - now both systems have feature parity. - -#### 3. **Conversation Memory & Session Management** - -| Feature | NeuroLink | Bedrock-MCP-Connector | Compatibility | -| ----------------- | ------------------------------------- | -------------------------------------------- | ------------------------------------------------------ | -| Memory Storage | In-memory ConversationMemoryManager | InMemoryMessageStorage + RedisMessageStorage | ✅ **Compatible** - Both have memory systems | -| Session Isolation | Session ID based | SessionIdentifier based | ✅ **Compatible** - Both isolate conversations | -| History Retrieval | `getConversationHistory(sessionId)` | `getMessages(session)` | ✅ **Compatible** - Different APIs, same functionality | -| Session Clearing | `clearConversationSession(sessionId)` | `clearMessages(session)` | ✅ **Compatible** - Both support cleanup | -| Statistics | `getConversationStats()` | `getMessageCount(session)` | ✅ **Compatible** - Both track usage | - -**Storage Backend Comparison:** - -- **NeuroLink**: In-memory only (runtime persistence) -- **Bedrock-MCP-Connector**: In-memory + Redis (persistent storage options) -- **Compatibility**: ✅ Both work for their intended use cases - -#### 4. **Event System & Observability** - -| Feature | NeuroLink | Bedrock-MCP-Connector | Compatibility | -| ----------------- | -------------------------------------- | ------------------------------------ | -------------------------------------------------- | -| Event Emitter | EventEmitter with 19+ event types | BedrockMCPClientEmitter | ✅ **Compatible** - Both have comprehensive events | -| Tool Events | `tool:start`, `tool:end`, `tool:error` | `tool:start`, `tool:end` via emitter | ✅ **Compatible** - Similar event patterns | -| Generation Events | `generate:start`, `generate:end` | `response:start`, `response:end` | ✅ **Compatible** - Equivalent functionality | -| Error Events | Comprehensive error emission | Error event emission | ✅ **Compatible** - Both emit errors | -| Monitoring | Performance tracking + analytics | Message logging + status tracking | ✅ **Compatible** - Both provide observability | - -**Event Test Results:** - -``` -✅ Tool lifecycle events: PASSED -✅ Error event emission: PASSED -✅ Event listener management: PASSED -✅ 19+ event types available: CONFIRMED -``` - -#### 5. **MCP Integration Patterns** - -| Feature | NeuroLink | Bedrock-MCP-Connector | Compatibility | -| ---------------- | --------------------------------------------- | ------------------------------------- | ------------------------------------------------ | -| MCP Client | ExternalServerManager + MCPClientFactory | MCPClient (direct) | ✅ **Compatible** - Both connect to MCP servers | -| Tool Discovery | ToolDiscoveryService + automatic registration | `getAllTools()` + manual registration | ✅ **Compatible** - Both discover tools | -| External Servers | Full lifecycle management | SSE connection management | ✅ **Compatible** - Both manage external servers | -| Tool Execution | Circuit breaker + retry logic | Direct tool calling | ✅ **Compatible** - Both execute MCP tools | - -### ⚠️ **ARCHITECTURAL DIFFERENCES (Not Compatibility Issues)** - -#### 1. **Message Format Handling** - -- **NeuroLink**: AI SDK abstracts message formats (`generateText()` handles everything) -- **Bedrock-MCP-Connector**: Manual `MessageContent[]` array management -- **Impact**: ✅ **No compatibility issue** - Both achieve same results - -#### 2. **Provider Architecture** - -- **NeuroLink**: Unified provider interface via BaseProvider + AI SDK -- **Bedrock-MCP-Connector**: Direct AWS SDK integration -- **Impact**: ✅ **No compatibility issue** - Different approaches, same outcomes - -#### 3. **Tool Integration Strategy** - -- **NeuroLink**: AI SDK tools parameter (automatic handling) -- **Bedrock-MCP-Connector**: Manual toolConfig management -- **Impact**: ✅ **No compatibility issue** - Both support function calling - -### 🔧 **IMPLEMENTATION DIFFERENCES (Preference-Based)** - -#### Storage Persistence - -- **NeuroLink**: Runtime-only memory (session-based) -- **Bedrock-MCP-Connector**: Optional Redis persistence -- **Recommendation**: Choose based on persistence requirements - -#### API Design Philosophy - -- **NeuroLink**: High-level abstractions (`generateText()`, `stream()`) -- **Bedrock-MCP-Connector**: Lower-level control (`ConverseAgent`, direct AWS) -- **Recommendation**: Choose based on control vs simplicity preference - -#### Error Handling Approach - -- **NeuroLink**: Circuit breaker + comprehensive retry logic -- **Bedrock-MCP-Connector**: Direct error propagation + logging -- **Recommendation**: Both approaches are valid - -## Migration Considerations - -### From Bedrock-MCP-Connector to NeuroLink - -#### ✅ **Easy Migrations:** - -1. **Tool Registration**: Convert ToolHandler to MCPExecutableTool format -2. **Basic Generation**: Replace `agent.invokeWithPrompt()` with `neuroLink.generate()` -3. **Session Management**: Map session concepts to NeuroLink's conversation memory - -#### ⚠️ **Requires Adaptation:** - -1. **Direct AWS SDK Access**: NeuroLink abstracts this - evaluate if direct access is needed -2. **Redis Storage**: NeuroLink uses in-memory - implement custom storage if persistence needed -3. **Fine-grained Message Control**: NeuroLink manages this automatically - -### From NeuroLink to Bedrock-MCP-Connector - -#### ✅ **Easy Migrations:** - -1. **Tool Execution**: Both support similar tool calling patterns -2. **Error Handling**: Both have error management (different approaches) -3. **MCP Integration**: Both support external MCP servers - -#### ⚠️ **Requires Adaptation:** - -1. **Message Format Management**: Need to handle MessageContent[] arrays manually -2. **Provider Abstraction**: Need to work directly with AWS SDK -3. **Multi-provider Support**: Bedrock-MCP-Connector is AWS-specific - -## Final Recommendations - -### Choose NeuroLink When: - -- ✅ You want unified multi-provider AI access (OpenAI, Anthropic, Google, etc.) -- ✅ You prefer high-level abstractions and automatic message handling -- ✅ You need comprehensive event system and observability -- ✅ You want built-in circuit breaker and retry logic -- ✅ Runtime-only conversation memory is sufficient - -### Choose Bedrock-MCP-Connector When: - -- ✅ You're building AWS Bedrock-specific applications -- ✅ You need direct control over AWS SDK interactions -- ✅ You require persistent conversation storage (Redis) -- ✅ You prefer fine-grained control over message formatting -- ✅ You're building infrastructure that needs AWS-native patterns - -### Hybrid Approach: - -Both systems can coexist in the same application for different use cases: - -- Use **NeuroLink** for high-level AI operations and multi-provider support -- Use **Bedrock-MCP-Connector** for AWS-specific deep integrations - -## Conclusion - -**There are no fundamental compatibility barriers between NeuroLink and Bedrock-MCP-Connector.** The systems use different architectural approaches but achieve equivalent functionality. The choice between them should be based on: - -1. **Architectural preferences** (abstraction vs control) -2. **Provider requirements** (multi-provider vs AWS-specific) -3. **Storage needs** (runtime vs persistent) -4. **Integration complexity** (high-level vs fine-grained) - -Both systems are well-engineered and suitable for production use in their respective domains. diff --git a/NEUROLINK_BEDROCK_COMPATIBILITY_ANALYSIS.md b/NEUROLINK_BEDROCK_COMPATIBILITY_ANALYSIS.md deleted file mode 100644 index 63c4d9917..000000000 --- a/NEUROLINK_BEDROCK_COMPATIBILITY_ANALYSIS.md +++ /dev/null @@ -1,2050 +0,0 @@ -# NEUROLINK vs BEDROCK-MCP-CONNECTOR: COMPREHENSIVE COMPATIBILITY ANALYSIS - -## Gap Analysis and Implementation Roadmap for Complete Replacement - ---- - -## EXECUTIVE SUMMARY - -This document provides an exhaustive analysis to replace the Bedrock-MCP-Connector library with NeuroLink's implementation while identifying and addressing all compatibility gaps. The primary focus is on AWS Bedrock integration issues, proxy support, authentication mechanisms, event systems, and data handling differences that need to be resolved for a seamless replacement. - -**Project Objective**: Enable NeuroLink to completely replace Bedrock-MCP-Connector functionality while fixing identified gaps in AWS integration, proxy support, authentication, events, and data handling. - ---- - -## SECTION 1: CURRENT IMPLEMENTATION ANALYSIS - -### 1.1 Bedrock-MCP-Connector Architecture Overview - -**Core Components:** - -``` -BedrockMCPClient -├── AWS Integration: @aws-sdk/client-bedrock-runtime -├── Authentication: AWS SDK Default Credential Chain -├── Proxy Support: None (AWS SDK handles internally) -├── Event System: Node.js EventEmitter with typed events -├── Storage: Pluggable (Memory/Redis) -├── MCP Integration: mcp-client via SSE -├── Tool Management: Dynamic registration and execution -└── CLI Interface: Interactive REPL with command processing -``` - -**AWS SDK Usage Pattern:** - -```typescript -// Current AWS SDK Integration -import { - BedrockRuntimeClient, - ConverseCommand, -} from "@aws-sdk/client-bedrock-runtime"; - -// Client Initialization -this.bedrockClient = new BedrockRuntimeClient({ region: this.region }); - -// API Call Pattern -const command = new ConverseCommand(commandInput); -const response = await this.bedrockClient.send(command); -``` - -### 1.2 NeuroLink Current Architecture Assessment - -**Need to analyze NeuroLink's current:** - -- AWS Bedrock integration implementation -- Authentication handling mechanisms -- Proxy configuration support -- Event emission patterns -- Request/response processing -- Error handling strategies -- Tool integration approach -- Session management -- Storage backends -- CLI interface capabilities - ---- - -## SECTION 2: CRITICAL GAP ANALYSIS AREAS - -### 2.0 Executive Gap Summary - -Based on detailed analysis of NeuroLink's current AWS Bedrock implementation, the following **CRITICAL COMPATIBILITY GAPS** have been identified that must be addressed for successful replacement of Bedrock-MCP-Connector: - -#### 🟢 **COMPLETED GAPS (100% Compatible)** - -1. **AWS Authentication Chain**: ✅ RESOLVED - Full AWS SDK v3 credential chain implemented with all 9 sources -2. **Event System**: ✅ RESOLVED - All 9 required Bedrock events implemented with correct timing and parameters -3. **Proxy Support**: ✅ RESOLVED - HTTP/HTTPS proxy support sufficient for Bedrock-MCP-Connector compatibility - -#### 🔴 **CRITICAL GAPS (Breaking Compatibility)** - -1. **Message Format**: NeuroLink uses `string` content vs `MessageContent[]` with tool support - **#1 BLOCKING ISSUE** -2. **Tool Integration**: Complete tool registration, execution, and MCP protocol missing - **#2 BLOCKING ISSUE** -3. **Session Management**: Missing conversation history and storage compatibility with tool results -4. ~~**Error Handling**: NeuroLink uses custom error types, not AWS SDK compatible errors~~ ✅ **RESOLVED - SUPERIOR TO BEDROCK** - -#### 🟡 **MODERATE GAPS (Partial Compatibility)** - -1. **Request/Response Format**: Using @ai-sdk wrapper instead of direct AWS SDK Converse API -2. **Configuration Management**: Different configuration patterns and environment variable handling - -#### 🟢 **MINOR GAPS (Framework Differences)** - -1. **TypeScript Types**: Different interface definitions but core functionality available -2. **Logging System**: Different logging approaches but not compatibility-breaking -3. **Performance Optimizations**: Different internal implementations but compatible outcomes - -#### Priority Fix Order: - -1. **P0 (Immediate)**: Error handling, Message format, Tool integration, Session management -2. **P1 (High)**: Request/Response format alignment, Configuration management -3. **P2 (Medium)**: Advanced proxy features (enhancement), Performance optimization -4. **P3 (Low)**: Type compatibility, Documentation - ---- - -### 2.1 AWS Authentication and Credential Management - -#### Bedrock-MCP-Connector Requirements: - -```typescript -// AWS SDK Default Credential Chain Support: -// 1. Environment Variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) -// 2. AWS Credentials File (~/.aws/credentials) -// 3. AWS Config File (~/.aws/config) -// 4. IAM Roles (EC2/ECS/Lambda) -// 5. AWS SSO -// 6. AWS STS Assume Role -// 7. Credential Process -// 8. Container Credentials -// 9. Instance Metadata Service (IMDS) - -// Direct AWS SDK usage in ConverseAgent.ts:44 -this.bedrockClient = new BedrockRuntimeClient({ region: this.region }); -``` - -#### NeuroLink Current Implementation Analysis: - -```typescript -// NeuroLink's AmazonBedrockProvider authentication (amazonBedrock.ts:67-86): -const awsConfig = { - accessKeyId: getAWSAccessKeyId(), // Only env vars: AWS_ACCESS_KEY_ID - secretAccessKey: getAWSSecretAccessKey(), // Only env vars: AWS_SECRET_ACCESS_KEY - region: getAWSRegion(), // Only env vars: AWS_REGION - fetch: createProxyFetch(), -}; - -// Dev environment only: -if (getAppEnvironment() === "dev") { - const sessionToken = getAWSSessionToken(); // Only env vars: AWS_SESSION_TOKEN - if (sessionToken) { - awsConfig.sessionToken = sessionToken; - } -} - -this.bedrock = createAmazonBedrock(awsConfig); -``` - -#### ✅ Authentication Implementation Status - COMPLETED: - -- [✅] **AWS Credentials File**: RESOLVED - AWS SDK v3 defaultProvider handles ~/.aws/credentials automatically -- [✅] **AWS Config File**: RESOLVED - AWS SDK v3 defaultProvider handles ~/.aws/config automatically -- [✅] **IAM Role Support**: RESOLVED - Full IAM role support for EC2/ECS/Lambda environments -- [✅] **AWS SSO Integration**: RESOLVED - AWS SSO workflows fully supported via AWS SDK -- [✅] **STS Token Handling**: RESOLVED - Temporary credentials and token refresh implemented -- [✅] **Credential Process**: RESOLVED - Custom credential providers supported -- [✅] **Container Credentials**: RESOLVED - ECS container credentials working -- [✅] **Instance Metadata**: RESOLVED - EC2 metadata service credentials supported -- [✅] **Environment Variable Authentication**: RESOLVED - AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY working -- [✅] **Session Token**: RESOLVED - Works in all environments (production and dev) - -#### ✅ Impact Assessment - RESOLVED: - -**COMPATIBILITY ACHIEVED**: NeuroLink now provides 100% authentication compatibility with Bedrock-MCP-Connector through AWS SDK v3 integration: - -1. **Production Deployments**: ✅ Applications on EC2/ECS/Lambda with IAM roles work perfectly -2. **Enterprise SSO**: ✅ Organizations using AWS SSO can authenticate seamlessly -3. **CI/CD Pipelines**: ✅ Automated deployments with assume role patterns fully supported -4. **Developer Experience**: ✅ Developers using AWS CLI profiles work out-of-the-box -5. **Security**: ✅ Temporary credentials and secure authentication patterns supported - -### 2.2 Proxy Support and Network Configuration - -#### Bedrock-MCP-Connector Implicit Requirements (via AWS SDK): - -```typescript -// AWS SDK Proxy Support: -// - HTTP_PROXY / HTTPS_PROXY environment variables -// - NO_PROXY bypass lists -// - SOCKS proxy support -// - Corporate proxy authentication -// - Custom certificate authorities -// - Network interface binding - -// Direct AWS SDK usage relies on Node.js global proxy configuration -this.bedrockClient = new BedrockRuntimeClient({ region: this.region }); -// AWS SDK automatically handles proxy via global agent and environment variables -``` - -#### NeuroLink Current Proxy Implementation Analysis: - -```typescript -// NeuroLink's proxyFetch.ts implementation (lines 12-52): -export function createProxyFetch(): typeof fetch { - const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy; - const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy; - - // If no proxy configured, return standard fetch - if (!httpsProxy && !httpProxy) { - return fetch; - } - - // Uses undici ProxyAgent for proxy support - const undici = await import("undici"); - const { ProxyAgent } = undici; - const dispatcher = new ProxyAgent(proxyUrl); - - return undici.fetch(fetchInput, { - ...fetchInit, - dispatcher: dispatcher, - }); -} - -// getProxyStatus() function (lines 228-240): -- Checks HTTP_PROXY/HTTPS_PROXY/NO_PROXY environment variables -- Uses undici-proxy-agent method -``` - -#### ✅ Proxy Support Compatibility Analysis - COMPLETE FOR BEDROCK COMPATIBILITY: - -- [✅] **HTTP/HTTPS Proxy**: NeuroLink supports HTTP_PROXY/HTTPS_PROXY env vars via undici (same as Bedrock-MCP-Connector) -- [🟢] **SOCKS Proxy**: Enhancement beyond Bedrock-MCP-Connector (not required for compatibility) -- [🟢] **Proxy Authentication**: Enhancement beyond Bedrock-MCP-Connector (not required for compatibility) -- [🟢] **Corporate Proxy**: Enhancement beyond Bedrock-MCP-Connector (not required for compatibility) -- [🟢] **Certificate Handling**: Enhancement beyond Bedrock-MCP-Connector (not required for compatibility) -- [🟢] **Proxy Bypass**: Enhancement beyond Bedrock-MCP-Connector (not required for compatibility) -- [🟢] **Network Interface**: Enhancement beyond Bedrock-MCP-Connector (not required for compatibility) -- [✅] **AWS SDK Compatibility**: Equivalent proxy support to Bedrock-MCP-Connector's AWS SDK usage - -#### Key Differences in Proxy Architecture: - -**Bedrock-MCP-Connector**: - -- Relies on AWS SDK's built-in proxy support -- Uses Node.js global HTTP/HTTPS agents -- Automatically inherits all AWS SDK proxy features -- Supports all proxy types AWS SDK supports - -**NeuroLink**: - -- Custom fetch implementation with undici ProxyAgent -- Only supports HTTP/HTTPS proxies -- Manual proxy configuration in amazonBedrock.ts:77 -- Proxy support limited to @ai-sdk calls, not native AWS SDK - -#### ✅ Impact Assessment - COMPATIBILITY ACHIEVED: - -**COMPATIBILITY COMPLETE**: NeuroLink provides equivalent proxy support to Bedrock-MCP-Connector for standard use cases: - -1. **Corporate Networks**: HTTP/HTTPS proxy support covers standard corporate proxy requirements (same as Bedrock-MCP-Connector) -2. **Standard Security**: Proxy support equivalent to AWS SDK default behavior -3. **Network Compatibility**: Standard proxy configurations work identically -4. **Enterprise Standard**: Basic proxy auth patterns supported through AWS SDK - -**Note**: Advanced proxy features (SOCKS, NTLM, etc.) are enhancements beyond Bedrock-MCP-Connector capabilities, not compatibility gaps. - -### 2.3 AWS Bedrock API Integration - -#### Bedrock-MCP-Connector AWS Integration: - -```typescript -// Required AWS Bedrock Features: -const commandInput = { - modelId: this.modelId, // Model selection - messages: messages, // Conversation history - system: [{ text: this.systemPrompt }], // System prompt - inferenceConfig: { - // Generation parameters - maxTokens: this.maxTokens, - temperature: this.temperature, - }, - toolConfig: toolConfig, // Tool specifications -}; -``` - -#### NeuroLink Bedrock Gaps to Identify: - -- [ ] **Model Support**: Does NeuroLink support all Bedrock model IDs? -- [ ] **Message Format**: Does NeuroLink handle the exact message structure? -- [ ] **System Prompts**: Can NeuroLink send system prompts in the correct format? -- [ ] **Inference Config**: Does NeuroLink support maxTokens, temperature, topP, etc.? -- [ ] **Tool Configuration**: Can NeuroLink send tool specifications to Bedrock? -- [ ] **Stop Sequences**: Does NeuroLink handle custom stop sequences? -- [ ] **Response Streaming**: Can NeuroLink handle streaming responses? -- [ ] **Tool Use Flow**: Does NeuroLink implement the complete tool use workflow? - -### 2.4 Error Handling and AWS SDK Compatibility - ✅ COMPLETED - -#### Bedrock-MCP-Connector Error Types: - -```typescript -// AWS SDK Error Handling (ConverseAgent.ts:230-232): -const command = new ConverseCommand(commandInput); -return await this.bedrockClient.send(command); - -// Simple error propagation - lets AWS SDK errors bubble up naturally -// No custom error handling or retry logic in Bedrock-MCP-Connector -// All error handling comes from AWS SDK defaults -``` - -#### NeuroLink Error Handling Analysis - SUPERIOR IMPLEMENTATION: - -**✅ COMPATIBILITY ACHIEVED - NeuroLink provides BETTER error handling than Bedrock-MCP-Connector:** - -```typescript -// NeuroLink's enhanced error handling (amazonBedrock.ts:302-333): -protected handleProviderError(error: unknown): Error { - if (error instanceof Error && error.name === "TimeoutError") { - return new TimeoutError(`Amazon Bedrock request timed out...`); - } - - const errorMessage = error instanceof Error ? error.message : String(error); - - if (errorMessage.includes("InvalidRequestException")) { - return new Error(`❌ Amazon Bedrock Request Error\n\n${errorMessage}\n\n🔧 Common Solutions:\n1. Check model ID format\n2. Verify request parameters\n3. Ensure AWS account has Bedrock access`); - } - - if (errorMessage.includes("AccessDeniedException")) { - return new Error(`❌ Amazon Bedrock Access Denied\n\n🔧 Required Steps:\n1. Ensure IAM user has bedrock:InvokeModel permission\n2. Check if Bedrock is available in your region\n3. Verify model access is enabled in Bedrock console`); - } - - // Additional error types with helpful guidance... -} -``` - -#### ✅ Error Handling Compatibility Status - EXCEEDED EXPECTATIONS: - -- [✅] **AWS Error Types**: NeuroLink preserves underlying AWS SDK errors + adds helpful guidance -- [✅] **Error Codes**: AWS SDK error codes maintained through error handling chain -- [✅] **Retry Logic**: NeuroLink inherits AWS SDK retry logic through direct BedrockRuntimeClient access -- [✅] **Exponential Backoff**: AWS SDK retry mechanisms preserved via getBedrockClient() -- [✅] **Rate Limiting**: AWS SDK throttling handling maintained + enhanced timeout controls -- [✅] **Error Messages**: NeuroLink provides SUPERIOR error messages with actionable guidance -- [✅] **Additional Features**: Timeout handling, structured error logging, debug capabilities - -#### ✅ Impact Assessment - COMPATIBILITY EXCEEDED: - -**SUPERIOR COMPATIBILITY**: NeuroLink provides better error handling than Bedrock-MCP-Connector: - -1. **Enhanced User Experience**: ✅ Actionable error messages with troubleshooting steps -2. **Preserved AWS SDK Behavior**: ✅ Underlying AWS errors and retry logic maintained -3. **Advanced Features**: ✅ Timeout handling, structured errors, debug logging -4. **Drop-in Compatibility**: ✅ Applications work identically but with better error reporting -5. **Debugging Support**: ✅ Enhanced error context and diagnostic information - -**Key Advantage**: Bedrock-MCP-Connector has NO custom error handling - it simply lets AWS SDK errors bubble up. NeuroLink enhances this with user-friendly messages while preserving all AWS SDK error behavior through dual access pattern. - -### 2.5 Event System Compatibility - -#### Bedrock-MCP-Connector Event Pattern: - -```typescript -// Required Event Types and Timing (BedrockMCPClient.ts): -emitter.on('message', (message: string) => void); // Lines 113, 127, 187, 335, etc. -emitter.on('error', (error: Error) => void); // Lines 90, 135, 155, 197, etc. -emitter.on('tool:start', (toolName: string, input: Record) => void); // Line 218 -emitter.on('tool:end', (toolName: string, result: any) => void); // Line 220 -emitter.on('response:start', () => void); // Line 186 -emitter.on('response:chunk', (chunk: string) => void); // Line 191 -emitter.on('response:end', (fullResponse: string) => void); // Line 192 -emitter.on('connected', () => void); // Line 128 -emitter.on('disconnected', () => void); // Line 151 - -// Event emitter instantiation (BedrockMCPClient.ts:64): -this.emitter = new EventEmitter() as BedrockMCPClientEmitter; -``` - -#### NeuroLink Current Event Implementation Analysis: - -```typescript -// NeuroLink has LIMITED event system in MCP components: -// ExternalServerManager.ts - Only MCP-related events: -this.emit("toolDiscovered", event); // Line 123 -this.emit("toolRemoved", event); // Line 127 -this.emit("disconnected", { - // Line 158 - serverId, - reason: "Manually removed", -}); - -// No event system in core Bedrock provider or BaseProvider -// No events in AmazonBedrockProvider class -// No EventEmitter inheritance in main classes -``` - -#### ✅ Event System Implementation Status - COMPLETED: - -- [✅] **Message Events**: RESOLVED - 'message' events implemented for status updates throughout operations -- [✅] **Error Events**: RESOLVED - 'error' events implemented with automatic emission during failures -- [✅] **Tool Events**: RESOLVED - 'tool:start' and 'tool:end' events implemented with correct parameters -- [✅] **Response Events**: RESOLVED - 'response:start', 'response:chunk', 'response:end' events implemented -- [✅] **Connection Events**: RESOLVED - 'connected'/'disconnected' events implemented for provider lifecycle -- [✅] **Event Emitter Pattern**: RESOLVED - Full EventEmitter integration with getEventEmitter() access -- [✅] **Universal Events**: RESOLVED - Events work across all providers, not just MCP-specific -- [✅] **Typed Events**: RESOLVED - Proper event interfaces and parameter types implemented - -#### ✅ Impact Assessment - COMPATIBILITY ACHIEVED: - -**COMPATIBILITY COMPLETE**: NeuroLink now provides 100% event system compatibility with Bedrock-MCP-Connector: - -1. **Integration Compatibility**: ✅ Applications listening to client events work perfectly -2. **Monitoring/Debugging**: ✅ Full observability through event emission system -3. **Progress Tracking**: ✅ Complete tool execution and response progress events -4. **Error Handling**: ✅ Comprehensive error events for external error handling -5. **State Management**: ✅ Connection/disconnection events for state tracking -6. **Logging Integration**: ✅ All event-based logging patterns supported - -**Implementation**: All 9 required Bedrock events implemented with systematic verification (9/9 tests passing). - -### 2.6 AWS Region Configuration and Environment Handling - -#### Bedrock-MCP-Connector Region Configuration: - -```typescript -// Direct hardcoded region with fallback (ConverseAgent.ts:43): -this.region = options.region || "us-east-1"; -this.bedrockClient = new BedrockRuntimeClient({ region: this.region }); - -// No environment variable support for region configuration -// No AWS config file integration -// Region set via constructor options only -``` - -#### NeuroLink Region Configuration Analysis: - -```typescript -// NeuroLink's region handling (providerConfig.ts:422-424): -export function getAWSRegion(): string { - return process.env.AWS_REGION || "us-east-1"; -} - -// Used in amazonBedrock.ts:76: -region: getAWSRegion(), - -// Supports environment variable AWS_REGION -// Same default fallback (us-east-1) -// No AWS config file integration -``` - -#### Region Configuration Compatibility: - -- [✅] **Default Region**: Both use 'us-east-1' as default fallback -- [✅] **Environment Variable**: NeuroLink DOES support AWS_REGION env var (improvement over Bedrock-MCP-Connector) -- [⚠️] **Configuration Source**: Different configuration patterns but functionally compatible -- [❌] **AWS Config File**: Neither implementation reads ~/.aws/config for default region -- [❌] **Cross-Region Support**: Neither supports cross-region operations or region switching - -#### Impact Assessment: - -**LOW COMPATIBILITY ISSUE**: Region configuration is actually better in NeuroLink, supporting environment variables which Bedrock-MCP-Connector lacks. - -### 2.7 AWS Error Handling and Retry Logic - -#### Bedrock-MCP-Connector Error Handling: - -```typescript -// Relies entirely on AWS SDK default error handling: -// - Uses BedrockRuntimeClient which includes built-in retry logic -// - AWS SDK automatically handles: ThrottlingException, ValidationException, etc. -// - Built-in exponential backoff with jitter -// - Standard AWS SDK error types and codes -// - Automatic service quota and rate limit handling - -// No custom error handling in Bedrock-MCP-Connector code -// Errors bubble up as AWS SDK errors with standard properties -``` - -#### NeuroLink Error Handling Analysis: - -```typescript -// Custom error handling in amazonBedrock.ts:159-190: -protected handleProviderError(error: unknown): Error { - if (error instanceof Error && error.name === "TimeoutError") { - return new TimeoutError(...); - } - - const errorMessage = error instanceof Error ? error.message : String(error); - - if (errorMessage.includes("InvalidRequestException")) { - return new Error("❌ Amazon Bedrock Request Error..."); - } - if (errorMessage.includes("AccessDeniedException")) { - return new Error("❌ Amazon Bedrock Access Denied..."); - } - if (errorMessage.includes("ValidationException")) { - return new Error("❌ Amazon Bedrock Validation Error..."); - } - - // Custom error messages instead of AWS SDK errors -} -``` - -#### Critical Error Handling Gaps: - -- [❌] **AWS SDK Error Types**: NeuroLink converts AWS errors to generic Error objects -- [❌] **Error Codes**: NeuroLink loses AWS error codes and structured error information -- [❌] **Retry Logic**: NeuroLink may not inherit AWS SDK retry behavior due to @ai-sdk wrapper -- [❌] **Exponential Backoff**: Custom error handling bypasses AWS SDK retry mechanisms -- [❌] **Service Quota Handling**: Custom errors don't include AWS service quota information -- [⚠️] **Error Messages**: NeuroLink provides user-friendly errors but loses technical details -- [❌] **Error Properties**: AWS SDK errors have specific properties (RequestId, etc.) that are lost - -#### Impact Assessment: - -**HIGH COMPATIBILITY ISSUE**: Applications expecting AWS SDK error types and retry behavior will encounter different error handling patterns. - -1. **Integration Breaking**: Applications that catch specific AWS error types will fail -2. **Monitoring Impact**: Error tracking systems expecting AWS error structure will break -3. **Retry Logic**: Applications may implement duplicate retry logic due to missing AWS SDK patterns -4. **Debugging Difficulty**: Lost AWS RequestId and structured error information - -### 2.8 Bedrock Converse API Usage Patterns - -#### Bedrock-MCP-Connector Direct AWS SDK Usage: - -```typescript -// Direct ConverseCommand usage (ConverseAgent.ts:194-210): -const commandInput: any = { - modelId: this.modelId, - messages: messages, - system: [{ text: this.systemPrompt }], - inferenceConfig: { - maxTokens: this.maxTokens, - temperature: this.temperature, - }, -}; - -if (this.toolManager) { - const toolConfig = this.toolManager.getToolConfig(); - if (toolConfig) { - commandInput.toolConfig = toolConfig; - } -} - -const command = new ConverseCommand(commandInput); -return await this.bedrockClient.send(command); - -// Direct access to AWS response structure: -// - response.output.message -// - response.stopReason -// - response.usage -// - Full AWS SDK response metadata -``` - -#### NeuroLink AI SDK Abstraction Usage: - -```typescript -// Using @ai-sdk/amazon-bedrock wrapper (amazonBedrock.ts:135-141): -const result = streamText({ - model: this.model, // Pre-initialized @ai-sdk model - messages: messages, // Converted to AI SDK format - maxTokens: options.maxTokens || DEFAULT_MAX_TOKENS, - temperature: options.temperature, - abortSignal: timeoutController?.controller.signal, -}); - -// AI SDK abstraction layer: -// - Uses this.bedrock = createAmazonBedrock(awsConfig) -// - No direct access to AWS ConverseCommand -// - Response wrapped in AI SDK format -// - Limited access to AWS-specific response metadata -``` - -#### Critical API Usage Gaps: - -- [❌] **Direct AWS SDK Access**: NeuroLink uses AI SDK wrapper, losing direct AWS API control -- [❌] **ConverseCommand Control**: No access to raw ConverseCommand parameters and response -- [❌] **AWS Response Metadata**: Loss of AWS-specific response fields (RequestId, ResponseMetadata) -- [❌] **Tool Configuration**: Different tool configuration pattern through AI SDK vs. direct toolConfig -- [❌] **Stop Reason Access**: AI SDK may not expose AWS-specific stop reasons -- [❌] **Usage Metrics**: Limited access to AWS Bedrock usage statistics -- [⚠️] **Parameter Mapping**: AI SDK parameters may not map 1:1 to AWS Bedrock parameters -- [❌] **Error Context**: AWS SDK errors wrapped/transformed by AI SDK layer - -#### Key Architectural Differences: - -**Bedrock-MCP-Connector**: - -- Direct AWS SDK BedrockRuntimeClient usage -- Full control over ConverseCommand parameters -- Direct access to all AWS response fields -- Native AWS error handling and retry logic -- Complete AWS SDK feature compatibility - -**NeuroLink**: - -- Abstracted through @ai-sdk/amazon-bedrock -- Simplified API but reduced AWS-specific control -- AI SDK standardized response format -- Custom error handling layer on top of AI SDK -- May lose AWS-specific features through abstraction - -#### Impact Assessment: - -**MODERATE TO HIGH COMPATIBILITY ISSUE**: The abstraction layer introduces several compatibility challenges: - -1. **API Control Loss**: Applications expecting direct AWS SDK patterns may not work -2. **Metadata Access**: Missing AWS-specific response metadata for monitoring/debugging -3. **Tool Integration**: Different tool configuration patterns may break existing integrations -4. **Error Handling**: Wrapped errors lose AWS SDK error structure and properties -5. **Feature Gaps**: AI SDK may not support all AWS Bedrock features immediately - -### 2.9 Message Format and Data Structure Compatibility - 🔴 CRITICAL GAP IDENTIFIED - -#### Bedrock-MCP-Connector Message Format (ACTUAL IMPLEMENTATION): - -```typescript -// Message interface (types.ts:73-76): -export interface Message { - role: "user" | "assistant" | "system"; - content: MessageContent[]; -} - -// Message content types (types.ts:39-68): -export type MessageContent = TextContent | ToolUseContent | ToolResultContent; - -interface TextContent { - text: string; -} - -interface ToolUseContent { - toolUse: { - toolUseId: string; - name: string; - input?: Record; - }; -} - -interface ToolResultContent { - toolResult: { - toolUseId: string; - content: Array<{ text: string }>; - status: "success" | "error"; - }; -} - -// REAL USAGE PATTERN (ConverseAgent.ts:135-141): -const userMessage: Message = { - role: "user", - content: content, // Array of MessageContent items -}; -await this.messageStorage.addMessage(this.session, userMessage); - -// Tool result handling (ConverseAgent.ts:344-348): -const userMessageWithToolResults: Message = { - role: "user", - content: toolResults, // Array of ToolResultContent -}; -``` - -#### NeuroLink Message Format Analysis - SIGNIFICANT INCOMPATIBILITY: - -```typescript -// ChatMessage interface (conversationTypes.ts): -export interface ChatMessage { - role: "user" | "assistant" | "system"; - content: string; // Simple string-only content -} - -// buildMessagesArray() usage pattern: -// - Only supports simple text strings -// - No support for tool use structures -// - No toolUseId tracking capability -// - No tool result integration -``` - -#### 🔴 CRITICAL Message Format Gaps - BREAKING COMPATIBILITY: - -- [❌] **Content Structure**: NeuroLink uses `string` content vs. Bedrock's `MessageContent[]` array -- [❌] **Tool Use Messages**: NeuroLink completely lacks `ToolUseContent` support -- [❌] **Tool Result Messages**: NeuroLink has no `ToolResultContent` capability -- [❌] **Tool Use Flow**: No `toolUseId` tracking system for tool execution lifecycle -- [❌] **Multi-Part Content**: Bedrock supports multiple content items per message, NeuroLink doesn't -- [❌] **Tool Result Handling**: No structured tool result representation in conversation -- [❌] **Status Tracking**: No success/error status tracking for tool executions -- [❌] **Tool Use Sequence**: Cannot maintain proper assistant→tool→user→assistant flow - -#### Real-World Tool Use Example from Bedrock-MCP-Connector: - -```typescript -// 1. Assistant message with tool use (ConverseAgent.ts:299-300): -await this.messageStorage.addMessage(this.session, { - role: "assistant", - content: [ - { - toolUse: { - toolUseId: "tool_123", - name: "weather_api", - input: { location: "New York" }, - }, - }, - ], -}); - -// 2. User message with tool result (ConverseAgent.ts:344-348): -await this.messageStorage.addMessage(this.session, { - role: "user", - content: [ - { - toolResult: { - toolUseId: "tool_123", - content: [{ text: "Temperature: 72°F" }], - status: "success", - }, - }, - ], -}); -``` - -#### ✅ Impact Assessment - CRITICAL COMPATIBILITY BREAKING: - -**BREAKING COMPATIBILITY**: Message format differences prevent tool use workflows: - -1. **Tool Integration Impossible**: ✅ NeuroLink cannot represent tool use messages in conversation -2. **Conversation History Loss**: ✅ Tool execution context cannot be preserved -3. **Sequential Tool Calls**: ✅ Multi-step tool workflows completely broken -4. **Session Management**: ✅ Cannot store/restore tool use conversations -5. **MCP Protocol**: ✅ Cannot integrate with MCP servers requiring tool message format - -**This is the #1 blocking issue for Bedrock-MCP-Connector replacement.** - -### 2.10 Session Management and Storage Backend Compatibility - -#### Bedrock-MCP-Connector Session Management: - -```typescript -// Session identification (RedisMessageStorage.ts:24): -keyPrefix: 'bedrock-mcp:conversation:' - -// SessionIdentifier interface: -interface SessionIdentifier { - sessionId: string; - userId?: string; -} - -// Storage operations: -- addMessage(session: SessionIdentifier, message: Message) -- getMessages(session: SessionIdentifier): Promise -- clearMessages(session: SessionIdentifier) -- updateMessage(session, index, message) -- Redis TTL support (24 hours default) -- Pluggable storage (Memory/Redis) -``` - -#### NeuroLink Session Management Analysis: - -```typescript -// NeuroLink uses different session patterns: -// 1. ConversationMemoryConfig for memory management -// 2. SessionMemory interface for session storage -// 3. ChatMessage[] storage format -// 4. Context management through ContextManager - -interface SessionMemory { - sessionId: string; - userId?: string; - messages: ChatMessage[]; // Different message format - metadata?: Record; - lastUpdated: Date; -} - -// Different storage patterns and session lifecycle -``` - -#### Critical Session Management Gaps: - -- [❌] **Message Storage Format**: Incompatible message structures (ChatMessage vs Message) -- [❌] **Session Storage APIs**: Different method signatures and interfaces -- [❌] **Redis Integration**: Different Redis key patterns and data structures -- [❌] **TTL Management**: Different session expiration handling -- [❌] **Update Operations**: Missing updateMessage functionality in NeuroLink -- [❌] **Storage Backend Interface**: Incompatible storage abstraction layers -- [⚠️] **Session Identification**: Similar session ID patterns but different usage - -#### Impact Assessment: - -**HIGH COMPATIBILITY ISSUE**: Storage incompatibility prevents session migration and data portability. - -### 2.11 Tool Integration and MCP Protocol - 🔴 CRITICAL GAP IDENTIFIED - -#### Bedrock-MCP-Connector Tool Flow (ACTUAL IMPLEMENTATION): - -```typescript -// 1. Tool Registration (BedrockMCPClient.ts:210-230): -registerTool( - name: string, - handler: ToolHandler, - description?: string, - inputSchema?: Record -): void { - const wrappedHandler: ToolHandler = async (name, input) => { - try { - this.emitter.emit("tool:start", name, input); - const result = await handler(name, input); - this.emitter.emit("tool:end", name, result); - return result; - } catch (error) { - this.emitter.emit("error", new Error(`Error executing tool ${name}: ${errorMessage}`)); - throw error; - } - }; - this.toolManager.registerTool(name, wrappedHandler, description, inputSchema); -} - -// 2. MCP Tool Discovery (BedrockMCPClient.ts:325-383): -private async registerMCPTools(): Promise { - const tools = await this.mcpClient.getAllTools(); - for (const tool of tools) { - const toolFunction: ToolHandler = async (name, input) => { - return await this.mcpClient!.callTool({ - name: name, - arguments: input || {} - }); - }; - this.registerTool(tool.name, toolFunction, tool.description, tool.inputSchema); - } -} - -// 3. Tool Execution Flow (ToolManager.ts:72-118): -async executeTool(request: ToolRequest): Promise { - const { toolUseId, name, input } = request; - const result = await this.tools[name].handler(name, input); - - return { - toolUseId, - content: Array.isArray(result.content) ? result.content : [{ text: String(result) }], - status: 'success' - }; -} - -// 4. AWS Bedrock Tool Config (ToolManager.ts:45-63): -getToolConfig(): ToolConfig | null { - return { - tools: Object.entries(this.tools).map(([name, tool]) => ({ - toolSpec: { - name, - description: tool.description || "Tool description", - inputSchema: { - json: tool.inputSchema || { type: "object", properties: {}, required: [] } - } - } - })) - }; -} - -// 5. Complete Tool Use Workflow (ConverseAgent.ts:293-354): -// a) Model requests tool use -// b) Tool execution with ToolManager -// c) Tool results added to conversation as user message -// d) Model continues with tool results -``` - -#### NeuroLink Tool Integration Analysis - MAJOR INCOMPATIBILITY: - -```typescript -// NeuroLink has basic MCP integration in ExternalServerManager but: -// - No direct tool registration API like registerTool() -// - No tool execution workflow compatible with Bedrock format -// - No ToolConfig generation for AWS Bedrock -// - No tool result integration in conversation flow -// - Different tool discovery patterns -``` - -#### 🔴 CRITICAL Tool Integration Gaps - BREAKING COMPATIBILITY: - -- [❌] **Tool Registration API**: NeuroLink lacks `registerTool(name, handler, description, schema)` method -- [❌] **MCP Protocol Integration**: No SSE-based MCP client integration pattern -- [❌] **Tool Discovery**: No automatic MCP tool discovery via `getAllTools()` -- [❌] **Tool Execution Flow**: Missing `ToolRequest`→`ToolResponse` execution pattern -- [❌] **Schema Validation**: No JSON schema validation for tool inputs -- [❌] **Bedrock Tool Config**: Cannot generate AWS Bedrock `ToolConfig` format -- [❌] **Event Emission**: No tool lifecycle events (`tool:start`, `tool:end`) -- [❌] **Tool Result Integration**: No mechanism to feed tool results back to conversation -- [❌] **Error Handling**: No tool-specific error handling and recovery -- [❌] **Tool Use Flow**: No support for AWS Bedrock tool use message sequence - -#### Real-World Tool Integration Example from Bedrock-MCP-Connector: - -```typescript -// Complete tool registration and usage workflow: - -// 1. Register custom tool -client.registerTool( - "weather_api", - async (name, input) => { - const weather = await fetchWeather(input.location); - return { content: [{ text: `Weather: ${weather}` }] }; - }, - "Get weather for a location", - { - type: "object", - properties: { - location: { type: "string", description: "City name" }, - }, - required: ["location"], - }, -); - -// 2. Connect to MCP server and auto-register tools -await client.connect(); // Automatically discovers and registers MCP tools - -// 3. Tool execution happens automatically during conversation -const response = await client.sendPrompt("What's the weather in NYC?"); -// → Model requests weather_api tool -// → Tool executes and returns result -// → Model incorporates result into response -``` - -#### ✅ Impact Assessment - CRITICAL COMPATIBILITY BREAKING: - -**BREAKING COMPATIBILITY**: Tool integration differences prevent MCP and tool workflows: - -1. **Tool Registration Impossible**: ✅ No compatible API for registering custom tools -2. **MCP Server Integration**: ✅ Cannot connect to and discover tools from MCP servers -3. **Tool Execution Broken**: ✅ No compatible tool execution workflow -4. **Conversation Integration**: ✅ Cannot integrate tool results into conversation flow -5. **Event-Driven Architecture**: ✅ Missing tool lifecycle event emission -6. **Schema Validation Missing**: ✅ No input validation for tool parameters - -**This is the #2 blocking issue for Bedrock-MCP-Connector replacement after message format.** - ---- - -## SECTION 3: SPECIFIC IMPLEMENTATION REQUIREMENTS - -### 3.1 AWS Credential Chain Implementation - -**Required Implementation in NeuroLink:** - -```typescript -// Priority Order for Credential Resolution: -1. Explicit credentials passed to constructor -2. Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN) -3. Web Identity Token (AWS_WEB_IDENTITY_TOKEN_FILE) -4. SSO credentials (aws configure sso) -5. AWS credentials file (~/.aws/credentials) -6. AWS config file (~/.aws/config) -7. Container credentials (ECS_CONTAINER_METADATA_URI) -8. Instance metadata credentials (EC2 IMDS) -9. Process credentials (credential_process in config) -``` - -**Code Structure Needed:** - -```typescript -interface AWSCredentials { - accessKeyId: string; - secretAccessKey: string; - sessionToken?: string; - expiration?: Date; -} - -interface AWSConfig { - region?: string; - credentials?: AWSCredentials; - endpoint?: string; - maxRetries?: number; - timeout?: number; - proxy?: ProxyConfig; -} - -class CredentialProvider { - async resolveCredentials(): Promise; - async refreshCredentials(): Promise; -} -``` - -### 3.2 Proxy Configuration Implementation - -**Required Proxy Support:** - -```typescript -interface ProxyConfig { - protocol: "http" | "https" | "socks4" | "socks5"; - host: string; - port: number; - auth?: { - username: string; - password: string; - }; - ca?: string | Buffer; // Custom CA certificates - timeout?: number; -} - -// Environment Variable Support: -// HTTP_PROXY, HTTPS_PROXY, NO_PROXY -// HTTPS_PROXY for SSL connections -// NO_PROXY for bypass rules -``` - -### 3.3 Request/Response Processing - -**AWS Bedrock Request Format:** - -```typescript -interface BedrockRequest { - modelId: string; - messages: Message[]; - system?: SystemMessage[]; - inferenceConfig?: { - maxTokens?: number; - temperature?: number; - topP?: number; - stopSequences?: string[]; - }; - toolConfig?: { - tools: ToolSpec[]; - toolChoice?: "auto" | "any" | { tool: { name: string } }; - }; -} -``` - -**Response Processing Requirements:** - -```typescript -interface BedrockResponse { - output: { - message: { - role: "assistant"; - content: Array; - }; - }; - stopReason: "end_turn" | "tool_use" | "max_tokens" | "stop_sequence"; - usage: { - inputTokens: number; - outputTokens: number; - totalTokens: number; - }; -} -``` - -### 3.4 Event System Implementation - -**Event Emission Requirements:** - -```typescript -class NeuroLinkEventEmitter extends EventEmitter { - // Must emit events at exact same points as Bedrock-MCP-Connector - emitMessage(message: string): void; - emitError(error: Error): void; - emitToolStart(toolName: string, input: any): void; - emitToolEnd(toolName: string, result: any): void; - emitResponseStart(): void; - emitResponseChunk(chunk: string): void; - emitResponseEnd(response: string): void; - emitConnected(): void; - emitDisconnected(): void; -} -``` - ---- - -## SECTION 4: DETAILED COMPATIBILITY TESTING MATRIX - -### 4.1 Authentication Testing Scenarios - -| Scenario | Bedrock-MCP-Connector | NeuroLink Status | Gap | -| --------------------- | --------------------- | ---------------- | ------------- | -| Environment Variables | ✅ Supported | ❓ Unknown | Test Required | -| AWS Credentials File | ✅ Supported | ❓ Unknown | Test Required | -| AWS Config File | ✅ Supported | ❓ Unknown | Test Required | -| IAM Roles (EC2) | ✅ Supported | ❓ Unknown | Test Required | -| IAM Roles (ECS) | ✅ Supported | ❓ Unknown | Test Required | -| IAM Roles (Lambda) | ✅ Supported | ❓ Unknown | Test Required | -| AWS SSO | ✅ Supported | ❓ Unknown | Test Required | -| STS Assume Role | ✅ Supported | ❓ Unknown | Test Required | -| Credential Process | ✅ Supported | ❓ Unknown | Test Required | -| Container Credentials | ✅ Supported | ❓ Unknown | Test Required | -| Instance Metadata | ✅ Supported | ❓ Unknown | Test Required | - -### 4.2 Proxy Testing Scenarios - -| Scenario | Bedrock-MCP-Connector | NeuroLink Status | Gap | -| ----------------------- | --------------------- | ---------------- | ------------- | -| HTTP Proxy | ✅ Supported | ❓ Unknown | Test Required | -| HTTPS Proxy | ✅ Supported | ❓ Unknown | Test Required | -| SOCKS4 Proxy | ✅ Supported | ❓ Unknown | Test Required | -| SOCKS5 Proxy | ✅ Supported | ❓ Unknown | Test Required | -| Proxy Authentication | ✅ Supported | ❓ Unknown | Test Required | -| Corporate Proxy | ✅ Supported | ❓ Unknown | Test Required | -| Proxy Bypass (NO_PROXY) | ✅ Supported | ❓ Unknown | Test Required | -| Custom CA Certificates | ✅ Supported | ❓ Unknown | Test Required | - -### 4.3 API Compatibility Testing - -| Feature | Bedrock-MCP-Connector | NeuroLink Status | Gap | -| ------------------ | --------------------- | ---------------- | ------------- | -| Model Selection | ✅ Supported | ❓ Unknown | Test Required | -| Message Format | ✅ Supported | ❓ Unknown | Test Required | -| System Prompts | ✅ Supported | ❓ Unknown | Test Required | -| Inference Config | ✅ Supported | ❓ Unknown | Test Required | -| Tool Configuration | ✅ Supported | ❓ Unknown | Test Required | -| Stop Sequences | ✅ Supported | ❓ Unknown | Test Required | -| Response Streaming | ✅ Supported | ❓ Unknown | Test Required | -| Error Handling | ✅ Supported | ❓ Unknown | Test Required | - -### 4.4 Event System Testing - -| Event Type | Expected Timing | Parameters | Status | -| ---------------- | --------------------- | ------------------------------- | ---------------- | -| `message` | Status updates | (message: string) | ❓ Test Required | -| `error` | Error conditions | (error: Error) | ❓ Test Required | -| `tool:start` | Before tool execution | (toolName: string, input: any) | ❓ Test Required | -| `tool:end` | After tool execution | (toolName: string, result: any) | ❓ Test Required | -| `response:start` | Before API call | () | ❓ Test Required | -| `response:chunk` | During streaming | (chunk: string) | ❓ Test Required | -| `response:end` | After response | (response: string) | ❓ Test Required | -| `connected` | MCP connection | () | ❓ Test Required | -| `disconnected` | MCP disconnection | () | ❓ Test Required | - ---- - -## SECTION 5: IMPLEMENTATION PRIORITY MATRIX - -### 5.1 Critical Path Items (Must Fix for Basic Functionality) - -**Priority 1 - Blocking Issues:** - -1. **AWS Authentication**: Must support environment variables and credentials file -2. **Basic Proxy Support**: Must support HTTP_PROXY/HTTPS_PROXY -3. **Bedrock API Format**: Must match exact request/response format -4. **Event Emission**: Must emit events at correct timing -5. **Error Compatibility**: Must throw compatible error types - -**Priority 2 - High Impact:** - -1. **IAM Role Support**: Required for production deployments -2. **Advanced Proxy**: SOCKS and corporate proxy support -3. **Tool Integration**: Complete tool registration and execution -4. **Session Management**: Compatible session handling -5. **Storage Backends**: Redis and memory storage compatibility - -**Priority 3 - Feature Completeness:** - -1. **AWS SSO Support**: Enterprise authentication -2. **Advanced Error Handling**: Complete retry logic -3. **Performance Optimization**: Connection pooling, caching -4. **CLI Compatibility**: Complete CLI feature parity -5. **Documentation**: Migration guides and compatibility notes - -### 5.2 Risk Assessment - -**High Risk Areas:** - -- **Authentication Chain**: Complex credential resolution logic -- **Proxy Support**: Network configuration variability -- **Error Handling**: AWS error type compatibility -- **Event Timing**: Exact event emission synchronization - -**Medium Risk Areas:** - -- **Tool Integration**: MCP protocol compatibility -- **Storage Backend**: Data format compatibility -- **CLI Interface**: Command processing differences - -**Low Risk Areas:** - -- **TypeScript Types**: Interface compatibility -- **Documentation**: Usage examples and guides -- **Testing**: Test suite development - ---- - -## SECTION 6: IMPLEMENTATION ROADMAP - -### Phase 1: Foundation Analysis (Weeks 1-2) - -- Complete NeuroLink architecture analysis -- Identify all current AWS integration gaps -- Document existing proxy support limitations -- Map current event system implementation -- Catalog authentication methods currently supported - -### Phase 2: Critical Gap Resolution (Weeks 3-6) - -- Implement missing AWS credential chain support -- Add comprehensive proxy configuration -- Fix Bedrock API format compatibility -- Implement compatible event emission system -- Add missing error handling and types - -### Phase 3: Advanced Feature Implementation (Weeks 7-10) - -- Add IAM role and SSO support -- Implement advanced proxy features -- Complete tool integration compatibility -- Add session management features -- Implement storage backend compatibility - -### Phase 4: Testing and Validation (Weeks 11-12) - -- Comprehensive compatibility testing -- Performance and load testing -- Security and compliance validation -- Migration testing with real workloads -- Documentation and user guides - -### Phase 5: Deployment and Migration (Weeks 13-14) - -- Staged rollout planning -- Monitoring and alerting setup -- Backward compatibility verification -- Production migration execution -- Post-migration support and optimization - ---- - -## SECTION 7: SUCCESS CRITERIA AND VALIDATION - -### 7.1 Compatibility Requirements - -**100% API Compatibility:** - -- All public methods must have identical signatures -- All events must be emitted at identical points -- All configuration options must be supported -- All error types must be compatible - -**100% Behavioral Compatibility:** - -- Authentication must work in all environments -- Proxy support must work in all network configurations -- Tool integration must be seamless -- Performance must be equivalent or better - -### 7.2 Testing Criteria - -**Functional Testing:** - -- All authentication methods tested in isolation -- All proxy configurations validated -- All AWS Bedrock features verified -- All event sequences validated -- All error scenarios covered - -**Integration Testing:** - -- Real AWS environment testing -- Corporate network testing -- Multi-tenant deployment testing -- Performance benchmark comparison -- Migration scenario testing - -**Regression Testing:** - -- Existing functionality preservation -- No performance degradation -- No security vulnerabilities -- No compatibility breaking changes - ---- - -## SECTION 8: RISK MITIGATION STRATEGIES - -### 8.1 Technical Risks - -**AWS API Changes:** - -- Monitor AWS Bedrock API updates -- Maintain version compatibility matrix -- Implement feature flags for new capabilities - -**Network Configuration Variability:** - -- Comprehensive proxy testing matrix -- Corporate network validation -- Edge case scenario coverage - -**Performance Impact:** - -- Continuous performance monitoring -- Load testing with realistic scenarios -- Resource usage optimization - -### 8.2 Business Risks - -**Migration Complexity:** - -- Phased rollout strategy -- Comprehensive rollback procedures -- User communication and training - -**Compatibility Issues:** - -- Extensive compatibility testing -- User acceptance testing -- Gradual migration approach - -**Support and Maintenance:** - -- Documentation and knowledge transfer -- Support team training -- Monitoring and alerting systems - ---- - ---- - -## SECTION 9: DETAILED IMPLEMENTATION ROADMAP & TODO LIST - -### 9.1 Updated Executive Gap Summary with Implementation Priorities - -Based on comprehensive analysis, **28 CRITICAL COMPATIBILITY GAPS** identified: - -#### 🔴 **P0 - BREAKING COMPATIBILITY (Must Fix First)** - -1. **AWS Authentication Chain** - Only env vars, missing IAM/SSO/credentials files -2. **Event System** - Complete absence of required event emission patterns -3. **Message Format** - String content vs MessageContent[] with tool support -4. **Session Storage** - Incompatible storage APIs and data structures -5. **Error Handling** - Custom errors vs AWS SDK error types and properties -6. **Tool Use Flow** - Missing toolUseId tracking and tool result messages - -#### 🟡 **P1 - HIGH IMPACT (Fix After P0)** - -7. **Proxy Advanced Features** - Missing SOCKS, auth, bypass logic -8. **AWS API Control** - AI SDK abstraction limits direct AWS control -9. **Storage Backend Interface** - Different Redis patterns and TTL handling -10. **Tool Registration** - Different tool discovery and execution patterns - -#### 🟢 **P2 - MEDIUM IMPACT (Enhancement)** - -11. **Request/Response Metadata** - Loss of AWS-specific response fields -12. **Retry Logic** - AI SDK may bypass AWS retry mechanisms -13. **Configuration Management** - Different env var and config patterns - -### 9.2 SUPER DETAILED IMPLEMENTATION TODO LIST (150+ Tasks) - -#### **PHASE 1: P0 CRITICAL GAPS (Weeks 1-4)** - -**🔴 A1: AWS Authentication Chain Implementation (12 tasks)** - -- [ ] **A1.1**: Create AWSCredentialProvider interface compatible with Bedrock-MCP-Connector -- [ ] **A1.2**: Implement EnvironmentCredentialProvider (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) -- [ ] **A1.3**: Implement FileCredentialProvider (~/.aws/credentials parsing) -- [ ] **A1.4**: Implement ConfigFileCredentialProvider (~/.aws/config parsing) -- [ ] **A1.5**: Implement IAMRoleCredentialProvider (EC2/ECS/Lambda metadata service) -- [ ] **A1.6**: Implement SSOCredentialProvider (AWS SSO integration) -- [ ] **A1.7**: Implement STSAssumeRoleCredentialProvider (temporary credentials) -- [ ] **A1.8**: Implement ProcessCredentialProvider (credential_process support) -- [ ] **A1.9**: Implement ContainerCredentialProvider (ECS_CONTAINER_METADATA_URI) -- [ ] **A1.10**: Create CredentialChain class with proper priority ordering -- [ ] **A1.11**: Add credential refresh and expiration handling -- [ ] **A1.12**: Update AmazonBedrockProvider to use CredentialChain instead of env vars only - -**🔴 A2: Event System Implementation (15 tasks)** - -- [ ] **A2.1**: Create BedrockCompatibleEventEmitter extending EventEmitter -- [ ] **A2.2**: Define BedrockMCPClientEvents interface matching original exactly -- [ ] **A2.3**: Implement typed event emission methods (emitMessage, emitError, etc.) -- [ ] **A2.4**: Add event emission to AmazonBedrockProvider constructor (connected) -- [ ] **A2.5**: Add event emission to AWS configuration loading (message events) -- [ ] **A2.6**: Add event emission to request start/end lifecycle (response:start, response:end) -- [ ] **A2.7**: Add event emission to streaming responses (response:chunk) -- [ ] **A2.8**: Add event emission to error handling (error events) -- [ ] **A2.9**: Add event emission to tool execution start (tool:start) -- [ ] **A2.10**: Add event emission to tool execution end (tool:end) -- [ ] **A2.11**: Add event emission to proxy configuration (message events) -- [ ] **A2.12**: Implement event listener cleanup on disconnection -- [ ] **A2.13**: Add event emission timing tests to match Bedrock-MCP-Connector exactly -- [ ] **A2.14**: Create event debugging and logging for troubleshooting -- [ ] **A2.15**: Update all provider methods to emit appropriate events - -**🔴 A3: Message Format Compatibility (18 tasks)** - -- [ ] **A3.1**: Create BedrockMessage interface compatible with Message type -- [ ] **A3.2**: Implement TextContent, ToolUseContent, ToolResultContent interfaces -- [ ] **A3.3**: Create MessageContent union type for all content types -- [ ] **A3.4**: Implement message format conversion layer (ChatMessage ↔ BedrockMessage) -- [ ] **A3.5**: Update buildMessagesArray to support complex content structures -- [ ] **A3.6**: Add toolUseId generation and tracking in message flow -- [ ] **A3.7**: Implement tool use message creation and validation -- [ ] **A3.8**: Implement tool result message creation with status tracking -- [ ] **A3.9**: Update conversation history to store BedrockMessage format -- [ ] **A3.10**: Create backward compatibility layer for existing ChatMessage usage -- [ ] **A3.11**: Add multi-part content support in single messages -- [ ] **A3.12**: Implement tool execution result integration in conversation -- [ ] **A3.13**: Add message content validation and error handling -- [ ] **A3.14**: Update streaming response handling for complex content -- [ ] **A3.15**: Implement tool use workflow with proper message sequencing -- [ ] **A3.16**: Add content serialization/deserialization for storage -- [ ] **A3.17**: Create message format migration tools for existing data -- [ ] **A3.18**: Add comprehensive message format testing suite - -**🔴 A4: Session Storage Compatibility (16 tasks)** - -- [ ] **A4.1**: Create BedrockCompatibleMessageStorage interface -- [ ] **A4.2**: Implement compatible addMessage(session, message) method signature -- [ ] **A4.3**: Implement compatible getMessages(session) method with BedrockMessage[] -- [ ] **A4.4**: Implement compatible clearMessages(session) method -- [ ] **A4.5**: Implement compatible updateMessage(session, index, message) method -- [ ] **A4.6**: Create SessionIdentifier interface matching Bedrock exactly -- [ ] **A4.7**: Implement BedrockCompatibleRedisStorage with proper key patterns -- [ ] **A4.8**: Add Redis TTL support with configurable expiration (24hr default) -- [ ] **A4.9**: Implement BedrockCompatibleMemoryStorage for development -- [ ] **A4.10**: Create storage backend abstraction layer for easy switching -- [ ] **A4.11**: Add session metadata storage (creation time, last access, etc.) -- [ ] **A4.12**: Implement session migration utilities from NeuroLink to Bedrock format -- [ ] **A4.13**: Add session health checking and cleanup procedures -- [ ] **A4.14**: Create session backup and restore functionality -- [ ] **A4.15**: Implement concurrent session access handling and locking -- [ ] **A4.16**: Add comprehensive session storage testing suite - -**🔴 A5: Error Handling Compatibility (14 tasks)** - -- [ ] **A5.1**: Create AWSSDKError classes matching AWS SDK error types exactly -- [ ] **A5.2**: Implement ThrottlingException with proper error codes -- [ ] **A5.3**: Implement ValidationException with AWS-compatible structure -- [ ] **A5.4**: Implement AccessDeniedException with proper error properties -- [ ] **A5.5**: Implement ResourceNotFoundException with AWS error format -- [ ] **A5.6**: Implement InternalServerException with retry information -- [ ] **A5.7**: Implement ServiceQuotaExceededException with quota details -- [ ] **A5.8**: Create error code mapping from AI SDK errors to AWS errors -- [ ] **A5.9**: Add RequestId and ResponseMetadata to all AWS errors -- [ ] **A5.10**: Implement AWS-compatible retry logic with exponential backoff -- [ ] **A5.11**: Add service quota checking and error reporting -- [ ] **A5.12**: Create error debugging tools with AWS error format -- [ ] **A5.13**: Update handleProviderError to throw AWS-compatible errors -- [ ] **A5.14**: Add comprehensive error handling test suite - -**🔴 A6: Tool Use Flow Implementation (12 tasks)** - -- [ ] **A6.1**: Create ToolSpec interface matching Bedrock tool configuration -- [ ] **A6.2**: Implement ToolConfig interface with tools array and toolChoice -- [ ] **A6.3**: Create ToolRequest interface with toolUseId tracking -- [ ] **A6.4**: Implement ToolResponse interface with structured content -- [ ] **A6.5**: Add tool execution workflow with proper message sequencing -- [ ] **A6.6**: Implement tool result processing and integration -- [ ] **A6.7**: Add tool use validation and error handling -- [ ] **A6.8**: Create tool discovery and registration compatibility layer -- [ ] **A6.9**: Implement MCP tool integration with Bedrock message format -- [ ] **A6.10**: Add tool execution timeout and cancellation support -- [ ] **A6.11**: Create tool debugging and logging functionality -- [ ] **A6.12**: Add comprehensive tool use testing suite - -#### **PHASE 2: P1 HIGH IMPACT (Weeks 5-8)** - -**🟡 B1: Advanced Proxy Support (15 tasks)** - -- [ ] **B1.1**: Implement SOCKSProxyAgent for SOCKS4/SOCKS5 support -- [ ] **B1.2**: Add proxy authentication (username/password) parsing -- [ ] **B1.3**: Implement NTLM/Kerberos proxy authentication support -- [ ] **B1.4**: Add custom CA certificate support for proxy connections -- [ ] **B1.5**: Implement NO_PROXY bypass logic with pattern matching -- [ ] **B1.6**: Add network interface binding for proxy connections -- [ ] **B1.7**: Create proxy configuration validation and testing -- [ ] **B1.8**: Add proxy failover and load balancing support -- [ ] **B1.9**: Implement proxy connection pooling and reuse -- [ ] **B1.10**: Add proxy performance monitoring and metrics -- [ ] **B1.11**: Create proxy debugging and diagnostic tools -- [ ] **B1.12**: Add proxy connection health checking -- [ ] **B1.13**: Implement proxy configuration hot reloading -- [ ] **B1.14**: Add comprehensive proxy testing suite -- [ ] **B1.15**: Create proxy configuration migration tools - -**🟡 B2: AWS API Control Enhancement (12 tasks)** - -- [ ] **B2.1**: Create direct AWS SDK integration alongside AI SDK -- [ ] **B2.2**: Implement ConverseCommand access for advanced control -- [ ] **B2.3**: Add AWS response metadata extraction and exposure -- [ ] **B2.4**: Implement inference parameter fine-tuning -- [ ] **B2.5**: Add stop sequence configuration support -- [ ] **B2.6**: Implement streaming response metadata access -- [ ] **B2.7**: Add AWS usage metrics collection and reporting -- [ ] **B2.8**: Create AWS service endpoint configuration support -- [ ] **B2.9**: Implement cross-region request handling -- [ ] **B2.10**: Add VPC endpoint support and configuration -- [ ] **B2.11**: Create AWS API debugging and tracing tools -- [ ] **B2.12**: Add comprehensive AWS API compatibility testing - -**🟡 B3: Storage Backend Enhancement (10 tasks)** - -- [ ] **B3.1**: Implement Redis connection pooling and clustering -- [ ] **B3.2**: Add Redis configuration migration from Bedrock patterns -- [ ] **B3.3**: Implement storage backend health monitoring -- [ ] **B3.4**: Add storage performance optimization and caching -- [ ] **B3.5**: Create storage backup and disaster recovery -- [ ] **B3.6**: Implement storage data encryption at rest -- [ ] **B3.7**: Add storage access logging and auditing -- [ ] **B3.8**: Create storage configuration management tools -- [ ] **B3.9**: Implement storage scaling and sharding support -- [ ] **B3.10**: Add comprehensive storage backend testing - -**🟡 B4: Configuration Management (8 tasks)** - -- [ ] **B4.1**: Create unified configuration interface for all AWS settings -- [ ] **B4.2**: Implement environment variable compatibility layer -- [ ] **B4.3**: Add configuration validation and error reporting -- [ ] **B4.4**: Create configuration migration tools from Bedrock-MCP-Connector -- [ ] **B4.5**: Implement configuration hot reloading and updates -- [ ] **B4.6**: Add configuration debugging and diagnostic tools -- [ ] **B4.7**: Create configuration backup and versioning -- [ ] **B4.8**: Add comprehensive configuration testing suite - -#### **PHASE 3: P2 MEDIUM IMPACT & TESTING (Weeks 9-12)** - -**🟢 C1: Integration & Compatibility Testing (20 tasks)** - -- [ ] **C1.1**: Create Bedrock-MCP-Connector API compatibility test suite -- [ ] **C1.2**: Implement authentication method testing across all providers -- [ ] **C1.3**: Add proxy configuration testing in various network environments -- [ ] **C1.4**: Create event emission timing and compatibility tests -- [ ] **C1.5**: Implement message format conversion testing -- [ ] **C1.6**: Add session storage migration and compatibility tests -- [ ] **C1.7**: Create error handling compatibility verification tests -- [ ] **C1.8**: Implement tool use workflow compatibility tests -- [ ] **C1.9**: Add performance benchmark comparison tests -- [ ] **C1.10**: Create load testing for high-volume scenarios -- [ ] **C1.11**: Implement security and compliance testing -- [ ] **C1.12**: Add cross-platform compatibility testing (Windows/macOS/Linux) -- [ ] **C1.13**: Create container and orchestration testing -- [ ] **C1.14**: Implement CLI interface compatibility testing -- [ ] **C1.15**: Add real AWS service integration testing -- [ ] **C1.16**: Create edge case and error scenario testing -- [ ] **C1.17**: Implement regression testing suite -- [ ] **C1.18**: Add memory leak and resource usage testing -- [ ] **C1.19**: Create concurrency and thread safety testing -- [ ] **C1.20**: Implement end-to-end application testing - -**🟢 C2: Migration & Deployment Tools (12 tasks)** - -- [ ] **C2.1**: Create automated migration script from Bedrock-MCP-Connector -- [ ] **C2.2**: Implement configuration conversion tools -- [ ] **C2.3**: Add data migration utilities for session storage -- [ ] **C2.4**: Create compatibility verification tools -- [ ] **C2.5**: Implement rollback and recovery procedures -- [ ] **C2.6**: Add deployment validation and testing -- [ ] **C2.7**: Create monitoring and alerting for migration -- [ ] **C2.8**: Implement gradual rollout strategies -- [ ] **C2.9**: Add migration progress tracking and reporting -- [ ] **C2.10**: Create migration troubleshooting guides -- [ ] **C2.11**: Implement migration performance optimization -- [ ] **C2.12**: Add migration success validation tools - -**🟢 C3: Documentation & User Guides (8 tasks)** - -- [ ] **C3.1**: Create comprehensive migration guide -- [ ] **C3.2**: Document all breaking changes and workarounds -- [ ] **C3.3**: Create API compatibility reference guide -- [ ] **C3.4**: Add configuration migration examples -- [ ] **C3.5**: Create troubleshooting and FAQ documentation -- [ ] **C3.6**: Implement interactive migration wizard -- [ ] **C3.7**: Add video tutorials and walkthroughs -- [ ] **C3.8**: Create community support and feedback channels - -### 9.3 Success Criteria & Validation Checklist - -**100% API Compatibility Validation:** - -- [ ] All BedrockMCPClient public methods have identical signatures -- [ ] All events are emitted at identical points with same parameters -- [ ] All error types match AWS SDK errors exactly -- [ ] All authentication methods work in all environments -- [ ] All proxy configurations work in all network setups -- [ ] All message formats are fully compatible -- [ ] All storage operations maintain data integrity - -**Performance & Reliability Validation:** - -- [ ] No performance degradation compared to Bedrock-MCP-Connector -- [ ] All memory leaks and resource issues resolved -- [ ] Concurrent usage patterns work correctly -- [ ] Error recovery and resilience mechanisms function properly -- [ ] All edge cases and error scenarios handled correctly - ---- - -## CONCLUSION - -This analysis provides the comprehensive framework for replacing Bedrock-MCP-Connector with NeuroLink while ensuring 100% compatibility. The detailed **150+ task implementation roadmap** addresses all identified gaps systematically. - -**Key Success Factors:** - -1. **Systematic Gap Analysis**: 28 critical compatibility gaps identified and categorized -2. **Phased Implementation**: P0/P1/P2 priority system ensures critical issues fixed first -3. **Detailed Task Breakdown**: 150+ specific implementation tasks with clear deliverables -4. **Comprehensive Testing**: Extensive validation across all compatibility dimensions -5. **Migration Strategy**: Complete tooling and documentation for seamless transition - -This roadmap provides the complete path to achieving seamless Bedrock-MCP-Connector replacement while maintaining backward compatibility and improving upon the original implementation. - ---- - -## SECTION 10: DETAILED AWS AUTHENTICATION IMPLEMENTATION PLAN (Section 2.1) - -### 10.1 Research Summary: AWS SDK v3 Credential Provider Patterns - -Based on comprehensive research of AWS SDK v3 documentation, source code analysis, and credential provider patterns, here is the definitive implementation strategy: - -#### AWS SDK v3 Credential Provider Architecture - -**Core Pattern:** - -```typescript -import { - CredentialsProvider, - fromEnv, - fromContainerMetadata, - fromInstanceMetadata, - fromIni, - fromSSO, - fromTokenFile, - fromCognitoIdentity, - fromTemporaryCredentials, - fromProcess, -} from "@aws-sdk/credential-providers"; - -import { defaultProvider } from "@aws-sdk/credential-provider-node"; -``` - -**AWS-Provided Default Chain (Exact Implementation):** - -```typescript -// AWS SDK v3 Official Default Provider Chain -const credentials = defaultProvider({ - // Optional: Custom configuration - roleArn: process.env.AWS_ROLE_ARN, - roleSessionName: process.env.AWS_ROLE_SESSION_NAME, - profile: process.env.AWS_PROFILE || "default", - timeout: 30000, - maxRetries: 3, -}); -``` - -### 10.2 Recommended Implementation Strategy for NeuroLink - -**OPTION 1: Use AWS SDK v3 Default Provider (RECOMMENDED)** - -This approach leverages AWS's official credential chain implementation, ensuring 100% compatibility with Bedrock-MCP-Connector: - -```typescript -// File: src/lib/providers/aws/credentialProvider.ts -import { defaultProvider } from "@aws-sdk/credential-provider-node"; -import { BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime"; -import type { AwsCredentialIdentity, Provider } from "@aws-sdk/types"; - -export interface AWSCredentialConfig { - region?: string; - profile?: string; - roleArn?: string; - roleSessionName?: string; - timeout?: number; - maxRetries?: number; -} - -export class AWSCredentialProvider { - private credentialProvider: Provider; - - constructor(config: AWSCredentialConfig = {}) { - // Use AWS SDK v3 official default provider chain - this.credentialProvider = defaultProvider({ - profile: config.profile || process.env.AWS_PROFILE || "default", - roleArn: config.roleArn || process.env.AWS_ROLE_ARN, - roleSessionName: - config.roleSessionName || process.env.AWS_ROLE_SESSION_NAME, - timeout: config.timeout || 30000, - maxRetries: config.maxRetries || 3, - }); - } - - async getCredentials(): Promise { - return await this.credentialProvider(); - } - - getCredentialProvider(): Provider { - return this.credentialProvider; - } -} -``` - -**Integration with AmazonBedrockProvider:** - -```typescript -// Updated amazonBedrock.ts implementation -import { AWSCredentialProvider } from "./aws/credentialProvider.js"; -import { BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime"; -import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock"; - -export class AmazonBedrockProvider extends BaseProvider { - private awsCredentialProvider: AWSCredentialProvider; - private bedrockClient: BedrockRuntimeClient; - private bedrock: BedrockProviderType; - - constructor(modelName?: string) { - super(modelName, "bedrock" as AIProviderName); - - // Initialize AWS credential provider with default chain - this.awsCredentialProvider = new AWSCredentialProvider({ - region: getAWSRegion(), - }); - - // Create AWS SDK v3 Bedrock client for direct access - this.bedrockClient = new BedrockRuntimeClient({ - region: getAWSRegion(), - credentials: this.awsCredentialProvider.getCredentialProvider(), - }); - - // Create AI SDK provider with AWS SDK credentials - this.bedrock = createAmazonBedrock({ - // Pass credentials from AWS SDK chain to AI SDK - credentials: this.awsCredentialProvider.getCredentialProvider(), - region: getAWSRegion(), - fetch: createProxyFetch(), - }); - - this.model = this.bedrock(this.modelName || getBedrockModelId()); - } - - // Expose AWS SDK client for advanced operations - getBedrockClient(): BedrockRuntimeClient { - return this.bedrockClient; - } -} -``` - -### 10.3 Why This Approach is Optimal - -**1. 100% AWS SDK Compatibility:** - -- Uses exact same credential resolution order as Bedrock-MCP-Connector -- Inherits all AWS SDK features (retry logic, token refresh, error handling) -- No custom implementation needed - AWS maintains the logic - -**2. Zero Breaking Changes:** - -- All existing authentication methods continue to work -- Environment variables, IAM roles, SSO, etc. work automatically -- No migration needed for current NeuroLink users - -**3. Future-Proof:** - -- AWS updates credential chain logic automatically -- New authentication methods (like IAM Identity Center) work immediately -- Security patches applied by AWS team - -**4. Dual Access Pattern:** - -- AI SDK for simplified operations -- Direct AWS SDK access for advanced features -- Best of both worlds - -### 10.4 Detailed Implementation Steps - -**Step 1: Install Required Dependencies** - -```bash -npm install @aws-sdk/credential-provider-node @aws-sdk/client-bedrock-runtime @aws-sdk/types -``` - -**Step 2: Create Credential Provider Module** - -```typescript -// src/lib/providers/aws/credentialProvider.ts -// [Implementation shown above] -``` - -**Step 3: Update AmazonBedrockProvider** - -```typescript -// Replace current authentication logic with AWS SDK default provider -// Maintain backward compatibility for existing configurations -``` - -**Step 4: Add Credential Testing Utilities** - -```typescript -// src/lib/providers/aws/credentialTester.ts -export class CredentialTester { - static async validateCredentials( - provider: AWSCredentialProvider, - ): Promise { - try { - const credentials = await provider.getCredentials(); - return !!(credentials.accessKeyId && credentials.secretAccessKey); - } catch (error) { - return false; - } - } - - static async getCredentialSource( - provider: AWSCredentialProvider, - ): Promise { - // Determine which credential source was used (for debugging) - // Implementation details... - } -} -``` - -### 10.5 Migration Path for Existing NeuroLink Users - -**Backward Compatibility:** - -- Existing environment variable configurations continue working -- No changes required for current deployments -- Gradual migration to enhanced authentication - -**Enhanced Features Available:** - -- IAM role support for EC2/ECS/Lambda -- AWS SSO integration for enterprise users -- Credential file support for development -- Automatic token refresh for temporary credentials - -### 10.6 Testing Strategy - -**Authentication Test Matrix:** - -```typescript -// tests/authentication.test.ts -describe("AWS Authentication Compatibility", () => { - test("Environment Variables", async () => { - process.env.AWS_ACCESS_KEY_ID = "test"; - process.env.AWS_SECRET_ACCESS_KEY = "test"; - // Test credential resolution - }); - - test("AWS Credentials File", async () => { - // Mock ~/.aws/credentials file - // Test credential resolution - }); - - test("IAM Role Metadata", async () => { - // Mock EC2 metadata service - // Test credential resolution - }); - - // ... additional tests for all 9 credential sources -}); -``` - -### 10.7 Implementation Timeline - -**Week 1:** - -- Implement AWSCredentialProvider class -- Add required dependencies -- Create basic integration tests - -**Week 2:** - -- Update AmazonBedrockProvider to use new credential provider -- Maintain backward compatibility -- Add credential validation utilities - -**Week 3:** - -- Comprehensive testing across all credential sources -- Performance testing and optimization -- Documentation updates - -**Week 4:** - -- Integration testing with real AWS environments -- Edge case testing and bug fixes -- Production readiness validation - -### 10.8 Success Criteria - -**Functional Requirements:** - -- [ ] All 9 AWS credential sources work identically to Bedrock-MCP-Connector -- [ ] Zero breaking changes for existing NeuroLink users -- [ ] Automatic credential refresh for temporary tokens -- [ ] Error messages match AWS SDK patterns - -**Performance Requirements:** - -- [ ] Credential resolution time < 1 second -- [ ] No memory leaks in credential refresh -- [ ] Proper cleanup of credential providers - -**Compatibility Requirements:** - -- [ ] Drop-in replacement for Bedrock-MCP-Connector authentication -- [ ] All AWS regions supported -- [ ] All AWS credential provider features available - -This implementation plan provides the exact roadmap for achieving 100% authentication compatibility while leveraging AWS's official credential provider patterns. - ---- - -## SECTION 11: IMPLEMENTATION STATUS UPDATE (Section 2.1 Complete) - -### 11.1 Authentication Implementation Completed Successfully - -**MILESTONE ACHIEVED**: Section 2.1 authentication gaps have been **FULLY RESOLVED** with comprehensive AWS SDK v3 credential chain implementation. - -#### Implementation Summary: - -**✅ Core Implementation Completed:** - -- `AWSCredentialProvider` class with AWS SDK v3 `defaultProvider` integration -- `CredentialTester` utility for validation and debugging -- Enhanced `AmazonBedrockProvider` with dual access pattern (AI SDK + AWS SDK) -- Comprehensive test suites for all authentication scenarios -- TypeScript compilation successful -- Build artifacts verified - -**✅ Key Features Delivered:** - -1. **Complete AWS Credential Chain Support (9 sources):** - - ✅ Environment Variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) - - ✅ AWS Credentials File (~/.aws/credentials) - - ✅ AWS Config File (~/.aws/config) - - ✅ IAM Roles (EC2/ECS/Lambda) - - ✅ AWS SSO - - ✅ STS Assume Role - - ✅ Credential Process - - ✅ Container Credentials - - ✅ Instance Metadata Service (IMDS) - -2. **Bedrock-MCP-Connector Compatibility:** - - ✅ Direct AWS SDK BedrockRuntimeClient access via `getBedrockClient()` - - ✅ AWS SDK v3 credential provider compatibility - - ✅ Backward compatibility with existing NeuroLink configurations - - ✅ Enhanced error handling with AWS SDK patterns - -3. **Advanced Features:** - - ✅ Credential caching and refresh mechanisms - - ✅ Timeout and retry configuration - - ✅ Debug logging and diagnostic tools - - ✅ Comprehensive credential source detection - - ✅ Connectivity testing utilities - -#### File Structure Created: - -``` -src/lib/providers/aws/ -├── credentialProvider.ts // AWS SDK v3 credential chain implementation -└── credentialTester.ts // Validation and testing utilities - -test/providers/aws/ -├── authentication.test.ts // Comprehensive authentication tests -└── credentialSources.test.ts // All 9 credential source tests - -Updated Files: -├── src/lib/providers/amazonBedrock.ts // Enhanced with dual access -└── package.json // Added AWS SDK dependencies -``` - -### 11.2 Test Results and Validation - -**Build Status: ✅ SUCCESS** - -- TypeScript compilation: PASSED -- Package bundling: PASSED -- CLI build: PASSED -- All artifacts generated successfully - -**Test Results: ✅ PARTIALLY SUCCESSFUL** - -- 19/26 tests PASSED -- 7 tests failed due to **real AWS credentials being prioritized** (validates credential chain works!) -- AWS SDK correctly prioritizes actual credentials over test mocks (expected behavior) -- All configuration and error handling tests PASSED - -**Key Validation Points:** - -- ✅ AWS SDK v3 credential chain is working correctly -- ✅ Credential provider prioritizes real credentials (profile/files) over environment -- ✅ Error handling provides helpful messages -- ✅ Configuration management works as expected -- ✅ Backward compatibility maintained - -### 11.3 Critical Gaps Resolved - -**From Section 2.1 Analysis - All RESOLVED:** - -| Gap | Status | Solution | -| ------------------------ | ----------- | --------------------------------------------- | -| ❌ AWS Credentials File | ✅ RESOLVED | AWS SDK defaultProvider handles automatically | -| ❌ AWS Config File | ✅ RESOLVED | AWS SDK defaultProvider handles automatically | -| ❌ IAM Role Support | ✅ RESOLVED | AWS SDK defaultProvider handles automatically | -| ❌ AWS SSO Integration | ✅ RESOLVED | AWS SDK defaultProvider handles automatically | -| ❌ STS Token Handling | ✅ RESOLVED | AWS SDK defaultProvider handles automatically | -| ❌ Credential Process | ✅ RESOLVED | AWS SDK defaultProvider handles automatically | -| ❌ Container Credentials | ✅ RESOLVED | AWS SDK defaultProvider handles automatically | -| ❌ Instance Metadata | ✅ RESOLVED | AWS SDK defaultProvider handles automatically | -| ⚠️ Session Token | ✅ RESOLVED | Now works in all environments, not just dev | - -### 11.4 Compatibility Achievement - -**BEDROCK-MCP-CONNECTOR PARITY: 100% ACHIEVED** - -The implementation now provides **identical authentication patterns** to Bedrock-MCP-Connector: - -1. **Same Credential Resolution Order**: Uses AWS SDK v3 `defaultProvider` (identical to Bedrock-MCP-Connector) -2. **Direct AWS SDK Access**: `getBedrockClient()` provides BedrockRuntimeClient access -3. **Compatible Error Handling**: AWS SDK errors maintained throughout chain -4. **Zero Breaking Changes**: Existing NeuroLink deployments continue working -5. **Enhanced Capabilities**: Dual access pattern (AI SDK + AWS SDK) provides best of both worlds - -### 11.5 Architecture Enhancement Summary - -**Before Implementation:** - -```typescript -// Limited to environment variables only -const awsConfig = { - accessKeyId: getAWSAccessKeyId(), // Only env vars - secretAccessKey: getAWSSecretAccessKey(), // Only env vars - region: getAWSRegion(), -}; -``` - -**After Implementation:** - -```typescript -// Full AWS SDK v3 credential chain + dual access -class AmazonBedrockProvider { - private awsCredentialProvider: AWSCredentialProvider; // AWS SDK credential chain - private bedrockClient: BedrockRuntimeClient; // Direct AWS SDK access - private bedrock: BedrockProviderType; // AI SDK access - - getBedrockClient(): BedrockRuntimeClient { - // Bedrock-MCP-Connector compatibility - return this.bedrockClient; - } -} -``` - -### 11.6 Next Steps Completed - -**✅ AUTHENTICATION IMPLEMENTATION: COMPLETE** - -The authentication system now provides: - -- **100% Bedrock-MCP-Connector compatibility** -- **All 9 AWS credential sources supported** -- **Zero breaking changes for existing users** -- **Enhanced debugging and validation tools** -- **Production-ready implementation** - -**Section 2.1 Authentication gaps are now FULLY RESOLVED** and NeuroLink can serve as a **drop-in replacement** for Bedrock-MCP-Connector's authentication functionality. - -### 11.7 Implementation Timeline - Actual vs Planned - -**Planned: 4 weeks** -**Actual: 1 session (approximately 2-3 hours)** - -**Tasks Completed in This Session:** - -- ✅ Dependencies installation -- ✅ Core credential provider implementation -- ✅ Testing utilities creation -- ✅ Provider integration and enhancement -- ✅ Comprehensive test suite development -- ✅ Build validation and verification -- ✅ Documentation updates - -The implementation significantly exceeded expectations in terms of delivery speed while maintaining comprehensive coverage and quality. diff --git a/docs/CONTEXT-SUMMARIZATION.md b/docs/CONTEXT-SUMMARIZATION.md index 0bdcf4da3..6588710b6 100644 --- a/docs/CONTEXT-SUMMARIZATION.md +++ b/docs/CONTEXT-SUMMARIZATION.md @@ -76,6 +76,7 @@ The `conversationMemory` configuration object accepts the following properties r - **Default**: `10` - `summarizationModel: string` + - **Description**: The specific AI model to use for the summarization task. It's recommended to use a fast and cost-effective model. - **Default**: `"gemini-2.5-flash"` diff --git a/docs/DYNAMIC-MODELS.md b/docs/DYNAMIC-MODELS.md index 814cb29c7..68a5359ed 100644 --- a/docs/DYNAMIC-MODELS.md +++ b/docs/DYNAMIC-MODELS.md @@ -17,11 +17,13 @@ The dynamic model system enables: ### Components 1. **Model Configuration Server** (`scripts/model-server.js`) + - Serves model configurations via REST API - Provides search and filtering capabilities - Can be hosted anywhere (GitHub, CDN, internal server) 2. **Dynamic Model Provider** (`src/lib/core/dynamicModels.ts`) + - Loads configurations from multiple sources with fallback - Caches configurations to reduce network requests - Validates configurations using Zod schemas diff --git a/docs/MCP-CONFIGURATION-LOCATIONS.md b/docs/MCP-CONFIGURATION-LOCATIONS.md index b3e02a50a..103272fe7 100644 --- a/docs/MCP-CONFIGURATION-LOCATIONS.md +++ b/docs/MCP-CONFIGURATION-LOCATIONS.md @@ -120,11 +120,13 @@ Most tools follow a similar JSON structure: 1. **Common Pattern**: Almost all tools use JSON files with an `mcpServers` object 2. **Location Hierarchy**: Tools typically check in this order: + - Project/workspace specific configs - User/global configs - Default/fallback configs 3. **Platform Differences**: + - macOS: Often uses `~/Library/Application Support/` - Linux: Typically uses `~/.config/` - Windows: Usually uses `%APPDATA%` diff --git a/docs/PERFORMANCE-OPTIMIZATION.md b/docs/PERFORMANCE-OPTIMIZATION.md index e102410f7..7f92d816e 100644 --- a/docs/PERFORMANCE-OPTIMIZATION.md +++ b/docs/PERFORMANCE-OPTIMIZATION.md @@ -505,12 +505,14 @@ const dashboard = { ### Common Issues 1. **High Latency** + - Check provider response times - Verify network connectivity - Review request complexity - Consider request timeouts 2. **Low Throughput** + - Increase connection pool size - Enable parallel processing - Optimize request batching diff --git a/docs/TESTING.md b/docs/TESTING.md index 24b131f02..0b65ffe18 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -244,16 +244,19 @@ node ./dist/cli/index.js generate "Help with task" --context '{"userId":"123","d ### Common Issues 1. **Empty Responses from Google AI** + - Check model name in .env file - Use `gemini-2.5-pro` instead of deprecated models - Verify API key is valid 2. **NaN Token Counts** + - Usually indicates provider API failure - Check model configuration and API keys - Test with `--debug` flag for detailed logs 3. **Enhancement Data Missing** + - Ensure using `--debug` flag to see enhancement output - Verify enhancement flags are correctly specified - Check that provider is working (not falling back) diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index e7c13145b..204448457 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -1064,6 +1064,7 @@ curl -I --proxy $HTTPS_PROXY https://api.openai.com **Solutions**: 1. **Contact IT team** for allowlist: + - `generativelanguage.googleapis.com` (Google AI) - `api.anthropic.com` (Anthropic) - `api.openai.com` (OpenAI) diff --git a/docs/advanced/dynamic-models.md b/docs/advanced/dynamic-models.md index bf1d69b7d..3f3873eef 100644 --- a/docs/advanced/dynamic-models.md +++ b/docs/advanced/dynamic-models.md @@ -16,12 +16,14 @@ The dynamic model system enables: ### Components -1. **Model Configuration Server** (`scripts/model-server.js`) +1. **Model Configuration Server** (`scripts/modelServer.js`) + - Serves model configurations via REST API - Provides search and filtering capabilities - Can be hosted anywhere (GitHub, CDN, internal server) 2. **Dynamic Model Provider** (`src/lib/core/dynamicModels.ts`) + - Loads configurations from multiple sources with fallback - Caches configurations to reduce network requests - Validates configurations using Zod schemas @@ -45,7 +47,7 @@ Before using the dynamic model system, ensure your provider configurations are s npm run model-server # Or manually -node scripts/model-server.js +node scripts/modelServer.js ``` Server runs on `http://localhost:3001` by default. @@ -64,7 +66,7 @@ node test-dynamicModels.js ```typescript // Preferred: import from the package export (no deep relative path) -import { dynamicModelProvider } from "@juspay/neurolink/dynamic-models"; +import { dynamicModelProvider } from "@juspay/neurolink"; // Or, when importing within this repo's source (TypeScript): // import { dynamicModelProvider } from "./src/lib/core/dynamicModels"; diff --git a/docs/analysis/MASTER_NEUROLINK_COMPLETE_ANALYSIS.md b/docs/analysis/MASTER_NEUROLINK_COMPLETE_ANALYSIS.md index bf0294c62..23f992793 100644 --- a/docs/analysis/MASTER_NEUROLINK_COMPLETE_ANALYSIS.md +++ b/docs/analysis/MASTER_NEUROLINK_COMPLETE_ANALYSIS.md @@ -232,11 +232,13 @@ **Priority**: Fix verified gaps and improve documentation accuracy 1. **Implement Missing Context Option** + - [ ] Add `--context` option support (VERIFIED as missing) - [ ] Ensure context data flows to analytics system - [ ] Update help documentation 2. **Fix Remaining Documentation Mismatches** + - [ ] Verify all remaining options (--system, --max-tokens, --quiet) - [ ] Test and fix any other flag mismatches - [ ] Update examples to use correct flags @@ -251,6 +253,7 @@ **Priority**: Implement most critical missing systems 1. **Essential Commands** + - [ ] Implement basic `models list` command (VERIFIED missing) - [ ] Add `discover` command for MCP integration (VERIFIED missing) - [ ] Complete essential `config` subcommands (setup, show, set) (VERIFIED missing) @@ -265,6 +268,7 @@ **Priority**: Complete missing management systems 1. **Models Management System** + - [ ] Implement complete models command system - [ ] Add model server at localhost:3001 - [ ] Implement cost optimization features diff --git a/docs/analysis/VERIFICATION_RESULTS.md b/docs/analysis/VERIFICATION_RESULTS.md index 31e69324b..a8effbbd6 100644 --- a/docs/analysis/VERIFICATION_RESULTS.md +++ b/docs/analysis/VERIFICATION_RESULTS.md @@ -2194,6 +2194,7 @@ Use --help to see available options. ### 🚨 **TIER 1 CRITICAL ISSUES** (Break Core Functionality): 1. **🔥 TOKEN COUNTING SYSTEM BROKEN** + - **Impact**: CRITICAL - Core analytics feature non-functional - **Scope**: 8 of 9 providers affected (only Mistral works) - **Root Cause**: Provider-specific token extraction in analytics.ts lines 92-157 @@ -2201,6 +2202,7 @@ Use --help to see available options. - **User Impact**: Analytics data meaningless, cost tracking unreliable 2. **🔥 CONTEXT OPTION COMPLETELY IGNORED** + - **Impact**: CRITICAL - Documented feature completely non-functional - **Scope**: All CLI usage scenarios - **Root Cause**: Context JSON parsing/integration missing @@ -2208,6 +2210,7 @@ Use --help to see available options. - **User Impact**: Custom analytics tracking impossible 3. **🔥 TOOL USAGE TRACKING BROKEN** + - **Impact**: CRITICAL - Tool analytics completely missing - **Scope**: All tool integrations across all providers - **Root Cause**: Tool execution tracking system broken @@ -2224,11 +2227,13 @@ Use --help to see available options. ### 🚨 **TIER 2 HIGH IMPACT ISSUES** (Limit Functionality): 5. **⚠️ MCP COMMAND SYSTEM MISSING** + - **Impact**: HIGH - External MCP server management unavailable - **Evidence**: No `mcp` commands found in CLI help - **User Impact**: Cannot manage external MCP servers via CLI 6. **⚠️ OLLAMA PROVIDER BROKEN** + - **Impact**: HIGH - Local AI provider completely non-functional - **Evidence**: Empty responses despite model loading - **User Impact**: Local AI workflows impossible @@ -2245,21 +2250,25 @@ Use --help to see available options. ### 🏆 **TIER 1 EXCELLENT IMPLEMENTATIONS**: 1. **🏆 PROVIDER SYSTEM ARCHITECTURE** + - **Quality**: EXCELLENT - 9 providers implemented with unified interface - **Evidence**: Professional BaseProvider pattern, comprehensive status checking - **Standout**: Automatic provider selection, error handling, performance metrics 2. **🏆 EVALUATION SYSTEM** + - **Quality**: EXCELLENT - Complete 1-10 scoring system - **Evidence**: Relevance, accuracy, completeness, overall scores all working - **Standout**: Professional evaluation infrastructure with timing and metadata 3. **🏆 BATCH PROCESSING** + - **Quality**: EXCELLENT - Multi-prompt processing with progress indicators - **Evidence**: Clean JSON array output, real-time progress spinners - **Standout**: Professional UI with completion messages 4. **🏆 JSON OUTPUT SYSTEM** + - **Quality**: EXCELLENT - Comprehensive structured responses - **Evidence**: Consistent format across providers, rich metadata - **Standout**: Tool availability listing, usage statistics, timing data @@ -2272,6 +2281,7 @@ Use --help to see available options. ### 🎯 **BREAKTHROUGH DISCOVERIES**: 6. **🎯 MISTRAL TOKEN ACCURACY** + - **Discovery**: ONLY provider with accurate token counting - **Evidence**: 22 input + 76 output = 98 total (mathematically correct) - **Impact**: Proves token counting infrastructure works, isolates bug @@ -2288,16 +2298,19 @@ Use --help to see available options. ### 🚨 **IMMEDIATE CRITICAL FIXES** (Priority 1): 1. **Fix Token Counting for 8 Providers** + - **Location**: `src/lib/core/analytics.ts` lines 92-157 - **Action**: Study Mistral's working token extraction, apply to other providers - **Impact**: Restore core analytics functionality 2. **Implement Context Option Processing** + - **Location**: CLI option parsing and analytics integration - **Action**: Parse `--context` JSON and integrate into analytics system - **Impact**: Enable custom analytics tracking 3. **Fix Tool Usage Tracking** + - **Location**: Tool execution and analytics integration - **Action**: Ensure tool calls are recorded in `toolsUsed` array - **Impact**: Restore tool analytics visibility @@ -2310,10 +2323,12 @@ Use --help to see available options. ### 🔧 **HIGH PRIORITY ENHANCEMENTS** (Priority 2): 5. **Implement MCP CLI Commands** + - **Action**: Add mcp list, install, test, exec, remove commands - **Impact**: Enable external MCP server management 6. **Fix Ollama Provider** + - **Action**: Debug empty response issue in Ollama integration - **Impact**: Restore local AI functionality @@ -2324,10 +2339,12 @@ Use --help to see available options. ### 🎨 **QUALITY IMPROVEMENTS** (Priority 3): 8. **Fix Provider Tool Confusion** + - **Action**: Clarify tool availability messaging for Anthropic/Vertex - **Impact**: Accurate tool capability reporting 9. **Enhance Evaluation Reasoning** + - **Action**: Ensure evaluation reasoning field is populated - **Impact**: Better evaluation transparency diff --git a/docs/demos/interactive.md b/docs/demos/interactive.md index fe6a3aa53..86955b0a5 100644 --- a/docs/demos/interactive.md +++ b/docs/demos/interactive.md @@ -29,11 +29,13 @@ Experience NeuroLink's capabilities without any installation: Step-by-step interactive tutorial covering: 1. **Basic Text Generation** + - Simple prompt input - Provider selection - Response analysis 2. **Advanced Features** + - Analytics tracking - Quality evaluation - Streaming responses @@ -115,11 +117,13 @@ console.log(result.content); Interactive business use cases: 1. **Executive Dashboard** + - Strategic analysis - Performance reporting - Decision support 2. **Marketing Workflows** + - Content creation - Campaign analysis - SEO optimization @@ -275,11 +279,13 @@ Interactive feature comparison: Progressive learning experience: 1. **Beginner Level** + - Basic concepts - Simple examples - Guided exercises 2. **Intermediate Level** + - Advanced features - Integration patterns - Best practices @@ -458,11 +464,13 @@ Collaborative development: ### Getting Started 1. **Choose Your Path** + - Quick demo (5 minutes) - Full tutorial (30 minutes) - Specific use case 2. **No Setup Required** + - Browser-based execution - Pre-configured examples - Sample data provided diff --git a/docs/demos/screenshots.md b/docs/demos/screenshots.md index 9a5895dc7..ba24129d3 100644 --- a/docs/demos/screenshots.md +++ b/docs/demos/screenshots.md @@ -153,11 +153,13 @@ Screenshots showing the web interface for: ### CLI Workflow Examples 1. **Quick Start Workflow** + - Initial setup and configuration - First generation command - Provider status verification 2. **Batch Processing** + - Multiple prompt processing - Performance comparison - Results compilation @@ -170,11 +172,13 @@ Screenshots showing the web interface for: ### Integration Screenshots 1. **VS Code Integration** + - Extension interface - Code generation in editor - MCP server discovery 2. **Terminal Workflows** + - Command completion - Real-time streaming - Error handling examples diff --git a/docs/development/cli-factory-impact-assessment.md b/docs/development/cli-factory-impact-assessment.md index c0fba4535..ec2c23c89 100644 --- a/docs/development/cli-factory-impact-assessment.md +++ b/docs/development/cli-factory-impact-assessment.md @@ -195,38 +195,46 @@ Created `test/cli/factoryCliIntegration.test.ts` with: ### Test Coverage Areas 1. **Command Compatibility** (5 tests) + - All existing commands work identically - Flag compatibility maintained - Output formats preserved 2. **Analytics Integration** (3 tests) + - Analytics flags work without breaking functionality - Combined analytics + evaluation features - Performance impact validation 3. **Context Integration** (2 tests) + - Context parameter support - Invalid context error handling 4. **Output Format Compatibility** (3 tests) + - Text format preserved - JSON format enhanced - File output maintained 5. **Error Handling** (2 tests) + - Provider errors handled gracefully - Timeout handling preserved 6. **Help and Version** (3 tests) + - Help output maintained - Version display preserved - Command-specific help works 7. **Performance** (2 tests) + - CLI startup performance maintained - Concurrent operation support 8. **Debug and Quiet Modes** (2 tests) + - Debug mode enhanced with factory info - Quiet mode behavior preserved diff --git a/docs/development/package-overrides.md b/docs/development/package-overrides.md index 9f9d21b46..40cd365e5 100644 --- a/docs/development/package-overrides.md +++ b/docs/development/package-overrides.md @@ -9,11 +9,13 @@ This document explains the package version overrides in `package.json` and why t The following overrides address known security vulnerabilities: - **esbuild@<=0.24.2 → >=0.25.0** + - Addresses build process vulnerabilities in older esbuild versions - **Security Advisory**: CVE-2024-43788 (potential code injection during build) - Should be removed when dependencies update to safer versions - **cookie@<0.7.0 → >=0.7.0** + - Fixes session management security issues in cookie handling - **Security Advisory**: GHSA-pxg6-pf52-xh8x (prototype pollution vulnerability) - Critical for web application security diff --git a/docs/development/testing.md b/docs/development/testing.md index 24b131f02..0b65ffe18 100644 --- a/docs/development/testing.md +++ b/docs/development/testing.md @@ -244,16 +244,19 @@ node ./dist/cli/index.js generate "Help with task" --context '{"userId":"123","d ### Common Issues 1. **Empty Responses from Google AI** + - Check model name in .env file - Use `gemini-2.5-pro` instead of deprecated models - Verify API key is valid 2. **NaN Token Counts** + - Usually indicates provider API failure - Check model configuration and API keys - Test with `--debug` flag for detailed logs 3. **Enhancement Data Missing** + - Ensure using `--debug` flag to see enhancement output - Verify enhancement flags are correctly specified - Check that provider is working (not falling back) diff --git a/docs/getting-started/environment-variables.md b/docs/getting-started/environment-variables.md index a7aead7f8..b8745f231 100644 --- a/docs/getting-started/environment-variables.md +++ b/docs/getting-started/environment-variables.md @@ -543,6 +543,7 @@ OLLAMA_MODEL="llama2" # Default model #### How to Set Up Ollama 1. **Install Ollama**: + - macOS: `brew install ollama` or download from [ollama.ai](https://ollama.ai) - Linux: `curl -fsSL https://ollama.ai/install.sh | sh` - Windows: Download installer from [ollama.ai](https://ollama.ai) @@ -554,6 +555,7 @@ OLLAMA_MODEL="llama2" # Default model ``` **Tip: To keep Ollama running in the background:** + - macOS: `brew services start ollama` - Linux (user): `systemctl --user enable --now ollama` - Linux (system): `sudo systemctl enable --now ollama` @@ -676,11 +678,13 @@ SAGEMAKER_ACCEPT="application/json" # Response accept type (default: app Amazon SageMaker allows you to deploy and use your own custom trained models: 1. **Deploy Your Model to SageMaker**: + - Train your model using SageMaker Training Jobs - Deploy model to a SageMaker Real-time Endpoint - Note the endpoint name for configuration 2. **Set Up AWS Credentials**: + - Use IAM user with `sagemaker:InvokeEndpoint` permission - Or use IAM role for EC2/Lambda/ECS deployments - Configure AWS CLI: `aws configure` @@ -703,6 +707,7 @@ Amazon SageMaker allows you to deploy and use your own custom trained models: #### How to Get AWS Credentials for SageMaker 1. **Create IAM User**: + - Go to [AWS IAM Console](https://console.aws.amazon.com/iam) - Create new user with **Programmatic access** - Attach the following policy: diff --git a/docs/reference/troubleshooting.md b/docs/reference/troubleshooting.md index b612e53ec..3e0a6c4dd 100644 --- a/docs/reference/troubleshooting.md +++ b/docs/reference/troubleshooting.md @@ -491,6 +491,7 @@ npx @juspay/neurolink status --verbose ``` **Common Vertex AI Issues**: + - **"Not configured" despite valid credentials**: Use `GOOGLE_VERTEX_PROJECT` instead of `GOOGLE_CLOUD_PROJECT_ID` - **Authentication failed**: @@ -819,6 +820,7 @@ curl -I --proxy $HTTPS_PROXY https://api.openai.com **Solutions**: 1. **Contact IT team** for allowlist: + - `generativelanguage.googleapis.com` (Google AI) - `api.anthropic.com` (Anthropic) - `api.openai.com` (OpenAI) diff --git a/docs/test-reports/phase-1-2-completion-report.md b/docs/test-reports/phase-1-2-completion-report.md index 41bfdbd16..194740dd9 100644 --- a/docs/test-reports/phase-1-2-completion-report.md +++ b/docs/test-reports/phase-1-2-completion-report.md @@ -58,16 +58,19 @@ #### **Tools Implemented (4)** 1. **generate-test-cases** + - Multiple language support (JavaScript, TypeScript, Python, Java) - Framework-specific configurations (Jest, Mocha, Vitest, Pytest) - Coverage options (comprehensive, edge cases, happy path) 2. **refactor-code** + - Multi-goal optimization (readability, maintainability, performance) - Language-aware refactoring patterns - Best practices enforcement 3. **generate-documentation** + - Multiple formats (Markdown, JSDoc, Docstring, HTML) - Audience-specific content generation - API reference and usage guide options diff --git a/docs/test-reports/visual-content-documentation-update-summary.md b/docs/test-reports/visual-content-documentation-update-summary.md index 33f5aeb3b..3137c8099 100644 --- a/docs/test-reports/visual-content-documentation-update-summary.md +++ b/docs/test-reports/visual-content-documentation-update-summary.md @@ -43,6 +43,7 @@ ### Videos Available - **CLI Videos**: + - cli-01-cli-help.mp4 - cli-02-provider-status.mp4 - cli-03-text-generation.mp4 diff --git a/docs/tracking/CLI_OPTIMIZATION_TRACKING.md b/docs/tracking/CLI_OPTIMIZATION_TRACKING.md index 313af0d25..429056c59 100644 --- a/docs/tracking/CLI_OPTIMIZATION_TRACKING.md +++ b/docs/tracking/CLI_OPTIMIZATION_TRACKING.md @@ -517,6 +517,7 @@ _Duration: 0.5 days | Risk: Low | Impact: Low_ 1. **Breaking Changes**: Risk of changing existing CLI behavior - **Mitigation**: Comprehensive regression testing, backward compatibility focus 2. **SDK Dependencies**: Risk of SDK methods not supporting CLI needs + - **Mitigation**: Verify SDK capabilities before implementation, fallback plans 3. **Performance Regression**: Risk of new architecture being slower @@ -525,6 +526,7 @@ _Duration: 0.5 days | Risk: Low | Impact: Low_ ### **Medium Risk Items** 1. **Universal Options Complexity**: Risk of options not making sense for all commands + - **Mitigation**: Careful design, graceful handling of no-op options 2. **Testing Coverage**: Risk of missing edge cases in testing diff --git a/docs/tracking/IMMEDIATE_WORK_PLAN.md b/docs/tracking/IMMEDIATE_WORK_PLAN.md index 6f84c19f1..1b5700754 100644 --- a/docs/tracking/IMMEDIATE_WORK_PLAN.md +++ b/docs/tracking/IMMEDIATE_WORK_PLAN.md @@ -32,6 +32,7 @@ **Investigation Plan**: 1. **Test Context Data Flow**: + - Trace how `--context` data flows through CLI → SDK → Provider - Verify if context data reaches AI generation logic - Check if context influences prompt construction or response processing @@ -64,6 +65,7 @@ **Implementation Plan**: 1. **Analyze Current Code**: + - Find provider status checking logic in codebase - Identify where sequential execution occurs - Map current timing and bottlenecks @@ -122,11 +124,13 @@ **Optimization Plan**: 1. **Profile Module Loading**: + - Analyze which modules take longest to load - Identify unnecessary imports in CLI startup path - Identify modules that can be converted to dynamic imports for on-demand loading 2. **Bundle Optimization**: + - Review TypeScript compilation output - Minimize initial module graph size - Consider dynamic imports for heavy modules @@ -149,11 +153,13 @@ **Investigation Plan**: 1. **Find the TODO Comment**: + - Locate exact TODO marker comment "// TODO: Fix hanging dynamic model provider.initialize()" in `src/lib/core/factory.ts` - Understand the hanging initialization issue - Identify the underlying technical cause of dynamic model initialization failures 2. **Debug Dynamic Model System**: + - Test dynamic model resolution functionality - Check if model server integration works - Verify model registry and resolver systems @@ -172,6 +178,7 @@ ### **Phase 1: Critical Fixes (Category 1)** 1. **Context Option Investigation** (Day 1) + - Deep dive into context data flow - Document current behavior and limitations - Implement fixes if integration is broken @@ -184,6 +191,7 @@ ### **Phase 2: Minor Improvements (Category 2)** 3. **Provider Edge Cases** (Day 2) + - Research and implement HuggingFace improvements - Debug and fix Ollama integration issues - Clean up TODO comments diff --git a/docs/visual-content/phase-1-2-visual-content-achievement.md b/docs/visual-content/phase-1-2-visual-content-achievement.md index 11b9c7188..b22c97ec0 100644 --- a/docs/visual-content/phase-1-2-visual-content-achievement.md +++ b/docs/visual-content/phase-1-2-visual-content-achievement.md @@ -12,31 +12,37 @@ ### **Screenshots Delivered** 1. **01-phase-1-2-overview.png** (278KB) - Complete Phase 1.2 workflow tools page + - Shows all 4 tools in professional grid layout - Displays performance metrics (100% test coverage, <1ms execution) - Green theme highlighting Phase 1.2 distinction 2. **02-generate-test-cases.png** (54KB) - Test case generation tool in action + - JavaScript function example with discount calculation - Framework selection showing Jest, Mocha, Vitest, Pytest - Coverage type options (comprehensive, edge cases, happy path) 3. **03-refactor-code.png** (46KB) - Code refactoring tool demonstration + - Original code snippet being refactored - Multi-goal optimization checkboxes (readability, maintainability, performance) - Successful refactoring output displayed 4. **04-generate-documentation.png** (53KB) - Documentation generation example + - UserAuthentication class being documented - Documentation type and format selection - Generated JSDoc output with comprehensive details 5. **05-debug-ai-output.png** (51KB) - AI output debugging analysis + - React component debugging scenario - Analysis depth options (detailed, quick, comprehensive) - Issues and recommendations displayed 6. **06-workflow-integration.png** (58KB) - Complete workflow integration demo + - Tabbed interface showing 5-step workflow - Original code → Refactor → Document → Test → Debug - All tools working together seamlessly diff --git a/docs/visual-content/phase-1-2-workflow-tools-plan.md b/docs/visual-content/phase-1-2-workflow-tools-plan.md index e41f71499..78069d1a2 100644 --- a/docs/visual-content/phase-1-2-workflow-tools-plan.md +++ b/docs/visual-content/phase-1-2-workflow-tools-plan.md @@ -62,18 +62,22 @@ Create professional visual documentation for the 4 AI Development Workflow Tools ## Implementation Steps 1. **Ensure Demo Server Running** + - Server should be on port 9876 - All 4 Phase 1.2 tools integrated 2. **Create AI Workflow Demo Page** + - Professional UI with forms for each tool - Green color theme for Phase 1.2 distinction 3. **Capture Screenshots** + - Use browser or Playwright for consistent captures - Save to `docs/visual-content/screenshots/phase-1-2-workflow/` 4. **Create Demo Videos** (Optional) + - Record tool demonstrations - Save to `docs/visual-content/videos/phase-1-2-workflow/` diff --git a/examples/sagemaker/README.md b/examples/sagemaker/README.md index 38532bd32..4e2df62d3 100644 --- a/examples/sagemaker/README.md +++ b/examples/sagemaker/README.md @@ -321,16 +321,19 @@ app.post("/generate", async (req, res) => { ### Common Issues 1. **"Endpoint not found"** + - Verify endpoint name spelling - Check if endpoint is in the correct region - Ensure endpoint is deployed and in service 2. **"Access denied"** + - Verify IAM permissions - Check AWS credentials - Ensure endpoint allows access from your account 3. **"Model not ready"** + - Wait for endpoint to finish deploying - Check endpoint status in AWS Console - Some models need warm-up time diff --git a/neurolink-demo/package.json b/neurolink-demo/package.json index a75af5f04..48a62e13c 100644 --- a/neurolink-demo/package.json +++ b/neurolink-demo/package.json @@ -16,7 +16,6 @@ "dev": "nodemon server.js" }, "dependencies": { - "@ai-sdk/amazon-bedrock": "^2.2.10", "@ai-sdk/google-vertex": "^2.2.0", "@ai-sdk/openai": "^0.0.66", "@juspay/neurolink": "^1.2.3", diff --git a/package.json b/package.json index 9b2d42c25..379e05922 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,6 @@ } }, "dependencies": { - "@ai-sdk/amazon-bedrock": "^1.0.0", "@ai-sdk/anthropic": "^1.2.12", "@ai-sdk/azure": "^1.3.24", "@ai-sdk/google": "^1.2.19", @@ -150,7 +149,6 @@ "@aws-sdk/client-sagemaker": "^3.862.0", "@aws-sdk/client-sagemaker-runtime": "^3.862.0", "@aws-sdk/credential-provider-node": "^3.876.0", - "@aws-sdk/credential-providers": "^3.876.0", "@aws-sdk/types": "^3.862.0", "@google-cloud/vertexai": "^1.10.0", "@google/generative-ai": "^0.24.1", @@ -197,6 +195,7 @@ "@semantic-release/github": "^11.0.0", "@semantic-release/npm": "^12.0.1", "@semantic-release/release-notes-generator": "^14.0.1", + "@smithy/types": "^4.3.2", "@sveltejs/adapter-auto": "^6.0.0", "@sveltejs/kit": "^2.16.0", "@sveltejs/package": "^2.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65d663e8f..8ff86d50b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,6 @@ importers: .: dependencies: - '@ai-sdk/amazon-bedrock': - specifier: ^1.0.0 - version: 1.1.6(zod@3.25.62) '@ai-sdk/anthropic': specifier: ^1.2.12 version: 1.2.12(zod@3.25.62) @@ -56,9 +53,6 @@ importers: '@aws-sdk/credential-provider-node': specifier: ^3.876.0 version: 3.876.0 - '@aws-sdk/credential-providers': - specifier: ^3.876.0 - version: 3.876.0 '@aws-sdk/types': specifier: ^3.862.0 version: 3.862.0 @@ -192,6 +186,9 @@ importers: '@semantic-release/release-notes-generator': specifier: ^14.0.1 version: 14.0.3(semantic-release@24.2.5(typescript@5.8.3)) + '@smithy/types': + specifier: ^4.3.2 + version: 4.3.2 '@sveltejs/adapter-auto': specifier: ^6.0.0 version: 6.0.1(@sveltejs/kit@2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@20.19.0)(yaml@2.8.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@20.19.0)(yaml@2.8.1))) @@ -291,12 +288,6 @@ importers: packages: - '@ai-sdk/amazon-bedrock@1.1.6': - resolution: {integrity: sha512-h6SJWpku+i8OsSz0A4RT2g2uD+3E0SUgWHsWRIpxmPNgM1DnH6lgSby5sxqAZDY5xJyJtRFW5vB9G3GEBjHy/g==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.0.0 - '@ai-sdk/anthropic@1.2.12': resolution: {integrity: sha512-YSzjlko7JvuiyQFmI9RN1tNZdEiZxc+6xld/0tq/VkJaHpEzGAb1yiNxxvmYVcjvfu/PcvCxAAYXmTYQQ63IHQ==} engines: {node: '>=18'} @@ -339,25 +330,12 @@ packages: peerDependencies: zod: ^3.0.0 - '@ai-sdk/provider-utils@2.1.6': - resolution: {integrity: sha512-Pfyaj0QZS22qyVn5Iz7IXcJ8nKIKlu2MeSAdKJzTwkAks7zdLaKVB+396Rqcp1bfQnxl7vaduQVMQiXUrgK8Gw==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.0.0 - peerDependenciesMeta: - zod: - optional: true - '@ai-sdk/provider-utils@2.2.8': resolution: {integrity: sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==} engines: {node: '>=18'} peerDependencies: zod: ^3.23.8 - '@ai-sdk/provider@1.0.7': - resolution: {integrity: sha512-q1PJEZ0qD9rVR+8JFEd01/QM++csMT5UVwYXSN2u54BrVw/D8TZLTeg2FEfKK00DgAx0UtWd8XOhhwITP9BT5g==} - engines: {node: '>=18'} - '@ai-sdk/provider@1.1.3': resolution: {integrity: sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==} engines: {node: '>=18'} @@ -407,10 +385,6 @@ packages: resolution: {integrity: sha512-BWk3FRDJhl4PrcFMqblbTjYlsaAYh8YMMmEwlgpqrzvrqc4H2aPGSnzPzzTKlc1PZgE/ZdSv6PO4whHrNzc+Bw==} engines: {node: '>=18.0.0'} - '@aws-sdk/client-cognito-identity@3.876.0': - resolution: {integrity: sha512-oSoTroa0sJ8TIFh/PamqAKBAmPzNvYCbWm7K9OFCOXApCxF7E981Cwd4AbXwLgy/AAMiXfPgCesPZqr0gTQQQQ==} - engines: {node: '>=18.0.0'} - '@aws-sdk/client-sagemaker-runtime@3.862.0': resolution: {integrity: sha512-Ek7N4kssKLA8VgcCKgxM9Ivnx9AGqF9HAfv/3DEFLoM38e7He7jyWyZxZZVNDQcu3xqWz7Ru1qOi13Mc0wgLkg==} engines: {node: '>=18.0.0'} @@ -443,10 +417,6 @@ packages: resolution: {integrity: sha512-sVFBFkdoPOPyY13NaXO1E/R9O5J6ixzHnnRbqrbXYM2QQgLNPTKIiRtmVEuVoFV9YULg+/aKm7caix8m468y9w==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-cognito-identity@3.876.0': - resolution: {integrity: sha512-6LlQCVef+DBpBZ3F1g7Fr6uuDCXLK4uf90f6bumZSg4ET/LUoAvIhWIiEZGkZ9jJ441Jnil73kOzwmsb7GkPWQ==} - engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-env@3.862.0': resolution: {integrity: sha512-/nafSJMuixcrCN1SmsOBIQ5m1fhr9ZnCxw3JZD9qJm3yNXhAshqAC+KcA3JGFnvdBVLhY/pUpdoQmxZmuFJItQ==} engines: {node: '>=18.0.0'} @@ -531,10 +501,6 @@ packages: resolution: {integrity: sha512-q/XSCP1uae5aB9veM8zcm6Gqu6A4ckX9ZbhHgCzURXVJDwp+nINW1hM9vppMjGw3ND9Ibx/adR+KfTI0TDMzqw==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-providers@3.876.0': - resolution: {integrity: sha512-ruCLlBpz+ggJQtdrnnfgjtFUJaHKN2WtNp1tyuV/qDmLk5vMgk2BSyOWLyTsbJC+L+I76w7NADY6VIRe9MiF0Q==} - engines: {node: '>=18.0.0'} - '@aws-sdk/eventstream-handler-node@3.873.0': resolution: {integrity: sha512-c3j9Q3RSR4+/01oHgx8b4WuD2HinVAalbsL7rJKlw86sP6ef1Gq7rVYFn74Ooh+2fIVecvX3cla/tdkR8PwBtA==} engines: {node: '>=18.0.0'} @@ -5123,15 +5089,6 @@ packages: snapshots: - '@ai-sdk/amazon-bedrock@1.1.6(zod@3.25.62)': - dependencies: - '@ai-sdk/provider': 1.0.7 - '@ai-sdk/provider-utils': 2.1.6(zod@3.25.62) - '@aws-sdk/client-bedrock-runtime': 3.876.0 - zod: 3.25.62 - transitivePeerDependencies: - - aws-crt - '@ai-sdk/anthropic@1.2.12(zod@3.25.62)': dependencies: '@ai-sdk/provider': 1.1.3 @@ -5181,15 +5138,6 @@ snapshots: '@ai-sdk/provider-utils': 2.2.8(zod@3.25.62) zod: 3.25.62 - '@ai-sdk/provider-utils@2.1.6(zod@3.25.62)': - dependencies: - '@ai-sdk/provider': 1.0.7 - eventsource-parser: 3.0.2 - nanoid: 3.3.11 - secure-json-parse: 2.7.0 - optionalDependencies: - zod: 3.25.62 - '@ai-sdk/provider-utils@2.2.8(zod@3.25.62)': dependencies: '@ai-sdk/provider': 1.1.3 @@ -5197,10 +5145,6 @@ snapshots: secure-json-parse: 2.7.0 zod: 3.25.62 - '@ai-sdk/provider@1.0.7': - dependencies: - json-schema: 0.4.0 - '@ai-sdk/provider@1.1.3': dependencies: json-schema: 0.4.0 @@ -5360,50 +5304,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-cognito-identity@3.876.0': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.876.0 - '@aws-sdk/credential-provider-node': 3.876.0 - '@aws-sdk/middleware-host-header': 3.873.0 - '@aws-sdk/middleware-logger': 3.876.0 - '@aws-sdk/middleware-recursion-detection': 3.873.0 - '@aws-sdk/middleware-user-agent': 3.876.0 - '@aws-sdk/region-config-resolver': 3.873.0 - '@aws-sdk/types': 3.862.0 - '@aws-sdk/util-endpoints': 3.873.0 - '@aws-sdk/util-user-agent-browser': 3.873.0 - '@aws-sdk/util-user-agent-node': 3.876.0 - '@smithy/config-resolver': 4.1.5 - '@smithy/core': 3.8.0 - '@smithy/fetch-http-handler': 5.1.1 - '@smithy/hash-node': 4.0.5 - '@smithy/invalid-dependency': 4.0.5 - '@smithy/middleware-content-length': 4.0.5 - '@smithy/middleware-endpoint': 4.1.18 - '@smithy/middleware-retry': 4.1.19 - '@smithy/middleware-serde': 4.0.9 - '@smithy/middleware-stack': 4.0.5 - '@smithy/node-config-provider': 4.1.4 - '@smithy/node-http-handler': 4.1.1 - '@smithy/protocol-http': 5.1.3 - '@smithy/smithy-client': 4.4.10 - '@smithy/types': 4.3.2 - '@smithy/url-parser': 4.0.5 - '@smithy/util-base64': 4.0.0 - '@smithy/util-body-length-browser': 4.0.0 - '@smithy/util-body-length-node': 4.0.0 - '@smithy/util-defaults-mode-browser': 4.0.26 - '@smithy/util-defaults-mode-node': 4.0.26 - '@smithy/util-endpoints': 3.0.7 - '@smithy/util-middleware': 4.0.5 - '@smithy/util-retry': 4.0.7 - '@smithy/util-utf8': 4.0.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/client-sagemaker-runtime@3.862.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 @@ -5682,16 +5582,6 @@ snapshots: fast-xml-parser: 5.2.5 tslib: 2.8.1 - '@aws-sdk/credential-provider-cognito-identity@3.876.0': - dependencies: - '@aws-sdk/client-cognito-identity': 3.876.0 - '@aws-sdk/types': 3.862.0 - '@smithy/property-provider': 4.0.5 - '@smithy/types': 4.3.2 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/credential-provider-env@3.862.0': dependencies: '@aws-sdk/core': 3.862.0 @@ -5959,30 +5849,6 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-providers@3.876.0': - dependencies: - '@aws-sdk/client-cognito-identity': 3.876.0 - '@aws-sdk/core': 3.876.0 - '@aws-sdk/credential-provider-cognito-identity': 3.876.0 - '@aws-sdk/credential-provider-env': 3.876.0 - '@aws-sdk/credential-provider-http': 3.876.0 - '@aws-sdk/credential-provider-ini': 3.876.0 - '@aws-sdk/credential-provider-node': 3.876.0 - '@aws-sdk/credential-provider-process': 3.876.0 - '@aws-sdk/credential-provider-sso': 3.876.0 - '@aws-sdk/credential-provider-web-identity': 3.876.0 - '@aws-sdk/nested-clients': 3.876.0 - '@aws-sdk/types': 3.862.0 - '@smithy/config-resolver': 4.1.5 - '@smithy/core': 3.8.0 - '@smithy/credential-provider-imds': 4.0.7 - '@smithy/node-config-provider': 4.1.4 - '@smithy/property-provider': 4.0.5 - '@smithy/types': 4.3.2 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/eventstream-handler-node@3.873.0': dependencies: '@aws-sdk/types': 3.862.0 diff --git a/src/lib/core/conversationMemoryManager.ts b/src/lib/core/conversationMemoryManager.ts index f34744e27..8fef7e72d 100644 --- a/src/lib/core/conversationMemoryManager.ts +++ b/src/lib/core/conversationMemoryManager.ts @@ -10,7 +10,11 @@ import type { ChatMessage, } from "../types/conversationTypes.js"; import { ConversationMemoryError } from "../types/conversationTypes.js"; -import { DEFAULT_MAX_TURNS_PER_SESSION, DEFAULT_MAX_SESSIONS, MESSAGES_PER_TURN } from "../config/conversationMemoryConfig.js"; +import { + DEFAULT_MAX_TURNS_PER_SESSION, + DEFAULT_MAX_SESSIONS, + MESSAGES_PER_TURN, +} from "../config/conversationMemoryConfig.js"; import { logger } from "../utils/logger.js"; import { NeuroLink } from "../neurolink.js"; @@ -74,12 +78,21 @@ export class ConversationMemoryManager { session.lastActivity = Date.now(); if (this.config.enableSummarization) { - const currentTurnCount = session.messages.length / MESSAGES_PER_TURN; - if (currentTurnCount > (this.config.summarizationThresholdTurns || 20)) { + const userAssistantCount = session.messages.filter( + (msg) => msg.role === "user" || msg.role === "assistant", + ).length; + const currentTurnCount = Math.floor( + userAssistantCount / MESSAGES_PER_TURN, + ); + if ( + currentTurnCount >= (this.config.summarizationThresholdTurns || 20) + ) { await this._summarizeSession(session); } } else { - const maxMessages = (this.config.maxTurnsPerSession || DEFAULT_MAX_TURNS_PER_SESSION) * MESSAGES_PER_TURN; + const maxMessages = + (this.config.maxTurnsPerSession || DEFAULT_MAX_TURNS_PER_SESSION) * + MESSAGES_PER_TURN; if (session.messages.length > maxMessages) { session.messages = session.messages.slice(-maxMessages); } @@ -110,7 +123,7 @@ export class ConversationMemoryManager { public getSession(sessionId: string): SessionMemory | undefined { return this.sessions.get(sessionId); } - + public createSummarySystemMessage(content: string): ChatMessage { return { role: "system", @@ -119,9 +132,14 @@ export class ConversationMemoryManager { } private async _summarizeSession(session: SessionMemory): Promise { - logger.info(`[ConversationMemory] Summarizing session ${session.sessionId}...`); + logger.info( + `[ConversationMemory] Summarizing session ${session.sessionId}...`, + ); const targetTurns = this.config.summarizationTargetTurns || 10; - const splitIndex = Math.max(0, session.messages.length - targetTurns * MESSAGES_PER_TURN); + const splitIndex = Math.max( + 0, + session.messages.length - targetTurns * MESSAGES_PER_TURN, + ); const messagesToSummarize = session.messages.slice(0, splitIndex); const recentMessages = session.messages.slice(splitIndex); @@ -129,24 +147,29 @@ export class ConversationMemoryManager { return; } - const summarizationPrompt = this._createSummarizationPrompt(messagesToSummarize); - - const summarizer = new NeuroLink({ conversationMemory: { enabled: false } }); + const summarizationPrompt = + this._createSummarizationPrompt(messagesToSummarize); + + const summarizer = new NeuroLink({ + conversationMemory: { enabled: false }, + }); try { const providerName = this.config.summarizationProvider; - + // Map provider names to correct format let mappedProvider = providerName; - if (providerName === 'vertex') { - mappedProvider = 'googlevertex'; + if (providerName === "vertex") { + mappedProvider = "googlevertex"; } - + if (!mappedProvider) { logger.error(`[ConversationMemory] Missing summarization provider`); return; } - logger.debug(`[ConversationMemory] Using provider: ${mappedProvider} for summarization`); + logger.debug( + `[ConversationMemory] Using provider: ${mappedProvider} for summarization`, + ); const summaryResult = await summarizer.generate({ input: { text: summarizationPrompt }, @@ -158,19 +181,28 @@ export class ConversationMemoryManager { if (summaryResult.content) { session.messages = [ this.createSummarySystemMessage(summaryResult.content), - ...recentMessages + ...recentMessages, ]; - logger.info(`[ConversationMemory] Summarization complete for session ${session.sessionId}.`); + logger.info( + `[ConversationMemory] Summarization complete for session ${session.sessionId}.`, + ); } else { - logger.warn(`[ConversationMemory] Summarization failed for session ${session.sessionId}. History not modified.`); + logger.warn( + `[ConversationMemory] Summarization failed for session ${session.sessionId}. History not modified.`, + ); } } catch (error) { - logger.error(`[ConversationMemory] Error during summarization for session ${session.sessionId}`, { error }); + logger.error( + `[ConversationMemory] Error during summarization for session ${session.sessionId}`, + { error }, + ); } } private _createSummarizationPrompt(history: ChatMessage[]): string { - const formattedHistory = history.map(msg => `${msg.role}: ${msg.content}`).join('\n\n'); + const formattedHistory = history + .map((msg) => `${msg.role}: ${msg.content}`) + .join("\n\n"); return ` You are a context summarization AI. Your task is to condense the following conversation history for another AI assistant. The summary must be a concise, third-person narrative that retains all critical information, including key entities, technical details, decisions made, and any specific dates or times mentioned. @@ -205,8 +237,13 @@ ${formattedHistory} return; } - const sessions = Array.from(this.sessions.entries()).sort(([, a], [, b]) => a.lastActivity - b.lastActivity); - const sessionsToRemove = sessions.slice(0, this.sessions.size - maxSessions); + const sessions = Array.from(this.sessions.entries()).sort( + ([, a], [, b]) => a.lastActivity - b.lastActivity, + ); + const sessionsToRemove = sessions.slice( + 0, + this.sessions.size - maxSessions, + ); for (const [sessionId] of sessionsToRemove) { this.sessions.delete(sessionId); diff --git a/src/lib/core/types.ts b/src/lib/core/types.ts index d3a2cb095..86fba6046 100644 --- a/src/lib/core/types.ts +++ b/src/lib/core/types.ts @@ -6,7 +6,10 @@ import type { import type { GenerateResult } from "../types/generateTypes.js"; import type { StreamOptions, StreamResult } from "../types/streamTypes.js"; import type { JsonValue } from "../types/common.js"; -import type { ChatMessage, ConversationMemoryConfig } from "../types/conversationTypes.js"; +import type { + ChatMessage, + ConversationMemoryConfig, +} from "../types/conversationTypes.js"; import type { TokenUsage, AnalyticsData } from "../types/providers.js"; import type { EvaluationData } from "../index.js"; diff --git a/src/lib/factories/providerRegistry.ts b/src/lib/factories/providerRegistry.ts index 5bc45f522..366559678 100644 --- a/src/lib/factories/providerRegistry.ts +++ b/src/lib/factories/providerRegistry.ts @@ -106,7 +106,6 @@ export class ProviderRegistry { ); return new AmazonBedrockProvider( modelName, - undefined, sdk as NeuroLink | undefined, ); }, diff --git a/src/lib/index.ts b/src/lib/index.ts index 91060f76b..0245dd350 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -47,6 +47,10 @@ export { isValidProvider, } from "./utils/providerUtils.js"; +// Dynamic Models exports +export { dynamicModelProvider } from "./core/dynamicModels.js"; +export type { ModelConfig, ModelRegistry } from "./core/dynamicModels.js"; + // Main NeuroLink wrapper class and diagnostic types export { NeuroLink } from "./neurolink.js"; export type { ProviderStatus, MCPStatus } from "./neurolink.js"; diff --git a/src/lib/providers/amazonBedrock.ts b/src/lib/providers/amazonBedrock.ts index 22fd5922a..a0e5cfc74 100644 --- a/src/lib/providers/amazonBedrock.ts +++ b/src/lib/providers/amazonBedrock.ts @@ -1,196 +1,156 @@ -import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock"; -import type { AmazonBedrockProvider as BedrockProviderType } from "@ai-sdk/amazon-bedrock"; -import type { ZodUnknownSchema } from "../types/typeAliases.js"; -import { streamText, type LanguageModelV1 } from "ai"; -import type { AIProviderName } from "../core/types.js"; -import type { StreamOptions, StreamResult } from "../types/streamTypes.js"; -import { BaseProvider } from "../core/baseProvider.js"; -import { logger } from "../utils/logger.js"; -import { createTimeoutController, TimeoutError } from "../utils/timeout.js"; -import { DEFAULT_MAX_TOKENS, DEFAULT_MAX_STEPS } from "../core/constants.js"; import { - validateApiKey, - createAWSAccessKeyConfig, - createAWSSecretConfig, - getAWSRegion, - getAWSSessionToken, -} from "../utils/providerConfig.js"; -import { buildMessagesArray } from "../utils/messageBuilder.js"; -import { createProxyFetch } from "../proxy/proxyFetch.js"; -import { configureAWSProxySupport as _configureAWSProxySupport } from "../proxy/awsProxyIntegration.js"; -import { AWSCredentialProvider } from "./aws/credentialProvider.js"; -import { BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime"; -import type { AWSCredentialConfig } from "../types/providers.js"; + BedrockRuntimeClient, + ConverseCommand, + ConverseStreamCommand, +} from "@aws-sdk/client-bedrock-runtime"; +import type { + ConverseCommandInput, + ConverseCommandOutput, + ConverseStreamCommandInput, + ToolConfiguration, + Message, + ContentBlock, + Tool as BedrockTool, + ToolSpecification, +} from "@aws-sdk/client-bedrock-runtime"; +import { + BedrockClient, + ListFoundationModelsCommand, +} from "@aws-sdk/client-bedrock"; + +import { BaseProvider } from "../core/baseProvider.js"; +import type { AIProviderName, EnhancedGenerateResult } from "../core/types.js"; +import type { StreamOptions, StreamResult } from "../types/streamTypes.js"; +import type { TextGenerationOptions } from "../core/types.js"; +import type { ToolDefinition, ToolArgs } from "../types/tools.js"; +import type { JsonValue } from "../types/common.js"; import type { NeuroLink } from "../neurolink.js"; +import { logger } from "../utils/logger.js"; +import type { DocumentType } from "@smithy/types"; +import { zodToJsonSchema } from "zod-to-json-schema"; +import type { ZodType } from "zod"; -// Configuration helpers -const getBedrockModelId = (): string => { - const model = process.env.BEDROCK_MODEL || process.env.BEDROCK_MODEL_ID; - if (!model) { - 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.", - ); - } - return model; -}; - -// Configuration helpers - now using consolidated utility -const getAWSAccessKeyId = (): string => { - return validateApiKey(createAWSAccessKeyConfig()); -}; - -const getAWSSecretAccessKey = (): string => { - return validateApiKey(createAWSSecretConfig()); -}; - -// Note: getAWSRegion and getAWSSessionToken are now directly imported from consolidated utility - -const getAppEnvironment = (): string => { - return process.env.PUBLIC_APP_ENVIRONMENT || "production"; -}; - -/** - * Amazon Bedrock Provider v3 - Enhanced Authentication Implementation - * - * BEDROCK-MCP-CONNECTOR COMPATIBILITY: Complete AWS SDK credential chain support - * - * Features: - * - Extends BaseProvider for shared functionality - * - AWS SDK v3 defaultProvider credential chain (9 sources) - * - Dual access: AI SDK + Direct AWS SDK BedrockRuntimeClient - * - Full backward compatibility with existing configurations - * - Enhanced error handling with setup guidance - * - Bedrock-MCP-Connector compatible authentication patterns - */ -export class AmazonBedrockProvider extends BaseProvider { - private awsCredentialProvider: AWSCredentialProvider; - private bedrockClient: BedrockRuntimeClient; - private bedrock: BedrockProviderType; - private model: LanguageModelV1; - - constructor( - modelName?: string, - credentialConfig?: AWSCredentialConfig, - neurolink?: NeuroLink, - ) { - super(modelName, "bedrock" as AIProviderName, neurolink); +interface BedrockToolUse { + toolUseId: string; + name: string; + input: Record; +} - // Debug: Bedrock initialization started - logger.debug("[Bedrock] Provider initialization started", { - requestedModel: modelName || "default", - environment: getAppEnvironment(), - }); +interface BedrockToolResult { + toolUseId: string; + content: Array<{ text: string }>; + status: string; +} - // Initialize AWS credential provider with full credential chain support - const defaultCredentialConfig: AWSCredentialConfig = { - region: getAWSRegion(), - enableDebugLogging: getAppEnvironment() === "dev", - ...credentialConfig, - }; +interface BedrockContentBlock { + text?: string; + toolUse?: BedrockToolUse; + toolResult?: BedrockToolResult; +} - // Debug: AWS configuration - logger.debug("[Bedrock] AWS configuration resolved", { - region: defaultCredentialConfig.region, - enableDebugLogging: defaultCredentialConfig.enableDebugLogging, - credentialConfigProvided: !!credentialConfig, - }); +interface BedrockMessage { + role: "user" | "assistant"; + content: BedrockContentBlock[]; +} + +export class AmazonBedrockProvider extends BaseProvider { + private bedrockClient: BedrockRuntimeClient; + private conversationHistory: BedrockMessage[] = []; - this.awsCredentialProvider = new AWSCredentialProvider( - defaultCredentialConfig, + constructor(modelName?: string, neurolink?: NeuroLink) { + super(modelName, "bedrock" as AIProviderName, neurolink); + + logger.debug( + "[AmazonBedrockProvider] Starting constructor with extensive logging for debugging", ); - // Debug: AWS credential detection status - logger.debug("[Bedrock] AWS credential detection status", { - hasAccessKey: !!process.env.AWS_ACCESS_KEY_ID, - hasSecretKey: !!process.env.AWS_SECRET_ACCESS_KEY, - hasSessionToken: !!process.env.AWS_SESSION_TOKEN, - hasProfile: !!process.env.AWS_PROFILE, - credentialChainEnabled: true, - }); + // Log environment variables for debugging + 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"}`, + ); - // Create AWS SDK v3 Bedrock client for direct access (Bedrock-MCP-Connector compatibility) - // Proxy support will be injected lazily when needed - this.bedrockClient = new BedrockRuntimeClient({ - region: defaultCredentialConfig.region, - credentials: this.awsCredentialProvider.getCredentialProvider(), - }); + try { + // Create BedrockRuntimeClient with clean configuration like working Bedrock-MCP-Connector + // Absolutely no proxy interference - let AWS SDK handle everything natively + logger.debug( + "[AmazonBedrockProvider] Creating BedrockRuntimeClient with clean configuration", + ); - // Debug: AWS region and service endpoint - logger.debug("[Bedrock] AWS service configuration", { - region: defaultCredentialConfig.region, - serviceEndpoint: `https://bedrock-runtime.${defaultCredentialConfig.region}.amazonaws.com`, - credentialProviderType: "AWS SDK v3 defaultProvider chain", - }); + this.bedrockClient = new BedrockRuntimeClient({ + region: process.env.AWS_REGION || "us-east-1", + // Clean configuration - AWS SDK will handle credentials via: + // 1. IAM roles (preferred in production) + // 2. Environment variables + // 3. AWS config files + // 4. Instance metadata + }); - // For now, use legacy configuration as AI SDK may not support credential providers directly - // TODO: Update when @ai-sdk/amazon-bedrock supports credential providers - const legacyAwsConfig = this.createLegacyAWSConfig(); + logger.debug( + `[AmazonBedrockProvider] Successfully created BedrockRuntimeClient with model: ${this.modelName}, region: ${process.env.AWS_REGION || "us-east-1"}`, + ); - try { - this.bedrock = createAmazonBedrock(legacyAwsConfig); + // Immediate health check to catch credential issues early + this.performInitialHealthCheck(); } catch (error) { - logger.error("Failed to create AI SDK provider", { - error: error instanceof Error ? error.message : String(error), - }); - throw new Error( - `Failed to initialize Amazon Bedrock AI SDK: ${error instanceof Error ? error.message : String(error)}`, + logger.error( + `[AmazonBedrockProvider] CRITICAL: Failed to initialize BedrockRuntimeClient:`, + error, ); + throw error; } + } - // Pre-initialize model for efficiency - const resolvedModelId = this.modelName || getBedrockModelId(); - - // Debug: Bedrock model validation process - logger.debug("[Bedrock] Model validation and ARN processing", { - requestedModel: this.modelName || "from environment", - resolvedModelId: resolvedModelId, - isInferenceProfile: resolvedModelId.includes(":inference-profile/"), - isFoundationModel: - resolvedModelId.startsWith("anthropic.") || - resolvedModelId.startsWith("amazon.") || - resolvedModelId.startsWith("meta."), - modelARNValidation: resolvedModelId.includes("arn:aws:bedrock:") - ? "Full ARN provided" - : "Model ID provided", + /** + * Perform initial health check to catch credential/connectivity issues early + * This prevents the health check failure we saw in production logs + */ + private async performInitialHealthCheck(): Promise { + const bedrockClient = new BedrockClient({ + region: process.env.AWS_REGION || "us-east-1", }); - this.model = this.bedrock(resolvedModelId); + try { + logger.debug( + "[AmazonBedrockProvider] Starting initial health check to validate credentials and connectivity", + ); - logger.debug("Amazon Bedrock Provider v3 initialized", { - modelName: this.modelName, - region: defaultCredentialConfig.region, - credentialProvider: "AWS SDK v3 defaultProvider", - hasDualAccess: true, - provider: this.providerName, - }); - } + // Try to list foundation models as a lightweight health check + const command = new ListFoundationModelsCommand({}); + const startTime = Date.now(); - /** - * Legacy AWS configuration for backward compatibility - */ - private createLegacyAWSConfig() { - const awsConfig: { - accessKeyId: string; - secretAccessKey: string; - region: string; - sessionToken?: string; - fetch?: typeof fetch; - } = { - accessKeyId: getAWSAccessKeyId(), - secretAccessKey: getAWSSecretAccessKey(), - region: getAWSRegion(), - fetch: createProxyFetch(), - }; + await bedrockClient.send(command); + const responseTime = Date.now() - startTime; - // Add session token for development environment - if (getAppEnvironment() === "dev") { - const sessionToken = getAWSSessionToken(); - if (sessionToken) { - awsConfig.sessionToken = sessionToken; + logger.debug( + `[AmazonBedrockProvider] Health check PASSED - credentials valid, connectivity good, responseTime: ${responseTime}ms`, + ); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + logger.error( + `[AmazonBedrockProvider] Health check FAILED - this will cause production failures:`, + { + error: errorMessage, + errorType: + error instanceof Error ? error.constructor.name : "Unknown", + region: process.env.AWS_REGION || "us-east-1", + hasAccessKey: !!process.env.AWS_ACCESS_KEY_ID, + hasSecretKey: !!process.env.AWS_SECRET_ACCESS_KEY, + }, + ); + // Don't throw here - let the actual usage fail with better context + } finally { + try { + bedrockClient.destroy(); + } catch { + // Ignore destroy errors during cleanup } } + } - return awsConfig; + // Not using AI SDK approach in conversation management + protected getAISDKModel(): never { + throw new Error("AmazonBedrockProvider does not use AI SDK models"); } protected getProviderName(): AIProviderName { @@ -198,316 +158,1230 @@ export class AmazonBedrockProvider extends BaseProvider { } protected getDefaultModel(): string { - return getBedrockModelId(); + return ( + process.env.BEDROCK_MODEL || "anthropic.claude-3-sonnet-20240229-v1:0" + ); } - /** - * Returns the Vercel AI SDK model instance for AWS Bedrock - */ - protected getAISDKModel(): LanguageModelV1 { - return this.model; - } + // Override the main generate method to implement conversation management + async generate( + optionsOrPrompt: TextGenerationOptions | string, + ): Promise { + logger.debug( + "[AmazonBedrockProvider] generate() called with conversation management", + ); - /** - * Get AWS SDK BedrockRuntimeClient for direct access (Bedrock-MCP-Connector compatibility) - * This provides the same direct AWS SDK access that Bedrock-MCP-Connector uses - */ - getBedrockClient(): BedrockRuntimeClient { - // Note: For synchronous access, proxy support is configured lazily - // If proxy support is critical, use getBedrockClientWithProxy() instead - return this.bedrockClient; - } + const options = + typeof optionsOrPrompt === "string" + ? { prompt: optionsOrPrompt } + : optionsOrPrompt; - /** - * Get AWS SDK BedrockRuntimeClient with proxy support ensured - * Use this method when proxy support is critical for the operation - */ - async getBedrockClientWithProxy(): Promise { - await this.ensureProxySupport(); - return this.bedrockClient; + // Clear conversation history for new generation + this.conversationHistory = []; + + // Add user message to conversation + const userMessage: BedrockMessage = { + role: "user", + content: [{ text: options.prompt }], + }; + this.conversationHistory.push(userMessage); + + logger.debug( + `[AmazonBedrockProvider] Starting conversation with prompt: ${options.prompt}`, + ); + + // Start conversation loop and return enhanced result + const text = await this.conversationLoop(options); + + return { + content: text, // CLI expects 'content' not 'text' + usage: { total: 0, input: 0, output: 0 }, + model: this.modelName || this.getDefaultModel(), + provider: this.getProviderName(), + }; } - /** - * Get AWS credential provider for advanced credential management - */ - getCredentialProvider(): AWSCredentialProvider { - return this.awsCredentialProvider; + private async conversationLoop( + options: TextGenerationOptions, + ): Promise { + const maxIterations = 10; // Prevent infinite loops + let iteration = 0; + + while (iteration < maxIterations) { + iteration++; + logger.debug( + `[AmazonBedrockProvider] Conversation iteration ${iteration}`, + ); + + try { + logger.debug(`[AmazonBedrockProvider] About to call Bedrock API`); + const response = await this.callBedrock(options); + logger.debug( + `[AmazonBedrockProvider] Received Bedrock response`, + JSON.stringify(response, null, 2), + ); + + const result = await this.handleBedrockResponse(response); + logger.debug(`[AmazonBedrockProvider] Handle response result:`, result); + + if (result.shouldContinue) { + logger.debug( + `[AmazonBedrockProvider] Continuing conversation loop...`, + ); + continue; + } else { + logger.debug( + `[AmazonBedrockProvider] Conversation completed with final text`, + ); + logger.debug( + `[AmazonBedrockProvider] Returning final text: "${result.text}"`, + ); + return result.text || ""; + } + } catch (error) { + logger.error( + `[AmazonBedrockProvider] Error in conversation loop:`, + error, + ); + throw this.handleProviderError(error); + } + } + + throw new Error("Conversation loop exceeded maximum iterations"); } - /** - * Ensure proxy support is configured for AWS SDK client if needed - */ - private async ensureProxySupport(): Promise { + private async callBedrock(options: TextGenerationOptions) { + const startTime = Date.now(); + logger.info( + `🚀 [AmazonBedrockProvider] Starting Bedrock API call at ${new Date().toISOString()}`, + ); + try { - const { createAWSProxyHandler } = await import( - "../proxy/awsProxyIntegration.js" + // Pre-call validation and logging + const region = + typeof this.bedrockClient.config.region === "function" + ? await this.bedrockClient.config.region() + : this.bedrockClient.config.region; + logger.info(`🔧 [AmazonBedrockProvider] Client region: ${region}`); + logger.info( + `🔧 [AmazonBedrockProvider] Model: ${this.modelName || this.getDefaultModel()}`, + ); + logger.info( + `🔧 [AmazonBedrockProvider] Conversation history length: ${this.conversationHistory.length}`, ); - const proxyHandler = await createAWSProxyHandler(); - if (proxyHandler) { - logger.debug("[Bedrock] Reinitializing client with proxy support"); + // Get all available tools + const aiTools = await this.getAllTools(); + const allTools = this.convertAISDKToolsToToolDefinitions(aiTools); + const toolConfig = this.formatToolsForBedrock(allTools); - // Recreate the client with proxy handler - this.bedrockClient = new BedrockRuntimeClient({ - region: this.awsCredentialProvider.getConfig().region, - credentials: this.awsCredentialProvider.getCredentialProvider(), - requestHandler: proxyHandler, - }); + const commandInput: ConverseCommandInput = { + modelId: this.modelName || this.getDefaultModel(), + messages: this.convertToAWSMessages(this.conversationHistory), + system: [ + { + text: + options.systemPrompt || + "You are a helpful assistant with access to external tools. Use tools when necessary to provide accurate information.", + }, + ], + inferenceConfig: { + maxTokens: options.maxTokens || 4096, + temperature: options.temperature || 0.7, + }, + }; + + if (toolConfig) { + commandInput.toolConfig = toolConfig; + logger.info( + `🛠️ [AmazonBedrockProvider] Tools configured: ${toolConfig.tools?.length || 0}`, + ); } + + // Log command details for debugging + logger.info(`📋 [AmazonBedrockProvider] Command input summary:`); + logger.info(` - Model ID: ${commandInput.modelId}`); + logger.info(` - Messages count: ${commandInput.messages?.length || 0}`); + logger.info(` - System prompts: ${commandInput.system?.length || 0}`); + logger.info(` - Max tokens: ${commandInput.inferenceConfig?.maxTokens}`); + logger.info( + ` - Temperature: ${commandInput.inferenceConfig?.temperature}`, + ); + + logger.debug( + `[AmazonBedrockProvider] Calling Bedrock with ${this.conversationHistory.length} messages and ${toolConfig?.tools?.length || 0} tools`, + ); + + // Create command and attempt API call + const command = new ConverseCommand(commandInput); + logger.info( + `⏳ [AmazonBedrockProvider] Sending ConverseCommand to Bedrock...`, + ); + + const apiCallStartTime = Date.now(); + const response = await this.bedrockClient.send(command); + const apiCallDuration = Date.now() - apiCallStartTime; + + logger.info(`✅ [AmazonBedrockProvider] Bedrock API call successful!`); + logger.info( + `⏱️ [AmazonBedrockProvider] API call duration: ${apiCallDuration}ms`, + ); + logger.info(`📊 [AmazonBedrockProvider] Response metadata:`); + logger.info(` - Stop reason: ${response.stopReason}`); + logger.info(` - Usage tokens: ${JSON.stringify(response.usage || {})}`); + logger.info(` - Metrics: ${JSON.stringify(response.metrics || {})}`); + + const totalDuration = Date.now() - startTime; + logger.info( + `🎯 [AmazonBedrockProvider] Total callBedrock duration: ${totalDuration}ms`, + ); + + return response; } catch (error) { - logger.warn("[Bedrock] Failed to configure proxy support", { error }); - // Continue without proxy support + const errorDuration = Date.now() - startTime; + logger.error( + `❌ [AmazonBedrockProvider] Bedrock API call failed after ${errorDuration}ms`, + ); + logger.error(`🔍 [AmazonBedrockProvider] Error details:`); + + if (error instanceof Error) { + logger.error(` - Error name: ${error.name}`); + logger.error(` - Error message: ${error.message}`); + logger.error(` - Error stack: ${error.stack}`); + } + + // Log AWS SDK specific error details + if (error && typeof error === "object") { + const awsError = error as Record; + if (awsError.$metadata && typeof awsError.$metadata === "object") { + const metadata = awsError.$metadata as Record; + logger.error(`🏭 [AmazonBedrockProvider] AWS SDK metadata:`); + logger.error(` - HTTP status: ${metadata.httpStatusCode}`); + logger.error(` - Request ID: ${metadata.requestId}`); + logger.error(` - Attempts: ${metadata.attempts}`); + logger.error(` - Total retry delay: ${metadata.totalRetryDelay}`); + } + + if (awsError.Code) { + logger.error(` - AWS Error Code: ${awsError.Code}`); + } + + if (awsError.Type) { + logger.error(` - AWS Error Type: ${awsError.Type}`); + } + + if (awsError.Fault) { + logger.error(` - AWS Fault: ${awsError.Fault}`); + } + } + + // Log environment details for debugging + logger.error(`🌍 [AmazonBedrockProvider] Environment diagnostics:`); + logger.error(` - AWS_REGION: ${process.env.AWS_REGION || "not set"}`); + logger.error(` - AWS_PROFILE: ${process.env.AWS_PROFILE || "not set"}`); + logger.error( + ` - AWS_ACCESS_KEY_ID: ${process.env.AWS_ACCESS_KEY_ID ? "set" : "not set"}`, + ); + logger.error( + ` - AWS_SECRET_ACCESS_KEY: ${process.env.AWS_SECRET_ACCESS_KEY ? "set" : "not set"}`, + ); + logger.error( + ` - AWS_SESSION_TOKEN: ${process.env.AWS_SESSION_TOKEN ? "set" : "not set"}`, + ); + + throw error; } } - /** - * Test AWS credentials and Bedrock connectivity - * Useful for debugging authentication issues - */ - async testConnectivity(): Promise<{ - credentialsValid: boolean; - bedrockAccessible: boolean; - credentialSource: string; - error?: string; - responseTime?: number; - }> { - const startTime = Date.now(); - try { - // Ensure proxy support is configured before testing - await this.ensureProxySupport(); + private async handleBedrockResponse( + response: ConverseCommandOutput, + ): Promise<{ shouldContinue: boolean; text?: string }> { + logger.debug( + `[AmazonBedrockProvider] Received response with stopReason: ${response.stopReason}`, + ); + + if (!response.output || !response.output.message) { + throw new Error("Invalid response structure from Bedrock API"); + } - const { CredentialTester } = await import("./aws/credentialTester.js"); + const assistantMessage = response.output.message; + const stopReason = response.stopReason; - // Add timeout protection using AbortController - const timeout = 15000; // 15 second timeout - const abortController = new AbortController(); - const timeoutId = setTimeout(() => { - abortController.abort(); - }, timeout); + // Add assistant message to conversation history + const bedrockAssistantMessage: BedrockMessage = { + role: "assistant", + content: (assistantMessage.content || []).map((item) => { + const bedrockItem: BedrockContentBlock = {}; + if ("text" in item && item.text) { + bedrockItem.text = item.text; + } + if ("toolUse" in item && item.toolUse) { + bedrockItem.toolUse = { + toolUseId: item.toolUse.toolUseId || "", + name: item.toolUse.name || "", + input: (item.toolUse.input as Record) || {}, + }; + } + if ("toolResult" in item && item.toolResult) { + bedrockItem.toolResult = { + toolUseId: item.toolResult.toolUseId || "", + content: (item.toolResult.content || []).map((c) => ({ + text: + typeof c === "object" && "text" in c + ? (c.text as string) || "" + : "", + })), + status: item.toolResult.status || "unknown", + }; + } + return bedrockItem; + }), + }; + this.conversationHistory.push(bedrockAssistantMessage); - try { - const [credentialResult, connectivityResult] = await Promise.race([ - Promise.all([ - CredentialTester.validateCredentials(this.awsCredentialProvider), - CredentialTester.testBedrockConnectivity( - this.awsCredentialProvider, - ), - ]), - new Promise((_, reject) => { - abortController.signal.addEventListener("abort", () => { - reject(new Error("Connectivity test timeout")); + if (stopReason === "end_turn" || stopReason === "stop_sequence") { + // Extract text from assistant message + const textContent = bedrockAssistantMessage.content + .filter((item: BedrockContentBlock) => item.text) + .map((item: BedrockContentBlock) => item.text) + .join(" "); + + return { shouldContinue: false, text: textContent }; + } else if (stopReason === "tool_use") { + logger.debug( + `[AmazonBedrockProvider] Tool use detected - executing tools immediately`, + ); + + // Execute all tool uses in the message + const toolResults = []; + + for (const contentItem of bedrockAssistantMessage.content) { + if (contentItem.toolUse) { + logger.debug( + `[AmazonBedrockProvider] Executing tool: ${contentItem.toolUse.name}`, + ); + + try { + // Execute tool using BaseProvider's tool execution + logger.debug( + `[AmazonBedrockProvider] Debug toolUse.input:`, + JSON.stringify(contentItem.toolUse.input, null, 2), + ); + const toolResult = await this.executeSingleTool( + contentItem.toolUse.name, + contentItem.toolUse.input || {}, + contentItem.toolUse.toolUseId, + ); + + logger.debug( + `[AmazonBedrockProvider] Tool execution successful: ${contentItem.toolUse.name}`, + ); + + toolResults.push({ + toolResult: { + toolUseId: contentItem.toolUse.toolUseId, + content: [{ text: String(toolResult) }], + status: "success", + }, }); - }), - ]); + } catch (error) { + logger.error( + `[AmazonBedrockProvider] Tool execution failed: ${contentItem.toolUse.name}`, + error, + ); - clearTimeout(timeoutId); + const errorMessage = + error instanceof Error ? error.message : String(error); + // Still create toolResult for failed tools to maintain 1:1 mapping with toolUse blocks + toolResults.push({ + toolResult: { + toolUseId: contentItem.toolUse.toolUseId, + content: [ + { + text: `Error executing tool ${contentItem.toolUse.name}: ${errorMessage}`, + }, + ], + status: "error", + }, + }); + } + } + } - return { - credentialsValid: credentialResult.isValid, - bedrockAccessible: connectivityResult.bedrockAccessible, - credentialSource: credentialResult.credentialSource, - error: credentialResult.error || connectivityResult.error, - responseTime: Date.now() - startTime, + // Add tool results as user message + if (toolResults.length > 0) { + const userMessageWithToolResults: BedrockMessage = { + role: "user", + content: toolResults, }; - } catch (timeoutError) { - clearTimeout(timeoutId); - throw timeoutError; + this.conversationHistory.push(userMessageWithToolResults); + + logger.debug( + `[AmazonBedrockProvider] Added ${toolResults.length} tool results to conversation`, + ); } - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : String(error); - return { - credentialsValid: false, - bedrockAccessible: false, - credentialSource: "unknown", - error: errorMessage, - responseTime: Date.now() - startTime, + + return { shouldContinue: true }; + } else if (stopReason === "max_tokens") { + // Handle max tokens by continuing conversation + const userMessage: BedrockMessage = { + role: "user", + content: [{ text: "Please continue." }], }; + this.conversationHistory.push(userMessage); + + return { shouldContinue: true }; + } else { + logger.warn( + `[AmazonBedrockProvider] Unrecognized stop reason "${stopReason}", ending conversation.`, + ); + return { shouldContinue: false, text: "" }; } } - // executeGenerate removed - BaseProvider handles all generation with tools + private convertToAWSMessages(bedrockMessages: BedrockMessage[]): Message[] { + return bedrockMessages.map((msg) => ({ + role: msg.role, + content: msg.content.map((item) => { + if (item.text) { + return { + text: item.text, + } as ContentBlock; + } + if (item.toolUse) { + return { + toolUse: { + toolUseId: item.toolUse.toolUseId, + name: item.toolUse.name, + input: item.toolUse.input, + }, + } as ContentBlock; + } + if (item.toolResult) { + return { + toolResult: { + toolUseId: item.toolResult.toolUseId, + content: item.toolResult.content, + status: item.toolResult.status, + }, + } as ContentBlock; + } + return { text: "" } as ContentBlock; + }), + })); + } + + private async executeSingleTool( + toolName: string, + args: Record, + _toolUseId?: string, + ): Promise { + logger.debug(`[AmazonBedrockProvider] Executing single tool: ${toolName}`, { + args, + }); - protected async executeStream( - options: StreamOptions, - _analysisSchema?: ZodUnknownSchema, - ): Promise { try { - this.validateStreamOptions(options); - const timeout = this.getTimeout(options); - const timeoutController = createTimeoutController( - timeout, - this.providerName, - "stream", - ); - - // Get tools consistently with generate method (now supports streaming with tools) - const shouldUseTools = !options.disableTools && this.supportsTools(); - const tools = shouldUseTools ? await this.getAllTools() : {}; - - // Build message array from options - const messages = buildMessagesArray(options); - - const result = streamText({ - model: this.model, - messages: messages, - tools, - maxSteps: options.maxSteps || DEFAULT_MAX_STEPS, - toolChoice: shouldUseTools ? "auto" : "none", - maxTokens: options.maxTokens || DEFAULT_MAX_TOKENS, - temperature: options.temperature, - abortSignal: timeoutController?.controller.signal, + // Use BaseProvider's tool execution mechanism + const aiTools = await this.getAllTools(); + const tools = this.convertAISDKToolsToToolDefinitions(aiTools); + + if (!tools[toolName]) { + throw new Error(`Tool not found: ${toolName}`); + } + + const tool = tools[toolName]; + if (!tool || !tool.execute) { + throw new Error(`Tool ${toolName} does not have execute method`); + } + + // Apply robust parameter handling like Bedrock-MCP-Connector + // Bedrock toolUse.input already contains the correct parameter structure + const toolInput = args || {}; + + // Add default parameters for common tools that Claude might call without required params + if (toolName === "list_directory" && !toolInput.path) { + toolInput.path = "."; + logger.debug( + `[AmazonBedrockProvider] Added default path '.' for list_directory tool`, + ); + } + + logger.debug(`[AmazonBedrockProvider] Tool input parameters:`, toolInput); + + // Convert Record to ToolArgs by filtering out non-JsonValue types + const toolArgs: ToolArgs = {}; + for (const [key, value] of Object.entries(toolInput)) { + // Only include values that are JsonValue compatible + if ( + value === null || + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean" || + (typeof value === "object" && value !== null) + ) { + toolArgs[key] = value as JsonValue; + } + } + + const result = await tool.execute(toolArgs); + logger.debug(`[AmazonBedrockProvider] Tool execution result:`, { + toolName, + result, }); - const streamResult = { - stream: (async function* (self: AmazonBedrockProvider) { - let chunkCount = 0; - let streamStarted = false; - let timeoutId: NodeJS.Timeout | null = null; + // Handle ToolResult type + if (result && typeof result === "object" && "success" in result) { + if (result.success && result.data !== undefined) { + if (typeof result.data === "string") { + return result.data; + } else if (typeof result.data === "object") { + return JSON.stringify(result.data, null, 2); + } else { + return String(result.data); + } + } else if (result.error) { + throw new Error(result.error.message || "Tool execution failed"); + } + } - try { - // Create timeout promise for first chunk with proper cleanup - const timeoutPromise = new Promise((_, reject) => { - timeoutId = setTimeout(() => { - if (!streamStarted && chunkCount === 0) { - reject( - new Error( - "❌ Amazon Bedrock Streaming Timeout\n\n" + - "Stream failed to produce any content within 5 seconds.\n\n" + - "🔧 Common Causes:\n" + - "1. Expired AWS credentials - run: aws sts get-caller-identity\n" + - "2. Missing Bedrock permissions - need: bedrock:InvokeModelWithResponseStream\n" + - "3. Model not available in your region\n" + - "4. Network connectivity issues\n\n" + - '💡 Try: neurolink generate "test" --provider bedrock\n' + - " (Generate mode provides more detailed error messages)", - ), - ); - } - }, 5000); + // Fallback for non-ToolResult return types + if (typeof result === "string") { + return result; + } else if (typeof result === "object") { + return JSON.stringify(result, null, 2); + } else { + return String(result); + } + } catch (error) { + logger.error(`[AmazonBedrockProvider] Tool execution error:`, { + toolName, + error, + }); + throw error; + } + } + + private convertAISDKToolsToToolDefinitions( + aiTools: Record, + ): Record> { + const result: Record> = {}; + + for (const [name, tool] of Object.entries(aiTools)) { + if ("description" in tool && tool.description) { + result[name] = { + description: tool.description, + parameters: "parameters" in tool ? tool.parameters : undefined, + execute: async (params: ToolArgs) => { + if ("execute" in tool && tool.execute) { + const result = await tool.execute(params as ToolArgs, { + toolCallId: `tool_${Date.now()}`, + messages: [], + }); + return { + success: true, + data: result, + }; + } + throw new Error(`Tool ${name} has no execute method`); + }, + }; + } + } + + return result; + } + + private formatToolsForBedrock( + tools: Record>, + ): ToolConfiguration | null { + if (!tools || Object.keys(tools).length === 0) { + return null; + } + + const bedrockTools: BedrockTool[] = Object.entries(tools).map( + ([name, tool]) => { + // Handle Zod schema or plain object schema + let schema: Record; + + if (tool.parameters && typeof tool.parameters === "object") { + // Check if it's a Zod schema + if ("_def" in tool.parameters) { + // It's a Zod schema, convert to JSON schema + schema = zodToJsonSchema(tool.parameters as ZodType) as Record< + string, + unknown + >; + } else { + // It's already a plain object schema + schema = tool.parameters as Record; + } + } else { + schema = { + type: "object", + properties: {}, + required: [], + }; + } + + // Ensure the schema always has type: "object" at the root level + if (!schema.type || schema.type !== "object") { + schema = { + type: "object", + properties: schema.properties || {}, + required: schema.required || [], + }; + } + + const toolSpec: ToolSpecification = { + name, + description: tool.description, + inputSchema: { + json: schema as DocumentType, + }, + }; + + return { + toolSpec, + } as BedrockTool; + }, + ); + + logger.debug( + `[AmazonBedrockProvider] Formatted ${bedrockTools.length} tools for Bedrock`, + ); + + return { tools: bedrockTools }; + } + + // Bedrock-MCP-Connector compatibility + getBedrockClient(): BedrockRuntimeClient { + return this.bedrockClient; + } + + protected async executeStream(options: StreamOptions): Promise { + logger.debug("🟢 [TRACE] executeStream ENTRY - starting streaming attempt"); + logger.info( + "🚀 [AmazonBedrockProvider] Attempting real streaming with ConverseStreamCommand", + ); + + try { + logger.debug( + "🟢 [TRACE] executeStream TRY block - about to call streamingConversationLoop", + ); + // CRITICAL FIX: Initialize conversation history like generate() does + // Clear conversation history for new streaming session + this.conversationHistory = []; + + // Add user message to conversation - exactly like generate() does + const userMessage: BedrockMessage = { + role: "user", + content: [{ text: options.input.text }], + }; + this.conversationHistory.push(userMessage); + + logger.debug( + `[AmazonBedrockProvider] Starting streaming conversation with prompt: ${options.input.text}`, + ); + + // Call the actual streaming implementation that already exists + logger.debug( + "🟢 [TRACE] executeStream - calling streamingConversationLoop NOW", + ); + const result = await this.streamingConversationLoop(options); + logger.debug( + "🟢 [TRACE] executeStream - streamingConversationLoop SUCCESS, returning result", + ); + return result; + } catch (error: unknown) { + logger.debug( + "🔴 [TRACE] executeStream CATCH - error caught from streamingConversationLoop", + ); + const errorObj = error as Error; + + // Check if error is related to streaming permissions + const isPermissionError = + (errorObj as unknown as Record)?.name === + "AccessDeniedException" || + (errorObj as unknown as Record)?.name === + "UnauthorizedOperation" || + errorObj?.message?.includes("bedrock:InvokeModelWithResponseStream") || + errorObj?.message?.includes("streaming") || + errorObj?.message?.includes("ConverseStream"); + + logger.debug( + "🔴 [TRACE] executeStream CATCH - checking if permission error", + ); + logger.debug( + `🔴 [TRACE] executeStream CATCH - isPermissionError=${isPermissionError}`, + ); + + if (isPermissionError) { + logger.debug( + "🟡 [TRACE] executeStream CATCH - PERMISSION ERROR DETECTED, starting fallback", + ); + logger.warn( + `[AmazonBedrockProvider] Streaming permissions not available, falling back to generate method: ${errorObj.message}`, + ); + + // Fallback to generate method and convert to streaming format + const generateResult = await this.generate({ + prompt: options.input.text, + }); + + if (!generateResult) { + throw new Error("Generate method returned null result"); + } + + // Convert generate result to streaming format + const stream = new ReadableStream({ + start(controller) { + // Split the response into chunks for pseudo-streaming + const responseText = generateResult.content || ""; + const chunks = responseText.split(" "); + + chunks.forEach((word: string, _index: number) => { + controller.enqueue({ content: word + " " }); }); - // Process stream with timeout handling - const streamIterator = result.textStream[Symbol.asyncIterator](); - let timeoutActive = true; - - while (true) { - let nextResult; - - if (timeoutActive) { - // Race between next chunk and timeout for first chunk only - nextResult = await Promise.race([ - streamIterator.next(), - timeoutPromise, - ]); - } else { - // No timeout for subsequent chunks - nextResult = await streamIterator.next(); - } + controller.enqueue({ content: "" }); + controller.close(); + }, + }); - if (nextResult.done) { - break; + // Convert ReadableStream to AsyncIterable like streamingConversationLoop does + const asyncIterable = { + async *[Symbol.asyncIterator]() { + const reader = stream.getReader(); + try { + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; + } + yield value; } + } finally { + reader.releaseLock(); + } + }, + }; + + return { + stream: asyncIterable, + usage: { total: 0, input: 0, output: 0 }, + model: this.modelName || this.getDefaultModel(), + provider: this.getProviderName(), + metadata: { + fallback: true, + }, + }; + } + + // Re-throw non-permission errors + throw error; + } + } - if (!streamStarted) { - streamStarted = true; - timeoutActive = false; - // Clear the timeout now that we have content - if (timeoutId) { - clearTimeout(timeoutId); - timeoutId = null; + private async streamingConversationLoop( + options: StreamOptions, + ): Promise { + logger.debug("🟦 [TRACE] streamingConversationLoop ENTRY"); + const maxIterations = 10; + let iteration = 0; + + // The REAL issue: ReadableStream errors don't bubble up to the caller + // So we need to make the first streaming call synchronously to test permissions + try { + logger.debug( + "🟦 [TRACE] streamingConversationLoop - testing first streaming call", + ); + const commandInput = await this.prepareStreamCommand(options); + const command = new ConverseStreamCommand(commandInput); + const response = await this.bedrockClient.send(command); + logger.debug( + "🟦 [TRACE] streamingConversationLoop - first streaming call SUCCESS", + ); + + // Process the first response immediately to avoid waste + + const stream = new ReadableStream({ + start: async (controller) => { + logger.debug( + "🟦 [TRACE] streamingConversationLoop - ReadableStream start() called", + ); + try { + // Process the first response we already have + if (response.stream) { + for await (const chunk of response.stream) { + if (chunk.contentBlockDelta?.delta?.text) { + controller.enqueue({ + content: chunk.contentBlockDelta.delta.text, + }); + } + if (chunk.messageStop) { + controller.close(); + return; } } + } - chunkCount++; - yield { content: nextResult.value }; + // Continue with normal iterations if needed + while (iteration < maxIterations) { + iteration++; + logger.debug( + `[AmazonBedrockProvider] Streaming iteration ${iteration}`, + ); + + const commandInput = await this.prepareStreamCommand(options); + const { stopReason, assistantMessage } = + await this.processStreamResponse(commandInput, controller); + + const shouldContinue = await this.handleStreamStopReason( + stopReason, + assistantMessage, + controller, + ); + if (!shouldContinue) { + break; + } } - // If no chunks received, likely an authentication error - if (chunkCount === 0) { - throw new Error( - "❌ Amazon Bedrock Streaming Error\n\n" + - "Stream completed with no content.\n\n" + - "🔧 Most Likely Causes:\n" + - "1. AWS credentials are expired or invalid\n" + - "2. Insufficient Bedrock permissions\n" + - "3. Model access not enabled in AWS console\n" + - "4. Region mismatch\n\n" + - "🔍 Debug Steps:\n" + - "1. Check credentials: aws sts get-caller-identity\n" + - '2. Test generate mode: neurolink generate "test" --provider bedrock\n' + - '3. Verify region: AWS_REGION=us-east-1 neurolink stream "test" --provider bedrock', + if (iteration >= maxIterations) { + controller.error( + new Error("Streaming conversation exceeded maximum iterations"), ); } } catch (error) { - // Clean up timeout on error - if (timeoutId) { - clearTimeout(timeoutId); - } - throw self.handleStreamError - ? self.handleStreamError(error) - : error; + logger.debug( + "🔴 [TRACE] streamingConversationLoop - CATCH block hit in ReadableStream", + ); + controller.error(error); } - })(this), - provider: this.providerName, - model: this.modelName, + }, + }); + + return { + stream: this.convertToAsyncIterable(stream), + usage: { total: 0, input: 0, output: 0 }, + model: this.modelName || this.getDefaultModel(), + provider: this.getProviderName(), }; + } catch (error: unknown) { + logger.debug( + "🔴 [TRACE] streamingConversationLoop - first streaming call FAILED, throwing", + ); + throw error; // This will be caught by executeStream + } + } - timeoutController?.cleanup(); - return streamResult; - } catch (error) { - throw this.handleProviderError(error); + private convertToAsyncIterable( + stream: ReadableStream, + ): AsyncIterable<{ content: string }> { + return { + async *[Symbol.asyncIterator]() { + const reader = stream.getReader(); + try { + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; + } + yield value; + } + } finally { + reader.releaseLock(); + } + }, + }; + } + + private async prepareStreamCommand( + options: StreamOptions, + ): Promise { + // CRITICAL DEBUG: Log conversation history before conversion + logger.info( + `🔍 [AmazonBedrockProvider] BEFORE conversion - conversationHistory length: ${this.conversationHistory.length}`, + ); + this.conversationHistory.forEach((msg, index) => { + logger.info( + `🔍 [AmazonBedrockProvider] Message ${index}: role=${msg.role}, content=${JSON.stringify(msg.content)}`, + ); + }); + + // Get all available tools + const aiTools = await this.getAllTools(); + const allTools = this.convertAISDKToolsToToolDefinitions(aiTools); + const toolConfig = this.formatToolsForBedrock(allTools); + + const convertedMessages = this.convertToAWSMessages( + this.conversationHistory, + ); + logger.info( + `🔍 [AmazonBedrockProvider] AFTER conversion - messages length: ${convertedMessages.length}`, + ); + convertedMessages.forEach((msg, index) => { + logger.info( + `🔍 [AmazonBedrockProvider] Converted Message ${index}: role=${msg.role}, content=${JSON.stringify(msg.content)}`, + ); + }); + + const commandInput: ConverseStreamCommandInput = { + modelId: this.modelName || this.getDefaultModel(), + messages: convertedMessages, + system: [ + { + text: + options.systemPrompt || + "You are a helpful assistant with access to external tools. Use tools when necessary to provide accurate information.", + }, + ], + inferenceConfig: { + maxTokens: options.maxTokens || 4096, + temperature: options.temperature || 0.7, + }, + }; + + if (toolConfig) { + commandInput.toolConfig = toolConfig; } + + logger.debug( + `[AmazonBedrockProvider] Calling Bedrock streaming with ${this.conversationHistory.length} messages`, + ); + + // DEBUG: Log exact conversation structure being sent to Bedrock + logger.debug(`[AmazonBedrockProvider] DEBUG - Conversation structure:`); + this.conversationHistory.forEach((msg, index) => { + logger.debug( + ` Message ${index} (${msg.role}): ${msg.content.length} content items`, + ); + msg.content.forEach((item, itemIndex) => { + const keys = Object.keys(item); + logger.debug(` Content ${itemIndex}: ${keys.join(", ")}`); + }); + }); + + return commandInput; } - protected handleStreamError(error: unknown): Error { - const errorMessage = error instanceof Error ? error.message : String(error); + private async processStreamResponse( + commandInput: ConverseStreamCommandInput, + controller: ReadableStreamDefaultController, + ): Promise<{ stopReason: string; assistantMessage: BedrockMessage }> { + const command = new ConverseStreamCommand(commandInput); + const response = await this.bedrockClient.send(command); + + if (!response.stream) { + throw new Error("No stream returned from Bedrock"); + } + + const currentMessageContent: BedrockContentBlock[] = []; + let stopReason = ""; + let currentText = ""; + + // Process streaming chunks + for await (const chunk of response.stream) { + if (chunk.contentBlockStart) { + // Starting a new content block + currentMessageContent.push({}); + } + + if (chunk.contentBlockDelta?.delta?.text) { + // Text delta - stream it to user + const textDelta = chunk.contentBlockDelta.delta.text; + currentText += textDelta; + + controller.enqueue({ + content: textDelta, + }); + } + + if (chunk.contentBlockStart?.start?.toolUse) { + // Tool use block starting - initialize tool information + const currentBlock = + currentMessageContent[currentMessageContent.length - 1]; + currentBlock.toolUse = { + name: chunk.contentBlockStart.start.toolUse.name || "", + input: {}, // Initialize empty - will be populated by delta chunks + toolUseId: + chunk.contentBlockStart.start.toolUse.toolUseId || + `tool_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`, + }; + } + + if (chunk.contentBlockDelta?.delta?.toolUse) { + // Tool use delta - accumulate tool information + const currentBlock = + currentMessageContent[currentMessageContent.length - 1]; + if (!currentBlock.toolUse) { + currentBlock.toolUse = { + name: "", + input: {}, + toolUseId: `tool_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`, + }; + } + // Use robust parameter merging like Bedrock-MCP-Connector + if (chunk.contentBlockDelta.delta.toolUse.input) { + // Merge parameters more robustly to avoid missing required parameters + const deltaInput = chunk.contentBlockDelta.delta.toolUse.input; + if (typeof deltaInput === "string") { + currentBlock.toolUse.input = { value: deltaInput }; + } else if ( + deltaInput && + typeof deltaInput === "object" && + !Array.isArray(deltaInput) + ) { + // Ensure both objects are properly typed before spreading + const currentInput = currentBlock.toolUse.input || {}; + const newInput = deltaInput; + currentBlock.toolUse.input = { + ...currentInput, + ...(newInput as Record), + } as Record; + } + } + } + + if (chunk.contentBlockStop) { + // Content block completed + const currentBlock = + currentMessageContent[currentMessageContent.length - 1]; + if (currentText && currentBlock && !currentBlock.toolUse) { + // Only add text to blocks that don't have toolUse + currentBlock.text = currentText; + } + currentText = ""; + } - // Stream-specific error handling - if ( - errorMessage.includes("no content") || - errorMessage.includes("Streaming Timeout") || - errorMessage.includes("Stream failed") - ) { - return new Error(errorMessage); // Already formatted in stream logic + if (chunk.messageStop) { + stopReason = chunk.messageStop.stopReason || "end_turn"; + break; + } } - // For other errors, use standard provider error handling - return this.handleProviderError(error); + // Add assistant message to conversation history + const assistantMessage: BedrockMessage = { + role: "assistant", + content: currentMessageContent, + }; + this.conversationHistory.push(assistantMessage); + + return { stopReason, assistantMessage }; } - protected handleProviderError(error: unknown): Error { - if (error instanceof Error && error.name === "TimeoutError") { - return new TimeoutError( - `Amazon Bedrock request timed out. Consider increasing timeout or using a lighter model.`, - this.defaultTimeout, + private async handleStreamStopReason( + stopReason: string, + assistantMessage: BedrockMessage, + controller: ReadableStreamDefaultController, + ): Promise { + if (stopReason === "end_turn" || stopReason === "stop_sequence") { + // Conversation completed + controller.close(); + return false; + } else if (stopReason === "tool_use") { + logger.debug( + `🛠️ [AmazonBedrockProvider] Tool use detected in streaming - executing tools`, ); + + await this.executeStreamTools(assistantMessage.content); + return true; // Continue conversation loop + } else if (stopReason === "max_tokens") { + // Handle max tokens by continuing conversation + const userMessage: BedrockMessage = { + role: "user", + content: [{ text: "Please continue." }], + }; + this.conversationHistory.push(userMessage); + return true; // Continue conversation loop + } else { + // Unknown stop reason - end conversation + controller.close(); + return false; } + } - const errorMessage = error instanceof Error ? error.message : String(error); + private async executeStreamTools( + messageContent: BedrockContentBlock[], + ): Promise { + // Execute all tool uses in the message - ensure 1:1 mapping like Bedrock-MCP-Connector + const toolResults = []; + let toolUseCount = 0; - if (errorMessage.includes("InvalidRequestException")) { - 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`, + // Count toolUse blocks first to ensure 1:1 mapping + for (const contentItem of messageContent) { + if (contentItem.toolUse) { + toolUseCount++; + } + } + + logger.debug( + `🔍 [AmazonBedrockProvider] Found ${toolUseCount} toolUse blocks in assistant message`, + ); + + for (const contentItem of messageContent) { + if (contentItem.toolUse) { + logger.debug( + `🔧 [AmazonBedrockProvider] Executing tool: ${contentItem.toolUse.name}`, + ); + + try { + const toolResult = await this.executeSingleTool( + contentItem.toolUse.name, + contentItem.toolUse.input || {}, + contentItem.toolUse.toolUseId, + ); + + logger.debug( + `✅ [AmazonBedrockProvider] Tool execution successful: ${contentItem.toolUse.name}`, + ); + + // Ensure exact structure matching Bedrock-MCP-Connector + toolResults.push({ + toolResult: { + toolUseId: contentItem.toolUse.toolUseId, + content: [{ text: String(toolResult) }], + status: "success", + }, + }); + } catch (error) { + logger.error( + `❌ [AmazonBedrockProvider] Tool execution failed: ${contentItem.toolUse.name}`, + error, + ); + + const errorMessage = + error instanceof Error ? error.message : String(error); + toolResults.push({ + toolResult: { + toolUseId: contentItem.toolUse.toolUseId, + content: [ + { + text: `Error executing tool ${contentItem.toolUse.name}: ${errorMessage}`, + }, + ], + status: "error", + }, + }); + } + } + } + + logger.debug( + `📊 [AmazonBedrockProvider] Created ${toolResults.length} toolResult blocks for ${toolUseCount} toolUse blocks`, + ); + + // Validate 1:1 mapping before adding to conversation + if (toolResults.length !== toolUseCount) { + logger.error( + `❌ [AmazonBedrockProvider] Mismatch: ${toolResults.length} toolResults vs ${toolUseCount} toolUse blocks`, + ); + throw new Error( + `Tool mapping mismatch: ${toolResults.length} toolResults for ${toolUseCount} toolUse blocks`, ); } - if (errorMessage.includes("AccessDeniedException")) { - 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`, + // Add tool results as user message - exact structure like Bedrock-MCP-Connector + if (toolResults.length > 0) { + const userMessageWithToolResults: BedrockMessage = { + role: "user", + content: toolResults, + }; + this.conversationHistory.push(userMessageWithToolResults); + + logger.debug( + `📤 [AmazonBedrockProvider] Added ${toolResults.length} tool results to conversation (1:1 mapping validated)`, ); } + } + + /** + * Health check for Amazon Bedrock service + * Uses ListFoundationModels API to validate connectivity and permissions + */ + async checkBedrockHealth(): Promise { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout - if (errorMessage.includes("ValidationException")) { + // Create a separate BedrockClient for health checks (not BedrockRuntimeClient) + // Use simple configuration like working example - no custom proxy handler + const healthCheckClient = new BedrockClient({ + region: process.env.AWS_REGION || "us-east-1", + }); + + try { + logger.debug("🔍 [AmazonBedrockProvider] Starting health check..."); + + const command = new ListFoundationModelsCommand({}); + const response = await healthCheckClient.send(command, { + abortSignal: controller.signal, + }); + + const models = response.modelSummaries || []; + const activeModels = models.filter( + (model) => model.modelLifecycle?.status === "ACTIVE", + ); + + logger.debug( + `✅ [AmazonBedrockProvider] Health check passed - Found ${activeModels.length} active models out of ${models.length} total models`, + ); + + if (activeModels.length === 0) { + throw new Error("No active foundation models available in the region"); + } + } catch (error: unknown) { + clearTimeout(timeoutId); + + const errorObj = error as Record; + + if (errorObj.name === "AbortError") { + throw new Error("Bedrock health check timed out after 10 seconds"); + } + + const errorMessage = + typeof errorObj.message === "string" ? errorObj.message : ""; + if ( + errorMessage.includes("UnauthorizedOperation") || + errorMessage.includes("AccessDenied") + ) { + throw new Error( + "Bedrock access denied. Check your AWS credentials and IAM permissions for bedrock:ListFoundationModels", + ); + } + + if (errorObj.code === "ECONNREFUSED" || errorObj.code === "ENOTFOUND") { + throw new Error( + "Unable to connect to Bedrock service. Check your network connectivity and AWS region configuration", + ); + } + + logger.error("❌ [AmazonBedrockProvider] Health check failed:", error); + throw new Error( + `Bedrock health check failed: ${errorMessage || "Unknown error"}`, + ); + } finally { + clearTimeout(timeoutId); + try { + healthCheckClient.destroy(); + } catch { + // Ignore destroy errors during cleanup + } + } + } + + protected handleProviderError(error: unknown): Error { + // Handle AWS SDK specific errors + const message = error instanceof Error ? error.message : String(error); + + if (message.includes("AccessDeniedException")) { 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`, + "AWS Bedrock access denied. Check your credentials and permissions.", ); } - 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`, - ); + if (message.includes("ValidationException")) { + return new Error(`AWS Bedrock validation error: ${message}`); + } + + return new Error(`AWS Bedrock error: ${message}`); } } - -export default AmazonBedrockProvider; diff --git a/src/lib/providers/aws/credentialProvider.ts b/src/lib/providers/aws/credentialProvider.ts deleted file mode 100644 index ff59fc2f9..000000000 --- a/src/lib/providers/aws/credentialProvider.ts +++ /dev/null @@ -1,303 +0,0 @@ -/** - * AWS Credential Provider for NeuroLink - * - * Provides 100% compatibility with Bedrock-MCP-Connector authentication patterns - * by leveraging AWS SDK v3's official defaultProvider credential chain. - * - * Supports all 9 AWS credential sources: - * 1. Environment Variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) - * 2. AWS Credentials File (~/.aws/credentials) - * 3. AWS Config File (~/.aws/config) - * 4. IAM Roles (EC2/ECS/Lambda) - * 5. AWS SSO - * 6. STS Assume Role - * 7. Credential Process - * 8. Container Credentials - * 9. Instance Metadata Service (IMDS) - */ - -import { defaultProvider } from "@aws-sdk/credential-provider-node"; -import { fromEnv } from "@aws-sdk/credential-providers"; -import type { AwsCredentialIdentity, Provider } from "@aws-sdk/types"; -import { logger } from "../../utils/logger.js"; -import type { AWSCredentialConfig } from "../../types/providers.js"; - -/** - * AWS Credential Provider class that wraps AWS SDK v3's defaultProvider - * to provide seamless compatibility with Bedrock-MCP-Connector authentication - */ -export class AWSCredentialProvider { - private credentialProvider: Provider; - private config: Required; - private isInitialized: boolean = false; - private lastCredentials: AwsCredentialIdentity | null = null; - private lastRefresh: number = 0; - - constructor(config: AWSCredentialConfig = {}) { - // Set default configuration values - this.config = { - region: config.region || process.env.AWS_REGION || "us-east-1", - profile: config.profile || process.env.AWS_PROFILE || "default", - roleArn: config.roleArn || process.env.AWS_ROLE_ARN || "", - roleSessionName: - config.roleSessionName || process.env.AWS_ROLE_SESSION_NAME || "", - timeout: config.timeout || 30000, - maxRetries: config.maxRetries || 3, - maxAttempts: config.maxAttempts || config.maxRetries || 3, - endpoint: config.endpoint || "", - enableDebugLogging: config.enableDebugLogging || false, - }; - - // Check if environment variables are set - if so, prioritize them - const hasEnvCredentials = !!( - process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY - ); - - if (hasEnvCredentials) { - // Force use of environment variables when they're explicitly set - this.credentialProvider = fromEnv(); - - if (this.config.enableDebugLogging) { - logger.debug("AWS Credential Provider: Using environment variables", { - accessKeyId: process.env.AWS_ACCESS_KEY_ID?.substring(0, 8) + "***", - hasSessionToken: !!process.env.AWS_SESSION_TOKEN, - region: this.config.region, - }); - } - } else { - // Use default provider chain when no environment variables - this.credentialProvider = defaultProvider({ - profile: this.config.profile, - roleArn: this.config.roleArn || undefined, - roleSessionName: this.config.roleSessionName || undefined, - timeout: this.config.timeout, - maxRetries: this.config.maxRetries, - }); - - if (this.config.enableDebugLogging) { - logger.debug("AWS Credential Provider: Using default provider chain", { - profile: this.config.profile, - roleArn: this.config.roleArn ? "***" : "none", - timeout: this.config.timeout, - maxRetries: this.config.maxRetries, - }); - } - } - - if (this.config.enableDebugLogging) { - logger.debug("AWS Credential Provider initialized", { - credentialSource: hasEnvCredentials ? "environment" : "default-chain", - region: this.config.region, - profile: this.config.profile, - roleArn: this.config.roleArn ? "***" : "none", - timeout: this.config.timeout, - maxRetries: this.config.maxRetries, - }); - } - - this.isInitialized = true; - } - - /** - * Get AWS credentials using the default provider chain - * Implements caching to avoid unnecessary credential resolution calls - */ - async getCredentials(): Promise { - if (this.config.enableDebugLogging) { - logger.debug("getCredentials() called", { - isInitialized: this.isInitialized, - hasLastCredentials: !!this.lastCredentials, - config: { - region: this.config.region, - profile: this.config.profile, - roleArn: this.config.roleArn ? "***" : "none", - timeout: this.config.timeout, - maxRetries: this.config.maxRetries, - }, - environment: { - AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID - ? process.env.AWS_ACCESS_KEY_ID.substring(0, 8) + "***" - : "not set", - AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY - ? "***" - : "not set", - AWS_SESSION_TOKEN: process.env.AWS_SESSION_TOKEN ? "set" : "not set", - AWS_REGION: process.env.AWS_REGION || "not set", - AWS_PROFILE: process.env.AWS_PROFILE || "not set", - }, - }); - } - - try { - if (!this.isInitialized) { - throw new Error("AWSCredentialProvider not initialized"); - } - - // Check if cached credentials are still valid (within 5 minutes) - const now = Date.now(); - if (this.lastCredentials && now - this.lastRefresh < 300000) { - // Check if credentials have expiration and are still valid - if ( - !this.lastCredentials.expiration || - this.lastCredentials.expiration > new Date(now + 60000) - ) { - if (this.config.enableDebugLogging) { - logger.debug("Using cached AWS credentials", { - cacheAge: now - this.lastRefresh, - hasExpiration: !!this.lastCredentials.expiration, - expiration: this.lastCredentials.expiration?.toISOString(), - }); - } - return this.lastCredentials; - } else { - if (this.config.enableDebugLogging) { - logger.debug("Cached credentials expired, refreshing", { - cacheAge: now - this.lastRefresh, - expiration: this.lastCredentials.expiration?.toISOString(), - }); - } - } - } - - if (this.config.enableDebugLogging) { - logger.debug("Calling AWS SDK credential provider", { - providerType: "defaultProvider", - timeout: this.config.timeout, - maxRetries: this.config.maxRetries, - }); - } - - // Resolve credentials using AWS SDK default provider chain - const credentials = await this.credentialProvider(); - - if (this.config.enableDebugLogging) { - logger.debug("AWS SDK credential provider returned", { - hasAccessKeyId: !!credentials.accessKeyId, - accessKeyIdPrefix: credentials.accessKeyId - ? credentials.accessKeyId.substring(0, 8) - : "none", - hasSecretAccessKey: !!credentials.secretAccessKey, - hasSessionToken: !!credentials.sessionToken, - hasExpiration: !!credentials.expiration, - expiration: credentials.expiration?.toISOString() || "none", - credentialType: credentials.accessKeyId?.startsWith("ASIA") - ? "temporary" - : "long-term", - }); - } - - // Cache the credentials - this.lastCredentials = credentials; - this.lastRefresh = now; - - if (this.config.enableDebugLogging) { - logger.debug("AWS credentials resolved and cached successfully", { - accessKeyId: credentials.accessKeyId.substring(0, 8) + "***", - hasSessionToken: !!credentials.sessionToken, - expiration: credentials.expiration?.toISOString() || "none", - credentialSource: "AWS SDK defaultProvider chain", - }); - } - - return credentials; - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : String(error); - - logger.error("Failed to resolve AWS credentials", { - error: errorMessage, - errorType: error instanceof Error ? error.constructor.name : "unknown", - stack: error instanceof Error ? error.stack : "no stack trace", - config: this.config, - environment: { - AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID ? "set" : "not set", - AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY - ? "set" - : "not set", - AWS_SESSION_TOKEN: process.env.AWS_SESSION_TOKEN ? "set" : "not set", - AWS_REGION: process.env.AWS_REGION || "not set", - AWS_PROFILE: process.env.AWS_PROFILE || "not set", - }, - }); - - // Provide helpful error messages for common credential issues - if (errorMessage.includes("No credentials found")) { - throw new Error( - "No AWS credentials found. Please configure one of the following:\n" + - "1. Environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY\n" + - "2. AWS credentials file: ~/.aws/credentials\n" + - "3. IAM role (if running on EC2/ECS/Lambda)\n" + - "4. AWS SSO: aws configure sso\n" + - "Original error: " + - errorMessage, - ); - } - - if (errorMessage.includes("Credential is expired")) { - throw new Error( - "AWS credentials have expired. Please refresh your credentials:\n" + - "1. Re-run aws configure\n" + - "2. Refresh SSO: aws sso login\n" + - "3. Assume new role if using temporary credentials\n" + - "Original error: " + - errorMessage, - ); - } - - throw new Error(`AWS credential resolution failed: ${errorMessage}`); - } - } - - /** - * Get the raw credential provider for direct use with AWS SDK clients - * This allows the credential provider to be passed directly to BedrockRuntimeClient - */ - getCredentialProvider(): Provider { - if (!this.isInitialized) { - throw new Error("AWSCredentialProvider not initialized"); - } - return this.credentialProvider; - } - - /** - * Force refresh of cached credentials - * Useful when credentials may have been updated externally - */ - async refreshCredentials(): Promise { - this.lastCredentials = null; - this.lastRefresh = 0; - return await this.getCredentials(); - } - - /** - * Check if credentials are currently available without throwing errors - */ - async isCredentialsAvailable(): Promise { - try { - await this.getCredentials(); - return true; - } catch { - return false; - } - } - - /** - * Get configuration information for debugging - */ - getConfig(): Readonly> { - return { ...this.config }; - } - - /** - * Clean up resources and clear cached credentials - */ - dispose(): void { - this.lastCredentials = null; - this.lastRefresh = 0; - this.isInitialized = false; - - if (this.config.enableDebugLogging) { - logger.debug("AWS Credential Provider disposed"); - } - } -} diff --git a/src/lib/providers/aws/credentialTester.ts b/src/lib/providers/aws/credentialTester.ts deleted file mode 100644 index 322b6cd9c..000000000 --- a/src/lib/providers/aws/credentialTester.ts +++ /dev/null @@ -1,554 +0,0 @@ -/** - * AWS Credential Testing Utilities for NeuroLink - * - * Provides comprehensive validation and debugging capabilities for AWS credentials - * to ensure compatibility with Bedrock-MCP-Connector authentication patterns. - */ - -import { AWSCredentialProvider } from "./credentialProvider.js"; -import { - BedrockClient, - ListFoundationModelsCommand, -} from "@aws-sdk/client-bedrock"; -import type { AwsCredentialIdentity } from "@aws-sdk/types"; -import { logger } from "../../utils/logger.js"; -import type { - AWSCredentialConfig, - CredentialValidationResult, - ServiceConnectivityResult, -} from "../../types/providers.js"; - -/** - * Interface for AWS error objects to safely extract error information - * Enhanced to support multiple AWS SDK error formats and patterns - */ -interface AWSError { - // Error codes (various formats across SDK versions) - Code?: string; - code?: string; - errorCode?: string; - ErrorCode?: string; - - // Error names and types - name?: string; - errorType?: string; - ErrorType?: string; - - // Error messages (various formats) - message?: string; - Message?: string; - errorMessage?: string; - - // Metadata (various patterns) - $metadata?: { - httpStatusCode?: number; - statusCode?: number; - status?: number; - requestId?: string; - RequestId?: string; - request_id?: string; - "x-amzn-requestid"?: string; - }; - metadata?: unknown; - $response?: unknown; - - // Nested/wrapped errors (AWS SDK v3 patterns) - cause?: unknown; - $fault?: unknown; - - // Constructor for fallback name extraction - constructor?: { - name?: string; - }; -} - -/** - * Comprehensive AWS error information extraction result - */ -interface AWSErrorInfo { - code?: string; - name?: string; - message?: string; - statusCode?: number; - requestId?: string; -} - -/** - * Helper function to safely extract comprehensive error information from AWS errors - * This centralizes error extraction and captures AWS SDK v3 name, statusCode, and requestId - * Enhanced to handle multiple AWS SDK error formats and edge cases - */ -function extractAwsErrorInfo(error: unknown): AWSErrorInfo { - const result: AWSErrorInfo = {}; - - if (typeof error === "object" && error !== null) { - const awsError = error as AWSError; // Use AWSError interface to handle various error shapes - - // Extract error code with comprehensive fallbacks - // AWS SDK v2: error.code, error.Code - // AWS SDK v3: error.name, error.Code, error.code - // Some services: error.errorCode, error.ErrorCode - result.code = - awsError.Code || - awsError.code || - awsError.errorCode || - awsError.ErrorCode || - awsError.name; // AWS SDK v3 often uses name as error code - - // Extract error name with fallbacks - // AWS SDK v3: error.name is primary - // Some errors: error.errorType, error.ErrorType - // Fallback to constructor name - result.name = - awsError.name || - awsError.errorType || - awsError.ErrorType || - awsError.constructor?.name; - - // Extract error message with fallbacks - // Standard: error.message - // Some services: error.Message, error.errorMessage - result.message = - awsError.message || - awsError.Message || - awsError.errorMessage || - String(error); // Last resort stringification - - // Extract metadata with multiple patterns - // AWS SDK v3: error.$metadata - // Some services: error.metadata, error.$response - const metadata = - awsError.$metadata || awsError.metadata || awsError.$response; - if (metadata && typeof metadata === "object") { - const metadataObj = metadata as Record; - result.statusCode = - (metadataObj.httpStatusCode as number) || - (metadataObj.statusCode as number) || - (metadataObj.status as number); - result.requestId = - (metadataObj.requestId as string) || - (metadataObj.RequestId as string) || - (metadataObj.request_id as string) || - (metadataObj["x-amzn-requestid"] as string); // Common response header - } - - // Handle wrapped errors (common in AWS SDK v3) - // Sometimes the actual error is nested in error.cause or error.$fault - if (!result.code && (awsError.cause || awsError.$fault)) { - const nestedError = awsError.cause || awsError.$fault; - const nestedInfo = extractAwsErrorInfo(nestedError); - // Merge nested error info, preferring current level - result.code = result.code || nestedInfo.code; - result.name = result.name || nestedInfo.name; - result.message = result.message || nestedInfo.message; - result.statusCode = result.statusCode || nestedInfo.statusCode; - result.requestId = result.requestId || nestedInfo.requestId; - } - } - - // Handle primitive error types (strings, etc.) - if (!result.message && error) { - result.message = String(error); - } - - return result; -} - -/** - * Credential testing and validation utility class - */ -export class CredentialTester { - /** - * Validate AWS credentials and detect their source - */ - static async validateCredentials( - provider: AWSCredentialProvider, - ): Promise { - const startTime = Date.now(); - - try { - // Get credentials from provider - const credentials = await provider.getCredentials(); - const config = provider.getConfig(); - - // Detect credential source based on available information - const credentialSource = await this.detectCredentialSource( - credentials, - config, - ); - - const result: CredentialValidationResult = { - isValid: true, - credentialSource, - region: config.region, - hasExpiration: !!credentials.expiration, - expirationTime: credentials.expiration, - debugInfo: { - accessKeyId: credentials.accessKeyId.substring(0, 8) + "***", - hasSessionToken: !!credentials.sessionToken, - providerConfig: config, - }, - }; - - logger.debug("Credential validation successful", { - source: credentialSource, - region: config.region, - validationTimeMs: Date.now() - startTime, - }); - - return result; - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : String(error); - - logger.error("Credential validation failed", { - error: errorMessage, - validationTimeMs: Date.now() - startTime, - }); - - return { - isValid: false, - credentialSource: "unknown", - region: provider.getConfig().region, - hasExpiration: false, - error: errorMessage, - debugInfo: { - accessKeyId: "unavailable", - hasSessionToken: false, - providerConfig: provider.getConfig(), - }, - }; - } - } - - /** - * Test AWS Bedrock service connectivity - */ - static async testBedrockConnectivity( - provider: AWSCredentialProvider, - region?: string, - ): Promise { - const startTime = Date.now(); - const testRegion = region || provider.getConfig().region; - - logger.debug("Starting Bedrock connectivity test", { - region: testRegion, - providerConfig: provider.getConfig(), - }); - - try { - // First, get credentials to see what we're working with - const credentials = await provider.getCredentials(); - logger.debug("Got credentials for Bedrock test", { - accessKeyId: credentials.accessKeyId.substring(0, 8) + "***", - hasSessionToken: !!credentials.sessionToken, - hasExpiration: !!credentials.expiration, - expiration: credentials.expiration?.toISOString(), - credentialType: credentials.accessKeyId?.startsWith("ASIA") - ? "temporary" - : "long-term", - }); - - // Create Bedrock client with credential provider (use BedrockClient for listing models) - logger.debug("Creating BedrockClient", { - region: testRegion, - credentialProviderType: typeof provider.getCredentialProvider(), - }); - - const bedrockClient = new BedrockClient({ - region: testRegion, - credentials: provider.getCredentialProvider(), - maxAttempts: provider.getConfig().maxAttempts || 3, - }); - - logger.debug( - "BedrockClient created, sending ListFoundationModelsCommand", - ); - - // Test connectivity by listing foundation models - const command = new ListFoundationModelsCommand({}); - const ctrl = new AbortController(); - const timeoutId = setTimeout( - () => ctrl.abort(), - provider.getConfig().timeout || 15000, // Default 15 second timeout - ); - let response; - try { - response = await bedrockClient.send(command, { - abortSignal: ctrl.signal, - }); - } finally { - clearTimeout(timeoutId); - } - - logger.debug("ListFoundationModelsCommand response received", { - hasModelSummaries: !!response.modelSummaries, - modelCount: response.modelSummaries?.length || 0, - responseMetadata: response.$metadata, - }); - - const models = response.modelSummaries || []; - const responseTime = Date.now() - startTime; - - const result: ServiceConnectivityResult = { - bedrockAccessible: true, - availableModels: models.length, - responseTimeMs: responseTime, - sampleModels: models - .slice(0, 5) - .map((model: { modelId?: string }) => model.modelId || "unknown"), - }; - - logger.debug("Bedrock connectivity test successful", { - region: testRegion, - modelsFound: models.length, - responseTimeMs: responseTime, - sampleModels: result.sampleModels, - }); - - return result; - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : String(error); - const responseTime = Date.now() - startTime; - - const { code, statusCode, requestId } = extractAwsErrorInfo(error); - logger.error("Bedrock connectivity test failed", { - region: testRegion, - error: errorMessage, - errorType: error instanceof Error ? error.constructor.name : "unknown", - errorCode: code, - statusCode, - requestId, - stack: error instanceof Error ? error.stack : "no stack trace", - responseTimeMs: responseTime, - }); - - return { - bedrockAccessible: false, - availableModels: 0, - responseTimeMs: responseTime, - error: errorMessage, - sampleModels: [], - }; - } - } - - /** - * Perform comprehensive credential and service testing - */ - static async runComprehensiveTest( - provider: AWSCredentialProvider, - testRegions: string[] = ["us-east-1", "us-west-2"], - ): Promise<{ - credentialValidation: CredentialValidationResult; - connectivityTests: Array<{ - region: string; - result: ServiceConnectivityResult; - }>; - overallStatus: "success" | "partial" | "failed"; - summary: string; - }> { - logger.debug("Starting comprehensive AWS credential and connectivity test"); - - // Test credential validation - const credentialValidation = await this.validateCredentials(provider); - - // Test connectivity across multiple regions (in parallel) - const connectivityTests = await Promise.all( - testRegions.map(async (region) => { - try { - const result = await this.testBedrockConnectivity(provider, region); - return { region, result }; - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : String(error); - return { - region, - result: { - bedrockAccessible: false, - availableModels: 0, - responseTimeMs: 0, - error: errorMessage, - sampleModels: [], - }, - }; - } - }), - ); - - // Determine overall status - let overallStatus: "success" | "partial" | "failed"; - let summary: string; - - if (!credentialValidation.isValid) { - overallStatus = "failed"; - summary = `Credential validation failed: ${credentialValidation.error}`; - } else { - const successfulConnections = connectivityTests.filter( - (test) => test.result.bedrockAccessible, - ).length; - - if (successfulConnections === testRegions.length) { - overallStatus = "success"; - summary = `All tests passed. Credentials valid, Bedrock accessible in ${successfulConnections}/${testRegions.length} regions.`; - } else if (successfulConnections > 0) { - overallStatus = "partial"; - summary = `Partial success. Credentials valid, Bedrock accessible in ${successfulConnections}/${testRegions.length} regions.`; - } else { - overallStatus = "failed"; - summary = `Credentials valid but Bedrock inaccessible in all tested regions.`; - } - } - - logger.info("Comprehensive test completed", { - overallStatus, - credentialSource: credentialValidation.credentialSource, - successfulConnections: connectivityTests.filter( - (test) => test.result.bedrockAccessible, - ).length, - totalRegionsTested: testRegions.length, - }); - - return { - credentialValidation, - connectivityTests, - overallStatus, - summary, - }; - } - - /** - * Detect the source of AWS credentials based on credential properties and environment - */ - private static async detectCredentialSource( - credentials: AwsCredentialIdentity, - config: Readonly>, - ): Promise { - // Check for environment variables (static creds) - if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) { - return credentials.sessionToken - ? "Environment Variables (with session token)" - : "Environment Variables"; - } - - // Explicit env‐based sources first - if (process.env.AWS_WEB_IDENTITY_TOKEN_FILE) { - return "Web Identity Token"; - } - if (process.env.AWS_CREDENTIAL_PROCESS) { - return "Credential Process"; - } - - // Check for role‐based credentials (temporary credentials with session token) - if (credentials.sessionToken && credentials.expiration) { - // Prefer explicit hints first - if (config.roleArn) { - return "STS Assume Role"; - } - - // Enhanced container detection - if ( - process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || - process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI - ) { - // Detect specific container environments - if (process.env.AWS_EXECUTION_ENV === "AWS_ECS_FARGATE") { - return "Container Credentials (ECS Fargate)"; - } - if (process.env.AWS_EXECUTION_ENV === "AWS_ECS_EC2") { - return "Container Credentials (ECS)"; - } - return "Container Credentials (ECS)"; - } - - // Enhanced Lambda detection - if (process.env.AWS_LAMBDA_FUNCTION_NAME) { - return "Lambda Execution Role"; - } - - // Enhanced EC2 detection - if (process.env.AWS_EXECUTION_ENV?.includes("EC2")) { - return "Instance Metadata (EC2)"; - } - - // Enhanced EKS detection - if (process.env.KUBERNETES_SERVICE_HOST) { - return "Service Account (EKS)"; - } - - return "Temporary Credentials (IAM Role)"; - } - - // SSO (env‐configured) - if (process.env.AWS_SSO_START_URL || process.env.AWS_SSO_REGION) { - return "AWS SSO"; - } - - // Check for profile‐based credentials - if (config.profile !== "default") { - return `AWS Profile (${config.profile})`; - } - if (process.env.AWS_PROFILE) { - return `AWS Profile (${process.env.AWS_PROFILE})`; - } - - // Default fallback - return "AWS Credentials File"; - } - - /** - * Get credential source name for debugging - */ - static async getCredentialSource( - provider: AWSCredentialProvider, - ): Promise { - try { - const credentials = await provider.getCredentials(); - const config = provider.getConfig(); - return await this.detectCredentialSource(credentials, config); - } catch { - return "Unable to determine credential source"; - } - } - - /** - * Test credential refresh functionality - */ - static async testCredentialRefresh(provider: AWSCredentialProvider): Promise<{ - refreshSuccessful: boolean; - refreshTimeMs: number; - error?: string; - }> { - const startTime = Date.now(); - - try { - await provider.refreshCredentials(); - const refreshTime = Date.now() - startTime; - - logger.debug("Credential refresh test successful", { - refreshTimeMs: refreshTime, - }); - - return { - refreshSuccessful: true, - refreshTimeMs: refreshTime, - }; - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : String(error); - const refreshTime = Date.now() - startTime; - - logger.error("Credential refresh test failed", { - error: errorMessage, - refreshTimeMs: refreshTime, - }); - - return { - refreshSuccessful: false, - refreshTimeMs: refreshTime, - error: errorMessage, - }; - } - } -} diff --git a/src/lib/types/generateTypes.ts b/src/lib/types/generateTypes.ts index d8f7dbd82..774250e80 100644 --- a/src/lib/types/generateTypes.ts +++ b/src/lib/types/generateTypes.ts @@ -1,8 +1,5 @@ import type { Tool } from "ai"; -import type { - ValidationSchema, - StandardRecord, -} from "./typeAliases.js"; +import type { ValidationSchema, StandardRecord } from "./typeAliases.js"; import type { AIProviderName, AnalyticsData, diff --git a/src/lib/utils/conversationMemoryUtils.ts b/src/lib/utils/conversationMemoryUtils.ts index 441d1283e..dbf5ddc91 100644 --- a/src/lib/utils/conversationMemoryUtils.ts +++ b/src/lib/utils/conversationMemoryUtils.ts @@ -12,9 +12,7 @@ import type { TextGenerationOptions, TextGenerationResult, } from "../core/types.js"; -import { - getConversationMemoryDefaults, -} from "../config/conversationMemoryConfig.js"; +import { getConversationMemoryDefaults } from "../config/conversationMemoryConfig.js"; import { logger } from "./logger.js"; /** @@ -66,7 +64,6 @@ export async function getConversationMessages( } } - /** * Store conversation turn for future context * Saves user messages and AI responses for conversation memory diff --git a/src/lib/utils/logger.ts b/src/lib/utils/logger.ts index 1d35d1549..5b439427c 100644 --- a/src/lib/utils/logger.ts +++ b/src/lib/utils/logger.ts @@ -408,10 +408,10 @@ export function setGlobalMCPLogLevel(level: LogLevel): void { * Example usage: * ``` * import { logger, LogLevels } from './logger'; // Import from your project's path - * + * * // Using the LogLevels constants (recommended for type safety): * logger.setLogLevel(LogLevels.debug); - * + * * // Or directly using string values: * logger.setLogLevel('debug'); * ``` diff --git a/src/lib/utils/providerUtils.ts b/src/lib/utils/providerUtils.ts index af8fd7640..fcf66ea5c 100644 --- a/src/lib/utils/providerUtils.ts +++ b/src/lib/utils/providerUtils.ts @@ -19,7 +19,13 @@ export async function getBestProvider( ): Promise { // Check requested provider FIRST - explicit user choice overrides defaults if (requestedProvider && requestedProvider !== "auto") { - // For explicit provider requests, check health first + // For explicit provider requests, ALWAYS honor the request + // Never override explicit provider selection with health-based fallbacks + logger.debug( + `[getBestProvider] Using explicitly requested provider: ${requestedProvider}`, + ); + + // Optional health check for logging purposes only try { const health = await ProviderHealthChecker.checkProviderHealth( requestedProvider as AIProviderName, @@ -28,22 +34,23 @@ export async function getBestProvider( if (health.isHealthy) { logger.debug( - `[getBestProvider] Using healthy explicitly requested provider: ${requestedProvider}`, + `[getBestProvider] Explicitly requested provider ${requestedProvider} is healthy`, ); - return requestedProvider; } else { logger.warn( - `[getBestProvider] Requested provider ${requestedProvider} is unhealthy, finding alternative`, + `[getBestProvider] Explicitly requested provider ${requestedProvider} may have issues, but using anyway`, { error: health.error }, ); } } catch (error) { logger.warn( - `[getBestProvider] Health check failed for ${requestedProvider}, using anyway`, + `[getBestProvider] Health check failed for explicitly requested provider ${requestedProvider}, using anyway`, { error: error instanceof Error ? error.message : String(error) }, ); - return requestedProvider; // Return anyway for explicit requests } + + // ALWAYS return the explicitly requested provider + return requestedProvider; } // Use health checker to get best available provider diff --git a/test/providers/aws/authentication.test.ts b/test/providers/aws/authentication.test.ts deleted file mode 100644 index 4e2ee5121..000000000 --- a/test/providers/aws/authentication.test.ts +++ /dev/null @@ -1,411 +0,0 @@ -/** - * AWS Authentication Test Suite for NeuroLink - * - * Comprehensive testing of all 9 AWS credential sources to ensure - * 100% compatibility with Bedrock-MCP-Connector authentication patterns. - */ - -import { describe, test, expect, beforeEach, afterEach, vi } from "vitest"; -import { - AWSCredentialProvider, - type AWSCredentialConfig, -} from "../../../src/lib/providers/aws/credentialProvider.js"; -import { CredentialTester } from "../../../src/lib/providers/aws/credentialTester.js"; -import { AmazonBedrockProvider } from "../../../src/lib/providers/amazonBedrock.js"; - -// Mock environment setup -const mockEnv = { - AWS_ACCESS_KEY_ID: "AKIA123456789EXAMPLE", - AWS_SECRET_ACCESS_KEY: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - AWS_SESSION_TOKEN: - "AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4Olgk", - AWS_REGION: "us-east-1", - AWS_PROFILE: "default", - AWS_ROLE_ARN: "arn:aws:iam::123456789012:role/ExampleRole", - AWS_ROLE_SESSION_NAME: "ExampleSessionName", -}; - -// Store original environment -const originalEnv = { ...process.env }; - -describe("AWS Authentication Compatibility Tests", () => { - beforeEach(() => { - // Clear all AWS and Bedrock related environment variables - Object.keys(process.env).forEach((key) => { - if ( - key.startsWith("AWS_") || - key === "BEDROCK_MODEL" || - key === "GOOGLE_" - ) { - delete process.env[key]; - } - }); - }); - - afterEach(() => { - // Complete environment restoration - // First clear all potentially set variables - Object.keys(process.env).forEach((key) => { - if ( - key.startsWith("AWS_") || - key === "BEDROCK_MODEL" || - key === "GOOGLE_" - ) { - delete process.env[key]; - } - }); - - // Then restore only the original AWS and Bedrock variables - Object.keys(originalEnv).forEach((key) => { - if ( - key.startsWith("AWS_") || - key === "BEDROCK_MODEL" || - key === "GOOGLE_" - ) { - if (originalEnv[key] !== undefined) { - process.env[key] = originalEnv[key]; - } - } - }); - }); - - describe("Environment Variable Authentication", () => { - test("should resolve credentials from environment variables", async () => { - // Set up environment variables - process.env.AWS_ACCESS_KEY_ID = mockEnv.AWS_ACCESS_KEY_ID; - process.env.AWS_SECRET_ACCESS_KEY = mockEnv.AWS_SECRET_ACCESS_KEY; - process.env.AWS_REGION = mockEnv.AWS_REGION; - - const provider = new AWSCredentialProvider({ - enableDebugLogging: true, - }); - - const credentials = await provider.getCredentials(); - - expect(credentials.accessKeyId).toBe(mockEnv.AWS_ACCESS_KEY_ID); - expect(credentials.secretAccessKey).toBe(mockEnv.AWS_SECRET_ACCESS_KEY); - expect(credentials.sessionToken).toBeUndefined(); - }); - - test("should resolve credentials with session token", async () => { - // Set up environment variables with session token - process.env.AWS_ACCESS_KEY_ID = mockEnv.AWS_ACCESS_KEY_ID; - process.env.AWS_SECRET_ACCESS_KEY = mockEnv.AWS_SECRET_ACCESS_KEY; - process.env.AWS_SESSION_TOKEN = mockEnv.AWS_SESSION_TOKEN; - process.env.AWS_REGION = mockEnv.AWS_REGION; - - const provider = new AWSCredentialProvider({ - enableDebugLogging: true, - }); - - const credentials = await provider.getCredentials(); - - expect(credentials.accessKeyId).toBe(mockEnv.AWS_ACCESS_KEY_ID); - expect(credentials.secretAccessKey).toBe(mockEnv.AWS_SECRET_ACCESS_KEY); - expect(credentials.sessionToken).toBe(mockEnv.AWS_SESSION_TOKEN); - }); - - test("should handle missing environment variables gracefully", async () => { - const provider = new AWSCredentialProvider({ - enableDebugLogging: true, - }); - - await expect(provider.getCredentials()).rejects.toThrow( - /No AWS credentials found.*Please configure one of the following/, - ); - }); - - test("should detect environment variable credential source", async () => { - process.env.AWS_ACCESS_KEY_ID = mockEnv.AWS_ACCESS_KEY_ID; - process.env.AWS_SECRET_ACCESS_KEY = mockEnv.AWS_SECRET_ACCESS_KEY; - process.env.AWS_REGION = mockEnv.AWS_REGION; - - const provider = new AWSCredentialProvider(); - const validationResult = - await CredentialTester.validateCredentials(provider); - - expect(validationResult.isValid).toBe(true); - expect(validationResult.credentialSource).toBe("Environment Variables"); - expect(validationResult.region).toBe(mockEnv.AWS_REGION); - }); - }); - - describe("Profile-based Authentication", () => { - test("should use specified AWS profile", async () => { - const provider = new AWSCredentialProvider({ - profile: "test-profile", - enableDebugLogging: true, - }); - - // This test will attempt to resolve credentials from ~/.aws/credentials - // In a test environment, this may fail, but we can test the configuration - expect(provider.getConfig().profile).toBe("test-profile"); - }); - - test("should default to default profile", async () => { - const provider = new AWSCredentialProvider(); - expect(provider.getConfig().profile).toBe("default"); - }); - - test("should respect AWS_PROFILE environment variable", async () => { - process.env.AWS_PROFILE = "custom-profile"; - - const provider = new AWSCredentialProvider(); - expect(provider.getConfig().profile).toBe("custom-profile"); - }); - }); - - describe("Role-based Authentication", () => { - test("should configure assume role parameters", async () => { - const provider = new AWSCredentialProvider({ - roleArn: mockEnv.AWS_ROLE_ARN, - roleSessionName: mockEnv.AWS_ROLE_SESSION_NAME, - enableDebugLogging: true, - }); - - const config = provider.getConfig(); - expect(config.roleArn).toBe(mockEnv.AWS_ROLE_ARN); - expect(config.roleSessionName).toBe(mockEnv.AWS_ROLE_SESSION_NAME); - }); - - test("should respect role environment variables", async () => { - process.env.AWS_ROLE_ARN = mockEnv.AWS_ROLE_ARN; - process.env.AWS_ROLE_SESSION_NAME = mockEnv.AWS_ROLE_SESSION_NAME; - - const provider = new AWSCredentialProvider(); - const config = provider.getConfig(); - - expect(config.roleArn).toBe(mockEnv.AWS_ROLE_ARN); - expect(config.roleSessionName).toBe(mockEnv.AWS_ROLE_SESSION_NAME); - }); - }); - - describe("Regional Configuration", () => { - test("should use specified region", async () => { - const provider = new AWSCredentialProvider({ - region: "us-west-2", - }); - - expect(provider.getConfig().region).toBe("us-west-2"); - }); - - test("should respect AWS_REGION environment variable", async () => { - process.env.AWS_REGION = "eu-west-1"; - - const provider = new AWSCredentialProvider(); - expect(provider.getConfig().region).toBe("eu-west-1"); - }); - - test("should default to us-east-1", async () => { - const provider = new AWSCredentialProvider(); - expect(provider.getConfig().region).toBe("us-east-1"); - }); - }); - - describe("Timeout and Retry Configuration", () => { - test("should configure timeout and retry parameters", async () => { - const provider = new AWSCredentialProvider({ - timeout: 45000, - maxRetries: 5, - }); - - const config = provider.getConfig(); - expect(config.timeout).toBe(45000); - expect(config.maxRetries).toBe(5); - }); - - test("should use default timeout and retry values", async () => { - const provider = new AWSCredentialProvider(); - const config = provider.getConfig(); - - expect(config.timeout).toBe(30000); - expect(config.maxRetries).toBe(3); - }); - }); - - describe("Credential Caching", () => { - test("should cache valid credentials", async () => { - process.env.AWS_ACCESS_KEY_ID = mockEnv.AWS_ACCESS_KEY_ID; - process.env.AWS_SECRET_ACCESS_KEY = mockEnv.AWS_SECRET_ACCESS_KEY; - process.env.AWS_REGION = mockEnv.AWS_REGION; - - const provider = new AWSCredentialProvider({ - enableDebugLogging: true, - }); - - // First call - const credentials1 = await provider.getCredentials(); - - // Second call should use cached credentials - const credentials2 = await provider.getCredentials(); - - expect(credentials1.accessKeyId).toBe(credentials2.accessKeyId); - expect(credentials1.secretAccessKey).toBe(credentials2.secretAccessKey); - }); - - test("should refresh cached credentials when requested", async () => { - process.env.AWS_ACCESS_KEY_ID = mockEnv.AWS_ACCESS_KEY_ID; - process.env.AWS_SECRET_ACCESS_KEY = mockEnv.AWS_SECRET_ACCESS_KEY; - process.env.AWS_REGION = mockEnv.AWS_REGION; - - const provider = new AWSCredentialProvider(); - - // Get initial credentials - await provider.getCredentials(); - - // Force refresh - const refreshedCredentials = await provider.refreshCredentials(); - - expect(refreshedCredentials.accessKeyId).toBe(mockEnv.AWS_ACCESS_KEY_ID); - expect(refreshedCredentials.secretAccessKey).toBe( - mockEnv.AWS_SECRET_ACCESS_KEY, - ); - }); - }); - - describe("Credential Availability Check", () => { - test("should return true when credentials are available", async () => { - process.env.AWS_ACCESS_KEY_ID = mockEnv.AWS_ACCESS_KEY_ID; - process.env.AWS_SECRET_ACCESS_KEY = mockEnv.AWS_SECRET_ACCESS_KEY; - process.env.AWS_REGION = mockEnv.AWS_REGION; - - const provider = new AWSCredentialProvider(); - const isAvailable = await provider.isCredentialsAvailable(); - - expect(isAvailable).toBe(true); - }); - - test("should return false when credentials are not available", async () => { - const provider = new AWSCredentialProvider(); - const isAvailable = await provider.isCredentialsAvailable(); - - expect(isAvailable).toBe(false); - }); - }); - - describe("Provider Lifecycle Management", () => { - test("should initialize and dispose properly", async () => { - const provider = new AWSCredentialProvider({ - enableDebugLogging: true, - }); - - expect(provider.getConfig()).toBeDefined(); - - provider.dispose(); - - // After disposal, the provider should handle errors gracefully - await expect(provider.getCredentials()).rejects.toThrow( - /AWSCredentialProvider not initialized/, - ); - }); - }); - - describe("AmazonBedrockProvider Integration", () => { - test("should create AmazonBedrockProvider with credential provider", async () => { - process.env.AWS_ACCESS_KEY_ID = mockEnv.AWS_ACCESS_KEY_ID; - process.env.AWS_SECRET_ACCESS_KEY = mockEnv.AWS_SECRET_ACCESS_KEY; - process.env.AWS_REGION = mockEnv.AWS_REGION; - process.env.BEDROCK_MODEL = "anthropic.claude-3-haiku-20240307-v1:0"; - - const bedrockProvider = new AmazonBedrockProvider(); - - expect(bedrockProvider.getCredentialProvider()).toBeDefined(); - expect(bedrockProvider.getBedrockClient()).toBeDefined(); - }); - - test("should support custom credential configuration", async () => { - process.env.BEDROCK_MODEL = "anthropic.claude-3-haiku-20240307-v1:0"; - - const customConfig = { - region: "us-west-2", - timeout: 45000, - enableDebugLogging: true, - }; - - const bedrockProvider = new AmazonBedrockProvider( - undefined, - customConfig, - ); - const credentialConfig = bedrockProvider - .getCredentialProvider() - .getConfig(); - - expect(credentialConfig.region).toBe("us-west-2"); - expect(credentialConfig.timeout).toBe(45000); - expect(credentialConfig.enableDebugLogging).toBe(true); - }); - - test("should fall back to legacy configuration when credential provider fails", async () => { - process.env.AWS_ACCESS_KEY_ID = mockEnv.AWS_ACCESS_KEY_ID; - process.env.AWS_SECRET_ACCESS_KEY = mockEnv.AWS_SECRET_ACCESS_KEY; - process.env.AWS_REGION = mockEnv.AWS_REGION; - process.env.BEDROCK_MODEL = "anthropic.claude-3-haiku-20240307-v1:0"; - - // Mock createAmazonBedrock to simulate failure - const originalConsoleWarn = console.warn; - const warnSpy = vi.fn(); - console.warn = warnSpy; - - try { - const bedrockProvider = new AmazonBedrockProvider(); - expect(bedrockProvider).toBeDefined(); - } finally { - console.warn = originalConsoleWarn; - } - }); - }); - - describe("Error Handling", () => { - test("should provide helpful error messages for missing credentials", async () => { - const provider = new AWSCredentialProvider(); - - await expect(provider.getCredentials()).rejects.toThrow( - /No AWS credentials found.*Please configure one of the following/, - ); - }); - - test("should handle expired credentials", async () => { - // This test would need to mock expired credentials - // For now, we'll test the error message format - const provider = new AWSCredentialProvider(); - - try { - await provider.getCredentials(); - } catch (error) { - expect(error).toBeInstanceOf(Error); - expect(error.message).toContain("AWS credential"); - } - }); - }); - - describe("Debugging and Diagnostics", () => { - test("should enable debug logging when requested", async () => { - const provider = new AWSCredentialProvider({ - enableDebugLogging: true, - }); - - expect(provider.getConfig().enableDebugLogging).toBe(true); - }); - - test("should provide configuration information", async () => { - const config = { - region: "us-west-2", - profile: "test-profile", - timeout: 45000, - maxRetries: 5, - enableDebugLogging: true, - }; - - const provider = new AWSCredentialProvider(config); - const retrievedConfig = provider.getConfig(); - - expect(retrievedConfig.region).toBe(config.region); - expect(retrievedConfig.profile).toBe(config.profile); - expect(retrievedConfig.timeout).toBe(config.timeout); - expect(retrievedConfig.maxRetries).toBe(config.maxRetries); - expect(retrievedConfig.enableDebugLogging).toBe( - config.enableDebugLogging, - ); - }); - }); -}); diff --git a/test/providers/aws/credentialSources.test.ts b/test/providers/aws/credentialSources.test.ts deleted file mode 100644 index 58a166b40..000000000 --- a/test/providers/aws/credentialSources.test.ts +++ /dev/null @@ -1,433 +0,0 @@ -/** - * AWS Credential Sources Test Suite for NeuroLink - * - * Tests all 9 AWS credential sources for compatibility with Bedrock-MCP-Connector: - * 1. Environment Variables - * 2. AWS Credentials File (~/.aws/credentials) - * 3. AWS Config File (~/.aws/config) - * 4. IAM Roles (EC2/ECS/Lambda) - * 5. AWS SSO - * 6. STS Assume Role - * 7. Credential Process - * 8. Container Credentials - * 9. Instance Metadata Service (IMDS) - */ - -import { describe, test, expect, beforeEach, afterEach, vi } from "vitest"; -import { AWSCredentialProvider } from "../../../src/lib/providers/aws/credentialProvider.js"; -import { CredentialTester } from "../../../src/lib/providers/aws/credentialTester.js"; -import fs from "fs"; -import os from "os"; -import path from "path"; - -// Store original environment -const originalEnv = { ...process.env }; - -describe("AWS Credential Sources Compatibility Tests", () => { - beforeEach(() => { - // Clear AWS environment variables - Object.keys(process.env).forEach((key) => { - if (key.startsWith("AWS_")) { - delete process.env[key]; - } - }); - }); - - afterEach(() => { - // Restore original environment - Object.keys(process.env).forEach((key) => { - if (key.startsWith("AWS_")) { - delete process.env[key]; - } - }); - Object.assign(process.env, originalEnv); - }); - - describe("AWS Credentials File Authentication", () => { - const mockCredentialsContent = `[default] -aws_access_key_id = AKIA123456789EXAMPLE -aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY - -[test-profile] -aws_access_key_id = AKIA987654321EXAMPLE -aws_secret_access_key = testSecretAccessKey123456789Example -region = us-west-2 - -[role-profile] -role_arn = arn:aws:iam::123456789012:role/ExampleRole -source_profile = default`; - - test("should attempt to read from AWS credentials file", async () => { - const provider = new AWSCredentialProvider({ - profile: "default", - enableDebugLogging: true, - }); - - // This test validates that the provider is configured to use credential files - // Actual file reading depends on the AWS SDK implementation - expect(provider.getConfig().profile).toBe("default"); - - // The provider should be able to handle credential file scenarios - const isAvailable = await provider.isCredentialsAvailable(); - // In test environment without real files, this may be false, but provider should handle gracefully - expect(typeof isAvailable).toBe("boolean"); - }); - - test("should use specified profile from credentials file", async () => { - const provider = new AWSCredentialProvider({ - profile: "test-profile", - enableDebugLogging: true, - }); - - expect(provider.getConfig().profile).toBe("test-profile"); - }); - - test("should detect credentials file as source when available", async () => { - // Skip this test if running in environment without AWS credentials file - const provider = new AWSCredentialProvider({ - profile: "default", - }); - - try { - const credentialSource = - await CredentialTester.getCredentialSource(provider); - // May detect various sources depending on environment - expect(typeof credentialSource).toBe("string"); - } catch (error) { - // Expected in test environment without credentials - expect(error).toBeInstanceOf(Error); - } - }); - }); - - describe("AWS Config File Authentication", () => { - test("should respect region configuration from config file", async () => { - const provider = new AWSCredentialProvider({ - profile: "default", - enableDebugLogging: true, - }); - - // AWS SDK will read from ~/.aws/config for region if not specified elsewhere - // We can test that the provider respects the config file structure - expect(provider.getConfig().profile).toBe("default"); - }); - - test("should handle config file role configuration", async () => { - const provider = new AWSCredentialProvider({ - profile: "role-profile", - enableDebugLogging: true, - }); - - expect(provider.getConfig().profile).toBe("role-profile"); - }); - }); - - describe("IAM Role Metadata Authentication", () => { - test("should configure for EC2 instance metadata", async () => { - // Mock EC2 metadata environment - delete process.env.AWS_EC2_METADATA_DISABLED; - - const provider = new AWSCredentialProvider({ - enableDebugLogging: true, - }); - - // Provider should be able to attempt metadata service - expect(provider.getConfig()).toBeDefined(); - - // Test availability (will fail in non-EC2 environment) - const isAvailable = await provider.isCredentialsAvailable(); - expect(typeof isAvailable).toBe("boolean"); - }); - - test("should handle metadata service being disabled", async () => { - process.env.AWS_EC2_METADATA_DISABLED = "true"; - - const provider = new AWSCredentialProvider({ - enableDebugLogging: true, - }); - - const isAvailable = await provider.isCredentialsAvailable(); - expect(typeof isAvailable).toBe("boolean"); - }); - - test("should detect instance metadata as credential source", async () => { - const provider = new AWSCredentialProvider(); - - try { - const validationResult = - await CredentialTester.validateCredentials(provider); - if (validationResult.isValid) { - // If credentials are found, check if they're from instance metadata - expect(validationResult.credentialSource).toMatch( - /metadata|Instance Metadata/i, - ); - } - } catch (error) { - // Expected in non-EC2 environment - expect(error).toBeInstanceOf(Error); - } - }); - }); - - describe("Container Credentials Authentication", () => { - test("should configure for ECS container credentials", async () => { - process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI = - "/v2/credentials/test-uuid"; - - const provider = new AWSCredentialProvider({ - enableDebugLogging: true, - }); - - // Provider should be configured to use container credentials - expect(provider.getConfig()).toBeDefined(); - - const isAvailable = await provider.isCredentialsAvailable(); - expect(typeof isAvailable).toBe("boolean"); - }); - - test("should handle full URI container credentials", async () => { - process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI = - "http://169.254.170.2/v2/credentials/test-uuid"; - - const provider = new AWSCredentialProvider({ - enableDebugLogging: true, - }); - - const isAvailable = await provider.isCredentialsAvailable(); - expect(typeof isAvailable).toBe("boolean"); - }); - - test("should detect container credentials as source", async () => { - process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI = - "/v2/credentials/test-uuid"; - - const provider = new AWSCredentialProvider(); - - try { - const credentialSource = - await CredentialTester.getCredentialSource(provider); - expect(typeof credentialSource).toBe("string"); - } catch (error) { - // Expected in non-container environment - expect(error).toBeInstanceOf(Error); - } - }); - }); - - describe("AWS SSO Authentication", () => { - test("should configure for AWS SSO", async () => { - const provider = new AWSCredentialProvider({ - profile: "sso-profile", - enableDebugLogging: true, - }); - - expect(provider.getConfig().profile).toBe("sso-profile"); - }); - - test("should detect SSO credentials when available", async () => { - const provider = new AWSCredentialProvider({ - profile: "sso-profile", - }); - - try { - const validationResult = - await CredentialTester.validateCredentials(provider); - if (validationResult.isValid) { - // SSO credentials typically have expiration but no session token - expect(validationResult.hasExpiration).toBeDefined(); - } - } catch (error) { - // Expected without SSO setup - expect(error).toBeInstanceOf(Error); - } - }); - }); - - describe("STS Assume Role Authentication", () => { - test("should configure assume role parameters", async () => { - const roleArn = "arn:aws:iam::123456789012:role/ExampleRole"; - const sessionName = "ExampleSessionName"; - - const provider = new AWSCredentialProvider({ - roleArn, - roleSessionName: sessionName, - enableDebugLogging: true, - }); - - const config = provider.getConfig(); - expect(config.roleArn).toBe(roleArn); - expect(config.roleSessionName).toBe(sessionName); - }); - - test("should respect assume role environment variables", async () => { - process.env.AWS_ROLE_ARN = "arn:aws:iam::123456789012:role/TestRole"; - process.env.AWS_ROLE_SESSION_NAME = "TestSession"; - - const provider = new AWSCredentialProvider(); - const config = provider.getConfig(); - - expect(config.roleArn).toBe(process.env.AWS_ROLE_ARN); - expect(config.roleSessionName).toBe(process.env.AWS_ROLE_SESSION_NAME); - }); - - test("should detect assume role credentials", async () => { - process.env.AWS_ROLE_ARN = "arn:aws:iam::123456789012:role/TestRole"; - - const provider = new AWSCredentialProvider(); - - try { - const validationResult = - await CredentialTester.validateCredentials(provider); - if (validationResult.isValid) { - // Assume role credentials typically have session tokens and expiration - expect(validationResult.debugInfo.hasSessionToken).toBeDefined(); - } - } catch (error) { - // Expected without proper role setup - expect(error).toBeInstanceOf(Error); - } - }); - }); - - describe("Credential Process Authentication", () => { - test("should configure for credential process", async () => { - process.env.AWS_CREDENTIAL_PROCESS = "aws-credential-helper"; - - const provider = new AWSCredentialProvider({ - enableDebugLogging: true, - }); - - // Provider should be configured to use credential process - expect(provider.getConfig()).toBeDefined(); - }); - - test("should detect credential process as source", async () => { - process.env.AWS_CREDENTIAL_PROCESS = "aws-credential-helper"; - - const provider = new AWSCredentialProvider(); - - try { - const credentialSource = - await CredentialTester.getCredentialSource(provider); - expect(typeof credentialSource).toBe("string"); - } catch (error) { - // Expected without proper credential process setup - expect(error).toBeInstanceOf(Error); - } - }); - }); - - describe("Web Identity Token Authentication", () => { - test("should configure for web identity token", async () => { - process.env.AWS_WEB_IDENTITY_TOKEN_FILE = - "/var/run/secrets/eks.amazonaws.com/serviceaccount/token"; - process.env.AWS_ROLE_ARN = - "arn:aws:iam::123456789012:role/EksServiceAccountRole"; - - const provider = new AWSCredentialProvider({ - enableDebugLogging: true, - }); - - expect(provider.getConfig().roleArn).toBe(process.env.AWS_ROLE_ARN); - }); - - test("should detect web identity token as source", async () => { - process.env.AWS_WEB_IDENTITY_TOKEN_FILE = "/var/run/secrets/token"; - process.env.AWS_ROLE_ARN = - "arn:aws:iam::123456789012:role/EksServiceAccountRole"; - - const provider = new AWSCredentialProvider(); - - try { - const credentialSource = - await CredentialTester.getCredentialSource(provider); - expect(typeof credentialSource).toBe("string"); - } catch (error) { - // Expected without proper web identity setup - expect(error).toBeInstanceOf(Error); - } - }); - }); - - describe("Cross-Region Authentication", () => { - test("should work with different AWS regions", async () => { - const regions = ["us-east-1", "us-west-2", "eu-west-1", "ap-southeast-1"]; - - for (const region of regions) { - const provider = new AWSCredentialProvider({ - region, - enableDebugLogging: true, - }); - - expect(provider.getConfig().region).toBe(region); - - // Test that region doesn't affect credential resolution logic - const isAvailable = await provider.isCredentialsAvailable(); - expect(typeof isAvailable).toBe("boolean"); - } - }); - }); - - describe("Multi-Profile Authentication", () => { - test("should handle multiple profiles", async () => { - const profiles = ["default", "dev", "staging", "production"]; - - for (const profile of profiles) { - const provider = new AWSCredentialProvider({ - profile, - enableDebugLogging: true, - }); - - expect(provider.getConfig().profile).toBe(profile); - } - }); - }); - - describe("Credential Source Detection", () => { - test("should accurately detect credential sources", async () => { - // Test various environment setups - const scenarios = [ - { - name: "Environment Variables", - setup: () => { - process.env.AWS_ACCESS_KEY_ID = "AKIA123456789EXAMPLE"; - process.env.AWS_SECRET_ACCESS_KEY = "testSecretKey"; - }, - }, - { - name: "Environment with Session Token", - setup: () => { - process.env.AWS_ACCESS_KEY_ID = "AKIA123456789EXAMPLE"; - process.env.AWS_SECRET_ACCESS_KEY = "testSecretKey"; - process.env.AWS_SESSION_TOKEN = "testSessionToken"; - }, - }, - { - name: "Role ARN Configuration", - setup: () => { - process.env.AWS_ROLE_ARN = - "arn:aws:iam::123456789012:role/TestRole"; - }, - }, - ]; - - for (const scenario of scenarios) { - // Clear environment - Object.keys(process.env).forEach((key) => { - if (key.startsWith("AWS_")) { - delete process.env[key]; - } - }); - - scenario.setup(); - - const provider = new AWSCredentialProvider(); - const credentialSource = - await CredentialTester.getCredentialSource(provider); - - expect(typeof credentialSource).toBe("string"); - expect(credentialSource.length).toBeGreaterThan(0); - } - }); - }); -}); diff --git a/tools/testing/providerValidator.js b/tools/testing/providerValidator.js index ffddd440e..6619b5410 100644 --- a/tools/testing/providerValidator.js +++ b/tools/testing/providerValidator.js @@ -227,7 +227,6 @@ class ProviderValidator { openai: "@ai-sdk/openai", anthropic: "@ai-sdk/anthropic", google: "@ai-sdk/google", - "aws-bedrock": "@ai-sdk/amazon-bedrock", azure: "@ai-sdk/openai", huggingface: "@huggingface/inference", ollama: "ollama",