class TextGenerationLoop

Traits

Constants

private PRELIMINARY_OUTPUT_BYTES

The minimum bytes a sub-agent must add before its unfinished output is reported again.

private REPAIR_TOOL_CALLS_CONTEXT_KEY

private TOOL_CONCURRENCY_CONTEXT_KEY

protected DEFAULT_MAX_STEPS

The default ceiling for the derived step budget when it is not set explicitly.

Methods

array
pendingToolCalls(array $messages)

Get the unresolved tool calls in the latest assistant turn, plus the ids already answered.

array
appendApprovalResults(array $messages, array $toolResults)

Append a resume's tool results, merging into the pause turn's partial results so one assistant turn keeps one answering message.

array
settleAbandonedToolCalls(array $messages, bool $exceptLatestAssistantTurn = false)

Settle unresolved historical tool calls so the history remains replayable, optionally leaving the latest assistant turn for a resume to decide.

int
latestAssistantTurnIndex(array $messages)

Find the index of the latest assistant message, or the message count when there is none.

pendingApprovalsFor(Collection $toolCalls, Collection $approvals)

Build pending approval requests for the given tool calls.

float
elapsedMilliseconds(int $startedAt)

Get the milliseconds elapsed since the given monotonic nanosecond reading.

string
executeTool(Tool $tool, array $arguments, string|null $toolCallId = null, RunContext|null $context = null)

Execute the given tool with the given arguments.

Tool|null
findTool(string $name, array $tools)

Find a tool by its name from the given tools array.

static string
joinReasoning(iterable $blocks)

Join reasoning blocks, separating each block with a blank line.

__construct(StepTextGateway $gateway)

Create a new text generation loop instance.

generate(TextProvider $provider, string $model, string|null $instructions, array $messages = [], array $tools = [], array|null $schema = null, TextGenerationOptions|null $options = null, int|null $timeout = null, array|null $approval = null, Closure|null $recordApprovalResults = null, RunContext|null $context = null)

Generate a response through model and tool steps.

stream(string $invocationId, TextProvider $provider, string $model, string|null $instructions, array $messages = [], array $tools = [], array|null $schema = null, TextGenerationOptions|null $options = null, int|null $timeout = null, array|null $approval = null, Closure|null $recordApprovalResults = null, array|null $validatedApproval = null, RunContext|null $context = null)

Stream a response through model and tool steps.

array
middlewareFor(TextGenerationOptions|null $options)

Get the runtime and declared middleware wrapping each generation step.

runStep(PendingStep $step, array $middleware, Closure $run)

Each middleware receives a StepResult from $next, whether the inner layer streamed, short-circuited or replaced the step.

toStepResult(mixed $result)

Normalize a middleware result to a step result.

attempt(PendingStep $step, PendingStep|null $previous, array $history, string|null $continuationToken, RunContext|null $context, Closure $call)

Send the prepared step to the model, reporting its start and any synchronous failure.

void
stepCompleted(RunContext|null $context, StepResult|null $attempt, StepResponse $response)

A step that middleware answered itself was never attempted and reports nothing.

void
stepFailed(RunContext|null $context, StepResult|null $attempt, Throwable $exception)

Report a failed generation attempt when one was made.

stepContextFor(PendingStep $step, PendingStep|null $previous, array $history, string|null $continuationToken)

A continuation replays only unchanged history, model and instructions.

eventsFor(string $invocationId, TextProvider $provider, string $model, StepResponse $response)

The stream events describing a step response that was produced without streaming.

int
resolveMaxSteps(TextGenerationOptions|null $options, array $tools)

Resolve the step budget: explicit maxSteps, else 1.5x tools capped at the ceiling, else 5.

array
stepToolResults(StepResponse $result, bool $isFinalStep, array $tools, RunContext|null $context = null)

Tool results to continue the loop with, plus any pending approvals that pause it.

array|null
earlyStepToolResults(StepResponse $result)

The early outcome for steps that pause or execute nothing, or null when tools should run.

streamedStepToolResults(StepResponse $result, bool $isFinalStep, array $tools, string $invocationId, TextGenerationOptions|null $options = null, RunContext|null $context = null)

Get tool results while streaming sub-agent activity.

preliminaryToolResults(Generator $events, ToolCall $toolCall, string $invocationId)

Report the output a still running tool has produced so far.

executeAgentToolStreaming(AgentTool $tool, array $arguments, string|null $toolCallId = null, RunContext|null $context = null)

Execute a sub-agent tool, streaming its activity while reporting through the run context.

array
approvalAwareToolResults(array $toolCalls, array $tools, bool $isFinalStep = false, RunContext|null $context = null)

Run the executable tools and collect any pending approvals.

resolvedToolResult(ToolCall $toolCall, Tool|null $tool, bool $isFinalStep, array $tools = [], RunContext|null $context = null)

Execute the resolved tool call, or mark it as repaired or exhausted when it can no longer run.

executedToolResult(ToolCall $toolCall, Tool $tool, RunContext|null $context = null)

Execute the tool for the given call, returning a failure to the model instead of ending the run.

string
failedToolResultText(Throwable $exception)

Get the result the model receives for a tool that threw.

recordedToolResult(ToolResult $result, RunContext|null $context)

Answer the recorded step with the result its tool just produced.

toolResult(ToolCall $toolCall, mixed $result, bool $failed = false)

Create a tool result for the given tool call.

array
resolveToolCalls(array $toolCalls, array $tools, bool $isFinalStep)

Split the step's tool calls into executable [ToolCall, Tool] pairs and pending approvals.

string
availableToolNames(array $tools)

The names of the locally executable tools, as advertised back to a model that called an unknown one.

resumeFromApproval(array $approval, array $messages, array $tools, array|null $validatedApproval = null, RunContext|null $context = null)

Apply the approval's decisions to the pending pause, returning the updated history and resume state.

array
resolveApprovalResults(array $approval, array $messages, array $tools, array|null $validatedApproval = null, RunContext|null $context = null)

Claim the pending calls and record each decision's outcome before executing the next.

bool
pausedForApproval(array $messages, array $tools)

Determine whether the history ends in a pause a real run could have left: every pending call is for a tool the agent still has, and at least one of them asks for approval.

toolsForPendingCalls(Collection $pendingToolCalls, array $tools)

Find the tool each pending call is for, keyed by the call's id.

array
validateApproval(array $approval, array $messages, array $tools)

Validate the approval's decisions against the pending tool calls, throwing on any mismatch.

Approval|null
approvalForTool(Tool|null $tool, ToolCall $toolCall)

Resolve the approval requirement for a tool call, or null when it is not gated.

string
generateEventId()

Generate a unique stream event ID.

buildAssistantMessage(StepResponse $result)

Build an assistant message from the given step response.

buildStep(StepResponse $result)

Build the recorded step from its provider response.

emptyResponse(TextProvider $provider, string $model, array $newMessages)

Build the response for a run that ended before any step completed.

buildFinalResponse(Collection $steps, array $newMessages, StepResponse|null $lastResult)

Build the final text response from all generated steps.

void
ensureSingleToolSearch(array $tools)

Ensure at most one tool search wrapper is registered per request.

array
toolsSupportedBy(TextProvider $provider, array $tools)

Drop provider tools the provider cannot run, unwrapping tool search into its deferred tools.

Details

protected array pendingToolCalls(array $messages)

Get the unresolved tool calls in the latest assistant turn, plus the ids already answered.

Parameters

array $messages

Return Value

array

protected array appendApprovalResults(array $messages, array $toolResults)

Append a resume's tool results, merging into the pause turn's partial results so one assistant turn keeps one answering message.

Parameters

array $messages
array $toolResults

Return Value

array

protected array settleAbandonedToolCalls(array $messages, bool $exceptLatestAssistantTurn = false)

Settle unresolved historical tool calls so the history remains replayable, optionally leaving the latest assistant turn for a resume to decide.

Parameters

array $messages
bool $exceptLatestAssistantTurn

Return Value

array

in HandlesToolApprovals at line 140
protected int latestAssistantTurnIndex(array $messages)

Find the index of the latest assistant message, or the message count when there is none.

Parameters

array $messages

Return Value

int

in HandlesToolApprovals at line 158
protected Collection pendingApprovalsFor(Collection $toolCalls, Collection $approvals)

Build pending approval requests for the given tool calls.

Parameters

Collection $toolCalls
Collection $approvals

Return Value

Collection

in MeasuresDuration at line 12
protected float elapsedMilliseconds(int $startedAt)

Get the milliseconds elapsed since the given monotonic nanosecond reading.

Parameters

int $startedAt

Return Value

float

in InvokesTools at line 28
protected string executeTool(Tool $tool, array $arguments, string|null $toolCallId = null, RunContext|null $context = null)

Execute the given tool with the given arguments.

Parameters

Tool $tool
array $arguments
string|null $toolCallId
RunContext|null $context

Return Value

string

in InvokesTools at line 63
protected Tool|null findTool(string $name, array $tools)

Find a tool by its name from the given tools array.

Parameters

string $name
array $tools

Return Value

Tool|null

in JoinsReasoning at line 16
static protected string joinReasoning(iterable $blocks)

Join reasoning blocks, separating each block with a blank line.

Parameters

iterable $blocks

Return Value

string

at line 107
__construct(StepTextGateway $gateway)

Create a new text generation loop instance.

Parameters

StepTextGateway $gateway

at line 118
TextResponse generate(TextProvider $provider, string $model, string|null $instructions, array $messages = [], array $tools = [], array|null $schema = null, TextGenerationOptions|null $options = null, int|null $timeout = null, array|null $approval = null, Closure|null $recordApprovalResults = null, RunContext|null $context = null)

Generate a response through model and tool steps.

Parameters

TextProvider $provider
string $model
string|null $instructions
array $messages
array $tools
array|null $schema
TextGenerationOptions|null $options
int|null $timeout
array|null $approval
Closure|null $recordApprovalResults
RunContext|null $context

Return Value

TextResponse

at line 261
Generator stream(string $invocationId, TextProvider $provider, string $model, string|null $instructions, array $messages = [], array $tools = [], array|null $schema = null, TextGenerationOptions|null $options = null, int|null $timeout = null, array|null $approval = null, Closure|null $recordApprovalResults = null, array|null $validatedApproval = null, RunContext|null $context = null)

Stream a response through model and tool steps.

Parameters

string $invocationId
TextProvider $provider
string $model
string|null $instructions
array $messages
array $tools
array|null $schema
TextGenerationOptions|null $options
int|null $timeout
array|null $approval
Closure|null $recordApprovalResults
array|null $validatedApproval

the caller's own eager validateApproval() result, reused instead of re-validating

RunContext|null $context

Return Value

Generator

at line 490
protected array middlewareFor(TextGenerationOptions|null $options)

Get the runtime and declared middleware wrapping each generation step.

Parameters

TextGenerationOptions|null $options

Return Value

array

at line 511
protected StepResult runStep(PendingStep $step, array $middleware, Closure $run)

Each middleware receives a StepResult from $next, whether the inner layer streamed, short-circuited or replaced the step.

Parameters

PendingStep $step
array $middleware
Closure $run

Return Value

StepResult

at line 527
protected StepResult toStepResult(mixed $result)

Normalize a middleware result to a step result.

Parameters

mixed $result

Return Value

StepResult

at line 542
protected StepResult attempt(PendingStep $step, PendingStep|null $previous, array $history, string|null $continuationToken, RunContext|null $context, Closure $call)

Send the prepared step to the model, reporting its start and any synchronous failure.

Parameters

PendingStep $step
PendingStep|null $previous
array $history
string|null $continuationToken
RunContext|null $context
Closure $call

Return Value

StepResult

at line 567
protected void stepCompleted(RunContext|null $context, StepResult|null $attempt, StepResponse $response)

A step that middleware answered itself was never attempted and reports nothing.

Parameters

RunContext|null $context
StepResult|null $attempt
StepResponse $response

Return Value

void

at line 577
protected void stepFailed(RunContext|null $context, StepResult|null $attempt, Throwable $exception)

Report a failed generation attempt when one was made.

Parameters

RunContext|null $context
StepResult|null $attempt
Throwable $exception

Return Value

void

at line 589
protected StepContext stepContextFor(PendingStep $step, PendingStep|null $previous, array $history, string|null $continuationToken)

A continuation replays only unchanged history, model and instructions.

Parameters

PendingStep $step
PendingStep|null $previous
array $history
string|null $continuationToken

Return Value

StepContext

at line 605
protected Generator eventsFor(string $invocationId, TextProvider $provider, string $model, StepResponse $response)

The stream events describing a step response that was produced without streaming.

Parameters

string $invocationId
TextProvider $provider
string $model
StepResponse $response

Return Value

Generator

at line 639
protected int resolveMaxSteps(TextGenerationOptions|null $options, array $tools)

Resolve the step budget: explicit maxSteps, else 1.5x tools capped at the ceiling, else 5.

Parameters

TextGenerationOptions|null $options
array $tools

Return Value

int

at line 717
protected array stepToolResults(StepResponse $result, bool $isFinalStep, array $tools, RunContext|null $context = null)

Tool results to continue the loop with, plus any pending approvals that pause it.

Parameters

StepResponse $result
bool $isFinalStep
array $tools
RunContext|null $context

Return Value

array

at line 728
protected array|null earlyStepToolResults(StepResponse $result)

The early outcome for steps that pause or execute nothing, or null when tools should run.

Parameters

StepResponse $result

Return Value

array|null

at line 747
protected Generator streamedStepToolResults(StepResponse $result, bool $isFinalStep, array $tools, string $invocationId, TextGenerationOptions|null $options = null, RunContext|null $context = null)

Get tool results while streaming sub-agent activity.

Parameters

StepResponse $result
bool $isFinalStep
array $tools
string $invocationId
TextGenerationOptions|null $options
RunContext|null $context

Return Value

Generator

at line 805
protected Generator preliminaryToolResults(Generator $events, ToolCall $toolCall, string $invocationId)

Report the output a still running tool has produced so far.

Parameters

Generator $events
ToolCall $toolCall
string $invocationId

Return Value

Generator

at line 851
protected Generator executeAgentToolStreaming(AgentTool $tool, array $arguments, string|null $toolCallId = null, RunContext|null $context = null)

Execute a sub-agent tool, streaming its activity while reporting through the run context.

Parameters

AgentTool $tool
array $arguments
string|null $toolCallId
RunContext|null $context

Return Value

Generator

at line 894
protected array approvalAwareToolResults(array $toolCalls, array $tools, bool $isFinalStep = false, RunContext|null $context = null)

Run the executable tools and collect any pending approvals.

Parameters

array $toolCalls
array $tools
bool $isFinalStep
RunContext|null $context

Return Value

array

at line 1015
protected ToolResult resolvedToolResult(ToolCall $toolCall, Tool|null $tool, bool $isFinalStep, array $tools = [], RunContext|null $context = null)

Execute the resolved tool call, or mark it as repaired or exhausted when it can no longer run.

Parameters

ToolCall $toolCall
Tool|null $tool
bool $isFinalStep
array $tools
RunContext|null $context

Return Value

ToolResult

at line 1027
protected ToolResult executedToolResult(ToolCall $toolCall, Tool $tool, RunContext|null $context = null)

Execute the tool for the given call, returning a failure to the model instead of ending the run.

Parameters

ToolCall $toolCall
Tool $tool
RunContext|null $context

Return Value

ToolResult

at line 1041
protected string failedToolResultText(Throwable $exception)

Get the result the model receives for a tool that threw.

Parameters

Throwable $exception

Return Value

string

at line 1053
protected ToolResult recordedToolResult(ToolResult $result, RunContext|null $context)

Answer the recorded step with the result its tool just produced.

Parameters

ToolResult $result
RunContext|null $context

Return Value

ToolResult

at line 1063
protected ToolResult toolResult(ToolCall $toolCall, mixed $result, bool $failed = false)

Create a tool result for the given tool call.

Parameters

ToolCall $toolCall
mixed $result
bool $failed

Return Value

ToolResult

at line 1082
protected array resolveToolCalls(array $toolCalls, array $tools, bool $isFinalStep)

Split the step's tool calls into executable [ToolCall, Tool] pairs and pending approvals.

Parameters

array $toolCalls
array $tools
bool $isFinalStep

Return Value

array

at line 1118
protected string availableToolNames(array $tools)

The names of the locally executable tools, as advertised back to a model that called an unknown one.

Parameters

array $tools

Return Value

string

at line 1134
protected ApprovalResumption resumeFromApproval(array $approval, array $messages, array $tools, array|null $validatedApproval = null, RunContext|null $context = null)

Apply the approval's decisions to the pending pause, returning the updated history and resume state.

Parameters

array $approval
array $messages
array $tools
array|null $validatedApproval

a caller's own eager validateApproval() result, reused instead of re-validating

RunContext|null $context

Return Value

ApprovalResumption

at line 1163
protected array resolveApprovalResults(array $approval, array $messages, array $tools, array|null $validatedApproval = null, RunContext|null $context = null)

Claim the pending calls and record each decision's outcome before executing the next.

Parameters

array $approval
array $messages
array $tools
array|null $validatedApproval

a caller's own eager validateApproval() result, reused instead of re-validating

RunContext|null $context

Return Value

array

at line 1236
bool pausedForApproval(array $messages, array $tools)

Determine whether the history ends in a pause a real run could have left: every pending call is for a tool the agent still has, and at least one of them asks for approval.

Parameters

array $messages
array $tools

Return Value

bool

at line 1253
protected Collection toolsForPendingCalls(Collection $pendingToolCalls, array $tools)

Find the tool each pending call is for, keyed by the call's id.

Parameters

Collection $pendingToolCalls
array $tools

Return Value

Collection

at line 1268
array validateApproval(array $approval, array $messages, array $tools)

Validate the approval's decisions against the pending tool calls, throwing on any mismatch.

Parameters

array $approval
array $messages
array $tools

Return Value

array

at line 1312
protected Approval|null approvalForTool(Tool|null $tool, ToolCall $toolCall)

Resolve the approval requirement for a tool call, or null when it is not gated.

Parameters

Tool|null $tool
ToolCall $toolCall

Return Value

Approval|null

at line 1322
protected string generateEventId()

Generate a unique stream event ID.

Return Value

string

at line 1330
protected AssistantMessage buildAssistantMessage(StepResponse $result)

Build an assistant message from the given step response.

Parameters

StepResponse $result

Return Value

AssistantMessage

at line 1342
protected Step buildStep(StepResponse $result)

Build the recorded step from its provider response.

Parameters

StepResponse $result

Return Value

Step

at line 1362
protected TextResponse emptyResponse(TextProvider $provider, string $model, array $newMessages)

Build the response for a run that ended before any step completed.

Parameters

TextProvider $provider
string $model
array $newMessages

Return Value

TextResponse

at line 1371
protected TextResponse buildFinalResponse(Collection $steps, array $newMessages, StepResponse|null $lastResult)

Build the final text response from all generated steps.

Parameters

Collection $steps
array $newMessages
StepResponse|null $lastResult

Return Value

TextResponse

at line 1413
protected void ensureSingleToolSearch(array $tools)

Ensure at most one tool search wrapper is registered per request.

Parameters

array $tools

Return Value

void

at line 1426
protected array toolsSupportedBy(TextProvider $provider, array $tools)

Drop provider tools the provider cannot run, unwrapping tool search into its deferred tools.

Parameters

TextProvider $provider
array $tools

Return Value

array