taskRepository = $taskRepository ?? new TaskRepository(); $this->saveResultUseCase = $saveResultUseCase ?? new SaveTaskResultUseCase(); $this->updateStatusUseCase = $updateStatusUseCase ?? new UpdateTaskStatusUseCase(); } public function execute(int $taskId, array $options = []): TaskResult { $task = $this->taskRepository->find($taskId); if ($task === null) { throw new \InvalidArgumentException("Task {$taskId} not found"); } $executorType = AssigneeType::from($options['executor_type'] ?? 'ollama'); $client = $this->getClient($executorType, $options); if (!$client->isAvailable()) { throw new \RuntimeException("{$executorType->label()} is not available"); } if ($task->getStatus() === TaskStatus::PENDING) { $this->updateStatusUseCase->startTask($taskId, $client->getClientName(), 'ai'); } $prompt = $this->buildPrompt($task, $options); $response = $client->execute($prompt, $options); $resultData = [ 'executor' => $client->getClientName(), 'executor_type' => $executorType->value, 'model_name' => $response->getModel() !== '' ? $response->getModel() : $client->getModelName(), 'request' => $prompt, 'response' => $response->getContent(), 'tokens_input' => $response->getTokensInput(), 'tokens_output' => $response->getTokensOutput(), 'duration_ms' => $response->getDurationMs(), 'status' => $response->isSuccess() ? 'success' : 'error', ]; if (!$response->isSuccess()) { $resultData['error_message'] = $response->getErrorMessage(); } $metadata = $response->getMetadata(); if (isset($metadata['cost_usd'])) { $resultData['cost_usd'] = $metadata['cost_usd']; } if (isset($options['assignment_id'])) { $resultData['assignment_id'] = $options['assignment_id']; } $result = $this->saveResultUseCase->execute($taskId, $resultData); if ($response->isSuccess() && ($options['auto_complete'] ?? false)) { $this->updateStatusUseCase->completeTask($taskId, $client->getClientName(), 'ai'); } return $result; } private function getClient(AssigneeType $type, array $options): AIClientInterface { return match ($type) { AssigneeType::OLLAMA => new OllamaClient( 'http://localhost:11434', $options['model'] ?? 'mistral' ), AssigneeType::ANTHROPIC_API => new AnthropicClient( $options['api_key'] ?? '', $options['model'] ?? 'claude-sonnet-4-20250514' ), default => throw new \InvalidArgumentException("Unsupported executor type: {$type->value}") }; } private function buildPrompt(Task $task, array $options): string { $prompt = $task->getTitle(); if ($task->getDescription() !== null) { $prompt .= "\n\n" . $task->getDescription(); } if (isset($options['additional_context'])) { $prompt .= "\n\n" . $options['additional_context']; } return $prompt; } public function executeWithOllama(int $taskId, string $model = 'mistral', array $options = []): TaskResult { return $this->execute($taskId, array_merge($options, [ 'executor_type' => 'ollama', 'model' => $model, ])); } public function executeWithAnthropic(int $taskId, string $model = 'claude-sonnet-4-20250514', array $options = []): TaskResult { return $this->execute($taskId, array_merge($options, [ 'executor_type' => 'anthropic_api', 'model' => $model, ])); } }