| import type { |
| AgentRunEvent, |
| AgentRunHeader, |
| AgentRunStore, |
| EmittedAgentRunEvent, |
| RuntimeEvent, |
| RuntimeEventStore, |
| StorageRef, |
| StoredMessage, |
| ToolResultContent, |
| } from '@maka/core'; |
| import { |
| decodeCanonicalToolResultContent, |
| isEmittedAgentRunEventType, |
| isSessionInlineRun, |
| } from '@maka/core'; |
| import { TOOL_RECOVERY_DECISION_FACT_KIND } from '@maka/core'; |
| import { |
| buildHistoryCompactCheckpoint, |
| matchHistoryCompactCheckpointPrefix, |
| validateHistoryCompactCheckpointShape, |
| } from './history-compact-checkpoint.js'; |
| import { isHistoryCompactContentEvent } from './history-compact.js'; |
| import { |
| classifyTerminalRuntimeLedger, |
| commitTerminalRunWithRuntimeFact, |
| } from './terminal-run-commit.js'; |
| import { buildToolOperationId } from './runtime-commit-sink.js'; |
| import { isContinuationStartRuntimeEvent } from './runtime-event-read-model.js'; |
| import { |
| buildToolResultArchiveResourceRef, |
| parseToolResultArchiveResourceRef, |
| } from './tool-result-archive-resource.js'; |
| import { |
| deserializeToolResultArchive, |
| isArchivedToolResultPlaceholder, |
| type ArchivedToolResultPlaceholder, |
| } from './tool-result-archive.js'; |
| |
| export interface ConversationCopySlice { |
| readonly messages: readonly StoredMessage[]; |
| readonly turnIds: readonly string[]; |
| readonly beforeTs?: number; |
| } |
| |
| interface ConversationCopyIdentityMap { |
| readonly sourceSessionId: string; |
| readonly targetSessionId: string; |
| } |
| |
| export interface ConversationCopyExternalChildReferences { |
| readonly runIds: ReadonlySet<string>; |
| readonly artifactIds: ReadonlySet<string>; |
| } |
| |
| export interface ConversationCopyLinkedChildReference { |
| readonly childSessionId: string; |
| readonly runId?: string; |
| readonly resumedFromRunId?: string; |
| readonly turnId?: string; |
| readonly artifactIds: readonly string[]; |
| readonly status: 'completed' | 'failed' | 'cancelled' | 'running' | 'waiting_for_user'; |
| readonly failureClass?: string; |
| } |
| |
| export type ConversationCopyArtifactReferenceMap = |
| | (ConversationCopyIdentityMap & { |
| readonly mode: 'exact'; |
| readonly artifactIds: ReadonlyMap<string, string>; |
| readonly relativePaths: ReadonlyMap<string, string>; |
| readonly linkedChildren: |
| | { readonly mode: 'reject' } |
| | { |
| readonly mode: 'preserve_validated'; |
| readonly references: ReadonlyMap<string, ConversationCopyExternalChildReferences>; |
| }; |
| }) |
| | (ConversationCopyIdentityMap & { |
| readonly mode: 'preserve_external'; |
| }); |
| |
| export type ConversationCopyMessageReferenceMap = ConversationCopyArtifactReferenceMap & { |
| readonly runIds: ReadonlyMap<string, string>; |
| readonly runtimeEventIds: ReadonlyMap<string, string>; |
| readonly providerTraceIds: ReadonlyMap<string, string>; |
| }; |
| |
| export type ConversationCopyReferenceMap = ConversationCopyMessageReferenceMap & { |
| readonly invocationIds: ReadonlyMap<string, string>; |
| readonly operationIds: ReadonlyMap<string, string>; |
| readonly agentRunEventIds: ReadonlyMap<string, string>; |
| }; |
| |
| export interface CloneConversationRuntimeLedgerInput { |
| readonly plan: ConversationRuntimeLedgerCopyPlan; |
| readonly copiedMessages: readonly StoredMessage[]; |
| readonly referenceMap: ConversationCopyArtifactReferenceMap; |
| readonly runStore: AgentRunStore; |
| readonly runtimeEventStore: RuntimeEventStore & { |
| importConversationCopyRuntimeEvents?( |
| sessionId: string, |
| batches: readonly { |
| readonly runId: string; |
| readonly events: readonly RuntimeEvent[]; |
| }[], |
| ): Promise<void>; |
| }; |
| readonly newId: () => string; |
| } |
| |
| export interface ConversationRuntimeLedgerCopyPlan { |
| readonly sourceSessionId: string; |
| readonly copyTurnIds: readonly string[]; |
| readonly inlineRuntimeEvents: readonly RuntimeEvent[]; |
| readonly runs: readonly { |
| readonly run: AgentRunHeader; |
| readonly runtimeEvents: readonly RuntimeEvent[]; |
| readonly operationalEvents: readonly AgentRunEvent[]; |
| }[]; |
| } |
| |
| export interface CloneConversationRuntimeLedgerResult { |
| readonly copiedMessages: readonly StoredMessage[]; |
| readonly runIdMap: readonly { |
| readonly sourceRunId: string; |
| readonly targetRunId: string; |
| }[]; |
| } |
| |
| export function createConversationCopySlice( |
| messages: readonly StoredMessage[], |
| sourceTurnId: string, |
| boundary: 'through' | 'before', |
| ): ConversationCopySlice | null { |
| const turnOrder: string[] = []; |
| const seen = new Set<string>(); |
| for (const message of messages) { |
| const turnId = messageTurnId(message); |
| if (turnId && !seen.has(turnId)) { |
| seen.add(turnId); |
| turnOrder.push(turnId); |
| } |
| } |
| const sourceIndex = turnOrder.indexOf(sourceTurnId); |
| if (sourceIndex < 0) return null; |
| const retainedTurnIds = |
| boundary === 'through' ? turnOrder.slice(0, sourceIndex + 1) : turnOrder.slice(0, sourceIndex); |
| const retained = new Set(retainedTurnIds); |
| const firstExcludedTurnId = |
| boundary === 'through' ? turnOrder[sourceIndex + 1] : turnOrder[sourceIndex]; |
| const firstExcludedTimestamps = |
| firstExcludedTurnId === undefined |
| ? [] |
| : messages |
| .filter((message) => messageTurnId(message) === firstExcludedTurnId) |
| .map((message) => message.ts); |
| return { |
| messages: messages.filter((message) => { |
| if (message.type === 'turn_state') return false; |
| const turnId = messageTurnId(message); |
| return turnId !== undefined && retained.has(turnId); |
| }), |
| turnIds: retainedTurnIds, |
| ...(firstExcludedTimestamps.length > 0 |
| ? { beforeTs: Math.min(...firstExcludedTimestamps) } |
| : {}), |
| }; |
| } |
| |
| export function rewriteConversationCopyMessage( |
| message: StoredMessage, |
| references: ConversationCopyMessageReferenceMap, |
| ): StoredMessage { |
| if (message.type === 'user' && message.attachments) { |
| return { |
| ...message, |
| attachments: message.attachments.map((attachment) => ({ |
| ...attachment, |
| ref: rewriteStorageRef(attachment.ref, references), |
| })), |
| }; |
| } |
| if (message.type === 'tool_result') { |
| return { |
| ...message, |
| content: rewriteToolResultContent(message.content, references), |
| }; |
| } |
| if (message.type === 'token_usage' && message.providerRequestTraceId) { |
| return { |
| ...message, |
| providerRequestTraceId: rewriteOwnedId( |
| message.providerRequestTraceId, |
| references.providerTraceIds, |
| 'provider trace', |
| ), |
| }; |
| } |
| return message; |
| } |
| |
| export async function prepareConversationRuntimeLedgerCopy(input: { |
| readonly sourceSessionId: string; |
| readonly sourceEvents: readonly RuntimeEvent[]; |
| readonly copiedMessages: readonly StoredMessage[]; |
| readonly runStore: Pick<AgentRunStore, 'listSessionRuns' | 'readEvents'>; |
| readonly runtimeEventStore: Pick<RuntimeEventStore, 'readRuntimeEvents'>; |
| }): Promise<ConversationRuntimeLedgerCopyPlan> { |
| const sourceRuns = await input.runStore.listSessionRuns(input.sourceSessionId); |
| const transcriptTurnIds = [ |
| ...new Set( |
| input.copiedMessages.map(messageTurnId).filter((turnId): turnId is string => !!turnId), |
| ), |
| ]; |
| const copyTurnIds = conversationCopyTurnClosure(sourceRuns, transcriptTurnIds); |
| const selectedRunEvents = await loadConversationCopyRunEvents( |
| sourceRuns, |
| input.sourceEvents, |
| copyTurnIds, |
| input.runtimeEventStore, |
| ); |
| const runs = await Promise.all( |
| selectedRunEvents.map(async ({ run, events }) => { |
| const operationalEvents = await input.runStore.readEvents(run.sessionId, run.runId); |
| if (events.length === 0) { |
| throw new Error(`Cannot copy AgentRun ${run.runId} without RuntimeEvent facts`); |
| } |
| const terminal = classifyTerminalRuntimeLedger(run, events); |
| if (isTerminalRunStatus(run.status) && terminal.kind !== 'fact') { |
| throw new Error(`Cannot copy terminal AgentRun ${run.runId} without one terminal fact`); |
| } |
| return { run, runtimeEvents: events, operationalEvents }; |
| }), |
| ); |
| const plan = { |
| sourceSessionId: input.sourceSessionId, |
| copyTurnIds, |
| inlineRuntimeEvents: [...input.sourceEvents], |
| runs, |
| }; |
| assertConversationRuntimeLedgerCopySupported(plan); |
| return plan; |
| } |
| |
| function assertConversationRuntimeLedgerCopySupported( |
| plan: ConversationRuntimeLedgerCopyPlan, |
| ): void { |
| const unsupported = plan.runs.some( |
| ({ run, runtimeEvents }) => |
| run.continuationSource !== undefined || runtimeEvents.some(isContinuationStartRuntimeEvent), |
| ); |
| if (!unsupported) return; |
| |
| const error = new Error( |
| 'Conversation copy contains durable runtime authority facts that require typed identity rewriting', |
| ) as Error & { code: string }; |
| error.code = 'branch_runtime_fact_rewrite_unsupported'; |
| throw error; |
| } |
| |
| export async function cloneConversationRuntimeLedger( |
| input: CloneConversationRuntimeLedgerInput, |
| ): Promise<CloneConversationRuntimeLedgerResult> { |
| if (input.plan.sourceSessionId !== input.referenceMap.sourceSessionId) { |
| throw new Error('Conversation copy plan does not belong to the source Session'); |
| } |
| const flattenedPlans = input.plan.runs.map(({ run, runtimeEvents, operationalEvents }) => ({ |
| run, |
| events: runtimeEvents, |
| operationalEvents, |
| terminal: classifyTerminalRuntimeLedger(run, runtimeEvents), |
| })); |
| const sourceCompactableEvents = sourceCompactableEventsByRunId( |
| flattenedPlans, |
| input.plan.inlineRuntimeEvents, |
| ); |
| const runIds = new Map(flattenedPlans.map(({ run }) => [run.runId, input.newId()])); |
| const targetInvocationIds = new Map(flattenedPlans.map(({ run }) => [run.runId, input.newId()])); |
| const invocationIds = new Map( |
| flattenedPlans.flatMap(({ run }) => |
| run.invocationId ? [[run.invocationId, targetInvocationIds.get(run.runId)!] as const] : [], |
| ), |
| ); |
| const copiedPermissionDecisions = new Map( |
| input.copiedMessages.flatMap((message) => |
| message.type === 'permission_decision' ? [[message.id, message] as const] : [], |
| ), |
| ); |
| const runtimeEventIds = new Map( |
| flattenedPlans.flatMap(({ events }) => |
| events.map((event) => [event.id, input.newId()] as const), |
| ), |
| ); |
| const operationalEventIds = new Map( |
| flattenedPlans.flatMap(({ operationalEvents }) => |
| operationalEvents.flatMap((event) => |
| isCopiedAgentRunEvent(event) ? [[event.id, input.newId()] as const] : [], |
| ), |
| ), |
| ); |
| const providerTraceIds = providerTraceIdMap(flattenedPlans, input.newId); |
| const operationIds = toolOperationIdMap(flattenedPlans, targetInvocationIds); |
| const references: ConversationCopyReferenceMap = { |
| ...input.referenceMap, |
| runIds, |
| invocationIds, |
| operationIds, |
| runtimeEventIds, |
| providerTraceIds, |
| agentRunEventIds: operationalEventIds, |
| }; |
| const clonedEventBySourceId = new Map<string, RuntimeEvent>(); |
| for (const plan of flattenedPlans) { |
| const runId = runIds.get(plan.run.runId)!; |
| const invocationId = targetInvocationIds.get(plan.run.runId)!; |
| for (const event of plan.events) { |
| clonedEventBySourceId.set( |
| event.id, |
| cloneRuntimeEvent( |
| event, |
| { |
| sessionId: input.referenceMap.targetSessionId, |
| runId, |
| eventId: runtimeEventIds.get(event.id)!, |
| invocationId, |
| }, |
| references, |
| copiedPermissionDecisions, |
| ), |
| ); |
| } |
| } |
| const checkpointIds = new Map<string, string>(); |
| const preparedPlans = flattenedPlans.map((plan) => { |
| const runId = runIds.get(plan.run.runId)!; |
| const invocationId = targetInvocationIds.get(plan.run.runId)!; |
| const clonedOperationalEvents = plan.operationalEvents.flatMap((event) => { |
| const clonedEvent = cloneAgentRunEvent( |
| event, |
| { |
| sessionId: input.referenceMap.targetSessionId, |
| runId, |
| eventId: operationalEventIds.get(event.id), |
| }, |
| references, |
| sourceCompactableEvents.get(plan.run.runId) ?? [], |
| clonedEventBySourceId, |
| checkpointIds, |
| operationalEventIds, |
| providerTraceIds, |
| ); |
| return clonedEvent ? [clonedEvent] : []; |
| }); |
| const terminalEvent = |
| plan.terminal.kind === 'fact' && isTerminalRunStatus(plan.run.status) |
| ? clonedEventBySourceId.get(plan.terminal.fact.terminalEvent.id) |
| : undefined; |
| if (plan.terminal.kind === 'fact' && isTerminalRunStatus(plan.run.status) && !terminalEvent) { |
| throw new Error(`Copied AgentRun ${plan.run.runId} lost its terminal RuntimeEvent`); |
| } |
| return { |
| plan, |
| runId, |
| clonedRun: cloneRunHeader( |
| plan.run, |
| input.referenceMap.targetSessionId, |
| runId, |
| invocationId, |
| references, |
| ), |
| clonedRuntimeEvents: plan.events.map((event) => clonedEventBySourceId.get(event.id)!), |
| clonedOperationalEvents, |
| terminalEvent, |
| }; |
| }); |
| const copiedMessages = input.copiedMessages.map((message) => |
| rewriteConversationCopyMessage(message, references), |
| ); |
| |
| for (const { clonedRun } of preparedPlans) { |
| await input.runStore.createRun(clonedRun); |
| } |
| |
| if (input.runtimeEventStore.importConversationCopyRuntimeEvents) { |
| await input.runtimeEventStore.importConversationCopyRuntimeEvents( |
| input.referenceMap.targetSessionId, |
| preparedPlans.map(({ runId, clonedRuntimeEvents }) => ({ |
| runId, |
| events: clonedRuntimeEvents, |
| })), |
| ); |
| } else { |
| for (const { runId, clonedRuntimeEvents } of preparedPlans) { |
| for (const clonedEvent of clonedRuntimeEvents) { |
| await input.runtimeEventStore.appendRuntimeEvent( |
| input.referenceMap.targetSessionId, |
| runId, |
| clonedEvent, |
| ); |
| } |
| } |
| } |
| |
| for (const { plan, runId, clonedOperationalEvents, terminalEvent } of preparedPlans) { |
| for (const clonedEvent of clonedOperationalEvents) { |
| await input.runStore.appendEvent(input.referenceMap.targetSessionId, runId, clonedEvent); |
| } |
| |
| if (plan.terminal.kind === 'fact' && isTerminalRunStatus(plan.run.status) && terminalEvent) { |
| await commitTerminalRunWithRuntimeFact({ |
| runStore: input.runStore, |
| runtimeEventStore: input.runtimeEventStore, |
| newId: input.newId, |
| sessionId: input.referenceMap.targetSessionId, |
| runId, |
| turnId: plan.run.turnId, |
| status: plan.terminal.fact.runStatus, |
| ts: terminalEvent.ts, |
| terminalEvent, |
| ...(plan.terminal.fact.failureClass |
| ? { failureClass: plan.terminal.fact.failureClass } |
| : {}), |
| ...(plan.run.failureMessage ? { failureMessage: plan.run.failureMessage } : {}), |
| ...(plan.terminal.fact.abortSource ? { abortSource: plan.terminal.fact.abortSource } : {}), |
| runEventData: { |
| recovered: true, |
| recoveryReason: 'conversation_runtime_ledger_clone', |
| sourceSessionId: plan.run.sessionId, |
| sourceRunId: plan.run.runId, |
| }, |
| }); |
| } |
| } |
| |
| return { |
| copiedMessages, |
| runIdMap: [...runIds].map(([sourceRunId, targetRunId]) => ({ |
| sourceRunId, |
| targetRunId, |
| })), |
| }; |
| } |
| |
| interface ConversationCopyRunEvents { |
| readonly run: AgentRunHeader; |
| readonly events: readonly RuntimeEvent[]; |
| } |
| |
| async function loadConversationCopyRunEvents( |
| sourceRuns: readonly AgentRunHeader[], |
| sourceEvents: readonly RuntimeEvent[], |
| copyTurnIds: readonly string[], |
| runtimeEventStore: Pick<RuntimeEventStore, 'readRuntimeEvents'>, |
| ): Promise<ConversationCopyRunEvents[]> { |
| const copiedTurnIds = new Set(copyTurnIds); |
| return Promise.all( |
| sourceRuns.flatMap((run) => { |
| if (!copiedTurnIds.has(run.turnId)) return []; |
| const projectedEvents = sourceEvents.filter( |
| (event) => event.runId === run.runId && copiedTurnIds.has(event.turnId), |
| ); |
| return [ |
| Promise.resolve( |
| projectedEvents.length > 0 |
| ? projectedEvents |
| : runtimeEventStore.readRuntimeEvents(run.sessionId, run.runId), |
| ).then((events) => ({ run, events })), |
| ]; |
| }), |
| ); |
| } |
| |
| export function archivedToolResultContainsConversationOwnedReferences( |
| serializedResult: string, |
| sourceSessionId: string, |
| externalChildReferences?: ReadonlyMap<string, ConversationCopyExternalChildReferences>, |
| ): boolean { |
| const value = deserializeToolResultArchive(serializedResult); |
| if (isArchivedToolResultPlaceholder(value)) return true; |
| |
| let content: ToolResultContent; |
| try { |
| content = decodeCanonicalToolResultContent(value); |
| } catch { |
| return false; |
| } |
| |
| if (content.kind === 'archived_tool_result') return true; |
| if (content.kind === 'image') { |
| return content.ref.kind === 'session_file' && content.ref.sessionId === sourceSessionId; |
| } |
| if (content.kind === 'subagent') { |
| const [linked] = conversationCopyLinkedChildReferences(content); |
| if (linked) { |
| return !linkedChildReferencesAreExternal(linked, externalChildReferences); |
| } |
| return content.runId !== undefined || content.artifactIds.length > 0; |
| } |
| if (content.kind === 'agent_swarm') { |
| if ( |
| content.items.some( |
| (item) => |
| !item.childSessionId && |
| (item.runId !== undefined || |
| item.resumedFromRunId !== undefined || |
| item.artifactIds.length > 0), |
| ) |
| ) { |
| return true; |
| } |
| return conversationCopyLinkedChildReferences(content).some( |
| (linked) => !linkedChildReferencesAreExternal(linked, externalChildReferences), |
| ); |
| } |
| return false; |
| } |
| |
| export function conversationCopyLinkedChildReferences( |
| content: ToolResultContent, |
| ): readonly ConversationCopyLinkedChildReference[] { |
| if (content.kind === 'subagent') { |
| if (!content.childSessionId) return []; |
| return [ |
| { |
| childSessionId: content.childSessionId, |
| ...(content.runId ? { runId: content.runId } : {}), |
| turnId: content.turnId, |
| artifactIds: content.artifactIds, |
| status: content.status, |
| ...(content.failureClass ? { failureClass: content.failureClass } : {}), |
| }, |
| ]; |
| } |
| if (content.kind !== 'agent_swarm') return []; |
| return content.items.flatMap((item) => |
| item.childSessionId |
| ? [ |
| { |
| childSessionId: item.childSessionId, |
| ...(item.runId ? { runId: item.runId } : {}), |
| ...(item.resumedFromRunId ? { resumedFromRunId: item.resumedFromRunId } : {}), |
| ...(item.turnId ? { turnId: item.turnId } : {}), |
| artifactIds: item.artifactIds, |
| status: item.status, |
| ...(item.failureClass ? { failureClass: item.failureClass } : {}), |
| }, |
| ] |
| : [], |
| ); |
| } |
| |
| export function collectConversationCopyLinkedChildReferences(input: { |
| readonly messages: readonly StoredMessage[]; |
| readonly runtimeEvents: readonly RuntimeEvent[]; |
| readonly archivedResults: readonly string[]; |
| }): readonly ConversationCopyLinkedChildReference[] { |
| const references: ConversationCopyLinkedChildReference[] = []; |
| const add = (value: unknown): void => { |
| if (isArchivedToolResultPlaceholder(value)) return; |
| try { |
| references.push( |
| ...conversationCopyLinkedChildReferences(decodeCanonicalToolResultContent(value)), |
| ); |
| } catch { |
| // Opaque tool results have no typed linked-child references. |
| } |
| }; |
| for (const message of input.messages) { |
| if (message.type === 'tool_result') { |
| references.push(...conversationCopyLinkedChildReferences(message.content)); |
| } |
| } |
| for (const event of input.runtimeEvents) { |
| if (event.content?.kind === 'function_response') add(event.content.result); |
| } |
| for (const serializedResult of input.archivedResults) { |
| add(deserializeToolResultArchive(serializedResult)); |
| } |
| return references; |
| } |
| |
| function cloneAgentRunEvent( |
| event: AgentRunEvent, |
| ids: { |
| readonly sessionId: string; |
| readonly runId: string; |
| readonly eventId?: string; |
| }, |
| references: ConversationCopyReferenceMap, |
| sourceCompactableEvents: readonly RuntimeEvent[], |
| clonedRuntimeEvents: ReadonlyMap<string, RuntimeEvent>, |
| checkpointIds: Map<string, string>, |
| operationalEventIds: ReadonlyMap<string, string>, |
| providerTraceIds: ReadonlyMap<string, string>, |
| ): EmittedAgentRunEvent | null { |
| if (event.type === 'event_corrupt') { |
| throw new Error(`Cannot copy corrupt AgentRun event ${event.id}`); |
| } |
| if (!isCopiedAgentRunEvent(event)) return null; |
| if (!ids.eventId) { |
| throw new Error(`Cannot copy AgentRun event ${event.id} without a target identity`); |
| } |
| |
| let data = event.data; |
| if (event.type === 'provider_request_captured') { |
| data = rewriteProviderRequestCapture(event, ids.eventId, references, providerTraceIds); |
| } else if (event.type === 'provider_request_attempt_recorded') { |
| data = rewriteProviderRequestAttempt( |
| event, |
| ids.eventId, |
| references, |
| operationalEventIds, |
| providerTraceIds, |
| ); |
| } else if (event.type === 'history_compact_checkpoint_recorded') { |
| const sourceCheckpoint = event.data?.checkpoint; |
| if (!validateHistoryCompactCheckpointShape(sourceCheckpoint, event.sessionId)) { |
| throw new Error(`Cannot copy invalid history compact checkpoint ${event.id}`); |
| } |
| const match = matchHistoryCompactCheckpointPrefix(sourceCheckpoint, sourceCompactableEvents); |
| if (match.reason) { |
| throw new Error(`Cannot copy unmatched history compact checkpoint ${event.id}`); |
| } |
| const coveredRuntimeEvents = match.coveredRuntimeEvents.map((sourceEvent) => { |
| const cloned = clonedRuntimeEvents.get(sourceEvent.id); |
| if (!cloned) { |
| throw new Error( |
| `History compact checkpoint ${event.id} crosses the conversation copy boundary`, |
| ); |
| } |
| return cloned; |
| }); |
| const headAnchor = |
| sourceCheckpoint.phase === 'mid_turn' |
| ? { |
| runtimeEventId: |
| clonedRuntimeEvents.get(sourceCheckpoint.headAnchor!.runtimeEventId)?.id ?? |
| sourceCheckpoint.headAnchor!.runtimeEventId, |
| turnId: sourceCheckpoint.headAnchor!.turnId, |
| } |
| : undefined; |
| const checkpoint = buildHistoryCompactCheckpoint({ |
| sessionId: references.targetSessionId, |
| coveredRuntimeEvents, |
| summary: sourceCheckpoint.summary, |
| highWaterName: sourceCheckpoint.highWaterName, |
| highWaterSeq: sourceCheckpoint.highWaterSeq, |
| now: sourceCheckpoint.createdAt, |
| ...(sourceCheckpoint.phase ? { phase: sourceCheckpoint.phase } : {}), |
| ...(headAnchor ? { headAnchor } : {}), |
| ...(sourceCheckpoint.previousCheckpointId && |
| checkpointIds.has(sourceCheckpoint.previousCheckpointId) |
| ? { |
| previousCheckpointId: checkpointIds.get(sourceCheckpoint.previousCheckpointId)!, |
| } |
| : {}), |
| }); |
| checkpointIds.set(sourceCheckpoint.checkpointId, checkpoint.checkpointId); |
| data = { |
| ...event.data, |
| checkpointId: checkpoint.checkpointId, |
| checkpoint, |
| }; |
| } |
| |
| return { |
| ...event, |
| id: ids.eventId, |
| sessionId: ids.sessionId, |
| runId: ids.runId, |
| ...(data ? { data } : {}), |
| }; |
| } |
| |
| function rewriteProviderRequestCapture( |
| event: AgentRunEvent, |
| eventId: string, |
| references: ConversationCopyReferenceMap, |
| providerTraceIds: ReadonlyMap<string, string>, |
| ): Record<string, unknown> { |
| const data = providerRequestCapture(event); |
| return { |
| ...data, |
| traceId: requiredMappedId(providerTraceIds, data.traceId, 'provider trace'), |
| captureId: eventId, |
| artifactId: rewriteOwnedArtifactId(data.artifactId, references), |
| }; |
| } |
| |
| function rewriteProviderRequestAttempt( |
| event: AgentRunEvent, |
| eventId: string, |
| references: ConversationCopyReferenceMap, |
| operationalEventIds: ReadonlyMap<string, string>, |
| providerTraceIds: ReadonlyMap<string, string>, |
| ): Record<string, unknown> { |
| const data = providerRequestAttempt(event); |
| return { |
| ...data, |
| traceId: requiredMappedId(providerTraceIds, data.traceId, 'provider trace'), |
| attemptId: eventId, |
| captureId: requiredMappedId(operationalEventIds, data.captureId, 'provider request capture'), |
| captureArtifactId: rewriteOwnedArtifactId(data.captureArtifactId, references), |
| }; |
| } |
| |
| function providerRequestCapture(event: AgentRunEvent): Record<string, unknown> & { |
| readonly traceId: string; |
| readonly captureId: string; |
| readonly artifactId: string; |
| } { |
| const data = event.data; |
| if ( |
| !data || |
| data.captureId !== event.id || |
| typeof data.traceId !== 'string' || |
| typeof data.artifactId !== 'string' |
| ) { |
| throw new Error(`Cannot copy invalid provider request capture ${event.id}`); |
| } |
| return { |
| ...data, |
| traceId: data.traceId, |
| captureId: data.captureId, |
| artifactId: data.artifactId, |
| }; |
| } |
| |
| function providerRequestAttempt(event: AgentRunEvent): Record<string, unknown> & { |
| readonly traceId: string; |
| readonly attemptId: string; |
| readonly captureId: string; |
| readonly captureArtifactId: string; |
| } { |
| const data = event.data; |
| if ( |
| !data || |
| data.attemptId !== event.id || |
| typeof data.traceId !== 'string' || |
| typeof data.captureId !== 'string' || |
| typeof data.captureArtifactId !== 'string' |
| ) { |
| throw new Error(`Cannot copy invalid provider request attempt ${event.id}`); |
| } |
| return { |
| ...data, |
| traceId: data.traceId, |
| attemptId: data.attemptId, |
| captureId: data.captureId, |
| captureArtifactId: data.captureArtifactId, |
| }; |
| } |
| |
| function requiredMappedId( |
| ids: ReadonlyMap<string, string>, |
| sourceId: string, |
| kind: string, |
| ): string { |
| const targetId = ids.get(sourceId); |
| if (!targetId) throw new Error(`Conversation copy is missing ${kind} ${sourceId}`); |
| return targetId; |
| } |
| |
| function rewriteOwnedArtifactId( |
| sourceArtifactId: string, |
| references: ConversationCopyArtifactReferenceMap, |
| ): string { |
| if (references.mode === 'preserve_external') return sourceArtifactId; |
| return rewriteOwnedId(sourceArtifactId, references.artifactIds, 'Artifact'); |
| } |
| |
| function rewriteOwnedId(sourceId: string, ids: ReadonlyMap<string, string>, kind: string): string { |
| return requiredMappedId(ids, sourceId, kind); |
| } |
| |
| function providerTraceIdMap( |
| plans: readonly { readonly operationalEvents: readonly AgentRunEvent[] }[], |
| newId: () => string, |
| ): Map<string, string> { |
| const result = new Map<string, string>(); |
| for (const { operationalEvents } of plans) { |
| for (const event of operationalEvents) { |
| if ( |
| event.type !== 'provider_request_captured' && |
| event.type !== 'provider_request_attempt_recorded' |
| ) { |
| continue; |
| } |
| const traceId = event.data?.traceId; |
| if (typeof traceId === 'string' && !result.has(traceId)) result.set(traceId, newId()); |
| } |
| } |
| return result; |
| } |
| |
| function toolOperationIdMap( |
| plans: readonly { |
| readonly run: AgentRunHeader; |
| readonly events: readonly RuntimeEvent[]; |
| }[], |
| targetInvocationIds: ReadonlyMap<string, string>, |
| ): Map<string, string> { |
| const result = new Map<string, string>(); |
| for (const { run, events } of plans) { |
| const invocationId = requiredMappedId(targetInvocationIds, run.runId, 'target invocation'); |
| for (const event of events) { |
| const dispatch = event.actions?.toolDispatch; |
| if (!dispatch) continue; |
| const targetOperationId = buildToolOperationId({ |
| invocationId, |
| providerToolCallId: dispatch.providerToolCallId, |
| }); |
| const existing = result.get(dispatch.operationId); |
| if (existing && existing !== targetOperationId) { |
| throw new Error(`Tool operation ${dispatch.operationId} crosses copied AgentRuns`); |
| } |
| result.set(dispatch.operationId, targetOperationId); |
| } |
| } |
| return result; |
| } |
| |
| function isCopiedAgentRunEvent(event: AgentRunEvent): event is EmittedAgentRunEvent { |
| // The rewriters below know which of this build's payloads carry source-owned references. A type |
| // this build does not emit cannot even be checked for them, so it is dropped rather than carried |
| // into the target with source identities intact. The ledger's `type` is open, so such an event |
| // may predate a retired writer or postdate this build entirely (#1942). |
| if (!isEmittedAgentRunEventType(event.type)) return false; |
| // Active/semantic blocks hash the exact provider-visible source. Rewriting |
| // target-owned RuntimeEvent and Artifact references invalidates that |
| // evidence, so a copied Session starts without these derived diagnostics. |
| return ( |
| event.type !== 'run_completed' && |
| event.type !== 'run_failed' && |
| event.type !== 'run_cancelled' && |
| event.type !== 'event_corrupt' && |
| event.type !== 'active_full_compact_block_recorded' && |
| event.type !== 'semantic_compact_block_recorded' |
| ); |
| } |
| |
| function cloneRuntimeEvent( |
| event: RuntimeEvent, |
| ids: { |
| readonly sessionId: string; |
| readonly runId: string; |
| readonly eventId: string; |
| readonly invocationId: string; |
| }, |
| references: ConversationCopyReferenceMap, |
| copiedPermissionDecisions: ReadonlyMap< |
| string, |
| Extract<StoredMessage, { type: 'permission_decision' }> |
| >, |
| ): RuntimeEvent { |
| const rewritten = rewriteRuntimeEventReferences(event, references); |
| const cloned: RuntimeEvent = { |
| ...rewritten, |
| id: ids.eventId, |
| invocationId: ids.invocationId, |
| sessionId: ids.sessionId, |
| runId: ids.runId, |
| }; |
| const accepted = event.actions?.permissionAnswerAccepted; |
| const decision = accepted ? copiedPermissionDecisions.get(accepted.requestId) : undefined; |
| if (!decision || !cloned.actions) return cloned; |
| const { permissionAnswerAccepted: _accepted, ...actions } = cloned.actions; |
| cloned.actions = { |
| ...actions, |
| permissionDecision: { |
| requestId: decision.id, |
| toolName: decision.toolName, |
| decision: decision.decision, |
| ...(decision.rememberForTurn !== undefined |
| ? { rememberForTurn: decision.rememberForTurn } |
| : {}), |
| ...(decision.reviewer !== undefined ? { reviewer: decision.reviewer } : {}), |
| ...(decision.rationale !== undefined ? { rationale: decision.rationale } : {}), |
| ...(decision.riskLevel !== undefined ? { riskLevel: decision.riskLevel } : {}), |
| }, |
| }; |
| cloned.ts = decision.ts; |
| return cloned; |
| } |
| |
| function cloneRunHeader( |
| source: AgentRunHeader, |
| targetSessionId: string, |
| runId: string, |
| invocationId: string, |
| references: ConversationCopyReferenceMap, |
| ): AgentRunHeader { |
| const cloned: AgentRunHeader = { |
| ...source, |
| invocationId, |
| sessionId: targetSessionId, |
| runId, |
| ...(source.parentRunId |
| ? { parentRunId: rewriteOwnedId(source.parentRunId, references.runIds, 'AgentRun') } |
| : {}), |
| ...(source.resumedFromRunId |
| ? { |
| resumedFromRunId: rewriteOwnedId(source.resumedFromRunId, references.runIds, 'AgentRun'), |
| } |
| : {}), |
| ...(source.retriedFromRunId |
| ? { |
| retriedFromRunId: rewriteOwnedId(source.retriedFromRunId, references.runIds, 'AgentRun'), |
| } |
| : {}), |
| ...(source.parentSessionId === references.sourceSessionId |
| ? { parentSessionId: targetSessionId } |
| : {}), |
| ...(source.continuationSource |
| ? { |
| continuationSource: { |
| ...source.continuationSource, |
| sourceInvocationId: rewriteOwnedId( |
| source.continuationSource.sourceInvocationId, |
| references.invocationIds, |
| 'invocation', |
| ), |
| sourceRunId: rewriteOwnedId( |
| source.continuationSource.sourceRunId, |
| references.runIds, |
| 'AgentRun', |
| ), |
| }, |
| } |
| : {}), |
| }; |
| if (isTerminalRunStatus(source.status)) { |
| cloned.status = 'running'; |
| delete cloned.completedAt; |
| delete cloned.failureClass; |
| delete cloned.failureMessage; |
| delete cloned.abortSource; |
| } |
| return cloned; |
| } |
| |
| function rewriteRuntimeEventReferences( |
| event: RuntimeEvent, |
| references: ConversationCopyReferenceMap, |
| ): RuntimeEvent { |
| const content = |
| event.content?.kind === 'text' && event.content.attachments |
| ? { |
| ...event.content, |
| attachments: event.content.attachments.map((attachment) => ({ |
| ...attachment, |
| ref: rewriteStorageRef(attachment.ref, references), |
| })), |
| } |
| : event.content?.kind === 'function_response' |
| ? { |
| ...event.content, |
| result: rewriteRuntimeToolResult(event.content.result, references), |
| } |
| : event.content; |
| const refs = event.refs |
| ? (() => { |
| const { operationId: _operationId, traceEventId: _traceEventId, ...preserved } = event.refs; |
| const traceEventId = event.refs.traceEventId |
| ? references.agentRunEventIds.get(event.refs.traceEventId) |
| : undefined; |
| return { |
| ...preserved, |
| ...(traceEventId ? { traceEventId } : {}), |
| ...(event.refs.operationId |
| ? { |
| operationId: rewriteOwnedId( |
| event.refs.operationId, |
| references.operationIds, |
| 'tool operation', |
| ), |
| } |
| : {}), |
| ...(event.refs.artifactId |
| ? { |
| artifactId: rewriteOwnedArtifactId(event.refs.artifactId, references), |
| } |
| : {}), |
| ...(event.refs.sourceInvocationId |
| ? { |
| sourceInvocationId: rewriteOwnedId( |
| event.refs.sourceInvocationId, |
| references.invocationIds, |
| 'invocation', |
| ), |
| } |
| : {}), |
| ...(event.refs.sourceRunId |
| ? { |
| sourceRunId: rewriteOwnedId(event.refs.sourceRunId, references.runIds, 'AgentRun'), |
| } |
| : {}), |
| ...(event.refs.providerRequestTraceId |
| ? { |
| providerRequestTraceId: rewriteOwnedId( |
| event.refs.providerRequestTraceId, |
| references.providerTraceIds, |
| 'provider trace', |
| ), |
| } |
| : {}), |
| }; |
| })() |
| : undefined; |
| const actions = rewriteRuntimeEventActions(event.actions, references); |
| return { |
| ...event, |
| ...(content ? { content } : {}), |
| ...(actions ? { actions } : {}), |
| ...(refs ? { refs } : {}), |
| }; |
| } |
| |
| function rewriteRuntimeEventActions( |
| actions: RuntimeEvent['actions'], |
| references: ConversationCopyReferenceMap, |
| ): RuntimeEvent['actions'] { |
| const dispatch = actions?.toolDispatch; |
| const recovery = actions?.toolRecovery; |
| if (!dispatch && !recovery) return actions; |
| const operationId = dispatch?.operationId ?? recovery?.payload.operationId; |
| const targetOperationId = operationId |
| ? rewriteOwnedId(operationId, references.operationIds, 'tool operation') |
| : undefined; |
| return { |
| ...actions, |
| ...(dispatch && targetOperationId |
| ? { toolDispatch: { ...dispatch, operationId: targetOperationId } } |
| : {}), |
| ...(recovery && targetOperationId |
| ? { |
| toolRecovery: rewriteToolRecoveryFact(recovery, targetOperationId, references), |
| } |
| : {}), |
| }; |
| } |
| |
| function rewriteToolRecoveryFact( |
| recovery: NonNullable<RuntimeEvent['actions']>['toolRecovery'], |
| operationId: string, |
| references: ConversationCopyReferenceMap, |
| ): NonNullable<RuntimeEvent['actions']>['toolRecovery'] { |
| if (!recovery || recovery.kind !== TOOL_RECOVERY_DECISION_FACT_KIND) { |
| return recovery ? { ...recovery, payload: { ...recovery.payload, operationId } } : recovery; |
| } |
| const payload = recovery.payload; |
| return { |
| ...recovery, |
| payload: { |
| ...payload, |
| operationId, |
| evidenceEventIds: payload.evidenceEventIds.map((eventId) => |
| requiredMappedId(references.runtimeEventIds, eventId, 'RuntimeEvent'), |
| ), |
| ...(payload.disposition === 'completed' |
| ? { |
| outcomeEventId: requiredMappedId( |
| references.runtimeEventIds, |
| payload.outcomeEventId, |
| 'RuntimeEvent', |
| ), |
| } |
| : {}), |
| }, |
| }; |
| } |
| |
| function rewriteToolResultContent( |
| content: ToolResultContent, |
| references: ConversationCopyMessageReferenceMap, |
| ): ToolResultContent { |
| if (content.kind === 'image') { |
| return { ...content, ref: rewriteStorageRef(content.ref, references) }; |
| } |
| if (content.kind === 'archived_tool_result') { |
| return { |
| ...content, |
| runtimeEventId: rewriteOwnedId( |
| content.runtimeEventId, |
| references.runtimeEventIds, |
| 'RuntimeEvent', |
| ), |
| ...(content.artifactId |
| ? { artifactId: rewriteOwnedArtifactId(content.artifactId, references) } |
| : {}), |
| }; |
| } |
| if (content.kind === 'json' && isArchivedToolResultPlaceholder(content.value)) { |
| return { |
| ...content, |
| value: rewriteArchivedToolResult(content.value, references), |
| }; |
| } |
| if (content.kind === 'subagent') { |
| return { |
| ...content, |
| ...(content.runId |
| ? { |
| runId: rewriteLinkedRunId( |
| content.runId, |
| content.childSessionId, |
| references, |
| 'AgentRun', |
| ), |
| } |
| : {}), |
| artifactIds: rewriteLinkedArtifactIds( |
| content.artifactIds, |
| content.childSessionId, |
| references, |
| ), |
| }; |
| } |
| if (content.kind === 'agent_swarm') { |
| return { |
| ...content, |
| items: content.items.map((item) => { |
| return { |
| ...item, |
| ...(item.runId |
| ? { |
| runId: rewriteLinkedRunId(item.runId, item.childSessionId, references, 'AgentRun'), |
| } |
| : {}), |
| ...(item.resumedFromRunId |
| ? { |
| resumedFromRunId: rewriteLinkedRunId( |
| item.resumedFromRunId, |
| item.childSessionId, |
| references, |
| 'resumed AgentRun', |
| ), |
| } |
| : {}), |
| artifactIds: rewriteLinkedArtifactIds(item.artifactIds, item.childSessionId, references), |
| }; |
| }), |
| }; |
| } |
| return content; |
| } |
| |
| function rewriteRuntimeToolResult( |
| value: unknown, |
| references: ConversationCopyMessageReferenceMap, |
| ): unknown { |
| if (isArchivedToolResultPlaceholder(value)) { |
| return rewriteArchivedToolResult(value, references); |
| } |
| let content: ToolResultContent; |
| try { |
| content = decodeCanonicalToolResultContent(value); |
| } catch { |
| return value; |
| } |
| return rewriteToolResultContent(content, references); |
| } |
| |
| function rewriteArtifactIds( |
| artifactIds: readonly string[], |
| references: ConversationCopyArtifactReferenceMap, |
| ): readonly string[] { |
| return artifactIds.map((artifactId) => rewriteOwnedArtifactId(artifactId, references)); |
| } |
| |
| function validatedExternalChildReferences( |
| childSessionId: string, |
| references: ConversationCopyMessageReferenceMap, |
| ): ConversationCopyExternalChildReferences | undefined { |
| if (references.mode === 'preserve_external') return undefined; |
| if (references.linkedChildren.mode === 'reject') { |
| throw new Error(`Conversation copy cannot retain linked child Session ${childSessionId}`); |
| } |
| const external = references.linkedChildren.references.get(childSessionId); |
| if (!external) { |
| throw new Error(`Conversation copy is missing linked child Session ${childSessionId}`); |
| } |
| return external; |
| } |
| |
| function rewriteLinkedRunId( |
| sourceId: string, |
| childSessionId: string | undefined, |
| references: ConversationCopyMessageReferenceMap, |
| kind: string, |
| ): string { |
| if (!childSessionId) return rewriteOwnedId(sourceId, references.runIds, kind); |
| const external = validatedExternalChildReferences(childSessionId, references); |
| return external ? preserveExternalId(sourceId, external.runIds, kind) : sourceId; |
| } |
| |
| function rewriteLinkedArtifactIds( |
| sourceIds: readonly string[], |
| childSessionId: string | undefined, |
| references: ConversationCopyMessageReferenceMap, |
| ): readonly string[] { |
| if (!childSessionId) return rewriteArtifactIds(sourceIds, references); |
| const external = validatedExternalChildReferences(childSessionId, references); |
| return external ? preserveExternalIds(sourceIds, external.artifactIds, 'Artifact') : sourceIds; |
| } |
| |
| function preserveExternalIds( |
| sourceIds: readonly string[], |
| externalIds: ReadonlySet<string>, |
| kind: string, |
| ): readonly string[] { |
| return sourceIds.map((sourceId) => preserveExternalId(sourceId, externalIds, kind)); |
| } |
| |
| function preserveExternalId( |
| sourceId: string, |
| externalIds: ReadonlySet<string>, |
| kind: string, |
| ): string { |
| if (!externalIds.has(sourceId)) { |
| throw new Error(`Conversation copy is missing external ${kind} ${sourceId}`); |
| } |
| return sourceId; |
| } |
| |
| function linkedChildReferencesAreExternal( |
| linked: ConversationCopyLinkedChildReference, |
| externalChildReferences?: ReadonlyMap<string, ConversationCopyExternalChildReferences>, |
| ): boolean { |
| const external = externalChildReferences?.get(linked.childSessionId); |
| return ( |
| external !== undefined && |
| [linked.runId, linked.resumedFromRunId] |
| .filter((id): id is string => !!id) |
| .every((runId) => external.runIds.has(runId)) && |
| linked.artifactIds.every((artifactId) => external.artifactIds.has(artifactId)) |
| ); |
| } |
| |
| function rewriteStorageRef( |
| ref: StorageRef, |
| references: ConversationCopyArtifactReferenceMap, |
| ): StorageRef { |
| if (ref.kind !== 'session_file' || ref.sessionId !== references.sourceSessionId) return ref; |
| if (references.mode === 'preserve_external') return ref; |
| const relativePath = references.relativePaths.get(ref.relativePath); |
| if (!relativePath) { |
| throw new Error(`Conversation copy is missing Session file ${ref.relativePath}`); |
| } |
| return { |
| ...ref, |
| sessionId: references.targetSessionId, |
| relativePath, |
| }; |
| } |
| |
| function rewriteArchivedToolResult( |
| value: ArchivedToolResultPlaceholder, |
| references: ConversationCopyMessageReferenceMap, |
| ): ArchivedToolResultPlaceholder { |
| const artifactId = rewriteOwnedArtifactId(value.artifactId, references); |
| const resource = value.resourceRef |
| ? parseToolResultArchiveResourceRef(value.resourceRef) |
| : undefined; |
| return { |
| ...value, |
| runtimeEventId: rewriteOwnedId( |
| value.runtimeEventId, |
| references.runtimeEventIds, |
| 'RuntimeEvent', |
| ), |
| artifactId, |
| ...(resource && artifactId !== value.artifactId |
| ? { |
| resourceRef: buildToolResultArchiveResourceRef({ |
| ...resource, |
| artifactId, |
| }), |
| } |
| : {}), |
| }; |
| } |
| |
| function messageTurnId(message: StoredMessage): string | undefined { |
| return 'turnId' in message && typeof message.turnId === 'string' ? message.turnId : undefined; |
| } |
| |
| function conversationCopyTurnClosure( |
| runs: readonly AgentRunHeader[], |
| retainedTurnIds: readonly string[], |
| ): string[] { |
| const result = [...new Set(retainedTurnIds)]; |
| const includedTurnIds = new Set(result); |
| const includedRunIds = new Set( |
| runs.filter((run) => includedTurnIds.has(run.turnId)).map((run) => run.runId), |
| ); |
| for (let changed = true; changed; ) { |
| changed = false; |
| for (const run of runs) { |
| if ( |
| isSessionInlineRun(run) || |
| !run.parentRunId || |
| !includedRunIds.has(run.parentRunId) || |
| includedRunIds.has(run.runId) |
| ) { |
| continue; |
| } |
| includedRunIds.add(run.runId); |
| if (!includedTurnIds.has(run.turnId)) { |
| includedTurnIds.add(run.turnId); |
| result.push(run.turnId); |
| } |
| changed = true; |
| } |
| } |
| return result; |
| } |
| |
| function sourceCompactableEventsByRunId( |
| plans: readonly { |
| readonly run: AgentRunHeader; |
| readonly events: readonly RuntimeEvent[]; |
| }[], |
| sessionEvents: readonly RuntimeEvent[], |
| ): ReadonlyMap<string, readonly RuntimeEvent[]> { |
| const plansByRunId = new Map(plans.map((plan) => [plan.run.runId, plan])); |
| const inlineEvents = sessionEvents.filter(isHistoryCompactContentEvent); |
| const result = new Map<string, readonly RuntimeEvent[]>(); |
| |
| for (const plan of plans) { |
| if (isSessionInlineRun(plan.run)) { |
| result.set(plan.run.runId, inlineEvents); |
| continue; |
| } |
| |
| const reverseChain = []; |
| const visited = new Set<string>(); |
| let cursor: (typeof plans)[number] | undefined = plan; |
| while (cursor) { |
| if (visited.has(cursor.run.runId)) { |
| throw new Error( |
| `Conversation copy child resume lineage contains a cycle at ${cursor.run.runId}`, |
| ); |
| } |
| visited.add(cursor.run.runId); |
| reverseChain.push(cursor); |
| const sourceRunId = cursor.run.resumedFromRunId; |
| if (!sourceRunId) break; |
| cursor = plansByRunId.get(sourceRunId); |
| if (!cursor) { |
| throw new Error( |
| `Conversation copy child resume source ${sourceRunId} crosses the copy boundary`, |
| ); |
| } |
| } |
| result.set( |
| plan.run.runId, |
| reverseChain |
| .reverse() |
| .flatMap((item) => item.events) |
| .filter(isHistoryCompactContentEvent), |
| ); |
| } |
| |
| return result; |
| } |
| |
| function isTerminalRunStatus(status: AgentRunHeader['status']): boolean { |
| return status === 'completed' || status === 'failed' || status === 'cancelled'; |
| } |