@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
1,244 lines • 51.6 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.getToolCallOutputItem = getToolCallOutputItem;
exports.executeFunctionToolCalls = executeFunctionToolCalls;
exports.executeShellActions = executeShellActions;
exports.executeApplyPatchOperations = executeApplyPatchOperations;
exports.executeComputerActions = executeComputerActions;
exports.executeHandoffCalls = executeHandoffCalls;
exports.collectInterruptions = collectInterruptions;
exports.checkForFinalOutputFromTools = checkForFinalOutputFromTools;
const agentToolRunResults_1 = require("../agentToolRunResults.js");
const errors_1 = require("../errors.js");
const handoff_1 = require("../handoff.js");
const items_1 = require("../items.js");
const message_1 = require("../helpers/message.js");
const logger_1 = __importDefault(require("../logger.js"));
const tool_1 = require("../tool.js");
const base64_1 = require("../utils/base64.js");
const smartString_1 = require("../utils/smartString.js");
const utils_1 = require("../utils/index.js");
const createSpans_1 = require("../tracing/createSpans.js");
const context_1 = require("../tracing/context.js");
const toolGuardrails_1 = require("../utils/toolGuardrails.js");
const steps_1 = require("./steps.js");
const TOOL_APPROVAL_REJECTION_MESSAGE = 'Tool execution was not approved.';
const REDACTED_TOOL_ERROR_MESSAGE = 'Tool execution failed. Error details are redacted.';
// 1x1 transparent PNG data URL used for rejected computer actions.
const TOOL_APPROVAL_REJECTION_SCREENSHOT_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4z8DwHwAFAAH/iZk9HQAAAABJRU5ErkJggg==';
async function resolveApprovalRejectionMessage({ runContext, toolType, toolName, callId, toolErrorFormatter, }) {
if (!toolErrorFormatter) {
return TOOL_APPROVAL_REJECTION_MESSAGE;
}
try {
const formattedMessage = await toolErrorFormatter({
kind: 'approval_rejected',
toolType,
toolName,
callId,
defaultMessage: TOOL_APPROVAL_REJECTION_MESSAGE,
runContext,
});
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 approval rejection message.');
}
}
catch (error) {
logger_1.default.warn(`toolErrorFormatter threw while formatting approval rejection: ${toErrorMessage(error)}`);
}
return TOOL_APPROVAL_REJECTION_MESSAGE;
}
/**
* @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),
},
};
}
/**
* @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, toolErrorFormatter) {
const deps = {
agent,
runner,
state,
toolErrorFormatter,
};
try {
const results = await Promise.all(toolRuns.map(async (toolRun) => {
const parseResult = parseToolArguments(toolRun);
// Handle parse errors gracefully instead of crashing
if (!parseResult.success) {
return buildParseErrorResult(deps, toolRun, parseResult.error);
}
const approvalOutcome = await handleFunctionApproval(deps, toolRun, parseResult.args);
if (approvalOutcome !== 'approved') {
return approvalOutcome;
}
return runApprovedFunctionTool(deps, toolRun);
}));
return results;
}
catch (e) {
if (e instanceof errors_1.ToolTimeoutError) {
e.state ??= state;
throw e;
}
throw new errors_1.ToolCallError(`Failed to run function tools: ${e}`, e, state);
}
}
function parseToolArguments(toolRun) {
try {
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);
}
}
return { success: true, args: parsedArgs };
}
catch (error) {
logger_1.default.debug(`Failed to parse tool arguments for ${toolRun.tool.name}: ${error}`);
return { success: false, error: error };
}
}
function buildApprovalRequestResult(deps, toolRun) {
return {
type: 'function_approval',
tool: toolRun.tool,
runItem: new items_1.RunToolApprovalItem(toolRun.toolCall, deps.agent),
};
}
function buildParseErrorResult(deps, toolRun, error) {
const errorMessage = `An error occurred while parsing tool arguments. Please try again with valid JSON. Error: ${error.message}`;
return {
type: 'function_output',
tool: toolRun.tool,
output: errorMessage,
runItem: new items_1.RunToolCallOutputItem(getToolCallOutputItem(toolRun.toolCall, errorMessage), deps.agent, errorMessage),
};
}
async function buildApprovalRejectionResult(deps, toolRun) {
const { agent, runner, state, toolErrorFormatter } = deps;
return withToolFunctionSpan(runner, toolRun.tool.name, async (span) => {
const response = await resolveApprovalRejectionMessage({
runContext: state._context,
toolType: 'function',
toolName: toolRun.tool.name,
callId: toolRun.toolCall.callId,
toolErrorFormatter,
});
const traceErrorMessage = runner.config.traceIncludeSensitiveData
? response
: TOOL_APPROVAL_REJECTION_MESSAGE;
span?.setError({
message: traceErrorMessage,
data: {
tool_name: toolRun.tool.name,
error: `Tool execution for ${toolRun.toolCall.callId} was manually rejected by user.`,
},
});
if (span && runner.config.traceIncludeSensitiveData) {
span.spanData.output = response;
}
return {
type: 'function_output',
tool: toolRun.tool,
output: response,
runItem: new items_1.RunToolCallOutputItem(getToolCallOutputItem(toolRun.toolCall, response), agent, response),
};
});
}
async function handleFunctionApproval(deps, toolRun, parsedArgs) {
const { state } = deps;
const needsApproval = await toolRun.tool.needsApproval(state._context, parsedArgs, toolRun.toolCall.callId);
if (!needsApproval) {
return 'approved';
}
const approval = state._context.isToolApproved({
toolName: toolRun.tool.name,
callId: toolRun.toolCall.callId,
});
if (approval === false) {
state.clearPendingAgentToolRun(toolRun.tool.name, toolRun.toolCall.callId);
return await buildApprovalRejectionResult(deps, toolRun);
}
if (approval !== true) {
return buildApprovalRequestResult(deps, toolRun);
}
return 'approved';
}
async function runApprovedFunctionTool(deps, toolRun) {
const { agent, runner, state } = deps;
return withToolFunctionSpan(runner, toolRun.tool.name, async (span) => {
if (span && runner.config.traceIncludeSensitiveData) {
span.spanData.input = toolRun.toolCall.arguments;
}
try {
const inputGuardrailResult = await (0, toolGuardrails_1.runToolInputGuardrails)({
guardrails: toolRun.tool.inputGuardrails,
context: state._context,
agent,
toolCall: toolRun.toolCall,
onResult: (result) => {
state._toolInputGuardrailResults.push(result);
},
});
emitToolStart(runner, state._context, agent, toolRun.tool, toolRun.toolCall);
let toolOutput;
if (inputGuardrailResult.type === 'reject') {
toolOutput = inputGuardrailResult.message;
}
else {
const resumeState = state.getPendingAgentToolRun(toolRun.tool.name, toolRun.toolCall.callId);
toolOutput = await (0, tool_1.invokeFunctionTool)({
tool: toolRun.tool,
runContext: state._context,
input: toolRun.toolCall.arguments,
details: { toolCall: toolRun.toolCall, resumeState },
});
toolOutput = await (0, toolGuardrails_1.runToolOutputGuardrails)({
guardrails: toolRun.tool.outputGuardrails,
context: state._context,
agent,
toolCall: toolRun.toolCall,
toolOutput,
onResult: (result) => {
state._toolOutputGuardrailResults.push(result);
},
});
}
const stringResult = (0, smartString_1.toSmartString)(toolOutput);
emitToolEnd(runner, state._context, agent, toolRun.tool, stringResult, toolRun.toolCall);
if (span && 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, agentToolRunResults_1.consumeAgentToolRunResult)(toolRun.toolCall);
if (nestedRunResult) {
functionResult.agentRunResult = nestedRunResult;
const nestedInterruptions = nestedRunResult.interruptions;
if (nestedInterruptions.length > 0) {
functionResult.interruptions = nestedInterruptions;
state.setPendingAgentToolRun(toolRun.tool.name, toolRun.toolCall.callId, nestedRunResult.state.toString());
}
else {
state.clearPendingAgentToolRun(toolRun.tool.name, toolRun.toolCall.callId);
}
}
return functionResult;
}
catch (error) {
span?.setError({
message: 'Error running tool',
data: {
tool_name: toolRun.tool.name,
error: String(error),
},
});
const errorResult = String(error);
emitToolEnd(runner, state._context, agent, toolRun.tool, errorResult, toolRun.toolCall);
throw error;
}
});
}
/**
* @internal
*/
// Internal helper: dispatch a computer action and return a screenshot (sync/async)
async function _runComputerActionAndScreenshot(computer, toolCall, runContext) {
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, runContext);
break;
case 'double_click':
await computer.doubleClick(action.x, action.y, runContext);
break;
case 'drag':
await computer.drag(action.path.map((p) => [p.x, p.y]), runContext);
break;
case 'keypress':
await computer.keypress(action.keys, runContext);
break;
case 'move':
await computer.move(action.x, action.y, runContext);
break;
case 'screenshot':
screenshot = await computer.screenshot(runContext);
break;
case 'scroll':
await computer.scroll(action.x, action.y, action.scroll_x, action.scroll_y, runContext);
break;
case 'type':
await computer.type(action.text, runContext);
break;
case 'wait':
await computer.wait(runContext);
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(runContext);
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);
}
}
function getTraceToolError(traceIncludeSensitiveData, errorMessage) {
return traceIncludeSensitiveData ? errorMessage : REDACTED_TOOL_ERROR_MESSAGE;
}
async function withToolFunctionSpan(runner, toolName, fn) {
if (runner.config.tracingDisabled || !(0, context_1.getCurrentTrace)()) {
return fn();
}
return (0, createSpans_1.withFunctionSpan)(async (span) => fn(span), {
data: {
name: toolName,
},
});
}
async function resolveToolApproval(options) {
const { runContext, toolName, callId, approvalItem, needsApproval, onApproval, } = options;
if (!needsApproval) {
return 'approved';
}
if (onApproval) {
const decision = await onApproval(runContext, approvalItem);
if (decision.approve === true) {
runContext.approveTool(approvalItem);
}
else if (decision.approve === false) {
runContext.rejectTool(approvalItem);
}
}
const approval = runContext.isToolApproved({
toolName,
callId,
});
if (approval === true) {
return 'approved';
}
if (approval === false) {
return 'rejected';
}
return 'pending';
}
async function handleToolApprovalDecision(options) {
const { runContext, toolName, callId, approvalItem, needsApproval, onApproval, buildRejectionItem, } = options;
const approvalState = await resolveToolApproval({
runContext,
toolName,
callId,
approvalItem,
needsApproval,
onApproval,
});
if (approvalState === 'rejected') {
return { status: 'rejected', item: await buildRejectionItem() };
}
if (approvalState === 'pending') {
return { status: 'pending', item: approvalItem };
}
return { status: 'approved' };
}
function emitToolStart(runner, runContext, agent, tool, toolCall) {
runner.emit('agent_tool_start', runContext, agent, tool, { toolCall });
if (typeof agent.emit === 'function') {
agent.emit('agent_tool_start', runContext, tool, { toolCall });
}
}
function emitToolEnd(runner, runContext, agent, tool, output, toolCall) {
runner.emit('agent_tool_end', runContext, agent, tool, output, { toolCall });
if (typeof agent.emit === 'function') {
agent.emit('agent_tool_end', runContext, tool, output, { toolCall });
}
}
async function executeShellActions(agent, actions, runner, runContext, customLogger = undefined, toolErrorFormatter) {
const _logger = customLogger ?? logger_1.default;
const results = [];
for (const action of actions) {
const shellTool = action.shell;
const toolCall = action.toolCall;
if (!shellTool.shell) {
_logger.warn(`Skipping shell action for tool "${shellTool.name}" because no local shell implementation is configured.`);
continue;
}
const approvalItem = new items_1.RunToolApprovalItem(toolCall, agent, shellTool.name);
const approvalDecision = await handleToolApprovalDecision({
runContext,
toolName: shellTool.name,
callId: toolCall.callId,
approvalItem,
needsApproval: await shellTool.needsApproval(runContext, toolCall.action, toolCall.callId),
onApproval: shellTool.onApproval,
buildRejectionItem: async () => {
const response = await resolveApprovalRejectionMessage({
runContext,
toolType: 'shell',
toolName: shellTool.name,
callId: toolCall.callId,
toolErrorFormatter,
});
const rejectionOutput = {
stdout: '',
stderr: response,
outcome: { type: 'exit', exitCode: null },
};
return new items_1.RunToolCallOutputItem({
type: 'shell_call_output',
callId: toolCall.callId,
output: [rejectionOutput],
}, agent, response);
},
});
if (approvalDecision.status !== 'approved') {
results.push(approvalDecision.item);
continue;
}
const shellItem = await withToolFunctionSpan(runner, shellTool.name, async (span) => {
if (span && runner.config.traceIncludeSensitiveData) {
span.spanData.input = JSON.stringify(toolCall.action);
}
emitToolStart(runner, runContext, agent, 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);
const traceError = getTraceToolError(runner.config.traceIncludeSensitiveData, errorText);
shellOutputs = [
{
stdout: '',
stderr: errorText,
outcome: { type: 'exit', exitCode: null },
},
];
span?.setError({
message: 'Error running tool',
data: {
tool_name: shellTool.name,
error: traceError,
},
});
_logger.error('Failed to execute shell action:', err);
}
shellOutputs = shellOutputs ?? [];
const output = JSON.stringify(shellOutputs);
emitToolEnd(runner, runContext, agent, shellTool, output, toolCall);
if (span && runner.config.traceIncludeSensitiveData) {
span.spanData.output = output;
}
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;
}
return new items_1.RunToolCallOutputItem(rawItem, agent, rawItem.output);
});
results.push(shellItem);
}
return results;
}
async function executeApplyPatchOperations(agent, actions, runner, runContext, customLogger = undefined, toolErrorFormatter) {
const _logger = customLogger ?? logger_1.default;
const results = [];
for (const action of actions) {
const applyPatchTool = action.applyPatch;
const toolCall = action.toolCall;
const editorContext = { runContext };
const approvalItem = new items_1.RunToolApprovalItem(toolCall, agent, applyPatchTool.name);
const approvalDecision = await handleToolApprovalDecision({
runContext,
toolName: applyPatchTool.name,
callId: toolCall.callId,
approvalItem,
needsApproval: await applyPatchTool.needsApproval(runContext, toolCall.operation, toolCall.callId),
onApproval: applyPatchTool.onApproval,
buildRejectionItem: async () => {
const response = await resolveApprovalRejectionMessage({
runContext,
toolType: 'apply_patch',
toolName: applyPatchTool.name,
callId: toolCall.callId,
toolErrorFormatter,
});
return new items_1.RunToolCallOutputItem({
type: 'apply_patch_call_output',
callId: toolCall.callId,
status: 'failed',
output: response,
}, agent, response);
},
});
if (approvalDecision.status !== 'approved') {
results.push(approvalDecision.item);
continue;
}
const applyPatchItem = await withToolFunctionSpan(runner, applyPatchTool.name, async (span) => {
if (span && runner.config.traceIncludeSensitiveData) {
span.spanData.input = JSON.stringify(toolCall.operation);
}
emitToolStart(runner, runContext, agent, applyPatchTool, toolCall);
let status = 'completed';
let output = '';
try {
let result;
switch (toolCall.operation.type) {
case 'create_file':
result = await applyPatchTool.editor.createFile(toolCall.operation, editorContext);
break;
case 'update_file':
result = await applyPatchTool.editor.updateFile(toolCall.operation, editorContext);
break;
case 'delete_file':
result = await applyPatchTool.editor.deleteFile(toolCall.operation, editorContext);
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);
const traceError = getTraceToolError(runner.config.traceIncludeSensitiveData, output);
span?.setError({
message: 'Error running tool',
data: {
tool_name: applyPatchTool.name,
error: traceError,
},
});
_logger.error('Failed to execute apply_patch operation:', err);
}
emitToolEnd(runner, runContext, agent, applyPatchTool, output, toolCall);
if (span && runner.config.traceIncludeSensitiveData) {
span.spanData.output = output;
}
const rawItem = {
type: 'apply_patch_call_output',
callId: toolCall.callId,
status,
};
if (output) {
rawItem.output = output;
}
return new items_1.RunToolCallOutputItem(rawItem, agent, output);
});
results.push(applyPatchItem);
}
return results;
}
/**
* @internal
* Executes any computer-use actions emitted by the model and returns the resulting items so
* the run history reflects the computer session.
*/
async function executeComputerActions(agent, actions, runner, runContext, customLogger = undefined, toolErrorFormatter) {
const _logger = customLogger ?? logger_1.default;
const results = [];
for (const action of actions) {
const toolCall = action.toolCall;
const computerTool = action.computer;
let cachedRejectionMessage;
const getRejectionMessage = async () => {
if (typeof cachedRejectionMessage === 'string') {
return cachedRejectionMessage;
}
cachedRejectionMessage = await resolveApprovalRejectionMessage({
runContext,
toolType: 'computer',
toolName: computerTool.name,
callId: toolCall.callId,
toolErrorFormatter,
});
return cachedRejectionMessage;
};
const pendingSafetyChecks = getPendingSafetyChecks(toolCall);
const approvalItem = new items_1.RunToolApprovalItem(toolCall, agent, computerTool.name);
const needsApprovalCandidate = computerTool
.needsApproval;
const needsApproval = typeof needsApprovalCandidate === 'function'
? await needsApprovalCandidate(runContext, toolCall.action, toolCall.callId)
: typeof needsApprovalCandidate === 'boolean'
? needsApprovalCandidate
: false;
const approvalDecision = await handleToolApprovalDecision({
runContext,
toolName: computerTool.name,
callId: toolCall.callId,
approvalItem,
needsApproval,
buildRejectionItem: async () => {
const rejectionMessage = await getRejectionMessage();
const rejectionOutput = {
type: 'computer_screenshot',
data: TOOL_APPROVAL_REJECTION_SCREENSHOT_DATA_URL,
providerData: {
approvalStatus: 'rejected',
message: rejectionMessage,
},
};
const rawItem = {
type: 'computer_call_result',
callId: toolCall.callId,
output: rejectionOutput,
};
return new items_1.RunToolCallOutputItem(rawItem, agent, TOOL_APPROVAL_REJECTION_SCREENSHOT_DATA_URL);
},
});
if (approvalDecision.status === 'rejected') {
const rejectionMessage = await getRejectionMessage();
results.push(approvalDecision.item);
results.push(new items_1.RunMessageOutputItem((0, message_1.assistant)(rejectionMessage), agent));
continue;
}
if (approvalDecision.status === 'pending') {
results.push(approvalDecision.item);
continue;
}
const computerItem = await withToolFunctionSpan(runner, computerTool.name, async (span) => {
if (span && runner.config.traceIncludeSensitiveData) {
span.spanData.input = JSON.stringify(toolCall.action);
}
// Hooks: on_tool_start (global + agent)
emitToolStart(runner, runContext, agent, computerTool, toolCall);
const acknowledgedSafetyChecks = pendingSafetyChecks && pendingSafetyChecks.length > 0
? await resolveSafetyCheckAcknowledgements({
runContext,
toolCall,
pendingSafetyChecks,
onSafetyCheck: computerTool.onSafetyCheck,
})
: undefined;
// Run the action and get screenshot.
let output;
try {
const computer = await (0, tool_1.resolveComputer)({
tool: computerTool,
runContext,
});
output = await _runComputerActionAndScreenshot(computer, toolCall, runContext);
}
catch (err) {
_logger.error('Failed to execute computer action:', err);
output = '';
const errorText = toErrorMessage(err);
const traceError = getTraceToolError(runner.config.traceIncludeSensitiveData, errorText);
span?.setError({
message: 'Error running tool',
data: {
tool_name: computerTool.name,
error: traceError,
},
});
}
// Hooks: on_tool_end (global + agent)
emitToolEnd(runner, runContext, agent, computerTool, output, toolCall);
// Return the screenshot as a data URL when available; fall back to an empty string on failures.
const imageUrl = output ? `data:image/png;base64,${output}` : '';
if (span && runner.config.traceIncludeSensitiveData) {
span.spanData.output = imageUrl;
}
const rawItem = {
type: 'computer_call_result',
callId: toolCall.callId,
output: { type: 'computer_screenshot', data: imageUrl },
};
if (acknowledgedSafetyChecks && acknowledgedSafetyChecks.length > 0) {
rawItem.providerData = {
acknowledgedSafetyChecks,
};
}
return new items_1.RunToolCallOutputItem(rawItem, agent, imageUrl);
});
results.push(computerItem);
}
return results;
}
/**
* @internal
* Drives handoff calls by invoking the downstream agent and capturing any generated items so
* the current agent can continue with the new context.
*/
async function executeHandoffCalls(agent, originalInput, preStepItems, newStepItems, newResponse, runHandoffs, runner, runContext) {
newStepItems = [...newStepItems];
if (runHandoffs.length === 0) {
logger_1.default.warn('Incorrectly called executeHandoffCalls with no handoffs. This should not happen. Moving on.');
return new steps_1.SingleStepResult(originalInput, newResponse, preStepItems, newStepItems, { type: 'next_step_run_again' });
}
if (runHandoffs.length > 1) {
// multiple handoffs. Ignoring all but the first one by adding reject responses for those
const outputMessage = 'Multiple handoffs detected, ignoring this one.';
for (let i = 1; i < runHandoffs.length; i++) {
newStepItems.push(new items_1.RunToolCallOutputItem(getToolCallOutputItem(runHandoffs[i].toolCall, outputMessage), agent, outputMessage));
}
}
const actualHandoff = runHandoffs[0];
return (0, createSpans_1.withHandoffSpan)(async (handoffSpan) => {
const handoff = actualHandoff.handoff;
const newAgent = await handoff.onInvokeHandoff(runContext, actualHandoff.toolCall.arguments);
handoffSpan.spanData.to_agent = newAgent.name;
if (runHandoffs.length > 1) {
const requestedAgents = runHandoffs.map((h) => h.handoff.agentName);
handoffSpan.setError({
message: 'Multiple handoffs requested',
data: {
requested_agents: requestedAgents,
},
});
}
newStepItems.push(new items_1.RunHandoffOutputItem(getToolCallOutputItem(actualHandoff.toolCall, (0, handoff_1.getTransferMessage)(newAgent)), agent, newAgent));
runner.emit('agent_handoff', runContext, agent, newAgent);
agent.emit('agent_handoff', runContext, newAgent);
const inputFilter = handoff.inputFilter ?? runner.config.handoffInputFilter;
if (inputFilter) {
logger_1.default.debug('Filtering inputs for handoff');
if (typeof inputFilter !== 'function') {
handoffSpan.setError({
message: 'Invalid input filter',
data: {
details: 'not callable',
},
});
}
const handoffInputData = {
inputHistory: Array.isArray(originalInput)
? [...originalInput]
: originalInput,
preHandoffItems: [...preStepItems],
newItems: [...newStepItems],
runContext,
};
const filtered = inputFilter(handoffInputData);
originalInput = filtered.inputHistory;
preStepItems = filtered.preHandoffItems;
newStepItems = filtered.newItems;
}
return new steps_1.SingleStepResult(originalInput, newResponse, preStepItems, newStepItems, { type: 'next_step_handoff', newAgent });
}, {
data: {
from_agent: agent.name,
},
});
}
const NOT_FINAL_OUTPUT = {
isFinalOutput: false,
isInterrupted: undefined,
};
/**
* Collects approval interruptions from tool execution results and any additional
* RunItems (e.g., shell/apply_patch approval placeholders).
*/
function collectInterruptions(toolResults, additionalItems = []) {
const interruptions = [];
for (const item of additionalItems) {
if (item instanceof items_1.RunToolApprovalItem) {
interruptions.push(item);
}
}
for (const result of toolResults) {
if (result.runItem instanceof items_1.RunToolApprovalItem) {
interruptions.push(result.runItem);
}
if (result.type === 'function_output') {
if (Array.isArray(result.interruptions)) {
interruptions.push(...result.interruptions);
}
else if (result.agentRunResult) {
const nestedInterruptions = result.agentRunResult.interruptions;
if (nestedInterruptions.length > 0) {
interruptions.push(...nestedInterruptions);
}
}
}
}
return interruptions;
}
/**
* @internal
* Determines whether tool executions produced a final agent output, triggered an interruption,
* or whether the agent loop should continue collecting more responses.
*/
async function checkForFinalOutputFromTools(agent, toolResults, state, additionalInterruptions = []) {
if (toolResults.length === 0 && additionalInterruptions.length === 0) {
return NOT_FINAL_OUTPUT;
}
const interruptions = collectInterruptions(toolResults, additionalInterruptions);
if (interruptions.length > 0) {
return {
isFinalOutput: false,
isInterrupted: true,
interruptions,
};
}
if (agent.toolUseBehavior === 'run_llm_again') {
return NOT_FINAL_OUTPUT;
}
const firstToolResult = toolResults[0];
if (agent.toolUseBehavior === 'stop_on_first_tool') {
if (firstToolResult?.type === 'function_output') {
const stringOutput = (0, smartString_1.toSmartString)(firstToolResult.output);
return {
isFinalOutput: true,
isInterrupted: undefined,
finalOutput: stringOutput,
};
}
return NOT_FINAL_OUTPUT;
}
const toolUseBehavior = agent.toolUseBehavior;
if (typeof toolUseBehavior === 'object') {
const stoppingTool = toolResults.find((r) => toolUseBehavior.stopAtToolNames.includes(r.tool.name));
if (stoppingTool?.type === 'function_output') {
const stringOutput = (0, smartString_1.toSmartString)(stoppingTool.output);
return {
isFinalOutput: true,
isInterrupted: undefined,
finalOutput: stringOutput,
};
}
return NOT_FINAL_OUTPUT;
}
if (typeof toolUseBehavior === 'function') {
return toolUseBehavior(state._context, toolResults);
}
throw new errors_1.UserError(`Invalid toolUseBehavior: ${toolUseBehavior}`, state);
}
/**
* Accepts whatever the tool returned and attempts to coerce it into the structured protocol
* shapes we expose to downstream model adapters (input_text/input_image/input_file). Tools are
* allowed to return either a single structured object or an array of them; anything else falls
* back to the legacy string pipeline.
*/
function normalizeStructuredToolOutputs(output) {
if (Array.isArray(output)) {
const structured = [];
for (const item of output) {
const normalized = normalizeStructuredToolOutput(item);
if (!normalized) {
return null;
}
structured.push(normalized);
}
return structured;
}
const normalized = normalizeStructuredToolOutput(output);
return normalized ? [normalized] : null;
}
/**
* Best-effort normalization of a single tool output item. If the object already matches the
* protocol shape we simply cast it; otherwise we copy the recognised fields into the canonical
* structure. Returning null lets the caller know we should revert to plain-string handling.
*/
function normalizeStructuredToolOutput(value) {
if (!isRecord(value)) {
return null;
}
const type = value.type;
if (type === 'text' && typeof value.text === 'string') {
const output = { type: 'text', text: value.text };
if (isRecord(value.providerData)) {
output.providerData = value.providerData;
}
return output;
}
if (type === 'image') {
const output = { type: 'image' };
let imageString;
let imageFileId;
const fallbackImageMediaType = isNonEmptyString(value.mediaType)
? value.mediaType
: undefined;
const imageField = value.image;
if (typeof imageField === 'string' && imageField.length > 0) {
imageString = imageField;
}
else if (isRecord(imageField)) {
const imageObj = imageField;
const inlineMediaType = isNonEmptyString(imageObj.mediaType)
? imageObj.mediaType
: fallbackImageMediaType;
if (isNonEmptyString(imageObj.url)) {
imageString = imageObj.url;
}
else if (isNonEmptyString(imageObj.data)) {
imageString = toInlineImageString(imageObj.data, inlineMediaType);
}
else if (imageObj.data instanceof Uint8Array &&
imageObj.data.length > 0) {
imageString = toInlineImageString(imageObj.data, inlineMediaType);
}
if (!imageString) {
const candidateId = (isNonEmptyString(imageObj.fileId) && imageObj.fileId) ||
(isNonEmptyString(imageObj.id) && imageObj.id) ||
undefined;
if (candidateId) {
imageFileId = candidateId;
}
}
}
if (!imageString &&
typeof value.imageUrl === 'string' &&
value.imageUrl.length > 0) {
imageString = value.imageUrl;
}
if (!imageFileId &&
typeof value.fileId === 'string' &&
value.fileId.length > 0) {
imageFileId = value.fileId;
}
if (!imageString &&
typeof value.data === 'string' &&
value.data.length > 0) {
imageString = fallbackImageMediaType
? toInlineImageString(value.data, fallbackImageMediaType)
: value.data;
}
else if (!imageString &&
value.data instanceof Uint8Array &&
value.data.length > 0) {
imageString = toInlineImageString(value.data, fallbackImageMediaType);
}
if (typeof value.detail === 'string' && value.detail.length > 0) {
output.detail = value.detail;
}
if (imageString) {
output.image = imageString;
}
else if (imageFileId) {
output.image = { fileId: imageFileId };
}
else {
return null;
}
if (isRecord(value.providerData)) {
output.providerData = value.providerData;
}
return output;
}
if (type === 'file') {
const fileValue = normalizeFileValue(value);
if (!fileValue) {
return null;
}
const output = { type: 'file', file: fileValue };
if (isRecord(value.providerData)) {
output.providerData = value.providerData;
}
return output;
}
return null;
}
/**
* Translates the normalized tool output into the protocol `input_*` items. This is the last hop
* before we hand the data to model-specific adapters, so we generate the exact schema expected by
* the protocol definitions.
*/
function convertStructuredToolOutputToInputItem(output) {
if (output.type === 'text') {
const result = {
type: 'input_text',
text: output.text,
};
if (output.providerData) {
result.providerData = output.providerData;
}
return result;
}
if (output.type === 'image') {
const result = { type: 'input_image' };
if (typeof output.detail === 'string' && output.detail.length > 0) {
result.detail = output.detail;
}
if (typeof output.image === 'string' && output.image.length > 0) {
result.image = output.image;
}
else if (isRecord(output.image)) {
const imageObj = output.image;
const inlineMediaType = isNonEmptyString(imageObj.mediaType)
? imageObj.mediaType
: undefined;
if (isNonEmptyString(imageObj.url)) {
result.image = imageObj.url;
}
else if (isNonEmptyString(imageObj.data)) {
result.image =
inlineMediaType && !imageObj.data.startsWith('data:')
? asDataUrl(imageObj.data, inlineMediaType)
: imageObj.data;
}
else if (imageObj.data instanceof Uint8Array &&
imageObj.data.length > 0) {
const base64 = (0, base64_1.encodeUint8ArrayToBase64)(imageObj.data);
result.image = asDataUrl(base64, inlineMediaType);
}
else {
const referencedId = (isNonEmptyString(imageObj.fileId) && imageObj.fileId) ||
(isNonEmptyString(imageObj.id) && imageObj.id) ||
undefined;
if (referencedId) {
result.image = { id: referencedId };
}
}
}
if (output.providerData) {
result.providerData = output.providerData;
}
return result;
}
if (output.type === 'file') {
const result = { type: 'input_file' };
const fileValue = output.file;
if (typeof fileValue === 'string') {
result.file = fileValue;
}
else if (fileValue && typeof fileValue === 'object') {
const record = fileValue;
if ('data' in record && record.data) {
const mediaType = record.mediaType ?? 'text/plain';
if (typeof record.data === 'string') {
result.file = asDataUrl(record.data, mediaType);
}
else {
const base64 = (0, base64_1.encodeUint8ArrayToBase64)(record.data);
result.file = asDataUrl(base64, mediaType);
}
}
else if (typeof record.url === 'string' && record.url.length > 0) {
result.file = { url: record.url };
}
else {
const referencedId = (typeof record.id === 'string' &&
record.id.length > 0 &&
record.id) ||
(typeof record.fileId === 'string' && record.fileId.length > 0
? record.fileId
: undefined);
if (referencedId) {
result.file = { id: referencedId };
}
}
if (typeof record.filename === 'string' && record.filename.length > 0) {
result.filename = record.filename;
}
}
if (output.providerData) {
result.providerData = output.providerData;
}
return result;
}
const exhaustiveCheck = output;
return exhaustiveCheck;
}
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 normalizeSafetyChecks(checks) {
if (!Array.isArray(checks)) {
return undefined;
}
const normalized = [];
for (const entry of checks) {
if (!isRecord(entry)) {
continue;
}
const id = entry.id;
const code = entry.code;
if (!isNonEmptyString(id) || !isNonEmptyString(code)) {
continue;
}
const message = 'message' in entry && isNonEmptyString(entry.message)
? entry.message
: undefined;
const normalizedEntry = { ...entry, id, code };
if (message) {
normalizedEntry.message = message;
}
normalized.push(normalizedEntry);
}
return normalized.length > 0 ? normalized : undefined;
}
function normalizeSafetyCheckResult(result) {
if (!result) {
return undefined;
}
if (!isRecord(result)) {
return undefined;
}
if ('acknowledgedSafetyChecks' in result) {
return normalizeSafetyChecks(result.acknowledgedSafetyChecks);
}
if ('acknowledged_safety_checks' in result) {
return normalizeSafetyChecks(result.acknowledged_safety_checks);
}
return undefined;
}
async