\nEXCERPT: \nBODY:\n\n" . "In BODY use plain text only: blank lines between paragraphs, '## ' before a heading, '- ' before a list item. Do not use HTML, links, code or tables. " . "Do not invent facts, statistics, quotations, prices or contact details; where the brief lacks a fact, keep the wording general. Write in the language of the brief."; public function __construct(private Ledger $ledger, private AuditLog $audit) { } public static function validBrief(string $brief): string { $brief = trim((string) preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', str_replace("\r\n", "\n", $brief))); $len = mb_strlen($brief); if ($len < self::BRIEF_MIN || $len > self::BRIEF_MAX || !preg_match('/\p{L}/u', $brief)) { throw new ContentStudioException('invalid_brief'); } return $brief; } /** * @return array{title:string, excerpt:string, body:string, model:string, input:int, output:int} * @throws ContentStudioException */ public function generate(Principal $principal, string $requestId, string $type, string $brief, ModelAdapter $adapter, string $model, ?int $connectionId): array { if (!$principal->isAuthenticated() || !$principal->can(Capabilities::USE_CONTENT_STUDIO)) { throw new ContentStudioException('forbidden'); } if (!in_array($type, self::TYPES, true)) { throw new ContentStudioException('invalid_type'); } if (!preg_match('/^cs_[a-f0-9]{24}$/', $requestId) || $model === '' || strlen($model) > 128) { throw new ContentStudioException('invalid_request'); } $brief = self::validBrief($brief); $site = $principal->siteId; $actor = $principal->actorRef(); $maxOut = min(self::MAX_OUTPUT, (int) ($adapter->capabilities()['max_output_tokens'] ?? self::MAX_OUTPUT)); $request = new ModelRequest($model, self::INSTRUCTION . "\nContent type: $type.", [['role' => 'user', 'content' => $brief]], $maxOut, $requestId, self::TIMEOUT); $units = ($adapter instanceof RequestBudget ? $adapter->inputReservation($request) : $request->estimatedInputTokens()) + $maxOut; if ($this->ledger->balance($site, 'site') === null) { throw new ContentStudioException('no_budget'); } try { $reservation = $this->ledger->reserve($site, $requestId, ['site'], $units); } catch (SpendingHaltedException) { throw new ContentStudioException('paused'); } catch (BudgetExceededException) { $this->audit->record($site, $actor, 'content_studio.generate', 'type:' . $type, 'budget_denied', $requestId); throw new ContentStudioException('budget_exceeded'); } if ($reservation->duplicate) { throw new ContentStudioException($reservation->state === Reservation::UNCERTAIN ? 'outcome_uncertain' : 'duplicate', $requestId); } $simulated = (bool) ($adapter->capabilities()['simulated'] ?? false); $this->ledger->attribute($site, $requestId, $simulated ? Reservation::ORIGIN_SIMULATED : Reservation::ORIGIN_CONTENT_STUDIO, null, $connectionId ?? ($adapter instanceof ConnectionBound ? $adapter->connectionId() : null), $model); $this->ledger->markDispatched($site, $requestId); $basis = $simulated ? Reservation::BASIS_SIMULATED : (($adapter->capabilities()['usage'] ?? '') === 'reported' ? Reservation::BASIS_REPORTED : Reservation::BASIS_ESTIMATED); try { $result = $adapter->complete($request, CancellationToken::none()); } catch (ProviderException $e) { if (!$e->dispatched) { $this->ledger->settle($site, $requestId, 0, null, Reservation::BASIS_NOT_DISPATCHED); } elseif ($e->rejected) { $this->ledger->settle($site, $requestId, 0, null, Reservation::BASIS_REJECTED); } elseif ($e->usageTokens !== null) { $this->ledger->settle($site, $requestId, $e->usageTokens, null, $basis); } else { $this->ledger->markUncertain($site, $requestId, 'content-studio-' . $e->kind); } $this->audit->record($site, $actor, 'content_studio.generate', 'type:' . $type, $e->dispatched && !$e->rejected && $e->usageTokens === null ? 'uncertain' : 'failed', $requestId, $e->kind); throw new ContentStudioException('provider_' . $e->kind, $requestId); } catch (\Throwable) { $this->ledger->markUncertain($site, $requestId, 'content-studio-unexpected'); $this->audit->record($site, $actor, 'content_studio.generate', 'type:' . $type, 'uncertain', $requestId); throw new ContentStudioException('outcome_uncertain', $requestId); } $this->ledger->settle($site, $requestId, $result->totalTokens(), $result->providerRef, $basis, $result->inputTokens, $result->outputTokens, $result->detail); $proposal = ContentFormatter::parse($result->text); if ($proposal['body'] === '') { $this->audit->record($site, $actor, 'content_studio.generate', 'type:' . $type, 'empty_output', $requestId); throw new ContentStudioException('empty_output', $requestId); } $this->audit->record($site, $actor, 'content_studio.generate', 'type:' . $type, 'ok', $requestId); return $proposal + ['model' => $model, 'input' => $result->inputTokens, 'output' => $result->outputTokens]; } } // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped