UNPKG

@openai/agents-core

Version:

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

259 lines • 13.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isAcceptedResponseCheckpoint = isAcceptedResponseCheckpoint; exports.assertAcceptedResponseContinuationAuthority = assertAcceptedResponseContinuationAuthority; exports.markAcceptedResponseProcessingStarted = markAcceptedResponseProcessingStarted; exports.markAcceptedResponseFinalizationStarted = markAcceptedResponseFinalizationStarted; exports.resumeAcceptedModelResponse = resumeAcceptedModelResponse; exports.applyTurnResult = applyTurnResult; exports.resumeInterruptedTurn = resumeInterruptedTurn; exports.handleInterruptedOutcome = handleInterruptedOutcome; const errors_1 = require("../errors.js"); const context_1 = require("../tracing/context.js"); const toolIdentity_1 = require("../toolIdentity.js"); const conversation_1 = require("./conversation.js"); const turnResolution_1 = require("./turnResolution.js"); function isAcceptedResponseCheckpoint(state) { const isCheckpoint = state._currentStep?.type === 'next_step_interruption' && state._currentStep.data?.responseAccepted === true; if (isCheckpoint) { assertAcceptedResponseServerOwnership(state); } return isCheckpoint; } function assertAcceptedResponseServerOwnership(state) { if ((0, conversation_1.getServerConversationOwner)(state._conversationId, state._previousResponseId)) { return; } throw new errors_1.UserError('Accepted model response state requires exactly one server-managed conversation owner', state); } function assertAcceptedResponseContinuationAuthority(state, requestedConversationId, requestedPreviousResponseId) { const currentStep = state._currentStep; const responseAccepted = (currentStep?.type === 'next_step_interruption' && currentStep.data?.responseAccepted === true) || (currentStep?.type === 'next_step_final_output' && currentStep.responseAccepted === true); if (!responseAccepted) { return; } assertAcceptedResponseServerOwnership(state); if ((requestedConversationId !== undefined && requestedConversationId !== state._conversationId) || (requestedPreviousResponseId !== undefined && requestedPreviousResponseId !== state._previousResponseId)) { throw new errors_1.UserError('Accepted model response state cannot change its server-managed conversation owner', state); } } function markAcceptedResponseProcessingStarted(state) { const currentStep = state._currentStep; if (currentStep?.type === 'next_step_interruption' && currentStep.data?.responseAccepted === true) { assertAcceptedResponseServerOwnership(state); currentStep.data.localProcessingStarted = true; } } function markAcceptedResponseFinalizationStarted(state) { const currentStep = state._currentStep; if (currentStep?.type !== 'next_step_final_output' || currentStep.responseAccepted !== true) { return; } assertAcceptedResponseServerOwnership(state); if (currentStep.localFinalizationStarted === true) { throw new errors_1.UserError('An accepted model response has unfinished local finalization and cannot be resumed safely; start a new run instead', state); } currentStep.localFinalizationStarted = true; } function hasUncommittedLocalEffects(processedResponse, suppressedToolCalls) { return [ ...processedResponse.functions.map((run) => run.toolCall), ...processedResponse.computerActions.map((run) => run.toolCall), ...processedResponse.shellActions.map((run) => run.toolCall), ...processedResponse.applyPatchActions.map((run) => run.toolCall), ...processedResponse.handoffs.map((run) => run.toolCall), ].some((toolCall) => !suppressedToolCalls.has(toolCall)); } /** * Continues local processing for a response that was already accepted by the model provider. */ async function resumeAcceptedModelResponse(options) { const { state } = options; if (!state._lastTurnResponse || !state._lastProcessedResponse) { throw new errors_1.UserError('An accepted model response could not be processed; start a new run instead of retrying it', state); } const suppressedToolCalls = (0, turnResolution_1.preflightToolInvocations)(state._currentAgent, state, state._lastProcessedResponse); if (state._currentStep?.type === 'next_step_interruption' && state._currentStep.data?.localProcessingStarted === true && hasUncommittedLocalEffects(state._lastProcessedResponse, suppressedToolCalls)) { throw new errors_1.UserError('An accepted model response has unfinished local tool work and cannot be resumed safely; start a new run instead', state); } markAcceptedResponseProcessingStarted(state); const turnResult = await (0, turnResolution_1.resolveTurnAfterModelResponse)(state._currentAgent, state._originalInput, state._generatedItems, state._lastTurnResponse, state._lastProcessedResponse, options.runner, state, options.toolErrorFormatter, options.agentToolParentRunConfig, undefined, options.signal, suppressedToolCalls, undefined, options.validateHandoffAgent); applyTurnResult({ state, turnResult, agent: state._currentAgent, toolsUsed: state._lastProcessedResponse.toolsUsed, resetTurnPersistence: false, onStepItems: options.onStepItems, }); return turnResult; } function applyTurnResult(options) { const { state, turnResult, agent, toolsUsed, resetTurnPersistence, onStepItems, } = options; const responseAccepted = isAcceptedResponseCheckpoint(state); onStepItems?.(turnResult); state._commitToolInvocations(turnResult.toolInvocationCommitItems); state._toolUseTracker.addToolUse(agent, toolsUsed); state._originalInput = turnResult.originalInput; state._replaceGeneratedItems(turnResult.generatedItems); if (resetTurnPersistence && turnResult.nextStep.type === 'next_step_run_again') { state.resetTurnPersistence(); } state._currentStep = responseAccepted && turnResult.nextStep.type === 'next_step_final_output' ? { ...turnResult.nextStep, responseAccepted: true } : turnResult.nextStep; state._finalOutputSource = turnResult.nextStep.type === 'next_step_final_output' ? (turnResult.finalOutputSource ?? 'turn_resolution') : undefined; } async function resumeInterruptedTurn(options) { const { state, runner, toolErrorFormatter, agentToolParentRunConfig, signal, onStepItems, validateHandoffAgent, beforeResumedSideEffects, onHandoff, } = options; // Validate recipient continuity before resumed-session callbacks can run. (0, turnResolution_1.preflightToolInvocations)(state._currentAgent, state, state._lastProcessedResponse); const approvedToolWillResume = state.getInterruptions().some((item) => { const rawItem = item.rawItem; if (rawItem.type === 'hosted_tool_call') { return false; } const toolName = rawItem.type === 'function_call' ? (item.functionToolStateKey ?? (0, toolIdentity_1.getFunctionToolStateKeyForCall)(rawItem, item.name)) : item.name; const callId = 'callId' in rawItem && typeof rawItem.callId === 'string' ? rawItem.callId : 'id' in rawItem && typeof rawItem.id === 'string' ? rawItem.id : undefined; return (toolName !== undefined && callId !== undefined && state._context._resolveToolInvocationApproval(item.agent, toolName, rawItem) === true); }); const pendingHandoffs = state._lastProcessedResponse?.handoffs ?? []; const preparation = approvedToolWillResume || pendingHandoffs.length > 0 ? await beforeResumedSideEffects?.() : undefined; let unfilteredHandoffInput; const turnResult = await (0, turnResolution_1.resolveInterruptedTurn)(state._currentAgent, state._originalInput, state._generatedItems, state._lastTurnResponse, state._lastProcessedResponse, runner, state, toolErrorFormatter, agentToolParentRunConfig, signal, validateHandoffAgent, preparation ? (input) => { unfilteredHandoffInput = state._captureHandoffInput(input.inputHistory, [...input.preHandoffItems, ...input.newItems]); } : undefined); const resumedSideEffects = turnResult.nextStep.type === 'next_step_handoff' || // A handled invocation can have external effects even when every sibling approval was rejected. turnResult.newStepItems.some((item) => { if (item.type !== 'tool_call_output_item' || item.executionStatus !== 'executed') { return false; } const { rawItem } = item; return (rawItem.type === 'function_call_result' && pendingHandoffs.some(({ toolCall }) => toolCall.callId === rawItem.callId)); }) || (approvedToolWillResume && turnResult.generatedItems.length > state._currentTurnPersistedItemCount); if (preparation && unfilteredHandoffInput) { preparation.handoffInput = state._captureHandoffInput(turnResult.originalInput, turnResult.generatedItems); const canonicalInput = state._deserializeHandoffInput(unfilteredHandoffInput); // Streaming observes the accepted filtered step, while persistence keeps the source pairs. onStepItems?.(turnResult); turnResult.originalInput = canonicalInput.originalInput; turnResult.preStepItems = []; turnResult.newStepItems = canonicalInput.generatedItems; turnResult.toolInvocationCommitItems = canonicalInput.generatedItems; } applyTurnResult({ state, turnResult, agent: state._currentAgent, toolsUsed: state._lastProcessedResponse?.toolsUsed ?? [], resetTurnPersistence: false, onStepItems: unfilteredHandoffInput ? undefined : onStepItems, }); if (turnResult.nextStep.type === 'next_step_run_again') { // Approval resolution completes the previous model turn. Persist that fact // before session writes so a retry also counts the next model request. state._currentTurnInProgress = false; } if (turnResult.nextStep.type === 'next_step_handoff') { // The transfer and tool effects have completed. Persist their continuation before // the caller attempts the fallible resumed Session append. state.setCurrentAgent(turnResult.nextStep.newAgent); state._currentStep = { type: 'next_step_run_again' }; state._noActiveAgentRun = true; state._currentTurnInProgress = false; if (state._currentAgentSpan) { state._currentAgentSpan.end(); (0, context_1.resetCurrentSpan)(); state.setCurrentAgentSpan(undefined); } if (!(0, conversation_1.getServerConversationOwner)(state._conversationId, state._previousResponseId)) { // Local history owns the completed source-agent results. Retaining its execution // plan would try to rehydrate source tools against the target agent on restore. // Server-managed continuation still needs the plan for ignored handoff outputs. state._lastProcessedResponse = undefined; } onHandoff?.(state._currentAgent); } // Map next-step outcomes to interruption flow control for the outer run loop. // return_interruption: still waiting on approvals. rerun_turn: prepare the next model turn. // advance_step: proceed without rerunning the same turn. if (turnResult.nextStep.type === 'next_step_interruption') { return { nextStep: turnResult.nextStep, action: 'return_interruption', resumedSideEffects, }; } if (turnResult.nextStep.type === 'next_step_run_again') { return { nextStep: turnResult.nextStep, action: 'rerun_turn', resumedSideEffects, }; } return { nextStep: turnResult.nextStep, action: 'advance_step', resumedSideEffects, }; } function handleInterruptedOutcome(options) { const { state, outcome, setContinuingInterruptedTurn } = options; switch (outcome.action) { case 'return_interruption': state._currentStep = outcome.nextStep; return { shouldReturn: true, shouldContinue: false }; case 'rerun_turn': // Preserve interruption context for preparation of the next model turn. setContinuingInterruptedTurn(true); state._currentStep = undefined; return { shouldReturn: false, shouldContinue: true }; case 'advance_step': setContinuingInterruptedTurn(false); if (outcome.nextStep.type === 'next_step_handoff') { // The transfer, lifecycle notification and durable continuation are already published. return { shouldReturn: false, shouldContinue: true }; } state._currentStep = outcome.nextStep; return { shouldReturn: false, shouldContinue: false }; default: { const _exhaustive = outcome.action; throw new Error(`Unhandled interruption outcome: ${_exhaustive}`); } } } //# sourceMappingURL=runLoop.js.map