@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
2,303 lines • 139 kB
JavaScript
import { getAgentToolStreamBuffer } from "./runner/agentToolStream.mjs";
import { Agent } from "./agent.mjs";
import { RunAgentUpdatedStreamEvent, RunRawModelStreamEvent } from "./events.mjs";
import { AgentsError, ModelBehaviorError, ModelTimeoutError, UserError, } from "./errors.mjs";
import { defineInputGuardrail, defineOutputGuardrail, } from "./guardrail.mjs";
import { RunHooks } from "./lifecycle.mjs";
import logger, { logModelAndToolActionDebug } from "./logger.mjs";
import { getDefaultModelProvider } from "./providers.mjs";
import { RunContext } from "./runContext.mjs";
import { RunResult, StreamedRunResult } from "./result.mjs";
import { assertPendingSessionCompactionTerminalAuthority, RunState, } from "./runState.mjs";
import { getCurrentTrace, getCurrentTraceContext, resetCurrentSpan, setCurrentSpan, withNewSpanContext, withTrace, withTraceContext, } from "./tracing/context.mjs";
import { includeTaskAndTurnSpans, mergeTracingConfig } from "./tracing/config.mjs";
import { Usage } from "./usage.mjs";
import { convertAgentOutputTypeToSerializable } from "./utils/tools.mjs";
import { snapshotRawUsage } from "./utils/rawUsage.mjs";
import { DEFAULT_MAX_TURNS } from "./runner/constants.mjs";
import { StreamEvent, StreamEventResponseCompleted } from "./types/protocol.mjs";
import { SandboxRuntimeManager } from "./sandbox/runtime/index.mjs";
import { ServerConversationTracker, applyCallModelInputFilter, getServerConversationOwner, } from "./runner/conversation.mjs";
import { createGuardrailTracker, finalizeOutputGuardrails, } from "./runner/guardrails.mjs";
import { adjustModelSettingsForLegacyModel, mergeModelSettings, maybeResetToolChoice, selectModel, } from "./runner/modelSettings.mjs";
import { getResponseWithRetry, getStreamedResponseWithRetry, validateModelTimeoutMs, } from "./runner/modelRetry.mjs";
import { processModelResponseAsync } from "./runner/modelOutputs.mjs";
import { addStepToRunResult, streamStepItemsToRunResult, isAbortError, } from "./runner/streaming.mjs";
import { acquireResumedSessionWriteOperation, createSessionPersistenceTracker, getSessionCompactionState, bindSessionCompactionState, prepareSessionCompactionExchange, captureSessionHistoryTransactionInputItems, markSessionHistoryTransactionInputPersisted, prepareSessionHistoryTransactionsForRun, prepareResumedSessionWrite, reconcilePendingSessionWriteBeforeRun, releaseProvisionalSessionHistoryTransactionBinding, releaseUnusedSessionHistoryTransactionBinding, prepareInputItemsWithSession, saveStreamInputToSession, saveStreamResultToSession, saveToSession, } from "./runner/sessionPersistence.mjs";
import { filterSuppressedToolCallItems, preflightModelResponseToolInvocations, preflightToolInvocations, resolveTurnAfterModelResponse, } from "./runner/turnResolution.mjs";
import { assertResumedSessionOutputGuardrailSafety, captureCurrentResponseToolOutputGuardrailResultStart, normalizeSerializedOutputGuardrailBlockedMessage, hasBlockedOutputExecutionEffect, hasPendingApprovedToolInputCompaction, hasPersistedToolOutput, hasTerminalToolOutputSource, sanitizeBlockedTerminalToolOutput, redactCancelledResponseToolOutputs, shouldDeferInterruptedSessionItems, } from "./runner/blockedOutputPersistence.mjs";
import { createOutputGuardrailBlockedMessageResolver, OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT, } from "./runner/outputGuardrailBlockedMessage.mjs";
import { prepareTurn } from "./runner/turnPreparation.mjs";
import { commitPendingInput, hasUnpersistedRunInput, mapPendingInputAfterContextProcessing, selectPendingInputForAdmission, } from "./runner/pendingInput.mjs";
import { prepareAgentArtifacts, trackDeferredToolRecovery, } from "./runner/modelPreparation.mjs";
import { applyTurnResult, assertAcceptedResponseContinuationAuthority, handleInterruptedOutcome, isAcceptedResponseCheckpoint, markAcceptedResponseProcessingStarted, markAcceptedResponseFinalizationStarted, resumeAcceptedModelResponse, resumeInterruptedTurn, } from "./runner/runLoop.mjs";
import { applyTraceOverrides, ensureActiveAgentSpanForInterruptedResume, ensureTurnSpan, finishRunnerSpan, getRunnerSpanErrorDetails, getTracing, setRunnerSpanError, startRunnerInvocationSpans, recordRunnerSpanUsage, } from "./runner/tracing.mjs";
import { getRunnerParentUsageRecorder, setRunStateUsageRecorder, } from "./runner/usageTracking.mjs";
import { getRunnerInvocationSpanParent, getRunStateTurnSpanParent, setRunStateTurnSpanParent, } from "./runner/invocationContext.mjs";
import { NoopTrace } from "./tracing/traces.mjs";
import { NOOP_TRACE_OR_SPAN_ID } from "./tracing/utils.mjs";
import { assertValidCompactionItems, CompactionItemValidationError, } from "./runner/items.mjs";
import { attachRunStateToError, invalidateAcceptedResponseReplayEvidence, prepareRunErrorFinalOutput, } from "./runner/errorHandlers.mjs";
import { finalizeSandboxRuntime, isSandboxRuntimeAgent, prepareSandboxInterruptedTurnResume, } from "./runner/sandbox.mjs";
import { buildAbortReconciliationInput, createStreamAbortReconciliationState, getAbortReconciliationPreviousResponseId, markAbortReconciliationComplete, recordStreamEventForAbortReconciliation, shouldReconcileStreamAbort, } from "./runner/streamReconciliation.mjs";
import { getImplicitModelSettingsForResolvedModel, validateToolExecutionConfig, validateToolNameCollisionPolicy, } from "./runner/runConfig.mjs";
function hasRetainableBlockedOutputEffect(state) {
return hasBlockedOutputExecutionEffect(state._generatedItems, state._currentTurnPersistedItemCount);
}
function commitDeferredRunErrorItemAfterPartialPersistence(state, preparedErrorOutput) {
if (preparedErrorOutput?.deferredItem &&
state._currentTurnPersistedItemCount > state._generatedItems.length) {
state._generatedItems.push(preparedErrorOutput.deferredItem);
return true;
}
return false;
}
export { getTracing } from "./runner/tracing.mjs";
export { selectModel } from "./runner/modelSettings.mjs";
export { getTurnInput } from "./runner/items.mjs";
class LazyDefaultModelProvider {
#modelProvider;
getModel(modelName) {
const modelProvider = this.#modelProvider ?? getDefaultModelProvider();
this.#modelProvider = modelProvider;
return modelProvider.getModel(modelName);
}
}
function isNoopTrace(trace) {
return trace instanceof NoopTrace || trace?.traceId === NOOP_TRACE_OR_SPAN_ID;
}
function rollbackUnstartedTurn(state, snapshot) {
if (!snapshot) {
return false;
}
state._currentTurn = snapshot.currentTurn;
state._currentTurnInProgress = snapshot.currentTurnInProgress;
return true;
}
function recordModelFailureUsage(usage, state, recordUsage) {
state.usage.add(usage);
recordUsage(usage);
}
function assertPendingInputServerOwnership(state, conversationId, previousResponseId) {
const serverManagesConversation = Boolean(conversationId || previousResponseId);
if (state._pendingInput.length === 0 ||
!serverManagesConversation ||
getServerConversationOwner(conversationId, previousResponseId)) {
return;
}
throw new UserError('Pending RunState input requires exactly one server-managed conversation owner', state);
}
export async function run(agent, input, options) {
const runner = getDefaultRunner();
if (options?.stream) {
return await runner.run(agent, input, options);
}
else {
return await runner.run(agent, input, options);
}
}
/**
* Orchestrates agent execution, including guardrails, tool calls, session persistence, and
* tracing. Reuse a `Runner` instance when you want consistent configuration across multiple runs.
*/
export class Runner extends RunHooks {
config;
traceOverrides;
/**
* Creates a runner with optional defaults that apply to every subsequent run invocation.
*
* @param config - Overrides for models, guardrails, tracing, or session behavior.
*/
constructor(config = {}) {
super();
this.config = {
modelProvider: config.modelProvider ?? new LazyDefaultModelProvider(),
model: config.model,
modelSettings: config.modelSettings,
handoffInputFilter: config.handoffInputFilter,
inputGuardrails: config.inputGuardrails,
outputGuardrails: config.outputGuardrails,
tracingDisabled: config.tracingDisabled ?? false,
traceIncludeSensitiveData: config.traceIncludeSensitiveData ?? true,
workflowName: config.workflowName ?? 'Agent workflow',
traceId: config.traceId,
groupId: config.groupId,
traceMetadata: config.traceMetadata,
tracing: config.tracing,
sandbox: config.sandbox,
toolExecution: validateToolExecutionConfig(config.toolExecution),
toolNotFoundBehavior: config.toolNotFoundBehavior ?? 'raise_error',
toolNameCollisionPolicy: validateToolNameCollisionPolicy(config.toolNameCollisionPolicy),
sessionInputCallback: config.sessionInputCallback,
callModelInputFilter: config.callModelInputFilter,
toolErrorFormatter: config.toolErrorFormatter,
outputGuardrailBlockedMessage: config.outputGuardrailBlockedMessage,
reasoningItemIdPolicy: config.reasoningItemIdPolicy,
};
this.traceOverrides = {
...(config.traceId !== undefined ? { traceId: config.traceId } : {}),
...(config.workflowName !== undefined
? { workflowName: config.workflowName }
: {}),
...(config.groupId !== undefined ? { groupId: config.groupId } : {}),
...(config.traceMetadata !== undefined
? { traceMetadata: config.traceMetadata }
: {}),
...(config.tracing?.apiKey !== undefined
? { tracingApiKey: config.tracing.apiKey }
: {}),
};
this.inputGuardrailDefs = (config.inputGuardrails ?? []).map(defineInputGuardrail);
this.outputGuardrailDefs = (config.outputGuardrails ?? []).map(defineOutputGuardrail);
}
async run(agent, input, options = {
stream: false,
context: undefined,
}) {
if (input instanceof RunState &&
(input._currentTurn > 0 || input._pendingInput.length > 0) &&
!input._initialInputGuardrailsCompleted) {
throw new UserError('Cannot resume this checkpoint because completion of initial input guardrails is unverified. Start a fresh run with the original input and context.', input);
}
this.#validateModelTimeoutForAgent(input instanceof RunState ? input._currentAgent : agent);
if (input instanceof RunState) {
if (isNoopTrace(input._trace)) {
input._trace = null;
}
if (input._currentAgentSpan?.spanId === NOOP_TRACE_OR_SPAN_ID) {
input.setCurrentAgentSpan(undefined);
}
}
const taskSpanName = this.#getTaskSpanName(input instanceof RunState ? input._trace?.name : undefined);
const capturedInvocationTraceContext = getCurrentTraceContext();
const invocationTraceContext = isNoopTrace(capturedInvocationTraceContext?.trace)
? undefined
: capturedInvocationTraceContext;
const configuredInvocationSpanParent = getRunnerInvocationSpanParent(this);
const invocationSpanParent = configuredInvocationSpanParent ??
(input instanceof RunState && input._trace
? input._trace
: (invocationTraceContext?.span ?? invocationTraceContext?.trace));
const resolvedOptions = options ?? { stream: false, context: undefined };
// Per-run options take precedence over runner defaults for session memory behavior.
const sessionInputCallback = resolvedOptions.sessionInputCallback ?? this.config.sessionInputCallback;
// Likewise allow callers to override callModelInputFilter on individual runs.
const callModelInputFilter = resolvedOptions.callModelInputFilter ?? this.config.callModelInputFilter;
// Per-run callback can override runner-level tool error formatting defaults.
const toolErrorFormatter = resolvedOptions.toolErrorFormatter ?? this.config.toolErrorFormatter;
const outputGuardrailBlockedMessage = resolvedOptions.outputGuardrailBlockedMessage === undefined
? this.config.outputGuardrailBlockedMessage
: resolvedOptions.outputGuardrailBlockedMessage;
const reasoningItemIdPolicy = resolvedOptions.reasoningItemIdPolicy ??
(input instanceof RunState ? input._reasoningItemIdPolicy : undefined) ??
this.config.reasoningItemIdPolicy;
const toolExecution = validateToolExecutionConfig(resolvedOptions.toolExecution ?? this.config.toolExecution);
const toolNotFoundBehavior = resolvedOptions.toolNotFoundBehavior ?? this.config.toolNotFoundBehavior;
const toolNameCollisionPolicy = validateToolNameCollisionPolicy(resolvedOptions.toolNameCollisionPolicy === undefined
? this.config.toolNameCollisionPolicy
: resolvedOptions.toolNameCollisionPolicy);
const hasCallModelInputFilter = Boolean(callModelInputFilter);
const tracingConfig = mergeTracingConfig(this.config.tracing, resolvedOptions.tracing);
const runContext = input instanceof RunState
? input._context
: resolvedOptions.context instanceof RunContext
? resolvedOptions.context
: new RunContext(resolvedOptions.context);
const traceOverrides = {
...this.traceOverrides,
...(resolvedOptions.tracing?.apiKey !== undefined
? { tracingApiKey: resolvedOptions.tracing.apiKey }
: {}),
};
const effectiveOptions = {
...resolvedOptions,
context: runContext,
sessionInputCallback,
callModelInputFilter,
toolErrorFormatter,
outputGuardrailBlockedMessage,
reasoningItemIdPolicy,
toolExecution,
toolNotFoundBehavior,
toolNameCollisionPolicy,
tracing: tracingConfig,
};
const useTaskAndTurnSpans = !this.config.tracingDisabled && includeTaskAndTurnSpans(tracingConfig);
const resumingFromState = input instanceof RunState;
// A resumed turn may be inactive but already have persisted items.
const preserveTurnPersistenceOnResume = resumingFromState &&
(input._currentTurnInProgress === true ||
input._currentTurnPersistedItemCount >
0);
const resumedConversationId = resumingFromState
? input._conversationId
: undefined;
const resumedPreviousResponseId = resumingFromState
? input._previousResponseId
: undefined;
const serverManagesConversation = Boolean(effectiveOptions.conversationId ?? resumedConversationId) ||
Boolean(effectiveOptions.previousResponseId ?? resumedPreviousResponseId);
// When the server tracks conversation history we defer to it for previous turns so local session
// persistence can focus solely on the new delta being generated in this process.
const session = effectiveOptions.session;
const sessionCompaction = getSessionCompactionState(session, input instanceof RunState ? input : undefined);
const resumedState = resumingFromState
? input
: undefined;
if (resumedState?._resumedSessionWriteInProgress) {
throw new UserError('The same RunState cannot resume or reconcile a Session write concurrently.');
}
const resumedStateRequiresInputCompaction = resumedState !== undefined &&
hasPendingApprovedToolInputCompaction(resumedState);
if (resumedStateRequiresInputCompaction) {
if (serverManagesConversation || session === undefined) {
throw new UserError('A pending Session compaction requires the same ordinary local Session to resume safely.');
}
assertPendingSessionCompactionTerminalAuthority(resumedState);
}
const releaseResumedSessionWriteOperation = resumedState &&
(resumedState._pendingSessionWrite !== undefined ||
resumedState._currentTurnSessionWriteCompactedItemCount !== undefined ||
resumedStateRequiresInputCompaction ||
resumedState._currentStep?.type === 'next_step_interruption')
? acquireResumedSessionWriteOperation(resumedState)
: undefined;
let releaseResumedSessionWriteOperationOnReturn = true;
try {
let provisionalSessionHistoryTransactionSessionId;
let provisionalSessionHistoryTransactionInputItems;
let pendingSessionWriteReconciled = false;
if (resumingFromState) {
assertAcceptedResponseContinuationAuthority(resumedState, effectiveOptions.conversationId, effectiveOptions.previousResponseId);
const hadSessionBinding = resumedState._currentTurnSessionHistoryTransactionSessionId !==
undefined;
const portableInputItems = resumedState._currentTurnSessionHistoryTransactionInputItems;
assertResumedSessionOutputGuardrailSafety(resumedState, session, this.#agentHasOutputGuardrail(resumedState._currentAgent));
pendingSessionWriteReconciled =
await reconcilePendingSessionWriteBeforeRun(session, resumedState, {
serverManagesConversation,
operationAlreadyOwned: releaseResumedSessionWriteOperation !== undefined,
});
resumedState.setReasoningItemIdPolicy(reasoningItemIdPolicy);
await prepareSessionHistoryTransactionsForRun(session, resumedState, {
serverManagesConversation,
});
if (!hadSessionBinding) {
provisionalSessionHistoryTransactionSessionId =
resumedState._currentTurnSessionHistoryTransactionSessionId;
provisionalSessionHistoryTransactionInputItems = portableInputItems;
}
}
const sessionPersistence = createSessionPersistenceTracker({
session,
runContext,
compactionState: sessionCompaction,
hasCallModelInputFilter,
persistInput: saveStreamInputToSession,
resumingFromState,
resumedSessionInputItems: resumingFromState
? input
._currentTurnSessionHistoryTransactionInputItems
: undefined,
});
let preparedInput = input;
if (!(preparedInput instanceof RunState)) {
const prepared = await prepareInputItemsWithSession(preparedInput, session, sessionInputCallback, {
// When the server tracks conversation state we only send the new turn inputs;
// previous messages are recovered via conversationId/previousResponseId.
includeHistoryInPreparedInput: !serverManagesConversation,
preserveDroppedNewItems: serverManagesConversation,
reasoningItemIdPolicy,
}, runContext, sessionCompaction);
if (serverManagesConversation && session) {
// When the server manages memory we only persist the new turn inputs locally so the
// conversation service stays the single source of truth for prior exchanges.
const sessionItems = prepared.sessionItems;
if (sessionItems && sessionItems.length > 0) {
preparedInput = sessionItems;
}
else {
preparedInput = prepared.preparedInput;
}
}
else {
preparedInput = prepared.preparedInput;
}
sessionPersistence?.setPreparedItems(prepared.sessionItems, prepared.preparedInput);
}
// Streaming runs persist the input asynchronously, so track a one-shot helper
// that can be awaited from multiple branches without double-writing.
const ensureStreamInputPersisted = sessionPersistence?.buildPersistInputOnce(serverManagesConversation);
const executeRun = async (effectiveInvocationSpanParent = invocationSpanParent) => {
if (effectiveOptions.stream) {
const streamResult = await this.#runIndividualStream(agent, preparedInput, taskSpanName, effectiveOptions, ensureStreamInputPersisted, sessionPersistence?.setPreparedTurnItems, sessionPersistence?.recordTurnItems, sessionPersistence?.getItemsForPersistence, preserveTurnPersistenceOnResume, provisionalSessionHistoryTransactionSessionId, provisionalSessionHistoryTransactionInputItems, {
sdkSessionId: async () => await session?.getSessionId(),
inputOverride: () => session
? sessionPersistence?.getItemsForPersistence()
: undefined,
}, effectiveInvocationSpanParent, pendingSessionWriteReconciled, sessionCompaction);
if (releaseResumedSessionWriteOperation) {
releaseResumedSessionWriteOperationOnReturn = false;
void streamResult.completed.then(releaseResumedSessionWriteOperation, releaseResumedSessionWriteOperation);
}
return streamResult;
}
const runResult = await this.#runIndividualNonStream(agent, preparedInput, effectiveOptions, taskSpanName, sessionPersistence?.setPreparedTurnItems, sessionPersistence?.recordTurnItems, sessionPersistence?.getItemsForPersistence, preserveTurnPersistenceOnResume, provisionalSessionHistoryTransactionSessionId, provisionalSessionHistoryTransactionInputItems, {
sdkSessionId: async () => await session?.getSessionId(),
inputOverride: () => session
? sessionPersistence?.getItemsForPersistence()
: undefined,
}, effectiveInvocationSpanParent, session && sessionPersistence && !serverManagesConversation
? async (result, persistenceOptions) => {
await saveToSession(session, sessionPersistence.getItemsForPersistence(), result, persistenceOptions);
}
: undefined, pendingSessionWriteReconciled, sessionCompaction);
return runResult;
};
if (this.config.tracingDisabled) {
const disabledTrace = new NoopTrace();
if (preparedInput instanceof RunState) {
preparedInput._currentAgentSpan?.end();
preparedInput._trace = null;
preparedInput.setCurrentAgentSpan(undefined);
}
return await withTrace(disabledTrace, async () => {
try {
const result = await executeRun(disabledTrace);
const clearDisabledTraceState = () => {
result.state._trace = null;
result.state.setCurrentAgentSpan(undefined);
};
if (result instanceof StreamedRunResult) {
void result.completed.then(clearDisabledTraceState, clearDisabledTraceState);
}
else {
clearDisabledTraceState();
}
return result;
}
catch (error) {
if (error instanceof AgentsError && error.state) {
error.state._trace = null;
error.state.setCurrentAgentSpan(undefined);
}
throw error;
}
});
}
if (preparedInput instanceof RunState && preparedInput._trace) {
const applied = applyTraceOverrides(preparedInput._trace, preparedInput._currentAgentSpan, traceOverrides);
preparedInput._trace = applied.trace;
preparedInput._currentAgentSpan = applied.currentSpan;
return await withTrace(preparedInput._trace, async () => {
if (preparedInput._currentAgentSpan && !useTaskAndTurnSpans) {
setCurrentSpan(preparedInput._currentAgentSpan);
}
return executeRun();
});
}
const executeInInvocationTrace = async (effectiveInvocationSpanParent = invocationSpanParent) => {
if (preparedInput instanceof RunState && !preparedInput._trace) {
preparedInput._trace = getCurrentTrace();
}
return executeRun(effectiveInvocationSpanParent);
};
if (invocationTraceContext) {
return await withTraceContext(invocationTraceContext, executeInInvocationTrace);
}
return await withTrace(this.config.workflowName ?? 'Agent workflow', async (trace) => executeInInvocationTrace(trace), {
traceId: this.config.traceId,
groupId: this.config.groupId,
metadata: this.config.traceMetadata,
// Per-run tracing config overrides exporter defaults such as environment API key.
tracingApiKey: tracingConfig?.apiKey,
});
}
finally {
if (releaseResumedSessionWriteOperationOnReturn) {
releaseResumedSessionWriteOperation?.();
}
}
}
// --------------------------------------------------------------
// Internals
// --------------------------------------------------------------
#getTaskSpanName(restoredWorkflowName) {
return (this.traceOverrides.workflowName ??
restoredWorkflowName ??
this.config.workflowName ??
'Agent workflow');
}
inputGuardrailDefs;
outputGuardrailDefs;
#agentHasOutputGuardrail(agent) {
return Boolean(this.outputGuardrailDefs.length > 0 || agent.outputGuardrails.length > 0);
}
#captureResponseGuardrailBoundary(state, overwrite) {
captureCurrentResponseToolOutputGuardrailResultStart(state, overwrite, this.#agentHasOutputGuardrail(state._currentAgent));
}
#shouldDeferInterruptedSessionItems(state) {
return shouldDeferInterruptedSessionItems(state, this.#agentHasOutputGuardrail(state._currentAgent));
}
/**
* @internal
* Resolves the effective model once so both run loops obey the same precedence rules.
*/
async #resolveModelForAgent(agent) {
const explicitlyModelSet = (agent.model !== undefined &&
agent.model !== Agent.DEFAULT_MODEL_PLACEHOLDER) ||
(this.config.model !== undefined &&
this.config.model !== Agent.DEFAULT_MODEL_PLACEHOLDER);
const selectedModel = selectModel(agent.model, this.config.model);
const resolvedModelName = typeof selectedModel === 'string' ? selectedModel : undefined;
const resolvedModel = typeof selectedModel === 'string'
? await this.config.modelProvider.getModel(selectedModel)
: selectedModel;
return { model: resolvedModel, explicitlyModelSet, resolvedModelName };
}
async #resolveSandboxRuntimeModelForAgent(agent) {
if (!isSandboxRuntimeAgent(agent)) {
return this.config.model;
}
const resolved = await this.#resolveModelForAgent(agent);
if (resolved.resolvedModelName &&
resolved.resolvedModelName.trim().length > 0) {
return {
model: resolved.resolvedModelName,
modelInstance: resolved.model,
};
}
return resolved.model;
}
#getAgentToolParentRunConfig(options) {
const hasSandboxOverride = typeof options.sandbox !== 'undefined';
const hasToolExecutionOverride = typeof options.toolExecution !== 'undefined';
const hasToolNotFoundBehaviorOverride = typeof options.toolNotFoundBehavior !== 'undefined';
const hasToolNameCollisionPolicyOverride = typeof options.toolNameCollisionPolicy !== 'undefined';
const hasTracingOverride = typeof options.tracing !== 'undefined';
if (!hasSandboxOverride &&
!hasToolExecutionOverride &&
!hasToolNotFoundBehaviorOverride &&
!hasToolNameCollisionPolicyOverride &&
!hasTracingOverride) {
return this.config;
}
return {
...this.config,
...(hasSandboxOverride ? { sandbox: options.sandbox } : {}),
...(hasToolExecutionOverride
? { toolExecution: options.toolExecution }
: {}),
...(hasToolNotFoundBehaviorOverride
? { toolNotFoundBehavior: options.toolNotFoundBehavior }
: {}),
...(hasToolNameCollisionPolicyOverride
? { toolNameCollisionPolicy: options.toolNameCollisionPolicy }
: {}),
...(hasTracingOverride ? { tracing: options.tracing } : {}),
};
}
/**
* @internal
*/
async #runIndividualNonStream(startingAgent, input, options, taskSpanName, sessionTurnInputUpdate,
// sessionInputUpdate lets the caller adjust queued session items after filters run so we
// persist exactly what we send to the model (e.g., after redactions or truncation).
sessionInputUpdate, getSessionInputForPersistence, preserveTurnPersistenceOnResume, provisionalSessionHistoryTransactionSessionId, provisionalSessionHistoryTransactionInputItems, sandboxMemoryRunContext, invocationSpanParent, persistResult, pendingSessionWriteReconciled = false, sessionCompaction) {
return withNewSpanContext(async () => {
// if we have a saved state we use that one, otherwise we create a new one
const isResumedState = input instanceof RunState;
const state = isResumedState
? input
: new RunState(options.context instanceof RunContext
? options.context
: new RunContext(options.context), input, startingAgent, options.maxTurns === undefined
? DEFAULT_MAX_TURNS
: options.maxTurns);
bindSessionCompactionState(state, sessionCompaction);
this.#validateModelTimeoutForAgent(state._currentAgent);
if (isResumedState) {
state._agentToolInvocation = undefined;
if (options.maxTurns !== undefined) {
state._maxTurns = options.maxTurns;
}
}
const sandboxRuntime = new SandboxRuntimeManager({
startingAgent,
sandboxConfig: options.sandbox ?? this.config.sandbox,
runState: isResumedState
? state
: undefined,
});
const agentToolParentRunConfig = this.#getAgentToolParentRunConfig(options);
const resolvedReasoningItemIdPolicy = options.reasoningItemIdPolicy ??
(isResumedState ? state._reasoningItemIdPolicy : undefined) ??
this.config.reasoningItemIdPolicy;
state.setReasoningItemIdPolicy(resolvedReasoningItemIdPolicy);
const resolvedConversationId = options.conversationId ??
(isResumedState ? state._conversationId : undefined);
const resolvedPreviousResponseId = options.previousResponseId ??
(isResumedState ? state._previousResponseId : undefined);
const serverManagesConversation = Boolean(resolvedConversationId || resolvedPreviousResponseId);
assertPendingInputServerOwnership(state, resolvedConversationId, resolvedPreviousResponseId);
if (!isResumedState) {
await prepareSessionHistoryTransactionsForRun(options.session, state, {
serverManagesConversation,
});
}
if (!isResumedState) {
state.setConversationContext(resolvedConversationId, resolvedPreviousResponseId);
}
const serverConversationTracker = serverManagesConversation
? new ServerConversationTracker({
conversationId: resolvedConversationId,
previousResponseId: resolvedPreviousResponseId,
reasoningItemIdPolicy: resolvedReasoningItemIdPolicy,
})
: undefined;
if (serverConversationTracker && isResumedState) {
serverConversationTracker.primeFromState({
originalInput: state._originalInput,
generatedItems: state._generatedItems,
modelResponses: state._modelResponses,
});
state.setConversationContext(serverConversationTracker.conversationId, serverConversationTracker.previousResponseId);
}
const toolErrorFormatter = options.toolErrorFormatter ?? this.config.toolErrorFormatter;
const outputGuardrailBlockedMessage = options.outputGuardrailBlockedMessage === undefined
? this.config.outputGuardrailBlockedMessage
: options.outputGuardrailBlockedMessage;
const useTaskAndTurnSpans = !this.config.tracingDisabled &&
includeTaskAndTurnSpans(options.tracing);
const resumingInterruptedTurn = isResumedState && state._currentStep?.type === 'next_step_interruption';
const invocationSpans = useTaskAndTurnSpans
? startRunnerInvocationSpans({
name: taskSpanName,
agent: state._currentAgent,
restoredAgentSpan: isResumedState
? state._currentAgentSpan
: undefined,
resumeInterruptedTurn: resumingInterruptedTurn,
parent: invocationSpanParent,
})
: undefined;
const taskSpan = invocationSpans?.taskSpan;
const optOutResumeAgentSpan = resumingInterruptedTurn && !useTaskAndTurnSpans
? ensureActiveAgentSpanForInterruptedResume({
agent: state._currentAgent,
restoredAgentSpan: isResumedState
? state._currentAgentSpan
: undefined,
parent: invocationSpanParent ?? getCurrentTrace() ?? undefined,
})
: undefined;
if (useTaskAndTurnSpans && isResumedState) {
state.setCurrentAgentSpan(invocationSpans?.agentSpan);
}
else if (optOutResumeAgentSpan) {
state.setCurrentAgentSpan(optOutResumeAgentSpan);
}
// Tracks when we resume an approval interruption so the next run-again step stays in the same turn.
let continuingInterruptedTurn = false;
let runError;
const attemptedRunErrorHandlers = new WeakSet();
let currentTurnSpan;
let turnPendingModelRequest;
let guardrailTracker = createGuardrailTracker();
const parentUsageRecorder = getRunnerParentUsageRecorder(this);
const recordUsage = (usage) => {
recordRunnerSpanUsage(taskSpan, usage);
recordRunnerSpanUsage(currentTurnSpan, usage);
parentUsageRecorder?.(usage);
};
setRunStateUsageRecorder(state, recordUsage);
let completedResult;
let persistenceCheckpoint;
let cancelledOutputRedacted = false;
const resumedStateHasPersistedToolOutput = isResumedState && hasPersistedToolOutput(state);
let approvedToolCheckpointCompacted = pendingSessionWriteReconciled ||
state._currentTurnSessionWriteCompactedItemCount ===
state._currentTurnPersistedItemCount ||
(resumedStateHasPersistedToolOutput &&
!hasPendingApprovedToolInputCompaction(state));
let approvedToolCheckpointRequiresLocalInputCompaction = resumedStateHasPersistedToolOutput;
let approvedToolCheckpointModelResponseCount = state._modelResponses.length;
let approvedToolFinalSessionWritePreparation;
let deferredApprovedToolFinalSessionWritePreparation;
let completedResultPersisted = false;
const completeResult = (result) => {
completedResult = result;
return result;
};
const persistNonStreamingResult = async (result, overrideOptions) => {
if (this.#shouldDeferInterruptedSessionItems(result.state)) {
return;
}
const hasUnpersistedItems = result.newItems.length > state._currentTurnPersistedItemCount ||
(overrideOptions?.additionalRunItems?.length ?? 0) > 0;
const modelResponseAdvanced = result.rawResponses.length > approvedToolCheckpointModelResponseCount;
const compactionOptions = approvedToolCheckpointRequiresLocalInputCompaction
? approvedToolCheckpointCompacted &&
!hasUnpersistedItems &&
!modelResponseAdvanced
? { runCompaction: false }
: { compactionMode: 'input' }
: undefined;
const persistenceOptions = {
...compactionOptions,
...overrideOptions,
};
const finalSessionWritePreparation = approvedToolFinalSessionWritePreparation;
await persistResult?.(result, {
...persistenceOptions,
resumedSessionWritePreparation: finalSessionWritePreparation ??
persistenceOptions.resumedSessionWritePreparation,
});
if (finalSessionWritePreparation) {
approvedToolFinalSessionWritePreparation = undefined;
if (persistenceOptions.deferResumedSessionWrite !== true) {
approvedToolCheckpointCompacted = true;
approvedToolCheckpointModelResponseCount =
result.rawResponses.length;
}
}
};
const activateDeferredApprovedToolFinalSessionWrite = () => {
const preparation = deferredApprovedToolFinalSessionWritePreparation;
if (!preparation) {
return;
}
if (state._pendingSessionHistoryTransaction !== undefined ||
state._currentTurnDeferredSessionItemIndexes.size > 0 ||
state._currentTurnBlockedSessionStartIndex !== undefined) {
deferredApprovedToolFinalSessionWritePreparation = undefined;
return;
}
if (!options.session || serverManagesConversation) {
throw new UserError('Resumed Session write preparation has no ordinary Session owner.');
}
deferredApprovedToolFinalSessionWritePreparation = undefined;
approvedToolFinalSessionWritePreparation = preparation;
};
const recordNonStreamingError = (error) => {
if (state._currentAgentSpan) {
state._currentAgentSpan.setError({
message: 'Error in agent run',
data: {
error: getRunnerSpanErrorDetails(error, this.config.traceIncludeSensitiveData),
},
});
}
setRunnerSpanError(currentTurnSpan, error, this.config.traceIncludeSensitiveData);
setRunnerSpanError(taskSpan, error, this.config.traceIncludeSensitiveData);
runError = error;
};
const finalizeCurrentOutput = async (preparedErrorOutput) => {
const currentStep = state._currentStep;
if (currentStep?.type !== 'next_step_final_output') {
throw new ModelBehaviorError('Expected a final output step while finalizing the run.', state);
}
markAcceptedResponseFinalizationStarted(state);
normalizeSerializedOutputGuardrailBlockedMessage(state);
const resolveBlockedMessage = createOutputGuardrailBlockedMessageResolver(outputGuardrailBlockedMessage, state);
const outputGuardrailResultStart = state._outputGuardrailResults.length;
let outputGuardrailBlocked = false;
try {
await finalizeOutputGuardrails({
state,
runnerOutputGuardrails: this.outputGuardrailDefs,
output: currentStep.output,
redactedOutput: OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT,
guardedTerminalToolOutput: hasTerminalToolOutputSource(state),
signal: options.signal,
sanitizeRejectedOutput: (...args) => sanitizeBlockedTerminalToolOutput(...args, resolveBlockedMessage, options.signal, () => {
outputGuardrailBlocked = true;
}),
persistBlockedOutput: persistResult
? async () => persistResult(new RunResult(state), {
outputBlocked: true,
})
: undefined,
persistUnblockedFailure: async () => {
activateDeferredApprovedToolFinalSessionWrite();
await persistNonStreamingResult(new RunResult(state));
completedResultPersisted = true;
},
onBlockedOutput: () => {
outputGuardrailBlocked = true;
},
releaseBlockedOutputPersistence: releaseUnusedSessionHistoryTransactionBinding,
});
}
catch (error) {
if (!outputGuardrailBlocked &&
(deferredApprovedToolFinalSessionWritePreparation !== undefined ||
state._pendingSessionWrite !== undefined ||
approvedToolCheckpointCompacted)) {
state._outputGuardrailResults.splice(outputGuardrailResultStart);
if (deferredApprovedToolFinalSessionWritePreparation !== undefined &&
state._pendingSessionWrite === undefined) {
try {
activateDeferredApprovedToolFinalSessionWrite();
await persistNonStreamingResult(new RunResult(state), { deferResumedSessionWrite: true });
}
catch (checkpointError) {
if (state._pendingSessionWrite === undefined) {
throw checkpointError;
}
}
}
}
throw error;
}
if (state._serializedCurrentStep === currentStep &&
hasRetainableBlockedOutputEffect(state)) {
releaseProvisionalSessionHistoryTransactionBinding(state, provisionalSessionHistoryTransactionSessionId, provisionalSessionHistoryTransactionInputItems);
throw new UserError('Accepted final output cannot be resumed directly from serialized terminal state. Start a new run from persisted session history.');
}
activateDeferredApprovedToolFinalSessionWrite();
finishRunnerSpan(currentTurnSpan);
setRunStateTurnSpanParent(state, undefined);
currentTurnSpan = undefined;
const result = new RunResult(state);
try {
await persistNonStreamingResult(result, preparedErrorOutput?.deferredItem
? { additionalRunItems: [preparedErrorOutput.deferredItem] }
: undefined);
}
catch (error) {
state._outputGuardrailResults.splice(outputGuardrailResultStart);
commitDeferredRunErrorItemAfterPartialPersistence(state, preparedErrorOutput);
throw error;
}
if (preparedErrorOutput?.deferredItem) {
state._generatedItems.push(preparedErrorOutput.deferredItem);
}
completedResultPersisted = true;
state._currentTurnInProgress = false;
this.emit('agent_end', state._context, state._currentAgent, currentStep.output);
state._currentAgent.emit('agent_end', state._context, currentStep.output);
return completeResult(result);
};
try {
while (true) {
// if we don't have a current step, we treat this as a new run
state._currentStep = state._currentStep ?? {
type: 'next_step_run_again',
};
if (isAcceptedResponseCheckpoint(state)) {
this.#captureResponseGuardrailBoundary(state, false);
await resumeAcceptedModelResponse({
state,
runner: this,
toolErrorFormatter,
agentToolParentRunConfig,
signal: options.signal,
validateHandoffAgent: (handoffAgent) => {
this.#validateModelTimeoutForAgent(handoffAgent);
},
});
}
if (state._currentStep.type === 'next_step_interruption') {
await prepareSandboxInterruptedTurnResume({
startingAgent,
state,
sandboxRuntime,
runConfigModel: await this.#resolveSandboxRuntimeModelForAgent(state._currentAgent),
toolNameCollisionPolicy: options.toolNameCollisionPolicy,
tracingParent: getRunStateTurnSpanParent(state) ?? state._currentAgentSpan,
});
if (useTaskAndTurnSpans) {
currentTurnSpan = ensureTurnSpan(currentTurnSpan, state._currentTurn, state._currentAgent.name, state._currentAgentSpan);
setRunStateTurnSpanParent(state, currentTurnSpan.span);
}
const { interruptedOutcome, approvedToolCheckpointDeferred } = await (async () => {
let resumedSessionWritePreparation;
this.#captureResponseGuardrailBoundary(state, false);
const outcome = await resumeInterruptedTurn({
state,
runner: this,
toolErrorFormatter,
agentToolParentRunConfig,
signal: options.signal,
validateHandoffAgent: (handoffAgent) => {
this.#validateModelTimeoutForAgent(handoffAgent);
},
beforeResumedSideEffects: async () => {
if (persistResult &&
options.session &&
!serverManagesConversation) {
resumedSessionWritePreparation =
await prepareResumedSessionWrite(options.session, state);
}
return resumedSessionWritePreparation;
},
});
const checkpointDeferred = outcome.resumedSideEffects &&
outcome.nextStep.type === 'next_step_final_output' &&
this.#agentHasOutputGuardrail(state._currentAgent);
if (outcome.resumedSideEffects) {
const approvedToolResult = new RunResult(state);
approvedToolCheckpointRequiresLocalInputCompaction = true;
if (persistResult &&
!this.#shouldDeferInterruptedSessionItems(state)) {
if (options.session &&
!serverManagesConversation &&
!resumedSessionWritePreparation) {
throw new UserError('Resumed Session write preparation is missing after approved tool execution.');
}
if (checkpointDeferred) {
deferredApprovedToolFinalSessionWritePreparation =
resumedSessionWritePreparation;
}
else if (outcome.nextStep.type === 'next_step_final_output') {
approvedToolFinalSessionWritePreparation =
resumedSessionWritePreparation;
}
if (outcome.nextStep.type !== 'next_step_final_output') {
await persistResult(approvedToolResult, {
compactionMode: 'input',
resumedSessionWritePreparation,
});
approvedToolCheckpointCompacted = true;
approvedToolCheckpointModelResponseCount =
approvedToolResult.rawResponses.length;
}
else {
approvedToolFinalSessionWritePreparation ??=
checkpointDeferred
? undefined
: resumedSessionWritePreparation;
}
}
}
return {
interruptedOutcome: outcome,
approvedToolCheckpointDeferred: checkpointDeferred,
};
})();
if (options.signal?.aborted && !approvedToolCheckpointDeferred) {
persistenceCheckpoint = new RunResult(state);
}
options.signal?.throwIfAborted();
// Don't reset counter here - resolveInterruptedTurn already adjusted it via rewind logic
// The counter will be reset when _currentTurn is incremented (starting a new turn)
const { shouldReturn, shouldContinue } = handleInterruptedOutcome({
state,
outcome: interruptedOutcome,
setContinuingInterruptedTurn: (value) => {
continuingInterruptedTurn = value;
},
});
// Approval resolution belongs to the previous turn; any next model call
// starts a new counted turn with its own span and usage.
finishRunnerSpan(currentTurnSpan);
setRunStateTurnSpanParent(state, undefined);
currentTurnSpan = undefined;
if (shouldReturn) {
// we are still in an interruption, so we need to avoid an infinite loop
return completeResult(new RunResult(state));
}
if (shouldContinue) {
continue;
}
}
if (state._currentStep.type === 'next_step_run_again') {
this.#validateModelTimeoutForAgent(state._currentAgent);
if (approvedToolCheckpointCompacted &&
state._currentTurnSessionHistoryTransactionSessionId === undefined) {
await prepareSessionHistoryTransactionsForRun(options.session, state, { serverManagesConversation: false });
}
const wasContinuingInterruptedTurn = continuingInterruptedTurn;
continuingInterruptedTurn = false;
guardrailTracker = createGuardrailTracker();
const previousTurn = state._currentTurn;
turnPendingModelRequest = {
currentTurn: previousTurn,
currentTurnInProgress: state._currentTurnInProgress,
};
const previousPersistedCount = state._currentTurnPersistedItemCount;
const previousGeneratedCount = state._generatedItems.length;
const { turnInput, pendingInputItems, pendingInputSourceItems } = await prepareTurn({
state,
input: state._originalInput,
generatedItems: state._generatedItems,
isResumedState,
preserveTurnPersistenceOnResume,
continuingInterruptedTurn: wasContinuingInterruptedTurn,
serverConversationTracker,
inputGuardrailDefs: this.inputGuardrailDefs,
guardrailHandlers: {
onParallelPromise: guardrailTracker.setPromise,
onParallelError: guardrailTracker.setError,
},
emitAgentStart: (context, agent, inputItems) => {
this.emit('agent_start', context, agent, inputItems);
},
onAgentSpanReady: useTaskAndTurnSpans
? (turn, agentName) => {
currentTurnSpan = ensureTurnSpan(currentTurnSpan, turn, agentName, state._currentAgentSpan);
setRunStateTurnSpanParent(state, currentTurnSpan.span);
}
: undefined,
agentSpanParent: taskSpan?.span ?? invocationSpanParent,
});
if (preserveTurnPersistenceOnResume &&
state._currentTurn > previousTurn &&
previousPersistedCount <= previousGeneratedCount) {
// Preserve persisted offsets from a resumed run to avoid re-saving prior items.
state._currentTurnPersistedItemCount = previousPersistedCount;
}
const sessionPreparedTurnInput = [...turnInput];
const preparedSandboxAgent = await sandboxRuntime.prepareAgent({
currentAgent: state._currentAgent,
turnInput,
runConfigModel: await this.#resolveSandboxRuntimeModelForAgent(state._currentAgent),
tracingParent: currentTurnSpan?.span ?? state._currentAgentSpan,
});
sessionTurnInputUpdate?.(sessionPreparedTurnInput, preparedSandboxAgent.turnInput);
const processedPendingInputItems = mapPendingInputAfterContextProcessing(pendingInputItems, sessionPreparedTurnInput, preparedSandboxAgent.turnInput);
const artifacts = await prepareAgentArtifacts(state, preparedSandboxAgent.executionAgent, options.toolNameCollisionPolicy);
const preparedCall = await this.#prepareModelCall(state, preparedSandboxAgent.executionAgent, options, artifacts, preparedSandboxAgent.turnInput, serverConversationTracker, sessionInputUpdate);
captureSessionHistoryTransactionInputItems(options.session, state, getSessionInputForPersistence?.());
await guardrailTracker.throwIfError();
const requiresPendingInputAdmissionCheckpoint = pendingInputItems.length > 0 ||
(!serverConversationTracker && hasUnpersistedRunInput(state));
if (requiresPendingInputAdmissionCheckpoint) {
options.signal?.throwIfAborted();
}
const admittedPendingInput = selectPendingInputForAdmission(processedPendingInputItems, preparedCall);
let localPendingInputCommitted = false;
const commitLocalPendingInput = async () => {
if (serverConversationTracker || localPendingInputCommitted) {
return;
}
localPendingInputCommitted = true;
commitPendingInput(state, pendingInputItems, admittedPendingInput, pendingInputSourceItems);
if (hasUnpersistedRunInput(state)) {
await saveToSession(options.session, undefined, new RunResult(state), { runCompaction: false });
}
if (requiresPendingInputAdmissionCheckpoint) {
options.signal?.throwIfAborted();
}
};
const deferLocalPendingInputAdmission = !serverConversationTracker &&
pendingInputItems.length > 0 &&
guardrailTracker.pending;
if (!deferLocalPendingInputAdmission) {
await commitLocalPendingInput();
}
const modelRequest = {
systemInstructions: preparedCall.modelInput.instructions,
prompt: preparedCall.prompt,
// Explicit agent/run config models should take precedence over prompt defaults.
...(preparedCall.explicitlyModelSet
? { overridePromptModel: true }
: {}),
input: preparedCall.modelInput.input,
previousResponseId: preparedCall.previousResponseId,
conversationId: preparedCall.conversationId,
modelSettings: preparedCall.modelSettings,
_internal: preparedCall.modelRequestInternal,
tools: preparedCall.serializedTools,
toolsExplicitlyProvided: preparedCall.toolsExplicitlyProvided,
outputType: convertAgentOutputTypeToSerializable(state._currentAgent.outputType),
handoffs: preparedCall.serializedHandoffs,
tracing: getTracing(this.config.tracingDisabled, this.config.traceIncludeSensitiveData),
signal: options.signal,
};
turnPendingModelRequest = undefined;
let serverInputMarked = false;
const markServerInputAccepted = (responseAvailable = true) => {
if (serverInputMarked || !serverConversationTracker) {
return;
}
serverConversationTracker.markInputAsSent(preparedCall.filterApplied
? preparedCall.sourceItems
: preparedCall.turnInput, {
filterApplied: preparedCall.filterApplied,
allTurnItems: preparedCall.turnInput,
});
commitPendingInput(state, pendingInputItems, admittedPendingInput, pendingInputSourceItems);
if (pendingInputItems.length > 0) {
if (admittedPendingInput.length > 0) {
serverConversationTracker.markInputAsSent(admittedPendingInput.map((item) => item.rawItem));
}
}
if (pendingInputItems.length > 0 || !responseAvailable) {
if (!responseAvailable) {
state._lastTurnResponse = undefined;
}
state._lastProcessedResponse = undefined;
state._currentStep = {
type: 'next_step_interruption',
data: { interruptions: [], responseAccepted: true },
};
}
serverInputMarked = true;
};
const completeCompactionExchange = prepareSessionCompactionExchange(options.session, state, preparedCall.modelInput.input);
const pendingModelResponse = getResponseWithRetry(preparedCall.model, modelRequest, {
onModelFailureUsage: (usage) => recordModelFailureUsage(usage, state, recordUsage),
...(serverConversationTracker
? {
onPossiblyAcceptedRequestFailure: () => markServerInputAccepted(false),
}
: {}),
});
if (deferLocalPendingInputAdmission) {
const modelResponseOutcome = pendingModelResponse.then((response) => ({ status: 'fulfilled', response }), (error) => ({ status: 'rejected', error }));
try {
await guardrailTracker.awaitCompletion();
await commitLocalPendingInput();
}
catch (error) {
// The request already started in parallel, so drain it before
// surfacing a guardrail or persistence failure.
await modelResponseOutcome;
throw error;
}
const outcome = await modelResponseOutcome;
if (outcome.status === 'rejected') {
throw outcome.error;
}
state._lastTurnResponse = outcome.response;
}
else {
state._lastTurnResponse = await pendingModelResponse;
}
if (serverConversationTracker) {
markServerInputAccepted();
}
completeCompactionExchange(state._lastTurnResponse.output, state._lastTurnResponse.responseId);
state._modelResponses.push(state._lastTurnResponse);
state._context.usage.add(state._lastTurnResponse.usage);
recordUsage(state._lastTurnResponse.usage);
state._noActiveAgentRun = false;
// After each turn record the items echoed by the server so future requests only
// include the incremental inputs that have not yet been acknowledged.
serverConversationTracker?.trackServerItems(state._lastTurnResponse);
if (serverConversationTracker) {
state.setConversationContext(serverConversationTracker.conversationId, serverConversationTracker.previousResponseId);
}
const processedResponse = await processModelResponseAsync(state._lastTurnResponse, state._currentAgent, preparedCall.tools, preparedCall.handoffs, state,
// Loading authority retains the agent ownership lost in model input.
[
...(Array.isArray(state._originalInput)
? state._originalInput
: []),
...state._generatedItems,
], options.toolNotFoundBehavior, {
allowPromptSuppliedTools: preparedCall.allowPromptSuppliedTools,
beforeClientToolSearch: () => preflightModelResponseToolInvocations(state._currentAgent, state, state._lastTurnResponse, preparedCall.tools, preparedCall.handoffs, this, options.signal),
});
serverConversationTracker?.trackServerItems(state._lastTurnResponse, processedResponse.newItems);
state._lastProcessedResponse = processedResponse;
trackDeferredToolRecovery(state, state._currentAgent, processedResponse);
const suppressedToolCalls = preflightToolInvocations(state._currentAgent, state, processedResponse);
await guardrailTracker.awaitCompletion();
markAcceptedResponseProcessingStarted(state);
this.#captureResponseGuardrailBoundary(state, true);
const turnResult = await resolveTurnAfterModelResponse(state._currentAgent, state._originalInput, state._generatedItems, state._lastTurnResponse, state._lastProcessedResponse, this, state, toolErrorFormatter, agentToolParentRunConfig, options.errorHandlers, options.signal, suppressedToolCalls, attemptedRunErrorHandlers, (handoffAgent) => this.#validateModelTimeoutForAgent(handoffAgent));
if (turnResult.nextStep.type === 'next_step_handoff') {
this.#validateModelTimeoutForAgent(turnResult.nextStep.newAgent);
}
applyTurnResult({
state,
turnResult,
agent: state._currentAgent,
toolsUsed: state._lastProcessedResponse?.toolsUsed ?? [],
resetTurnPersistence: !isResumedState,
});
if (options.signal?.aborted) {
const guardedFinalOutput = turnResult.nextStep.type === 'next_step_final_output' &&
this.#agentHasOutputGuardrail(state._currentAgent);
if (guardedFinalOutput && !isResumedState) {
// Ordinary callers cannot recover the runner-owned state from
// the original abort reason. Keep a replay-safe completion
// record without publishing the unchecked tool output.
redactCancelledResponseToolOutputs(state, 'Tool output discarded because the run was cancelled before output validation.');
cancelledOutputRedacted = true;
}
if (!guardedFinalOutput || cancelledOutputRedacted) {
persistenceCheckpoint = new RunResult(state);
}
// Caller-owned RunState retains the candidate for validation on resume.
}
options.signal?.throwIfAborted();
if (turnResult.nextStep.type !== 'next_step_final_output') {
finishRunnerSpan(currentTurnSpan);
setRunStateTurnSpanParent(state, undefined);
currentTurnSpan = undefined;
}
}
const currentStep = state._currentStep;
if (!currentStep) {
logger.debug('Running next loop');
continue;
}
switch (currentStep.type) {
case 'next_step_final_output':
if (options.signal?.aborted) {
releaseProvisionalSessionHistoryTransactionBinding(state, provisionalSessionHistoryTransactionSessionId, provisionalSessionHistoryTransactionInputItems);
}
options.signal?.throwIfAborted();
return await finalizeCurrentOutput();
case 'next_step_handoff':
this.#validateModelTimeoutForAgent(currentStep.newAgent);
state.setCurrentAgent(currentStep.newAgent);
if (state._currentAgentSpan) {
state._currentAgentSpan.end();
resetCurrentSpan();
state.setCurrentAgentSpan(undefined);
}
state._noActiveAgentRun = true;
state._currentTurnInProgress = false;
// We've processed the handoff, so we need to run the loop again.
state._currentStep = { type: 'next_step_run_again' };
break;
case 'next_step_interruption':
// Interrupted. Don't run any guardrails.
return completeResult(new RunResult(state));
case 'next_step_run_again':
state._currentTurnInProgress = false;
logger.debug('Running next loop');
break;
default:
logger.debug('Running next loop');
}
}
}
catch (caughtError) {
if (guardrailTracker.pending) {
await guardrailTracker.awaitCompletion({ suppressErrors: true });
}
const err = guardrailTracker.failed
? guardrailTracker.error
: caughtError;
if (guardrailTracker.failed) {
invalidateAcceptedResponseReplayEvidence(state);
}
const restoredPendingTurn = rollbackUnstartedTurn(state, turnPendingModelRequest);
turnPendingModelRequest = undefined;
if (!restoredPendingTurn) {
state._currentTurnInProgress = false;
}
attachRunStateToError(err, state);
releaseUnusedSessionHistoryTransactionBinding(state);
const errorHandled = state._pendingSessionWrite
? undefined
: await prepareRunErrorFinalOutput({
error: err,
state,
errorHandlers: options.errorHandlers,
responseAccepted: isAcceptedResponseCheckpoint(state),
attemptedErrors: attemptedRunErrorHandlers,
});
if (errorHandled) {
try {
return await finalizeCurrentOutput(errorHandled);
}
catch (finalizationError) {
recordNonStreamingError(finalizationError);
throw finalizationError;
}
}
recordNonStreamingError(err);
throw err;
}
finally {
finishRunnerSpan(currentTurnSpan);
setRunStateTurnSpanParent(state, undefined);
const preserveSandboxSessions = state._currentStep?.type === 'next_step_interruption';
try {
try {
await finalizeSandboxRuntime({
state: state,
sandboxRuntime,
preserveSessionsForInterruption: preserveSandboxSessions,
finishAgentSpanForInterruption: Boolean(taskSpan) || runError !== undefined,
runError,
groupId: this.config.groupId,
memoryContext: sandboxMemoryRunContext,
runAgent: async (agent, input, runOptions) => await this.run(agent, input, runOptions),
tracingParent: taskSpan?.span ??
state._currentAgentSpan ??
invocationSpanParent,
});
}
catch (error) {
setRunnerSpanError(taskSpan, error, this.config.traceIncludeSensitiveData);
await Promise.reject(error);
}
const resultToPersist = completedResult ?? persistenceCheckpoint;
if (resultToPersist && !completedResultPersisted) {
try {
await persistNonStreamingResult(resultToPersist,
// The current response is already redacted; append the full
// suffix so earlier completed turns remain replayable.
cancelledOutputRedacted ? { runCompaction: false } : undefined);
}
catch (error) {
setRunnerSpanError(taskSpan, error, this.config.traceIncludeSensitiveData);
await Promise.reject(error);
}
}
}
finally {
finishRunnerSpan(taskSpan);
}
}
});
}
/**
* @internal
*/
async #runStreamLoop(result, startingAgent, sandboxRuntime, options, isResumedState, ensureStreamInputPersisted, sessionTurnInputUpdate, sessionInputUpdate, getStreamInputForPersistence, preserveTurnPersistenceOnResume, provisionalSessionHistoryTransactionSessionId, provisionalSessionHistoryTransactionInputItems, sandboxMemoryRunContext, taskSpan, invocationSpanParent, pendingSessionWriteReconciled = false) {
const resolvedReasoningItemIdPolicy = options.reasoningItemIdPolicy ??
(isResumedState ? result.state._reasoningItemIdPolicy : undefined) ??
this.config.reasoningItemIdPolicy;
result.state.setReasoningItemIdPolicy(resolvedReasoningItemIdPolicy);
const resolvedConversationId = options.conversationId ?? result.state._conversationId;
const resolvedPreviousResponseId = options.previousResponseId ?? result.state._previousResponseId;
const serverManagesConversation = Boolean(resolvedConversationId) || Boolean(resolvedPreviousResponseId);
assertPendingInputServerOwnership(result.state, resolvedConversationId, resolvedPreviousResponseId);
const serverConversationTracker = serverManagesConversation
? new ServerConversationTracker({
conversationId: resolvedConversationId,
previousResponseId: resolvedPreviousResponseId,
reasoningItemIdPolicy: resolvedReasoningItemIdPolicy,
})
: undefined;
if (serverConversationTracker) {
result.state.setConversationContext(serverConversationTracker.conversationId, serverConversationTracker.previousResponseId);
}
let sentInputToModel = false;
let streamInputPersisted = false;
let guardrailTracker = createGuardrailTracker();
const persistStreamInputIfNeeded = async () => {
if (streamInputPersisted || !ensureStreamInputPersisted) {
return;
}
// Both success and error paths call this helper, so guard against multiple writes.
await ensureStreamInputPersisted();
streamInputPersisted = true;
markSessionHistoryTransactionInputPersisted(result.state);
};
const awaitInputGuardrails = async () => {
await guardrailTracker.awaitCompletion();
if (guardrailTracker.failed) {
throw guardrailTracker.error;
}
};
if (serverConversationTracker && isResumedState) {
serverConversationTracker.primeFromState({
originalInput: result.state._originalInput,
generatedItems: result.state._generatedItems,
modelResponses: result.state._modelResponses,
});
result.state.setConversationContext(serverConversationTracker.conversationId, serverConversationTracker.previousResponseId);
}
const toolErrorFormatter = options.toolErrorFormatter ?? this.config.toolErrorFormatter;
const outputGuardrailBlockedMessage = options.outputGuardrailBlockedMessage === undefined
? this.config.outputGuardrailBlockedMessage
: options.outputGuardrailBlockedMessage;
const agentToolParentRunConfig = this.#getAgentToolParentRunConfig(options);
const useTaskAndTurnSpans = !this.config.tracingDisabled && includeTaskAndTurnSpans(options.tracing);
// Tracks when we resume an approval interruption so the next run-again step stays in the same turn.
let continuingInterruptedTurn = false;
let runError;
const attemptedRunErrorHandlers = new WeakSet();
let suppressStreamInputPersistence = false;
const resumedStateHasPersistedToolOutput = isResumedState && hasPersistedToolOutput(result.state);
let approvedToolCheckpointCompacted = pendingSessionWriteReconciled ||
result.state._currentTurnSessionWriteCompactedItemCount ===
result.state._currentTurnPersistedItemCount ||
(resumedStateHasPersistedToolOutput &&
!hasPendingApprovedToolInputCompaction(result.state));
let approvedToolCheckpointRequiresLocalInputCompaction = resumedStateHasPersistedToolOutput;
let approvedToolCheckpointModelResponseCount = result.rawResponses.length;
let approvedToolFinalSessionWritePreparation;
let deferredApprovedToolFinalSessionWritePreparation;
let currentTurnSpan;
let turnPendingModelRequest;
let commitDeferredLocalPendingInput;
const parentUsageRecorder = getRunnerParentUsageRecorder(this);
const recordUsage = (usage) => {
recordRunnerSpanUsage(taskSpan, usage);
recordRunnerSpanUsage(currentTurnSpan, usage);
parentUsageRecorder?.(usage);
};
setRunStateUsageRecorder(result.state, recordUsage);
const saveStreamResultWithCompactionOwnership = async (overrideOptions) => {
if (this.#shouldDeferInterruptedSessionItems(result.state)) {
return;
}
const hasUnpersistedItems = result.newItems.length > result.state._currentTurnPersistedItemCount ||
(overrideOptions?.additionalRunItems?.length ?? 0) > 0;
const modelResponseAdvanced = result.rawResponses.length > approvedToolCheckpointModelResponseCount;
const compactionOptions = approvedToolCheckpointRequiresLocalInputCompaction
? approvedToolCheckpointCompacted &&
!hasUnpersistedItems &&
!modelResponseAdvanced
? { runCompaction: false }
: { compactionMode: 'input' }
: undefined;
const persistenceOptions = {
...compactionOptions,
...overrideOptions,
};
const finalSessionWritePreparation = approvedToolFinalSessionWritePreparation;
const sessionInputItems = streamInputPersisted
? undefined
: getStreamInputForPersistence?.();
// A remote session may commit addItems before rejecting, and compaction happens after the
// append. Once the combined save starts, retrying the input is therefore unsafe.
streamInputPersisted = true;
await saveStreamResultToSession(options.session, result, {
...persistenceOptions,
resumedSessionWritePreparation: finalSessionWritePreparation ??
persistenceOptions.resumedSessionWritePreparation,
}, sessionInputItems);
if (finalSessionWritePreparation) {
approvedToolFinalSessionWritePreparation = undefined;
if (persistenceOptions.deferResumedSessionWrite !== true) {
approvedToolCheckpointCompacted = true;
approvedToolCheckpointModelResponseCount = result.rawResponses.length;
}
}
};
const activateDeferredApprovedToolFinalSessionWrite = () => {
const preparation = deferredApprovedToolFinalSessionWritePreparation;
if (!preparation) {
return;
}
if (result.state._pendingSessionHistoryTransaction !== undefined ||
result.state._currentTurnDeferredSessionItemIndexes.size > 0 ||
result.state._currentTurnBlockedSessionStartIndex !== undefined) {
deferredApprovedToolFinalSessionWritePreparation = undefined;
return;
}
if (!options.session || serverManagesConversation) {
throw new UserError('Resumed Session write preparation has no ordinary Session owner.');
}
deferredApprovedToolFinalSessionWritePreparation = undefined;
approvedToolFinalSessionWritePreparation = preparation;
};
const recordStreamingError = (error) => {
if (result.state._currentAgentSpan) {
result.state._currentAgentSpan.setError({
message: 'Error in agent run',
data: {
error: getRunnerSpanErrorDetails(error, this.config.traceIncludeSensitiveData),
},
});
}
setRunnerSpanError(currentTurnSpan, error, this.config.traceIncludeSensitiveData);
setRunnerSpanError(taskSpan, error, this.config.traceIncludeSensitiveData);
runError = error;
};
const finalizeStreamOutput = async (preparedErrorOutput) => {
const currentStep = result.state._currentStep;
if (currentStep?.type !== 'next_step_final_output') {
throw new ModelBehaviorError('Expected a final output step while finalizing the run.', result.state);
}
markAcceptedResponseFinalizationStarted(result.state);
result._hideFinalOutput();
normalizeSerializedOutputGuardrailBlockedMessage(result.state);
const resolveBlockedMessage = createOutputGuardrailBlockedMessageResolver(outputGuardrailBlockedMessage, result.state);
const outputGuardrailResultStart = result.state._outputGuardrailResults.length;
let outputGuardrailBlocked = false;
try {
await finalizeOutputGuardrails({
state: result.state,
runnerOutputGuardrails: this.outputGuardrailDefs,
output: currentStep.output,
redactedOutput: OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT,
guardedTerminalToolOutput: hasTerminalToolOutputSource(result.state),
signal: options.signal,
sanitizeRejectedOutput: (...args) => sanitizeBlockedTerminalToolOutput(...args, resolveBlockedMessage, options.signal, () => {
outputGuardrailBlocked = true;
}),
persistBlockedOutput: !serverManagesConversation
? async () => saveStreamResultWithCompactionOwnership({
outputBlocked: true,
})
: undefined,
persistUnblockedFailure: !serverManagesConversation
? async () => {
activateDeferredApprovedToolFinalSessionWrite();
await saveStreamResultWithCompactionOwnership();
}
: undefined,
onBlockedOutput: () => {
outputGuardrailBlocked = true;
},
releaseBlockedOutputPersistence: releaseUnusedSessionHistoryTransactionBinding,
});
}
catch (error) {
if (!outputGuardrailBlocked &&
(deferredApprovedToolFinalSessionWritePreparation !== undefined ||
result.state._pendingSessionWrite !== undefined ||
approvedToolCheckpointCompacted)) {
result.state._outputGuardrailResults.splice(outputGuardrailResultStart);
if (deferredApprovedToolFinalSessionWritePreparation !== undefined &&
result.state._pendingSessionWrite === undefined) {
try {
activateDeferredApprovedToolFinalSessionWrite();
await saveStreamResultWithCompactionOwnership({
deferResumedSessionWrite: true,
});
}
catch (checkpointError) {
if (result.state._pendingSessionWrite === undefined) {
throw checkpointError;
}
}
}
}
throw error;
}
if (result.state._serializedCurrentStep === currentStep &&
hasRetainableBlockedOutputEffect(result.state)) {
releaseProvisionalSessionHistoryTransactionBinding(result.state, provisionalSessionHistoryTransactionSessionId, provisionalSessionHistoryTransactionInputItems);
throw new UserError('Accepted final output cannot be resumed directly from serialized terminal state. Start a new run from persisted session history.');
}
activateDeferredApprovedToolFinalSessionWrite();
finishRunnerSpan(currentTurnSpan);
setRunStateTurnSpanParent(result.state, undefined);
currentTurnSpan = undefined;
result.state._currentTurnInProgress = false;
// Guardrails must succeed before persisting session memory to avoid storing blocked outputs.
if (!serverManagesConversation) {
try {
await saveStreamResultWithCompactionOwnership(preparedErrorOutput?.deferredItem
? { additionalRunItems: [preparedErrorOutput.deferredItem] }
: undefined);
}
catch (error) {
result.state._outputGuardrailResults.splice(outputGuardrailResultStart);
const itemCommitted = commitDeferredRunErrorItemAfterPartialPersistence(result.state, preparedErrorOutput);
if (itemCommitted && preparedErrorOutput?.deferredItem) {
streamStepItemsToRunResult(result, [
preparedErrorOutput.deferredItem,
]);
recordStreamingError(error);
result._raiseError(error, { preserveQueuedItems: true });
return;
}
throw error;
}
}
if (preparedErrorOutput?.deferredItem) {
result.state._generatedItems.push(preparedErrorOutput.deferredItem);
}
if (preparedErrorOutput?.deferredItem) {
streamStepItemsToRunResult(result, [preparedErrorOutput.deferredItem]);
result._preserveQueuedItemsOnError();
}
result._revealFinalOutput();
this.emit('agent_end', result.state._context, result.state._currentAgent, currentStep.output);
result.state._currentAgent.emit('agent_end', result.state._context, currentStep.output);
};
try {
while (true) {
// Let the current action batch settle, but never start new work after
// cancellation once a turn has already begun. Preserve the existing
// first-request behavior for an initially aborted stream.
if (result.cancelled &&
(result.state._currentTurn > 0 ||
result.state._currentStep?.type === 'next_step_final_output')) {
if (result.state._currentStep?.type === 'next_step_final_output') {
releaseProvisionalSessionHistoryTransactionBinding(result.state, provisionalSessionHistoryTransactionSessionId, provisionalSessionHistoryTransactionInputItems);
}
return;
}
const currentAgent = result.state._currentAgent;
result.state._currentStep = result.state._currentStep ?? {
type: 'next_step_run_again',
};
if (isAcceptedResponseCheckpoint(result.state)) {
this.#captureResponseGuardrailBoundary(result.state, false);
await resumeAcceptedModelResponse({
state: result.state,
runner: this,
toolErrorFormatter,
agentToolParentRunConfig,
signal: options.signal,
validateHandoffAgent: (handoffAgent) => {
this.#validateModelTimeoutForAgent(handoffAgent);
},
onStepItems: (turnResult) => {
addStepToRunResult(result, turnResult);
},
});
}
if (result.state._currentStep.type === 'next_step_interruption') {
await prepareSandboxInterruptedTurnResume({
startingAgent,
state: result.state,
sandboxRuntime,
runConfigModel: await this.#resolveSandboxRuntimeModelForAgent(result.state._currentAgent),
toolNameCollisionPolicy: options.toolNameCollisionPolicy,
tracingParent: getRunStateTurnSpanParent(result.state) ??
result.state._currentAgentSpan,
});
if (useTaskAndTurnSpans) {
currentTurnSpan = ensureTurnSpan(currentTurnSpan, result.state._currentTurn, result.state._currentAgent.name, result.state._currentAgentSpan);
setRunStateTurnSpanParent(result.state, currentTurnSpan.span);
}
const interruptedOutcome = await (async () => {
let resumedSessionWritePreparation;
this.#captureResponseGuardrailBoundary(result.state, false);
const outcome = await resumeInterruptedTurn({
state: result.state,
runner: this,
toolErrorFormatter,
agentToolParentRunConfig,
signal: options.signal,
validateHandoffAgent: (handoffAgent) => {
this.#validateModelTimeoutForAgent(handoffAgent);
},
onStepItems: (turnResult) => {
addStepToRunResult(result, turnResult);
},
onHandoff: (agent) => {
result._addItem(new RunAgentUpdatedStreamEvent(agent));
},
beforeResumedSideEffects: async () => {
if (options.session && !serverManagesConversation) {
resumedSessionWritePreparation =
await prepareResumedSessionWrite(options.session, result.state);
}
return resumedSessionWritePreparation;
},
});
if (outcome.resumedSideEffects) {
const checkpointDeferred = outcome.nextStep.type === 'next_step_final_output' &&
this.#agentHasOutputGuardrail(result.state._currentAgent);
approvedToolCheckpointRequiresLocalInputCompaction = true;
if (!serverManagesConversation &&
options.session &&
!this.#shouldDeferInterruptedSessionItems(result.state)) {
if (!resumedSessionWritePreparation) {
throw new UserError('Resumed Session write preparation is missing after approved tool execution.');
}
if (checkpointDeferred) {
deferredApprovedToolFinalSessionWritePreparation =
resumedSessionWritePreparation;
}
else if (outcome.nextStep.type === 'next_step_final_output') {
approvedToolFinalSessionWritePreparation =
resumedSessionWritePreparation;
}
else {
await saveStreamResultToSession(options.session, result, {
compactionMode: 'input',
resumedSessionWritePreparation,
});
approvedToolCheckpointCompacted = true;
approvedToolCheckpointModelResponseCount =
result.rawResponses.length;
}
}
}
return outcome;
})();
// Don't reset counter here - resolveInterruptedTurn already adjusted it via rewind logic
// The counter will be reset when _currentTurn is incremented (starting a new turn)
const { shouldReturn, shouldContinue } = handleInterruptedOutcome({
state: result.state,
outcome: interruptedOutcome,
setContinuingInterruptedTurn: (value) => {
continuingInterruptedTurn = value;
},
});
// Approval resolution belongs to the previous turn; any next model call
// starts a new counted turn with its own span and usage.
finishRunnerSpan(currentTurnSpan);
setRunStateTurnSpanParent(result.state, undefined);
currentTurnSpan = undefined;
if (shouldReturn) {
// we are still in an interruption, so we need to avoid an infinite loop
return;
}
if (shouldContinue) {
continue;
}
}
if (result.state._currentStep.type === 'next_step_run_again') {
this.#validateModelTimeoutForAgent(result.state._currentAgent);
commitDeferredLocalPendingInput = undefined;
if (approvedToolCheckpointCompacted &&
result.state._currentTurnSessionHistoryTransactionSessionId ===
undefined) {
await prepareSessionHistoryTransactionsForRun(options.session, result.state, { serverManagesConversation: false });
}
guardrailTracker = createGuardrailTracker();
const wasContinuingInterruptedTurn = continuingInterruptedTurn;
continuingInterruptedTurn = false;
const previousTurn = result.state._currentTurn;
turnPendingModelRequest = {
currentTurn: previousTurn,
currentTurnInProgress: result.state._currentTurnInProgress,
};
const previousPersistedCount = result.state._currentTurnPersistedItemCount;
const previousGeneratedCount = result.state._generatedItems.length;
const preparedTurn = await prepareTurn({
state: result.state,
input: result.input,
generatedItems: result.newItems,
isResumedState,
preserveTurnPersistenceOnResume,
continuingInterruptedTurn: wasContinuingInterruptedTurn,
serverConversationTracker,
inputGuardrailDefs: this.inputGuardrailDefs,
guardrailHandlers: {
onParallelPromise: guardrailTracker.setPromise,
onParallelError: (err) => {
guardrailTracker.setError(err);
},
},
emitAgentStart: (context, agent, inputItems) => {
this.emit('agent_start', context, agent, inputItems);
},
onAgentSpanReady: useTaskAndTurnSpans
? (turn, agentName) => {
currentTurnSpan = ensureTurnSpan(currentTurnSpan, turn, agentName, result.state._currentAgentSpan);
setRunStateTurnSpanParent(result.state, currentTurnSpan.span);
}
: undefined,
agentSpanParent: taskSpan?.span ?? invocationSpanParent,
});
if (preserveTurnPersistenceOnResume &&
result.state._currentTurn > previousTurn &&
previousPersistedCount <= previousGeneratedCount) {
// Preserve persisted offsets from a resumed run to avoid re-saving prior items.
result.state._currentTurnPersistedItemCount =
previousPersistedCount;
}
const { turnInput, pendingInputItems, pendingInputSourceItems } = preparedTurn;
// If guardrails are still running, defer input persistence until they finish.
const sessionPreparedTurnInput = [...turnInput];
const preparedSandboxAgent = await sandboxRuntime.prepareAgent({
currentAgent: result.state._currentAgent,
turnInput,
runConfigModel: await this.#resolveSandboxRuntimeModelForAgent(result.state._currentAgent),
tracingParent: currentTurnSpan?.span ?? result.state._currentAgentSpan,
});
sessionTurnInputUpdate?.(sessionPreparedTurnInput, preparedSandboxAgent.turnInput);
const processedPendingInputItems = mapPendingInputAfterContextProcessing(pendingInputItems, sessionPreparedTurnInput, preparedSandboxAgent.turnInput);
const artifacts = await prepareAgentArtifacts(result.state, preparedSandboxAgent.executionAgent, options.toolNameCollisionPolicy);
const preparedCall = await this.#prepareModelCall(result.state, preparedSandboxAgent.executionAgent, options, artifacts, preparedSandboxAgent.turnInput, serverConversationTracker, sessionInputUpdate);
captureSessionHistoryTransactionInputItems(options.session, result.state, getStreamInputForPersistence?.());
await guardrailTracker.throwIfError();
// Once a logical turn is established, do not start another model
// request if cancellation arrives during asynchronous preparation.
if ((sentInputToModel || isResumedState) && result.cancelled) {
rollbackUnstartedTurn(result.state, turnPendingModelRequest);
turnPendingModelRequest = undefined;
return;
}
const admittedPendingInput = selectPendingInputForAdmission(processedPendingInputItems, preparedCall);
let localPendingInputCommitted = false;
const commitLocalPendingInput = async () => {
if (serverConversationTracker || localPendingInputCommitted) {
return;
}
localPendingInputCommitted = true;
commitPendingInput(result.state, pendingInputItems, admittedPendingInput, pendingInputSourceItems);
if (hasUnpersistedRunInput(result.state)) {
await saveStreamResultToSession(options.session, result, {
runCompaction: false,
});
}
};
const deferLocalPendingInputAdmission = !serverConversationTracker &&
pendingInputItems.length > 0 &&
guardrailTracker.pending;
if (deferLocalPendingInputAdmission) {
commitDeferredLocalPendingInput = async () => {
commitDeferredLocalPendingInput = undefined;
await commitLocalPendingInput();
};
}
else {
await commitLocalPendingInput();
if ((sentInputToModel || isResumedState) && result.cancelled) {
rollbackUnstartedTurn(result.state, turnPendingModelRequest);
turnPendingModelRequest = undefined;
return;
}
}
let finalResponse = undefined;
const abortReconciliationState = createStreamAbortReconciliationState();
let inputMarked = false;
let responseAcceptedCheckpointed = false;
let streamEventCount = 0;
let lastStreamEventType;
let timedOutModelCallFailed = false;
const markServerInputAccepted = (responseAvailable = true) => {
if (!serverConversationTracker) {
return;
}
if (!inputMarked) {
// Mark inputs after the first stream event, an explicit abort after
// request start, or provider advice that the failed request may have
// been accepted.
// Record the exact input that was sent so the server tracker can advance safely.
serverConversationTracker.markInputAsSent(preparedCall.filterApplied
? preparedCall.sourceItems
: preparedCall.turnInput, {
filterApplied: preparedCall.filterApplied,
allTurnItems: preparedCall.turnInput,
});
commitPendingInput(result.state, pendingInputItems, admittedPendingInput, pendingInputSourceItems);
if (pendingInputItems.length > 0) {
if (admittedPendingInput.length > 0) {
serverConversationTracker.markInputAsSent(admittedPendingInput.map((item) => item.rawItem));
}
}
inputMarked = true;
}
if (!responseAcceptedCheckpointed &&
(pendingInputItems.length > 0 || !responseAvailable)) {
if (!responseAvailable) {
result.state._lastTurnResponse = undefined;
}
result.state._lastProcessedResponse = undefined;
result.state._currentStep = {
type: 'next_step_interruption',
data: { interruptions: [], responseAccepted: true },
};
responseAcceptedCheckpointed = true;
}
};
const reconcileStreamAbortIfNeeded = async () => {
if (!serverConversationTracker ||
!shouldReconcileStreamAbort(abortReconciliationState)) {
return;
}
const reconciliationInput = buildAbortReconciliationInput(abortReconciliationState);
try {
const reconciliationResponse = await getResponseWithRetry(preparedCall.model, {
systemInstructions: preparedCall.modelInput.instructions,
prompt: preparedCall.prompt,
...(preparedCall.explicitlyModelSet
? { overridePromptModel: true }
: {}),
input: reconciliationInput,
previousResponseId: getAbortReconciliationPreviousResponseId(abortReconciliationState, preparedCall),
conversationId: preparedCall.conversationId,
modelSettings: preparedCall.modelSettings,
_internal: preparedCall.modelRequestInternal,
tools: preparedCall.serializedTools,
toolsExplicitlyProvided: preparedCall.toolsExplicitlyProvided,
handoffs: preparedCall.serializedHandoffs,
outputType: convertAgentOutputTypeToSerializable(currentAgent.outputType),
tracing: getTracing(this.config.tracingDisabled, this.config.traceIncludeSensitiveData),
}, {
onModelFailureUsage: (usage) => recordModelFailureUsage(usage, result.state, recordUsage),
});
markAbortReconciliationComplete(abortReconciliationState, reconciliationResponse);
result.state._context.usage.add(reconciliationResponse.usage);
recordUsage(reconciliationResponse.usage);
serverConversationTracker.trackServerItems(reconciliationResponse);
result.state.setConversationContext(serverConversationTracker.conversationId, serverConversationTracker.previousResponseId);
}
catch (error) {
logModelAndToolActionDebug(logger, 'Failed to reconcile streamed tool calls after abort.', error);
}
};
const modelRequest = {
systemInstructions: preparedCall.modelInput.instructions,
prompt: preparedCall.prompt,
// Streaming requests should also honor explicitly chosen models.
...(preparedCall.explicitlyModelSet
? { overridePromptModel: true }
: {}),
input: preparedCall.modelInput.input,
previousResponseId: preparedCall.previousResponseId,
conversationId: preparedCall.conversationId,
modelSettings: preparedCall.modelSettings,
_internal: preparedCall.modelRequestInternal,
tools: preparedCall.serializedTools,
toolsExplicitlyProvided: preparedCall.toolsExplicitlyProvided,
handoffs: preparedCall.serializedHandoffs,
outputType: convertAgentOutputTypeToSerializable(currentAgent.outputType),
tracing: getTracing(this.config.tracingDisabled, this.config.traceIncludeSensitiveData),
signal: options.signal,
};
// Publish the turn only after the complete request is constructed,
// immediately before the model request starts.
turnPendingModelRequest = undefined;
result.currentTurn = result.state._currentTurn;
sentInputToModel = true;
const completeCompactionExchange = prepareSessionCompactionExchange(options.session, result.state, preparedCall.modelInput.input);
try {
for await (const event of getStreamedResponseWithRetry(preparedCall.model, modelRequest, {
onModelFailureUsage: (usage) => recordModelFailureUsage(usage, result.state, recordUsage),
onModelTimeout: () => {
timedOutModelCallFailed = true;
},
...(serverConversationTracker
? {
onPossiblyAcceptedRequestFailure: () => markServerInputAccepted(false),
}
: {}),
})) {
streamEventCount++;
lastStreamEventType = event.type;
markServerInputAccepted(pendingInputItems.length === 0);
await guardrailTracker.throwIfError();
recordStreamEventForAbortReconciliation(abortReconciliationState, event);
if (event.type === 'response_done') {
assertValidCompactionItems(event.response.output);
let rawUsage;
if (modelRequest.modelSettings.preserveRawUsage === true) {
try {
rawUsage = snapshotRawUsage(event.response.rawUsage);
}
catch {
rawUsage = undefined;
}
}
const parsed = StreamEventResponseCompleted.parse({
type: event.type,
...(event.providerData
? { providerData: event.providerData }
: {}),
response: {
id: event.response.id,
requestId: event.response.requestId,
usage: event.response.usage,
output: event.response.output,
...(event.response.providerData
? { providerData: event.response.providerData }
: {}),
...(rawUsage !== undefined ? { rawUsage } : {}),
},
});
finalResponse = {
usage: new Usage(parsed.response.usage),
output: parsed.response.output,
responseId: parsed.response.id,
requestId: parsed.response.requestId,
...(rawUsage !== undefined ? { rawUsage } : {}),
};
result.state._lastTurnResponse = finalResponse;
result.state._context.usage.add(finalResponse.usage);
recordUsage(finalResponse.usage);
}
if (result.cancelled) {
// When the user's code exits a loop to consume the stream, we need to break
// this loop to prevent internal false errors and unnecessary processing
await awaitInputGuardrails();
await commitDeferredLocalPendingInput?.();
await reconcileStreamAbortIfNeeded();
return;
}
result._addItem(new RunRawModelStreamEvent(event));
}
}
catch (error) {
if (isAbortError(error)) {
if (sentInputToModel) {
markServerInputAccepted();
}
await awaitInputGuardrails();
await commitDeferredLocalPendingInput?.();
await reconcileStreamAbortIfNeeded();
return;
}
if (error instanceof ModelTimeoutError || timedOutModelCallFailed) {
if (streamEventCount > 0) {
markServerInputAccepted(false);
}
await awaitInputGuardrails();
await commitDeferredLocalPendingInput?.();
await reconcileStreamAbortIfNeeded();
}
throw error;
}
if (finalResponse) {
markServerInputAccepted();
}
await awaitInputGuardrails();
await commitDeferredLocalPendingInput?.();
if (result.cancelled) {
return;
}
result.state._noActiveAgentRun = false;
if (!finalResponse) {
const lastEventKind = StreamEvent.options.find((schema) => schema.shape.type.value === lastStreamEventType)?.shape.type.value ??
(streamEventCount > 0 ? 'unknown' : 'none');
throw new ModelBehaviorError(`Model did not produce a final response! Stream ended before response_done (event_count=${streamEventCount}, last_event_type=${lastEventKind}).`, result.state);
}
result.state._lastTurnResponse = finalResponse;
completeCompactionExchange(finalResponse.output, finalResponse.responseId);
// Keep the tracker in sync with the streamed response so reconnections remain accurate.
serverConversationTracker?.trackServerItems(finalResponse);
if (serverConversationTracker) {
result.state.setConversationContext(serverConversationTracker.conversationId, serverConversationTracker.previousResponseId);
}
result.state._modelResponses.push(result.state._lastTurnResponse);
const processedResponse = await processModelResponseAsync(result.state._lastTurnResponse, currentAgent, preparedCall.tools, preparedCall.handoffs, result.state,
// Loading authority retains the agent ownership lost in model input.
[
...(Array.isArray(result.state._originalInput)
? result.state._originalInput
: []),
...result.state._generatedItems,
], options.toolNotFoundBehavior, {
allowPromptSuppliedTools: preparedCall.allowPromptSuppliedTools,
beforeClientToolSearch: () => preflightModelResponseToolInvocations(currentAgent, result.state, result.state._lastTurnResponse, preparedCall.tools, preparedCall.handoffs, this, options.signal),
});
serverConversationTracker?.trackServerItems(result.state._lastTurnResponse, processedResponse.newItems);
result.state._lastProcessedResponse = processedResponse;
trackDeferredToolRecovery(result.state, currentAgent, processedResponse);
const suppressedToolCalls = preflightToolInvocations(currentAgent, result.state, processedResponse);
const streamableItems = filterSuppressedToolCallItems(processedResponse.newItems, suppressedToolCalls);
// Record the items emitted directly from the model response so we do not
// stream them again after tools and other side effects finish.
const preToolItems = new Set(streamableItems);
if (preToolItems.size > 0) {
streamStepItemsToRunResult(result, streamableItems);
}
markAcceptedResponseProcessingStarted(result.state);
this.#captureResponseGuardrailBoundary(result.state, true);
const turnResult = await resolveTurnAfterModelResponse(currentAgent, result.state._originalInput, result.state._generatedItems, result.state._lastTurnResponse, result.state._lastProcessedResponse, this, result.state, toolErrorFormatter, agentToolParentRunConfig, options.errorHandlers, options.signal, suppressedToolCalls, attemptedRunErrorHandlers, (handoffAgent) => this.#validateModelTimeoutForAgent(handoffAgent));
if (turnResult.nextStep.type === 'next_step_handoff') {
this.#validateModelTimeoutForAgent(turnResult.nextStep.newAgent);
}
applyTurnResult({
state: result.state,
turnResult,
agent: currentAgent,
toolsUsed: processedResponse.toolsUsed,
resetTurnPersistence: !isResumedState,
onStepItems: (step) => {
addStepToRunResult(result, step, { skipItems: preToolItems });
},
});
if (turnResult.nextStep.type !== 'next_step_final_output') {
finishRunnerSpan(currentTurnSpan);
setRunStateTurnSpanParent(result.state, undefined);
currentTurnSpan = undefined;
}
}
const currentStep = result.state._currentStep;
switch (currentStep.type) {
case 'next_step_final_output':
await finalizeStreamOutput();
return;
case 'next_step_interruption':
// We are done for now. Don't run any output guardrails.
if (!serverManagesConversation) {
await saveStreamResultWithCompactionOwnership();
}
return;
case 'next_step_handoff':
this.#validateModelTimeoutForAgent(currentStep.newAgent);
result.state.setCurrentAgent(currentStep.newAgent);
if (result.state._currentAgentSpan) {
result.state._currentAgentSpan.end();
resetCurrentSpan();
}
result.state.setCurrentAgentSpan(undefined);
result._addItem(new RunAgentUpdatedStreamEvent(result.state._currentAgent));
result.state._noActiveAgentRun = true;
result.state._currentTurnInProgress = false;
// We've processed the handoff, so we need to run the loop again.
result.state._currentStep = {
type: 'next_step_run_again',
};
break;
case 'next_step_run_again':
result.state._currentTurnInProgress = false;
logger.debug('Running next loop');
break;
default:
logger.debug('Running next loop');
}
}
}
catch (caughtError) {
if (guardrailTracker.pending) {
await guardrailTracker.awaitCompletion({ suppressErrors: true });
}
if (!guardrailTracker.failed) {
await commitDeferredLocalPendingInput?.();
}
const error = guardrailTracker.failed
? guardrailTracker.error
: caughtError;
const restoredPendingTurn = rollbackUnstartedTurn(result.state, turnPendingModelRequest);
turnPendingModelRequest = undefined;
if (!restoredPendingTurn) {
result.state._currentTurnInProgress = false;
}
attachRunStateToError(error, result.state);
releaseUnusedSessionHistoryTransactionBinding(result.state);
suppressStreamInputPersistence =
error instanceof CompactionItemValidationError;
const errorHandled = result.state._pendingSessionWrite
? undefined
: await prepareRunErrorFinalOutput({
error,
state: result.state,
errorHandlers: options.errorHandlers,
streamResult: result,
responseAccepted: isAcceptedResponseCheckpoint(result.state),
attemptedErrors: attemptedRunErrorHandlers,
});
if (errorHandled) {
try {
await finalizeStreamOutput(errorHandled);
return;
}
catch (finalizationError) {
recordStreamingError(finalizationError);
throw finalizationError;
}
}
recordStreamingError(error);
throw error;
}
finally {
finishRunnerSpan(currentTurnSpan);
setRunStateTurnSpanParent(result.state, undefined);
if (guardrailTracker.pending) {
await guardrailTracker.awaitCompletion({ suppressErrors: true });
}
if (sentInputToModel &&
!streamInputPersisted &&
!guardrailTracker.failed &&
!suppressStreamInputPersistence) {
await persistStreamInputIfNeeded();
}
const preserveSandboxSessions = result.state._currentStep?.type === 'next_step_interruption' ||
(result.cancelled &&
result.state._currentStep?.type !== 'next_step_final_output' &&
runError === undefined);
try {
try {
await finalizeSandboxRuntime({
state: result.state,
sandboxRuntime,
preserveSessionsForInterruption: preserveSandboxSessions,
finishAgentSpanForInterruption: Boolean(taskSpan) || runError !== undefined,
runError,
groupId: this.config.groupId,
memoryContext: sandboxMemoryRunContext,
runAgent: async (agent, input, runOptions) => await this.run(agent, input, runOptions),
tracingParent: taskSpan?.span ??
result.state._currentAgentSpan ??
invocationSpanParent,
});
}
catch (error) {
setRunnerSpanError(taskSpan, error, this.config.traceIncludeSensitiveData);
await Promise.reject(error);
}
}
finally {
finishRunnerSpan(taskSpan);
}
}
}
/**
* @internal
*/
async #runIndividualStream(agent, input, taskSpanName, options, ensureStreamInputPersisted, sessionTurnInputUpdate, sessionInputUpdate, getStreamInputForPersistence, preserveTurnPersistenceOnResume, provisionalSessionHistoryTransactionSessionId, provisionalSessionHistoryTransactionInputItems, sandboxMemoryRunContext, invocationSpanParent, pendingSessionWriteReconciled = false, sessionCompaction) {
options = options ?? {};
return withNewSpanContext(async () => {
// Initialize or reuse existing state
const isResumedState = input instanceof RunState;
const state = isResumedState
? input
: new RunState(options.context instanceof RunContext
? options.context
: new RunContext(options.context), input, agent, options.maxTurns === undefined
? DEFAULT_MAX_TURNS
: options.maxTurns);
bindSessionCompactionState(state, sessionCompaction);
this.#validateModelTimeoutForAgent(state._currentAgent);
if (isResumedState) {
state._agentToolInvocation = undefined;
if (options.maxTurns !== undefined) {
state._maxTurns = options.maxTurns;
}
}
const resolvedReasoningItemIdPolicy = options.reasoningItemIdPolicy ??
(isResumedState ? state._reasoningItemIdPolicy : undefined) ??
this.config.reasoningItemIdPolicy;
state.setReasoningItemIdPolicy(resolvedReasoningItemIdPolicy);
const resolvedConversationId = options.conversationId ??
(isResumedState ? state._conversationId : undefined);
const resolvedPreviousResponseId = options.previousResponseId ??
(isResumedState ? state._previousResponseId : undefined);
if (!isResumedState) {
await prepareSessionHistoryTransactionsForRun(options.session, state, {
serverManagesConversation: Boolean(resolvedConversationId || resolvedPreviousResponseId),
});
}
const useTaskAndTurnSpans = !this.config.tracingDisabled &&
includeTaskAndTurnSpans(options.tracing);
const resumingInterruptedTurn = isResumedState && state._currentStep?.type === 'next_step_interruption';
const invocationSpans = useTaskAndTurnSpans
? startRunnerInvocationSpans({
name: taskSpanName,
agent: state._currentAgent,
restoredAgentSpan: isResumedState
? state._currentAgentSpan
: undefined,
resumeInterruptedTurn: resumingInterruptedTurn,
parent: invocationSpanParent,
})
: undefined;
const taskSpan = invocationSpans?.taskSpan;
const optOutResumeAgentSpan = resumingInterruptedTurn && !useTaskAndTurnSpans
? ensureActiveAgentSpanForInterruptedResume({
agent: state._currentAgent,
restoredAgentSpan: isResumedState
? state._currentAgentSpan
: undefined,
parent: invocationSpanParent ?? getCurrentTrace() ?? undefined,
})
: undefined;
if (useTaskAndTurnSpans && isResumedState) {
state.setCurrentAgentSpan(invocationSpans?.agentSpan);
}
else if (optOutResumeAgentSpan) {
state.setCurrentAgentSpan(optOutResumeAgentSpan);
}
const sandboxRuntime = new SandboxRuntimeManager({
startingAgent: agent,
sandboxConfig: options.sandbox ?? this.config.sandbox,
runState: isResumedState
? state
: undefined,
});
if (!isResumedState) {
state.setConversationContext(resolvedConversationId, resolvedPreviousResponseId);
}
// Initialize the streamed result with existing state
const result = new StreamedRunResult({
signal: options.signal,
state,
});
result._setAgentToolStreamBuffer(getAgentToolStreamBuffer(this));
const streamOptions = {
...options,
signal: result._getAbortSignal(),
};
// Setup defaults
result.maxTurns = state._maxTurns;
// Continue the stream loop without blocking
const streamLoopPromise = this.#runStreamLoop(result, agent, sandboxRuntime, streamOptions, isResumedState, ensureStreamInputPersisted, sessionTurnInputUpdate, sessionInputUpdate, getStreamInputForPersistence, preserveTurnPersistenceOnResume, provisionalSessionHistoryTransactionSessionId, provisionalSessionHistoryTransactionInputItems, sandboxMemoryRunContext, taskSpan, invocationSpanParent, pendingSessionWriteReconciled).then(() => {
result._done();
}, (err) => {
result._raiseError(err);
});
// Attach the stream loop promise so trace end waits for the loop to complete
result._setStreamLoopPromise(streamLoopPromise);
return result;
});
}
/**
* @internal
* Validates timeout settings before turn preparation can run hooks, persist
* input, or initialize sandbox resources.
*/
#validateModelTimeoutForAgent(executionAgent) {
const agentModelSettings = executionAgent.hasExplicitModelSettings()
? executionAgent.modelSettings
: undefined;
validateModelTimeoutMs(mergeModelSettings(this.config.modelSettings, agentModelSettings));
}
/**
* @internal
* Applies call-level filters and merges session updates so the model request mirrors exactly
* what we persisted for history.
*/
async #prepareModelCall(state, executionAgent, options, artifacts, turnInput, serverConversationTracker, sessionInputUpdate) {
const { model, explicitlyModelSet, resolvedModelName } = await this.#resolveModelForAgent(executionAgent);
const hasExplicitAgentModelSettings = executionAgent.hasExplicitModelSettings();
// Store is caller-owned even when reasoning/text settings are implicit defaults.
const agentModelSettings = hasExplicitAgentModelSettings
? executionAgent.modelSettings
: Object.prototype.hasOwnProperty.call(executionAgent.modelSettings, 'store')
? { store: executionAgent.modelSettings.store }
: undefined;
// Only adapters that honor prompt model selection can replace the default model.
const promptOwnsModel = model.supportsPromptModelSelection === true &&
executionAgent.prompt !== undefined &&
!explicitlyModelSet;
const implicitModelSettings = hasExplicitAgentModelSettings || promptOwnsModel
? undefined
: getImplicitModelSettingsForResolvedModel(explicitlyModelSet, resolvedModelName);
let modelSettings = mergeModelSettings(implicitModelSettings, this.config.modelSettings);
modelSettings = mergeModelSettings(modelSettings, agentModelSettings);
// Record effort ownership before legacy model cleanup can remove an explicit choice.
const hasReasoningEffort = modelSettings.reasoning?.effort !== undefined ||
modelSettings.providerData?.reasoning_effort !== undefined;
const modelRequestInternal = {
reasoningEffortImplicit: hasReasoningEffort
? implicitModelSettings?.reasoning?.effort !== undefined &&
!hasExplicitTopLevelReasoningEffort(this.config.modelSettings) &&
!hasExplicitTopLevelReasoningEffort(agentModelSettings)
: undefined,
tracingParent: getRunStateTurnSpanParent(state) ?? state._currentAgentSpan,
toolNameCollisionPolicy: options.toolNameCollisionPolicy ?? 'warn',
};
modelSettings = adjustModelSettingsForLegacyModel(explicitlyModelSet, agentModelSettings ?? implicitModelSettings ?? {}, modelSettings, resolvedModelName);
modelSettings = maybeResetToolChoice(state._currentAgent, state._toolUseTracker, modelSettings);
validateModelTimeoutMs(modelSettings);
state._lastModelSettings = modelSettings;
const systemInstructions = await executionAgent.getSystemPrompt(state._context);
const prompt = await executionAgent.getPrompt(state._context);
const allowPromptSuppliedTools = Boolean(prompt) &&
!(artifacts.toolsExplicitlyProvided &&
artifacts.serializedTools.length === 0 &&
artifacts.serializedHandoffs.length === 0);
const { modelInput, sourceItems, persistedItems, sourceMatchKinds, filterApplied, preserveInputIdentity, } = await applyCallModelInputFilter(state._currentAgent, options.callModelInputFilter, state._context, turnInput, systemInstructions);
// Persist normalized clones so session history mirrors the exact model payload. An empty array
// is intentional when a filter removes everything.
sessionInputUpdate?.(sourceItems, persistedItems);
const previousResponseId = serverConversationTracker?.previousResponseId ??
options.previousResponseId;
const conversationId = serverConversationTracker?.conversationId ?? options.conversationId;
return {
...artifacts,
model,
explicitlyModelSet,
modelRequestInternal,
modelSettings,
modelInput,
prompt,
allowPromptSuppliedTools,
previousResponseId,
conversationId,
sourceItems,
persistedItems,
sourceMatchKinds,
filterApplied,
preserveInputIdentity,
turnInput,
};
}
}
// internal helpers and constants
let defaultRunner;
function hasExplicitTopLevelReasoningEffort(settings) {
return settings?.reasoning?.effort !== undefined;
}
const getDefaultRunner = () => {
if (!defaultRunner) {
defaultRunner = new Runner();
}
return defaultRunner;
};
//# sourceMappingURL=run.mjs.map