@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
1,153 lines • 54.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.nextStepSchema = void 0;
exports.preflightModelResponseToolInvocations = preflightModelResponseToolInvocations;
exports.preflightToolInvocations = preflightToolInvocations;
exports.filterSuppressedToolCallItems = filterSuppressedToolCallItems;
exports.resolveInterruptedTurn = resolveInterruptedTurn;
exports.resolveTurnAfterModelResponse = resolveTurnAfterModelResponse;
const zod_1 = require("zod");
const agentToolSourceRegistry_1 = require("../agentToolSourceRegistry.js");
const errors_1 = require("../errors.js");
const items_1 = require("../items.js");
const logger_1 = __importStar(require("../logger.js"));
const messages_1 = require("../utils/messages.js");
const safeExecute_1 = require("../utils/safeExecute.js");
const finalOutputError_1 = require("../utils/finalOutputError.js");
const standardSchema_1 = require("../utils/standardSchema.js");
const context_1 = require("../tracing/context.js");
const steps_1 = require("./steps.js");
Object.defineProperty(exports, "nextStepSchema", { enumerable: true, get: function () { return steps_1.nextStepSchema; } });
const toolExecution_1 = require("./toolExecution.js");
const invocationContext_1 = require("./invocationContext.js");
const mcpApprovals_1 = require("./mcpApprovals.js");
const toolIdentity_1 = require("../toolIdentity.js");
const errorHandlers_1 = require("./errorHandlers.js");
const items_2 = require("./items.js");
const streamReconciliation_1 = require("./streamReconciliation.js");
const siblingCancellation_1 = require("./siblingCancellation.js");
const toolInvocation_1 = require("../toolInvocation.js");
const DEFAULT_TOOL_NOT_FOUND_MESSAGE = (toolName) => `Tool '${toolName}' not found.`;
function preflightModelResponseToolInvocations(agent, state, modelResponse, tools, handoffs, runner, signal) {
const functionMap = (0, toolIdentity_1.buildFunctionToolLookupMap)(tools.filter((tool) => tool.type === 'function'));
const handoffMap = new Map(handoffs.map((handoff) => [handoff.toolName, handoff]));
const computer = tools.find((tool) => tool.type === 'computer');
const shell = tools.find((tool) => tool.type === 'shell');
const applyPatch = tools.find((tool) => tool.type === 'apply_patch');
const seen = new Map();
let selectedHandoff;
const validate = (toolName, rawItem) => {
if (!toolName) {
return false;
}
const { callId, fingerprint } = state._context._validateToolInvocation(agent, toolName, rawItem);
const completed = state._preflightToolInvocation(agent, callId, fingerprint);
const previous = seen.get(callId);
if (previous !== undefined && previous !== fingerprint) {
throw new errors_1.ModelBehaviorError(`Tool call ID ${callId} was reused for different tool invocations in the same model response.`, state);
}
seen.set(callId, fingerprint);
return !completed && previous !== fingerprint;
};
for (const output of modelResponse.output) {
if (output.type === 'function_call') {
const handoff = !(0, toolIdentity_1.getToolCallNamespace)(output)
? handoffMap.get(output.name)
: undefined;
if (handoff) {
const shouldRun = validate((0, toolInvocation_1.getHandoffToolInvocationName)(handoff.toolName), output);
if (shouldRun && !selectedHandoff) {
selectedHandoff = handoff;
}
continue;
}
const resolvedTool = (0, toolIdentity_1.resolveFunctionToolCall)(output, functionMap);
validate(resolvedTool
? (0, toolIdentity_1.getFunctionToolStateKeyForResolvedCall)(output, resolvedTool)
: (0, toolIdentity_1.getFunctionToolStateKeyForCall)(output, output.name), output);
continue;
}
if (output.type === 'computer_call') {
validate(computer?.name ?? 'computer', output);
continue;
}
if (output.type === 'shell_call') {
validate(shell?.name ?? 'shell', output);
continue;
}
if (output.type === 'apply_patch_call') {
validate(applyPatch?.name ?? 'apply_patch', output);
continue;
}
if (output.type === 'hosted_tool_call' &&
(output.providerData?.type === 'mcp_approval_request' ||
output.name === 'mcp_approval_request')) {
const providerData = output.providerData;
validate(typeof providerData?.name === 'string'
? providerData.name
: output.name, output);
}
}
if (selectedHandoff && !signal?.aborted) {
validateHandoffInputFilter(selectedHandoff, runner, state);
}
}
function preflightToolInvocations(agent, state, processedResponse) {
const suppressed = new Set();
const seen = new Map();
const observe = (toolName, rawItem) => {
if (!toolName) {
return undefined;
}
const { callId, fingerprint } = state._context._validateToolInvocation(agent, toolName, rawItem);
const completed = state._observeToolInvocation(agent, callId, fingerprint);
const previous = seen.get(callId);
const shouldSuppress = completed || previous === fingerprint;
if (shouldSuppress) {
suppressed.add(rawItem);
}
seen.set(callId, fingerprint);
return { callId, fingerprint, shouldSuppress };
};
for (const run of processedResponse.functions ?? []) {
const toolName = (0, toolIdentity_1.getFunctionToolStateKey)(run.tool) ??
(0, toolIdentity_1.getFunctionToolStateKeyForCall)(run.toolCall, run.tool.name);
const observation = observe(toolName, run.toolCall);
if (observation &&
!observation.shouldSuppress &&
!(0, toolIdentity_1.matchesFunctionToolRecipient)(run.mcpToolBinding ?? null, run.tool) &&
state._context.isToolApproved({
toolName: toolName,
callId: observation.callId,
agent,
functionTool: false,
}) !== false) {
throw new errors_1.UserError('Cannot resume a function tool call with a missing or different MCP recipient binding. Restore the original MCP configuration and tool listing, or start a new run.', state);
}
}
for (const run of processedResponse.functionToolsNotFound ?? []) {
observe((0, toolIdentity_1.getFunctionToolStateKeyForCall)(run.toolCall, run.toolName), run.toolCall);
}
for (const run of processedResponse.computerActions ?? []) {
observe(run.computer.name, run.toolCall);
}
for (const run of processedResponse.shellActions ?? []) {
observe(run.shell.name, run.toolCall);
}
for (const run of processedResponse.applyPatchActions ?? []) {
observe(run.applyPatch.name, run.toolCall);
}
const claimedHostedMcpItems = new Set();
for (const run of processedResponse.mcpApprovalRequests ?? []) {
const toolName = run.requestItem.name;
const observation = observe(toolName, run.requestItem.rawItem);
if (!toolName || !observation) {
continue;
}
const sourceRawItems = [];
for (const item of processedResponse.newItems) {
if (!(item instanceof items_1.RunToolCallItem) ||
item.rawItem.type !== 'hosted_tool_call') {
continue;
}
const rawItem = item.rawItem;
if (claimedHostedMcpItems.has(rawItem)) {
continue;
}
const providerData = rawItem.providerData;
const isApprovalRequest = providerData?.type === 'mcp_approval_request' ||
rawItem.name === 'mcp_approval_request';
const sourceReferenceId = typeof providerData?.id === 'string'
? providerData.id
: (0, toolInvocation_1.getToolInvocationCallId)(rawItem);
if (isApprovalRequest && sourceReferenceId === observation.callId) {
sourceRawItems.push(rawItem);
}
}
if (sourceRawItems.length !== 1) {
throw new errors_1.ModelBehaviorError(`Hosted MCP approval request ${observation.callId} does not have exactly one source tool invocation.`);
}
const sourceRawItem = sourceRawItems[0];
claimedHostedMcpItems.add(sourceRawItem);
if ((0, toolInvocation_1.getToolInvocationFingerprint)(toolName, sourceRawItem) !==
observation.fingerprint) {
throw new errors_1.ModelBehaviorError(`Hosted MCP approval request ${observation.callId} does not match its source tool invocation.`);
}
if (observation.shouldSuppress) {
suppressed.add(sourceRawItem);
}
}
for (const run of processedResponse.handoffs ?? []) {
observe((0, toolInvocation_1.getHandoffToolInvocationName)(run.handoff.toolName), run.toolCall);
}
return suppressed;
}
function validateAgentToolSources(functionRuns, validateAgent) {
if (!validateAgent) {
return;
}
for (const run of functionRuns) {
const sourceAgent = (0, agentToolSourceRegistry_1.getAgentToolSourceAgent)(run.tool);
if (sourceAgent) {
validateAgent(sourceAgent);
}
}
}
function isSuppressedToolCallItem(item, suppressed) {
if (!(item instanceof items_1.RunToolCallItem) &&
!(item instanceof items_1.RunHandoffCallItem) &&
!(item instanceof items_1.RunToolApprovalItem)) {
return false;
}
const rawItem = item.rawItem;
if (rawItem.type !== 'function_call' &&
rawItem.type !== 'computer_call' &&
rawItem.type !== 'shell_call' &&
rawItem.type !== 'apply_patch_call' &&
rawItem.type !== 'hosted_tool_call') {
return false;
}
return suppressed.has(rawItem);
}
function filterSuppressedToolCallItems(items, suppressed) {
return items.filter((item) => !isSuppressedToolCallItem(item, suppressed));
}
async function resolveToolNotFoundMessage(state, toolRun, toolErrorFormatter) {
const defaultMessage = toolRun.reason === 'not_loaded'
? `Error: Tool ${toolRun.toolName} is not loaded for the current agent. The requested function was not executed. Call tool_search to load it, then retry the original function call with the same arguments.`
: DEFAULT_TOOL_NOT_FOUND_MESSAGE(toolRun.toolName);
if (!toolErrorFormatter) {
return defaultMessage;
}
try {
const formattedMessage = await toolErrorFormatter({
kind: 'tool_not_found',
toolType: 'function',
toolName: toolRun.toolName,
callId: toolRun.toolCall.callId,
defaultMessage,
runContext: state._context,
});
if (typeof formattedMessage === 'string') {
return formattedMessage;
}
if (typeof formattedMessage !== 'undefined') {
logger_1.default.warn('toolErrorFormatter returned a non-string value. Falling back to the default tool not found message.');
}
}
catch (error) {
(0, logger_1.logToolActionWarning)(logger_1.default, 'toolErrorFormatter threw while formatting tool not found:', error);
}
return defaultMessage;
}
async function buildToolNotFoundOutputItems(agent, state, toolRuns, toolErrorFormatter) {
const items = [];
for (const toolRun of toolRuns) {
const message = await resolveToolNotFoundMessage(state, toolRun, toolErrorFormatter);
items.push(new items_1.RunToolCallOutputItem((0, toolExecution_1.getToolCallOutputItem)(toolRun.toolCall, message), agent, message));
}
return items;
}
const APPROVAL_ITEM_TYPES = [
'function_call',
'computer_call',
'hosted_tool_call',
'shell_call',
'apply_patch_call',
];
function isHostedMcpApprovalItem(item) {
return (0, toolIdentity_1.getHostedMcpApprovalRequestIdentity)(item) !== undefined;
}
function resolveApprovalState(item, state, processedResponse) {
if (isHostedMcpApprovalItem(item)) {
return 'pending';
}
const rawItem = item.rawItem;
const toolName = item.toolName ??
('name' in rawItem && typeof rawItem.name === 'string'
? rawItem.name
: undefined);
const callId = 'callId' in rawItem && typeof rawItem.callId === 'string'
? rawItem.callId
: 'id' in rawItem && typeof rawItem.id === 'string'
? rawItem.id
: undefined;
if (!toolName || !callId) {
return 'pending';
}
let toolNames = [toolName];
if (rawItem.type === 'function_call') {
const functionRun = item.agent === state._currentAgent
? processedResponse.functions.find((run) => run.toolCall.callId === callId)
: undefined;
if (functionRun) {
const stateKey = (0, toolIdentity_1.getFunctionToolStateKey)(functionRun.tool);
toolNames = stateKey ? [stateKey] : toolNames;
}
else {
const stateKey = item.functionToolStateKey ??
(0, toolIdentity_1.getFunctionToolStateKeyForCall)(rawItem, toolName);
toolNames = stateKey ? [stateKey] : toolNames;
}
}
const approval = toolNames
.map((candidate) => state._context._resolveToolInvocationApproval(item.agent, candidate, rawItem))
.find((decision) => typeof decision !== 'undefined');
if (approval === true) {
return 'approved';
}
if (approval === false) {
return 'rejected';
}
return 'pending';
}
function isApprovalItemLike(value) {
if (!value || typeof value !== 'object') {
return false;
}
if (!('rawItem' in value)) {
return false;
}
const rawItem = value.rawItem;
if (!rawItem || typeof rawItem !== 'object') {
return false;
}
const itemType = rawItem.type;
return APPROVAL_ITEM_TYPES.includes(itemType);
}
function getApprovalIdentity(approval) {
const rawItem = approval.rawItem;
if (!rawItem) {
return undefined;
}
const agent = 'agent' in approval ? approval.agent : undefined;
if (rawItem.type === 'function_call' && rawItem.callId) {
return { agent, value: `function_call:${rawItem.callId}` };
}
if ('callId' in rawItem && rawItem.callId) {
return { agent, value: `${rawItem.type}:${rawItem.callId}` };
}
const id = 'id' in rawItem ? rawItem.id : undefined;
if (id) {
return { agent, value: `${rawItem.type}:${id}` };
}
const providerData = typeof rawItem.providerData === 'object' && rawItem.providerData
? rawItem.providerData
: undefined;
if (providerData?.id) {
return { agent, value: `${rawItem.type}:provider:${providerData.id}` };
}
const agentName = 'agent' in approval && approval.agent ? approval.agent.name : '';
try {
return {
agent,
value: `${agentName}:${rawItem.type}:${JSON.stringify(rawItem)}`,
};
}
catch {
return { agent, value: `${agentName}:${rawItem.type}` };
}
}
function hasApprovalIdentity(identities, identity) {
return identities.get(identity.agent)?.has(identity.value) ?? false;
}
function addApprovalIdentity(identities, identity) {
const values = identities.get(identity.agent) ?? new Set();
values.add(identity.value);
identities.set(identity.agent, values);
}
function buildAppendContext(existingItems) {
const seenItems = new Set(existingItems);
const seenApprovalIdentities = new Map();
for (const item of existingItems) {
if (item instanceof items_1.RunToolApprovalItem) {
const identity = getApprovalIdentity(item);
if (identity) {
addApprovalIdentity(seenApprovalIdentities, identity);
}
}
}
return { seenItems, seenApprovalIdentities };
}
function appendRunItemIfNew(item, target, context) {
if (context.seenItems.has(item)) {
return;
}
if (item instanceof items_1.RunToolApprovalItem) {
const identity = getApprovalIdentity(item);
if (identity) {
if (hasApprovalIdentity(context.seenApprovalIdentities, identity)) {
return;
}
addApprovalIdentity(context.seenApprovalIdentities, identity);
}
}
context.seenItems.add(item);
target.push(item);
}
function buildApprovedCallIdSet(items, type) {
const callIds = new Set();
for (const item of items) {
if (!(item instanceof items_1.RunToolApprovalItem)) {
continue;
}
const rawItem = item.rawItem;
if (!rawItem || rawItem.type !== type) {
continue;
}
const callKey = getToolActionKey(rawItem);
if (callKey) {
callIds.add(callKey);
}
}
return callIds;
}
function collectCompletedCallIds(items, type, agent) {
const completed = new Set();
for (const item of items) {
const rawItem = item.rawItem;
if (!rawItem || typeof rawItem !== 'object') {
continue;
}
if (rawItem.type !== type) {
continue;
}
if (agent && (!('agent' in item) || item.agent !== agent)) {
continue;
}
const callId = rawItem.callId;
if (typeof callId === 'string') {
completed.add(callId);
}
}
return completed;
}
function filterActionsByApproval(preStepItems, actions, type) {
const allowedCallIds = buildApprovedCallIdSet(preStepItems, type);
if (allowedCallIds.size === 0) {
return [];
}
return actions.filter((action) => {
const callKey = getToolActionKey(action.toolCall);
return typeof callKey === 'string' && allowedCallIds.has(callKey);
});
}
function filterPendingActions(actions, options) {
return actions.filter((action) => {
const callId = action.toolCall.callId;
const hasCallId = typeof callId === 'string';
if (options.allowedCallIds && options.allowedCallIds.size > 0) {
if (!hasCallId || !options.allowedCallIds.has(callId)) {
return false;
}
}
if (hasCallId && options.completedCallIds.has(callId)) {
return false;
}
return true;
});
}
function queueActionsByCallId(actions) {
const queued = {
byCallId: new Map(),
withoutCallId: [],
};
for (const action of actions) {
const callKey = getToolActionKey(action.toolCall);
if (!callKey) {
queued.withoutCallId.push(action);
continue;
}
const actionsForCallId = queued.byCallId.get(callKey) ?? [];
actionsForCallId.push(action);
queued.byCallId.set(callKey, actionsForCallId);
}
return queued;
}
function takeQueuedAction(actionsByCallId, callId) {
const queued = actionsByCallId.get(callId);
const action = queued?.shift();
if (!queued || queued.length === 0) {
actionsByCallId.delete(callId);
}
return action;
}
function appendRemainingQueuedActions(target, actionsByCallId) {
for (const queued of actionsByCallId.values()) {
target.push(...queued);
}
}
function orderShellAndApplyPatchActions(sourceItems, shellActions, applyPatchActions) {
const shellActionsByCallId = queueActionsByCallId(shellActions);
const applyPatchActionsByCallId = queueActionsByCallId(applyPatchActions);
const orderedActions = [];
for (const item of sourceItems) {
const rawItem = item.rawItem;
if (rawItem?.type === 'shell_call') {
const callKey = getToolActionKey(rawItem);
const action = takeQueuedAction(shellActionsByCallId.byCallId, callKey ?? '');
if (action) {
orderedActions.push({ type: 'shell', action });
}
continue;
}
if (rawItem?.type === 'apply_patch_call') {
const callKey = getToolActionKey(rawItem);
const action = takeQueuedAction(applyPatchActionsByCallId.byCallId, callKey ?? '');
if (action) {
orderedActions.push({ type: 'apply_patch', action });
}
}
}
const remainingShellActions = [];
appendRemainingQueuedActions(remainingShellActions, shellActionsByCallId.byCallId);
remainingShellActions.push(...shellActionsByCallId.withoutCallId);
for (const action of remainingShellActions) {
orderedActions.push({ type: 'shell', action });
}
const remainingApplyPatchActions = [];
appendRemainingQueuedActions(remainingApplyPatchActions, applyPatchActionsByCallId.byCallId);
remainingApplyPatchActions.push(...applyPatchActionsByCallId.withoutCallId);
for (const action of remainingApplyPatchActions) {
orderedActions.push({ type: 'apply_patch', action });
}
return orderedActions;
}
function getToolActionKey(value) {
if (typeof value.callId === 'string' && value.callId.length > 0) {
return value.callId;
}
if (typeof value.id === 'string' && value.id.length > 0) {
return value.id;
}
return undefined;
}
async function executeShellAndApplyPatchActionsInOrder(args) {
const results = [];
for (const action of orderShellAndApplyPatchActions(args.sourceItems, args.shellActions, args.applyPatchActions)) {
if (args.signal?.aborted) {
const rawItem = action.type === 'shell'
? (0, streamReconciliation_1.buildShellAbortResult)(action.action.toolCall)
: (0, streamReconciliation_1.buildApplyPatchAbortResult)(action.action.toolCall);
results.push(new items_1.RunToolCallOutputItem(rawItem, args.agent, 'aborted'));
continue;
}
const items = action.type === 'shell'
? await (0, toolExecution_1.executeShellActions)(args.agent, [action.action], args.runner, args.state._context, undefined, args.toolErrorFormatter)
: await (0, toolExecution_1.executeApplyPatchOperations)(args.agent, [action.action], args.runner, args.state._context, undefined, args.toolErrorFormatter);
results.push(...items);
}
return results;
}
function truncateForDeveloper(message, maxLength = 160) {
const trimmed = message.trim();
if (!trimmed) {
return 'Schema validation failed.';
}
if (trimmed.length <= maxLength) {
return trimmed;
}
return `${trimmed.slice(0, maxLength - 3)}...`;
}
function formatFinalOutputTypeError(error) {
// Surface structured output validation hints without echoing potentially large or sensitive payloads.
try {
if (error instanceof zod_1.z.ZodError) {
const issue = error.issues[0];
if (issue) {
const issuePathParts = Array.isArray(issue.path) ? issue.path : [];
const issuePath = issuePathParts.length > 0
? issuePathParts.map((part) => String(part)).join('.')
: '(root)';
const message = truncateForDeveloper(issue.message ?? '');
return `Invalid output type: final assistant output failed schema validation at "${issuePath}" (${message}).`;
}
return 'Invalid output type: final assistant output failed schema validation.';
}
if (error instanceof Error && error.message) {
return `Invalid output type: ${truncateForDeveloper(error.message)}`;
}
}
catch {
// Swallow formatting errors so we can return a generic message below.
}
return 'Invalid output type: final assistant output did not match the expected schema.';
}
function buildTurnRunErrorData(state, agent, originalInput, preStepItems, newItems) {
const generatedItems = preStepItems.concat(newItems);
return {
input: originalInput,
newItems: generatedItems,
history: (0, items_2.getTurnInput)(originalInput, generatedItems, state._reasoningItemIdPolicy),
output: (0, items_2.getRunOutput)(generatedItems, state._reasoningItemIdPolicy),
rawResponses: state._modelResponses,
lastAgent: agent,
state,
};
}
async function resolveInvalidFinalOutput(args) {
return (0, errorHandlers_1.preserveInvalidFinalOutputRedaction)(async (redactFromStart) => {
const handlerResult = await (0, errorHandlers_1.resolveRunErrorHandler)({
error: args.error,
errorKind: 'invalidFinalOutput',
errorHandlers: args.errorHandlers,
context: args.state._context,
runData: buildTurnRunErrorData(args.state, args.agent, args.originalInput, args.preStepItems, args.newItems),
attemptedErrors: args.attemptedErrors,
});
if (!handlerResult) {
(0, errorHandlers_1.invalidateAcceptedResponseReplayEvidence)(args.state);
return undefined;
}
const outputText = (0, errorHandlers_1.formatRunErrorFinalOutput)(args.agent, handlerResult.finalOutput);
(0, errorHandlers_1.validateRunErrorHandlerFinalOutput)(args.agent, outputText, true, redactFromStart);
if (handlerResult.includeInHistory !== false) {
args.newItems.push((0, errorHandlers_1.createRunErrorFinalOutputItem)(args.agent, outputText));
}
return outputText;
}, (0, finalOutputError_1.isDataRedactedError)(args.error));
}
/**
* @internal
* Continues a turn that was previously interrupted waiting for tool approval. Executes the now
* approved tools and returns the resulting step transition.
*/
async function resolveInterruptedTurn(agent, originalInput, originalPreStepItems, newResponse, processedResponse, runner, state, toolErrorFormatter, agentToolParentRunConfig, signal, validateHandoffAgent, beforeHandoffInputFilter) {
const suppressedToolCalls = preflightToolInvocations(agent, state, processedResponse);
const handoffRuns = processedResponse.handoffs.filter((run) => !suppressedToolCalls.has(run.toolCall));
if (handoffRuns.length > 0) {
if (!signal?.aborted) {
validateHandoffInputFilter(handoffRuns[0].handoff, runner, state);
}
validateHandoffAgent?.(handoffRuns[0].handoff.agent);
}
// call_ids for function tools
const functionCallIds = originalPreStepItems
.filter((item) => item instanceof items_1.RunToolApprovalItem &&
item.agent === agent &&
'callId' in item.rawItem &&
item.rawItem.type === 'function_call')
.map((item) => item.rawItem.callId);
const completedFunctionCallIds = collectCompletedCallIds(originalPreStepItems, 'function_call_result', agent);
const completedComputerCallIds = collectCompletedCallIds(originalPreStepItems, 'computer_call_result');
const completedShellCallIds = collectCompletedCallIds(originalPreStepItems, 'shell_call_output');
const completedApplyPatchCallIds = collectCompletedCallIds(originalPreStepItems, 'apply_patch_call_output');
// We already persisted the turn once when the approval interrupt was raised, so the
// counter reflects the approval items as "flushed". When we resume the same turn we need
// to rewind it so the eventual tool output for this call is still written to the session.
const pendingApprovalItems = state
.getInterruptions()
.filter(isApprovalItemLike);
const pendingApprovalIdentities = new Map();
for (const approval of pendingApprovalItems) {
if (!(approval instanceof items_1.RunToolApprovalItem)) {
continue;
}
if (isHostedMcpApprovalItem(approval)) {
continue;
}
const rawItem = approval.rawItem;
if (rawItem.type === 'function_call' &&
approval.agent === agent &&
rawItem.callId &&
completedFunctionCallIds.has(rawItem.callId)) {
continue;
}
if (rawItem.type === 'computer_call' &&
rawItem.callId &&
completedComputerCallIds.has(rawItem.callId)) {
continue;
}
if (rawItem.type === 'shell_call' &&
rawItem.callId &&
completedShellCallIds.has(rawItem.callId)) {
continue;
}
if (rawItem.type === 'apply_patch_call' &&
rawItem.callId &&
completedApplyPatchCallIds.has(rawItem.callId)) {
continue;
}
const identity = getApprovalIdentity(approval);
if (identity) {
if (resolveApprovalState(approval, state, processedResponse) === 'pending') {
addApprovalIdentity(pendingApprovalIdentities, identity);
}
}
}
// Run function tools that require approval or are resuming a nested agent tool run.
const functionToolRuns = processedResponse.functions.filter((run) => {
if (suppressedToolCalls.has(run.toolCall)) {
return false;
}
const callId = run.toolCall.callId;
if (!callId) {
return false;
}
const isApprovedCall = functionCallIds.includes(callId);
const isPendingNested = (0, toolIdentity_1.getFunctionToolStateKeys)(run.tool, run.availableFunctionTools ?? [run.tool]).some((stateKey) => state.hasPendingAgentToolRun(stateKey, callId));
if (!isApprovedCall && !isPendingNested) {
return false;
}
return !completedFunctionCallIds.has(callId);
});
validateAgentToolSources(functionToolRuns, validateHandoffAgent);
const shellRuns = filterPendingActions(filterActionsByApproval(originalPreStepItems, (processedResponse.shellActions ?? []).filter((run) => !suppressedToolCalls.has(run.toolCall)), 'shell_call'), {
completedCallIds: completedShellCallIds,
});
const pendingComputerActions = filterPendingActions(filterActionsByApproval(originalPreStepItems, (processedResponse.computerActions ?? []).filter((run) => !suppressedToolCalls.has(run.toolCall)), 'computer_call'), {
completedCallIds: completedComputerCallIds,
});
const applyPatchRuns = filterPendingActions(filterActionsByApproval(originalPreStepItems, (processedResponse.applyPatchActions ?? []).filter((run) => !suppressedToolCalls.has(run.toolCall)), 'apply_patch_call'), {
completedCallIds: completedApplyPatchCallIds,
});
const functionResults = await (0, toolExecution_1.executeFunctionToolCalls)(agent, functionToolRuns, runner, state, toolErrorFormatter, agentToolParentRunConfig, signal);
// Computer actions may require approval; only pending approved actions are executed on resume.
const computerResults = pendingComputerActions.length > 0
? await (0, toolExecution_1.executeComputerActions)(agent, pendingComputerActions, runner, state._context, undefined, toolErrorFormatter, signal)
: [];
const shellAndApplyPatchResults = shellRuns.length > 0 || applyPatchRuns.length > 0
? await executeShellAndApplyPatchActionsInOrder({
agent,
sourceItems: originalPreStepItems,
shellActions: shellRuns,
applyPatchActions: applyPatchRuns,
runner,
state,
toolErrorFormatter,
signal,
})
: [];
const pendingFunctionToolsNotFound = filterPendingActions((processedResponse.functionToolsNotFound ?? []).filter((run) => !suppressedToolCalls.has(run.toolCall)), {
completedCallIds: completedFunctionCallIds,
});
const toolNotFoundResults = await buildToolNotFoundOutputItems(agent, state, pendingFunctionToolsNotFound, toolErrorFormatter);
const newItems = [];
const appendContext = buildAppendContext(originalPreStepItems);
const appendIfNew = (item) => appendRunItemIfNew(item, newItems, appendContext);
for (const result of functionResults) {
if (result.type === 'function_output' &&
Array.isArray(result.interruptions) &&
result.interruptions.length > 0) {
continue;
}
appendIfNew(result.runItem);
}
for (const result of toolNotFoundResults) {
appendIfNew(result);
}
for (const result of computerResults) {
appendIfNew(result);
}
for (const result of shellAndApplyPatchResults) {
appendIfNew(result);
}
const additionalInterruptions = (0, toolExecution_1.collectInterruptions)([], [...computerResults, ...shellAndApplyPatchResults]);
const hostedMcpApprovals = await (0, mcpApprovals_1.handleHostedMcpApprovals)({
requests: (processedResponse.mcpApprovalRequests ?? []).filter((run) => !suppressedToolCalls.has(run.requestItem.rawItem)),
agent,
state,
functionResults,
appendIfNew,
resolveApproval: (rawItem) => {
if (!(0, toolIdentity_1.getHostedMcpApprovalRequestIdentity)(rawItem)?.requestId) {
return undefined;
}
return state._context._resolveToolInvocationApproval(agent, (0, toolInvocation_1.getHostedMcpApprovalToolName)(rawItem.name, rawItem), rawItem);
},
resolveRejectionMessage: (rawItem) => state._context._getToolInvocationRejectionMessage(agent, (0, toolInvocation_1.getHostedMcpApprovalToolName)(rawItem.name, rawItem), rawItem),
});
// Server-managed conversations rely on preStepItems to re-surface pending approvals.
// Keep unresolved hosted MCP approvals in place so HITL flows still have something to approve next turn.
// Drop resolved approval placeholders so they are not replayed on the next turn, but keep
// pending approvals in place to signal the outstanding work to the UI and session store.
const preStepItems = originalPreStepItems.filter((item) => {
if (!(item instanceof items_1.RunToolApprovalItem)) {
return true;
}
if (isHostedMcpApprovalItem(item)) {
if (hostedMcpApprovals.pendingApprovals.has(item)) {
return true;
}
const approvalIdentity = (0, toolIdentity_1.getHostedMcpApprovalRequestIdentity)(item.rawItem);
const approvalRequestKey = approvalIdentity
? (0, toolIdentity_1.getHostedMcpApprovalRequestKey)(approvalIdentity)
: undefined;
if (approvalRequestKey) {
return hostedMcpApprovals.pendingApprovalKeys.has(approvalRequestKey);
}
return true;
}
// Preserve all other approval items so resumptions can continue to reference the
// original approval requests (e.g., function/shell/apply_patch) ONLY while they are still pending.
const identity = getApprovalIdentity(item);
if (!identity) {
return true;
}
return hasApprovalIdentity(pendingApprovalIdentities, identity);
});
const keptApprovalItems = new Set();
for (const item of preStepItems) {
if (item instanceof items_1.RunToolApprovalItem) {
keptApprovalItems.add(item);
}
}
let removedApprovalCount = 0;
for (const item of originalPreStepItems) {
if (item instanceof items_1.RunToolApprovalItem && !keptApprovalItems.has(item)) {
removedApprovalCount++;
}
}
if (removedApprovalCount > 0) {
// Persisting the approval request already advanced the counter once, so undo the increment
// to make sure we write the final tool output back to the session when the turn resumes.
state.rewindTurnPersistence(removedApprovalCount);
}
const handoffStep = await resolveHandoffAfterTools({
agent,
originalInput,
preStepItems,
newItems,
newResponse,
handoffRuns,
functionResults,
additionalInterruptions,
runner,
state,
signal,
validateHandoffAgent,
beforeHandoffInputFilter,
});
if (handoffStep) {
return handoffStep;
}
const completedStep = await maybeCompleteTurnFromToolResults({
agent,
runner,
state,
functionResults,
originalInput,
newResponse,
preStepItems,
newItems,
additionalInterruptions,
});
if (completedStep) {
return completedStep;
}
// we only ran new tools and side effects. We need to run the rest of the agent
return new steps_1.SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
}
/**
* @internal
* Executes every follow-up action the model requested (function tools, computer actions, MCP flows),
* appends their outputs to the run history, and determines the next step for the agent loop.
*/
async function resolveTurnAfterModelResponse(agent, originalInput, originalPreStepItems, newResponse, processedResponse, runner, state, toolErrorFormatter, agentToolParentRunConfig, errorHandlers, signal, preflightedToolCalls, attemptedErrors, validateHandoffAgent) {
const suppressedToolCalls = preflightedToolCalls ??
preflightToolInvocations(agent, state, processedResponse);
// Reuse the same array reference so we can compare object identity when deciding whether to
// append new items, ensuring we never double-stream existing RunItems.
const preStepItems = originalPreStepItems;
const newItems = [];
const appendContext = buildAppendContext(originalPreStepItems);
const appendIfNew = (item) => appendRunItemIfNew(item, newItems, appendContext);
for (const item of processedResponse.newItems) {
if (isSuppressedToolCallItem(item, suppressedToolCalls)) {
continue;
}
appendIfNew(item);
}
const handoffRuns = processedResponse.handoffs.filter((run) => !suppressedToolCalls.has(run.toolCall));
if (handoffRuns.length > 0) {
if (!signal?.aborted) {
validateHandoffInputFilter(handoffRuns[0].handoff, runner, state);
}
validateHandoffAgent?.(handoffRuns[0].handoff.agent);
}
const functionRuns = processedResponse.functions.filter((run) => !suppressedToolCalls.has(run.toolCall));
validateAgentToolSources(functionRuns, validateHandoffAgent);
// Run function tools and computer actions in parallel; neither depends on the other's side effects.
// Shell and apply_patch actions both mutate the sandbox filesystem, so preserve model order.
const runFunctionTools = (executionSignal = signal, cancelSiblingCategories) => (0, toolExecution_1.executeFunctionToolCalls)(agent, functionRuns, runner, state, toolErrorFormatter, agentToolParentRunConfig, executionSignal, cancelSiblingCategories);
const runComputerActions = (executionSignal = signal, cancelSiblingCategories) => (0, toolExecution_1.executeComputerActions)(agent, processedResponse.computerActions.filter((run) => !suppressedToolCalls.has(run.toolCall)), runner, state._context, undefined, toolErrorFormatter, executionSignal, cancelSiblingCategories);
const [functionResults, computerResults] = processedResponse.functions.some((run) => !suppressedToolCalls.has(run.toolCall)) &&
processedResponse.computerActions.some((run) => !suppressedToolCalls.has(run.toolCall))
? await (0, siblingCancellation_1.runWithSiblingCancellation)([runFunctionTools, runComputerActions], signal)
: await Promise.all([runFunctionTools(), runComputerActions()]);
const shellAndApplyPatchResults = processedResponse.shellActions.some((run) => !suppressedToolCalls.has(run.toolCall)) ||
processedResponse.applyPatchActions.some((run) => !suppressedToolCalls.has(run.toolCall))
? await executeShellAndApplyPatchActionsInOrder({
agent,
sourceItems: processedResponse.newItems,
shellActions: processedResponse.shellActions.filter((run) => !suppressedToolCalls.has(run.toolCall)),
applyPatchActions: processedResponse.applyPatchActions.filter((run) => !suppressedToolCalls.has(run.toolCall)),
runner,
state,
toolErrorFormatter,
signal,
})
: [];
const toolNotFoundResults = await buildToolNotFoundOutputItems(agent, state, (processedResponse.functionToolsNotFound ?? []).filter((run) => !suppressedToolCalls.has(run.toolCall)), toolErrorFormatter);
for (const result of functionResults) {
if (result.type === 'function_output' &&
Array.isArray(result.interruptions) &&
result.interruptions.length > 0) {
continue;
}
appendIfNew(result.runItem);
}
for (const item of toolNotFoundResults) {
appendIfNew(item);
}
for (const item of computerResults) {
appendIfNew(item);
}
for (const item of shellAndApplyPatchResults) {
appendIfNew(item);
}
const additionalInterruptions = (0, toolExecution_1.collectInterruptions)([], [...computerResults, ...shellAndApplyPatchResults]);
if (processedResponse.mcpApprovalRequests.length > 0) {
await (0, mcpApprovals_1.handleHostedMcpApprovals)({
requests: processedResponse.mcpApprovalRequests.filter((run) => !suppressedToolCalls.has(run.requestItem.rawItem)),
agent,
state,
functionResults,
appendIfNew,
resolveApproval: (rawItem) => {
if (!(0, toolIdentity_1.getHostedMcpApprovalRequestIdentity)(rawItem)?.requestId) {
return undefined;
}
return state._context._resolveToolInvocationApproval(agent, (0, toolInvocation_1.getHostedMcpApprovalToolName)(rawItem.name, rawItem), rawItem);
},
resolveRejectionMessage: (rawItem) => state._context._getToolInvocationRejectionMessage(agent, (0, toolInvocation_1.getHostedMcpApprovalToolName)(rawItem.name, rawItem), rawItem),
});
}
const handoffStep = await resolveHandoffAfterTools({
agent,
originalInput,
preStepItems,
newItems,
newResponse,
handoffRuns,
functionResults,
additionalInterruptions,
runner,
state,
signal,
validateHandoffAgent,
});
if (handoffStep) {
return handoffStep;
}
const completedStep = await maybeCompleteTurnFromToolResults({
agent,
runner,
state,
functionResults,
originalInput,
newResponse,
preStepItems,
newItems,
additionalInterruptions,
});
if (completedStep) {
return completedStep;
}
// If the model issued any tool calls or handoffs in this turn,
// we must NOT treat any assistant message in the same turn as the final output.
// We should run the loop again so the model can see the tool results and respond.
if (processedResponse.hasToolsOrApprovalsToRun()) {
return new steps_1.SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
}
// No tool calls/actions in this turn; safe to consider a plain assistant message as final.
const messageItems = newItems.filter((item) => item instanceof items_1.RunMessageOutputItem);
// A model response may split assistant text across multiple messages around
// non-message output items such as reasoning. Preserve all of that text while
// retaining the last message's refusal semantics.
const lastMessageText = messageItems.length > 0
? (0, messages_1.getTextFromOutputMessage)(messageItems[messageItems.length - 1].rawItem)
: undefined;
const potentialFinalOutput = typeof lastMessageText === 'undefined'
? undefined
: (0, messages_1.getOutputText)(newResponse) || lastMessageText;
// Keep looping if any tool output placeholders still require an approval follow-up.
const hasPendingToolsOrApprovals = functionResults.some((result) => result.runItem instanceof items_1.RunToolApprovalItem) || additionalInterruptions.length > 0;
if (!hasPendingToolsOrApprovals && messageItems.length > 0) {
const refusal = (0, messages_1.getRefusalFromOutputMessage)(messageItems[messageItems.length - 1].rawItem);
if (refusal && typeof potentialFinalOutput === 'undefined') {
const refusalError = new errors_1.ModelRefusalError(refusal, state);
const handlerResult = await (0, errorHandlers_1.resolveRunErrorHandler)({
error: refusalError,
errorHandlers,
context: state._context,
runData: buildTurnRunErrorData(state, agent, originalInput, preStepItems, newItems),
attemptedErrors,
});
if (!handlerResult) {
throw refusalError;
}
const outputText = (0, errorHandlers_1.formatRunErrorFinalOutput)(agent, handlerResult.finalOutput);
(0, errorHandlers_1.validateRunErrorHandlerFinalOutput)(agent, outputText);
if (handlerResult.includeInHistory !== false) {
newItems.push((0, errorHandlers_1.createRunErrorFinalOutputItem)(agent, outputText));
}
return new steps_1.SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_final_output', output: outputText }, 'error_handler');
}
}
const isMissingStructuredFinalOutput = agent.outputType !== 'text' && !potentialFinalOutput;
// Recover missing structured output when configured; otherwise run again.
if (typeof potentialFinalOutput === 'undefined' ||
isMissingStructuredFinalOutput) {
if (!hasPendingToolsOrApprovals && isMissingStructuredFinalOutput) {
const outputError = new errors_1.ModelBehaviorError('Model returned no final output for the structured output type.');
const handledOutput = await resolveInvalidFinalOutput({
error: outputError,
errorHandlers,
agent,
originalInput,
preStepItems,
newItems,
state,
attemptedErrors,
});
if (typeof handledOutput !== 'undefined') {
return new steps_1.SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_final_output', output: handledOutput }, 'error_handler');
}
}
return new steps_1.SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
}
if (!hasPendingToolsOrApprovals) {
if (agent.outputType === 'text') {
return new steps_1.SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
type: 'next_step_final_output',
output: potentialFinalOutput,
});
}
if (agent.outputType !== 'text' && potentialFinalOutput) {
// Structured output schema => always leads to a final output if we have text.
const [error] = await (0, safeExecute_1.safeExecute)(() => (0, finalOutputError_1.processFinalOutputWithRedaction)(() => agent.processFinalOutput(potentialFinalOutput)));
if (error) {
if ((0, standardSchema_1.isAsyncStandardSchemaValidationError)(error)) {
throw error;
}
const redacted = (0, finalOutputError_1.isDataRedactedError)(error);
const outputErrorMessage = redacted
? error.message
: formatFinalOutputTypeError(error);
const includeTraceErrorDetails = runner.config.traceIncludeSensitiveData && !redacted;
(0, context_1.addErrorToCurrentSpan)({
message: includeTraceErrorDetails
? outputErrorMessage
: finalOutputError_1.REDACTED_FINAL_OUTPUT_ERROR_MESSAGE,
data: includeTraceErrorDetails ? { error: String(error) } : {},
});
const outputError = redacted
? error
: new errors_1.ModelBehaviorError(outputErrorMessage);
const handledOutput = await resolveInvalidFinalOutput({
error: outputError,
errorHandlers,
agent,
originalInput,
preStepItems,
newItems,
state,
attemptedErrors,
});
if (typeof handledOutput === 'undefined') {
throw outputError;
}
return new steps_1.SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_final_output', output: handledOutput }, 'error_handler');
}
return new steps_1.SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_final_output', output: potentialFinalOutput });
}
}
return new steps_1.SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
}
// Local handoff filters cannot remove history already owned by the server.
function validateHandoffInputFilter(handoff, runner, state) {
const inputFilter = handoff.inputFilter ?? runner.config.handoffInputFilter;
if (inputFilter != null &&
(state._conversationId || state._previousResponseId)) {
throw new errors_1.UserError('Handoff input filters cannot be used with conversationId or previousResponseId because server-managed history cannot be filtered locally. Use explicit client-managed history or a Session without either continuation option.');
}
}
// Handoffs retain precedence over terminal tool behavior, but cannot discard pending approvals.
async function resolveHandoffAfterTools(options) {
const { agent, originalInput, preStepItems, newItems, newResponse, handoffRuns, functionResults, additionalInterruptions, runner, state, signal, validateHandoffAgent, beforeHandoffInputFilter, } = options;
if (handoffRuns.length === 0) {
return undefined;
}
if (signal?.aborted) {
const appendContext = buildAppendContext([...preStepItems, ...newItems]);
for (const { toolCall } of handoffRuns) {
const rawItem = (0, streamReconciliation_1.buildFunctionAbortResult)(toolCall);
appendRunItemIfNew(new items_1.RunToolCallOutputItem(rawItem, agent, rawItem.output), newItems, appendContext);
}
return undefined;
}
const interruptions = (0, toolExecution_1.collectInterruptions)(functionResults, additionalInterruptions);
if (interruptions.length > 0) {
return new steps_1.SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
type: 'next_step_interruption',
data: { interruptions },
});
}
return (0, toolExecution_1.executeHandoffCalls)(agent, originalInput, preStepItems, newItems, newResponse, handoffRuns, runner, state._context, (0, invocationContext_1.getRunStateTurnSpanParent)(state) ?? state._currentAgentSpan, validateHandoffAgent, beforeHandoffInputFilter, signal);
}
// Consolidates the logic that determines whether tool results yielded a final answer,
// triggered an interruption, or require the agent loop to continue running.
async function maybeCompleteTurnFromToolResults({ agent, runner: _runner, state, functionResults, originalInput, newResponse, preStepItems, newItems, additionalInterruptions = [], }) {
const toolOutcome = await (0, toolExecution_1.checkForFinalOutputFromTools)(agent, functionResults, state, additionalInterruptions);
if (toolOutcome.isFinalOutput) {
// Intentional: explicit toolUseBehavior finalization (for example stop_on_first_tool) takes precedence even when other provider-managed work is still pending.
return new steps_1.SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
type: 'next_step_final_output',
output: toolOutcome.finalOutput,
}, 'tool_result');
}
if (toolOutcome.isInterrupted) {
return new steps_1.SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
type: 'next_step_interruption',
data: {
interruptions: toolOutcome.interruptions,
},
});
}
return null;
}
//# sourceMappingURL=turnResolution.js.map