/** * Claude Provider - Executes queries using Claude Agent SDK * * Wraps the @anthropic-ai/claude-agent-sdk for seamless integration * with the provider architecture. */ import { query, type Options } from '@anthropic-ai/claude-agent-sdk'; import { BaseProvider } from './base-provider.js'; import type { ExecuteOptions, ProviderMessage, InstallationStatus, ModelDefinition, } from './types.js'; export class ClaudeProvider extends BaseProvider { getName(): string { return 'claude'; } /** * Execute a query using Claude Agent SDK */ async *executeQuery(options: ExecuteOptions): AsyncGenerator { const { prompt, model, cwd, systemPrompt, maxTurns = 20, allowedTools, abortController, conversationHistory, sdkSessionId, } = options; // Build Claude SDK options const defaultTools = ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch']; const toolsToUse = allowedTools || defaultTools; const sdkOptions: Options = { model, systemPrompt, maxTurns, cwd, allowedTools: toolsToUse, permissionMode: 'acceptEdits', sandbox: { enabled: true, autoAllowBashIfSandboxed: true, }, abortController, // Resume existing SDK session if we have a session ID ...(sdkSessionId && conversationHistory && conversationHistory.length > 0 ? { resume: sdkSessionId } : {}), }; // Build prompt payload let promptPayload: string | AsyncIterable; if (Array.isArray(prompt)) { // Multi-part prompt (with images) promptPayload = (async function* () { const multiPartPrompt = { type: 'user' as const, session_id: '', message: { role: 'user' as const, content: prompt, }, parent_tool_use_id: null, }; yield multiPartPrompt; })(); } else { // Simple text prompt promptPayload = prompt; } // Execute via Claude Agent SDK try { const stream = query({ prompt: promptPayload, options: sdkOptions }); // Stream messages directly - they're already in the correct format for await (const msg of stream) { yield msg as ProviderMessage; } } catch (error) { console.error('[ClaudeProvider] executeQuery() error during execution:', error); throw error; } } /** * Detect Claude SDK installation (always available via npm) */ async detectInstallation(): Promise { // Claude SDK is always available since it's a dependency const hasApiKey = !!process.env.ANTHROPIC_API_KEY; const status: InstallationStatus = { installed: true, method: 'sdk', hasApiKey, authenticated: hasApiKey, }; return status; } /** * Get available Claude models */ getAvailableModels(): ModelDefinition[] { const models = [ { id: 'claude-opus-4-5-20251101', name: 'Claude Opus 4.5', modelString: 'claude-opus-4-5-20251101', provider: 'anthropic', description: 'Most capable Claude model', contextWindow: 200000, maxOutputTokens: 16000, supportsVision: true, supportsTools: true, tier: 'premium' as const, default: true, }, { id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', modelString: 'claude-sonnet-4-20250514', provider: 'anthropic', description: 'Balanced performance and cost', contextWindow: 200000, maxOutputTokens: 16000, supportsVision: true, supportsTools: true, tier: 'standard' as const, }, { id: 'claude-3-5-sonnet-20241022', name: 'Claude 3.5 Sonnet', modelString: 'claude-3-5-sonnet-20241022', provider: 'anthropic', description: 'Fast and capable', contextWindow: 200000, maxOutputTokens: 8000, supportsVision: true, supportsTools: true, tier: 'standard' as const, }, { id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5', modelString: 'claude-haiku-4-5-20251001', provider: 'anthropic', description: 'Fastest Claude model', contextWindow: 200000, maxOutputTokens: 8000, supportsVision: true, supportsTools: true, tier: 'basic' as const, }, ] satisfies ModelDefinition[]; return models; } /** * Check if the provider supports a specific feature */ supportsFeature(feature: string): boolean { const supportedFeatures = ['tools', 'text', 'vision', 'thinking']; return supportedFeatures.includes(feature); } }