string, 'args' => array] entries. * * @var array> * @since 1.0.0 */ public array $toolCalls; /** * Updated message history (Message[]) — for the next step's persistence. * * @var Message[] * @since 1.0.0 */ public array $messages; /** * Token usage statistics as a plain array. * * @var array * @since 1.0.0 */ public array $tokenUsage; /** * Error, if the run failed. * * @var WP_Error|null * @since 1.0.0 */ public ?WP_Error $error; /** * Provider finish/stop reason for the final candidate. * * One of the WP AI Client FinishReasonEnum values ('stop', 'length', * 'content_filter', 'tool_calls', 'error'). Empty string when unknown. * * @var string * @since 1.2.0 */ public string $stopReason = ''; /** * Whether this response is paused waiting for user confirmation before * a write ability is executed. * * @var bool * @since 1.0.0 */ public bool $isPendingConfirmation = false; /** * UUID token that identifies the stored pending state in the transient store. * * @var string * @since 1.0.0 */ public string $confirmationToken = ''; /** * Tool calls awaiting confirmation (each ['name' => string, 'args' => array]). * * @var array> * @since 1.0.0 */ public array $pendingToolCalls = []; /** * What the paused run waits for: 'confirmation' (write actions to approve) * or 'questions' (the agent asked the user via the ask-user tool). * * Both kinds set $isPendingConfirmation, so every caller that only needs * to know "the run is paused for the user" keeps working unchanged. * * @var string * @since 1.3.1 */ public string $pendingKind = ''; /** * The questions the agent asked, as {intro: string, questions: array}. * * @var array * @since 1.3.1 */ public array $pendingQuestions = []; /** * Set when the agent delivered a plan through present-plan, as * {summary: string}. The turn is finished (the plan is its text); the chat * opens the plan review modal for the next step. * * @var array * @since 1.3.1 */ public array $planReview = []; /** * Constructor. * * @param string $text Final assistant text. * @param array> $toolCalls Executed tool calls. * @param Message[] $messages Updated message history. * @param array $tokenUsage Token usage. * @param WP_Error|null $error Error, if any. * @param string $stopReason Provider finish/stop reason. * * @since 1.0.0 */ public function __construct( string $text = '', array $toolCalls = [], array $messages = [], array $tokenUsage = [], ?WP_Error $error = null, string $stopReason = '' ) { $this->text = $text; $this->toolCalls = $toolCalls; $this->messages = $messages; $this->tokenUsage = $tokenUsage; $this->error = $error; $this->stopReason = $stopReason; } /** * Creates an error response. * * @param WP_Error $error The error. * * @return self * @since 1.0.0 */ public static function fromError(WP_Error $error): self { return new self('', [], [], [], $error); } /** * Creates a pending-confirmation response. * * @param string $token UUID token for the transient store. * @param array> $pendingToolCalls Tool calls awaiting approval. * @param Message[] $messages Message history at the time of detection. * @param array $tokenUsage Token usage so far. * @param array> $toolCalls Tool calls already executed in this run, * before the pending one was requested. * * @return self * @since 1.0.0 */ public static function pendingConfirmation( string $token, array $pendingToolCalls, array $messages, array $tokenUsage, array $toolCalls = [] ): self { $instance = new self('', $toolCalls, $messages, $tokenUsage); $instance->isPendingConfirmation = true; $instance->pendingKind = 'confirmation'; $instance->confirmationToken = $token; $instance->pendingToolCalls = $pendingToolCalls; return $instance; } /** * Creates a response paused on questions the agent asked the user. * * @param string $token UUID token for the transient store. * @param array $questions Normalized {intro, questions} payload. * @param string $text Text the model wrote alongside the call. * @param Message[] $messages Message history ending with the asking message. * @param array $tokenUsage Token usage so far. * @param array> $toolCalls Tool calls already executed in this run. * * @return self * @since 1.3.1 */ public static function pendingQuestions( string $token, array $questions, string $text, array $messages, array $tokenUsage, array $toolCalls = [] ): self { $instance = new self($text, $toolCalls, $messages, $tokenUsage); $instance->isPendingConfirmation = true; $instance->pendingKind = 'questions'; $instance->confirmationToken = $token; $instance->pendingQuestions = $questions; return $instance; } /** * Whether this response represents an error. * * @return bool * @since 1.0.0 */ public function isError(): bool { return $this->error instanceof WP_Error; } /** * Converts the response to an array suitable for a REST response. * * Note: the raw Message[] history is intentionally excluded; it is kept only * in memory for the (future) persistence step. * * @return array * @since 1.0.0 */ public function toArray(): array { if ($this->isError()) { return [ 'success' => false, 'error' => [ 'code' => $this->error->get_error_code(), 'message' => $this->error->get_error_message(), ], ]; } if ('questions' === $this->pendingKind) { return [ 'success' => true, 'pendingQuestions' => $this->pendingQuestions, 'confirmationToken' => $this->confirmationToken, 'text' => $this->text, 'toolCalls' => $this->toolCalls, ]; } if ($this->isPendingConfirmation) { $first = $this->pendingToolCalls[0] ?? []; return [ 'success' => true, 'pendingConfirmation' => true, 'confirmationToken' => $this->confirmationToken, 'pendingAction' => [ 'name' => $first['name'] ?? '', 'args' => $first['args'] ?? [], ], 'pendingToolCalls' => $this->pendingToolCalls, // Calls already executed before the pending one was requested, so a // resumed multi-step run can report everything that has run so far. 'toolCalls' => $this->toolCalls, ]; } $payload = [ 'success' => true, 'text' => $this->text, 'toolCalls' => $this->toolCalls, 'tokenUsage' => $this->tokenUsage, ]; if ('' !== $this->stopReason) { $payload['stopReason'] = $this->stopReason; } if (! empty($this->planReview)) { $payload['planReview'] = $this->planReview; } return $payload; } }