UNPKG

@openai/agents-core

Version:

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

1,622 lines • 77.5 kB
import { UserError } from "../errors.mjs";
import { isOpenAIResponsesCompactionAwareSession, isOpenAIResponsesCompactionOwnershipAwareSession, isRunContextAwareSession, isSessionHistoryTransactionAwareSession, } from "../memory/session.mjs";
import { assertPendingSessionWriteOwnership, capturePendingSessionWriteTerminalFinalization, clearPendingSessionWriteTerminalProducer, getPendingSessionWriteAppendItems, } from "../runState.mjs";
import { ModelItem } from "../types/protocol.mjs";
import { Usage } from "../usage.mjs";
import { assertValidCompactionItems, buildAgentInputPool, deduplicateAgentInputItemsPreferringLatest, dropOrphanToolCalls, extractOutputItemsFromRunItems, toAgentInputList, getAgentInputItemKey, removeAgentInputFromPool, stripReasoningItemIdForPolicy, trimToLatestCompaction, } from "./items.mjs";
import logger, { logModelAndToolActionWarning } from "../logger.mjs";
import { consumeModelFailureUsage, getRunStateUsageRecorder, } from "./usageTracking.mjs";
import { buildRunItemPersistencePlan as buildCanonicalRunItemPersistencePlan, getBlockedOutputSessionSnapshotRunItems, hasBlockedOutputExecutionEffect, hasPendingApprovedToolInputCompaction, hasTerminalToolOutputSource, selectRunItemIndexesForBlockedOutput, } from "./blockedOutputPersistence.mjs";
import { agentItemRangeMatches, normalizeItemsForSessionPersistence, sessionItemArraysMatch, } from "./sessionItems.mjs";
export { selectRunItemIndexesForBlockedOutput } from "./blockedOutputPersistence.mjs";
const SESSION_LIMIT_UNSET = Symbol('sessionLimitUnset');
// Ownership survives same-live resume, but never RunState serialization or a different session.
const sessionCompactionStates = new WeakMap();
export function getSessionCompactionState(session, state) {
    if (!isOpenAIResponsesCompactionOwnershipAwareSession(session)) {
        return undefined;
    }
    const existing = state && sessionCompactionStates.get(state);
    if (existing?.session === session) {
        return existing;
    }
    const binding = { session, ownership: null };
    if (state) {
        sessionCompactionStates.set(state, binding);
    }
    return binding;
}
export function bindSessionCompactionState(state, binding) {
    if (binding) {
        sessionCompactionStates.set(state, binding);
    }
}
/** Capture final input now, but authorize compaction only after the response succeeds. */
export function prepareSessionCompactionExchange(session, state, input) {
    const binding = getSessionCompactionState(session, state);
    if (!binding) {
        return () => { };
    }
    binding.modelExchange = undefined;
    const snapshot = structuredClone(input);
    const reasoningItemIdPolicy = state._reasoningItemIdPolicy;
    return (output, responseId) => {
        binding.modelExchange = {
            items: [...snapshot, ...structuredClone(output)],
            responseId,
            reasoningItemIdPolicy,
        };
    };
}
async function getSessionItems(session, runContext, limit = SESSION_LIMIT_UNSET) {
    if (runContext && isRunContextAwareSession(session)) {
        return session.getItems(limit === SESSION_LIMIT_UNSET ? undefined : limit, runContext);
    }
    return limit === SESSION_LIMIT_UNSET
        ? session.getItems()
        : session.getItems(limit);
}
async function addSessionItems(session, items, runContext, compactionState) {
    if (isOpenAIResponsesCompactionOwnershipAwareSession(session)) {
        await session.addItemsWithCompactionOwnership(items, compactionState?.session === session ? compactionState.ownership : null, runContext);
        return;
    }
    if (runContext && isRunContextAwareSession(session)) {
        await session.addItems(items, runContext);
        return;
    }
    await session.addItems(items);
}
async function clearSession(session, runContext) {
    if (runContext && isRunContextAwareSession(session)) {
        await session.clearSession(runContext);
        return;
    }
    await session.clearSession();
}
function canonicalizeSessionHistoryTransaction(transaction) {
    let decoded;
    try {
        const encoded = JSON.stringify(transaction);
        if (encoded === undefined) {
            throw new Error('Missing JSON transaction.');
        }
        decoded = JSON.parse(encoded);
    }
    catch {
        throw new UserError('Session history transaction cannot be represented in durable RunState JSON.');
    }
    if (!decoded || typeof decoded !== 'object') {
        throw new UserError('Session history transaction is invalid.');
    }
    const record = decoded;
    const parseItems = (value) => {
        if (!Array.isArray(value)) {
            throw new UserError('Session history transaction items are invalid.');
        }
        return value.map((item) => ModelItem.parse(item));
    };
    if (record.type === 'append_items') {
        return {
            type: record.type,
            items: parseItems(record.items),
        };
    }
    if (record.type === 'replace_suffix') {
        return {
            type: record.type,
            expectedSuffix: parseItems(record.expectedSuffix),
            replacement: parseItems(record.replacement),
        };
    }
    throw new UserError('Session history transaction is invalid.');
}
function getRunItemIndexesForPendingTransaction(state, runItems) {
    const indexes = runItems.map((item) => state._generatedItems.indexOf(item));
    if (indexes.some((index) => index < 0) ||
        new Set(indexes).size !== indexes.length) {
        throw new UserError('Session history transaction run items do not belong to the current RunState.');
    }
    return indexes;
}
function getSessionHistoryTransactionOperationId(state, transactionKind, alreadyPersistedCount, persistedItemCount) {
    return [
        state._sessionHistoryTransactionId,
        state._currentTurn,
        transactionKind,
        alreadyPersistedCount,
        persistedItemCount,
    ].join(':');
}
function buildPendingSessionHistoryTransaction(state) {
    const pending = state._pendingSessionHistoryTransaction;
    if (!pending) {
        throw new UserError('Session history transaction state is missing.');
    }
    const reasoningItemIdPolicy = state._currentTurnSessionReasoningItemIdPolicy;
    if (reasoningItemIdPolicy === undefined) {
        throw new UserError('Session history transaction reasoning-item policy is missing.');
    }
    const runItems = pending.runItemIndexes.map((index) => state._generatedItems[index]);
    const replacementItems = pending.replaceRunItemIndexes.map((index) => state._generatedItems[index]);
    const items = normalizeItemsForSessionPersistence([
        ...(state._currentTurnSessionHistoryTransactionInputItems ?? []),
        ...extractOutputItemsFromRunItems(runItems, reasoningItemIdPolicy),
    ]);
    if (pending.transactionKind === 'blocked_append') {
        if (items.length === 0) {
            throw new UserError('Pending blocked session history transaction cannot be empty.');
        }
        return canonicalizeSessionHistoryTransaction({
            type: 'append_items',
            items,
        });
    }
    const expectedSuffix = normalizeItemsForSessionPersistence(extractOutputItemsFromRunItems(replacementItems, reasoningItemIdPolicy));
    if (expectedSuffix.length === 0 || items.length === 0) {
        throw new UserError('Pending accepted session history transaction requires a non-empty suffix and replacement.');
    }
    return canonicalizeSessionHistoryTransaction({
        type: 'replace_suffix',
        expectedSuffix,
        replacement: items,
    });
}
function getEffectiveSessionReasoningItemIdPolicy(session, state) {
    return session.preserveReasoningItemIdsForPersistence?.() === true
        ? 'preserve'
        : (state._reasoningItemIdPolicy ?? 'preserve');
}
function getComparableSessionItems(session, items) {
    const detachedItems = structuredClone(items);
    return structuredClone(session.prepareHistoryItemsForPersistenceComparison?.(detachedItems) ??
        detachedItems);
}
export async function prepareResumedSessionWrite(session, state) {
    if (state._pendingSessionWrite !== undefined) {
        throw new UserError('A pending Session write must be reconciled before another append is prepared.');
    }
    if (!state._resumedSessionWriteInProgress) {
        throw new UserError('A resumed Session append requires exclusive ownership of its RunState.');
    }
    if (state._currentStep?.type !== 'next_step_interruption') {
        throw new UserError('Only interrupted tool work can prepare a resumed Session append.');
    }
    const sessionId = await session.getSessionId();
    if (sessionId.length === 0) {
        throw new UserError('Cannot prepare a Session append without a session ID.');
    }
    return {
        session,
        state,
        sessionId,
        reasoningItemIdPolicy: getEffectiveSessionReasoningItemIdPolicy(session, state),
    };
}
export function acquireResumedSessionWriteOperation(state) {
    if (state._resumedSessionWriteInProgress) {
        throw new UserError('The same RunState cannot resume or reconcile a Session write concurrently.');
    }
    state._resumedSessionWriteInProgress = true;
    let released = false;
    return () => {
        if (!released) {
            released = true;
            state._resumedSessionWriteInProgress = false;
        }
    };
}
function checkpointPreparedResumedSessionWrite(options) {
    const { session, state, preparation, items, alreadyPersistedCount, persistedItemCount, reasoningItemIdPolicy, } = options;
    if (!state._resumedSessionWriteInProgress) {
        throw new UserError('A resumed Session append requires exclusive ownership of its RunState.');
    }
    if (preparation.session !== session || preparation.state !== state) {
        throw new UserError('Resumed Session write preparation belongs to a different runtime operation.');
    }
    if (state._pendingSessionWrite !== undefined ||
        state._generatedItems.length !== persistedItemCount ||
        persistedItemCount <= alreadyPersistedCount ||
        items.length === 0 ||
        preparation.reasoningItemIdPolicy !== reasoningItemIdPolicy) {
        throw new UserError('Resumed Session write preparation is stale.');
    }
    if (state._currentStep?.type !== 'next_step_run_again' &&
        state._currentStep?.type !== 'next_step_interruption' &&
        !hasTerminalToolOutputSource(state)) {
        throw new UserError('Only resumed tool work awaiting another model turn, approval, or terminal tool finalization can checkpoint a Session append.');
    }
    const pendingItems = {
        sessionId: preparation.sessionId,
        alreadyPersistedCount,
        persistedItemCount,
        reasoningItemIdPolicy,
    };
    const reconstructedItems = getPendingSessionWriteAppendItems(state, pendingItems);
    if (reconstructedItems.length !== items.length ||
        !agentItemRangeMatches(reconstructedItems, items, 0)) {
        throw new UserError('Resumed Session write items do not match their canonical RunState owners.');
    }
    const pending = {
        phase: 'prepared',
        ...pendingItems,
        handoffInput: preparation.handoffInput,
        terminalToolFinalization: capturePendingSessionWriteTerminalFinalization(state),
    };
    state._pendingSessionWrite = structuredClone(pending);
    assertPendingSessionWriteOwnership(state, pending);
    return pending;
}
function promotePreparedSessionWrite(session, state, storedItems) {
    const pending = state._pendingSessionWrite;
    if (pending?.phase !== 'prepared') {
        throw new UserError('Prepared Session write state is missing.');
    }
    assertPendingSessionWriteOwnership(state, pending);
    const appendItems = getPendingSessionWriteAppendItems(state, pending);
    const beforeItems = getComparableSessionItems(session, storedItems);
    const comparableAppendItems = getComparableSessionItems(session, appendItems);
    const combinedItems = getComparableSessionItems(session, [
        ...storedItems,
        ...appendItems,
    ]);
    const expectedCombinedItems = [...beforeItems, ...comparableAppendItems];
    if (beforeItems.length !== storedItems.length ||
        comparableAppendItems.length !== appendItems.length ||
        combinedItems.length !== storedItems.length + appendItems.length) {
        throw new UserError('Session persistence comparison must preserve stored item boundaries.');
    }
    if (!sessionItemArraysMatch(combinedItems, expectedCombinedItems)) {
        throw new UserError('Session history changed before a prepared resumed write could be completed safely.');
    }
    const appendReady = {
        ...pending,
        phase: 'append_ready',
        beforeItems,
        comparableAppendItems,
    };
    state._pendingSessionWrite = structuredClone(appendReady);
    assertPendingSessionWriteOwnership(state, appendReady);
    return appendReady;
}
async function appendPreparedResumedSessionWrite(options) {
    const { session, state, preparation } = options;
    const sessionId = await session.getSessionId();
    if (sessionId !== preparation.sessionId) {
        throw new UserError('A prepared Session write belongs to a different session and cannot be resumed safely.');
    }
    const storedItems = await getSessionItems(session, state._context);
    const pending = promotePreparedSessionWrite(session, state, storedItems);
    await addSessionItems(session, getPendingSessionWriteAppendItems(state, pending), state._context, getSessionCompactionState(session, state));
}
export async function reconcilePendingSessionWriteBeforeRun(session, state, options) {
    const pending = state._pendingSessionWrite;
    if (pending === undefined) {
        return false;
    }
    if (options.operationAlreadyOwned === true &&
        !state._resumedSessionWriteInProgress) {
        throw new UserError('Pending Session write reconciliation lost exclusive RunState ownership.');
    }
    const releaseOperation = options.operationAlreadyOwned === true
        ? undefined
        : acquireResumedSessionWriteOperation(state);
    try {
        assertPendingSessionWriteOwnership(state, pending);
        if (options.serverManagesConversation || session === undefined) {
            throw new UserError('A pending Session write requires the same ordinary local Session to resume safely.');
        }
        if (pending.phase === 'compaction_pending') {
            await completePendingApprovedToolInputCompaction(session, state);
            return true;
        }
        if (getEffectiveSessionReasoningItemIdPolicy(session, state) !==
            pending.reasoningItemIdPolicy) {
            throw new UserError('Session persistence policy changed while a resumed write was pending.');
        }
        if (state._currentTurnSessionHistoryTransactionSessionId !== undefined &&
            !isSessionHistoryTransactionAwareSession(session)) {
            throw new UserError('A pending transaction-aware Session write requires the same transaction-aware local Session to resume safely.');
        }
        const appendItems = getPendingSessionWriteAppendItems(state, pending);
        if (pending.phase === 'append_ready') {
            const comparableAppendItems = getComparableSessionItems(session, appendItems);
            if (pending.comparableAppendItems.length !== appendItems.length ||
                comparableAppendItems.length !== appendItems.length ||
                !sessionItemArraysMatch(comparableAppendItems, pending.comparableAppendItems)) {
                throw new UserError('RunState pending Session write comparison evidence does not match the runtime Session.');
            }
        }
        const sessionId = await session.getSessionId();
        if (sessionId !== pending.sessionId) {
            throw new UserError('A pending Session write belongs to a different session and cannot be resumed safely.');
        }
        const storedItems = await getSessionItems(session, state._context);
        const currentItems = getComparableSessionItems(session, storedItems);
        if (currentItems.length !== storedItems.length) {
            throw new UserError('Session persistence comparison must preserve stored item boundaries.');
        }
        const appendReady = pending.phase === 'prepared'
            ? promotePreparedSessionWrite(session, state, storedItems)
            : pending;
        const expectedAfterItems = [
            ...appendReady.beforeItems,
            ...appendReady.comparableAppendItems,
        ];
        if (sessionItemArraysMatch(currentItems, appendReady.beforeItems)) {
            await addSessionItems(session, appendItems, state._context, getSessionCompactionState(session, state));
        }
        else if (!sessionItemArraysMatch(currentItems, expectedAfterItems)) {
            throw new UserError('Session history changed while a resumed write was unacknowledged and cannot be reconciled safely.');
        }
        commitSessionPersistenceState({
            state,
            persistedItemCount: pending.persistedItemCount,
            deferredRunItemIndexes: [],
        });
        markPendingApprovedToolInputCompaction(state);
        await completePendingApprovedToolInputCompaction(session, state);
        return true;
    }
    finally {
        releaseOperation?.();
    }
}
export function selectRunItemsForBlockedOutput(items, unpersistedStartIndex = 0) {
    return selectRunItemIndexesForBlockedOutput(items, unpersistedStartIndex).map((index) => items[index]);
}
export function canPersistBlockedOutputToSession(session, state) {
    return (isSessionHistoryTransactionAwareSession(session) &&
        selectRunItemIndexesForBlockedOutput(state._generatedItems, state._currentTurnPersistedItemCount).length > 0);
}
export async function prepareSessionHistoryTransactionsForRun(session, state, options) {
    const boundSessionId = state._currentTurnSessionHistoryTransactionSessionId;
    const hasTransactionAuthority = boundSessionId !== undefined ||
        state._pendingSessionHistoryTransaction !== undefined ||
        state._currentTurnDeferredSessionItemIndexes.size > 0;
    if (hasTransactionAuthority &&
        state._pendingLegacyCompactionSessionItems !== undefined) {
        throw new UserError('RunState cannot combine legacy compaction reconciliation with output guardrail transaction authority.');
    }
    if (!hasTransactionAuthority) {
        await reconcileLegacyCompactionSessionBeforeResume(session, state);
    }
    if (options.serverManagesConversation ||
        !isSessionHistoryTransactionAwareSession(session)) {
        if (hasTransactionAuthority) {
            throw new UserError('Output guardrail session persistence must resume with the same transaction-aware local session.');
        }
        return;
    }
    const sessionId = await session.getSessionId();
    if (boundSessionId !== undefined && boundSessionId !== sessionId) {
        throw new UserError('Output guardrail session persistence belongs to a different session and cannot be resumed safely.');
    }
    state._currentTurnSessionHistoryTransactionSessionId = sessionId;
    state._currentTurnSessionReasoningItemIdPolicy ??=
        getEffectiveSessionReasoningItemIdPolicy(session, state);
    state._currentTurnSessionHistoryTransactionInputItems ??= [];
    await flushPendingSessionHistoryTransaction(session, state);
    await assertBlockedSessionSuffixMatches(session, state);
}
export function captureSessionHistoryTransactionInputItems(session, state, items) {
    if (session === undefined) {
        state._currentTurnSessionHistoryTransactionInputItems =
            normalizeItemsForSessionPersistence(items ?? []);
        return;
    }
    if (!isSessionHistoryTransactionAwareSession(session)) {
        return;
    }
    if (state._currentTurnSessionHistoryTransactionSessionId === undefined) {
        return;
    }
    state._currentTurnSessionHistoryTransactionInputItems =
        normalizeItemsForSessionPersistence(items ?? []);
}
export function markSessionHistoryTransactionInputPersisted(state) {
    if (state._currentTurnSessionHistoryTransactionInputItems !== undefined) {
        state._currentTurnSessionHistoryTransactionInputItems = [];
    }
}
export function releaseUnusedSessionHistoryTransactionBinding(state) {
    if (state._pendingSessionHistoryTransaction !== undefined ||
        state._currentTurnBlockedSessionStartIndex !== undefined ||
        state._currentTurnDeferredSessionItemIndexes.size > 0 ||
        hasBlockedOutputExecutionEffect(state._generatedItems, state._currentTurnPersistedItemCount)) {
        return;
    }
    clearSessionHistoryTransactionBinding(state);
}
export function releaseProvisionalSessionHistoryTransactionBinding(state, provisionalSessionId, portableInputItems) {
    if (provisionalSessionId === undefined ||
        state._currentTurnSessionHistoryTransactionSessionId !==
            provisionalSessionId ||
        state._pendingSessionHistoryTransaction !== undefined ||
        state._currentTurnBlockedSessionStartIndex !== undefined ||
        state._currentTurnDeferredSessionItemIndexes.size > 0) {
        return;
    }
    clearSessionHistoryTransactionBinding(state);
    state._currentTurnSessionHistoryTransactionInputItems = portableInputItems;
}
function clearSessionHistoryTransactionBinding(state) {
    state._currentTurnSessionHistoryTransactionSessionId = undefined;
    state._currentTurnSessionReasoningItemIdPolicy = undefined;
    state._currentTurnSessionHistoryTransactionInputItems = undefined;
    state._currentTurnSessionHistoryTransactionCanReplaceAcceptedOutput =
        undefined;
}
function commitSessionPersistenceState(options) {
    const { state, persistedItemCount, deferredRunItemIndexes } = options;
    state._currentTurnPersistedItemCount = persistedItemCount;
    if (state._currentTurnSessionWriteCompactedItemCount !== persistedItemCount) {
        state._currentTurnSessionWriteCompactedItemCount = undefined;
    }
    state._currentTurnDeferredSessionItemIndexes = new Set(deferredRunItemIndexes);
    if (deferredRunItemIndexes.length === 0) {
        state._currentTurnBlockedSessionStartIndex = undefined;
        clearSessionHistoryTransactionBinding(state);
    }
}
function markPendingApprovedToolInputCompaction(state) {
    const pending = state._pendingSessionWrite;
    if (pending?.phase !== 'append_ready') {
        throw new UserError('A settled resumed Session append is missing its compaction authority.');
    }
    const { beforeItems: _beforeItems, comparableAppendItems: _comparableAppendItems, ...ownedCompaction } = pending;
    state._currentTurnSessionWriteCompactedItemCount = undefined;
    state._pendingSessionWrite = {
        ...ownedCompaction,
        phase: 'compaction_pending',
    };
    assertPendingSessionWriteOwnership(state, state._pendingSessionWrite);
}
function commitApprovedToolInputCompaction(state, persistedItemCount) {
    const handoffInput = state._pendingSessionWrite?.handoffInput;
    if (handoffInput) {
        const input = state._deserializeHandoffInput(handoffInput);
        state._originalInput = input.originalInput;
        state._replaceGeneratedItems(input.generatedItems);
        state._currentTurnPersistedItemCount = input.generatedItems.length;
    }
    state._currentTurnSessionWriteCompactedItemCount = handoffInput
        ? state._generatedItems.length
        : persistedItemCount;
    state._pendingSessionWrite = undefined;
    clearPendingSessionWriteTerminalProducer(state);
}
async function completePendingApprovedToolInputCompaction(session, state) {
    const pending = state._pendingSessionWrite;
    if (pending?.phase !== 'compaction_pending') {
        throw new UserError('Pending Session compaction authority is missing.');
    }
    assertPendingSessionWriteOwnership(state, pending);
    if (getEffectiveSessionReasoningItemIdPolicy(session, state) !==
        pending.reasoningItemIdPolicy) {
        throw new UserError('Session persistence policy changed while compaction was pending.');
    }
    if ((await session.getSessionId()) !== pending.sessionId) {
        throw new UserError('A pending Session compaction belongs to a different session and cannot be resumed safely.');
    }
    await runCompactionOnSession(session, undefined, state, 'input');
    commitApprovedToolInputCompaction(state, pending.persistedItemCount);
}
async function assertBlockedSessionSuffixMatches(session, state) {
    const blockedStartIndex = state._currentTurnBlockedSessionStartIndex;
    if (blockedStartIndex === undefined) {
        return;
    }
    const canReplaceAcceptedOutput = state._currentTurnSessionHistoryTransactionCanReplaceAcceptedOutput;
    if (canReplaceAcceptedOutput !== true) {
        throw new UserError('Accepted output cannot replace a sparse session suffix created from a serialized final step. Start a new run from the persisted session history.');
    }
    const reasoningItemIdPolicy = state._currentTurnSessionReasoningItemIdPolicy;
    if (reasoningItemIdPolicy === undefined) {
        throw new UserError('Blocked session history replacement authority is incomplete.');
    }
    const hasDeferredItems = state._currentTurnDeferredSessionItemIndexes.size > 0;
    const plan = hasDeferredItems
        ? buildRunItemPersistencePlan(state, state._generatedItems, false, true)
        : undefined;
    if (hasDeferredItems && plan?.transactionKind !== 'accepted_replace') {
        throw new UserError('Blocked session history replacement authority is incomplete.');
    }
    const suffixRunItems = hasDeferredItems
        ? plan.runItemsToReplace
        : state._generatedItems.slice(blockedStartIndex, state._currentTurnPersistedItemCount);
    const expectedSuffix = normalizeItemsForSessionPersistence(extractOutputItemsFromRunItems(suffixRunItems, reasoningItemIdPolicy));
    const currentItems = await getSessionItems(session, state._context);
    const comparableCurrentItems = session.prepareHistoryItemsForPersistenceComparison?.(currentItems) ??
        currentItems;
    const comparableExpectedSuffix = session.prepareHistoryItemsForPersistenceComparison?.(expectedSuffix) ??
        expectedSuffix;
    if (comparableExpectedSuffix.length === 0 ||
        !agentItemRangeMatches(comparableCurrentItems, comparableExpectedSuffix, comparableCurrentItems.length - comparableExpectedSuffix.length)) {
        throw new UserError('Session history suffix no longer matches the transaction precondition.');
    }
}
function buildRunItemPersistencePlan(state, items, outputBlocked, canUseHistoryTransactions) {
    const blockedSnapshot = outputBlocked
        ? getBlockedOutputSessionSnapshotRunItems(state)
        : [];
    const blockedSnapshotStart = state._generatedItems.length - blockedSnapshot.length;
    return buildCanonicalRunItemPersistencePlan({
        items,
        alreadyPersistedCount: state._currentTurnPersistedItemCount ?? 0,
        currentDeferredIndexes: state._currentTurnDeferredSessionItemIndexes,
        outputBlocked,
        canUseHistoryTransactions,
        blockedSnapshot: blockedSnapshot.length > 0 ||
            (outputBlocked &&
                canUseHistoryTransactions &&
                items.length === (state._currentTurnPersistedItemCount ?? 0))
            ? {
                items: canUseHistoryTransactions
                    ? state._generatedItems.slice(blockedSnapshotStart)
                    : blockedSnapshot,
                startIndex: blockedSnapshotStart,
                alreadyPersistedCount: state._currentTurnBlockedSessionStartIndex ??
                    state._currentTurnPersistedItemCount,
            }
            : undefined,
    });
}
async function persistSessionRunItemPlan(options) {
    const { session, state, persistencePlan, sessionInputItems, lastResponseId, persistenceOptions, } = options;
    if (persistenceOptions.outputBlocked === true &&
        persistencePlan.useHistoryTransaction &&
        persistencePlan.runItemsToPersist.length === 0 &&
        (sessionInputItems?.length ?? 0) === 0) {
        await prepareSessionHistoryTransactionsForRun(session, state, {
            serverManagesConversation: false,
        });
        return;
    }
    await persistRunItemsToSession({
        session,
        state,
        newRunItems: persistencePlan.runItemsToPersist,
        runItemsToReplace: persistencePlan.runItemsToReplace,
        processedRunItemCount: persistencePlan.processedRunItemCount,
        deferredRunItemIndexes: persistencePlan.deferredRunItemIndexes,
        useHistoryTransaction: persistencePlan.useHistoryTransaction,
        transactionKind: persistencePlan.transactionKind,
        extraInputItems: sessionInputItems,
        lastResponseId: persistenceOptions.outputBlocked
            ? undefined
            : lastResponseId,
        alreadyPersistedCount: persistencePlan.alreadyPersistedCount,
        runCompaction: persistenceOptions.outputBlocked === true
            ? false
            : (persistenceOptions.runCompaction ?? true),
        compactionMode: persistenceOptions.compactionMode,
        resumedSessionWritePreparation: persistenceOptions.resumedSessionWritePreparation,
        deferResumedSessionWrite: persistenceOptions.deferResumedSessionWrite === true,
    });
}
class SessionReconciliationRecoveryError extends Error {
    primaryError;
    rollbackError;
    errors;
    cause;
    constructor(primaryError, rollbackError) {
        super('Failed to reconcile legacy compaction session history and restore the previous session contents.');
        this.primaryError = primaryError;
        this.rollbackError = rollbackError;
        this.name = 'SessionReconciliationRecoveryError';
        this.errors = [primaryError, rollbackError];
        this.cause = primaryError;
    }
}
export function createSessionPersistenceTracker(options) {
    class SessionPersistenceTrackerImpl {
        session;
        runContext;
        hasCallModelInputFilter;
        persistInput;
        originalSnapshot;
        filteredSnapshot;
        preparedSources;
        initialPreparedItems;
        initialSourcePositions;
        ownedFilteredItems = new Map();
        persistedInputOrder = [];
        persistedInput = false;
        constructor() {
            this.session = options.session;
            this.runContext = options.runContext;
            this.hasCallModelInputFilter = options.hasCallModelInputFilter;
            this.persistInput = options.persistInput;
            this.originalSnapshot = options.resumingFromState
                ? cloneItems(options.resumedSessionInputItems ?? [])
                : undefined;
            this.filteredSnapshot = options.resumedSessionInputItems
                ? cloneItems(options.resumedSessionInputItems)
                : undefined;
            this.preparedSources = options.resumingFromState ? [] : undefined;
            this.initialPreparedItems = options.resumingFromState ? [] : undefined;
            this.initialSourcePositions = options.resumingFromState ? [] : undefined;
        }
        setPreparedItems = (items, preparedInput) => {
            const sessionItems = items ??
                (this.session === undefined && preparedInput !== undefined
                    ? toAgentInputList(preparedInput)
                    : []);
            this.originalSnapshot = cloneItems(deduplicateAgentInputItemsPreferringLatest(sessionItems));
            if (Array.isArray(preparedInput)) {
                this.preparedSources = undefined;
                this.initialPreparedItems = preparedInput;
                this.initialSourcePositions = findOwnedItemIndexes(preparedInput, sessionItems);
            }
            else if (typeof preparedInput === 'string') {
                this.preparedSources = undefined;
                this.initialPreparedItems = sessionItems;
                this.initialSourcePositions = findOwnedItemIndexes(sessionItems, sessionItems);
            }
            else {
                this.preparedSources = sessionItems.map((item, ownerIndex) => ({
                    item,
                    ownerIndex,
                }));
                this.initialPreparedItems = undefined;
                this.initialSourcePositions = undefined;
            }
            this.ownedFilteredItems.clear();
            this.persistedInputOrder = [];
        };
        setPreparedTurnItems = (preparedItems, processedItems) => {
            if (!this.initialPreparedItems || !this.initialSourcePositions) {
                return;
            }
            const preparedSources = mapPreparedSourcesAfterContextProcessing(this.initialPreparedItems, preparedItems, this.initialSourcePositions, { validateReplacements: false });
            this.preparedSources = mapPreparedSourcesAfterContextProcessing(preparedItems, processedItems, findPreparedSourcePositions(preparedItems, preparedSources));
        };
        recordTurnItems = (sourceItems, filteredItems) => {
            if (filteredItems !== undefined) {
                if (!this.preparedSources) {
                    this.filteredSnapshot = cloneItems(filteredItems);
                    return;
                }
                const next = reconcilePersistableFilteredItems({
                    preparedSources: this.preparedSources,
                    sourceItems,
                    filteredItems,
                    ownedFilteredItems: this.ownedFilteredItems,
                    persistedInputOrder: this.persistedInputOrder,
                });
                this.ownedFilteredItems = next.ownedFilteredItems;
                this.persistedInputOrder = next.persistedInputOrder;
                this.filteredSnapshot = next.filteredSnapshot;
                return;
            }
            this.filteredSnapshot = buildSnapshotForUnfilteredItems({
                preparedSourceCounts: this.preparedSources
                    ? countItemReferences(this.preparedSources.map((source) => source.item))
                    : undefined,
                sourceItems,
                existingSnapshot: this.filteredSnapshot,
            });
        };
        getItemsForPersistence = () => {
            if (this.filteredSnapshot !== undefined) {
                return this.filteredSnapshot;
            }
            if (this.hasCallModelInputFilter) {
                return undefined;
            }
            return this.originalSnapshot;
        };
        buildPersistInputOnce = (serverManagesConversation) => {
            if (!this.session || serverManagesConversation) {
                return undefined;
            }
            const persistInput = this.persistInput ?? saveStreamInputToSession;
            return async () => {
                if (this.persistedInput) {
                    return;
                }
                const itemsToPersist = this.getItemsForPersistence();
                if (!itemsToPersist || itemsToPersist.length === 0) {
                    return;
                }
                this.persistedInput = true;
                if (options.compactionState) {
                    await persistInput(this.session, itemsToPersist, this.runContext, options.compactionState);
                }
                else {
                    await persistInput(this.session, itemsToPersist, this.runContext);
                }
            };
        };
    }
    return new SessionPersistenceTrackerImpl();
}
function cloneItems(items) {
    return items.map((item) => structuredClone(item));
}
function countItemReferences(items) {
    const counts = new Map();
    for (const item of items) {
        counts.set(item, (counts.get(item) ?? 0) + 1);
    }
    return counts;
}
function findOwnedItemIndexes(preparedInput, ownedItems) {
    const remainingOwnerIndexes = new Map();
    for (const [ownerIndex, item] of ownedItems.entries()) {
        const indexes = remainingOwnerIndexes.get(item) ?? [];
        indexes.push(ownerIndex);
        remainingOwnerIndexes.set(item, indexes);
    }
    const positions = [];
    for (const [index, item] of preparedInput.entries()) {
        const ownerIndex = remainingOwnerIndexes.get(item)?.shift();
        if (ownerIndex === undefined) {
            continue;
        }
        positions.push({ preparedIndex: index, ownerIndex });
    }
    return positions;
}
function findPreparedSourcePositions(preparedItems, preparedSources) {
    const remainingOwnerIndexes = new Map();
    for (const source of preparedSources) {
        const indexes = remainingOwnerIndexes.get(source.item) ?? [];
        indexes.push(source.ownerIndex);
        remainingOwnerIndexes.set(source.item, indexes);
    }
    const positions = [];
    for (const [preparedIndex, item] of preparedItems.entries()) {
        const ownerIndex = remainingOwnerIndexes.get(item)?.shift();
        if (ownerIndex !== undefined) {
            positions.push({ preparedIndex, ownerIndex });
        }
    }
    return positions;
}
function mapPreparedSourcesAfterContextProcessing(preparedItems, processedItems, preparedSourcePositions, options = {}) {
    const ownerIndexByPreparedIndex = new Map(preparedSourcePositions.map(({ preparedIndex, ownerIndex }) => [
        preparedIndex,
        ownerIndex,
    ]));
    const preparedIndexesByReference = new Map();
    const preparedIndexesByKey = new Map();
    for (const [index, item] of preparedItems.entries()) {
        const referenceIndexes = preparedIndexesByReference.get(item) ?? [];
        referenceIndexes.push(index);
        preparedIndexesByReference.set(item, referenceIndexes);
        const key = getAgentInputItemKey(item);
        const keyIndexes = preparedIndexesByKey.get(key) ?? [];
        keyIndexes.push(index);
        preparedIndexesByKey.set(key, keyIndexes);
    }
    const mappedPreparedIndexes = new Array(processedItems.length);
    const usedPreparedIndexes = new Set();
    const ambiguousKeys = new Set();
    const ambiguousProcessedIndexes = new Set();
    const mapOccurrences = (processedIndexesByIdentity, preparedIndexesByIdentity) => {
        for (const [identity, processedIndexes] of processedIndexesByIdentity) {
            const availablePreparedIndexes = (preparedIndexesByIdentity.get(identity) ?? []).filter((index) => !usedPreparedIndexes.has(index));
            const availableProcessedIndexes = processedIndexes.filter((index) => mappedPreparedIndexes[index] === undefined);
            for (let index = 0; index <
                Math.min(availableProcessedIndexes.length, availablePreparedIndexes.length); index++) {
                const processedIndex = availableProcessedIndexes[index];
                const preparedIndex = availablePreparedIndexes[index];
                if (processedIndex === undefined || preparedIndex === undefined) {
                    continue;
                }
                mappedPreparedIndexes[processedIndex] = preparedIndex;
                usedPreparedIndexes.add(preparedIndex);
            }
        }
    };
    const processedIndexesByReference = new Map();
    for (const [index, item] of processedItems.entries()) {
        const indexes = processedIndexesByReference.get(item) ?? [];
        indexes.push(index);
        processedIndexesByReference.set(item, indexes);
    }
    mapOccurrences(processedIndexesByReference, preparedIndexesByReference);
    const referenceMappedPreparedIndexes = new Set(usedPreparedIndexes);
    for (const [item, processedIndexes] of processedIndexesByReference) {
        const preparedIndexes = preparedIndexesByReference.get(item);
        if (preparedIndexes === undefined) {
            continue;
        }
        const preparedCount = preparedIndexes.length;
        for (const processedIndex of processedIndexes.slice(preparedCount)) {
            ambiguousKeys.add(getAgentInputItemKey(item));
            ambiguousProcessedIndexes.add(processedIndex);
        }
    }
    const processedIndexesByKey = new Map();
    for (const [index, item] of processedItems.entries()) {
        if (mappedPreparedIndexes[index] !== undefined ||
            ambiguousProcessedIndexes.has(index)) {
            continue;
        }
        const key = getAgentInputItemKey(item);
        const indexes = processedIndexesByKey.get(key) ?? [];
        indexes.push(index);
        processedIndexesByKey.set(key, indexes);
    }
    const availablePreparedCountByKey = new Map();
    for (const [key, indexes] of preparedIndexesByKey) {
        availablePreparedCountByKey.set(key, indexes.filter((index) => !usedPreparedIndexes.has(index)).length);
    }
    for (const [key, indexes] of processedIndexesByKey) {
        const availablePreparedCount = availablePreparedCountByKey.get(key) ?? 0;
        if (preparedIndexesByKey.has(key) &&
            indexes.length > availablePreparedCount) {
            ambiguousKeys.add(key);
            indexes.forEach((index) => ambiguousProcessedIndexes.add(index));
        }
    }
    // Reserve unchanged positional clones before matching equal-content occurrences globally.
    // Otherwise, an earlier removed occurrence can steal a later clone's prepared position and
    // cause an injected replacement to inherit the clone's Session ownership. Equal-sized key
    // groups retain forward occurrence matching because unrelated insertions may shift the group.
    for (const [index, item] of processedItems.entries()) {
        const preparedItem = preparedItems[index];
        const key = getAgentInputItemKey(item);
        if (mappedPreparedIndexes[index] !== undefined ||
            ambiguousProcessedIndexes.has(index) ||
            preparedItem === undefined ||
            usedPreparedIndexes.has(index) ||
            key !== getAgentInputItemKey(preparedItem) ||
            (processedIndexesByKey.get(key)?.length ?? 0) >=
                (availablePreparedCountByKey.get(key) ?? 0)) {
            continue;
        }
        mappedPreparedIndexes[index] = index;
        usedPreparedIndexes.add(index);
    }
    // Public capabilities may return clones. Resolve remaining occurrences against the complete
    // prepared sequence so callback reordering and equal-content history retain their ownership.
    const matchableProcessedIndexesByKey = new Map([...processedIndexesByKey].filter(([, indexes]) => indexes.every((index) => !ambiguousProcessedIndexes.has(index))));
    mapOccurrences(matchableProcessedIndexesByKey, preparedIndexesByKey);
    const remainingProcessedIndexes = processedItems
        .map((_, index) => index)
        .filter((index) => mappedPreparedIndexes[index] === undefined &&
        !ambiguousProcessedIndexes.has(index));
    const remainingPreparedIndexes = preparedItems
        .map((_, index) => index)
        .filter((index) => !usedPreparedIndexes.has(index) &&
        !ambiguousKeys.has(getAgentInputItemKey(preparedItems[index])));
    // Preserve the explicitly supported whole-context single-item rewrite. Once any surrounding
    // occurrence exists, an unmatched item could instead be a deletion plus an injection, so leave
    // it unowned rather than assigning Session provenance from matching residual cardinality alone.
    if (options.validateReplacements !== false &&
        preparedItems.length === 1 &&
        processedItems.length === 1 &&
        remainingProcessedIndexes.length === 1 &&
        remainingPreparedIndexes.length === 1) {
        const processedIndex = remainingProcessedIndexes[0];
        const preparedIndex = remainingPreparedIndexes[0];
        if (processedIndex !== undefined && preparedIndex !== undefined) {
            mappedPreparedIndexes[processedIndex] = preparedIndex;
            usedPreparedIndexes.add(preparedIndex);
        }
    }
    const hasUnmatchedProcessedItem = remainingProcessedIndexes.some((index) => mappedPreparedIndexes[index] === undefined);
    const hasUnmatchedOwnedItem = remainingPreparedIndexes.some((index) => !usedPreparedIndexes.has(index) && ownerIndexByPreparedIndex.has(index));
    const hasUnprovenOwnedClone = [...preparedIndexesByKey.values()].some((indexes) => indexes.some((index) => ownerIndexByPreparedIndex.has(index)) &&
        indexes.some((index) => !ownerIndexByPreparedIndex.has(index)) &&
        indexes.some((index) => ownerIndexByPreparedIndex.has(index) &&
            usedPreparedIndexes.has(index) &&
            !referenceMappedPreparedIndexes.has(index)) &&
        indexes.some((index) => !usedPreparedIndexes.has(index)));
    if (options.validateReplacements !== false &&
        hasUnmatchedProcessedItem &&
        (hasUnmatchedOwnedItem || hasUnprovenOwnedClone)) {
        throw new UserError('Capability.processContext() cannot replace Session-owned input without preserving its identity. Use callModelInputFilter for persistence-aware input replacement.');
    }
    return processedItems.flatMap((item, index) => {
        const preparedIndex = mappedPreparedIndexes[index];
        const ownerIndex = preparedIndex === undefined
            ? undefined
            : ownerIndexByPreparedIndex.get(preparedIndex);
        return ownerIndex === undefined ? [] : [{ item, ownerIndex }];
    });
}
/**
 * Maps selected input occurrences through sandbox capability context processing.
 */
export function mapOwnedInputItemsAfterContextProcessing(preparedItems, processedItems, ownedItems) {
    return mapPreparedSourcesAfterContextProcessing(preparedItems, processedItems, findOwnedItemIndexes(preparedItems, ownedItems));
}
function reconcilePersistableFilteredItems(options) {
    const { preparedSources, sourceItems, filteredItems, ownedFilteredItems, persistedInputOrder, } = options;
    const sourceOwnerIndexes = new Map();
    for (const source of preparedSources) {
        const ownerIndexes = sourceOwnerIndexes.get(source.item) ?? [];
        ownerIndexes.push(source.ownerIndex);
        sourceOwnerIndexes.set(source.item, ownerIndexes);
    }
    const sourceOccurrences = new Map();
    const representedOwnedIndexes = new Set();
    const currentOrder = [];
    const nextOwnedFilteredItems = new Map(ownedFilteredItems);
    for (let i = 0; i < filteredItems.length; i++) {
        const filteredItem = filteredItems[i];
        if (!filteredItem) {
            continue;
        }
        const source = sourceItems[i];
        if (source && typeof source === 'object') {
            const occurrence = sourceOccurrences.get(source) ?? 0;
            sourceOccurrences.set(source, occurrence + 1);
            const ownedIndex = sourceOwnerIndexes.get(source)?.[occurrence];
            if (ownedIndex !== undefined) {
                representedOwnedIndexes.add(ownedIndex);
                nextOwnedFilteredItems.set(ownedIndex, structuredClone(filteredItem));
                currentOrder.push({ type: 'owned', index: ownedIndex });
            }
            continue;
        }
        // Items without a source were injected by the callback. Preserve them without attempting
        // content-based identity matching, which could collapse ordinary messages.
        currentOrder.push({
            type: 'injected',
            item: structuredClone(filteredItem),
        });
    }
    const nextPersistedInputOrder = persistedInputOrder.filter((occurrence) => occurrence.type === 'owned' &&
        !representedOwnedIndexes.has(occurrence.index));
    nextPersistedInputOrder.push(...currentOrder);
    return {
        ownedFilteredItems: nextOwnedFilteredItems,
        persistedInputOrder: nextPersistedInputOrder,
        filteredSnapshot: nextPersistedInputOrder.flatMap((occurrence) => {
            if (occurrence.type === 'injected') {
                return [structuredClone(occurrence.item)];
            }
            const item = nextOwnedFilteredItems.get(occurrence.index);
            return item ? [structuredClone(item)] : [];
        }),
    };
}
function buildSnapshotForUnfilteredItems(options) {
    const { preparedSourceCounts, sourceItems, existingSnapshot } = options;
    if (!preparedSourceCounts) {
        const filtered = sourceItems
            .filter((item) => Boolean(item))
            .map((item) => structuredClone(item));
        return filtered.length > 0
            ? filtered
            : existingSnapshot === undefined
                ? []
                : existingSnapshot;
    }
    const filtered = [];
    const includedSourceCounts = new Map();
    for (const item of sourceItems) {
        if (!item) {
            continue;
        }
        const preparedOccurrences = preparedSourceCounts.get(item) ?? 0;
        const includedOccurrences = includedSourceCounts.get(item) ?? 0;
        if (includedOccurrences >= preparedOccurrences) {
            continue;
        }
        includedSourceCounts.set(item, includedOccurrences + 1);
        filtered.push(structuredClone(item));
    }
    if (filtered.length > 0) {
        return filtered;
    }
    return existingSnapshot === undefined ? [] : existingSnapshot;
}
export async function saveToSession(session, sessionInputItems, result, options = {}) {
    const state = result.state;
    const additionalRunItems = options.additionalRunItems ?? [];
    if (additionalRunItems.length > 0) {
        const { additionalRunItems: _additionalRunItems, ...baseOptions } = options;
        await saveToSession(session, sessionInputItems, result, {
            ...baseOptions,
            runCompaction: false,
        });
        await persistRunItemsToSession({
            session,
            state,
            newRunItems: additionalRunItems,
            runItemsToReplace: [],
            processedRunItemCount: additionalRunItems.length,
            deferredRunItemIndexes: [],
            useHistoryTransaction: false,
            extraInputItems: [],
            lastResponseId: result.lastResponseId,
            alreadyPersistedCount: state._currentTurnPersistedItemCount,
            runCompaction: options.runCompaction ?? true,
            compactionMode: options.compactionMode,
        });
        return;
    }
    const persistencePlan = buildRunItemPersistencePlan(state, result.newItems, options.outputBlocked === true, isSessionHistoryTransactionAwareSession(session));
    if (typeof process !== 'undefined' &&
        process.env?.OPENAI_AGENTS__DEBUG_SAVE_SESSION) {
        console.debug('saveToSession:newRunItems', persistencePlan.runItemsToPersist.map((item) => item.type));
    }
    await persistSessionRunItemPlan({
        session,
        state,
        persistencePlan,
        sessionInputItems,
        lastResponseId: result.lastResponseId,
        persistenceOptions: options,
    });
}
export async function saveStreamInputToSession(session, sessionInputItems, runContext, compactionState) {
    if (!session) {
        return;
    }
    if (!sessionInputItems || sessionInputItems.length === 0) {
        return;
    }
    const sanitizedInput = normalizeItemsForSessionPersistence(sessionInputItems);
    const compactedInput = trimToLatestCompaction(sanitizedInput);
    assertPersistableCompactionBoundary(compactedInput);
    if (compactedInput[0]?.type === 'compaction') {
        const previousItems = await getSessionItems(session, runContext);
        await replaceSessionItemsWithRecovery(session, previousItems, compactedInput, runContext);
        return;
    }
    await addSessionItems(session, sanitizedInput, runContext, compactionState);
}
export async function saveStreamResultToSession(session, result, options = {}, sessionInputItems) {
    const state = result.state;
    const additionalRunItems = options.additionalRunItems ?? [];
    if (additionalRunItems.length > 0) {
        const { additionalRunItems: _additionalRunItems, ...baseOptions } = options;
        await saveStreamResultToSession(session, result, {
            ...baseOptions,
            runCompaction: false,
        }, sessionInputItems);
        await persistRunItemsToSession({
            session,
            state,
            newRunItems: additionalRunItems,
            runItemsToReplace: [],
            processedRunItemCount: additionalRunItems.length,
            deferredRunItemIndexes: [],
            useHistoryTransaction: false,
            extraInputItems: [],
            lastResponseId: result.lastResponseId,
            alreadyPersistedCount: state._currentTurnPersistedItemCount,
            runCompaction: options.runCompaction ?? true,
            compactionMode: options.compactionMode,
        });
        return;
    }
    const persistencePlan = buildRunItemPersistencePlan(state, result.newItems, options.outputBlocked === true, isSessionHistoryTransactionAwareSession(session));
    await persistSessionRunItemPlan({
        session,
        state,
        persistencePlan,
        sessionInputItems,
        lastResponseId: result.lastResponseId,
        persistenceOptions: options,
    });
}
export async function reconcileLegacyCompactionSessionBeforeResume(session, state) {
    const pendingLegacyItems = state._pendingLegacyCompactionSessionItems;
    if (pendingLegacyItems === undefined) {
        return;
    }
    const normalizedPendingItems = normalizeItemsForSessionPersistence(pendingLegacyItems);
    assertPersistableCompactionBoundary(normalizedPendingItems);
    assertPendingLegacyCompactionItemsMatchState(state, normalizedPendingItems, undefined);
    if (!session) {
        throwLegacyCompactionReconciliationError();
    }
    const reasoningItemIdPolicy = session.preserveReasoningItemIdsForPersistence?.() === true
        ? undefined
        : state._reasoningItemIdPolicy;
    const sanitizedPendingItems = normalizeItemsForSessionPersistence(pendingLegacyItems.map((item) => stripReasoningItemIdForPolicy(item, reasoningItemIdPolicy)));
    assertPendingLegacyCompactionItemsMatchState(state, sanitizedPendingItems, reasoningItemIdPolicy);
    await reconcileLegacyCompactionSessionItems(session, sanitizedPendingItems, state._context);
    state._pendingLegacyCompactionSessionItems = undefined;
}
function assertPendingLegacyCompactionItemsMatchState(state, pendingItems, reasoningItemIdPolicy) {
    if (pendingItems.length < 2 || pendingItems[0]?.type !== 'compaction') {
        throwLegacyCompactionReconciliationError();
    }
    const persistedItemCount = state._currentTurnPersistedItemCount;
    if (!Number.isInteger(persistedItemCount) ||
        persistedItemCount < 1 ||
        persistedItemCount > state._generatedItems.length) {
        throwLegacyCompactionReconciliationError();
    }
    let matchingCandidates = 0;
    for (let index = 0; index < persistedItemCount; index += 1) {
        if (state._generatedItems[index]?.type !== 'compaction_item') {
            continue;
        }
        const candidateItems = normalizeItemsForSessionPersistence(extractOutputItemsFromRunItems(state._generatedItems.slice(index, persistedItemCount), reasoningItemIdPolicy));
        if (candidateItems.length > 1 &&
            agentItemRangeMatches(candidateItems, pendingItems, 0) &&
            candidateItems.length === pendingItems.length) {
            matchingCandidates += 1;
        }
    }
    if (matchingCandidates !== 1) {
        throwLegacyCompactionReconciliationError();
    }
}
export async function prepareInputItemsWithSession(input, session, sessionInputCallback, options, runContext, compactionState) {
    const newInputItems = toAgentInputList(input);
    assertValidCompactionItems(newInputItems);
    if (!session) {
        return {
            preparedInput: input,
            sessionItems: undefined,
        };
    }
    const includeHistoryInPreparedInput = options?.includeHistoryInPreparedInput ?? true;
    const preserveDroppedNewItems = options?.preserveDroppedNewItems ?? false;
    const reasoningItemIdPolicy = options?.reasoningItemIdPolicy;
    let storedItems;
    if (compactionState?.session === session &&
        isOpenAIResponsesCompactionOwnershipAwareSession(session)) {
        const snapshot = await session.getItemsWithCompactionOwnership(runContext);
        compactionState.ownership = snapshot.ownership;
        storedItems = snapshot.items;
    }
    else {
        storedItems = await getSessionItems(session, runContext);
    }
    const history = trimToLatestCompaction(storedItems);
    assertPersistableCompactionBoundary(history);
    if (!sessionInputCallback) {
        const historyForModelInput = history.map((item) => prepareHistoryItemForModelInput(session, item, reasoningItemIdPolicy));
        const preparedInput = includeHistoryInPreparedInput
            ? dropOrphanToolCalls([...historyForModelInput, ...newInputItems], {
                pruningIndexes: new Set(history.map((_, index) => index)),
            })
            : newInputItems;
        return {
            preparedInput,
            sessionItems: newInputItems,
        };
    }
    const historySnapshot = history.slice();
    const newInputSnapshot = newInputItems.slice();
    // Keep the original history objects alive so their identities remain valid even if the
    // callback removes them from the list it receives.
    const originalHistoryItems = new Set(historySnapshot);
    const combined = await sessionInputCallback(history, newInputItems);
    if (!Array.isArray(combined)) {
        throw new UserError('Session input callback must return an array of AgentInputItem objects.');
    }
    assertValidCompactionItems(combined);
    const historyCounts = buildItemFrequencyMap(historySnapshot, {
        session,
        prepareForModelInput: true,
        reasoningItemIdPolicy,
    });
    const newInputCounts = buildItemFrequencyMap(newInputSnapshot);
    const historyRefs = buildAgentInputPool(historySnapshot);
    const newInputRefs = buildAgentInputPool(newInputSnapshot);
    const historyIndexes = new Set();
    const appended = [];
    for (const [index, item] of combined.entries()) {
        const historyKey = getHistoryItemModelInputKey(session, item, reasoningItemIdPolicy);
        const newInputKey = getAgentInputItemKey(item);
        if (originalHistoryItems.has(item)) {
            if (removeAgentInputFromPool(historyRefs, item)) {
                decrementCount(historyCounts, historyKey);
            }
            if (removeAgentInputFromPool(newInputRefs, item)) {
                decrementCount(newInputCounts, newInputKey);
            }
            historyIndexes.add(index);
            continue;
        }
        if (removeAgentInputFromPool(newInputRefs, item)) {
            decrementCount(newInputCounts, newInputKey);
            appended.push(item);
            continue;
        }
        if (removeAgentInputFromPool(historyRefs, item)) {
            decrementCount(historyCounts, historyKey);
            historyIndexes.add(index);
            continue;
        }
        const historyRemaining = historyCounts.get(historyKey) ?? 0;
        if (historyRemaining > 0) {
            historyCounts.set(historyKey, historyRemaining - 1);
            historyIndexes.add(index);
            continue;
        }
        const newRemaining = newInputCounts.get(newInputKey) ?? 0;
        if (newRemaining > 0) {
            newInputCounts.set(newInputKey, newRemaining - 1);
            appended.push(item);
            continue;
        }
        appended.push(item);
    }
    const preparedItems = includeHistoryInPreparedInput
        ? combined
        : appended.length > 0
            ? appended
            : preserveDroppedNewItems
                ? newInputSnapshot
                : [];
    if (preserveDroppedNewItems &&
        appended.length === 0 &&
        newInputSnapshot.length > 0) {
        // In server-managed conversations we cannot drop the turn delta; restore it and warn callers.
        logger.warn('sessionInputCallback dropped all new inputs in a server-managed conversation; original turn inputs were restored to avoid losing the API delta. Keep at least one new item or omit conversationId if you intended to drop them.');
    }
    const prunedPreparedItems = includeHistoryInPreparedInput
        ? dropOrphanToolCalls(prepareHistoryItemsForModelInput(session, preparedItems, historyIndexes, reasoningItemIdPolicy), { pruningIndexes: historyIndexes })
        : preparedItems;
    return {
        preparedInput: prunedPreparedItems,
        sessionItems: appended,
    };
}
function prepareHistoryItemsForModelInput(session, items, historyIndexes, reasoningItemIdPolicy) {
    if (historyIndexes.size === 0) {
        return items;
    }
    return items.map((item, index) => historyIndexes.has(index)
        ? prepareHistoryItemForModelInput(session, item, reasoningItemIdPolicy)
        : item);
}
function prepareHistoryItemForModelInput(session, item, reasoningItemIdPolicy) {
    const prepared = session.prepareHistoryItemForModelInput?.(item) ?? item;
    return stripReasoningItemIdForPolicy(prepared, reasoningItemIdPolicy);
}
function getHistoryItemModelInputKey(session, item, reasoningItemIdPolicy) {
    return getAgentInputItemKey(prepareHistoryItemForModelInput(session, item, reasoningItemIdPolicy));
}
async function persistRunItemsToSession(options) {
    const { session, state, newRunItems, runItemsToReplace, processedRunItemCount, deferredRunItemIndexes, useHistoryTransaction, transactionKind, extraInputItems = [], lastResponseId, alreadyPersistedCount, runCompaction, compactionMode, resumedSessionWritePreparation, deferResumedSessionWrite = false, } = options;
    if (!session) {
        return;
    }
    if (state._pendingLegacyCompactionSessionItems !== undefined &&
        resumedSessionWritePreparation === undefined) {
        await prepareSessionHistoryTransactionsForRun(session, state, {
            serverManagesConversation: false,
        });
    }
    const effectiveReasoningItemIdPolicy = resumedSessionWritePreparation?.reasoningItemIdPolicy ??
        getEffectiveSessionReasoningItemIdPolicy(session, state);
    const frozenReasoningItemIdPolicy = useHistoryTransaction || transactionKind === 'blocked_append'
        ? (state._currentTurnSessionReasoningItemIdPolicy ??
            effectiveReasoningItemIdPolicy)
        : effectiveReasoningItemIdPolicy;
    if (useHistoryTransaction &&
        transactionKind === 'accepted_replace' &&
        (state._currentTurnBlockedSessionStartIndex === undefined ||
            state._currentTurnSessionReasoningItemIdPolicy === undefined)) {
        throw new UserError('Accepted session history replacement is missing its blocked-output persistence authority.');
    }
    const itemsToSave = [
        ...extraInputItems,
        ...extractOutputItemsFromRunItems(newRunItems, frozenReasoningItemIdPolicy),
    ];
    const sanitizedItems = normalizeItemsForSessionPersistence(itemsToSave);
    const compactedItems = trimToLatestCompaction(sanitizedItems);
    if (resumedSessionWritePreparation &&
        !useHistoryTransaction &&
        compactedItems[0]?.type !== 'compaction') {
        checkpointPreparedResumedSessionWrite({
            session,
            state,
            preparation: resumedSessionWritePreparation,
            items: sanitizedItems,
            alreadyPersistedCount,
            persistedItemCount: alreadyPersistedCount + processedRunItemCount,
            reasoningItemIdPolicy: frozenReasoningItemIdPolicy,
        });
        if (deferResumedSessionWrite) {
            return;
        }
        if (getEffectiveSessionReasoningItemIdPolicy(session, state) !==
            resumedSessionWritePreparation.reasoningItemIdPolicy) {
            throw new UserError('Session persistence policy changed after resumed tool execution.');
        }
    }
    const hadPendingHistoryTransaction = state._pendingSessionHistoryTransaction !== undefined;
    await prepareSessionHistoryTransactionsForRun(session, state, {
        serverManagesConversation: false,
    });
    if (hadPendingHistoryTransaction &&
        state._pendingSessionHistoryTransaction === undefined) {
        if (runCompaction) {
            await runCompactionOnSession(session, lastResponseId, state, compactionMode);
        }
        return;
    }
    const persistedItemCount = alreadyPersistedCount + processedRunItemCount;
    const commitPersistenceState = () => {
        commitSessionPersistenceState({
            state,
            persistedItemCount,
            deferredRunItemIndexes,
        });
    };
    if (itemsToSave.length === 0) {
        const pendingApprovedToolInputCompaction = hasPendingApprovedToolInputCompaction(state);
        if (pendingApprovedToolInputCompaction) {
            if (!runCompaction || compactionMode !== 'input') {
                throw new UserError('Pending Session compaction must settle before other persistence work.');
            }
            await completePendingApprovedToolInputCompaction(session, state);
            return;
        }
        commitPersistenceState();
        if (runCompaction) {
            await runCompactionOnSession(session, lastResponseId, state, compactionMode);
        }
        return;
    }
    if (useHistoryTransaction) {
        if (!isSessionHistoryTransactionAwareSession(session) ||
            transactionKind === undefined ||
            state._currentTurnSessionHistoryTransactionSessionId === undefined ||
            state._currentTurnSessionReasoningItemIdPolicy === undefined) {
            throw new UserError('Session history transaction capability was lost while persisting run items.');
        }
        const normalizedInputItems = normalizeItemsForSessionPersistence(extraInputItems);
        const frozenInputItems = state._currentTurnSessionHistoryTransactionInputItems;
        if (frozenInputItems === undefined ||
            frozenInputItems.length !== normalizedInputItems.length ||
            !agentItemRangeMatches(normalizedInputItems, frozenInputItems, 0)) {
            throw new UserError('Session history transaction input no longer matches the pre-execution snapshot.');
        }
        const persistedItemCount = alreadyPersistedCount + processedRunItemCount;
        state._currentTurnSessionHistoryTransactionCanReplaceAcceptedOutput ??=
            state._serializedCurrentStep === undefined ||
                state._currentStep !== state._serializedCurrentStep;
        const operationId = getSessionHistoryTransactionOperationId(state, transactionKind, alreadyPersistedCount, persistedItemCount);
        if (newRunItems.length > 0 || deferredRunItemIndexes.length > 0) {
            state._currentTurnBlockedSessionStartIndex ??= alreadyPersistedCount;
        }
        state._pendingSessionHistoryTransaction = structuredClone({
            operationId,
            transactionKind,
            runItemIndexes: getRunItemIndexesForPendingTransaction(state, newRunItems),
            replaceRunItemIndexes: getRunItemIndexesForPendingTransaction(state, runItemsToReplace),
            alreadyPersistedCount,
            persistedItemCount,
            deferredItemIndexes: deferredRunItemIndexes,
        });
        await flushPendingSessionHistoryTransaction(session, state);
        if (runCompaction) {
            await runCompactionOnSession(session, lastResponseId, state, compactionMode);
        }
        return;
    }
    assertPersistableCompactionBoundary(compactedItems);
    if (compactedItems[0]?.type === 'compaction') {
        const previousItems = await getSessionItems(session, state._context);
        await replaceSessionItemsWithRecovery(session, previousItems, compactedItems, state._context);
    }
    else {
        if (resumedSessionWritePreparation) {
            await appendPreparedResumedSessionWrite({
                session,
                state,
                preparation: resumedSessionWritePreparation,
            });
        }
        else {
            await addSessionItems(session, sanitizedItems, state._context, getSessionCompactionState(session, state));
        }
    }
    if (resumedSessionWritePreparation) {
        commitPersistenceState();
        if (runCompaction) {
            markPendingApprovedToolInputCompaction(state);
            await completePendingApprovedToolInputCompaction(session, state);
        }
        else {
            state._pendingSessionWrite = undefined;
            clearPendingSessionWriteTerminalProducer(state);
        }
        return;
    }
    commitPersistenceState();
    if (runCompaction) {
        await runCompactionOnSession(session, lastResponseId, state, compactionMode);
    }
}
async function flushPendingSessionHistoryTransaction(session, state) {
    const pending = state._pendingSessionHistoryTransaction;
    if (!pending) {
        return;
    }
    if (!isSessionHistoryTransactionAwareSession(session)) {
        throw new UserError('A pending session history transaction requires the same transaction-aware session to resume safely.');
    }
    const boundSessionId = state._currentTurnSessionHistoryTransactionSessionId;
    if (boundSessionId === undefined) {
        throw new UserError('A pending session history transaction is missing its session binding.');
    }
    const sessionId = await session.getSessionId();
    if (sessionId !== boundSessionId) {
        throw new UserError('A pending session history transaction belongs to a different session and cannot be resumed safely.');
    }
    if (state._currentTurnSessionHistoryTransactionCanReplaceAcceptedOutput ===
        undefined) {
        throw new UserError('A pending session history transaction is missing its accepted-output replacement authority.');
    }
    const transactionArgs = {
        operationId: pending.operationId,
        transaction: buildPendingSessionHistoryTransaction(state),
    };
    if (isRunContextAwareSession(session)) {
        await session.applyHistoryTransaction(transactionArgs, state._context);
    }
    else {
        await session.applyHistoryTransaction(transactionArgs);
    }
    state._currentTurnPersistedItemCount = pending.persistedItemCount;
    state._currentTurnDeferredSessionItemIndexes = new Set(pending.deferredItemIndexes);
    state._pendingSessionHistoryTransaction = undefined;
    state._currentTurnSessionHistoryTransactionInputItems = [];
    if (pending.transactionKind === 'accepted_replace') {
        state._currentTurnBlockedSessionStartIndex = undefined;
        state._currentTurnSessionHistoryTransactionSessionId = undefined;
        state._currentTurnSessionReasoningItemIdPolicy = undefined;
        state._currentTurnSessionHistoryTransactionInputItems = undefined;
        state._currentTurnSessionHistoryTransactionCanReplaceAcceptedOutput =
            undefined;
    }
}
async function reconcileLegacyCompactionSessionItems(session, pendingItems, runContext) {
    if (pendingItems.length === 0 || pendingItems[0]?.type !== 'compaction') {
        throwLegacyCompactionReconciliationError();
    }
    assertPersistableCompactionBoundary(pendingItems);
    const previousItems = await getSessionItems(session, runContext);
    assertValidCompactionItems(trimToLatestCompaction(previousItems));
    const comparablePreviousItems = session.prepareHistoryItemsForPersistenceComparison?.(previousItems) ??
        previousItems;
    const comparablePendingItems = session.prepareHistoryItemsForPersistenceComparison?.(pendingItems) ??
        pendingItems;
    const compactedComparablePreviousItems = trimToLatestCompaction(comparablePreviousItems);
    if (compactedComparablePreviousItems.length === comparablePendingItems.length &&
        agentItemRangeMatches(compactedComparablePreviousItems, comparablePendingItems, 0)) {
        return;
    }
    const previouslyPersistedSuffix = comparablePendingItems.slice(1);
    if (!agentItemRangeMatches(comparablePreviousItems, previouslyPersistedSuffix, comparablePreviousItems.length - previouslyPersistedSuffix.length)) {
        throwLegacyCompactionReconciliationError();
    }
    await replaceSessionItemsWithRecovery(session, previousItems, pendingItems, runContext);
}
function assertPersistableCompactionBoundary(items) {
    assertValidCompactionItems(items);
}
function throwLegacyCompactionReconciliationError() {
    throw new UserError('Run state cannot safely reconcile its restored compaction item with session history.');
}
async function replaceSessionItemsWithRecovery(session, previousItems, replacementItems, runContext) {
    assertValidCompactionItems(trimToLatestCompaction(previousItems));
    assertValidCompactionItems(trimToLatestCompaction(replacementItems));
    if (replacementItems[0]?.type === 'compaction' &&
        session.replaceHistoryWithCompaction) {
        const comparablePreviousItems = session.prepareHistoryItemsForPersistenceComparison?.(previousItems) ??
            previousItems;
        const comparableReplacementItems = session.prepareHistoryItemsForPersistenceComparison?.(replacementItems) ??
            replacementItems;
        const compactedPreviousItems = trimToLatestCompaction(comparablePreviousItems);
        if (compactedPreviousItems.length === comparableReplacementItems.length &&
            agentItemRangeMatches(compactedPreviousItems, comparableReplacementItems, 0)) {
            return;
        }
        if (runContext && isRunContextAwareSession(session)) {
            await session.replaceHistoryWithCompaction(replacementItems, runContext);
        }
        else {
            await session.replaceHistoryWithCompaction(replacementItems);
        }
        return;
    }
    try {
        await clearSession(session, runContext);
        if (replacementItems.length > 0) {
            await addSessionItems(session, replacementItems, runContext);
        }
    }
    catch (error) {
        await restoreSessionItemsAfterFailedReplacement(session, previousItems, error, runContext);
        throw error;
    }
}
async function restoreSessionItemsAfterFailedReplacement(session, previousItems, replacementError, runContext) {
    try {
        const currentItems = await getSessionItems(session, runContext);
        if (currentItems.length === previousItems.length &&
            agentItemRangeMatches(currentItems, previousItems, 0)) {
            return;
        }
    }
    catch (inspectionError) {
        logModelAndToolActionWarning(logger, 'Failed to inspect session history after compaction replacement failed.', inspectionError);
    }
    try {
        await clearSession(session, runContext);
        if (previousItems.length > 0) {
            await addSessionItems(session, previousItems, runContext);
        }
    }
    catch (restoreError) {
        logModelAndToolActionWarning(logger, 'Failed to restore session history after compaction replacement failed.', restoreError);
        throw new SessionReconciliationRecoveryError(replacementError, restoreError);
    }
    logModelAndToolActionWarning(logger, 'Restored session history after compaction replacement failed.', replacementError);
}
async function runCompactionOnSession(session, responseId, state, compactionMode) {
    if (!isOpenAIResponsesCompactionAwareSession(session)) {
        return;
    }
    const store = state._lastModelSettings?.store ?? state._currentAgent.modelSettings?.store;
    const compactionArgs = typeof responseId === 'undefined' &&
        typeof store === 'undefined' &&
        typeof compactionMode === 'undefined'
        ? undefined
        : {
            ...(typeof responseId === 'undefined' ? {} : { responseId }),
            ...(typeof store === 'undefined' ? {} : { store }),
            ...(typeof compactionMode === 'undefined' ? {} : { compactionMode }),
        };
    let compactionResult;
    try {
        compactionResult = isOpenAIResponsesCompactionOwnershipAwareSession(session)
            ? await session.runCompaction(compactionArgs, state._context, getSessionCompactionState(session, state)?.ownership ?? null, getSessionCompactionState(session, state)?.modelExchange)
            : isRunContextAwareSession(session)
                ? await session.runCompaction(compactionArgs, state._context)
                : await session.runCompaction(compactionArgs);
    }
    catch (error) {
        const usage = consumeModelFailureUsage(error);
        if (usage) {
            state._context.usage.add(usage);
            getRunStateUsageRecorder(state)?.(usage);
        }
        throw error;
    }
    if (!compactionResult) {
        return;
    }
    const usage = compactionResult.usage;
    const usageIncrement = new Usage({
        requests: 1,
        inputTokens: usage.inputTokens,
        outputTokens: usage.outputTokens,
        totalTokens: usage.totalTokens,
        inputTokensDetails: usage.inputTokensDetails,
        outputTokensDetails: usage.outputTokensDetails,
        requestUsageEntries: [usage],
    });
    state._context.usage.add(usageIncrement);
    getRunStateUsageRecorder(state)?.(usageIncrement);
}
function buildItemFrequencyMap(items, options) {
    const counts = new Map();
    for (const item of items) {
        const key = options?.prepareForModelInput && options.session
            ? getHistoryItemModelInputKey(options.session, item, options.reasoningItemIdPolicy)
            : getAgentInputItemKey(item);
        counts.set(key, (counts.get(key) ?? 0) + 1);
    }
    return counts;
}
function decrementCount(map, key) {
    const remaining = (map.get(key) ?? 0) - 1;
    if (remaining <= 0) {
        map.delete(key);
    }
    else {
        map.set(key, remaining);
    }
}
//# sourceMappingURL=sessionPersistence.mjs.map