@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
259 lines • 11.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.trackDeferredToolRecovery = trackDeferredToolRecovery;
exports.prepareAgentArtifacts = prepareAgentArtifacts;
exports.resolveModelVisibleToolNameCollisions = resolveModelVisibleToolNameCollisions;
const errors_1 = require("../errors.js");
const logger_1 = __importDefault(require("../logger.js"));
const tool_1 = require("../tool.js");
const toolIdentity_1 = require("../toolIdentity.js");
const serialize_1 = require("../utils/serialize.js");
const tracing_1 = require("./tracing.js");
const toolSearch_1 = require("./toolSearch.js");
const computerInitPromisesByRunState = new WeakMap();
// Recovery intent is owned by a live run and its public source Agent, never by
// serialized processing metadata or a prepared capability clone.
const deferredToolRecoveryAgents = new WeakMap();
function trackDeferredToolRecovery(state, sourceAgent, response) {
if (response.functionToolsNotFound?.some((call) => call.reason === 'not_loaded')) {
deferredToolRecoveryAgents.set(state, sourceAgent);
}
else {
deferredToolRecoveryAgents.delete(state);
}
}
function getComputerInitMap(state) {
let initMap = computerInitPromisesByRunState.get(state);
if (!initMap) {
initMap = new WeakMap();
computerInitPromisesByRunState.set(state, initMap);
}
return initMap;
}
async function initComputerOnce(computer, state) {
if (typeof computer.initRun !== 'function') {
return;
}
const initMap = getComputerInitMap(state);
const existing = initMap.get(computer);
if (existing) {
await existing;
return;
}
const initPromise = (async () => {
await computer.initRun?.(state._context);
})();
initMap.set(computer, initPromise);
try {
await initPromise;
}
catch (error) {
initMap.delete(computer);
throw error;
}
}
/**
* Collects tools and handoffs for the current agent so model calls and tracing share the same
* snapshot of enabled capabilities.
*/
async function prepareAgentArtifacts(state, executionAgent = state._currentAgent, toolNameCollisionPolicy = 'warn') {
const collectedCapabilities = await collectAgentCapabilities(state, executionAgent);
const capabilities = resolveModelVisibleToolNameCollisions(collectedCapabilities.tools, collectedCapabilities.handoffs, toolNameCollisionPolicy);
if (deferredToolRecoveryAgents.get(state) === state._currentAgent &&
!capabilities.tools.some(toolSearch_1.isDeferredHostedMcpTool)) {
// A hosted search may omit definitions already loaded in the response chain.
// Use the existing local loader for this recovery turn so the active Agent
// receives fresh discovery before any deferred function can execute.
// Keep deferred MCP on server search: client output serializes its credentials.
capabilities.tools = capabilities.tools.map((tool) => tool.type === 'hosted_tool' &&
tool.providerData?.type === 'tool_search' &&
tool.providerData.execution !== 'client' &&
tool.providerData.parameters == null
? {
...tool,
providerData: { ...tool.providerData, execution: 'client' },
}
: tool);
}
(0, toolSearch_1.validateClientToolSearchSupport)(capabilities.tools);
await warmUpComputerTools(capabilities.tools, state._context);
await initializeComputerTools(capabilities.tools, state);
state.setCurrentAgentSpan((0, tracing_1.ensureAgentSpan)({
agent: executionAgent,
handoffs: capabilities.handoffs,
tools: capabilities.tools,
currentSpan: state._currentAgentSpan,
}));
return {
...capabilities,
serializedHandoffs: capabilities.handoffs.map((handoff) => (0, serialize_1.serializeHandoff)(handoff)),
serializedTools: await Promise.all(capabilities.tools.map(async (tool) => (0, serialize_1.serializeTool)(tool.type === 'computer'
? {
...tool,
// Another run may replace tool.computer while preparation awaits.
computer: await (0, tool_1.resolveComputer)({
tool,
runContext: state._context,
}),
}
: tool))),
toolsExplicitlyProvided: executionAgent.hasExplicitToolConfig(),
};
}
async function collectAgentCapabilities(state, executionAgent) {
const handoffs = await executionAgent.getEnabledHandoffs(state._context);
const configuredTools = (await executionAgent.getAllTools(state._context, state._currentAgentSpan));
const runtimeLoadedTools = state.getToolSearchRuntimeTools(state._currentAgent);
const enabledRuntimeTools = (await Promise.all(runtimeLoadedTools.map(async (tool) => ({
tool,
enabled: tool.type !== 'function' ||
(await tool.isEnabled(state._context, state._currentAgent)),
}))))
.filter(({ enabled }) => enabled)
.map(({ tool }) => tool);
return { handoffs, tools: [...configuredTools, ...enabledRuntimeTools] };
}
/** @internal */
function resolveModelVisibleToolNameCollisions(tools, handoffs, collisionPolicy) {
const topLevelEntriesByName = new Map();
const namespacedFunctionEntriesByName = new Map();
const deferredFunctionEntriesByName = new Map();
const addName = (entriesByName, name, entry) => {
const entries = entriesByName.get(name) ?? [];
entries.push(entry);
entriesByName.set(name, entries);
};
for (const [index, tool] of tools.entries()) {
if (tool.type === 'function') {
const namespace = (0, toolIdentity_1.getFunctionToolNamespace)(tool);
if (namespace === tool.name) {
throw new errors_1.UserError('Responses tool search reserves same-name namespaces for deferred top-level function tools. Rename the namespace or tool name to avoid ambiguous dispatch.');
}
const functionCategory = namespace
? 'namespaced'
: tool.deferLoading === true
? 'deferred'
: 'top-level';
const entry = {
kind: 'function tool',
index,
functionCategory,
};
const dispatchName = (0, toolIdentity_1.getFunctionToolQualifiedName)(tool) ?? tool.name;
addName(functionCategory === 'namespaced'
? namespacedFunctionEntriesByName
: functionCategory === 'deferred'
? deferredFunctionEntriesByName
: topLevelEntriesByName, dispatchName, entry);
}
}
for (const [index, handoff] of handoffs.entries()) {
addName(topLevelEntriesByName, handoff.toolName, {
kind: 'handoff',
index,
});
}
const namespacedDuplicates = [...namespacedFunctionEntriesByName.entries()]
.filter(([, entries]) => entries.length > 1)
.sort(([left], [right]) => left.localeCompare(right));
const deferredDuplicates = [...deferredFunctionEntriesByName.entries()]
.filter(([, entries]) => entries.length > 1)
.sort(([left], [right]) => left.localeCompare(right));
const strictDuplicatesByName = new Map([
...namespacedDuplicates,
...deferredDuplicates,
]);
const strictDuplicates = [...strictDuplicatesByName.entries()].sort(([left], [right]) => left.localeCompare(right));
if (strictDuplicates.length > 0) {
throwToolNameCollisionError(strictDuplicates);
}
const topLevelDuplicates = [...topLevelEntriesByName.entries()]
.filter(([, entries]) => entries.length > 1)
.sort(([left], [right]) => left.localeCompare(right));
if (topLevelDuplicates.length === 0) {
return { tools, handoffs };
}
if (collisionPolicy === 'error') {
throwToolNameCollisionError(topLevelDuplicates);
}
const retainedToolIndices = new Set(tools.map((_, index) => index));
const retainedHandoffIndices = new Set(handoffs.map((_, index) => index));
for (const [name, entries] of topLevelDuplicates) {
warnToolNameCollision(name, entries);
const handoffEntries = entries.filter((entry) => entry.kind === 'handoff');
const winner = handoffEntries.at(-1) ?? entries.at(-1);
for (const entry of entries) {
if (entry === winner) {
continue;
}
if (entry.kind === 'function tool') {
retainedToolIndices.delete(entry.index);
}
else {
retainedHandoffIndices.delete(entry.index);
}
}
}
return {
tools: tools.filter((_, index) => retainedToolIndices.has(index)),
handoffs: handoffs.filter((_, index) => retainedHandoffIndices.has(index)),
};
}
const toolNameCollisionRemediation = 'Function tools and handoffs must have unique routed names. Assign unique tool names or toolNameOverride values, or use a namespace.';
function throwToolNameCollisionError(duplicates) {
if (logger_1.default.dontLogToolData) {
throw new errors_1.UserError(`Duplicate enabled function tool or handoff names found. ${toolNameCollisionRemediation}`);
}
const label = duplicates.length === 1 ? 'name' : 'names';
const details = duplicates
.map(([name, entries]) => `'${name}' (${formatToolKinds(entries.map((entry) => entry.kind))})`)
.join(', ');
throw new errors_1.UserError(`Duplicate enabled tool ${label} found: ${details}. ${toolNameCollisionRemediation}`);
}
function warnToolNameCollision(name, entries) {
if (logger_1.default.dontLogToolData) {
logger_1.default.warn('Tool name collision detected. Assign unique routed tool names or enable tool data logging for details. Only the current dispatch winner will be exposed.');
return;
}
logger_1.default.warn(`Duplicate enabled tool name found: '${name}' (${formatToolKinds(entries.map((entry) => entry.kind))}). ${toolNameCollisionRemediation} Only the current dispatch winner will be exposed.`);
}
function formatToolKinds(kinds) {
const functionToolCount = kinds.filter((kind) => kind === 'function tool').length;
const handoffCount = kinds.length - functionToolCount;
const descriptions = [];
if (functionToolCount > 0) {
descriptions.push(functionToolCount === 1
? 'function tool'
: `${functionToolCount} function tools`);
}
if (handoffCount > 0) {
descriptions.push(handoffCount === 1 ? 'handoff' : `${handoffCount} handoffs`);
}
return descriptions.join(' and ');
}
async function warmUpComputerTools(tools, runContext) {
const computerTools = tools.filter((tool) => tool.type === 'computer');
if (computerTools.length === 0) {
return;
}
await Promise.all(computerTools.map(async (tool) => {
await (0, tool_1.resolveComputer)({ tool, runContext });
}));
}
async function initializeComputerTools(tools, state) {
const computerTools = tools.filter((tool) => tool.type === 'computer');
if (computerTools.length === 0) {
return;
}
await Promise.all(computerTools.map(async (tool) => {
const computer = await (0, tool_1.resolveComputer)({
tool,
runContext: state._context,
});
await initComputerOnce(computer, state);
}));
}
//# sourceMappingURL=modelPreparation.js.map