UNPKG

@openai/agents-core

Version:

The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.

888 lines • 41.8 kB
import { ModelBehaviorError } from "../errors.mjs"; import { RunCompactionItem, RunHandoffCallItem, RunInputItem, RunMessageOutputItem, RunReasoningItem, RunToolApprovalItem, RunToolCallItem, RunToolCallOutputItem, RunToolSearchCallItem, RunToolSearchOutputItem, } from "../items.mjs"; import { getClientToolSearchExecutor, getToolSearchRuntimeRoutingKey, } from "../tool.mjs"; import { addErrorToCurrentSpan } from "../tracing/context.mjs"; import { buildFunctionToolLookupMap, getFunctionToolNamespace, getFunctionToolQualifiedName, getFunctionToolStateKey, isDeferredTopLevelFunctionTool, getToolCallNamespace, resolveFunctionToolCall, } from "../toolIdentity.mjs"; import { getToolSearchMatchKey, getToolSearchExecution, getToolSearchOutputReplacementKey, getToolSearchProviderCallId, } from "../utils/index.mjs"; import { createToolRunFunction } from "./types.mjs"; import { addHostedMcpToolsFromToolSearchOutput, addLoadedToolNamesFromToolSearchOutput, createBuiltInClientToolSearchOutput, executeCustomClientToolSearch, getClientToolSearchHelper, toolSearchOutputLoadsHostedMcpTool, recordHostedMcpDiscovery, registerRuntimeToolSearchTools, } from "./toolSearch.mjs"; import { ensureToolCallerAllowed } from "./toolCaller.mjs"; import { assertValidCompactionItems, trimToLatestCompaction } from "./items.mjs"; import { attributeToolSearchOutput } from "./toolSearchAttribution.mjs"; function ensureToolAvailable(tool, message, data) { if (!tool) { addErrorToCurrentSpan({ message, data, }); throw new ModelBehaviorError(message); } return tool; } function ensureProgrammaticToolCallingAvailable(output, tools, agent, options) { const programmaticTool = tools.find((tool) => tool.type === 'hosted_tool' && tool.providerData?.type === 'programmatic_tool_calling'); if (programmaticTool || options.allowPromptSuppliedTools === true) { return; } ensureToolAvailable(programmaticTool, `Model produced a program item without programmaticToolCallingTool() in Agent (${agent.name}).`, { agent_name: agent.name, tool_call_id: output.callId, }); } const MCP_HOSTED_CALL_TYPES = new Set([ 'mcp_call', 'mcp_list_tools', 'mcp_approval_request', 'mcp_approval_response', ]); function ensureHostedToolCallAllowed(output, tools, mcpToolMap, loadedState, agent) { const providerType = output.providerData?.type; const isCodeInterpreterCall = providerType === 'code_interpreter_call' || providerType === 'code_interpreter' || output.name === 'code_interpreter_call' || output.name === 'code_interpreter'; if (isCodeInterpreterCall) { const codeInterpreterTool = tools.find((tool) => tool.type === 'hosted_tool' && tool.providerData?.type === 'code_interpreter'); if (!codeInterpreterTool) { return; } ensureToolCallerAllowed(output, codeInterpreterTool.providerData?.allowed_callers, codeInterpreterTool.name, agent); return; } if (!MCP_HOSTED_CALL_TYPES.has(providerType) && !MCP_HOSTED_CALL_TYPES.has(output.name)) { return; } const serverLabel = output.providerData?.server_label; if (typeof serverLabel !== 'string') { return; } const mcpTool = mcpToolMap.get(serverLabel); if (!mcpTool) { return; } ensureToolCallerAllowed(output, mcpTool.providerData.allowed_callers, serverLabel, agent); // Hosted MCP discovery can be reported before the tool-search result. // Listing tools does not execute them or mark the server as loaded. if (providerType === 'mcp_list_tools' || output.name === 'mcp_list_tools') { return; } if (mcpTool.providerData.defer_loading !== true || [ ...loadedState.keyedToolSearchOutputsByKey.values(), ...loadedState.anonymousToolSearchOutputs, ].some((searchOutput) => toolSearchOutputLoadsHostedMcpTool(searchOutput, serverLabel, output.providerData?.name))) { return; } const message = `Model produced deferred MCP call ${serverLabel} before it was loaded via tool_search.`; addErrorToCurrentSpan({ message, data: { agent_name: agent.name, mcp_server_label: serverLabel, tool_call_id: output.id, }, }); throw new ModelBehaviorError(message); } function handleToolCallAction({ output, tool, agent, errorMessage, errorData, items, toolsUsed, actions, buildAction, }) { const resolvedTool = ensureToolAvailable(tool, errorMessage, errorData); ensureToolCallerAllowed(output, resolvedTool.allowedCallers, resolvedTool.name, agent); items.push(new RunToolCallItem(output, agent)); toolsUsed.push(resolvedTool.name); actions.push(buildAction(resolvedTool)); } function recordHandoffRequest(output, handoff, agent, items, toolsUsed, handoffs, completedInvocations) { toolsUsed.push(output.name); const request = { toolCall: output, handoff }; if (completedInvocations?.has(output.callId) || (handoffs.length > 0 && !completedInvocations?.has(handoffs[0].toolCall.callId))) { handoffs.push(request); return; } // Keep the first fresh request first for execution and managed continuation. // Preflight still validates the identity of every request, including replays. handoffs.unshift(request); items.push(new RunHandoffCallItem(output, agent)); } function resolveFunctionOrHandoff(toolCall, handoffMap, functionMap) { const namespace = getToolCallNamespace(toolCall); if (!namespace) { const handoff = handoffMap.get(toolCall.name); if (handoff) { return { type: 'handoff', handoff }; } } const functionTool = resolveFunctionToolCall(toolCall, functionMap); if (!functionTool) { return { type: 'not_found', toolName: getToolCallNamespace(toolCall) && getToolCallNamespace(toolCall) !== toolCall.name ? `${getToolCallNamespace(toolCall)}.${toolCall.name}` : toolCall.name, }; } return { type: 'function', tool: functionTool }; } function throwFunctionToolNotFound(toolName, agent) { const message = `Tool ${toolName} not found in agent ${agent.name}.`; addErrorToCurrentSpan({ message, data: { tool_name: toolName, agent_name: agent.name, }, }); throw new ModelBehaviorError(message); } function recordMissingFunctionTool(output, toolName, agent, items, toolsUsed, functionToolsNotFound, reason) { toolsUsed.push(toolName); items.push(new RunToolCallItem(output, agent)); functionToolsNotFound.push({ toolCall: output, toolName, ...(reason ? { reason } : {}), }); } function normalizeFunctionToolCallForStorage(toolCall, tool) { const namespace = getToolCallNamespace(toolCall); const explicitNamespace = getFunctionToolNamespace(tool); const qualifiedToolName = getFunctionToolQualifiedName(tool); if (namespace || !explicitNamespace || !qualifiedToolName || toolCall.name !== qualifiedToolName) { return toolCall; } return { ...toolCall, name: tool.name, namespace: explicitNamespace, }; } function getRawAgentInputItem(item) { if (item && typeof item === 'object' && 'rawItem' in item && item.rawItem && typeof item.rawItem === 'object') { return item.rawItem; } return item; } function refreshLoadedDeferredToolNames(state) { state.loadedToolNames.clear(); for (const toolSearchOutput of state.keyedToolSearchOutputsByKey.values()) { addLoadedToolNamesFromToolSearchOutput(toolSearchOutput, state.loadedToolNames); } for (const toolSearchOutput of state.anonymousToolSearchOutputs) { addLoadedToolNamesFromToolSearchOutput(toolSearchOutput, state.loadedToolNames); } } function recordLoadedToolSearchOutput(state, toolSearchOutput) { const replacementKey = getToolSearchOutputReplacementKey(toolSearchOutput); if (replacementKey) { state.keyedToolSearchOutputsByKey.set(replacementKey, toolSearchOutput); } else { state.anonymousToolSearchOutputs.push(toolSearchOutput); } refreshLoadedDeferredToolNames(state); } function collectLoadedDeferredToolStateFromHistory(items, agent, agentName) { const state = { anonymousToolSearchOutputs: [], keyedToolSearchOutputsByKey: new Map(), loadedToolNames: new Set(), }; // Use the active history boundary without discarding the surviving item owners. for (const item of trimToLatestCompaction(items)) { if ((item instanceof RunToolSearchOutputItem || item instanceof RunInputItem) && item.agent !== agent) { continue; } const rawItem = getRawAgentInputItem(item); if (rawItem?.type !== 'tool_search_output') { continue; } // Raw history has no live owner; only an unambiguous recorded name can restore discovery. if (!(item instanceof RunToolSearchOutputItem) && (typeof agentName !== 'string' || rawItem.toolSearchAgentName !== agentName)) { continue; } const replacementKey = getToolSearchOutputReplacementKey(rawItem); if (replacementKey) { state.keyedToolSearchOutputsByKey.set(replacementKey, rawItem); } else { state.anonymousToolSearchOutputs.push(rawItem); } } refreshLoadedDeferredToolNames(state); return state; } function seedHostedMcpToolsFromLoadedDeferredToolState(state, mcpToolMap, preserveExistingServerLabels) { for (const toolSearchOutput of state.keyedToolSearchOutputsByKey.values()) { addHostedMcpToolsFromToolSearchOutput(toolSearchOutput, mcpToolMap, { preserveExistingServerLabels, }); } for (const toolSearchOutput of state.anonymousToolSearchOutputs) { addHostedMcpToolsFromToolSearchOutput(toolSearchOutput, mcpToolMap, { preserveExistingServerLabels, }); } } function buildFunctionToolMap(tools) { return buildFunctionToolLookupMap(tools.filter((t) => t.type === 'function')); } function getUnresolvedClientToolSearchCalls(modelResponse, hasClientToolSearchTool) { const clientToolSearchCalls = []; const pendingCalls = []; const latestCallByMatchKey = new Map(); const resolvedCalls = new Set(); for (const output of modelResponse.output) { if (output.type === 'tool_search_call') { const execution = getToolSearchExecution(output); if (execution === 'client' || (typeof execution === 'undefined' && hasClientToolSearchTool)) { clientToolSearchCalls.push(output); pendingCalls.push(output); const matchKey = getToolSearchMatchKey(output); if (matchKey) { latestCallByMatchKey.set(matchKey, output); } } continue; } if (output.type !== 'tool_search_output' || getToolSearchExecution(output) === 'server') { continue; } const explicitCallId = getToolSearchProviderCallId(output); const matchedCall = explicitCallId ? latestCallByMatchKey.get(explicitCallId) : pendingCalls.shift(); if (!matchedCall) { continue; } resolvedCalls.add(matchedCall); const pendingIndex = pendingCalls.indexOf(matchedCall); if (pendingIndex >= 0) { pendingCalls.splice(pendingIndex, 1); } } return clientToolSearchCalls.filter((call) => !resolvedCalls.has(call)); } function buildGeneratedClientToolSearchOutputMap(modelResponse, tools, hasClientToolSearchTool) { const clientToolSearchCalls = getUnresolvedClientToolSearchCalls(modelResponse, hasClientToolSearchTool); const generatedOutputs = new Map(); for (const toolSearchCall of clientToolSearchCalls) { generatedOutputs.set(toolSearchCall, createBuiltInClientToolSearchOutput(toolSearchCall, tools)); } return generatedOutputs; } async function buildGeneratedClientToolSearchOutputMapAsync(args) { const { agent, handoffs, modelResponse, replaceableRuntimeToolKeys, runContext, tools, } = args; const clientToolSearchTool = getClientToolSearchHelper(tools); const hasClientToolSearchTool = typeof clientToolSearchTool !== 'undefined'; const executionTools = [...tools]; const validationAvailableTools = [...tools]; const validationFunctionMap = buildFunctionToolMap(tools); const validationHandoffMap = new Map(handoffs.map((handoff) => [handoff.toolName, handoff])); const validationMcpToolMap = new Map(tools .filter((tool) => tool.type === 'hosted_tool' && tool.providerData?.type === 'mcp') .map((tool) => [tool.providerData.server_label, tool])); const validationReplaceableRuntimeToolKeys = new Set(replaceableRuntimeToolKeys); const clientToolSearchCalls = getUnresolvedClientToolSearchCalls(modelResponse, hasClientToolSearchTool); const generatedOutputs = new Map(); for (const toolSearchCall of clientToolSearchCalls) { if (clientToolSearchTool && getClientToolSearchExecutor(clientToolSearchTool)) { const generatedOutput = await executeCustomClientToolSearch({ agent, runContext, toolSearchCall, toolSearchTool: clientToolSearchTool, tools: executionTools, }); registerRuntimeToolSearchTools({ availableTools: validationAvailableTools, functionMap: validationFunctionMap, handoffMap: validationHandoffMap, mcpToolMap: validationMcpToolMap, replaceableRuntimeToolKeys: validationReplaceableRuntimeToolKeys, runtimeTools: generatedOutput.runtimeTools, }); generatedOutputs.set(toolSearchCall, generatedOutput); executionTools.push(...generatedOutput.callbackRuntimeTools); continue; } generatedOutputs.set(toolSearchCall, { output: createBuiltInClientToolSearchOutput(toolSearchCall, executionTools), runtimeTools: [], callbackRuntimeTools: [], }); } return generatedOutputs; } function checkDeferredFunctionToolLoaded(toolCall, tool, loadedToolNames, agent, behavior) { if (tool.deferLoading !== true) { return true; } const explicitNamespace = getFunctionToolNamespace(tool); const qualifiedName = getFunctionToolQualifiedName(tool); const isLoaded = (qualifiedName ? loadedToolNames.has(qualifiedName) : false) || ((!explicitNamespace || explicitNamespace === tool.name) && loadedToolNames.has(tool.name)); if (isLoaded) { return true; } if (behavior === 'return_error_to_model') { return false; } const toolName = qualifiedName ?? tool.name; const message = `Model produced deferred function call ${toolName} before it was loaded via tool_search.`; addErrorToCurrentSpan({ message, data: { agent_name: agent.name, tool_name: toolName, tool_call_id: toolCall.callId, }, }); throw new ModelBehaviorError(message); } function parseShellCallStatus(status) { if (status === 'in_progress' || status === 'completed' || status === 'incomplete') { return status; } return undefined; } function isShellCallPendingStatus(status) { if (typeof status === 'undefined') { return true; } return parseShellCallStatus(status) === 'in_progress'; } function hasPendingShellOutputStatus(output) { const outputStatus = output.status ?? output.providerData?.status; if (typeof outputStatus !== 'string') { return false; } return isShellCallPendingStatus(outputStatus); } /** * 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. */ export function processModelResponse(modelResponse, agent, tools, handoffs, priorItems = [], toolNotFoundBehavior = 'raise_error', processingOptions = {}) { assertValidCompactionItems(modelResponse.output); const items = []; const runHandoffs = []; const runFunctions = []; const functionToolsNotFound = []; const runComputerActions = []; const runShellActions = []; let hasHostedShellCall = false; 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 = buildFunctionToolMap(tools); 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])); const originalMcpServerLabels = new Set(mcpToolMap.keys()); const hasClientToolSearchTool = tools.some((tool) => tool.type === 'hosted_tool' && tool.providerData?.type === 'tool_search' && tool.providerData.execution === 'client'); const loadedDeferredToolState = collectLoadedDeferredToolStateFromHistory(priorItems, agent, processingOptions.toolSearchAgentName); seedHostedMcpToolsFromLoadedDeferredToolState(loadedDeferredToolState, mcpToolMap, originalMcpServerLabels); const generatedClientToolSearchOutputsByCall = buildGeneratedClientToolSearchOutputMap(modelResponse, tools, hasClientToolSearchTool); let hasGeneratedClientToolSearchOutputs = false; for (const output of modelResponse.output) { if (output.type === 'message') { if (output.role === 'assistant') { items.push(new RunMessageOutputItem(output, agent)); } } else if (output.type === 'tool_search_call') { items.push(new RunToolSearchCallItem(output, agent)); toolsUsed.push('tool_search'); const generatedOutput = generatedClientToolSearchOutputsByCall.get(output); if (generatedOutput) { items.push(new RunToolSearchOutputItem(attributeToolSearchOutput(generatedOutput, processingOptions.toolSearchAgentName), agent)); recordLoadedToolSearchOutput(loadedDeferredToolState, generatedOutput); addHostedMcpToolsFromToolSearchOutput(generatedOutput, mcpToolMap, { preserveExistingServerLabels: originalMcpServerLabels, }); hasGeneratedClientToolSearchOutputs = true; } } else if (output.type === 'tool_search_output') { const recordedOutput = recordHostedMcpDiscovery(output, [ ...agent.tools, ...tools, ]); items.push(new RunToolSearchOutputItem(attributeToolSearchOutput(recordedOutput, processingOptions.toolSearchAgentName), agent)); recordLoadedToolSearchOutput(loadedDeferredToolState, recordedOutput); addHostedMcpToolsFromToolSearchOutput(output, mcpToolMap, { preserveExistingServerLabels: originalMcpServerLabels, }); } else if (output.type === 'program') { ensureProgrammaticToolCallingAvailable(output, tools, agent, processingOptions); items.push(new RunToolCallItem(output, agent)); toolsUsed.push('programmatic_tool_calling'); } else if (output.type === 'program_output') { items.push(new RunToolCallOutputItem(output, agent, output.output)); } else if (output.type === 'hosted_tool_call') { ensureHostedToolCallAllowed(output, tools, mcpToolMap, loadedDeferredToolState, agent); items.push(new 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})`; addErrorToCurrentSpan({ message, data: { mcp_server_label: mcpServerLabel }, }); throw new ModelBehaviorError(message); } // Do this approval later: // We support both onApproval callback (like the Python SDK does) and HITL patterns. const approvalItem = new 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, ...(output.caller ? { caller: output.caller } : {}), }, 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 RunReasoningItem(output, agent)); } else if (output.type === 'compaction') { items.push(new RunCompactionItem(output, agent)); } else if (output.type === 'computer_call') { handleToolCallAction({ output, tool: computerTool, agent, errorMessage: 'Model produced computer action without a computer tool.', errorData: { agent_name: agent.name }, items, toolsUsed, actions: runComputerActions, buildAction: (resolvedTool) => ({ toolCall: output, computer: resolvedTool, }), }); } else if (output.type === 'shell_call') { const resolvedShellTool = ensureToolAvailable(shellTool, 'Model produced shell action without a shell tool.', { agent_name: agent.name }); ensureToolCallerAllowed(output, resolvedShellTool.allowedCallers, resolvedShellTool.name, agent); items.push(new RunToolCallItem(output, agent)); toolsUsed.push(resolvedShellTool.name); const shellEnvironmentType = resolvedShellTool.environment?.type ?? 'local'; // Hosted container shell is executed by the API provider, so no local action is queued. if (shellEnvironmentType !== 'local') { if (isShellCallPendingStatus(output.status)) { hasHostedShellCall = true; } continue; } if (!resolvedShellTool.shell) { const message = 'Model produced local shell action without a local shell implementation.'; addErrorToCurrentSpan({ message, data: { agent_name: agent.name }, }); throw new ModelBehaviorError(message); } runShellActions.push({ toolCall: output, shell: resolvedShellTool, }); } else if (output.type === 'shell_call_output') { items.push(new RunToolCallOutputItem(output, agent, output.output)); if (hasPendingShellOutputStatus(output)) { hasHostedShellCall = true; } } else if (output.type === 'apply_patch_call') { handleToolCallAction({ output, tool: applyPatchTool, agent, errorMessage: 'Model produced apply_patch action without an apply_patch tool.', errorData: { agent_name: agent.name }, items, toolsUsed, actions: runApplyPatchActions, buildAction: (resolvedTool) => ({ toolCall: output, applyPatch: resolvedTool, }), }); } /* * Intentionally skip returning here so function_call processing can still * run when output.type matches other tool call types. */ if (output.type !== 'function_call') { continue; } const resolved = resolveFunctionOrHandoff(output, handoffMap, functionMap); if (resolved.type === 'not_found') { if (toolNotFoundBehavior !== 'return_error_to_model') { throwFunctionToolNotFound(resolved.toolName, agent); } recordMissingFunctionTool(output, resolved.toolName, agent, items, toolsUsed, functionToolsNotFound); } else if (resolved.type === 'handoff') { ensureToolCallerAllowed(output, undefined, resolved.handoff.toolName, agent); recordHandoffRequest(output, resolved.handoff, agent, items, toolsUsed, runHandoffs, processingOptions.completedInvocations); } else { ensureToolCallerAllowed(output, resolved.tool.allowedCallers, getFunctionToolQualifiedName(resolved.tool) ?? resolved.tool.name, agent); if (!checkDeferredFunctionToolLoaded(output, resolved.tool, loadedDeferredToolState.loadedToolNames, agent, toolNotFoundBehavior)) { recordMissingFunctionTool(normalizeFunctionToolCallForStorage(output, resolved.tool), getFunctionToolQualifiedName(resolved.tool) ?? resolved.tool.name, agent, items, toolsUsed, functionToolsNotFound, 'not_loaded'); continue; } const normalizedToolCall = normalizeFunctionToolCallForStorage(output, resolved.tool); toolsUsed.push(getFunctionToolQualifiedName(resolved.tool) ?? resolved.tool.name); items.push(new RunToolCallItem(normalizedToolCall, agent, !getToolCallNamespace(normalizedToolCall) && isDeferredTopLevelFunctionTool(resolved.tool) ? getFunctionToolStateKey(resolved.tool) : undefined)); runFunctions.push(createToolRunFunction({ toolCall: normalizedToolCall, tool: resolved.tool, availableFunctionTools: [...new Set(functionMap.values())], })); } } return { newItems: items, handoffs: runHandoffs, functions: runFunctions, functionToolsNotFound, computerActions: runComputerActions, shellActions: runShellActions, applyPatchActions: runApplyPatchActions, mcpApprovalRequests: runMCPApprovalRequests, toolsUsed: toolsUsed, hasToolsOrApprovalsToRun() { return (runHandoffs.length > 0 || runFunctions.length > 0 || functionToolsNotFound.length > 0 || runMCPApprovalRequests.length > 0 || runComputerActions.length > 0 || runShellActions.length > 0 || hasHostedShellCall || runApplyPatchActions.length > 0 || hasGeneratedClientToolSearchOutputs); }, }; } export async function processModelResponseAsync(modelResponse, agent, tools, handoffs, state, priorItems = [], toolNotFoundBehavior = 'raise_error', processingOptions = {}) { assertValidCompactionItems(modelResponse.output); processingOptions = { ...processingOptions, toolSearchAgentName: state._getToolSearchAgentName(agent), completedInvocations: state._completedToolInvocations.get(agent), }; const clientToolSearchTool = getClientToolSearchHelper(tools); const hasCustomClientToolSearchExecutor = Boolean(clientToolSearchTool && getClientToolSearchExecutor(clientToolSearchTool)); const hasRelevantClientToolSearchCall = modelResponse.output.some((output) => output.type === 'tool_search_call' && (getToolSearchExecution(output) === 'client' || (typeof getToolSearchExecution(output) === 'undefined' && typeof clientToolSearchTool !== 'undefined'))); if (!hasCustomClientToolSearchExecutor || !hasRelevantClientToolSearchCall) { return processModelResponse(modelResponse, agent, tools, handoffs, priorItems, toolNotFoundBehavior, processingOptions); } processingOptions.beforeClientToolSearch?.(); const items = []; const runHandoffs = []; const runFunctions = []; const functionToolsNotFound = []; const runComputerActions = []; const runShellActions = []; let hasHostedShellCall = false; const runApplyPatchActions = []; const runMCPApprovalRequests = []; const toolsUsed = []; const handoffMap = new Map(handoffs.map((h) => [h.toolName, h])); const functionMap = buildFunctionToolMap(tools); 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])); const originalMcpServerLabels = new Set(mcpToolMap.keys()); const replaceableRuntimeToolKeys = new Set(state .getToolSearchRuntimeTools(agent) .map((tool) => getToolSearchRuntimeRoutingKey(tool)) .filter((key) => typeof key === 'string')); const loadedDeferredToolState = collectLoadedDeferredToolStateFromHistory(priorItems, agent, processingOptions.toolSearchAgentName); seedHostedMcpToolsFromLoadedDeferredToolState(loadedDeferredToolState, mcpToolMap, originalMcpServerLabels); const generatedClientToolSearchOutputsByCall = await buildGeneratedClientToolSearchOutputMapAsync({ agent, handoffs, modelResponse, replaceableRuntimeToolKeys, runContext: state._context, tools, }); let hasGeneratedClientToolSearchOutputs = false; const availableTools = [...tools]; for (const output of modelResponse.output) { if (output.type === 'message') { if (output.role === 'assistant') { items.push(new RunMessageOutputItem(output, agent)); } } else if (output.type === 'tool_search_call') { items.push(new RunToolSearchCallItem(output, agent)); toolsUsed.push('tool_search'); const generatedOutput = generatedClientToolSearchOutputsByCall.get(output); if (generatedOutput) { items.push(new RunToolSearchOutputItem(attributeToolSearchOutput(generatedOutput.output, processingOptions.toolSearchAgentName), agent)); recordLoadedToolSearchOutput(loadedDeferredToolState, generatedOutput.output); addHostedMcpToolsFromToolSearchOutput(generatedOutput.output, mcpToolMap, { preserveExistingServerLabels: originalMcpServerLabels, }); const replacedRuntimeTools = state.getToolSearchRuntimeToolsForOutput(agent, generatedOutput.output); const registeredRuntimeTools = registerRuntimeToolSearchTools({ availableTools, functionMap, handoffMap, mcpToolMap, replaceableRuntimeToolKeys, replacedRuntimeTools, runtimeTools: generatedOutput.runtimeTools, }); state.recordToolSearchRuntimeTools(agent, generatedOutput.output, registeredRuntimeTools); hasGeneratedClientToolSearchOutputs = true; } } else if (output.type === 'tool_search_output') { const recordedOutput = recordHostedMcpDiscovery(output, [ ...agent.tools, ...availableTools, ]); items.push(new RunToolSearchOutputItem(attributeToolSearchOutput(recordedOutput, processingOptions.toolSearchAgentName), agent)); recordLoadedToolSearchOutput(loadedDeferredToolState, recordedOutput); addHostedMcpToolsFromToolSearchOutput(output, mcpToolMap, { preserveExistingServerLabels: originalMcpServerLabels, }); } else if (output.type === 'program') { ensureProgrammaticToolCallingAvailable(output, availableTools, agent, processingOptions); items.push(new RunToolCallItem(output, agent)); toolsUsed.push('programmatic_tool_calling'); } else if (output.type === 'program_output') { items.push(new RunToolCallOutputItem(output, agent, output.output)); } else if (output.type === 'hosted_tool_call') { ensureHostedToolCallAllowed(output, availableTools, mcpToolMap, loadedDeferredToolState, agent); items.push(new RunToolCallItem(output, agent)); const toolName = output.name; toolsUsed.push(toolName); if (output.providerData?.type === 'mcp_approval_request' || output.name === 'mcp_approval_request') { 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})`; addErrorToCurrentSpan({ message, data: { mcp_server_label: mcpServerLabel }, }); throw new ModelBehaviorError(message); } const approvalItem = new RunToolApprovalItem({ type: 'hosted_tool_call', name: providerData.name, id: providerData.id, status: 'in_progress', providerData, ...(output.caller ? { caller: output.caller } : {}), }, agent); runMCPApprovalRequests.push({ requestItem: approvalItem, mcpTool: mcpServerTool, }); if (!mcpServerTool.providerData.on_approval) { items.push(approvalItem); } } } else if (output.type === 'reasoning') { items.push(new RunReasoningItem(output, agent)); } else if (output.type === 'compaction') { items.push(new RunCompactionItem(output, agent)); } else if (output.type === 'computer_call') { handleToolCallAction({ output, tool: computerTool, agent, errorMessage: 'Model produced computer action without a computer tool.', errorData: { agent_name: agent.name }, items, toolsUsed, actions: runComputerActions, buildAction: (resolvedTool) => ({ toolCall: output, computer: resolvedTool, }), }); } else if (output.type === 'shell_call') { const resolvedShellTool = ensureToolAvailable(shellTool, 'Model produced shell action without a shell tool.', { agent_name: agent.name }); ensureToolCallerAllowed(output, resolvedShellTool.allowedCallers, resolvedShellTool.name, agent); items.push(new RunToolCallItem(output, agent)); toolsUsed.push(resolvedShellTool.name); const shellEnvironmentType = resolvedShellTool.environment?.type ?? 'local'; if (shellEnvironmentType !== 'local') { if (isShellCallPendingStatus(output.status)) { hasHostedShellCall = true; } continue; } if (!resolvedShellTool.shell) { const message = 'Model produced local shell action without a local shell implementation.'; addErrorToCurrentSpan({ message, data: { agent_name: agent.name }, }); throw new ModelBehaviorError(message); } runShellActions.push({ toolCall: output, shell: resolvedShellTool, }); } else if (output.type === 'shell_call_output') { items.push(new RunToolCallOutputItem(output, agent, output.output)); if (hasPendingShellOutputStatus(output)) { hasHostedShellCall = true; } } else if (output.type === 'apply_patch_call') { handleToolCallAction({ output, tool: applyPatchTool, agent, errorMessage: 'Model produced apply_patch action without an apply_patch tool.', errorData: { agent_name: agent.name }, items, toolsUsed, actions: runApplyPatchActions, buildAction: (resolvedTool) => ({ toolCall: output, applyPatch: resolvedTool, }), }); } if (output.type !== 'function_call') { continue; } const resolved = resolveFunctionOrHandoff(output, handoffMap, functionMap); if (resolved.type === 'not_found') { if (toolNotFoundBehavior !== 'return_error_to_model') { throwFunctionToolNotFound(resolved.toolName, agent); } recordMissingFunctionTool(output, resolved.toolName, agent, items, toolsUsed, functionToolsNotFound); } else if (resolved.type === 'handoff') { ensureToolCallerAllowed(output, undefined, resolved.handoff.toolName, agent); recordHandoffRequest(output, resolved.handoff, agent, items, toolsUsed, runHandoffs, processingOptions.completedInvocations); } else { ensureToolCallerAllowed(output, resolved.tool.allowedCallers, getFunctionToolQualifiedName(resolved.tool) ?? resolved.tool.name, agent); if (!checkDeferredFunctionToolLoaded(output, resolved.tool, loadedDeferredToolState.loadedToolNames, agent, toolNotFoundBehavior)) { recordMissingFunctionTool(normalizeFunctionToolCallForStorage(output, resolved.tool), getFunctionToolQualifiedName(resolved.tool) ?? resolved.tool.name, agent, items, toolsUsed, functionToolsNotFound, 'not_loaded'); continue; } const normalizedToolCall = normalizeFunctionToolCallForStorage(output, resolved.tool); toolsUsed.push(getFunctionToolQualifiedName(resolved.tool) ?? resolved.tool.name); items.push(new RunToolCallItem(normalizedToolCall, agent, !getToolCallNamespace(normalizedToolCall) && isDeferredTopLevelFunctionTool(resolved.tool) ? getFunctionToolStateKey(resolved.tool) : undefined)); runFunctions.push(createToolRunFunction({ toolCall: normalizedToolCall, tool: resolved.tool, availableFunctionTools: [...new Set(functionMap.values())], })); } } return { newItems: items, handoffs: runHandoffs, functions: runFunctions, functionToolsNotFound, computerActions: runComputerActions, shellActions: runShellActions, applyPatchActions: runApplyPatchActions, mcpApprovalRequests: runMCPApprovalRequests, toolsUsed, hasToolsOrApprovalsToRun() { return (runHandoffs.length > 0 || runFunctions.length > 0 || functionToolsNotFound.length > 0 || runMCPApprovalRequests.length > 0 || runComputerActions.length > 0 || runShellActions.length > 0 || hasHostedShellCall || runApplyPatchActions.length > 0 || hasGeneratedClientToolSearchOutputs); }, }; } //# sourceMappingURL=modelOutputs.mjs.map