@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
1,196 lines • 82.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentToolUseTracker = exports.nextStepSchema = void 0;
exports.processModelResponse = processModelResponse;
exports.maybeResetToolChoice = maybeResetToolChoice;
exports.resolveInterruptedTurn = resolveInterruptedTurn;
exports.resolveTurnAfterModelResponse = resolveTurnAfterModelResponse;
exports.getToolCallOutputItem = getToolCallOutputItem;
exports.executeFunctionToolCalls = executeFunctionToolCalls;
exports.executeShellActions = executeShellActions;
exports.executeApplyPatchOperations = executeApplyPatchOperations;
exports.executeComputerActions = executeComputerActions;
exports.executeHandoffCalls = executeHandoffCalls;
exports.checkForFinalOutputFromTools = checkForFinalOutputFromTools;
exports.streamStepItemsToRunResult = streamStepItemsToRunResult;
exports.addStepToRunResult = addStepToRunResult;
exports.toInputItemList = toInputItemList;
exports.extractOutputItemsFromRunItems = extractOutputItemsFromRunItems;
exports.saveToSession = saveToSession;
exports.saveStreamInputToSession = saveStreamInputToSession;
exports.saveStreamResultToSession = saveStreamResultToSession;
exports.prepareInputItemsWithSession = prepareInputItemsWithSession;
const agent_1 = require("./agent.js");
const errors_1 = require("./errors.js");
const handoff_1 = require("./handoff.js");
const items_1 = require("./items.js");
const logger_1 = __importDefault(require("./logger.js"));
const tool_1 = require("./tool.js");
const messages_1 = require("./utils/messages.js");
const createSpans_1 = require("./tracing/createSpans.js");
const tools_1 = require("./utils/tools.js");
const base64_1 = require("./utils/base64.js");
const smartString_1 = require("./utils/smartString.js");
const safeExecute_1 = require("./utils/safeExecute.js");
const context_1 = require("./tracing/context.js");
const events_1 = require("./events.js");
const zod_1 = require("zod");
const utils_1 = require("./utils/index.js");
const session_1 = require("./memory/session.js");
const usage_1 = require("./usage.js");
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 itemType === 'function_call' || itemType === 'hosted_tool_call';
}
function getApprovalIdentity(approval) {
const rawItem = approval.rawItem;
if (!rawItem) {
return undefined;
}
if (rawItem.type === 'function_call' && rawItem.callId) {
return `function_call:${rawItem.callId}`;
}
if ('callId' in rawItem && rawItem.callId) {
return `${rawItem.type}:${rawItem.callId}`;
}
const id = 'id' in rawItem ? rawItem.id : undefined;
if (id) {
return `${rawItem.type}:${id}`;
}
const providerData = typeof rawItem.providerData === 'object' && rawItem.providerData
? rawItem.providerData
: undefined;
if (providerData?.id) {
return `${rawItem.type}:provider:${providerData.id}`;
}
const agentName = 'agent' in approval && approval.agent ? approval.agent.name : '';
try {
return `${agentName}:${rawItem.type}:${JSON.stringify(rawItem)}`;
}
catch {
return `${agentName}:${rawItem.type}`;
}
}
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 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)}...`;
}
/**
* @internal
* Walks a raw model response and classifies each item so the runner can schedule follow-up work.
* Returns both the serializable RunItems (for history/streaming) and the actionable tool metadata.
*/
function processModelResponse(modelResponse, agent, tools, handoffs) {
const items = [];
const runHandoffs = [];
const runFunctions = [];
const runComputerActions = [];
const runShellActions = [];
const runApplyPatchActions = [];
const runMCPApprovalRequests = [];
const toolsUsed = [];
const handoffMap = new Map(handoffs.map((h) => [h.toolName, h]));
// Resolve tools upfront so we can look up the concrete handler in O(1) while iterating outputs.
const functionMap = new Map(tools.filter((t) => t.type === 'function').map((t) => [t.name, t]));
const computerTool = tools.find((t) => t.type === 'computer');
const shellTool = tools.find((t) => t.type === 'shell');
const applyPatchTool = tools.find((t) => t.type === 'apply_patch');
const mcpToolMap = new Map(tools
.filter((t) => t.type === 'hosted_tool' && t.providerData?.type === 'mcp')
.map((t) => t)
.map((t) => [t.providerData.server_label, t]));
for (const output of modelResponse.output) {
if (output.type === 'message') {
if (output.role === 'assistant') {
items.push(new items_1.RunMessageOutputItem(output, agent));
}
}
else if (output.type === 'hosted_tool_call') {
items.push(new items_1.RunToolCallItem(output, agent));
const toolName = output.name;
toolsUsed.push(toolName);
if (output.providerData?.type === 'mcp_approval_request' ||
output.name === 'mcp_approval_request') {
// Hosted remote MCP server's approval process
const providerData = output.providerData;
const mcpServerLabel = providerData.server_label;
const mcpServerTool = mcpToolMap.get(mcpServerLabel);
if (typeof mcpServerTool === 'undefined') {
const message = `MCP server (${mcpServerLabel}) not found in Agent (${agent.name})`;
(0, context_1.addErrorToCurrentSpan)({
message,
data: { mcp_server_label: mcpServerLabel },
});
throw new errors_1.ModelBehaviorError(message);
}
// Do this approval later:
// We support both onApproval callback (like the Python SDK does) and HITL patterns.
const approvalItem = new items_1.RunToolApprovalItem({
type: 'hosted_tool_call',
// We must use this name to align with the name sent from the servers
name: providerData.name,
id: providerData.id,
status: 'in_progress',
providerData,
}, agent);
runMCPApprovalRequests.push({
requestItem: approvalItem,
mcpTool: mcpServerTool,
});
if (!mcpServerTool.providerData.on_approval) {
// When onApproval function exists, it confirms the approval right after this.
// Thus, this approval item must be appended only for the next turn interruption patterns.
items.push(approvalItem);
}
}
}
else if (output.type === 'reasoning') {
items.push(new items_1.RunReasoningItem(output, agent));
}
else if (output.type === 'computer_call') {
items.push(new items_1.RunToolCallItem(output, agent));
toolsUsed.push('computer_use');
if (!computerTool) {
(0, context_1.addErrorToCurrentSpan)({
message: 'Model produced computer action without a computer tool.',
data: {
agent_name: agent.name,
},
});
throw new errors_1.ModelBehaviorError('Model produced computer action without a computer tool.');
}
runComputerActions.push({
toolCall: output,
computer: computerTool,
});
}
else if (output.type === 'shell_call') {
items.push(new items_1.RunToolCallItem(output, agent));
toolsUsed.push('shell');
if (!shellTool) {
(0, context_1.addErrorToCurrentSpan)({
message: 'Model produced shell action without a shell tool.',
data: {
agent_name: agent.name,
},
});
throw new errors_1.ModelBehaviorError('Model produced shell action without a shell tool.');
}
runShellActions.push({
toolCall: output,
shell: shellTool,
});
}
else if (output.type === 'apply_patch_call') {
items.push(new items_1.RunToolCallItem(output, agent));
toolsUsed.push('apply_patch');
if (!applyPatchTool) {
(0, context_1.addErrorToCurrentSpan)({
message: 'Model produced apply_patch action without an apply_patch tool.',
data: {
agent_name: agent.name,
},
});
throw new errors_1.ModelBehaviorError('Model produced apply_patch action without an apply_patch tool.');
}
runApplyPatchActions.push({
toolCall: output,
applyPatch: applyPatchTool,
});
}
if (output.type !== 'function_call') {
continue;
}
toolsUsed.push(output.name);
const handoff = handoffMap.get(output.name);
if (handoff) {
items.push(new items_1.RunHandoffCallItem(output, agent));
runHandoffs.push({
toolCall: output,
handoff: handoff,
});
}
else {
const functionTool = functionMap.get(output.name);
if (!functionTool) {
(0, context_1.addErrorToCurrentSpan)({
message: `Tool ${output.name} not found in agent ${agent.name}.`,
data: {
tool_name: output.name,
agent_name: agent.name,
},
});
throw new errors_1.ModelBehaviorError(`Tool ${output.name} not found in agent ${agent.name}.`);
}
items.push(new items_1.RunToolCallItem(output, agent));
runFunctions.push({
toolCall: output,
tool: functionTool,
});
}
}
return {
newItems: items,
handoffs: runHandoffs,
functions: runFunctions,
computerActions: runComputerActions,
shellActions: runShellActions,
applyPatchActions: runApplyPatchActions,
mcpApprovalRequests: runMCPApprovalRequests,
toolsUsed: toolsUsed,
hasToolsOrApprovalsToRun() {
return (runHandoffs.length > 0 ||
runFunctions.length > 0 ||
runMCPApprovalRequests.length > 0 ||
runComputerActions.length > 0 ||
runShellActions.length > 0 ||
runApplyPatchActions.length > 0);
},
};
}
exports.nextStepSchema = zod_1.z.discriminatedUnion('type', [
zod_1.z.object({
type: zod_1.z.literal('next_step_handoff'),
newAgent: zod_1.z.any(),
}),
zod_1.z.object({
type: zod_1.z.literal('next_step_final_output'),
output: zod_1.z.string(),
}),
zod_1.z.object({
type: zod_1.z.literal('next_step_run_again'),
}),
zod_1.z.object({
type: zod_1.z.literal('next_step_interruption'),
data: zod_1.z.record(zod_1.z.string(), zod_1.z.any()),
}),
]);
/**
* Internal convenience wrapper that groups the outcome of a single agent turn. It lets the caller
* update the RunState in one shot and decide which step to execute next.
*/
class SingleStepResult {
originalInput;
modelResponse;
preStepItems;
newStepItems;
nextStep;
constructor(
/**
* The input items (i.e., the items before run() was called). May be mutated by handoff input filters.
*/
originalInput,
/**
* The model response for the current step
*/
modelResponse,
/**
* The items before the current step was executed
*/
preStepItems,
/**
* The items after the current step was executed
*/
newStepItems,
/**
* The next step to execute
*/
nextStep) {
this.originalInput = originalInput;
this.modelResponse = modelResponse;
this.preStepItems = preStepItems;
this.newStepItems = newStepItems;
this.nextStep = nextStep;
}
/**
* The items generated during the agent run (i.e. everything generated after originalInput)
*/
get generatedItems() {
return this.preStepItems.concat(this.newStepItems);
}
}
/**
* @internal
* Resets the tool choice when the agent is configured to prefer a fresh tool selection after
* any tool usage. This prevents the provider from reusing stale tool hints across turns.
*/
function maybeResetToolChoice(agent, toolUseTracker, modelSettings) {
if (agent.resetToolChoice && toolUseTracker.hasUsedTools(agent)) {
return { ...modelSettings, toolChoice: undefined };
}
return modelSettings;
}
/**
* @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) {
// call_ids for function tools
const functionCallIds = originalPreStepItems
.filter((item) => item instanceof items_1.RunToolApprovalItem &&
'callId' in item.rawItem &&
item.rawItem.type === 'function_call')
.map((item) => item.rawItem.callId);
// 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);
if (pendingApprovalItems.length > 0) {
const pendingApprovalIdentities = new Set();
for (const approval of pendingApprovalItems) {
const identity = getApprovalIdentity(approval);
if (identity) {
pendingApprovalIdentities.add(identity);
}
}
if (pendingApprovalIdentities.size > 0) {
let rewindCount = 0;
for (let index = originalPreStepItems.length - 1; index >= 0; index--) {
const item = originalPreStepItems[index];
if (!(item instanceof items_1.RunToolApprovalItem)) {
continue;
}
const identity = getApprovalIdentity(item);
if (!identity) {
continue;
}
if (!pendingApprovalIdentities.has(identity)) {
continue;
}
rewindCount++;
pendingApprovalIdentities.delete(identity);
if (pendingApprovalIdentities.size === 0) {
break;
}
}
// 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.
if (rewindCount > 0) {
state._currentTurnPersistedItemCount = Math.max(0, state._currentTurnPersistedItemCount - rewindCount);
}
}
}
// Run function tools that require approval after they get their approval results
const functionToolRuns = processedResponse.functions.filter((run) => {
return functionCallIds.includes(run.toolCall.callId);
});
const functionResults = await executeFunctionToolCalls(agent, functionToolRuns, runner, state);
// There is no built-in HITL approval surface for computer tools today, so every pending action
// is executed immediately when the turn resumes.
const computerResults = processedResponse.computerActions.length > 0
? await executeComputerActions(agent, processedResponse.computerActions, runner, state._context)
: [];
// When resuming we receive the original RunItem references; suppress duplicates so history and streaming do not double-emit the same items.
const originalPreStepItemSet = new Set(originalPreStepItems);
const newItems = [];
const newItemsSet = new Set();
const appendIfNew = (item) => {
if (originalPreStepItemSet.has(item) || newItemsSet.has(item)) {
return;
}
newItems.push(item);
newItemsSet.add(item);
};
for (const result of functionResults) {
appendIfNew(result.runItem);
}
for (const result of computerResults) {
appendIfNew(result);
}
// Run MCP tools that require approval after they get their approval results
const mcpApprovalRuns = processedResponse.mcpApprovalRequests.filter((run) => {
return (run.requestItem.type === 'tool_approval_item' &&
run.requestItem.rawItem.type === 'hosted_tool_call' &&
run.requestItem.rawItem.providerData?.type === 'mcp_approval_request');
});
// Hosted MCP approvals may still be waiting on a human decision when the turn resumes.
const pendingHostedMCPApprovals = new Set();
const pendingHostedMCPApprovalIds = new Set();
// Keep track of approvals we still need to surface next turn so HITL flows can resume cleanly.
for (const run of mcpApprovalRuns) {
// the approval_request_id "mcpr_123..."
const rawItem = run.requestItem.rawItem;
if (rawItem.type !== 'hosted_tool_call') {
continue;
}
const approvalRequestId = rawItem.id;
const approved = state._context.isToolApproved({
// Since this item name must be the same with the one sent from Responses API server
toolName: rawItem.name,
callId: approvalRequestId,
});
if (typeof approved !== 'undefined') {
const providerData = {
approve: approved,
approval_request_id: approvalRequestId,
reason: undefined,
};
// Tell Responses API server the approval result in the next turn
const responseItem = new items_1.RunToolCallItem({
type: 'hosted_tool_call',
name: 'mcp_approval_response',
providerData,
}, agent);
appendIfNew(responseItem);
}
else {
pendingHostedMCPApprovals.add(run.requestItem);
pendingHostedMCPApprovalIds.add(approvalRequestId);
functionResults.push({
type: 'hosted_mcp_tool_approval',
tool: run.mcpTool,
runItem: run.requestItem,
});
appendIfNew(run.requestItem);
}
}
// 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 (item.rawItem.type === 'hosted_tool_call' &&
item.rawItem.providerData?.type === 'mcp_approval_request') {
if (pendingHostedMCPApprovals.has(item)) {
return true;
}
const approvalRequestId = item.rawItem.id;
if (approvalRequestId) {
return pendingHostedMCPApprovalIds.has(approvalRequestId);
}
return false;
}
return false;
});
const completedStep = await maybeCompleteTurnFromToolResults({
agent,
runner,
state,
functionResults,
originalInput,
newResponse,
preStepItems,
newItems,
});
if (completedStep) {
return completedStep;
}
// we only ran new tools and side effects. We need to run the rest of the agent
return new 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) {
// 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 seenItems = new Set(originalPreStepItems);
const newItems = [];
const appendIfNew = (item) => {
if (seenItems.has(item)) {
return;
}
newItems.push(item);
seenItems.add(item);
};
for (const item of processedResponse.newItems) {
appendIfNew(item);
}
// Run function tools and computer actions in parallel; neither depends on the other's side effects.
const [functionResults, computerResults, shellResults, applyPatchResults] = await Promise.all([
executeFunctionToolCalls(agent, processedResponse.functions, runner, state),
executeComputerActions(agent, processedResponse.computerActions, runner, state._context),
executeShellActions(agent, processedResponse.shellActions, runner, state._context),
executeApplyPatchOperations(agent, processedResponse.applyPatchActions, runner, state._context),
]);
for (const result of functionResults) {
appendIfNew(result.runItem);
}
for (const item of computerResults) {
appendIfNew(item);
}
for (const item of shellResults) {
appendIfNew(item);
}
for (const item of applyPatchResults) {
appendIfNew(item);
}
// run hosted MCP approval requests
if (processedResponse.mcpApprovalRequests.length > 0) {
for (const approvalRequest of processedResponse.mcpApprovalRequests) {
const toolData = approvalRequest.mcpTool
.providerData;
const requestData = approvalRequest.requestItem.rawItem
.providerData;
if (toolData.on_approval) {
// synchronously handle the approval process here
const approvalResult = await toolData.on_approval(state._context, approvalRequest.requestItem);
const approvalResponseData = {
approve: approvalResult.approve,
approval_request_id: requestData.id,
reason: approvalResult.reason,
};
newItems.push(new items_1.RunToolCallItem({
type: 'hosted_tool_call',
name: 'mcp_approval_response',
providerData: approvalResponseData,
}, agent));
}
else {
// receive a user's approval on the next turn
newItems.push(approvalRequest.requestItem);
const approvalItem = {
type: 'hosted_mcp_tool_approval',
tool: approvalRequest.mcpTool,
runItem: new items_1.RunToolApprovalItem({
type: 'hosted_tool_call',
name: requestData.name,
id: requestData.id,
arguments: requestData.arguments,
status: 'in_progress',
providerData: requestData,
}, agent),
};
functionResults.push(approvalItem);
// newItems.push(approvalItem.runItem);
}
}
}
// process handoffs
if (processedResponse.handoffs.length > 0) {
return await executeHandoffCalls(agent, originalInput, preStepItems, newItems, newResponse, processedResponse.handoffs, runner, state._context);
}
const completedStep = await maybeCompleteTurnFromToolResults({
agent,
runner,
state,
functionResults,
originalInput,
newResponse,
preStepItems,
newItems,
});
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.
const hadToolCallsOrActions = (processedResponse.functions?.length ?? 0) > 0 ||
(processedResponse.computerActions?.length ?? 0) > 0 ||
(processedResponse.shellActions?.length ?? 0) > 0 ||
(processedResponse.applyPatchActions?.length ?? 0) > 0 ||
(processedResponse.mcpApprovalRequests?.length ?? 0) > 0 ||
(processedResponse.handoffs?.length ?? 0) > 0;
if (hadToolCallsOrActions) {
return new 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);
// we will use the last content output as the final output
const potentialFinalOutput = messageItems.length > 0
? (0, messages_1.getLastTextFromOutputMessage)(messageItems[messageItems.length - 1].rawItem)
: undefined;
// if there is no output we just run again
if (typeof potentialFinalOutput === 'undefined') {
return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
}
// Keep looping if any tool output placeholders still require an approval follow-up.
const hasPendingToolsOrApprovals = functionResults.some((result) => result.runItem instanceof items_1.RunToolApprovalItem);
if (!hasPendingToolsOrApprovals) {
if (agent.outputType === 'text') {
return new 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 { parser } = (0, tools_1.getSchemaAndParserFromInputType)(agent.outputType, 'final_output');
const [error] = await (0, safeExecute_1.safeExecute)(() => parser(potentialFinalOutput));
if (error) {
const outputErrorMessage = formatFinalOutputTypeError(error);
(0, context_1.addErrorToCurrentSpan)({
message: outputErrorMessage,
data: {
error: String(error),
},
});
throw new errors_1.ModelBehaviorError(outputErrorMessage);
}
return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_final_output', output: potentialFinalOutput });
}
}
return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, { type: 'next_step_run_again' });
}
// 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, state, functionResults, originalInput, newResponse, preStepItems, newItems, }) {
const toolOutcome = await checkForFinalOutputFromTools(agent, functionResults, state);
if (toolOutcome.isFinalOutput) {
runner.emit('agent_end', state._context, agent, toolOutcome.finalOutput);
agent.emit('agent_end', state._context, toolOutcome.finalOutput);
return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
type: 'next_step_final_output',
output: toolOutcome.finalOutput,
});
}
if (toolOutcome.isInterrupted) {
return new SingleStepResult(originalInput, newResponse, preStepItems, newItems, {
type: 'next_step_interruption',
data: {
interruptions: toolOutcome.interruptions,
},
});
}
return null;
}
/**
* @internal
* Normalizes tool outputs once so downstream code works with fully structured protocol items.
* Doing this here keeps API surface stable even when providers add new shapes.
*/
function getToolCallOutputItem(toolCall, output) {
const maybeStructuredOutputs = normalizeStructuredToolOutputs(output);
if (maybeStructuredOutputs) {
const structuredItems = maybeStructuredOutputs.map(convertStructuredToolOutputToInputItem);
return {
type: 'function_call_result',
name: toolCall.name,
callId: toolCall.callId,
status: 'completed',
output: structuredItems,
};
}
return {
type: 'function_call_result',
name: toolCall.name,
callId: toolCall.callId,
status: 'completed',
output: {
type: 'text',
text: (0, smartString_1.toSmartString)(output),
},
};
}
function normalizeFileValue(value) {
const directFile = value.file;
if (typeof directFile === 'string' && directFile.length > 0) {
return directFile;
}
const normalizedObject = normalizeFileObjectCandidate(directFile);
if (normalizedObject) {
return normalizedObject;
}
const legacyValue = normalizeLegacyFileValue(value);
if (legacyValue) {
return legacyValue;
}
return null;
}
function normalizeFileObjectCandidate(value) {
if (!isRecord(value)) {
return null;
}
if ('data' in value && value.data !== undefined) {
const dataValue = value.data;
const hasStringData = typeof dataValue === 'string' && dataValue.length > 0;
const hasBinaryData = dataValue instanceof Uint8Array && dataValue.length > 0;
if (!hasStringData && !hasBinaryData) {
return null;
}
if (!isNonEmptyString(value.mediaType) ||
!isNonEmptyString(value.filename)) {
return null;
}
return {
data: typeof dataValue === 'string' ? dataValue : new Uint8Array(dataValue),
mediaType: value.mediaType,
filename: value.filename,
};
}
if (isNonEmptyString(value.url)) {
const result = { url: value.url };
if (isNonEmptyString(value.filename)) {
result.filename = value.filename;
}
return result;
}
const referencedId = (isNonEmptyString(value.id) && value.id) ||
(isNonEmptyString(value.fileId) && value.fileId);
if (referencedId) {
const result = { id: referencedId };
if (isNonEmptyString(value.filename)) {
result.filename = value.filename;
}
return result;
}
return null;
}
function normalizeLegacyFileValue(value) {
const filename = typeof value.filename === 'string' && value.filename.length > 0
? value.filename
: undefined;
const mediaType = typeof value.mediaType === 'string' && value.mediaType.length > 0
? value.mediaType
: undefined;
if (typeof value.fileData === 'string' && value.fileData.length > 0) {
if (!mediaType || !filename) {
return null;
}
return { data: value.fileData, mediaType, filename };
}
if (value.fileData instanceof Uint8Array && value.fileData.length > 0) {
if (!mediaType || !filename) {
return null;
}
return { data: new Uint8Array(value.fileData), mediaType, filename };
}
if (typeof value.fileUrl === 'string' && value.fileUrl.length > 0) {
const result = { url: value.fileUrl };
if (filename) {
result.filename = filename;
}
return result;
}
if (typeof value.fileId === 'string' && value.fileId.length > 0) {
const result = { id: value.fileId };
if (filename) {
result.filename = filename;
}
return result;
}
return null;
}
function isRecord(value) {
return typeof value === 'object' && value !== null;
}
function isNonEmptyString(value) {
return typeof value === 'string' && value.length > 0;
}
function toInlineImageString(data, mediaType) {
if (typeof data === 'string') {
if (mediaType && !data.startsWith('data:')) {
return asDataUrl(data, mediaType);
}
return data;
}
const base64 = (0, base64_1.encodeUint8ArrayToBase64)(data);
return asDataUrl(base64, mediaType);
}
function asDataUrl(base64, mediaType) {
return mediaType ? `data:${mediaType};base64,${base64}` : base64;
}
/**
* @internal
* Runs every function tool call requested by the model and returns their outputs alongside
* the `RunItem` instances that should be appended to history.
*/
async function executeFunctionToolCalls(agent, toolRuns, runner, state) {
async function runSingleTool(toolRun) {
let parsedArgs = toolRun.toolCall.arguments;
if (toolRun.tool.parameters) {
if ((0, utils_1.isZodObject)(toolRun.tool.parameters)) {
parsedArgs = toolRun.tool.parameters.parse(parsedArgs);
}
else {
parsedArgs = JSON.parse(parsedArgs);
}
}
// Some tools require a human or policy check before execution; defer until approval is recorded.
const needsApproval = await toolRun.tool.needsApproval(state._context, parsedArgs, toolRun.toolCall.callId);
if (needsApproval) {
const approval = state._context.isToolApproved({
toolName: toolRun.tool.name,
callId: toolRun.toolCall.callId,
});
if (approval === false) {
// rejected
return (0, createSpans_1.withFunctionSpan)(async (span) => {
const response = 'Tool execution was not approved.';
span.setError({
message: response,
data: {
tool_name: toolRun.tool.name,
error: `Tool execution for ${toolRun.toolCall.callId} was manually rejected by user.`,
},
});
span.spanData.output = response;
return {
type: 'function_output',
tool: toolRun.tool,
output: response,
runItem: new items_1.RunToolCallOutputItem(getToolCallOutputItem(toolRun.toolCall, response), agent, response),
};
}, {
data: {
name: toolRun.tool.name,
},
});
}
if (approval !== true) {
// this approval process needs to be done in the next turn
return {
type: 'function_approval',
tool: toolRun.tool,
runItem: new items_1.RunToolApprovalItem(toolRun.toolCall, agent),
};
}
}
return (0, createSpans_1.withFunctionSpan)(async (span) => {
if (runner.config.traceIncludeSensitiveData) {
span.spanData.input = toolRun.toolCall.arguments;
}
try {
runner.emit('agent_tool_start', state._context, agent, toolRun.tool, {
toolCall: toolRun.toolCall,
});
agent.emit('agent_tool_start', state._context, toolRun.tool, {
toolCall: toolRun.toolCall,
});
const toolOutput = await toolRun.tool.invoke(state._context, toolRun.toolCall.arguments, { toolCall: toolRun.toolCall });
// Use string data for tracing and event emitter
const stringResult = (0, smartString_1.toSmartString)(toolOutput);
runner.emit('agent_tool_end', state._context, agent, toolRun.tool, stringResult, { toolCall: toolRun.toolCall });
agent.emit('agent_tool_end', state._context, toolRun.tool, stringResult, { toolCall: toolRun.toolCall });
if (runner.config.traceIncludeSensitiveData) {
span.spanData.output = stringResult;
}
const functionResult = {
type: 'function_output',
tool: toolRun.tool,
output: toolOutput,
runItem: new items_1.RunToolCallOutputItem(getToolCallOutputItem(toolRun.toolCall, toolOutput), agent, toolOutput),
};
const nestedRunResult = (0, agent_1.consumeAgentToolRunResult)(toolRun.toolCall);
if (nestedRunResult) {
functionResult.agentRunResult = nestedRunResult;
const nestedInterruptions = nestedRunResult.interruptions;
if (nestedInterruptions.length > 0) {
functionResult.interruptions = nestedInterruptions;
}
}
return functionResult;
}
catch (error) {
span.setError({
message: 'Error running tool',
data: {
tool_name: toolRun.tool.name,
error: String(error),
},
});
// Emit agent_tool_end even on error to maintain consistent event lifecycle
const errorResult = String(error);
runner.emit('agent_tool_end', state._context, agent, toolRun.tool, errorResult, {
toolCall: toolRun.toolCall,
});
agent.emit('agent_tool_end', state._context, toolRun.tool, errorResult, {
toolCall: toolRun.toolCall,
});
throw error;
}
}, {
data: {
name: toolRun.tool.name,
},
});
}
try {
const results = await Promise.all(toolRuns.map(runSingleTool));
return results;
}
catch (e) {
throw new errors_1.ToolCallError(`Failed to run function tools: ${e}`, e, state);
}
}
/**
* @internal
*/
// Internal helper: dispatch a computer action and return a screenshot (sync/async)
async function _runComputerActionAndScreenshot(computer, toolCall) {
const action = toolCall.action;
let screenshot;
// Dispatch based on action type string (assume action.type exists)
switch (action.type) {
case 'click':
await computer.click(action.x, action.y, action.button);
break;
case 'double_click':
await computer.doubleClick(action.x, action.y);
break;
case 'drag':
await computer.drag(action.path.map((p) => [p.x, p.y]));
break;
case 'keypress':
await computer.keypress(action.keys);
break;
case 'move':
await computer.move(action.x, action.y);
break;
case 'screenshot':
screenshot = await computer.screenshot();
break;
case 'scroll':
await computer.scroll(action.x, action.y, action.scroll_x, action.scroll_y);
break;
case 'type':
await computer.type(action.text);
break;
case 'wait':
await computer.wait();
break;
default:
action; // ensures that we handle every action we know of
// Unknown action, just take screenshot
break;
}
if (typeof screenshot !== 'undefined') {
return screenshot;
}
// Always return screenshot as base64 string
if (typeof computer.screenshot === 'function') {
screenshot = await computer.screenshot();
if (typeof screenshot !== 'undefined') {
return screenshot;
}
}
throw new Error('Computer does not implement screenshot()');
}
function toErrorMessage(error) {
if (error instanceof Error) {
return error.message || error.toString();
}
try {
return JSON.stringify(error);
}
catch {
return String(error);
}
}
async function executeShellActions(agent, actions, runner, runContext, customLogger = undefined) {
const _logger = customLogger ?? logger_1.default;
const results = [];
for (const action of actions) {
const shellTool = action.shell;
const toolCall = action.toolCall;
const approvalItem = new items_1.RunToolApprovalItem(toolCall, agent, shellTool.name);
const requiresApproval = await shellTool.needsApproval(runContext, toolCall.action, toolCall.callId);
if (requiresApproval) {
if (shellTool.onApproval) {
const decision = await shellTool.onApproval(runContext, approvalItem);
if (decision.approve === true) {
runContext.approveTool(approvalItem);
}
else if (decision.approve === false) {
runContext.rejectTool(approvalItem);
}
}
const approval = runContext.isToolApproved({
toolName: shellTool.name,
callId: toolCall.callId,
});
if (approval === false) {
const response = 'Tool execution was not approved.';
const rejectionOutput = {
stdout: '',
stderr: response,
outcome: { type: 'exit', exitCode: null },
};
results.push(new items_1.RunToolCallOutputItem({
type: 'shell_call_output',
callId: toolCall.callId,
output: [rejectionOutput],
}, agent, response));
continue;
}
if (approval !== true) {
results.push(approvalItem);
continue;
}
}
runner.emit('agent_tool_start', runContext, agent, shellTool, {
toolCall,
});
if (typeof agent.emit === 'function') {
agent.emit('agent_tool_start', runContext, shellTool, { toolCall });
}
let shellOutputs;
const providerMeta = {};
let maxOutputLength;
try {
const shellResult = await shellTool.shell.run(toolCall.action);
shellOutputs = shellResult.output ?? [];
if (shellResult.providerData) {
Object.assign(providerMeta, shellResult.providerData);
}
if (typeof shellResult.maxOutputLength === 'number') {
maxOutputLength = shellResult.maxOutputLength;
}
}
catch (err) {
const errorText = toErrorMessage(err);
shellOutputs = [
{
stdout: '',
stderr: errorText,
outcome: { type: 'exit', exitCode: null },
},
];
_logger.error('Failed to execute shell action:', err);
}
shellOutputs = shellOutputs ?? [];
runner.emit('agent_tool_end', runContext, agent, shellTool, JSON.stringify(shellOutputs), {
toolCall,
});
if (typeof agent.emit === 'function') {
agent.emit('agent_tool_end', runContext, shellTool, JSON.stringify(shellOutputs), {
toolCall,
});
}
const rawItem = {
type: 'shell_call_output',
callId: toolCall.callId,
output: shellOutputs ?? [],
};
if (typeof maxOutputLength === 'number') {
rawItem.maxOutputLength = maxOutputLength;
}
if (Object.keys(providerMeta).length > 0) {
rawItem.providerData = providerMeta;
}
results.push(new items_1.RunToolCallOutputItem(rawItem, agent, rawItem.output));
}
return results;
}
async function executeApplyPatchOperations(agent, actions, runner, runContext, customLogger = undefined) {
const _logger = customLogger ?? logger_1.default;
const results = [];
for (const action of actions) {
const applyPatchTool = action.applyPatch;
const toolCall = action.toolCall;
const approvalItem = new items_1.RunToolApprovalItem(toolCall, agent, applyPatchTool.name);
const requiresApproval = await applyPatchTool.needsApproval(runContext, toolCall.operation, toolCall.callId);
if (requiresApproval) {
if (applyPatchTool.onApproval) {
const decision = await applyPatchTool.onApproval(runContext, approvalItem);
if (decision.approve === true) {
runContext.approveTool(approvalItem);
}
else if (decision.approve === false) {
runContext.rejectTool(approvalItem);
}
}
const approval = runContext.isToolApproved({
toolName: applyPatchTool.name,
callId: toolCall.callId,
});
if (approval === false) {
const response = 'Tool execution was not approved.';
results.push(new items_1.RunToolCallOutputItem({
type: 'apply_patch_call_output',
callId: toolCall.callId,
status: 'failed',
output: response,
}, agent, response));
continue;
}
if (approval !== true) {
results.push(approvalItem);
continue;
}
}
runner.emit('agent_tool_start', runContext, agent, applyPatchTool, {
toolCall,
});
if (typeof agent.emit === 'function') {
agent.emit('agent_tool_start', runContext, applyPatchTool, {
toolCall,
});
}
let status = 'completed';
let output = '';
try {
let result;
switch (toolCall.operation.type) {
case 'create_file':
result = await applyPatchTool.editor.createFile(toolCall.operation);
break;
case 'update_file':
result = await applyPatchTool.editor.updateFile(toolCall.operation);
break;
case 'delete_file':
result = await applyPatchTool.editor.deleteFile(toolCall.operation);
break;
default:
throw new Error('Unsupported apply_patch operation');
}
if (result && typeof result.status === 'string') {
status = result.status;
}
if (result && typeof result.output === 'string') {
output = result.output;
}
}
catch (err) {
status = 'failed';
output = toErrorMessage(err);
_logger.error('Failed to execute apply_patch operation:', err);
}
runner.emit('agent_tool_end', runContext, agent, applyPatchTool, output, {
toolCall,
});
if (typeof agent.emit === 'function') {
a