@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
217 lines • 10 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.prepareTurn = prepareTurn;
exports.getManagedConversationSupplementalItems = getManagedConversationSupplementalItems;
const errors_1 = require("../errors.js");
const items_1 = require("../items.js");
const logger_1 = __importDefault(require("../logger.js"));
const guardrails_1 = require("./guardrails.js");
const items_2 = require("./items.js");
const tracing_1 = require("./tracing.js");
const toolExecution_1 = require("./toolExecution.js");
const preparedInputIdentityByRunState = new WeakMap();
function getPreparedInputIdentityCache(state) {
const cached = preparedInputIdentityByRunState.get(state);
if (cached) {
return cached;
}
const created = { pendingInputItems: new WeakMap() };
preparedInputIdentityByRunState.set(state, created);
return created;
}
function getPreparedOriginalInput(state, input) {
if (typeof input !== 'string') {
return input;
}
const cache = getPreparedInputIdentityCache(state);
if (cache.normalizedStringInput?.source === input) {
return cache.normalizedStringInput.items;
}
const normalized = (0, items_2.prepareModelInputItems)(input, []);
cache.normalizedStringInput = { source: input, items: normalized };
return normalized;
}
function getPreparedPendingInputItem(state, item, occurrenceIndex) {
if (!item || typeof item !== 'object') {
return new items_1.RunInputItem(structuredClone(item), state._currentAgent);
}
const cache = getPreparedInputIdentityCache(state);
const cached = cache.pendingInputItems.get(item)?.[occurrenceIndex];
if (cached) {
cached.agent = state._currentAgent;
return cached;
}
const prepared = new items_1.RunInputItem(structuredClone(item), state._currentAgent);
const preparedOccurrences = cache.pendingInputItems.get(item) ?? [];
preparedOccurrences[occurrenceIndex] = prepared;
cache.pendingInputItems.set(item, preparedOccurrences);
if (prepared.rawItem && typeof prepared.rawItem === 'object') {
cache.pendingInputItems.set(prepared.rawItem, [prepared]);
}
return prepared;
}
async function prepareTurn(options) {
const { state, input, generatedItems, isResumedState, preserveTurnPersistenceOnResume, continuingInterruptedTurn, serverConversationTracker, inputGuardrailDefs, guardrailHandlers, emitAgentStart, onAgentSpanReady, agentSpanParent, } = options;
const { isResumingFromInterruption } = beginTurn(state, {
isResumedState,
preserveTurnPersistenceOnResume,
continuingInterruptedTurn,
});
if (state._maxTurns !== null && state._currentTurn > state._maxTurns) {
state._currentAgentSpan?.setError({
message: 'Max turns exceeded',
data: { max_turns: state._maxTurns },
});
throw new errors_1.MaxTurnsExceededError(`Max turns (${state._maxTurns}) exceeded`, state);
}
logger_1.default.debug(`Running agent ${state._currentAgent.name} (turn ${state._currentTurn})`);
state.setCurrentAgentSpan((0, tracing_1.ensureAgentSpan)({
agent: state._currentAgent,
handoffs: [],
tools: [],
currentSpan: state._currentAgentSpan,
parent: agentSpanParent,
}));
onAgentSpanReady?.(state._currentTurn, state._currentAgent.name);
const pendingInputSourceItems = [...state._pendingInput];
await runInputGuardrailsForTurn(state, inputGuardrailDefs, isResumingFromInterruption, guardrailHandlers);
const pendingInputOccurrences = new WeakMap();
const pendingInputItems = pendingInputSourceItems.map((item) => {
const occurrenceIndex = item && typeof item === 'object'
? (pendingInputOccurrences.get(item) ?? 0)
: 0;
if (item && typeof item === 'object') {
pendingInputOccurrences.set(item, occurrenceIndex + 1);
}
return getPreparedPendingInputItem(state, item, occurrenceIndex);
});
const generatedItemsForTurn = generatedItems.concat(pendingInputItems);
const preparedOriginalInput = getPreparedOriginalInput(state, input);
const turnInput = serverConversationTracker
? serverConversationTracker.prepareInput(preparedOriginalInput, generatedItemsForTurn, getManagedConversationSupplementalItems(state), pendingInputItems)
: (0, items_2.prepareModelInputItems)(preparedOriginalInput, generatedItemsForTurn, state._reasoningItemIdPolicy);
if (state._noActiveAgentRun) {
state._currentAgent.emit('agent_start', state._context, state._currentAgent, turnInput);
emitAgentStart?.(state._context, state._currentAgent, turnInput);
}
return {
turnInput,
pendingInputItems,
pendingInputSourceItems,
};
}
const IGNORED_HANDOFF_OUTPUT_MESSAGE = 'Multiple handoffs detected, ignoring this one.';
const managedConversationSupplementalItemsCache = new WeakMap();
function getManagedConversationSupplementalItems(state) {
const processedResponse = state._lastProcessedResponse;
if (!processedResponse?.handoffs.length) {
return [];
}
const { handoffs } = processedResponse;
const selected = processedResponse.newItems.find((item) => item instanceof items_1.RunHandoffCallItem);
if (selected && handoffs.length === 1) {
return [];
}
const completions = state._completedToolInvocationEvidence.get(selected?.agent ?? state._currentAgent);
const primaryId = handoffs[0].toolCall.callId;
const recoveredOutput = (callId) => {
const pair = completions?.get(callId)?.items;
if (pair?.[0] instanceof items_1.RunHandoffCallItem &&
pair[1].type === 'tool_call_output_item' &&
pair[1].rawItem.type === 'function_call_result' &&
state._generatedItems.includes(pair[1])) {
return pair[1].rawItem;
}
};
const primaryRecovery = recoveredOutput(primaryId);
// A filter may have removed the successful transfer from local history.
if (!primaryRecovery &&
(handoffs.length === 1 ||
!state._generatedItems.some((item) => item instanceof items_1.RunHandoffOutputItem &&
item.rawItem.callId === primaryId))) {
return [];
}
const cached = managedConversationSupplementalItemsCache.get(processedResponse);
if (cached)
return cached;
// The selected result travels with normal history. An exact replay has no new call
// item, so send a fresh copy of its previously committed result for this response.
const acknowledged = new Set();
if (selected || !primaryRecovery)
acknowledged.add(primaryId);
const items = [];
for (const { toolCall } of handoffs) {
if (acknowledged.has(toolCall.callId))
continue;
acknowledged.add(toolCall.callId);
const previous = recoveredOutput(toolCall.callId);
items.push(previous
? { ...previous }
: (0, toolExecution_1.getToolCallOutputItem)(toolCall, IGNORED_HANDOFF_OUTPUT_MESSAGE));
}
managedConversationSupplementalItemsCache.set(processedResponse, items);
return items;
}
async function runInputGuardrailsForTurn(state, runnerGuardrails, isResumingFromInterruption, handlers = {}) {
const guardrailInput = state._pendingInput.length > 0 ? state.pendingInput : undefined;
if (guardrailInput === undefined &&
(state._currentTurn !== 1 || isResumingFromInterruption)) {
return;
}
const guardrailDefs = (0, guardrails_1.buildInputGuardrailDefinitions)(state, runnerGuardrails);
const guardrails = (0, guardrails_1.splitInputGuardrails)(guardrailDefs);
if (guardrailInput === undefined) {
state._initialInputGuardrailsCompleted = false;
}
const completeInitialInputGuardrails = () => {
if (guardrailInput === undefined) {
state._initialInputGuardrailsCompleted = true;
}
};
if (guardrails.blocking.length > 0) {
await (0, guardrails_1.runInputGuardrails)(state, guardrails.blocking, {
input: guardrailInput,
});
}
if (guardrails.parallel.length > 0) {
const parallelGuardrailPromise = (0, guardrails_1.runInputGuardrails)(state, guardrails.parallel, {
input: guardrailInput,
onErrorObserved: handlers.onParallelError,
})
.then((results) => {
completeInitialInputGuardrails();
return results;
})
.catch(() => []);
handlers.onParallelPromise?.(parallelGuardrailPromise);
}
else {
completeInitialInputGuardrails();
}
}
function beginTurn(state, options) {
const isResumingFromInterruption = options.isResumedState && options.continuingInterruptedTurn;
const resumingTurnInProgress = options.isResumedState && state._currentTurnInProgress === true;
// Resolving approvals does not consume a turn, but the next model call does.
// Only resume an already-counted turn when its model work is still in progress.
if (!resumingTurnInProgress) {
state._currentTurn++;
// Resumed approval work may have persisted items after the run-entry snapshot.
// Keep those offsets even when the checkpoint initially had none.
if (!options.isResumedState ||
(!options.preserveTurnPersistenceOnResume &&
state._currentTurnPersistedItemCount === 0)) {
state.resetTurnPersistence();
}
else if (state._currentTurnPersistedItemCount > state._generatedItems.length) {
// Reset if a stale count would skip items in subsequent turns.
state.resetTurnPersistence();
}
}
state._currentTurnInProgress = true;
return { isResumingFromInterruption };
}
//# sourceMappingURL=turnPreparation.js.map