@openai/agents-core
Version:
The OpenAI Agents SDK is a lightweight yet powerful framework for building multi-agent workflows.
1,424 lines • 62.4 kB
JavaScript
import { UserError } from "../errors.mjs";
import { RunHandoffOutputItem, RunMessageOutputItem, RunReasoningItem, RunToolCallItem, RunToolCallOutputItem, RunToolSearchCallItem, RunToolSearchOutputItem, } from "../items.mjs";
import { getToolCallName, getToolCallNamespace, getToolCallQualifiedName, getFunctionToolLookupKeyForCall, matchesFunctionToolName, } from "../toolIdentity.mjs";
import { getToolSearchExecution, getToolSearchMatchKey, getToolSearchOutputReplacementKey, getToolSearchProviderCallId, } from "../tooling.mjs";
import { FunctionCallItem as FunctionCallItemSchema, FunctionCallResultItem as FunctionCallResultItemSchema, } from "../types/protocol.mjs";
import { Usage } from "../usage.mjs";
import { toSmartString } from "../utils/smartString.mjs";
import { getSerializedOutputGuardrailResults, sanitizeBlockedOutputGuardrailResults, sanitizeBlockedToolOutputGuardrailResults, replaceSanitizedOutputGuardrailMessages, } from "./guardrails.mjs";
import { invalidateOutputItemNormalization } from "./items.mjs";
import { getToolResultCorrelationForCall, getToolResultCorrelationForResult, getToolResultCorrelationKey, } from "./toolResultCorrelation.mjs";
import { addLoadedToolNamesFromToolSearchOutput, toolSearchOutputLoadsHostedMcpTool, } from "./toolSearch.mjs";
import { OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT } from "./outputGuardrailBlockedMessage.mjs";
const currentResponseGuardrails = new WeakMap();
export function captureCurrentResponseToolOutputGuardrailResultStart(state, overwrite, hasOutputGuardrail = false) {
const previous = currentResponseGuardrails.get(state);
currentResponseGuardrails.set(state, {
startIndex: overwrite || !previous
? state._toolOutputGuardrailResults.length
: previous.startIndex,
hasOutputGuardrail,
});
}
export function getCurrentResponseToolOutputGuardrailResultStart(state) {
return currentResponseGuardrails.get(state)?.startIndex;
}
export function restoreCurrentResponseToolOutputGuardrailResultStart(state, startIndex, hasNestedOwnership) {
currentResponseGuardrails.set(state, {
startIndex,
hasOutputGuardrail: hasNestedOwnership,
});
}
/** Selects the checkpoint format only. Resume always evaluates the configured guardrails. */
export function needsNestedApprovalOwnership(state, generatedItemStartIndex) {
// The writer has already identified the current response range. Running restored selection
// again here would revalidate every child for each propagated interruption.
return (currentResponseGuardrails.get(state)?.hasOutputGuardrail === true &&
state._currentAgent.toolUseBehavior !== 'run_llm_again' &&
state._generatedItems
.slice(generatedItemStartIndex)
.some(isOutputBearingApprovalItem));
}
function currentCompletedToolRuns(state) {
const responseCalls = getResponseOutput(currentResponse(state))?.filter((item) => item.type === 'function_call');
const processedRuns = state._lastProcessedResponse?.functions ?? [];
if (!responseCalls?.length || processedRuns.length < responseCalls.length) {
return [];
}
return processedRuns.slice(-responseCalls.length).filter((run, index) => {
try {
return (JSON.stringify(buildCanonicalFunctionCall(run.toolCall)) ===
JSON.stringify(buildCanonicalFunctionCall(responseCalls[index])) &&
state._completedToolInvocationEvidence
.get(state._currentAgent)
?.has(run.toolCall.callId));
}
catch {
return false;
}
});
}
function currentTerminalToolRuns(state) {
if (state._currentStep?.type !== 'next_step_final_output') {
return [];
}
const behavior = state._currentAgent.toolUseBehavior;
if (behavior === 'run_llm_again') {
return [];
}
const completedRuns = currentCompletedToolRuns(state);
if (typeof behavior === 'object' &&
!completedRuns.some((run) => behavior.stopAtToolNames.some((toolName) => matchesFunctionToolName(run.tool, toolName)))) {
return [];
}
return completedRuns;
}
function currentDeclarativeTerminalToolOutput(state) {
const behavior = state._currentAgent.toolUseBehavior;
if (behavior === 'run_llm_again' ||
typeof behavior === 'function' ||
state._currentStep?.type !== 'next_step_final_output') {
return undefined;
}
const completedRuns = currentTerminalToolRuns(state);
const selectedRun = behavior === 'stop_on_first_tool'
? completedRuns[0]
: completedRuns.find((run) => behavior.stopAtToolNames.some((toolName) => matchesFunctionToolName(run.tool, toolName)));
const callId = selectedRun?.toolCall.callId;
const outputItem = callId
? state._completedToolInvocationEvidence
.get(state._currentAgent)
?.get(callId)?.items[1]
: undefined;
return outputItem instanceof RunToolCallOutputItem
? toSmartString(outputItem.output)
: undefined;
}
export function hasPersistedToolOutput(state) {
return state._generatedItems
.slice(0, state._currentTurnPersistedItemCount)
.some((item) => item.type === 'tool_call_output_item');
}
export function hasPendingApprovedToolInputCompaction(state) {
return state._pendingSessionWrite?.phase === 'compaction_pending';
}
export function hasDeterministicTerminalToolOutputSource(state) {
return (state._currentStep?.type === 'next_step_final_output' &&
currentDeclarativeTerminalToolOutput(state) === state._currentStep.output);
}
export function hasTerminalToolOutputSource(state) {
return (state._finalOutputSource === 'tool_result' ||
(state._finalOutputSource === undefined &&
state._serializedCurrentStep === state._currentStep &&
currentTerminalToolRuns(state).length > 0));
}
export function sanitizeBlockedTerminalToolOutput(state, outputGuardrailResultStart, completedOutputGuardrailTripwireResult, observedOutputGuardrailResults, tripwire, ownedOutputGuardrailResults, resolveBlockedMessage, signal, onBlockedOutput) {
if (!hasTerminalToolOutputSource(state) ||
!completedOutputGuardrailTripwireResult) {
return false;
}
onBlockedOutput?.();
redactBlockedResponseToolOutputs(state);
const sanitizedOutputGuardrailResults = sanitizeBlockedOutputGuardrailResults(state, outputGuardrailResultStart, OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT, observedOutputGuardrailResults, tripwire, ownedOutputGuardrailResults);
sanitizeBlockedToolOutputGuardrailResults(state, getCurrentResponseToolOutputGuardrailResultStart(state) ?? 0, OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT);
if (!resolveBlockedMessage) {
return OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT;
}
const blockedOutputGuardrailResult = completedOutputGuardrailTripwireResult;
let guardrailName = 'output_guardrail';
try {
const candidate = blockedOutputGuardrailResult.guardrail.name;
if (typeof candidate === 'string' && candidate.length > 0) {
guardrailName = candidate;
}
}
catch {
// Keep the safe fallback name when caller-owned metadata is unreadable.
}
signal?.throwIfAborted();
return awaitBlockedMessageWithAbort(resolveBlockedMessage(guardrailName), signal)
.then((blockedMessage) => {
signal?.throwIfAborted();
if (typeof blockedMessage !== 'string' ||
blockedMessage.length === 0 ||
blockedMessage === OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT) {
return OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT;
}
redactBlockedResponseToolOutputs(state, blockedMessage);
replaceSanitizedOutputGuardrailMessages(state, sanitizedOutputGuardrailResults, blockedMessage, tripwire);
return blockedMessage;
})
.catch(() => {
signal?.throwIfAborted();
return OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT;
});
}
function awaitBlockedMessageWithAbort(promise, signal) {
if (!signal) {
return promise;
}
signal.throwIfAborted();
return new Promise((resolve, reject) => {
let settled = false;
const cleanup = () => {
signal.removeEventListener('abort', onAbort);
};
const onAbort = () => {
if (settled) {
return;
}
settled = true;
cleanup();
try {
signal.throwIfAborted();
}
catch (error) {
reject(error);
}
};
signal.addEventListener('abort', onAbort, { once: true });
if (signal.aborted) {
onAbort();
return;
}
promise.then((value) => {
if (settled) {
return;
}
settled = true;
cleanup();
resolve(value);
}, (error) => {
if (settled) {
return;
}
settled = true;
cleanup();
reject(error);
});
});
}
export function shouldDeferInterruptedSessionItems(state, hasOutputGuardrail) {
return (state._currentStep?.type === 'next_step_interruption' &&
hasOutputGuardrail &&
state._currentAgent.toolUseBehavior !== 'run_llm_again' &&
hasOutputBearingApprovalCheckpoint(state));
}
export function assertResumedSessionOutputGuardrailSafety(state, session, hasOutputGuardrail) {
if (hasOutputGuardrail &&
state._serializedCurrentStep === state._currentStep &&
state._currentStep?.type === 'next_step_final_output' &&
state._finalOutputSource === undefined &&
currentCompletedToolRuns(state).length > 0 &&
!hasTerminalToolOutputSource(state)) {
throw new UserError('Cannot resume this serialized terminal output because terminal tool output provenance could not be verified after the tool behavior changed. Continue the live RunState or start a new run from safe input.');
}
const shouldDefer = shouldDeferInterruptedSessionItems(state, hasOutputGuardrail);
let restoredSelection;
if (state._serializedCurrentStep !== undefined && shouldDefer) {
const responseOutput = getResponseOutput(currentResponse(state));
restoredSelection = responseOutput
? currentRunItemSelection(state, responseOutput)
: undefined;
if (!responseOutput ||
responseOutput.some((item) => item.type !== 'function_call') ||
!restoredSelection?.proven ||
getCurrentResponseToolOutputGuardrailResultStart(state) === undefined) {
const reasons = [
!responseOutput ? 'missing response output' : undefined,
responseOutput?.some((item) => item.type !== 'function_call')
? 'non-function response item'
: undefined,
!restoredSelection?.proven
? 'unproven generated-item range'
: undefined,
getCurrentResponseToolOutputGuardrailResultStart(state) === undefined
? 'missing guardrail-result boundary'
: undefined,
].filter((reason) => reason !== undefined);
throw new UserError(`Cannot resume this serialized output-bearing approval checkpoint because current-response provenance was not preserved (${reasons.join(', ')}). Start a new run from safe input.`);
}
}
if (session === undefined) {
if (shouldDefer && state._currentTurnPersistedItemCount > 0) {
state.resetTurnPersistence();
}
return;
}
if (!shouldDefer || state._currentTurnPersistedItemCount <= 0) {
return;
}
const currentCallIds = new Set((state._lastProcessedResponse?.functions ?? []).map((run) => run.toolCall.callId));
const currentStart = restoredSelection
? state._generatedItems.length - restoredSelection.primary.length
: state._generatedItems.findIndex((item) => item instanceof RunToolCallItem &&
item.rawItem.type === 'function_call' &&
currentCallIds.has(item.rawItem.callId));
const firstOutputIndex = state._generatedItems.findIndex((item, index) => index >= currentStart && item instanceof RunToolCallOutputItem);
if (currentStart >= 0 &&
firstOutputIndex === state._currentTurnPersistedItemCount &&
state._generatedItems
.slice(currentStart, firstOutputIndex)
.every((item) => 'rawItem' in item &&
item.rawItem.type === 'function_call' &&
currentCallIds.has(item.rawItem.callId))) {
return;
}
throw new UserError('Cannot resume an output-bearing approval checkpoint with a Session while output guardrails are enabled because the persisted response ownership cannot be proven. Start a new run from safe input.');
}
const REPLAY_SAFE_HOSTED_TOOL_TYPES = new Set([
'web_search_call',
'web_search',
'file_search_call',
'file_search',
'image_generation_call',
'image_generation',
'code_interpreter_call',
'code_interpreter',
'mcp_call',
'mcp_list_tools',
]);
function getProgramCallerId(item) {
if (!item || typeof item !== 'object' || !('caller' in item)) {
return undefined;
}
const caller = item.caller;
if (!caller || typeof caller !== 'object') {
return undefined;
}
const candidate = caller;
return candidate.type === 'program' && typeof candidate.callerId === 'string'
? candidate.callerId
: undefined;
}
function getProviderStatus(item) {
const providerData = item.providerData;
return providerData && typeof providerData === 'object'
? providerData.status
: undefined;
}
function hasOnlyCompletedStatusEvidence(itemStatus, providerStatus, allowMissing) {
if (itemStatus === undefined && providerStatus === undefined) {
return allowMissing;
}
return ((itemStatus === undefined || itemStatus === 'completed') &&
(providerStatus === undefined || providerStatus === 'completed'));
}
function supportsLocalExecutionProvenance(type) {
return (type === 'function_call_result' ||
type === 'shell_call_output' ||
type === 'computer_call_result' ||
type === 'apply_patch_call_output');
}
function getBlockedPairKind(item, role) {
if (role === 'call') {
if (item.type === 'tool_call_item') {
return 'tool';
}
if (item.type === 'handoff_call_item') {
return 'handoff';
}
return undefined;
}
if (item instanceof RunToolCallOutputItem) {
return 'tool';
}
if (item instanceof RunHandoffOutputItem) {
return 'handoff';
}
return undefined;
}
function classifyBlockedToolRecord(item) {
const rawItem = item.rawItem;
if (!rawItem) {
return undefined;
}
const type = rawItem.type;
const status = rawItem.status;
let role;
let terminal;
switch (type) {
case 'program':
case 'function_call':
case 'shell_call':
case 'computer_call':
case 'apply_patch_call':
role = 'call';
// A locally executed call can retain an in-progress provider status. The matching result's
// durable execution provenance is the authoritative side-effect boundary.
terminal = true;
break;
case 'function_call_result':
case 'program_output':
role = 'result';
terminal = status === 'completed';
break;
case 'computer_call_result': {
role = 'result';
const providerData = rawItem.providerData;
const providerStatus = providerData && typeof providerData === 'object'
? providerData.status
: undefined;
terminal = providerStatus === undefined || providerStatus === 'completed';
break;
}
case 'shell_call_output': {
role = 'result';
terminal = hasOnlyCompletedStatusEvidence(status, getProviderStatus(rawItem), true);
break;
}
case 'apply_patch_call_output':
role = 'result';
terminal = status === 'completed' || status === 'failed';
break;
default:
return undefined;
}
const correlation = role === 'call'
? getToolResultCorrelationForCall(rawItem)
: getToolResultCorrelationForResult(rawItem);
const kind = getBlockedPairKind(item, role);
return correlation
? {
key: getToolResultCorrelationKey(correlation),
role,
terminal,
kind,
committed: kind === 'handoff' ||
(kind === 'tool' &&
role === 'result' &&
supportsLocalExecutionProvenance(type) &&
item.executionStatus === 'executed'),
programCallerId: getProgramCallerId(rawItem),
programOwnerId: type === 'program' &&
typeof rawItem.callId === 'string'
? rawItem.callId
: undefined,
}
: undefined;
}
function getBlockedRunItemAgent(item) {
return item instanceof RunHandoffOutputItem ? item.sourceAgent : item.agent;
}
function isReplaySafeTerminalHostedToolCall(item) {
const rawItem = item.rawItem;
const providerData = rawItem?.type === 'hosted_tool_call' &&
rawItem.providerData &&
typeof rawItem.providerData === 'object'
? rawItem.providerData
: undefined;
const itemStatus = rawItem?.type === 'hosted_tool_call' ? rawItem.status : undefined;
const providerStatus = providerData?.status;
if (item.type !== 'tool_call_item' ||
rawItem?.type !== 'hosted_tool_call' ||
!hasOnlyCompletedStatusEvidence(itemStatus, providerStatus, false)) {
return false;
}
const providerType = providerData?.type;
if (providerType !== undefined) {
return (typeof providerType === 'string' &&
REPLAY_SAFE_HOSTED_TOOL_TYPES.has(providerType));
}
return REPLAY_SAFE_HOSTED_TOOL_TYPES.has(rawItem.name);
}
function collectBlockedToolSearchPairs(items) {
const latestCallByAgentAndKey = new Map();
const pendingClientCalls = new Map();
const pendingServerCalls = new Map();
const pairs = [];
for (const [index, item] of items.entries()) {
if (item instanceof RunToolSearchCallItem) {
const key = getToolSearchMatchKey(item.rawItem);
if (!key) {
continue;
}
const server = getToolSearchExecution(item.rawItem) === 'server';
const pairKey = `${server ? 'server' : 'client'}:${key}`;
const occurrence = { callIndex: index, call: item };
const byKey = latestCallByAgentAndKey.get(item.agent) ?? new Map();
byKey.set(pairKey, occurrence);
latestCallByAgentAndKey.set(item.agent, byKey);
const pending = server ? pendingServerCalls : pendingClientCalls;
const occurrences = pending.get(item.agent) ?? [];
occurrences.push(occurrence);
pending.set(item.agent, occurrences);
continue;
}
if (!(item instanceof RunToolSearchOutputItem)) {
continue;
}
const server = getToolSearchExecution(item.rawItem) === 'server';
const pending = server ? pendingServerCalls : pendingClientCalls;
const pendingOccurrences = pending.get(item.agent) ?? [];
const explicitKey = getToolSearchProviderCallId(item.rawItem);
const occurrence = explicitKey
? latestCallByAgentAndKey
.get(item.agent)
?.get(`${server ? 'server' : 'client'}:${explicitKey}`)
: pendingOccurrences.shift();
if (explicitKey && occurrence) {
const pendingIndex = pendingOccurrences.indexOf(occurrence);
if (pendingIndex >= 0) {
pendingOccurrences.splice(pendingIndex, 1);
}
}
pairs.push({
callIndex: occurrence?.callIndex,
outputIndex: index,
output: item.rawItem,
agent: item.agent,
replacementKey: getToolSearchOutputReplacementKey(item.rawItem),
valid: occurrence !== undefined &&
item.rawItem.status === 'completed' &&
(!server || occurrence.call.rawItem.status === 'completed'),
});
}
return pairs;
}
function toolSearchOutputLoadsRetainedCall(output, call) {
const loadedToolNames = new Set();
addLoadedToolNamesFromToolSearchOutput(output, loadedToolNames);
if (call.type === 'function_call') {
const qualifiedName = getToolCallQualifiedName(call);
if (qualifiedName && loadedToolNames.has(qualifiedName)) {
return true;
}
const name = getToolCallName(call);
const namespace = getToolCallNamespace(call);
return (name !== undefined &&
(!namespace || namespace === name) &&
loadedToolNames.has(name));
}
const providerData = call.type === 'hosted_tool_call' &&
call.providerData &&
typeof call.providerData === 'object'
? call.providerData
: undefined;
return ((providerData?.type === 'mcp_call' ||
providerData?.type === 'mcp_list_tools') &&
typeof providerData.server_label === 'string' &&
toolSearchOutputLoadsHostedMcpTool(output, providerData.server_label, providerData.name));
}
function isToolSearchDependentRetainedCall(call) {
if (call.type === 'function_call') {
return true;
}
const providerData = call.type === 'hosted_tool_call' &&
call.providerData &&
typeof call.providerData === 'object'
? call.providerData
: undefined;
return ((providerData?.type === 'mcp_call' ||
providerData?.type === 'mcp_list_tools') &&
typeof providerData.server_label === 'string');
}
/**
* Selects replay-safe tool effects when an assistant's final output is blocked.
*
* Unknown run-item kinds are excluded. Reasoning is retained only when the next non-reasoning
* item is a retained tool call or one of its required provenance records.
*/
export function selectRunItemIndexesForBlockedOutput(items, unpersistedStartIndex = 0) {
const pairsByAgent = new Map();
const programOwnerIndexesByAgent = new Map();
const standaloneCalls = [];
const toolSearchPairs = collectBlockedToolSearchPairs(items);
for (const [index, item] of items.entries()) {
if (isReplaySafeTerminalHostedToolCall(item)) {
standaloneCalls.push({
index,
programCallerId: getProgramCallerId(item.rawItem),
agent: getBlockedRunItemAgent(item),
});
continue;
}
const record = classifyBlockedToolRecord(item);
if (!record) {
continue;
}
const itemAgent = getBlockedRunItemAgent(item);
const pairs = pairsByAgent.get(itemAgent) ?? new Map();
pairsByAgent.set(itemAgent, pairs);
let pair = pairs.get(record.key);
if (!pair) {
pair = { valid: true, calls: [], results: [] };
pairs.set(record.key, pair);
}
if (!record.terminal ||
!record.kind ||
(pair.kind !== undefined &&
pair.kind !== record.kind &&
!(pair.kind === 'handoff' &&
record.role === 'result' &&
item instanceof RunToolCallOutputItem &&
item.rawItem.type === 'function_call_result' &&
item.executionStatus === 'executed'))) {
pair.valid = false;
continue;
}
pair.kind = record.kind;
if (record.programOwnerId !== undefined && record.kind === 'tool') {
const ownersById = programOwnerIndexesByAgent.get(itemAgent) ?? new Map();
const ownerIndexes = ownersById.get(record.programOwnerId) ?? [];
ownerIndexes.push(index);
ownersById.set(record.programOwnerId, ownerIndexes);
programOwnerIndexesByAgent.set(itemAgent, ownersById);
}
const member = {
index,
programCallerId: record.programCallerId,
agent: itemAgent,
};
if (record.role === 'call') {
pair.calls.push(member);
}
else {
pair.results.push({ ...member, committed: record.committed });
}
}
const retainedIndexes = new Set();
const retainedCallIndexes = new Set();
const directlyRetainedCallIndexes = new Set();
const retainedResultIndexByCallIndex = new Map();
const programOwnerIndexByCallIndex = new Map();
const retainCallWithProgramOwner = (call) => {
if (call.programCallerId !== undefined) {
const ownerIndexes = programOwnerIndexesByAgent.get(call.agent)?.get(call.programCallerId) ??
[];
if (ownerIndexes.length !== 1 || ownerIndexes[0] >= call.index) {
return false;
}
retainedIndexes.add(ownerIndexes[0]);
retainedCallIndexes.add(ownerIndexes[0]);
programOwnerIndexByCallIndex.set(call.index, ownerIndexes[0]);
}
retainedIndexes.add(call.index);
retainedCallIndexes.add(call.index);
return true;
};
for (const pairs of pairsByAgent.values()) {
for (const pair of pairs.values()) {
if (!pair.valid || pair.calls.length !== 1 || pair.results.length !== 1) {
continue;
}
const call = pair.calls[0];
const result = pair.results[0];
if (call.index >= result.index ||
call.programCallerId !== result.programCallerId ||
(!result.committed && call.index >= unpersistedStartIndex)) {
continue;
}
if (retainCallWithProgramOwner(call)) {
directlyRetainedCallIndexes.add(call.index);
retainedIndexes.add(result.index);
retainedResultIndexByCallIndex.set(call.index, result.index);
}
}
}
for (const standaloneCall of standaloneCalls) {
if (retainCallWithProgramOwner(standaloneCall)) {
directlyRetainedCallIndexes.add(standaloneCall.index);
}
}
for (const retainedCallIndex of [...retainedCallIndexes]) {
const retainedCall = items[retainedCallIndex];
const rawItem = retainedCall.rawItem;
if (!rawItem || !isToolSearchDependentRetainedCall(rawItem)) {
continue;
}
const precedingOccurrences = toolSearchPairs.filter((pair) => pair.agent === retainedCall.agent &&
pair.outputIndex < retainedCallIndex);
const hasMatchingOutput = precedingOccurrences.some((pair) => toolSearchOutputLoadsRetainedCall(pair.output, rawItem));
if (!hasMatchingOutput) {
continue;
}
const latestByReplacementKey = new Map();
const additiveOccurrences = [];
for (const occurrence of precedingOccurrences) {
if (occurrence.replacementKey === undefined) {
additiveOccurrences.push(occurrence);
}
else {
latestByReplacementKey.set(occurrence.replacementKey, occurrence);
}
}
const supplier = [
...latestByReplacementKey.values(),
...additiveOccurrences,
]
.filter((occurrence) => occurrence.valid &&
occurrence.callIndex !== undefined &&
toolSearchOutputLoadsRetainedCall(occurrence.output, rawItem))
.sort((left, right) => left.outputIndex - right.outputIndex)
.at(-1);
if (!supplier) {
retainedIndexes.delete(retainedCallIndex);
retainedCallIndexes.delete(retainedCallIndex);
directlyRetainedCallIndexes.delete(retainedCallIndex);
const resultIndex = retainedResultIndexByCallIndex.get(retainedCallIndex);
if (resultIndex !== undefined) {
retainedIndexes.delete(resultIndex);
}
continue;
}
retainedIndexes.add(supplier.callIndex);
retainedIndexes.add(supplier.outputIndex);
retainedCallIndexes.add(supplier.callIndex);
}
const neededProgramOwnerIndexes = new Set([...programOwnerIndexByCallIndex]
.filter(([callIndex]) => retainedCallIndexes.has(callIndex))
.map(([, ownerIndex]) => ownerIndex));
for (const ownerIndex of programOwnerIndexByCallIndex.values()) {
if (!directlyRetainedCallIndexes.has(ownerIndex) &&
!neededProgramOwnerIndexes.has(ownerIndex)) {
retainedIndexes.delete(ownerIndex);
retainedCallIndexes.delete(ownerIndex);
}
}
if (retainedIndexes.size === 0) {
return [];
}
for (let index = items.length - 1; index >= 0; index -= 1) {
if (items[index]?.type !== 'reasoning_item') {
continue;
}
for (let nextIndex = index + 1; nextIndex < items.length; nextIndex += 1) {
if (items[nextIndex]?.type === 'reasoning_item') {
continue;
}
if (retainedCallIndexes.has(nextIndex)) {
retainedIndexes.add(index);
}
break;
}
}
return [...retainedIndexes]
.filter((index) => index >= unpersistedStartIndex)
.sort((left, right) => left - right);
}
export function hasBlockedOutputExecutionEffect(items, unpersistedStartIndex = 0) {
return (selectRunItemIndexesForBlockedOutput(items, unpersistedStartIndex).length >
0 ||
items
.slice(unpersistedStartIndex)
.some((item) => item instanceof RunToolCallOutputItem &&
item.executionStatus === 'executed'));
}
export function buildRunItemPersistencePlan(options) {
const { items, alreadyPersistedCount, currentDeferredIndexes, outputBlocked, canUseHistoryTransactions, blockedSnapshot, } = options;
const newRunItemIndexes = Array.from({ length: Math.max(0, items.length - alreadyPersistedCount) }, (_value, offset) => alreadyPersistedCount + offset);
if (outputBlocked) {
if (blockedSnapshot) {
const snapshotPersistedCount = canUseHistoryTransactions
? alreadyPersistedCount
: blockedSnapshot.alreadyPersistedCount;
const persistedSnapshotItems = Math.max(0, snapshotPersistedCount - blockedSnapshot.startIndex);
return {
alreadyPersistedCount: snapshotPersistedCount,
runItemsToPersist: blockedSnapshot.items.slice(persistedSnapshotItems),
runItemsToReplace: [],
processedRunItemCount: newRunItemIndexes.length,
deferredRunItemIndexes: [],
useHistoryTransaction: canUseHistoryTransactions,
transactionKind: 'blocked_append',
};
}
if (!canUseHistoryTransactions) {
return {
alreadyPersistedCount,
runItemsToPersist: [],
runItemsToReplace: [],
processedRunItemCount: 0,
deferredRunItemIndexes: [],
useHistoryTransaction: false,
};
}
const retainedIndexes = selectRunItemIndexesForBlockedOutput(items, alreadyPersistedCount);
if (retainedIndexes.length === 0) {
return {
alreadyPersistedCount,
runItemsToPersist: [],
runItemsToReplace: [],
processedRunItemCount: 0,
deferredRunItemIndexes: [],
useHistoryTransaction: false,
};
}
const retainedIndexSet = new Set(retainedIndexes);
const deferredRunItemIndexes = [
...new Set([
...currentDeferredIndexes,
...newRunItemIndexes.filter((index) => !retainedIndexSet.has(index)),
]),
]
.filter((index) => index < items.length)
.sort((left, right) => left - right);
return {
alreadyPersistedCount,
runItemsToPersist: retainedIndexes.map((index) => items[index]),
runItemsToReplace: [],
processedRunItemCount: newRunItemIndexes.length,
deferredRunItemIndexes,
useHistoryTransaction: true,
transactionKind: 'blocked_append',
};
}
const deferredIndexes = [...currentDeferredIndexes]
.filter((index) => index < items.length)
.sort((left, right) => left - right);
if (deferredIndexes.length === 0) {
return {
alreadyPersistedCount,
runItemsToPersist: items.slice(alreadyPersistedCount),
runItemsToReplace: [],
processedRunItemCount: newRunItemIndexes.length,
deferredRunItemIndexes: [],
useHistoryTransaction: false,
};
}
if (!canUseHistoryTransactions) {
throw new UserError('Cannot persist accepted output from this RunState because its blocked output was saved by a transaction-aware session. Resume with the same transaction-aware session.');
}
const firstDeferredIndex = deferredIndexes[0];
const deferredIndexSet = new Set(deferredIndexes);
const processedEndIndex = Math.min(alreadyPersistedCount, items.length);
const runItemsToReplace = [];
for (let index = firstDeferredIndex; index < processedEndIndex; index += 1) {
if (!deferredIndexSet.has(index)) {
runItemsToReplace.push(items[index]);
}
}
let replacementStartIndex = firstDeferredIndex;
if (runItemsToReplace.length === 0) {
const anchorIndex = firstDeferredIndex - 1;
if (anchorIndex < 0) {
throw new UserError('Cannot persist accepted output because the blocked session suffix has no retained anchor.');
}
runItemsToReplace.push(items[anchorIndex]);
replacementStartIndex = anchorIndex;
}
return {
alreadyPersistedCount,
runItemsToPersist: items.slice(replacementStartIndex),
runItemsToReplace,
processedRunItemCount: newRunItemIndexes.length,
deferredRunItemIndexes: [],
useHistoryTransaction: true,
transactionKind: 'accepted_replace',
};
}
function optionalField(key, value) {
return value === undefined ? {} : { [key]: value };
}
function buildCanonicalFunctionCall(rawItem) {
const parsed = FunctionCallItemSchema.parse(rawItem);
return FunctionCallItemSchema.parse({
type: 'function_call',
name: parsed.name,
...optionalField('namespace', parsed.namespace),
callId: parsed.callId,
arguments: parsed.arguments,
...optionalField('id', parsed.id),
...optionalField('status', parsed.status),
...optionalField('caller', parsed.caller),
});
}
export function buildBlockedToolOutputRawItem(rawItem, blockedMessage = OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT) {
const parsed = FunctionCallResultItemSchema.parse(rawItem);
return FunctionCallResultItemSchema.parse({
type: 'function_call_result',
name: parsed.name,
...optionalField('namespace', parsed.namespace),
callId: parsed.callId,
output: blockedMessage,
...optionalField('id', parsed.id),
...optionalField('status', parsed.status),
});
}
function getResponseOutput(response) {
if (!response)
return [];
try {
return Array.isArray(response.output)
? response.output.slice()
: undefined;
}
catch {
return undefined;
}
}
function copyResponseString(response, key) {
try {
const value = response[key];
return typeof value === 'string' && value.length > 0 ? value : undefined;
}
catch {
return undefined;
}
}
function cloneBlockedResponse(response, output) {
let usage = new Usage();
try {
usage = new Usage(response.usage);
}
catch {
// Usage is not replay authority. Keep the replacement data-free.
}
return {
usage,
output,
...optionalField('responseId', copyResponseString(response, 'responseId')),
...optionalField('requestId', copyResponseString(response, 'requestId')),
};
}
function currentResponse(state) {
return state._lastTurnResponse;
}
function functionCallKey(item) {
if (item?.type !== 'function_call')
return undefined;
try {
const parsed = FunctionCallItemSchema.parse(item);
const toolKey = getFunctionToolLookupKeyForCall(parsed);
return toolKey ? JSON.stringify([toolKey, parsed.callId]) : undefined;
}
catch {
return undefined;
}
}
function functionResultKey(item) {
if (item?.type !== 'function_call_result')
return undefined;
try {
const parsed = FunctionCallResultItemSchema.parse(item);
const toolKey = getFunctionToolLookupKeyForCall(parsed);
return toolKey ? JSON.stringify([toolKey, parsed.callId]) : undefined;
}
catch {
return undefined;
}
}
function functionRunItemSignature(item) {
try {
if (item instanceof RunToolCallItem &&
item.rawItem.type === 'function_call') {
return JSON.stringify(buildCanonicalFunctionCall(item.rawItem));
}
if (item instanceof RunToolCallOutputItem &&
item.rawItem.type === 'function_call_result') {
const parsed = FunctionCallResultItemSchema.parse(item.rawItem);
return JSON.stringify({
type: parsed.type,
name: parsed.name,
...optionalField('namespace', parsed.namespace),
callId: parsed.callId,
output: parsed.output,
...optionalField('id', parsed.id),
...optionalField('status', parsed.status),
});
}
}
catch {
return undefined;
}
return undefined;
}
function functionRunItemSequenceIsPrefix(items, prefix) {
return (prefix.length <= items.length &&
prefix.every((item, index) => {
const signature = functionRunItemSignature(item);
return (signature !== undefined &&
signature === functionRunItemSignature(items[index]));
}));
}
function boundedCurrentFunctionResponseStart(generated, responseOutput) {
const responseKeys = responseOutput.map(functionCallKey);
if (responseKeys.length === 0 ||
responseKeys.some((key) => key === undefined)) {
return undefined;
}
const start = generated.length - responseKeys.length * 2;
if (start < 0)
return undefined;
const suffix = generated.slice(start);
const calls = suffix.filter((item) => item instanceof RunToolCallItem && item.rawItem.type === 'function_call');
const results = suffix.filter((item) => item instanceof RunToolCallOutputItem &&
item.rawItem.type === 'function_call_result');
if (calls.length !== responseKeys.length ||
results.length !== responseKeys.length ||
calls.some((item, index) => functionCallKey(item.rawItem) !== responseKeys[index]) ||
results.some((item, index) => functionResultKey(item.rawItem) !== responseKeys[index])) {
return undefined;
}
return start;
}
function restoredCurrentRunItemSelection(state, responseOutput) {
if (state._serializedCurrentStep !== state._currentStep ||
state._currentStep?.type !== 'next_step_interruption') {
return undefined;
}
const generated = state._generatedItems;
const processed = state._lastProcessedResponse?.newItems ?? [];
// A valid schema 1.20 restore relinks these wrappers. Do not fall back to structural
// equality for an approval checkpoint whose serialized ownership was absent or invalid.
const responseKeys = responseOutput.map(functionCallKey);
if (getCurrentResponseToolOutputGuardrailResultStart(state) === undefined ||
processed.length === 0 ||
processed.length !== responseKeys.length ||
responseKeys.some((key) => key === undefined) ||
processed.some((item, index) => !(item instanceof RunToolCallItem) ||
item.rawItem.type !== 'function_call' ||
functionCallKey(item.rawItem) !== responseKeys[index])) {
const fallbackStart = Math.min(state._currentTurnPersistedItemCount, generated.length);
const fallback = generated.slice(fallbackStart);
return { primary: fallback, aliases: fallback, proven: false };
}
const matchingStarts = [];
for (let start = 0; start <= generated.length - processed.length; start += 1) {
if (processed.every((item, offset) => generated[start + offset] === item)) {
matchingStarts.push(start);
}
}
const start = matchingStarts.length === 1 ? matchingStarts[0] : undefined;
const processedEnd = start === undefined ? undefined : start + processed.length;
const interruptions = state._currentStep.data.interruptions;
const interruptionIndexes = interruptions.map((interruption) => generated.flatMap((item, index) => item === interruption ? [index] : []));
if (start === undefined ||
interruptionIndexes.some((indexes, offset) => indexes.length === 0
? !state._ownsNestedApproval(interruptions[offset], interruptions)
: indexes.length !== 1 || indexes[0] < (processedEnd ?? Infinity))) {
const fallbackStart = Math.min(state._currentTurnPersistedItemCount, generated.length);
const fallback = generated.slice(fallbackStart);
return { primary: fallback, aliases: fallback, proven: false };
}
const primary = generated.slice(start);
return { primary, aliases: primary, proven: true };
}
function currentRunItemSelection(state, responseOutput) {
const restoredSelection = restoredCurrentRunItemSelection(state, responseOutput);
if (restoredSelection) {
return restoredSelection;
}
const generated = state._generatedItems.slice();
const responseObjects = new Set(responseOutput);
const processed = state._lastProcessedResponse?.newItems ?? [];
const ownsResponseItem = (item) => {
try {
return ('rawItem' in item && responseObjects.has(item.rawItem));
}
catch {
return false;
}
};
const processedOwnsResponse = processed.some(ownsResponseItem);
const processedItems = new Set(processed);
const processedOwnerStart = generated.findIndex((item) => processedItems.has(item));
const completedInvocationOwners = new Set((state._lastProcessedResponse?.functions ?? []).flatMap((run) => state._completedToolInvocationEvidence
.get(state._currentAgent)
?.get(run.toolCall.callId)?.items ?? []));
const completedInvocationOwnerStart = generated.findIndex((item) => completedInvocationOwners.has(item));
const boundedCurrentStart = boundedCurrentFunctionResponseStart(generated, responseOutput);
let start = generated.findIndex(ownsResponseItem);
if (start < 0 &&
boundedCurrentStart !== undefined &&
(processedOwnerStart >= boundedCurrentStart ||
completedInvocationOwnerStart >= boundedCurrentStart)) {
start = boundedCurrentStart;
}
if (start >= 0) {
const primary = generated.slice(start);
const aliases = [...primary];
const currentProcessedCalls = new Set((state._lastProcessedResponse?.functions ?? []).map((run) => run.toolCall));
let currentProcessedStart = processed.findIndex(ownsResponseItem);
if (currentProcessedStart < 0) {
currentProcessedStart = processed.findIndex((item) => primary.includes(item));
}
if (currentProcessedStart < 0) {
currentProcessedStart = processed.findIndex((item) => item instanceof RunToolCallItem &&
item.rawItem.type === 'function_call' &&
currentProcessedCalls.has(item.rawItem));
}
if (currentProcessedStart >= 0 &&
(processedOwnsResponse ||
processedOwnerStart >= start ||
completedInvocationOwnerStart >= start)) {
for (const item of processed.slice(currentProcessedStart)) {
if (!aliases.includes(item))
aliases.push(item);
}
}
return { primary, aliases, proven: true };
}
if (state._currentTurnBlockedSessionStartIndex !== undefined) {
const blockedStart = Math.min(state._currentTurnBlockedSessionStartIndex, generated.length);
return {
primary: generated.slice(blockedStart),
aliases: generated.slice(blockedStart),
proven: true,
};
}
const fallbackStart = Math.min(state._currentTurnPersistedItemCount, generated.length);
const fallback = generated.slice(fallbackStart);
return { primary: fallback, aliases: fallback, proven: false };
}
function buildCurrentFunctionPairPlan(state, responseOutput, selection, blockedMessage = OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT) {
if (!selection.proven)
return undefined;
try {
const canonicalResponse = responseOutput.map(buildCanonicalFunctionCall);
const responseKeys = canonicalResponse.map((item) => {
const key = functionCallKey(item);
if (!key)
throw new Error();
return key;
});
if (responseKeys.length === 0 ||
new Set(responseKeys).size !== responseKeys.length) {
return undefined;
}
const callKeys = [];
const resultKeys = [];
const callIndexes = new Map();
const resultIndexes = new Map();
const sanitizedItems = [];
for (const [index, item] of selection.primary.entries()) {
if (item instanceof RunToolCallItem &&
item.rawItem.type === 'function_call') {
const key = functionCallKey(item.rawItem);
if (!key || callIndexes.has(key))
return undefined;
callKeys.push(key);
callIndexes.set(key, index);
sanitizedItems.push(new RunToolCallItem(buildCanonicalFunctionCall(item.rawItem), item.agent, item.functionToolStateKey));
continue;
}
if (item instanceof RunToolCallOutputItem &&
item.rawItem.type === 'function_call_result') {
const key = functionResultKey(item.rawItem);
if (!key || resultIndexes.has(key))
return undefined;
resultKeys.push(key);
resultIndexes.set(key, index);
sanitizedItems.push(new RunToolCallOutputItem(buildBlockedToolOutputRawItem(item.rawItem, blockedMessage), item.agent, blockedMessage, undefined, item.executionStatus));
continue;
}
return undefined;
}
if (callKeys.length !== responseKeys.length ||
resultKeys.length !== responseKeys.length ||
callKeys.some((key, index) => key !== responseKeys[index]) ||
resultKeys.some((key, index) => key !== responseKeys[index]) ||
responseKeys.some((key) => callIndexes.get(key) === undefined ||
resultIndexes.get(key) === undefined ||
callIndexes.get(key) >= resultIndexes.get(key))) {
return undefined;
}
const replacements = new Map();
for (const [index, item] of selection.primary.entries()) {
replacements.set(item, sanitizedItems[index]);
}
const processed = state._lastProcessedResponse?.newItems ?? [];
const aliases = new Set(selection.aliases);
let currentProcessedStart = processed.findIndex((item) => aliases.has(item));
if (currentProcessedStart < 0 &&
state._serializedCurrentStep !== undefined &&
state._serializedCurrentStep === state._currentStep &&
functionRunItemSequenceIsPrefix(selection.primary, processed)) {
currentProcessedStart = 0;
for (const item of processed)
aliases.add(item);
}
if (currentProcessedStart >= 0) {
const currentProcessed = processed.slice(currentProcessedStart);
if (currentProcessed.some((item) => !aliases.has(item)) ||
!functionRunItemSequenceIsPrefix(selection.primary, currentProcessed)) {
return undefined;
}
for (const [index, item] of currentProcessed.entries()) {
if (replacements.has(item))
continue;
const replacement = sanitizedItems[index];
if (replacement instanceof RunToolCallItem &&
item instanceof RunToolCallItem) {
replacements.set(item, new RunToolCallItem(replacement.rawItem, item.agent, replacement.functionToolStateKey));
}
else if (replacement instanceof RunToolCallOutputItem &&
item instanceof RunToolCallOutputItem) {
replacements.set(item, new RunToolCallOutputItem(replacement.rawItem, item.agent, blockedMessage, undefined, replacement.executionStatus));
}
else {
return undefined;
}
}
}
return {
responseOutput: sanitizedItems.flatMap((item) => item instanceof RunToolCallItem && item.rawItem.type === 'function_call'
? [item.rawItem]
: []),
replacements,
sanitizedItems,
};
}
catch {
return undefined;
}
}
function replaceRunItems(state, replacements, responseOutput, canonicalResponseOutput) {
const rawReplacements = new Map();
for (const [item, replacement] of replacements) {
if ('rawItem' in item) {
rawReplacements.set(item.rawItem, replacement && 'rawItem' in replacement
? replacement.rawItem
: undefined);
}
}
for (const [index, item] of (responseOutput ?? []).entries()) {
rawReplacements.set(item, canonicalResponseOutput[index]);
}
const replace = (items) => items.flatMap((item) => {
if (!replacements.has(item))
return [item];
const replacement = replacements.get(item);
return replacement ? [replacement] : [];
});
state._generatedItems = replace(state._generatedItems);
if (state._lastProcessedResponse) {
const processed = state._lastProcessedResponse;
processed.newItems = replace(processed.newItems);
const replaceToolActions = (actions, allowRestoredCurrentPosition = false) => actions?.flatMap((action, index) => {
const currentIndex = index - (actions.length - canonicalResponseOutput.length);
if (!rawReplacements.has(action.toolCall) &&
allowRestoredCurrentPosition &&
state._serializedCurrentStep !== undefined &&
state._serializedCurrentStep === state._currentStep &&
action.toolCall.type === 'function_call' &&
currentIndex >= 0 &&
canonicalResponseOutput[currentIndex] &&
JSON.stringify(buildCanonicalFunctionCall(action.toolCall)) ===
JSON.stringify(canonicalResponseOutput[currentIndex])) {
rawReplacements.set(action.toolCall, canonicalResponseOutput[currentIndex]);
}
if (!rawReplacements.has(action.toolCall))
return [action];
const replacement = rawReplacements.get(action.toolCall);
if (!replacement || replacement.type !== action.toolCall.type)
return [];
action.toolCall = replacement;
return [action];
});
if (processed.functions) {
processed.functions = replaceToolActions(processed.functions, true);
}
if (processed.handoffs) {
processed.handoffs = replaceToolActions(processed.handoffs);
}
if (processed.functionToolsNotFound) {
processed.functionToolsNotFound = replaceToolActions(processed.functionToolsNotFound);
}
if (processed.computerActions) {
processed.computerActions = replaceToolActions(processed.computerActions);
}
if (processed.shellActions) {
processed.shellActions = replaceToolActions(processed.shellActions);
}
if (processed.applyPatchActions) {
processed.applyPatchActions = replaceToolActions(processed.applyPatchActions);
}
}
for (const [agent, invocations] of state._completedToolInvocationEvidence) {
for (const [callId, evidence] of invocations) {
const items = replace(evidence.items);
if (items.length !== 2) {
invocations.delete(callId);
state._completedToolInvocations.get(agent)?.delete(callId);
}
else {
evidence.items = items;
}
}
}
}
function replaceCurrentResponse(state, response, output) {
const replacement = cloneBlockedResponse(response, output);
let replacedResponse = false;
state._modelResponses = state._modelResponses.map((candidate) => {
if (candidate !== response)
return candidate;
replacedResponse = true;
return replacement;
});
if (!replacedResponse &&
state._serializedCurrentStep !== undefined &&
state._serializedCurrentStep === state._currentStep &&
state._currentStep?.type === 'next_step_final_output' &&
state._modelResponses.length > 0) {
const archivedResponse = state._modelResponses.at(-1);
try {
if (archivedResponse &&
JSON.stringify(getResponseOutput(archivedResponse)) ===
JSON.stringify(getResponseOutput(response))) {
state._modelResponses[state._modelResponses.length - 1] = replacement;
}
}
catch {
// An unrelated or unreadable archived response is not current ownership.
}
}
if (state._lastTurnResponse === response)
state._lastTurnResponse = replacement;
}
function markBlockedState(state, blockedMessage) {
if (state._currentStep?.type === 'next_step_final_output') {
state._currentStep.output = blockedMessage;
}
invalidateOutputItemNormalization(state._generatedItems);
if (state._lastProcessedResponse) {
invalidateOutputItemNormalization(state._lastProcessedResponse.newItems);
}
}
/** Redacts runner-owned cancellation state without dropping mixed-response tool pairs. */
export function redactCancelledResponseToolOutputs(state, blockedMessage) {
const response = currentResponse(state);
const responseOutput = getResponseOutput(response);
if (response &&
responseOutput &&
responseOutput.some((item) => item.type === 'function_call')) {
const selection = currentRunItemSelection(state, responseOutput);
if (selection.proven) {
// Project the owned live response onto the existing function-pair path.
// Remove non-tool aliases together, before positional pair validation.
const removals = new Map();
for (const item of selection.aliases) {
if (item instanceof RunMessageOutputItem ||
item instanceof RunReasoningItem) {
removals.set(item, undefined);
}
}
replaceRunItems(state, removals, undefined, []);
replaceCurrentResponse(state, response, responseOutput.filter((item) => item.type !== 'message' && item.type !== 'reasoning'));
}
}
return redactBlockedResponseToolOutputs(state, blockedMessage);
}
/** Replaces a rejected function response with allowlisted replay-safe values. */
export function redactBlockedResponseToolOutputs(state, blockedMessage = OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT) {
const response = currentResponse(state);
const responseOutput = getResponseOutput(response);
const selection = currentRunItemSelection(state, responseOutput ?? []);
const plan = response && responseOutput
? buildCurrentFunctionPairPlan(state, responseOutput, selection, blockedMessage)
: undefined;
if (!plan) {
const replacements = new Map();
for (const item of selection.aliases)
replacements.set(item, undefined);
replaceRunItems(state, replacements, responseOutput, []);
if (response)
replaceCurrentResponse(state, response, []);
markBlockedState(state, blockedMessage);
return selection.aliases.length > 0 || response !== undefined;
}
replaceRunItems(state, plan.replacements, responseOutput, plan.responseOutput);
replaceCurrentResponse(state, response, plan.responseOutput);
markBlockedState(state, blockedMessage);
return plan.replacements.size > 0 || plan.responseOutput.length > 0;
}
export function getBlockedOutputSessionSnapshotRunItems(state) {
const responseOutput = getResponseOutput(currentResponse(state));
if (!responseOutput)
return [];
const selection = currentRunItemSelection(state, responseOutput);
const blockedMessage = state._currentStep?.type === 'next_step_final_output' &&
typeof state._currentStep.output === 'string' &&
state._currentStep.output.length > 0
? state._currentStep.output
: OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT;
const plan = buildCurrentFunctionPairPlan(state, responseOutput, selection, blockedMessage);
return plan?.sanitizedItems ?? [];
}
export function isCanonicalBlockedOutputPayload(item) {
return isCanonicalBlockedOutputPayloadForMessage(item, OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT);
}
function isCanonicalBlockedOutputPayloadForMessage(item, blockedMessage) {
if (item?.type !== 'function_call_result')
return false;
try {
return (JSON.stringify(item) ===
JSON.stringify(buildBlockedToolOutputRawItem(item, blockedMessage)));
}
catch {
return false;
}
}
/** Returns a custom blocked message only when the restored response owns its canonical payload. */
function getCanonicalSerializedOutputGuardrailBlockedMessage(state) {
if (state._serializedCurrentStep !== state._currentStep ||
state._currentStep?.type !== 'next_step_final_output' ||
typeof state._currentStep.output !== 'string' ||
state._currentStep.output.length === 0 ||
state._currentStep.output === OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT) {
return undefined;
}
const blockedMessage = state._currentStep.output;
const responseOutput = getResponseOutput(currentResponse(state));
if (!responseOutput)
return undefined;
const selection = currentRunItemSelection(state, responseOutput);
const plan = buildCurrentFunctionPairPlan(state, responseOutput, selection, blockedMessage);
if (!plan ||
JSON.stringify(responseOutput) !== JSON.stringify(plan.responseOutput)) {
return undefined;
}
for (const [item, canonicalItem] of plan.replacements) {
if (item instanceof RunToolCallItem &&
canonicalItem instanceof RunToolCallItem) {
if (JSON.stringify(item.rawItem) !== JSON.stringify(canonicalItem.rawItem)) {
return undefined;
}
continue;
}
if (item instanceof RunToolCallOutputItem &&
canonicalItem instanceof RunToolCallOutputItem) {
if (item.output !== blockedMessage ||
item.customData !== undefined ||
!isCanonicalBlockedOutputPayloadForMessage(item.rawItem, blockedMessage)) {
return undefined;
}
continue;
}
return undefined;
}
return blockedMessage;
}
/** Neutralizes a canonical restored placeholder without trusting serialized guardrail identity. */
export function normalizeSerializedOutputGuardrailBlockedMessage(state) {
const blockedMessage = getCanonicalSerializedOutputGuardrailBlockedMessage(state);
if (!blockedMessage ||
!state._outputGuardrailResults.some((result) => result.agent === state._currentAgent &&
result.agentOutput === blockedMessage &&
result.output.tripwireTriggered === true &&
result.output.outputInfo === undefined)) {
return;
}
const ownedResults = getSerializedOutputGuardrailResults(state, blockedMessage);
// A saved verdict only identifies data to neutralize; current guards must rerun.
redactBlockedResponseToolOutputs(state);
replaceSanitizedOutputGuardrailMessages(state, ownedResults, OUTPUT_GUARDRAIL_BLOCKED_TOOL_OUTPUT);
}
export function hasOutputBearingApprovalCheckpoint(state) {
const responseOutput = getResponseOutput(currentResponse(state));
if (!responseOutput)
return true;
if (responseOutput.some((item) => item.type !== 'function_call'))
return true;
const selection = currentRunItemSelection(state, responseOutput);
if (!selection.proven)
return true;
return selection.primary.some(isOutputBearingApprovalItem);
}
function isOutputBearingApprovalItem(item) {
return (item instanceof RunToolCallOutputItem ||
(item instanceof RunToolCallItem &&
item.rawItem.type === 'hosted_tool_call'));
}
//# sourceMappingURL=blockedOutputPersistence.mjs.map