['input' => 3.0, 'output' => 15.0], 'claude-opus-4-20250514' => ['input' => 15.0, 'output' => 75.0], 'claude-haiku-3-20250514' => ['input' => 0.25, 'output' => 1.25], ]; public function __construct( string $apiKey = '', string $model = 'claude-sonnet-4-20250514', int $timeout = 120 ) { $this->apiKey = $apiKey !== '' ? $apiKey : $this->loadApiKey(); $this->model = $model; $this->baseUrl = 'https://api.anthropic.com/v1'; $this->timeout = $timeout; } private function loadApiKey(): string { if (defined('ANTHROPIC_API_KEY')) { return ANTHROPIC_API_KEY; } return \Infrastructure\Config\CredentialService::getAnthropicApiKey(); } public function execute(string $prompt, array $options = []): AIResponse { if ($this->apiKey === '') { return AIResponse::error('Anthropic API Key not configured', $this->model); } $model = $options['model'] ?? $this->model; $startTime = microtime(true); try { $ch = curl_init($this->baseUrl . '/messages'); $messages = [ ['role' => 'user', 'content' => $prompt], ]; $payload = [ 'model' => $model, 'max_tokens' => $options['max_tokens'] ?? 4096, 'messages' => $messages, ]; if (isset($options['system'])) { $payload['system'] = $options['system']; } if (isset($options['temperature'])) { $payload['temperature'] = $options['temperature']; } curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'x-api-key: ' . $this->apiKey, 'anthropic-version: 2023-06-01', ], CURLOPT_TIMEOUT => $this->timeout, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); $durationMs = (int) ((microtime(true) - $startTime) * 1000); if ($response === false || $error !== '') { return AIResponse::error("cURL Error: {$error}", $model); } $data = json_decode($response, true); if ($httpCode !== 200) { $errorMsg = $data['error']['message'] ?? "HTTP Error: {$httpCode}"; return AIResponse::error($errorMsg, $model); } if (!isset($data['content'][0]['text'])) { return AIResponse::error('Invalid response format', $model); } $tokensInput = $data['usage']['input_tokens'] ?? null; $tokensOutput = $data['usage']['output_tokens'] ?? null; $cost = $this->calculateCost($model, $tokensInput, $tokensOutput); return AIResponse::success( $data['content'][0]['text'], $tokensInput, $tokensOutput, $durationMs, $model, [ 'stop_reason' => $data['stop_reason'] ?? null, 'cost_usd' => $cost, ] ); } catch (\Exception $e) { return AIResponse::error($e->getMessage(), $model); } } public function isAvailable(): bool { return $this->apiKey !== ''; } public function getClientName(): string { return 'anthropic_api'; } public function getModelName(): string { return $this->model; } private function calculateCost(string $model, ?int $inputTokens, ?int $outputTokens): ?float { if ($inputTokens === null || $outputTokens === null) { return null; } $pricing = self::PRICING[$model] ?? self::PRICING['claude-sonnet-4-20250514']; $inputCost = ($inputTokens / 1_000_000) * $pricing['input']; $outputCost = ($outputTokens / 1_000_000) * $pricing['output']; return round($inputCost + $outputCost, 6); } public function setModel(string $model): self { $this->model = $model; return $this; } public static function getAvailableModels(): array { return array_keys(self::PRICING); } }