@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
1,008 lines • 41.7 kB
JavaScript
import { toFunctionToolName } from "./utils/tools.mjs";
import { getFunctionToolApprovalInput } from "./utils/functionToolApproval.mjs";
import { getSchemaAndParserFromInputType } from "./utils/tools.mjs";
import { isZodObject } from "./utils/typeGuards.mjs";
import { combineAbortSignals, isAbortError, isSiblingCancellationSignal, } from "./utils/abortSignals.mjs";
import { ToolTimeoutError, UserError } from "./errors.mjs";
import { createInvalidToolInputDisposition, createInvalidToolInputFailure, getInvalidToolInputFailure, isRedactedInvalidToolInputError, refreshInvalidToolInputFailure, } from "./toolInputError.mjs";
import { createInvalidToolOutputError, getInvalidToolOutputFailure, isRedactedInvalidToolOutputError, refreshInvalidToolOutputFailure, } from "./toolOutputError.mjs";
import logger, { logToolActionWarning } from "./logger.mjs";
import { getCurrentSpan } from "./tracing/index.mjs";
import { includesFunctionToolErrorDetails, REDACTED_TOOL_ERROR_MESSAGE, } from "./functionToolTracing.mjs";
import { toSmartString } from "./utils/smartString.mjs";
import { normalizeHostedMcpRequireApproval } from "./utils/mcpApproval.mjs";
import { isAsyncStandardSchemaValidationError, isStandardSchemaWithJSON, } from "./utils/standardSchema.mjs";
import { FUNCTION_TOOL_NAMESPACE, FUNCTION_TOOL_NAMESPACE_DESCRIPTION, getFunctionToolLookupKeyForTool, getFunctionToolQualifiedName, } from "./toolIdentity.mjs";
import { resolveToolInputGuardrails, resolveToolOutputGuardrails, } from "./toolGuardrail.mjs";
import { normalizeToolAllowedCallers, } from "./utils/toolCallers.mjs";
export const FUNCTION_TOOL_PARSED_INPUT_CALLBACK = Symbol('openai.agents.functionToolParsedInputCallback');
export const FUNCTION_TOOL_INVALID_OUTPUT_FAILURE_CALLBACK = Symbol('openai.agents.functionToolInvalidOutputFailureCallback');
const FUNCTION_TOOL_OUTPUT_VALIDATOR = Symbol('openai.agents.functionToolOutputValidator');
const STATIC_FUNCTION_TOOL_APPROVAL_POLICIES = new WeakSet();
const functionToolInputParsers = new WeakMap();
const functionToolPreparedInputs = new WeakMap();
function parseFunctionToolInput(parser, input) {
try {
return { success: true, value: parser(input) };
}
catch (error) {
return { success: false, error };
}
}
/** Prepare input for SDK runners through the internal utilities entry point. */
export function prepareFunctionToolInput(tool, input) {
const registration = functionToolInputParsers.get(tool.invoke);
if (!registration) {
return undefined;
}
const result = parseFunctionToolInput(registration.parser, input);
if (!result.success && isAsyncStandardSchemaValidationError(result.error)) {
throw result.error;
}
const base = {
consumed: false,
input,
token: registration.token,
validationMode: registration.validationMode,
};
if (result.success) {
if (hasDynamicFunctionToolApprovalPolicy(tool)) {
// Snapshot application-owned transform output before any policy can await.
const value = getFunctionToolApprovalInput(result.value);
return { ...base, result: { success: true, value } };
}
return { ...base, result };
}
return {
...base,
result,
disposition: createInvalidToolInputDisposition(),
};
}
/** Attach prepared input for SDK runners through the internal utilities entry point. */
export function setFunctionToolPreparedInput(details, preparedInput) {
functionToolPreparedInputs.set(details, preparedInput);
}
function copyFunctionToolPreparedInput(source, target) {
if (!source || !target) {
return;
}
const preparedInput = functionToolPreparedInputs.get(source);
if (preparedInput) {
functionToolPreparedInputs.set(target, preparedInput);
}
}
export function hasDynamicFunctionToolApprovalPolicy(tool) {
return !STATIC_FUNCTION_TOOL_APPROVAL_POLICIES.has(tool.needsApproval);
}
export function hasInspectableFunctionToolArguments(value) {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function isComputerProvider(candidate) {
if (isComputerInstance(candidate)) {
return false;
}
return (!!candidate &&
typeof candidate === 'object' &&
typeof candidate.create === 'function');
}
function isComputerInstance(candidate) {
if (!candidate || typeof candidate !== 'object') {
return false;
}
const maybeComputer = candidate;
return (typeof maybeComputer.screenshot === 'function' &&
typeof maybeComputer.click === 'function' &&
typeof maybeComputer.doubleClick === 'function' &&
typeof maybeComputer.drag === 'function' &&
typeof maybeComputer.keypress === 'function' &&
typeof maybeComputer.move === 'function' &&
typeof maybeComputer.scroll === 'function' &&
typeof maybeComputer.type === 'function' &&
typeof maybeComputer.wait === 'function');
}
/**
* Exposes a computer to the agent as a tool to be called.
*
* @param options Additional configuration for the computer tool like specifying the location of your agent
* @returns a computer tool definition
*/
export function computerTool(options) {
if (!options.computer) {
throw new UserError('computerTool requires a computer instance or an initializer function.');
}
const needsApproval = typeof options.needsApproval === 'function'
? options.needsApproval
: async () => typeof options.needsApproval === 'boolean'
? options.needsApproval
: false;
const tool = {
type: 'computer',
name: options.name ?? 'computer_use_preview',
computer: options.computer,
needsApproval,
onSafetyCheck: options.onSafetyCheck,
customDataExtractor: options.customDataExtractor,
};
if (typeof options.computer === 'function' ||
isComputerProvider(options.computer)) {
computerInitializerMap.set(tool, options.computer);
}
return tool;
}
// Keeps per-tool cache of computer instances keyed by RunContext so each run gets its own instance.
const computerCache = new WeakMap();
// Tracks the initializer so we do not overwrite the callable on the tool when we memoize the resolved instance.
const computerInitializerMap = new WeakMap();
// Allows cleanup routines to find all resolved computer instances for a given run context.
const computersByRunContext = new WeakMap();
function getComputerInitializer(tool) {
const initializer = computerInitializerMap.get(tool);
if (initializer) {
return initializer;
}
if (typeof tool.computer === 'function' ||
isComputerProvider(tool.computer)) {
computerInitializerMap.set(tool, tool.computer);
return tool.computer;
}
return undefined;
}
function trackResolvedComputer(tool, runContext, resolved) {
let resolvedByRun = computersByRunContext.get(runContext);
if (!resolvedByRun) {
resolvedByRun = new Map();
computersByRunContext.set(runContext, resolvedByRun);
}
resolvedByRun.set(tool, resolved);
}
/**
* Returns a computer instance for the provided run context. Caches per run to avoid sharing across runs.
* @internal
*/
export async function resolveComputer(args) {
const { tool, runContext } = args;
// Cache instances per RunContext so a single Computer is not shared across simultaneous runs.
const toolKey = tool;
let perContext = computerCache.get(toolKey);
if (!perContext) {
perContext = new WeakMap();
computerCache.set(toolKey, perContext);
}
const cached = perContext.get(runContext);
if (cached) {
trackResolvedComputer(tool, runContext, cached);
return cached.computer;
}
const initializerConfig = getComputerInitializer(tool);
const lifecycle = initializerConfig && isComputerProvider(initializerConfig)
? initializerConfig
: isComputerProvider(tool.computer)
? tool.computer
: undefined;
const initializer = typeof initializerConfig === 'function'
? initializerConfig
: (lifecycle?.create ??
(typeof tool.computer === 'function'
? tool.computer
: undefined));
const disposer = lifecycle?.dispose;
const computer = initializer && typeof initializer === 'function'
? await initializer({ runContext })
: tool.computer;
if (!computer) {
throw new UserError('The computer tool did not provide a computer instance.');
}
const resolved = {
computer,
dispose: disposer,
};
perContext.set(runContext, resolved);
trackResolvedComputer(tool, runContext, resolved);
tool.computer = computer;
return computer;
}
/**
* Disposes any computer instances created for the provided run context.
* @internal
*/
export async function disposeResolvedComputers({ runContext, }) {
const resolvedByRun = computersByRunContext.get(runContext);
if (!resolvedByRun) {
return;
}
computersByRunContext.delete(runContext);
const disposers = [];
for (const [tool, resolved] of resolvedByRun.entries()) {
const perContext = computerCache.get(tool);
perContext?.delete(runContext);
const storedInitializer = getComputerInitializer(tool);
if (storedInitializer) {
tool.computer = storedInitializer;
}
if (resolved.dispose) {
disposers.push(async () => {
await resolved.dispose?.({ runContext, computer: resolved.computer });
});
}
}
for (const dispose of disposers) {
try {
await dispose();
}
catch (error) {
logToolActionWarning(logger, 'Failed to dispose computer for run context:', error);
}
}
}
function normalizeShellToolSkillReference(skill) {
if (!skill.skillId) {
throw new UserError('shellTool skill_reference requires a skillId.');
}
return skill;
}
function normalizeShellToolInlineSkill(skill) {
const sourceCandidate = skill.source;
if (!sourceCandidate || typeof sourceCandidate !== 'object') {
throw new UserError('shellTool inline skill source is required.');
}
const source = sourceCandidate;
if (source.mediaType !== 'application/zip') {
throw new UserError('shellTool inline skill source.mediaType must be application/zip.');
}
return {
type: 'inline',
name: skill.name,
description: skill.description,
source: {
type: 'base64',
data: source.data,
mediaType: source.mediaType,
},
};
}
function normalizeShellToolContainerSkill(skill) {
if (skill.type === 'skill_reference') {
return normalizeShellToolSkillReference(skill);
}
return normalizeShellToolInlineSkill(skill);
}
function normalizeShellToolContainerNetworkPolicy(policy) {
if (!policy) {
return undefined;
}
if (policy.type === 'disabled') {
return policy;
}
return {
type: 'allowlist',
allowedDomains: policy.allowedDomains,
domainSecrets: policy.domainSecrets,
};
}
function normalizeShellToolEnvironment(environment) {
if (!environment || environment.type === 'local') {
return environment ?? { type: 'local' };
}
if (environment.type === 'container_auto') {
return {
type: 'container_auto',
fileIds: environment.fileIds,
memoryLimit: environment.memoryLimit,
networkPolicy: normalizeShellToolContainerNetworkPolicy(environment.networkPolicy),
skills: environment.skills?.map(normalizeShellToolContainerSkill),
};
}
const containerId = environment.containerId;
if (!containerId) {
throw new UserError('shellTool with container_reference environment requires a containerId.');
}
return {
type: 'container_reference',
containerId,
};
}
export function shellTool(options) {
const environment = normalizeShellToolEnvironment(options.environment);
const allowedCallers = normalizeToolAllowedCallers(options.allowedCallers, options.name ?? 'shell');
const needsApproval = typeof options.needsApproval === 'function'
? options.needsApproval
: async () => typeof options.needsApproval === 'boolean'
? options.needsApproval
: false;
if (environment.type === 'local') {
const localShell = options.shell;
if (!localShell) {
throw new UserError('shellTool with local environment requires a shell implementation.');
}
return {
type: 'shell',
name: options.name ?? 'shell',
environment,
shell: localShell,
needsApproval,
onApproval: options.onApproval,
...(allowedCallers ? { allowedCallers } : {}),
};
}
if (typeof options.shell !== 'undefined') {
throw new UserError('shellTool with hosted environment does not accept a shell implementation.');
}
if (typeof options.needsApproval !==
'undefined' ||
typeof options.onApproval !== 'undefined') {
throw new UserError('shellTool with hosted environment does not support needsApproval or onApproval.');
}
return {
type: 'shell',
name: options.name ?? 'shell',
environment,
needsApproval,
...(allowedCallers ? { allowedCallers } : {}),
};
}
export function applyPatchTool(options) {
const name = options.name ?? 'apply_patch';
const allowedCallers = normalizeToolAllowedCallers(options.allowedCallers, name);
const needsApproval = typeof options.needsApproval === 'function'
? options.needsApproval
: async () => typeof options.needsApproval === 'boolean'
? options.needsApproval
: false;
return {
type: 'apply_patch',
name,
editor: options.editor,
needsApproval,
onApproval: options.onApproval,
...(allowedCallers ? { allowedCallers } : {}),
customDataExtractor: options.customDataExtractor,
};
}
/**
* Creates a hosted MCP tool definition.
*
* @param options - Configuration for the hosted MCP tool, including server connection details
* and approval requirements.
*/
export function hostedMcpTool(options) {
const allowedCallers = normalizeToolAllowedCallers(options.allowedCallers, options.serverLabel);
const providerAllowedCallers = allowedCallers
? [...allowedCallers]
: undefined;
if ('serverUrl' in options) {
// the MCP servers compatible with the specification
const providerData = typeof options.requireApproval === 'undefined' ||
options.requireApproval === 'never'
? {
type: 'mcp',
server_label: options.serverLabel,
server_url: options.serverUrl,
authorization: options.authorization,
require_approval: 'never',
allowed_tools: toMcpAllowedToolsFilter(options.allowedTools),
allowed_callers: providerAllowedCallers,
defer_loading: options.deferLoading,
headers: options.headers,
server_description: options.serverDescription,
}
: {
type: 'mcp',
server_label: options.serverLabel,
server_url: options.serverUrl,
authorization: options.authorization,
allowed_tools: toMcpAllowedToolsFilter(options.allowedTools),
allowed_callers: providerAllowedCallers,
defer_loading: options.deferLoading,
headers: options.headers,
require_approval: buildRequireApproval(options.requireApproval),
on_approval: options.onApproval,
server_description: options.serverDescription,
};
return {
type: 'hosted_tool',
name: 'hosted_mcp',
providerData,
};
}
else if ('connectorId' in options) {
// OpenAI's connectors
const providerData = typeof options.requireApproval === 'undefined' ||
options.requireApproval === 'never'
? {
type: 'mcp',
server_label: options.serverLabel,
connector_id: options.connectorId,
authorization: options.authorization,
require_approval: 'never',
allowed_tools: toMcpAllowedToolsFilter(options.allowedTools),
allowed_callers: providerAllowedCallers,
defer_loading: options.deferLoading,
headers: options.headers,
server_description: options.serverDescription,
}
: {
type: 'mcp',
server_label: options.serverLabel,
connector_id: options.connectorId,
authorization: options.authorization,
allowed_tools: toMcpAllowedToolsFilter(options.allowedTools),
allowed_callers: providerAllowedCallers,
defer_loading: options.deferLoading,
headers: options.headers,
require_approval: buildRequireApproval(options.requireApproval),
on_approval: options.onApproval,
server_description: options.serverDescription,
};
return {
type: 'hosted_tool',
name: 'hosted_mcp',
providerData,
};
}
else {
// the MCP servers compatible with the specification
const providerData = typeof options.requireApproval === 'undefined' ||
options.requireApproval === 'never'
? {
type: 'mcp',
server_label: options.serverLabel,
require_approval: 'never',
allowed_tools: toMcpAllowedToolsFilter(options.allowedTools),
allowed_callers: providerAllowedCallers,
defer_loading: options.deferLoading,
server_description: options.serverDescription,
}
: {
type: 'mcp',
server_label: options.serverLabel,
allowed_tools: toMcpAllowedToolsFilter(options.allowedTools),
allowed_callers: providerAllowedCallers,
defer_loading: options.deferLoading,
require_approval: buildRequireApproval(options.requireApproval),
on_approval: options.onApproval,
server_description: options.serverDescription,
};
return {
type: 'hosted_tool',
name: 'hosted_mcp',
providerData,
};
}
}
export const CLIENT_TOOL_SEARCH_EXECUTOR = Symbol('clientToolSearchExecutor');
export function attachClientToolSearchExecutor(tool, executor) {
Object.defineProperty(tool, CLIENT_TOOL_SEARCH_EXECUTOR, {
value: executor,
enumerable: false,
configurable: true,
});
return tool;
}
export function getClientToolSearchExecutor(tool) {
if (tool.type !== 'hosted_tool') {
return undefined;
}
return tool[CLIENT_TOOL_SEARCH_EXECUTOR];
}
export function getToolSearchRuntimeToolKey(tool) {
if (tool.type === 'function') {
return getFunctionToolQualifiedName(tool) ?? tool.name;
}
if (tool.type === 'hosted_tool' &&
tool.providerData?.type === 'mcp' &&
typeof tool.providerData.server_label === 'string' &&
tool.providerData.server_label.length > 0) {
return `mcp:${tool.providerData.server_label}`;
}
return undefined;
}
/** @internal */
export function getToolSearchRuntimeRoutingKey(tool) {
if (tool.type === 'function') {
return getFunctionToolLookupKeyForTool(tool);
}
return getToolSearchRuntimeToolKey(tool);
}
const FUNCTION_TOOL_TIMEOUT_BEHAVIORS = [
'error_as_result',
'raise_exception',
];
const FUNCTION_TOOL_TIMEOUT_ALREADY_ENFORCED = Symbol('functionToolTimeoutAlreadyEnforced');
/**
* The default function to invoke when an error occurs while running the tool.
*
* Returns a fixed message without inspecting the exception. Provide a custom
* `errorFunction` to return application-approved details to the model.
*/
function defaultToolErrorFunction() {
return 'An error occurred while running the tool.';
}
function defaultFunctionToolTimeoutErrorMessage(args) {
return `Tool '${args.toolName}' timed out after ${args.timeoutMs}ms.`;
}
const MAX_FUNCTION_TOOL_TIMEOUT_MS = 2_147_483_647;
function cloneObjectWithDescriptorsAndOverrides(value, overrides) {
const descriptors = Object.getOwnPropertyDescriptors(value);
for (const key of Reflect.ownKeys(overrides)) {
delete descriptors[key];
}
const clone = Object.create(Object.getPrototypeOf(value), descriptors);
return Object.assign(clone, overrides);
}
function normalizeFunctionToolTimeoutConfig(args) {
const { toolName, timeoutMs, timeoutBehavior, timeoutErrorFunction } = args;
if (typeof timeoutMs !== 'undefined') {
if (typeof timeoutMs !== 'number' || Number.isNaN(timeoutMs)) {
throw new UserError(`Function tool '${toolName}' timeoutMs must be a finite number in milliseconds.`);
}
if (!Number.isFinite(timeoutMs)) {
throw new UserError(`Function tool '${toolName}' timeoutMs must be finite.`);
}
if (timeoutMs <= 0) {
throw new UserError(`Function tool '${toolName}' timeoutMs must be greater than 0.`);
}
if (timeoutMs > MAX_FUNCTION_TOOL_TIMEOUT_MS) {
throw new UserError(`Function tool '${toolName}' timeoutMs must be less than or equal to ${MAX_FUNCTION_TOOL_TIMEOUT_MS}.`);
}
}
const resolvedBehavior = timeoutBehavior ?? 'error_as_result';
if (!FUNCTION_TOOL_TIMEOUT_BEHAVIORS.includes(resolvedBehavior)) {
throw new UserError(`Function tool '${toolName}' timeoutBehavior must be one of: ${FUNCTION_TOOL_TIMEOUT_BEHAVIORS.join(', ')}.`);
}
if (typeof timeoutErrorFunction !== 'undefined' &&
typeof timeoutErrorFunction !== 'function') {
throw new UserError(`Function tool '${toolName}' timeoutErrorFunction must be a function when provided.`);
}
return {
timeoutMs,
timeoutBehavior: resolvedBehavior,
timeoutErrorFunction,
};
}
async function invokeFunctionToolWithTimeout(args) {
const { toolName, invoke, runContext, input, details, timeoutMs: configuredTimeoutMs, timeoutBehavior: configuredTimeoutBehavior, timeoutErrorFunction: configuredTimeoutErrorFunction, } = args;
const { timeoutMs, timeoutBehavior, timeoutErrorFunction } = normalizeFunctionToolTimeoutConfig({
toolName,
timeoutMs: configuredTimeoutMs,
timeoutBehavior: configuredTimeoutBehavior,
timeoutErrorFunction: configuredTimeoutErrorFunction,
});
if (typeof timeoutMs === 'undefined') {
return invoke(runContext, input, details);
}
let timeoutId;
let timeoutTriggered = false;
const timeoutController = new AbortController();
const { signal: invocationSignal, cleanup: cleanupAbortSignals } = combineAbortSignals(details?.signal, timeoutController.signal);
const invokeDetails = invocationSignal
? details
? cloneObjectWithDescriptorsAndOverrides(details, {
signal: invocationSignal,
})
: { signal: invocationSignal }
: details;
copyFunctionToolPreparedInput(details, invokeDetails);
const timeoutError = new ToolTimeoutError({
toolName,
timeoutMs,
});
let invocationPromise;
try {
invocationPromise = Promise.resolve(invoke(runContext, input, invokeDetails));
return await Promise.race([
invocationPromise,
new Promise((_, reject) => {
timeoutId = setTimeout(() => {
timeoutTriggered = true;
reject(timeoutError);
timeoutController.abort(timeoutError);
}, timeoutMs);
}),
]);
}
catch (error) {
const isTimeoutError = error === timeoutError ||
(timeoutTriggered && timeoutController.signal.reason === timeoutError);
if (!isTimeoutError) {
throw error;
}
const siblingCancellationSignal = details?.signal;
if (siblingCancellationSignal &&
isSiblingCancellationSignal(siblingCancellationSignal)) {
throw siblingCancellationSignal.reason;
}
if (timeoutBehavior === 'raise_exception') {
throw timeoutError;
}
if (timeoutErrorFunction) {
try {
return await timeoutErrorFunction(runContext, timeoutError, details);
}
catch (error) {
const invalidOutputFailure = getInvalidToolOutputFailure(error);
if (invalidOutputFailure) {
details?.[FUNCTION_TOOL_INVALID_OUTPUT_FAILURE_CALLBACK]?.(invalidOutputFailure);
if (refreshInvalidToolOutputFailure(invalidOutputFailure)) {
throw invalidOutputFailure.error;
}
}
throw error;
}
}
return defaultFunctionToolTimeoutErrorMessage({
toolName,
timeoutMs,
});
}
finally {
if (typeof timeoutId !== 'undefined') {
clearTimeout(timeoutId);
}
cleanupAbortSignals();
}
}
/**
* Validate a function tool output when the tool was created with a runtime
* output schema.
*/
export function validateFunctionToolOutput(args) {
const validator = args.tool[FUNCTION_TOOL_OUTPUT_VALIDATOR];
if (!validator) {
return args.output;
}
return validator(args.output, args.runContext, args.details);
}
/**
* Invoke a function tool while enforcing optional per-tool timeout settings.
*/
export async function invokeFunctionTool(args) {
const { tool, runContext, input, details } = args;
const invokeWithTimeoutFlag = (invocationRunContext, invocationInput, invocationDetails) => {
const detailsWithFlag = typeof invocationDetails === 'undefined'
? undefined
: cloneObjectWithDescriptorsAndOverrides(invocationDetails, {
[FUNCTION_TOOL_TIMEOUT_ALREADY_ENFORCED]: true,
});
copyFunctionToolPreparedInput(invocationDetails, detailsWithFlag);
return tool.invoke(invocationRunContext, invocationInput, detailsWithFlag);
};
return invokeFunctionToolWithTimeout({
toolName: tool.name,
invoke: invokeWithTimeoutFlag,
runContext,
input,
details,
timeoutMs: tool.timeoutMs,
timeoutBehavior: tool.timeoutBehavior,
timeoutErrorFunction: tool.timeoutErrorFunction,
});
}
/**
* Exposes a function to the agent as a tool to be called
*
* @param options The options for the tool
* @returns A new tool
*/
export function tool(options) {
const name = options.name
? toFunctionToolName(options.name)
: toFunctionToolName(options.execute.name);
if (!name) {
throw new Error('Tool name cannot be empty. Either name your function or provide a name in the options.');
}
const strictMode = options.strict ?? true;
if (!strictMode && isZodObject(options.parameters)) {
throw new UserError('Strict mode is required for Zod parameters');
}
if (!strictMode && isStandardSchemaWithJSON(options.parameters)) {
throw new UserError('Strict mode is required for Standard Schema parameters');
}
const hasOutputSchema = typeof options.outputSchema !== 'undefined';
const toolErrorFunction = typeof options.errorFunction === 'undefined'
? hasOutputSchema
? null
: defaultToolErrorFunction
: options.errorFunction;
const { timeoutMs, timeoutBehavior, timeoutErrorFunction: configuredTimeoutErrorFunction, } = normalizeFunctionToolTimeoutConfig({
toolName: name,
timeoutMs: options.timeoutMs,
timeoutBehavior: options.timeoutBehavior ??
(hasOutputSchema ? 'raise_exception' : undefined),
timeoutErrorFunction: options.timeoutErrorFunction,
});
if (hasOutputSchema &&
timeoutBehavior === 'error_as_result' &&
!configuredTimeoutErrorFunction) {
throw new UserError(`Function tool '${name}' with an output schema requires timeoutErrorFunction when timeoutBehavior is 'error_as_result'.`);
}
const allowedCallers = normalizeToolAllowedCallers(options.allowedCallers, name);
const { parser, schema: parameters } = getSchemaAndParserFromInputType(options.parameters, name, { strict: strictMode });
const inputParserToken = {};
const zodOutputSchema = isZodObject(options.outputSchema)
? options.outputSchema
: undefined;
const outputSchema = zodOutputSchema
? getSchemaAndParserFromInputType(zodOutputSchema, `${name}_output`, {
strict: true,
}).schema
: options.outputSchema;
function validateOutput(output, runContext, details) {
if (!zodOutputSchema) {
return output;
}
try {
return zodOutputSchema.parse(output);
}
catch (error) {
throw createInvalidToolOutputError(`Invalid output for function tool '${name}'.`, error, { runContext, output, details });
}
}
const timeoutErrorFunction = configuredTimeoutErrorFunction
? async (runContext, error, details) => validateOutput(await configuredTimeoutErrorFunction(runContext, error, details), runContext, details)
: undefined;
async function _invoke(runContext, input, details) {
const preparedInput = details
? functionToolPreparedInputs.get(details)
: undefined;
const canUsePreparedInput = preparedInput !== undefined &&
!preparedInput.consumed &&
preparedInput.input === input &&
preparedInput.token === inputParserToken;
if (canUsePreparedInput) {
preparedInput.consumed = true;
}
const parseResult = canUsePreparedInput && preparedInput
? preparedInput.result
: parseFunctionToolInput(parser, input);
if (!parseResult.success) {
// supply the same context as options.execute for consuming
// downstream code to implement self-healing and/or tracing
const failure = createInvalidToolInputFailure({
message: 'Invalid JSON input for tool',
originalError: parseResult.error,
toolInvocation: { runContext, input, details },
disposition: canUsePreparedInput && preparedInput
? preparedInput.disposition
: undefined,
fatal: isAsyncStandardSchemaValidationError(parseResult.error),
});
if (failure.redacted || logger.dontLogToolData) {
refreshInvalidToolInputFailure(failure);
logger.debug(`Invalid JSON input for tool ${name}`);
}
else {
logger.debug(`Invalid JSON input for tool ${name}: ${input}`);
}
throw failure.error;
}
const parsed = parseResult.value;
if (logger.dontLogToolData) {
logger.debug(`Invoking tool ${name}`);
}
else {
logger.debug(`Invoking tool ${name} with input ${input}`);
}
details?.[FUNCTION_TOOL_PARSED_INPUT_CALLBACK]?.(parsed);
const result = validateOutput(await options.execute(parsed, runContext, details), runContext, details);
const stringResult = toSmartString(result);
if (logger.dontLogToolData) {
logger.debug(`Tool ${name} completed`);
}
else {
logger.debug(`Tool ${name} returned: ${stringResult}`);
}
return result;
}
async function invokeWithoutTimeout(runContext, input, details) {
return _invoke(runContext, input, details).catch(async (error) => {
if (details?.signal?.aborted &&
(isSiblingCancellationSignal(details.signal) ||
error === details.signal.reason ||
isAbortError(error) ||
details.signal.reason instanceof ToolTimeoutError)) {
throw error;
}
if (getInvalidToolInputFailure(error)?.fatal) {
throw error;
}
return resolveToolFailure(runContext, error, details);
});
}
async function resolveToolFailure(runContext, error, details) {
const invalidOutputFailure = getInvalidToolOutputFailure(error);
if (invalidOutputFailure) {
details?.[FUNCTION_TOOL_INVALID_OUTPUT_FAILURE_CALLBACK]?.(invalidOutputFailure);
}
if (!toolErrorFunction) {
if (invalidOutputFailure &&
refreshInvalidToolOutputFailure(invalidOutputFailure)) {
throw invalidOutputFailure.error;
}
throw error;
}
const invalidInputFailure = getInvalidToolInputFailure(error);
const redactedInputBeforeCallback = invalidInputFailure
? refreshInvalidToolInputFailure(invalidInputFailure)
: isRedactedInvalidToolInputError(error);
const redactedOutputBeforeCallback = invalidOutputFailure
? refreshInvalidToolOutputFailure(invalidOutputFailure)
: isRedactedInvalidToolOutputError(error);
const redactedBeforeCallback = redactedInputBeforeCallback || redactedOutputBeforeCallback;
const callbackError = invalidInputFailure?.error ?? invalidOutputFailure?.error ?? error;
const errorDetails = redactedBeforeCallback ? undefined : details;
const currentSpan = getCurrentSpan();
const includeErrorDetails = toolErrorFunction !== defaultToolErrorFunction &&
!redactedBeforeCallback &&
includesFunctionToolErrorDetails(details);
currentSpan?.setError({
message: 'Error running tool (non-fatal)',
data: {
tool_name: name,
error: includeErrorDetails
? callbackError.toString()
: REDACTED_TOOL_ERROR_MESSAGE,
},
});
try {
const output = await toolErrorFunction(runContext, callbackError, errorDetails);
if (invalidInputFailure &&
!redactedInputBeforeCallback &&
refreshInvalidToolInputFailure(invalidInputFailure)) {
throw invalidInputFailure.error;
}
if (invalidOutputFailure &&
!redactedOutputBeforeCallback &&
refreshInvalidToolOutputFailure(invalidOutputFailure)) {
throw invalidOutputFailure.error;
}
const validatedOutput = validateOutput(output, runContext, errorDetails);
if (invalidInputFailure &&
!redactedInputBeforeCallback &&
refreshInvalidToolInputFailure(invalidInputFailure)) {
throw invalidInputFailure.error;
}
if (invalidOutputFailure &&
!redactedOutputBeforeCallback &&
refreshInvalidToolOutputFailure(invalidOutputFailure)) {
throw invalidOutputFailure.error;
}
return validatedOutput;
}
catch (callbackFailure) {
if (invalidInputFailure &&
refreshInvalidToolInputFailure(invalidInputFailure)) {
throw invalidInputFailure.error;
}
if (invalidOutputFailure &&
refreshInvalidToolOutputFailure(invalidOutputFailure)) {
throw invalidOutputFailure.error;
}
const callbackInvalidOutputFailure = getInvalidToolOutputFailure(callbackFailure);
if (callbackInvalidOutputFailure) {
details?.[FUNCTION_TOOL_INVALID_OUTPUT_FAILURE_CALLBACK]?.(callbackInvalidOutputFailure);
if (refreshInvalidToolOutputFailure(callbackInvalidOutputFailure)) {
throw callbackInvalidOutputFailure.error;
}
}
throw callbackFailure;
}
}
async function invoke(runContext, input, details) {
const detailsWithFlag = details;
if (detailsWithFlag?.[FUNCTION_TOOL_TIMEOUT_ALREADY_ENFORCED]) {
return invokeWithoutTimeout(runContext, input, details);
}
return invokeFunctionToolWithTimeout({
toolName: name,
invoke: invokeWithoutTimeout,
runContext,
input,
details,
timeoutMs,
timeoutBehavior,
timeoutErrorFunction,
});
}
const needsApproval = typeof options.needsApproval === 'function'
? options.needsApproval
: async () => typeof options.needsApproval === 'boolean'
? options.needsApproval
: false;
if (typeof options.needsApproval !== 'function') {
STATIC_FUNCTION_TOOL_APPROVAL_POLICIES.add(needsApproval);
}
functionToolInputParsers.set(invoke, {
parser,
token: inputParserToken,
validationMode: !isZodObject(options.parameters) &&
isStandardSchemaWithJSON(options.parameters)
? 'standard'
: 'other',
});
const isEnabled = typeof options.isEnabled === 'function'
? async (runContext, agent) => {
const predicate = options.isEnabled;
const result = await predicate({ runContext, agent });
return Boolean(result);
}
: async () => typeof options.isEnabled === 'boolean' ? options.isEnabled : true;
return {
type: 'function',
name,
description: options.description,
parameters,
strict: strictMode,
deferLoading: options.deferLoading ?? false,
providerData: options.providerData,
...(allowedCallers ? { allowedCallers } : {}),
...(outputSchema ? { outputSchema } : {}),
invoke,
...(hasOutputSchema
? {
errorFunction: toolErrorFunction,
}
: {}),
needsApproval,
timeoutMs,
timeoutBehavior,
timeoutErrorFunction,
isEnabled,
inputGuardrails: resolveToolInputGuardrails(options.inputGuardrails),
outputGuardrails: resolveToolOutputGuardrails(options.outputGuardrails),
customDataExtractor: options.customDataExtractor,
...(zodOutputSchema
? { [FUNCTION_TOOL_OUTPUT_VALIDATOR]: validateOutput }
: {}),
};
}
/**
* Responses API only. Group function tools under a shared namespace.
*
* @param options - Namespace metadata and function tools to attach.
* @returns shallow-cloned function tools carrying namespace metadata.
*/
export function toolNamespace(options) {
if (typeof options !== 'object' || options === null) {
throw new UserError('toolNamespace() requires a namespace config object.');
}
const { name, description, tools } = options;
if (typeof name !== 'string' || name.trim().length === 0) {
throw new UserError('toolNamespace() requires a non-empty namespace name.');
}
if (typeof description !== 'string' || description.trim().length === 0) {
throw new UserError('toolNamespace() requires a non-empty description because the Responses API requires namespace descriptions.');
}
if (!Array.isArray(tools)) {
throw new UserError('toolNamespace() requires an array of function tools.');
}
if (tools.some((tool) => tool?.type !== 'function')) {
throw new UserError('toolNamespace() only supports FunctionTool instances.');
}
const namespaceName = toFunctionToolName(name.trim());
if (tools.some((tool) => tool.name === namespaceName)) {
throw new UserError(`toolNamespace() does not allow a tool named "${namespaceName}" inside namespace "${namespaceName}" because Responses self-namespaced calls would be ambiguous.`);
}
const normalizedDescription = description.trim();
return tools.map((tool) => cloneObjectWithDescriptorsAndOverrides(tool, {
[FUNCTION_TOOL_NAMESPACE]: namespaceName,
[FUNCTION_TOOL_NAMESPACE_DESCRIPTION]: normalizedDescription,
}));
}
function buildRequireApproval(requireApproval) {
const normalized = normalizeHostedMcpRequireApproval(requireApproval);
if (normalized === 'never') {
throw new UserError('Invalid hosted MCP requireApproval: approval-required branch cannot normalize to "never".');
}
return normalized;
}
function toMcpAllowedToolsFilter(allowedTools) {
if (typeof allowedTools === 'undefined') {
return undefined;
}
if (Array.isArray(allowedTools)) {
return { tool_names: allowedTools };
}
return { tool_names: allowedTools?.toolNames ?? [] };
}
//# sourceMappingURL=tool.mjs.map