@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
JavaScript
"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