UNPKG

@openai/agents-core

Version:

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

1,175 lines • 51.1 kB
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RunContext = void 0;
const errors_1 = require("./errors.js");
const logger_1 = __importDefault(require("./logger.js"));
const toolIdentity_1 = require("./toolIdentity.js");
const usage_1 = require("./usage.js");
const toolInvocation_1 = require("./toolInvocation.js");
function cloneApprovalRecord(record) {
    return {
        approved: Array.isArray(record.approved)
            ? [...record.approved]
            : record.approved,
        rejected: Array.isArray(record.rejected)
            ? [...record.rejected]
            : record.rejected,
        ...(record.messages ? { messages: { ...record.messages } } : {}),
        ...(record.stickyRejectMessage !== undefined
            ? { stickyRejectMessage: record.stickyRejectMessage }
            : {}),
    };
}
function cloneApprovalMap(records) {
    return new Map([...records].map(([toolName, record]) => [
        toolName,
        cloneApprovalRecord(record),
    ]));
}
function cloneAgentApprovalMap(records) {
    return new Map([...records].map(([agent, approvals]) => [
        agent,
        cloneApprovalMap(approvals),
    ]));
}
const COMPUTER_APPROVAL_TOOL_NAMES = new Set([
    'computer',
    'computer_use_preview',
]);
function getApprovalToolNameCandidates(toolName, includeFunctionAliases) {
    if (COMPUTER_APPROVAL_TOOL_NAMES.has(toolName)) {
        return toolName === 'computer'
            ? ['computer', 'computer_use_preview']
            : ['computer_use_preview', 'computer'];
    }
    if (includeFunctionAliases) {
        const bareFunctionKey = toolName.startsWith('[')
            ? undefined
            : (0, toolIdentity_1.getFunctionToolLookupKey)(toolName);
        return bareFunctionKey ? [toolName, bareFunctionKey] : [toolName];
    }
    return [toolName];
}
function mergeApprovalList(current, incoming) {
    if (current === true || incoming === true) {
        return true;
    }
    const currentList = Array.isArray(current) ? current : [];
    const incomingList = Array.isArray(incoming) ? incoming : [];
    return Array.from(new Set([...currentList, ...incomingList]));
}
function mergeApprovalMessages(current, incoming) {
    if (current.rejected === true) {
        return current.messages;
    }
    const merged = current.messages || incoming.messages
        ? { ...incoming.messages, ...current.messages }
        : undefined;
    if (!merged) {
        return undefined;
    }
    if (Array.isArray(current.rejected)) {
        for (const callId of current.rejected) {
            if (!current.messages ||
                !Object.prototype.hasOwnProperty.call(current.messages, callId)) {
                delete merged[callId];
            }
        }
    }
    return Object.keys(merged).length > 0 ? merged : undefined;
}
function mergeStickyRejectMessage(current, incoming) {
    if (current.rejected === true) {
        return current.stickyRejectMessage;
    }
    return current.stickyRejectMessage ?? incoming.stickyRejectMessage;
}
function mergeApprovalRecords(current, incoming) {
    const mergedMessages = mergeApprovalMessages(current, incoming);
    const stickyRejectMessage = mergeStickyRejectMessage(current, incoming);
    return {
        approved: mergeApprovalList(current.approved, incoming.approved),
        rejected: mergeApprovalList(current.rejected, incoming.rejected),
        ...(mergedMessages ? { messages: mergedMessages } : {}),
        ...(stickyRejectMessage !== undefined ? { stickyRejectMessage } : {}),
    };
}
/**
 * A context object that is passed to the `Runner.run()` method.
 */
class RunContext {
    /**
     * The context object you passed to the `Runner.run()` method.
     */
    context;
    /**
     * The usage of the agent run so far. For streamed responses, the usage will be updated in real-time
     */
    usage;
    /**
     * Structured input for the current agent tool run, when available.
     */
    toolInput;
    /**
     * A map of tool names to whether they have been approved.
     */
    #approvals;
    /**
     * Hosted MCP approvals scoped by server label and tool name.
     */
    #hostedMcpApprovals;
    /**
     * Function-tool approvals scoped to the public agent that owns the call.
     */
    #functionApprovalState;
    /**
     * Canonical invocation fingerprints for per-call approval decisions.
     */
    #approvalInvocations;
    /**
     * Non-function per-call decisions scoped to the public agent that owns them.
     */
    #toolApprovalsByAgent;
    constructor(context = {}) {
        this.context = context;
        this.usage = new usage_1.Usage();
        this.#approvals = new Map();
        this.#hostedMcpApprovals = new Map();
        this.#functionApprovalState = {
            approvalsByAgent: new Map(),
            legacyApprovals: new Map(),
        };
        this.#approvalInvocations = new Map();
        this.#toolApprovalsByAgent = new Map();
    }
    /**
     * Creates a child context instance for forked runs.
     * Subclasses should override this to preserve custom instance state safely.
     * @internal
     */
    _createFork() {
        return new RunContext(this.context);
    }
    /**
     * Copies shared runtime state into a child context.
     * @internal
     */
    _cloneSharedState(target) {
        target.context = this.context;
        target.usage = this.usage;
        target.#approvals = this.#approvals;
        target.#hostedMcpApprovals = this.#hostedMcpApprovals;
        target.#functionApprovalState = this.#functionApprovalState;
        target.#approvalInvocations = this.#approvalInvocations;
        target.#toolApprovalsByAgent = this.#toolApprovalsByAgent;
        return target;
    }
    /**
     * Creates an isolated context copy for transactional RunState restoration.
     * @internal
     */
    _cloneForRunStateDeserialization() {
        const clone = this._createFork();
        clone.context = this.context;
        clone.usage = this.usage;
        clone.toolInput = this.toolInput;
        clone.#approvals = cloneApprovalMap(this.#approvals);
        clone.#hostedMcpApprovals = cloneApprovalMap(this.#hostedMcpApprovals);
        clone.#functionApprovalState = {
            approvalsByAgent: cloneAgentApprovalMap(this.#functionApprovalState.approvalsByAgent),
            legacyApprovals: cloneApprovalMap(this.#functionApprovalState.legacyApprovals),
        };
        clone.#approvalInvocations = new Map([...this.#approvalInvocations].map(([agent, invocations]) => [
            agent,
            new Map(invocations),
        ]));
        clone.#toolApprovalsByAgent = cloneAgentApprovalMap(this.#toolApprovalsByAgent);
        return clone;
    }
    /**
     * Replaces approval state after a transactional RunState restoration passes.
     * @internal
     */
    _replaceApprovalState(source) {
        this.#approvals.clear();
        for (const [toolName, approval] of source.#approvals) {
            this.#approvals.set(toolName, cloneApprovalRecord(approval));
        }
        this.#hostedMcpApprovals.clear();
        for (const [stateKey, approval] of source.#hostedMcpApprovals) {
            this.#hostedMcpApprovals.set(stateKey, cloneApprovalRecord(approval));
        }
        this.#functionApprovalState.approvalsByAgent.clear();
        for (const [agent, approvals] of source.#functionApprovalState
            .approvalsByAgent) {
            this.#functionApprovalState.approvalsByAgent.set(agent, cloneApprovalMap(approvals));
        }
        this.#functionApprovalState.legacyApprovals.clear();
        for (const [toolName, approval] of source.#functionApprovalState
            .legacyApprovals) {
            this.#functionApprovalState.legacyApprovals.set(toolName, cloneApprovalRecord(approval));
        }
        this.#approvalInvocations.clear();
        for (const [agent, invocations] of source.#approvalInvocations) {
            this.#approvalInvocations.set(agent, new Map(invocations));
        }
        this.#toolApprovalsByAgent.clear();
        for (const [agent, approvals] of source.#toolApprovalsByAgent) {
            this.#toolApprovalsByAgent.set(agent, cloneApprovalMap(approvals));
        }
    }
    /**
     * Rebuild per-call approval bindings from serialized RunState data.
     * @internal
     */
    _rebuildApprovalInvocations(invocations, agentsByIdentity) {
        this.#approvalInvocations = new Map();
        this.#toolApprovalsByAgent = new Map();
        this._mergeApprovalInvocations(invocations, agentsByIdentity);
    }
    /**
     * Merge serialized per-call approval bindings into a live context.
     * @internal
     */
    _mergeApprovalInvocations(invocations, agentsByIdentity) {
        this._validateApprovalInvocations(invocations, agentsByIdentity);
        for (const { agentIdentity, invocations: byCallId, approvals, } of invocations) {
            const agent = agentsByIdentity.get(agentIdentity);
            this.#mergeAgentApprovalInvocations(agent, new Map(Object.entries(byCallId)));
            for (const [toolName, incoming] of Object.entries(approvals)) {
                this.#setToolApprovalRecord(agent, toolName, incoming);
            }
        }
    }
    /**
     * Validate serialized approval-invocation ownership and conflicts without
     * mutating this context.
     * @internal
     */
    _validateApprovalInvocations(invocations, agentsByIdentity) {
        const seenAgentIdentities = new Set();
        for (const { agentIdentity, invocations: byCallId } of invocations) {
            if (!agentsByIdentity.has(agentIdentity) ||
                seenAgentIdentities.has(agentIdentity)) {
                throw new errors_1.UserError('RunState contains invalid approval invocation ownership for the current agent graph.');
            }
            seenAgentIdentities.add(agentIdentity);
            const agent = agentsByIdentity.get(agentIdentity);
            for (const [callId, fingerprint] of Object.entries(byCallId)) {
                this._validateAgentApprovalInvocation(agent, callId, fingerprint);
            }
        }
    }
    /**
     * Bind a legacy per-call decision to its serialized approval item.
     * @internal
     */
    _bindLegacyApprovalInvocation(approvalItem) {
        const callId = (0, toolInvocation_1.getToolInvocationCallId)(approvalItem.rawItem);
        const agentInvocations = this.#getApprovalInvocations(approvalItem.agent);
        if (!callId || agentInvocations.has(callId)) {
            return;
        }
        const toolName = this.#getApprovalItemToolName(approvalItem);
        const decision = this.isToolApproved({
            toolName,
            callId,
            functionTool: false,
            ...(approvalItem.rawItem.type === 'function_call'
                ? { agent: approvalItem.agent }
                : {}),
        });
        if (decision !== undefined) {
            agentInvocations.set(callId, (0, toolInvocation_1.getToolInvocationFingerprint)(toolName, approvalItem.rawItem));
            if (approvalItem.rawItem.type !== 'function_call') {
                this.#copyPerCallToolApproval(approvalItem.agent, toolName, callId);
            }
        }
    }
    /**
     * Fail closed when an agent-local call ID with a per-call decision is reused.
     * @internal
     */
    _validateAgentApprovalInvocation(agent, callId, fingerprint) {
        const approvedFingerprint = this.#approvalInvocations
            .get(agent)
            ?.get(callId);
        if (approvedFingerprint !== undefined &&
            approvedFingerprint !== fingerprint) {
            throw new errors_1.ModelBehaviorError(`Tool call ID ${callId} was reused for a different invocation after an approval decision.`);
        }
    }
    /**
     * Validate a canonical tool invocation before approval lookup or execution.
     * @internal
     */
    _validateToolInvocation(agent, toolName, rawItem) {
        const callId = (0, toolInvocation_1.getToolInvocationCallId)(rawItem);
        if (!callId) {
            throw new errors_1.ModelBehaviorError('Tool invocation is missing a non-empty call ID.');
        }
        const fingerprint = (0, toolInvocation_1.getToolInvocationFingerprint)(toolName, rawItem);
        this._validateAgentApprovalInvocation(agent, callId, fingerprint);
        return { callId, fingerprint };
    }
    /**
     * Resolve a decision only when its per-call binding belongs to this agent.
     * Sticky tool-wide decisions remain shared.
     * @internal
     */
    _resolveToolInvocationApproval(agent, toolName, rawItem) {
        const { callId, fingerprint } = this._validateToolInvocation(agent, toolName, rawItem);
        if (rawItem.type === 'function_call') {
            return this.#resolveFunctionApprovalEntryGroups(agent, toolName, callId, this.#approvalInvocations.get(agent)?.get(callId) === fingerprint)?.decision;
        }
        const entries = this.#getToolApprovalEntries(toolName, agent);
        if (this.#approvalInvocations.get(agent)?.get(callId) === fingerprint) {
            const exactDecision = this.#resolveExactApprovalEntries(entries, callId);
            if (exactDecision !== undefined) {
                return exactDecision;
            }
        }
        const stickyEntries = (0, toolIdentity_1.getHostedMcpApprovalRequestIdentity)(rawItem)
            ? this.#getHostedMcpApprovalEntries(toolName)
            : this.#getApprovalEntries(toolName, false);
        return this.#resolveStickyApprovalEntries(stickyEntries);
    }
    /**
     * Resolve a rejection message only from the owning per-call binding or a
     * sticky tool-wide rejection.
     * @internal
     */
    _getToolInvocationRejectionMessage(agent, toolName, rawItem) {
        if (rawItem.type === 'function_call') {
            const { callId, fingerprint } = this._validateToolInvocation(agent, toolName, rawItem);
            const resolution = this.#resolveFunctionApprovalEntryGroups(agent, toolName, callId, this.#approvalInvocations.get(agent)?.get(callId) === fingerprint);
            if (resolution?.decision !== false) {
                return undefined;
            }
            return this.#getRejectionMessageForRejectedCall(resolution.entries, callId);
        }
        const decision = this._resolveToolInvocationApproval(agent, toolName, rawItem);
        if (decision !== false) {
            return undefined;
        }
        const callId = (0, toolInvocation_1.getToolInvocationCallId)(rawItem);
        const entries = this.#getToolApprovalEntries(toolName, agent);
        const stickyEntries = (0, toolIdentity_1.getHostedMcpApprovalRequestIdentity)(rawItem)
            ? this.#getHostedMcpApprovalEntries(toolName)
            : this.#getApprovalEntries(toolName, false);
        return this.#getRejectionMessageForRejectedCall([...entries, ...stickyEntries], callId);
    }
    /**
     * Rebuild the approvals map from a serialized state.
     * @internal
     *
     * @param approvals - The approvals map to rebuild.
     */
    _rebuildApprovals(approvals) {
        this.#approvals = new Map();
        for (const [toolName, approval] of Object.entries(approvals)) {
            this.#setApprovalRecord(toolName, approval);
        }
    }
    /**
     * Merge approvals from a serialized state without discarding existing entries.
     * @internal
     *
     * @param approvals - The approvals map to merge.
     */
    _mergeApprovals(approvals) {
        for (const [toolName, incoming] of Object.entries(approvals)) {
            this.#setApprovalRecord(toolName, incoming);
        }
    }
    /** @internal */
    _rebuildHostedMcpApprovals(approvals) {
        this.#hostedMcpApprovals = new Map();
        this._mergeHostedMcpApprovals(approvals);
    }
    /** @internal */
    _mergeHostedMcpApprovals(approvals) {
        for (const [stateKey, incoming] of Object.entries(approvals)) {
            if (!(0, toolIdentity_1.getHostedMcpApprovalIdentityFromStateKey)(stateKey)) {
                continue;
            }
            const current = this.#hostedMcpApprovals.get(stateKey);
            this.#hostedMcpApprovals.set(stateKey, current ? mergeApprovalRecords(current, incoming) : incoming);
        }
    }
    /**
     * Rebuild owner-scoped function approvals from serialized RunState data.
     * @internal
     */
    _rebuildFunctionApprovals(approvals, agentsByIdentity) {
        this.#functionApprovalState.approvalsByAgent = new Map();
        this._mergeFunctionApprovals(approvals, agentsByIdentity);
    }
    /**
     * Rebuild legacy function-only approvals from serialized RunState data.
     * @internal
     */
    _rebuildLegacyFunctionApprovals(approvals) {
        this.#functionApprovalState.legacyApprovals = new Map();
        this._mergeLegacyFunctionApprovals(approvals);
    }
    /**
     * Merge legacy function-only approvals from serialized RunState data.
     * @internal
     */
    _mergeLegacyFunctionApprovals(approvals) {
        for (const [toolName, incoming] of Object.entries(approvals)) {
            this.#setLegacyFunctionApprovalRecord(toolName, incoming);
        }
    }
    /**
     * Retain only legacy approvals proven to belong to enabled function tools.
     * @internal
     */
    _retainLegacyFunctionApprovals(toolNames) {
        for (const toolName of this.#functionApprovalState.legacyApprovals.keys()) {
            if (!toolNames.has(toolName)) {
                this.#functionApprovalState.legacyApprovals.delete(toolName);
            }
        }
    }
    /**
     * Remove migrated function-only keys from the non-function aggregate map.
     * @internal
     */
    _removeMigratedFunctionApprovalsFromAggregate(functionToolNames, exactNonFunctionToolNames) {
        for (const toolName of functionToolNames) {
            if (!exactNonFunctionToolNames.has(toolName)) {
                this.#approvals.delete(toolName);
            }
        }
    }
    /**
     * Merge owner-scoped function approvals from serialized RunState data.
     * @internal
     */
    _mergeFunctionApprovals(approvals, agentsByIdentity) {
        this._validateFunctionApprovalOwners(approvals, agentsByIdentity);
        for (const { agentIdentity, approvals: approvalsByTool } of approvals) {
            const agent = agentsByIdentity.get(agentIdentity);
            for (const [toolName, incoming] of Object.entries(approvalsByTool)) {
                const current = this.#getFunctionApprovalMap(agent).get(toolName);
                // A saved decision must not override a current permanent decision,
                // including its per-call exceptions and rejection messages.
                if (current?.approved === true || current?.rejected === true) {
                    continue;
                }
                this.#setFunctionApprovalRecord(agent, toolName, incoming);
            }
        }
    }
    /**
     * Validate serialized function-approval owners without mutating this context.
     * @internal
     */
    _validateFunctionApprovalOwners(approvals, agentsByIdentity) {
        const seenAgentIdentities = new Set();
        for (const { agentIdentity } of approvals) {
            if (seenAgentIdentities.has(agentIdentity) ||
                !agentsByIdentity.has(agentIdentity)) {
                throw new errors_1.UserError('RunState contains invalid function approval ownership for the current agent graph.');
            }
            seenAgentIdentities.add(agentIdentity);
        }
    }
    /**
     * Merge all approval state from another live context.
     * @internal
     */
    _mergeApprovalState(source) {
        this.#mergeLiveApprovalInvocations(source.#approvalInvocations);
        this.#mergeLiveToolApprovals(source.#toolApprovalsByAgent);
        for (const [toolName, incoming] of source.#approvals) {
            this.#setApprovalRecord(toolName, incoming);
        }
        for (const [stateKey, incoming] of source.#hostedMcpApprovals) {
            this._mergeHostedMcpApprovals({ [stateKey]: incoming });
        }
        for (const [agent, approvalsByTool] of source.#functionApprovalState
            .approvalsByAgent) {
            for (const [toolName, incoming] of approvalsByTool) {
                this.#setFunctionApprovalRecord(agent, toolName, incoming);
            }
        }
        for (const [toolName, incoming] of source.#functionApprovalState
            .legacyApprovals) {
            this.#setLegacyFunctionApprovalRecord(toolName, incoming);
        }
    }
    /**
     * Merges canonicalized legacy approvals without replacing existing exact
     * non-function records that use the same legacy name.
     * @internal
     */
    _mergeApprovalStatePreservingExactKeys(source, exactToolNames) {
        this.#mergeLiveApprovalInvocations(source.#approvalInvocations);
        this.#mergeLiveToolApprovals(source.#toolApprovalsByAgent);
        for (const [toolName, incoming] of source.#approvals) {
            if (exactToolNames.has(toolName) && this.#approvals.has(toolName)) {
                continue;
            }
            this.#setApprovalRecord(toolName, incoming);
        }
        for (const [stateKey, incoming] of source.#hostedMcpApprovals) {
            this._mergeHostedMcpApprovals({ [stateKey]: incoming });
        }
        for (const [agent, approvalsByTool] of source.#functionApprovalState
            .approvalsByAgent) {
            for (const [toolName, incoming] of approvalsByTool) {
                this.#setFunctionApprovalRecord(agent, toolName, incoming);
            }
        }
        for (const [toolName, incoming] of source.#functionApprovalState
            .legacyApprovals) {
            this.#setLegacyFunctionApprovalRecord(toolName, incoming);
        }
    }
    /**
     * Moves a legacy function-tool approval to an exact category-aware key.
     * @internal
     */
    _migrateToolApproval(agent, legacyToolName, canonicalToolName, callIds, migratePermanentDecision, preserveMovedCallDecisions = false) {
        if (legacyToolName === canonicalToolName) {
            return;
        }
        const legacy = this.#functionApprovalState.legacyApprovals.get(legacyToolName);
        if (!legacy) {
            return;
        }
        const callIdSet = new Set(callIds);
        const movedApproved = legacy.approved === true
            ? migratePermanentDecision
                ? true
                : [...callIdSet]
            : Array.isArray(legacy.approved)
                ? legacy.approved.filter((callId) => callIdSet.has(callId))
                : [];
        const movedRejected = legacy.rejected === true
            ? migratePermanentDecision
                ? true
                : [...callIdSet]
            : Array.isArray(legacy.rejected)
                ? legacy.rejected.filter((callId) => callIdSet.has(callId))
                : [];
        let movedMessages = legacy.messages
            ? Object.fromEntries(Object.entries(legacy.messages).filter(([callId]) => callIdSet.has(callId)))
            : undefined;
        if (legacy.rejected === true &&
            !migratePermanentDecision &&
            legacy.stickyRejectMessage !== undefined) {
            for (const callId of callIdSet) {
                (movedMessages ??= {})[callId] ??= legacy.stickyRejectMessage;
            }
        }
        const moved = {
            approved: movedApproved,
            rejected: movedRejected,
            ...(movedMessages && Object.keys(movedMessages).length > 0
                ? { messages: movedMessages }
                : {}),
            ...(migratePermanentDecision && legacy.stickyRejectMessage !== undefined
                ? { stickyRejectMessage: legacy.stickyRejectMessage }
                : {}),
        };
        const hasMovedDecision = moved.approved === true ||
            moved.rejected === true ||
            (Array.isArray(moved.approved) && moved.approved.length > 0) ||
            (Array.isArray(moved.rejected) && moved.rejected.length > 0);
        if (!hasMovedDecision) {
            return;
        }
        this.#setFunctionApprovalRecord(agent, canonicalToolName, moved);
        const remainingApproved = legacy.approved === true
            ? migratePermanentDecision
                ? []
                : true
            : Array.isArray(legacy.approved)
                ? legacy.approved.filter((callId) => preserveMovedCallDecisions || !callIdSet.has(callId))
                : [];
        const remainingRejected = legacy.rejected === true
            ? migratePermanentDecision
                ? []
                : true
            : Array.isArray(legacy.rejected)
                ? legacy.rejected.filter((callId) => preserveMovedCallDecisions || !callIdSet.has(callId))
                : [];
        const remainingMessages = legacy.messages
            ? Object.fromEntries(Object.entries(legacy.messages).filter(([callId]) => preserveMovedCallDecisions || !callIdSet.has(callId)))
            : undefined;
        const remaining = {
            approved: remainingApproved,
            rejected: remainingRejected,
            ...(remainingMessages && Object.keys(remainingMessages).length > 0
                ? { messages: remainingMessages }
                : {}),
            ...(!migratePermanentDecision && legacy.stickyRejectMessage !== undefined
                ? { stickyRejectMessage: legacy.stickyRejectMessage }
                : {}),
        };
        const hasRemainingDecision = remaining.approved === true ||
            remaining.rejected === true ||
            (Array.isArray(remaining.approved) && remaining.approved.length > 0) ||
            (Array.isArray(remaining.rejected) && remaining.rejected.length > 0);
        if (hasRemainingDecision) {
            this.#functionApprovalState.legacyApprovals.set(legacyToolName, remaining);
        }
        else {
            this.#functionApprovalState.legacyApprovals.delete(legacyToolName);
        }
    }
    /**
     * Retrieve the caller-provided rejection message for a specific tool call.
     *
     * @param toolName - The name of the tool.
     * @param callId - The call ID of the tool invocation.
     * @returns The message string if one was provided, `undefined` otherwise.
     */
    getRejectionMessage(toolName, callId, options = {}) {
        const functionTool = options.functionTool ?? true;
        const includeFunctionState = functionTool ||
            (0, toolIdentity_1.getFunctionToolLegacyStateKeyFromStateKey)(toolName) !== undefined;
        return this.#getRejectionMessageFromEntries([
            ...this.#getApprovalEntries(toolName, functionTool),
            ...(includeFunctionState
                ? [
                    ...this.#getLegacyFunctionApprovalEntries(toolName),
                    ...this.#getFunctionApprovalEntries(toolName, functionTool),
                ]
                : []),
        ], callId);
    }
    /**
     * Retrieve a rejection message for the agent that owns a function call.
     * @internal
     */
    _getFunctionRejectionMessage(toolName, callId, agent) {
        const resolution = this.#resolveFunctionApprovalEntryGroups(agent, toolName, callId);
        if (resolution?.decision !== false) {
            return undefined;
        }
        return this.#getRejectionMessageForRejectedCall(resolution.entries, callId);
    }
    /**
     * Serialize public RunContext state using the released aggregate shape.
     */
    toJSON() {
        return this.#serialize();
    }
    /**
     * Serialize RunContext state with stable function-approval ownership.
     * @internal
     */
    _toJSONForRunState(agentIdentityKeys) {
        return this.#serialize(agentIdentityKeys);
    }
    #serialize(agentIdentityKeys) {
        const approvals = new Map(this.#approvals);
        if (!agentIdentityKeys) {
            for (const [stateKey, incoming] of this.#hostedMcpApprovals) {
                const identity = (0, toolIdentity_1.getHostedMcpApprovalIdentityFromStateKey)(stateKey);
                if (!identity?.toolName) {
                    continue;
                }
                const current = approvals.get(identity.toolName);
                approvals.set(identity.toolName, current ? mergeApprovalRecords(current, incoming) : incoming);
            }
            for (const [toolName, incoming] of this.#functionApprovalState
                .legacyApprovals) {
                const current = approvals.get(toolName);
                approvals.set(toolName, current ? mergeApprovalRecords(current, incoming) : incoming);
            }
            for (const approvalsByTool of this.#functionApprovalState.approvalsByAgent.values()) {
                for (const [toolName, incoming] of approvalsByTool) {
                    const publicToolName = (0, toolIdentity_1.getFunctionToolLegacyStateKeyFromStateKey)(toolName) ?? toolName;
                    const current = approvals.get(publicToolName);
                    approvals.set(publicToolName, current ? mergeApprovalRecords(current, incoming) : incoming);
                }
            }
        }
        const json = {
            context: this.context,
            usage: this.usage,
            approvals: Object.fromEntries(approvals.entries()),
        };
        if (agentIdentityKeys && this.#hostedMcpApprovals.size > 0) {
            json.hostedMcpApprovals = Object.fromEntries(this.#hostedMcpApprovals);
        }
        if (agentIdentityKeys) {
            const functionApprovals = [];
            for (const [agent, approvalsByTool] of this.#functionApprovalState
                .approvalsByAgent) {
                const agentIdentity = agentIdentityKeys.get(agent);
                if (!agentIdentity) {
                    // Nested agent runs share their parent's context. Only persist
                    // approvals owned by agents reachable from this RunState graph.
                    continue;
                }
                functionApprovals.push({
                    agentIdentity,
                    approvals: Object.fromEntries(approvalsByTool),
                });
            }
            if (functionApprovals.length > 0) {
                json.functionApprovals = functionApprovals;
            }
            if (this.#functionApprovalState.legacyApprovals.size > 0) {
                json.legacyFunctionApprovals = Object.fromEntries(this.#functionApprovalState.legacyApprovals);
            }
            const approvalInvocations = [];
            const approvalAgents = new Set([
                ...this.#approvalInvocations.keys(),
                ...this.#toolApprovalsByAgent.keys(),
            ]);
            for (const agent of approvalAgents) {
                const invocations = this.#approvalInvocations.get(agent) ?? new Map();
                const approvalsByTool = this.#toolApprovalsByAgent.get(agent) ?? new Map();
                const agentIdentity = agentIdentityKeys.get(agent);
                if (agentIdentity &&
                    (invocations.size > 0 || approvalsByTool.size > 0)) {
                    approvalInvocations.push({
                        agentIdentity,
                        invocations: Object.fromEntries(invocations),
                        approvals: Object.fromEntries(approvalsByTool),
                    });
                }
            }
            json.approvalInvocations = approvalInvocations;
        }
        if (typeof this.toolInput !== 'undefined') {
            json.toolInput = this.toolInput;
        }
        return json;
    }
    #getCallId(approvalItem) {
        const hostedIdentity = (0, toolIdentity_1.getHostedMcpApprovalRequestIdentity)(approvalItem);
        if (hostedIdentity?.requestId) {
            return hostedIdentity.requestId;
        }
        if ('callId' in approvalItem.rawItem) {
            return approvalItem.rawItem.callId;
        }
        const providerData = approvalItem.rawItem.providerData;
        return (approvalItem.rawItem.id ?? providerData?.itemId ?? providerData?.id ?? '');
    }
    /**
     * Check if a tool call has been approved.
     *
     * @param approval - Details about the tool call being evaluated.
     * @returns `true` if the tool call has been approved, `false` if blocked and `undefined` if not yet approved or rejected.
     */
    isToolApproved(approval) {
        const { toolName, callId, functionTool = true, agent } = approval;
        if (agent) {
            return this.#resolveFunctionApprovalEntryGroups(agent, toolName, callId, true, functionTool)?.decision;
        }
        const includeFunctionState = functionTool ||
            (0, toolIdentity_1.getFunctionToolLegacyStateKeyFromStateKey)(toolName) !== undefined;
        return this.#resolveApprovalEntries([
            ...this.#getApprovalEntries(toolName, functionTool),
            ...(includeFunctionState
                ? this.#getLegacyFunctionApprovalEntries(toolName)
                : []),
            ...(!agent && includeFunctionState
                ? this.#getFunctionApprovalEntries(toolName, functionTool)
                : []),
        ], callId);
    }
    /** @internal */
    _getHostedMcpApprovalStatus(approvalItem) {
        const identity = (0, toolIdentity_1.getHostedMcpApprovalRequestIdentity)(approvalItem);
        const stateKey = identity
            ? (0, toolIdentity_1.getHostedMcpApprovalStateKey)(identity)
            : undefined;
        if (!identity?.requestId || !stateKey) {
            return undefined;
        }
        return this.#resolveApprovalEntries(this.#getHostedMcpApprovalEntries(stateKey), identity.requestId);
    }
    /** @internal */
    _getHostedMcpRejectionMessage(approvalItem) {
        const identity = (0, toolIdentity_1.getHostedMcpApprovalRequestIdentity)(approvalItem);
        const stateKey = identity
            ? (0, toolIdentity_1.getHostedMcpApprovalStateKey)(identity)
            : undefined;
        if (!identity?.requestId || !stateKey) {
            return undefined;
        }
        return this.#getRejectionMessageFromEntries(this.#getHostedMcpApprovalEntries(stateKey), identity.requestId);
    }
    #resolveApprovalEntries(approvalEntries, callId) {
        return (this.#resolveExactApprovalEntries(approvalEntries, callId) ??
            this.#resolveStickyApprovalEntries(approvalEntries));
    }
    #resolveExactApprovalEntries(approvalEntries, callId) {
        const individualCallApproval = approvalEntries.some((approvalEntry) => Array.isArray(approvalEntry.approved)
            ? approvalEntry.approved.includes(callId)
            : false);
        const individualCallRejection = approvalEntries.some((approvalEntry) => Array.isArray(approvalEntry.rejected)
            ? approvalEntry.rejected.includes(callId)
            : false);
        if (individualCallApproval && individualCallRejection) {
            logger_1.default.warn(`Tool call ${callId} is both approved and rejected at the same time. Approval takes precedence`);
            return true;
        }
        if (individualCallApproval) {
            return true;
        }
        if (individualCallRejection) {
            return false;
        }
        return undefined;
    }
    #resolveApprovalEntryGroups(approvalEntryGroups, callId, includeExactDecisions = true) {
        if (includeExactDecisions) {
            for (const entries of approvalEntryGroups) {
                const decision = this.#resolveExactApprovalEntries(entries, callId);
                if (decision !== undefined) {
                    return { decision, entries };
                }
            }
        }
        for (const entries of approvalEntryGroups) {
            const decision = this.#resolveStickyApprovalEntries(entries);
            if (decision !== undefined) {
                return { decision, entries };
            }
        }
        return undefined;
    }
    #resolveFunctionApprovalEntryGroups(agent, toolName, callId, includeExactDecisions = true, includeFunctionAliases = false) {
        return this.#resolveApprovalEntryGroups([
            this.#getFunctionApprovalEntries(toolName, includeFunctionAliases, agent),
            this.#getLegacyFunctionApprovalEntries(toolName),
        ], callId, includeExactDecisions);
    }
    #resolveStickyApprovalEntries(approvalEntries) {
        const hasPermanentApproval = approvalEntries.some((approvalEntry) => approvalEntry.approved === true);
        const hasPermanentRejection = approvalEntries.some((approvalEntry) => approvalEntry.rejected === true);
        if (hasPermanentApproval && hasPermanentRejection) {
            logger_1.default.warn('Tool is permanently approved and rejected at the same time. Approval takes precedence');
            return true;
        }
        if (hasPermanentApproval) {
            return true;
        }
        if (hasPermanentRejection) {
            return false;
        }
        return undefined;
    }
    #getRejectionMessageFromEntries(approvalEntries, callId) {
        if (this.#resolveExactApprovalEntries(approvalEntries, callId) === true) {
            return undefined;
        }
        return this.#getRejectionMessageForRejectedCall(approvalEntries, callId);
    }
    #getRejectionMessageForRejectedCall(approvalEntries, callId) {
        for (const approvalEntry of approvalEntries) {
            if (typeof approvalEntry.messages?.[callId] === 'string') {
                return approvalEntry.messages[callId];
            }
        }
        for (const approvalEntry of approvalEntries) {
            if (approvalEntry.rejected === true) {
                return approvalEntry.stickyRejectMessage;
            }
        }
        return undefined;
    }
    /**
     * Approve a tool call.
     *
     * @param approvalItem - The tool approval item to approve.
     * @param options - Additional approval behavior options.
     */
    approveTool(approvalItem, { alwaysApprove = false } = {}) {
        const toolName = this.#getApprovalItemStorageKey(approvalItem, alwaysApprove);
        const isFunctionCall = approvalItem.rawItem.type === 'function_call';
        const approvals = isFunctionCall
            ? this.#getFunctionApprovalMap(approvalItem.agent)
            : (0, toolIdentity_1.getHostedMcpApprovalRequestIdentity)(approvalItem)
                ? this.#hostedMcpApprovals
                : this.#approvals;
        const approvalKey = this.#getApprovalStorageKey(toolName, isFunctionCall, approvals);
        const callId = this.#getCallId(approvalItem);
        this.#bindApprovalInvocation(toolName, approvalItem);
        if (alwaysApprove) {
            approvals.set(approvalKey, {
                approved: true,
                rejected: [],
            });
            return;
        }
        this.#recordPerCallApproval(approvals, approvalKey, callId, true);
        if (!isFunctionCall) {
            const scopedApprovals = this.#getToolApprovalMap(approvalItem.agent);
            const scopedKey = this.#getApprovalStorageKey(toolName, false, scopedApprovals);
            this.#recordPerCallApproval(scopedApprovals, scopedKey, callId, true);
        }
    }
    /**
     * Reject a tool call.
     *
     * @param approvalItem - The tool approval item to reject.
     */
    rejectTool(approvalItem, { alwaysReject = false, message, } = {}) {
        const toolName = this.#getApprovalItemStorageKey(approvalItem, alwaysReject);
        const isFunctionCall = approvalItem.rawItem.type === 'function_call';
        const approvals = isFunctionCall
            ? this.#getFunctionApprovalMap(approvalItem.agent)
            : (0, toolIdentity_1.getHostedMcpApprovalRequestIdentity)(approvalItem)
                ? this.#hostedMcpApprovals
                : this.#approvals;
        const approvalKey = this.#getApprovalStorageKey(toolName, isFunctionCall, approvals);
        const callId = this.#getCallId(approvalItem);
        this.#bindApprovalInvocation(toolName, approvalItem);
        if (alwaysReject) {
            approvals.set(approvalKey, {
                approved: false,
                rejected: true,
                ...(message !== undefined
                    ? {
                        messages: { [callId]: message },
                        stickyRejectMessage: message,
                    }
                    : {}),
            });
            return;
        }
        this.#recordPerCallApproval(approvals, approvalKey, callId, false, message);
        if (!isFunctionCall) {
            const scopedApprovals = this.#getToolApprovalMap(approvalItem.agent);
            const scopedKey = this.#getApprovalStorageKey(toolName, false, scopedApprovals);
            this.#recordPerCallApproval(scopedApprovals, scopedKey, callId, false, message);
        }
    }
    #recordPerCallApproval(approvals, approvalKey, callId, approved, message) {
        const approvalEntry = approvals.get(approvalKey) ?? {
            approved: [],
            rejected: [],
        };
        const opposite = approved ? approvalEntry.rejected : approvalEntry.approved;
        if (Array.isArray(opposite)) {
            const oppositeIndex = opposite.indexOf(callId);
            if (oppositeIndex !== -1) {
                opposite.splice(oppositeIndex, 1);
            }
        }
        let decisions = approved ? approvalEntry.approved : approvalEntry.rejected;
        if (decisions !== true) {
            if (!Array.isArray(decisions)) {
                decisions = [];
                if (approved) {
                    approvalEntry.approved = decisions;
                }
                else {
                    approvalEntry.rejected = decisions;
                }
            }
            if (!decisions.includes(callId)) {
                decisions.push(callId);
            }
        }
        if (approved) {
            if (approvalEntry.messages) {
                delete approvalEntry.messages[callId];
                if (Object.keys(approvalEntry.messages).length === 0) {
                    delete approvalEntry.messages;
                }
            }
        }
        else if (message !== undefined) {
            approvalEntry.messages = approvalEntry.messages ?? {};
            approvalEntry.messages[callId] = message;
        }
        approvals.set(approvalKey, approvalEntry);
    }
    #bindApprovalInvocation(toolName, approvalItem) {
        const { callId, fingerprint } = this._validateToolInvocation(approvalItem.agent, toolName, approvalItem.rawItem);
        this.#getApprovalInvocations(approvalItem.agent).set(callId, fingerprint);
    }
    #getApprovalInvocations(agent) {
        const existing = this.#approvalInvocations.get(agent);
        if (existing) {
            return existing;
        }
        const invocations = new Map();
        this.#approvalInvocations.set(agent, invocations);
        return invocations;
    }
    #mergeAgentApprovalInvocations(agent, incoming) {
        for (const [callId, fingerprint] of incoming) {
            this._validateAgentApprovalInvocation(agent, callId, fingerprint);
            this.#getApprovalInvocations(agent).set(callId, fingerprint);
        }
    }
    #mergeLiveApprovalInvocations(incoming) {
        for (const [agent, invocations] of incoming) {
            this.#mergeAgentApprovalInvocations(agent, invocations);
        }
    }
    #mergeLiveToolApprovals(incoming) {
        for (const [agent, approvalsByTool] of incoming) {
            for (const [toolName, approval] of approvalsByTool) {
                this.#setToolApprovalRecord(agent, toolName, approval);
            }
        }
    }
    /**
     * Creates a child context that shares approvals and usage, with tool input set.
     * @internal
     */
    _forkWithToolInput(toolInput) {
        const fork = this._cloneSharedState(this._createFork());
        fork.toolInput = toolInput;
        return fork;
    }
    /**
     * Creates a child context that shares approvals and usage, without tool input.
     * @internal
     */
    _forkWithoutToolInput() {
        const fork = this._cloneSharedState(this._createFork());
        fork.toolInput = undefined;
        return fork;
    }
    #getApprovalEntries(toolName, includeFunctionAliases) {
        return getApprovalToolNameCandidates(toolName, includeFunctionAliases)
            .map((candidate) => this.#approvals.get(candidate))
            .filter((approval) => approval !== undefined);
    }
    #getToolApprovalEntries(toolName, agent) {
        const approvalsByTool = this.#toolApprovalsByAgent.get(agent);
        if (!approvalsByTool) {
            return [];
        }
        return getApprovalToolNameCandidates(toolName, false)
            .map((candidate) => approvalsByTool.get(candidate))
            .filter((approval) => approval !== undefined);
    }
    #getHostedMcpApprovalEntries(stateKey) {
        const approval = this.#hostedMcpApprovals.get(stateKey);
        return approval ? [approval] : [];
    }
    #getToolApprovalMap(agent) {
        const existing = this.#toolApprovalsByAgent.get(agent);
        if (existing) {
            return existing;
        }
        const approvals = new Map();
        this.#toolApprovalsByAgent.set(agent, approvals);
        return approvals;
    }
    #copyPerCallToolApproval(agent, toolName, callId) {
        const sourceEntries = this.#getApprovalEntries(toolName, false);
        const approved = sourceEntries.some((entry) => Array.isArray(entry.approved) && entry.approved.includes(callId));
        const rejected = sourceEntries.some((entry) => Array.isArray(entry.rejected) && entry.rejected.includes(callId));
        if (!approved && !rejected) {
            return;
        }
        const approvals = this.#getToolApprovalMap(agent);
        const approvalKey = this.#getApprovalStorageKey(toolName, false, approvals);
        if (approved) {
            this.#recordPerCallApproval(approvals, approvalKey, callId, true);
        }
        if (rejected) {
            this.#recordPerCallApproval(approvals, approvalKey, callId, false, this.#getRejectionMessageFromEntries(sourceEntries, callId));
        }
    }
    #getFunctionApprovalEntries(toolName, includeFunctionAliases, agent) {
        const candidates = getApprovalToolNameCandidates(toolName, includeFunctionAliases);
        const approvalMaps = agent
            ? [this.#functionApprovalState.approvalsByAgent.get(agent)]
            : [...this.#functionApprovalState.approvalsByAgent.values()];
        return approvalMaps.flatMap((approvalsByTool) => approvalsByTool
            ? candidates
                .map((candidate) => approvalsByTool.get(candidate))
                .filter((approval) => approval !== undefined)
            : []);
    }
    #getLegacyFunctionApprovalEntries(toolName) {
        const legacyToolName = (0, toolIdentity_1.getFunctionToolLegacyStateKeyFromStateKey)(toolName) ?? toolName;
        return getApprovalToolNameCandidates(legacyToolName, true)
            .map((candidate) => this.#functionApprovalState.legacyApprovals.get(candidate))
            .filter((approval) => approval !== undefined);
    }
    #getFunctionApprovalMap(agent) {
        const existing = this.#functionApprovalState.approvalsByAgent.get(agent);
        if (existing) {
            return existing;
        }
        const approvals = new Map();
        this.#functionApprovalState.approvalsByAgent.set(agent, approvals);
        return approvals;
    }
    #getApprovalItemToolName(approvalItem) {
        const fallbackName = approvalItem.toolName ?? approvalItem.rawItem.name;
        if (approvalItem.rawItem.type !== 'function_call') {
            return fallbackName;
        }
        return (approvalItem.functionToolStateKey ??
            (0, toolIdentity_1.getFunctionToolStateKeyForCall)(approvalItem.rawItem, fallbackName) ??
            fallbackName);
    }
    #getApprovalItemStorageKey(approvalItem, persistent) {
        const hostedIdentity = (0, toolIdentity_1.getHostedMcpApprovalRequestIdentity)(approvalItem);
        if (!hostedIdentity) {
            if (approvalItem.rawItem.type === 'hosted_tool_call' && persistent) {
                throw new errors_1.UserError('Persistent hosted approval decisions require valid MCP approval request provider data.');
            }
            return this.#getApprovalItemToolName(approvalItem);
        }
        if (!hostedIdentity.requestId) {
            throw new errors_1.UserError('Hosted MCP approval decisions require a non-empty request id.');
        }
        if (!hostedIdentity.serverLabel || !hostedIdentity.toolName) {
            throw new errors_1.UserError('Hosted MCP approval decisions require a non-empty server label and tool name.');
        }
        return (0, toolIdentity_1.getHostedMcpApprovalStateKey)(hostedIdentity);
    }
    #getApprovalStorageKey(toolName, includeFunctionAliases, approvals = this.#approvals) {
        return (getApprovalToolNameCandidates(toolName, includeFunctionAliases).find((candidate) => approvals.has(candidate)) ?? toolName);
    }
    #setFunctionApprovalRecord(agent, toolName, incoming) {
        const approvalsByTool = this.#getFunctionApprovalMap(agent);
        const current = approvalsByTool.get(toolName);
        approvalsByTool.set(toolName, current ? mergeApprovalRecords(current, incoming) : incoming);
    }
    #setToolApprovalRecord(agent, toolName, incoming) {
        const approvalsByTool = this.#getToolApprovalMap(agent);
        const current = approvalsByTool.get(toolName);
        approvalsByTool.set(toolName, current ? mergeApprovalRecords(current, incoming) : incoming);
    }
    #setLegacyFunctionApprovalRecord(toolName, incoming) {
        const current = this.#functionApprovalState.legacyApprovals.get(toolName);
        this.#functionApprovalState.legacyApprovals.set(toolName, current ? mergeApprovalRecords(current, incoming) : incoming);
    }
    #setApprovalRecord(toolName, incoming) {
        const candidates = getApprovalToolNameCandidates(toolName, false);
        const existingEntries = candidates
            .map((candidate) => this.#approvals.get(candidate))
            .filter((approval) => approval !== undefined);
        const mergedExisting = existingEntries.reduce((current, approval) => current ? mergeApprovalRecords(current, approval) : approval, undefined);
        const merged = mergedExisting
            ? mergeApprovalRecords(mergedExisting, incoming)
            : incoming;
        const targetKey = candidates.find((candidate) => this.#approvals.has(candidate)) ??
            toolName;
        this.#approvals.set(targetKey, merged);
        for (const candidate of candidates) {
            if (candidate !== targetKey) {
                this.#approvals.delete(candidate);
            }
        }
    }
}
exports.RunContext = RunContext;
//# sourceMappingURL=runContext.js.map