// Direct API agent with multi-provider support (Anthropic, OpenRouter, Gemini) import Anthropic from '@anthropic-ai/sdk'; import { logger } from '../utils/logger.js'; import { withRetry } from '../utils/retry.js'; import { execSync } from 'child_process'; import { ModelRouter } from '../router/router.js'; // Lazy initialize clients let anthropic = null; let router = null; function getAnthropicClient() { if (!anthropic) { const apiKey = process.env.ANTHROPIC_API_KEY; // Validate API key format if (!apiKey) { throw new Error('ANTHROPIC_API_KEY is required but not set'); } if (!apiKey.startsWith('sk-ant-')) { throw new Error(`Invalid ANTHROPIC_API_KEY format. Expected format: sk-ant-...\n` + `Got: ${apiKey.substring(0, 10)}...\n\n` + `Please check your API key at: https://console.anthropic.com/settings/keys`); } anthropic = new Anthropic({ apiKey }); } return anthropic; } function getRouter() { if (!router) { // Router will now auto-create config from environment variables if no file exists router = new ModelRouter(); } return router; } function getCurrentProvider() { // Determine provider from environment if (process.env.PROVIDER === 'gemini' || process.env.USE_GEMINI === 'true') { return 'gemini'; } if (process.env.PROVIDER === 'openrouter' || process.env.USE_OPENROUTER === 'true') { return 'openrouter'; } if (process.env.PROVIDER === 'onnx' || process.env.USE_ONNX === 'true') { return 'onnx'; } return 'anthropic'; } // Define claude-flow tools as native Anthropic tool definitions const claudeFlowTools = [ { name: 'memory_store', description: 'Store a value in persistent memory with optional namespace and TTL', input_schema: { type: 'object', properties: { key: { type: 'string', description: 'Memory key' }, value: { type: 'string', description: 'Value to store' }, namespace: { type: 'string', description: 'Memory namespace', default: 'default' }, ttl: { type: 'number', description: 'Time-to-live in seconds' } }, required: ['key', 'value'] } }, { name: 'memory_retrieve', description: 'Retrieve a value from persistent memory', input_schema: { type: 'object', properties: { key: { type: 'string', description: 'Memory key' }, namespace: { type: 'string', description: 'Memory namespace', default: 'default' } }, required: ['key'] } }, { name: 'memory_search', description: 'Search for keys matching a pattern in memory', input_schema: { type: 'object', properties: { pattern: { type: 'string', description: 'Search pattern (supports wildcards)' }, namespace: { type: 'string', description: 'Memory namespace to search in' }, limit: { type: 'number', description: 'Maximum results to return', default: 10 } }, required: ['pattern'] } }, { name: 'swarm_init', description: 'Initialize a multi-agent swarm with specified topology', input_schema: { type: 'object', properties: { topology: { type: 'string', enum: ['mesh', 'hierarchical', 'ring', 'star'], description: 'Swarm topology' }, maxAgents: { type: 'number', description: 'Maximum number of agents', default: 8 }, strategy: { type: 'string', enum: ['balanced', 'specialized', 'adaptive'], description: 'Agent distribution strategy', default: 'balanced' } }, required: ['topology'] } }, { name: 'agent_spawn', description: 'Spawn a new agent in the swarm', input_schema: { type: 'object', properties: { type: { type: 'string', enum: ['researcher', 'coder', 'analyst', 'optimizer', 'coordinator'], description: 'Agent type' }, capabilities: { type: 'array', items: { type: 'string' }, description: 'Agent capabilities' }, name: { type: 'string', description: 'Custom agent name' } }, required: ['type'] } }, { name: 'task_orchestrate', description: 'Orchestrate a complex task across the swarm', input_schema: { type: 'object', properties: { task: { type: 'string', description: 'Task description or instructions' }, strategy: { type: 'string', enum: ['parallel', 'sequential', 'adaptive'], description: 'Execution strategy', default: 'adaptive' }, priority: { type: 'string', enum: ['low', 'medium', 'high', 'critical'], description: 'Task priority', default: 'medium' }, maxAgents: { type: 'number', description: 'Maximum agents to use for this task' } }, required: ['task'] } }, { name: 'swarm_status', description: 'Get current swarm status and metrics', input_schema: { type: 'object', properties: { verbose: { type: 'boolean', description: 'Include detailed metrics', default: false } } } } ]; // Execute tool calls using claude-flow CLI async function executeToolCall(toolName, toolInput) { try { logger.info('Executing tool', { toolName, input: toolInput }); switch (toolName) { case 'memory_store': { const { key, value, namespace = 'default', ttl } = toolInput; const cmd = `npx claude-flow@alpha memory store "${key}" "${value}" --namespace "${namespace}"${ttl ? ` --ttl ${ttl}` : ''}`; const result = execSync(cmd, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }); logger.info('Memory stored', { key, namespace }); return `āœ… Stored successfully\nšŸ“ Key: ${key}\nšŸ“¦ Namespace: ${namespace}\nšŸ’¾ Size: ${value.length} bytes`; } case 'memory_retrieve': { const { key, namespace = 'default' } = toolInput; const cmd = `npx claude-flow@alpha memory retrieve "${key}" --namespace "${namespace}"`; const result = execSync(cmd, { encoding: 'utf-8' }); logger.info('Memory retrieved', { key }); return `āœ… Retrieved:\n${result}`; } case 'memory_search': { const { pattern, namespace, limit = 10 } = toolInput; const cmd = `npx claude-flow@alpha memory search "${pattern}"${namespace ? ` --namespace "${namespace}"` : ''} --limit ${limit}`; const result = execSync(cmd, { encoding: 'utf-8' }); return `šŸ” Search results:\n${result}`; } case 'swarm_init': { const { topology, maxAgents = 8, strategy = 'balanced' } = toolInput; const cmd = `npx claude-flow@alpha swarm init --topology ${topology} --max-agents ${maxAgents} --strategy ${strategy}`; const result = execSync(cmd, { encoding: 'utf-8' }); return `šŸš€ Swarm initialized:\n${result}`; } case 'agent_spawn': { const { type, capabilities, name } = toolInput; const capStr = capabilities ? ` --capabilities "${capabilities.join(',')}"` : ''; const nameStr = name ? ` --name "${name}"` : ''; const cmd = `npx claude-flow@alpha agent spawn --type ${type}${capStr}${nameStr}`; const result = execSync(cmd, { encoding: 'utf-8' }); return `šŸ¤– Agent spawned:\n${result}`; } case 'task_orchestrate': { const { task, strategy = 'adaptive', priority = 'medium', maxAgents } = toolInput; const maxStr = maxAgents ? ` --max-agents ${maxAgents}` : ''; const cmd = `npx claude-flow@alpha task orchestrate "${task}" --strategy ${strategy} --priority ${priority}${maxStr}`; const result = execSync(cmd, { encoding: 'utf-8' }); return `⚔ Task orchestrated:\n${result}`; } case 'swarm_status': { const { verbose = false } = toolInput; const cmd = `npx claude-flow@alpha swarm status${verbose ? ' --verbose' : ''}`; const result = execSync(cmd, { encoding: 'utf-8' }); return `šŸ“Š Swarm status:\n${result}`; } default: throw new Error(`Unknown tool: ${toolName}`); } } catch (error) { logger.error('Tool execution failed', { toolName, error: error.message }); return `āŒ Tool execution failed: ${error.message}`; } } /** * Direct API agent using Anthropic SDK with native tool calling * Bypasses Claude Agent SDK subprocess issues entirely */ export async function directApiAgent(agent, input, onStream) { const startTime = Date.now(); logger.info('Starting direct API agent', { agent: agent.name, input: input.substring(0, 100) }); return withRetry(async () => { const messages = [ { role: 'user', content: input } ]; let finalOutput = ''; let toolUseCount = 0; const maxToolUses = 10; // Prevent infinite loops // Agentic loop: keep calling API until no more tool uses while (toolUseCount < maxToolUses) { logger.debug('API call iteration', { toolUseCount, messagesLength: messages.length }); const provider = getCurrentProvider(); let response; try { // Use router for non-Anthropic providers if (provider === 'gemini' || provider === 'openrouter') { const routerInstance = getRouter(); // Convert Anthropic messages format to router format const routerMessages = messages.map(msg => ({ role: msg.role, content: typeof msg.content === 'string' ? msg.content : msg.content.map((block) => { if ('text' in block) return { type: 'text', text: block.text }; if ('tool_use_id' in block) return { type: 'tool_result', content: block.content }; if ('name' in block && 'input' in block) return { type: 'tool_use', id: block.id, name: block.name, input: block.input }; return { type: 'text', text: '' }; }).filter((b) => b.type === 'text' || b.type === 'tool_use' || b.type === 'tool_result') })); // Add system prompt as first message if needed const messagesWithSystem = agent.systemPrompt ? [{ role: 'system', content: agent.systemPrompt }, ...routerMessages] : routerMessages; const params = { model: provider === 'gemini' ? (process.env.COMPLETION_MODEL || 'gemini-2.0-flash-exp') : (process.env.COMPLETION_MODEL || 'deepseek/deepseek-chat'), messages: messagesWithSystem, maxTokens: 8192, temperature: 0.7 }; const routerResponse = await routerInstance.chat(params); // Convert router response to Anthropic format response = { id: routerResponse.id, model: routerResponse.model, stop_reason: routerResponse.stopReason, content: routerResponse.content.map(block => { if (block.type === 'text') return { type: 'text', text: block.text || '' }; if (block.type === 'tool_use') return { type: 'tool_use', id: block.id || '', name: block.name || '', input: block.input || {} }; return { type: 'text', text: '' }; }) }; } else { // Use Anthropic client for Anthropic provider const client = getAnthropicClient(); response = await client.messages.create({ model: process.env.COMPLETION_MODEL || 'claude-sonnet-4-5-20250929', max_tokens: 8192, system: agent.systemPrompt || 'You are a helpful AI assistant.', messages, tools: claudeFlowTools }); } } catch (error) { // Enhance authentication errors with helpful guidance if (error?.status === 401 || error?.statusCode === 401) { const providerName = provider === 'gemini' ? 'Google Gemini' : provider === 'openrouter' ? 'OpenRouter' : 'Anthropic'; const apiKey = provider === 'gemini' ? process.env.GOOGLE_GEMINI_API_KEY : provider === 'openrouter' ? process.env.OPENROUTER_API_KEY : process.env.ANTHROPIC_API_KEY; throw new Error(`āŒ ${providerName} API authentication failed (401)\n\n` + `Your API key is invalid, expired, or lacks permissions.\n` + `Current key: ${apiKey?.substring(0, 15)}...\n\n` + `Please check your ${providerName} API key and update your .env file.\n\n` + `Alternative providers:\n` + ` --provider anthropic (Claude models)\n` + ` --provider openrouter (100+ models, 99% cost savings)\n` + ` --provider gemini (Google models)\n` + ` --provider onnx (free local inference)`); } throw error; } logger.debug('API response', { stopReason: response.stop_reason, contentBlocks: response.content.length }); // Process response content const toolResults = []; for (const block of response.content) { if (block.type === 'text') { finalOutput += block.text; if (onStream) { onStream(block.text); } } else if (block.type === 'tool_use') { toolUseCount++; logger.info('Tool use requested', { toolName: block.name, toolUseId: block.id }); // Execute the tool const toolResult = await executeToolCall(block.name, block.input); // Collect tool result toolResults.push({ type: 'tool_result', tool_use_id: block.id, content: toolResult }); logger.debug('Tool result collected', { toolUseId: block.id, resultLength: toolResult.length }); } } // If there were tool uses, add assistant message and all tool results if (toolResults.length > 0) { // Add assistant message with tool uses messages.push({ role: 'assistant', content: response.content }); // Add all tool results in one user message messages.push({ role: 'user', content: toolResults }); logger.debug('Tool results added to conversation', { count: toolResults.length }); } // Stop if no tool use or end_turn if (response.stop_reason === 'end_turn' || response.content.every((b) => b.type === 'text')) { // Add final assistant message if it has text const textContent = response.content.filter((b) => b.type === 'text'); if (textContent.length > 0 && messages[messages.length - 1].role !== 'assistant') { messages.push({ role: 'assistant', content: response.content }); } break; } } const duration = Date.now() - startTime; logger.info('Direct API agent completed', { agent: agent.name, duration, toolUseCount, outputLength: finalOutput.length }); return { output: finalOutput, agent: agent.name }; }); } //# sourceMappingURL=directApiAgent.js.map