*/ public array $backoff = [30, 120, 300]; public int $timeout = 90; public function __construct(private readonly int $requestId) { $queue = (string) config('ai-editing.queue.name', 'default'); $this->onQueue($queue); } public function handle( AiImageProviderManager $providers, AiSafetyPolicyService $safetyPolicy, AiEditingRuntimeConfig $runtimeConfig, AiUsageLedgerService $usageLedger ): void { $request = AiEditRequest::query()->with('style')->find($this->requestId); if (! $request) { return; } if (! in_array($request->status, [AiEditRequest::STATUS_QUEUED, AiEditRequest::STATUS_PROCESSING], true)) { return; } if ($request->status === AiEditRequest::STATUS_QUEUED) { $request->forceFill([ 'status' => AiEditRequest::STATUS_PROCESSING, 'started_at' => $request->started_at ?: now(), ])->save(); } $attempt = ((int) $request->providerRuns()->max('attempt')) + 1; $providerRun = AiProviderRun::query()->create([ 'request_id' => $request->id, 'provider' => $request->provider, 'attempt' => $attempt, 'status' => AiProviderRun::STATUS_RUNNING, 'started_at' => now(), ]); $result = $providers->forProvider($request->provider)->submit($request); $this->finalizeProviderRun($providerRun, $result); $this->applyProviderResult($request->fresh(['outputs']), $result, $safetyPolicy, $runtimeConfig, $usageLedger); } private function finalizeProviderRun(AiProviderRun $run, AiProviderResult $result): void { $run->forceFill([ 'provider_task_id' => $result->providerTaskId, 'status' => $result->status === 'succeeded' ? AiProviderRun::STATUS_SUCCEEDED : ($result->status === 'processing' ? AiProviderRun::STATUS_RUNNING : AiProviderRun::STATUS_FAILED), 'http_status' => $result->httpStatus, 'finished_at' => $result->status === 'processing' ? null : now(), 'duration_ms' => $run->started_at ? (int) max(0, $run->started_at->diffInMilliseconds(now())) : null, 'cost_usd' => $result->costUsd, 'request_payload' => $result->requestPayload, 'response_payload' => $result->responsePayload, 'error_message' => $result->failureMessage, ])->save(); } private function applyProviderResult( AiEditRequest $request, AiProviderResult $result, AiSafetyPolicyService $safetyPolicy, AiEditingRuntimeConfig $runtimeConfig, AiUsageLedgerService $usageLedger ): void { if ($result->status === 'succeeded') { $outputDecision = $safetyPolicy->evaluateProviderOutput($result); if ($outputDecision->blocked) { $request->forceFill([ 'status' => AiEditRequest::STATUS_BLOCKED, 'safety_state' => $outputDecision->state, 'safety_reasons' => $outputDecision->reasonCodes, 'failure_code' => $outputDecision->failureCode ?? 'output_policy_blocked', 'failure_message' => $outputDecision->failureMessage, 'completed_at' => now(), ])->save(); return; } DB::transaction(function () use ($request, $result): void { foreach ($result->outputs as $output) { AiEditOutput::query()->updateOrCreate( [ 'request_id' => $request->id, 'provider_asset_id' => (string) Arr::get($output, 'provider_asset_id', ''), ], [ 'provider_url' => Arr::get($output, 'provider_url'), 'mime_type' => Arr::get($output, 'mime_type'), 'width' => Arr::get($output, 'width'), 'height' => Arr::get($output, 'height'), 'is_primary' => true, 'safety_state' => 'passed', 'safety_reasons' => [], 'generated_at' => now(), 'metadata' => ['provider' => $request->provider], ] ); } $request->forceFill([ 'status' => AiEditRequest::STATUS_SUCCEEDED, 'safety_state' => 'passed', 'safety_reasons' => [], 'failure_code' => null, 'failure_message' => null, 'completed_at' => now(), ])->save(); }); $usageLedger->recordDebitForRequest($request->fresh(), $result->costUsd, [ 'source' => 'process_job', ]); return; } if ($result->status === 'processing') { $request->forceFill([ 'status' => AiEditRequest::STATUS_PROCESSING, 'failure_code' => null, 'failure_message' => null, ])->save(); if ($result->providerTaskId !== null && $result->providerTaskId !== '') { PollAiEditRequest::dispatch($request->id, $result->providerTaskId, 1) ->delay(now()->addSeconds(20)) ->onQueue($runtimeConfig->queueName()); } return; } $request->forceFill([ 'status' => $result->status === 'blocked' ? AiEditRequest::STATUS_BLOCKED : AiEditRequest::STATUS_FAILED, 'safety_state' => $result->safetyState ?? $request->safety_state, 'safety_reasons' => $result->safetyReasons !== [] ? $result->safetyReasons : $request->safety_reasons, 'failure_code' => $result->failureCode, 'failure_message' => $result->failureMessage, 'completed_at' => now(), ])->save(); } }