UNPKG

@openai/agents-core

Version:

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

244 lines 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processModelResponse = processModelResponse; const errors_1 = require("../errors.js"); const items_1 = require("../items.js"); const context_1 = require("../tracing/context.js"); function ensureToolAvailable(tool, message, data) { if (!tool) { (0, context_1.addErrorToCurrentSpan)({ message, data, }); throw new errors_1.ModelBehaviorError(message); } return tool; } function handleToolCallAction({ output, tool, agent, errorMessage, errorData, items, toolsUsed, actions, buildAction, }) { const resolvedTool = ensureToolAvailable(tool, errorMessage, errorData); items.push(new items_1.RunToolCallItem(output, agent)); toolsUsed.push(resolvedTool.name); actions.push(buildAction(resolvedTool)); } function resolveFunctionOrHandoff(toolCall, handoffMap, functionMap, agent) { const handoff = handoffMap.get(toolCall.name); if (handoff) { return { type: 'handoff', handoff }; } const functionTool = functionMap.get(toolCall.name); if (!functionTool) { const message = `Tool ${toolCall.name} not found in agent ${agent.name}.`; (0, context_1.addErrorToCurrentSpan)({ message, data: { tool_name: toolCall.name, agent_name: agent.name, }, }); throw new errors_1.ModelBehaviorError(message); } return { type: 'function', tool: functionTool }; } 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. */ function processModelResponse(modelResponse, agent, tools, handoffs) { const items = []; const runHandoffs = []; const runFunctions = []; 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 = new Map(tools .filter((t) => t.type === 'function') .map((t) => [t.name, t])); const computerTool = tools.find((t) => t.type === 'computer'); const shellTool = tools.find((t) => t.type === 'shell'); const applyPatchTool = tools.find((t) => t.type === 'apply_patch'); const mcpToolMap = new Map(tools .filter((t) => t.type === 'hosted_tool' && t.providerData?.type === 'mcp') .map((t) => t) .map((t) => [t.providerData.server_label, t])); for (const output of modelResponse.output) { if (output.type === 'message') { if (output.role === 'assistant') { items.push(new items_1.RunMessageOutputItem(output, agent)); } } else if (output.type === 'hosted_tool_call') { items.push(new items_1.RunToolCallItem(output, agent)); const toolName = output.name; toolsUsed.push(toolName); if (output.providerData?.type === 'mcp_approval_request' || output.name === 'mcp_approval_request') { // Hosted remote MCP server's approval process const providerData = output.providerData; const mcpServerLabel = providerData.server_label; const mcpServerTool = mcpToolMap.get(mcpServerLabel); if (typeof mcpServerTool === 'undefined') { const message = `MCP server (${mcpServerLabel}) not found in Agent (${agent.name})`; (0, context_1.addErrorToCurrentSpan)({ message, data: { mcp_server_label: mcpServerLabel }, }); throw new errors_1.ModelBehaviorError(message); } // Do this approval later: // We support both onApproval callback (like the Python SDK does) and HITL patterns. const approvalItem = new items_1.RunToolApprovalItem({ type: 'hosted_tool_call', // We must use this name to align with the name sent from the servers name: providerData.name, id: providerData.id, status: 'in_progress', providerData, }, agent); runMCPApprovalRequests.push({ requestItem: approvalItem, mcpTool: mcpServerTool, }); if (!mcpServerTool.providerData.on_approval) { // When onApproval function exists, it confirms the approval right after this. // Thus, this approval item must be appended only for the next turn interruption patterns. items.push(approvalItem); } } } else if (output.type === 'reasoning') { items.push(new items_1.RunReasoningItem(output, agent)); } else if (output.type === 'computer_call') { 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 }); items.push(new items_1.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.'; (0, context_1.addErrorToCurrentSpan)({ message, data: { agent_name: agent.name }, }); throw new errors_1.ModelBehaviorError(message); } runShellActions.push({ toolCall: output, shell: resolvedShellTool, }); } else if (output.type === 'shell_call_output') { items.push(new items_1.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; } toolsUsed.push(output.name); const resolved = resolveFunctionOrHandoff(output, handoffMap, functionMap, agent); if (resolved.type === 'handoff') { items.push(new items_1.RunHandoffCallItem(output, agent)); runHandoffs.push({ toolCall: output, handoff: resolved.handoff, }); } else { items.push(new items_1.RunToolCallItem(output, agent)); runFunctions.push({ toolCall: output, tool: resolved.tool, }); } } return { newItems: items, handoffs: runHandoffs, functions: runFunctions, computerActions: runComputerActions, shellActions: runShellActions, applyPatchActions: runApplyPatchActions, mcpApprovalRequests: runMCPApprovalRequests, toolsUsed: toolsUsed, hasToolsOrApprovalsToRun() { return (runHandoffs.length > 0 || runFunctions.length > 0 || runMCPApprovalRequests.length > 0 || runComputerActions.length > 0 || runShellActions.length > 0 || hasHostedShellCall || runApplyPatchActions.length > 0); }, }; } //# sourceMappingURL=modelOutputs.js.map