blob: 95864321bc8b720e1bf9a4e9fef5b1b8ac4db671 [file]
import { isPartialRuntimeEvent, isTerminalRuntimeEvent } from '@maka/core';
import type {
AgentRunEvent,
AgentRunHeader,
AgentRunEventType,
AgentRunStore,
RuntimeEvent,
RuntimeEventStore,
} from '@maka/core';
import {
classifyRuntimeEventTerminalFact,
type RuntimeEventTerminalFact,
} from './runtime-event-read-model.js';
export type TerminalAgentRunStatus = Extract<
AgentRunHeader['status'],
'completed' | 'failed' | 'cancelled'
>;
export type TerminalRuntimeLedgerClassification =
| {
kind: 'fact';
fact: RuntimeEventTerminalFact;
terminalEvents: readonly RuntimeEvent[];
}
| {
kind: 'none';
terminalEvents: readonly RuntimeEvent[];
}
| {
kind: 'incomplete_single_terminal';
terminalEvent: RuntimeEvent;
terminalEvents: readonly RuntimeEvent[];
}
| {
kind: 'ambiguous';
terminalEvents: readonly RuntimeEvent[];
};
export function classifyTerminalRuntimeLedger(
run: AgentRunHeader,
events: readonly RuntimeEvent[],
): TerminalRuntimeLedgerClassification {
const terminalEvents = matchingTerminalRuntimeEvents(run, events);
if (terminalEvents.length === 0) {
return { kind: 'none', terminalEvents };
}
if (terminalEvents.length > 1) {
return { kind: 'ambiguous', terminalEvents };
}
const fact = classifyRuntimeEventTerminalFact(run, events).fact;
if (fact) {
return { kind: 'fact', fact, terminalEvents };
}
return {
kind: 'incomplete_single_terminal',
terminalEvent: terminalEvents[0]!,
terminalEvents,
};
}
export interface CommitTerminalRunWithRuntimeFactInput {
runStore: AgentRunStore;
runtimeEventStore: RuntimeEventStore;
newId: () => string;
sessionId: string;
runId: string;
turnId: string;
status: TerminalAgentRunStatus;
ts: number;
terminalEvent: RuntimeEvent;
failureClass?: string;
failureMessage?: string;
traceWriteError?: string;
abortSource?: string;
runEventData?: Record<string, unknown>;
runEventMessage?: string;
existingEvents?: readonly Pick<AgentRunEvent, 'type'>[];
}
export async function commitTerminalRunWithRuntimeFact(
input: CommitTerminalRunWithRuntimeFactInput,
): Promise<void> {
if (isPartialRuntimeEvent(input.terminalEvent)) {
throw new Error('terminal RuntimeEvent must be final before terminal run header');
}
const terminalStatus = terminalRunStatusFromRuntimeEvent(input.terminalEvent);
if (!terminalStatus) {
throw new Error('terminal RuntimeEvent must carry a terminal status');
}
if (terminalStatus !== input.status) {
throw new Error(
`terminal RuntimeEvent status ${input.terminalEvent.status} cannot commit ${input.status} run header`,
);
}
if (
input.terminalEvent.sessionId !== input.sessionId ||
input.terminalEvent.runId !== input.runId ||
input.terminalEvent.turnId !== input.turnId
) {
throw new Error('terminal RuntimeEvent identity does not match run header commit');
}
await input.runtimeEventStore.ensureTerminalRuntimeEventDurable(
input.sessionId,
input.runId,
input.terminalEvent,
);
await commitTerminalRunProjection(input);
}
async function commitTerminalRunProjection(
input: CommitTerminalRunWithRuntimeFactInput,
): Promise<void> {
const failureClass = input.status === 'failed' ? (input.failureClass ?? 'unknown') : undefined;
const abortSource = input.status === 'cancelled' ? input.abortSource : undefined;
await input.runStore.updateRun(
input.sessionId,
input.runId,
{
status: input.status,
updatedAt: input.ts,
completedAt: input.ts,
...(failureClass ? { failureClass } : {}),
...(input.failureMessage ? { failureMessage: input.failureMessage } : {}),
...(input.traceWriteError ? { traceWriteError: input.traceWriteError } : {}),
...(abortSource ? { abortSource } : {}),
},
{ durable: true },
);
if (hasTerminalAgentRunEvent(input.existingEvents ?? [])) return;
const data = terminalRunEventData(input.status, failureClass, input.runEventData);
await input.runStore.appendEvent(
input.sessionId,
input.runId,
{
type: terminalAgentRunEventType(input.status),
id: input.newId(),
runId: input.runId,
sessionId: input.sessionId,
turnId: input.turnId,
ts: input.ts,
...(input.runEventMessage ? { message: input.runEventMessage } : {}),
...(Object.keys(data).length > 0 ? { data } : {}),
},
{ durable: true },
);
}
export interface CommitOrCreateTerminalRunFactInput
extends Omit<CommitTerminalRunWithRuntimeFactInput, 'status' | 'terminalEvent'> {
terminalEvent?: RuntimeEvent;
allowHeaderCommitFailure?: boolean;
fallbackStatus: TerminalAgentRunStatus;
fallbackInvocationId: string;
fallbackFailureClass?: string;
fallbackFailureMessage?: string;
}
export interface CommitOrCreateTerminalRunFactResult {
terminalEvent: RuntimeEvent;
status: TerminalAgentRunStatus;
failureClass?: string;
createdTerminalEvent: boolean;
headerCommitted: boolean;
headerCommitError?: unknown;
}
export async function commitOrCreateTerminalRunFact(
input: CommitOrCreateTerminalRunFactInput,
): Promise<CommitOrCreateTerminalRunFactResult> {
const createdTerminalEvent = !input.terminalEvent;
const effectiveAbortSource =
input.fallbackStatus === 'cancelled' ? (input.abortSource ?? 'user_stop') : input.abortSource;
const terminalEvent =
input.terminalEvent ??
buildSyntheticTerminalRuntimeEvent({
id: input.newId(),
invocationId: input.fallbackInvocationId,
run: {
sessionId: input.sessionId,
runId: input.runId,
turnId: input.turnId,
},
status: input.fallbackStatus,
ts: input.ts,
...(input.fallbackFailureClass ? { failureClass: input.fallbackFailureClass } : {}),
...(effectiveAbortSource ? { abortSource: effectiveAbortSource } : {}),
...((input.fallbackFailureMessage ?? input.failureMessage)
? { message: input.fallbackFailureMessage ?? input.failureMessage }
: {}),
});
const status = terminalRunStatusFromRuntimeEvent(terminalEvent);
if (!status) {
throw new Error('terminal RuntimeEvent must carry a terminal status');
}
if (isPartialRuntimeEvent(terminalEvent)) {
throw new Error('terminal RuntimeEvent must be final before terminal run header');
}
if (
terminalEvent.sessionId !== input.sessionId ||
terminalEvent.runId !== input.runId ||
terminalEvent.turnId !== input.turnId
) {
throw new Error('terminal RuntimeEvent identity does not match run header commit');
}
const failureClass =
status === 'failed'
? (runtimeEventFailureClass(terminalEvent) ?? input.failureClass ?? 'unknown')
: undefined;
await input.runtimeEventStore.ensureTerminalRuntimeEventDurable(
input.sessionId,
input.runId,
terminalEvent,
);
let headerCommitted = false;
let headerCommitError: unknown;
try {
await commitTerminalRunProjection({
...input,
terminalEvent,
status,
...(failureClass ? { failureClass } : {}),
...(effectiveAbortSource ? { abortSource: effectiveAbortSource } : {}),
});
headerCommitted = true;
} catch (error) {
if (!input.allowHeaderCommitFailure) throw error;
headerCommitError = error;
}
return {
terminalEvent,
status,
...(failureClass ? { failureClass } : {}),
createdTerminalEvent,
headerCommitted,
...(headerCommitError !== undefined ? { headerCommitError } : {}),
};
}
export interface BuildSyntheticTerminalRuntimeEventInput {
id: string;
invocationId: string;
run: Pick<AgentRunHeader, 'runId' | 'sessionId' | 'turnId'>;
status: TerminalAgentRunStatus;
ts: number;
failureClass?: string;
abortSource?: string;
recoveryReason?: string;
diagnostic?: Record<string, unknown>;
message?: string;
}
export function buildSyntheticTerminalRuntimeEvent(
input: BuildSyntheticTerminalRuntimeEventInput,
): RuntimeEvent {
const failureClass = input.status === 'failed' ? (input.failureClass ?? 'unknown') : undefined;
const abortSource = input.status === 'cancelled' ? input.abortSource : undefined;
return {
id: input.id,
invocationId: input.invocationId,
runId: input.run.runId,
sessionId: input.run.sessionId,
turnId: input.run.turnId,
ts: input.ts,
partial: false,
role: 'system',
author: 'system',
status: input.status === 'cancelled' ? 'aborted' : input.status,
...(failureClass
? {
content: {
kind: 'error',
code: failureClass,
reason: failureClass,
message: input.message ?? failureClass,
},
}
: {}),
actions: {
endInvocation: true,
stateDelta: {
...(input.recoveryReason ? { recovered: true, recoveryReason: input.recoveryReason } : {}),
...(input.diagnostic ?? {}),
...(failureClass ? { failureClass } : {}),
...(abortSource ? { abortSource } : {}),
},
},
};
}
export interface BuildRecoveredTerminalRuntimeEventInput {
id: string;
run: Pick<AgentRunHeader, 'runId' | 'sessionId' | 'turnId' | 'invocationId'>;
status: TerminalAgentRunStatus;
ts: number;
invocationId?: string;
failureClass?: string;
abortSource?: string;
recoveryReason: string;
diagnostic?: Record<string, unknown>;
message?: string;
}
export function buildRecoveredTerminalRuntimeEvent(
input: BuildRecoveredTerminalRuntimeEventInput,
): RuntimeEvent {
return buildSyntheticTerminalRuntimeEvent({
id: input.id,
invocationId: input.run.invocationId ?? input.invocationId ?? `recovery-${input.run.runId}`,
run: input.run,
ts: input.ts,
status: input.status,
...(input.failureClass ? { failureClass: input.failureClass } : {}),
...(input.status === 'cancelled' ? { abortSource: input.abortSource ?? 'unknown' } : {}),
recoveryReason: input.recoveryReason,
...(input.diagnostic ? { diagnostic: input.diagnostic } : {}),
...(input.message ? { message: input.message } : {}),
});
}
export function hasTerminalAgentRunEvent(events: readonly Pick<AgentRunEvent, 'type'>[]): boolean {
return events.some(
(event) =>
event.type === 'run_completed' ||
event.type === 'run_failed' ||
event.type === 'run_cancelled',
);
}
function terminalAgentRunEventType(status: TerminalAgentRunStatus): AgentRunEventType {
if (status === 'cancelled') return 'run_cancelled';
if (status === 'failed') return 'run_failed';
return 'run_completed';
}
function terminalRunEventData(
status: TerminalAgentRunStatus,
failureClass: string | undefined,
runEventData: Record<string, unknown> | undefined,
): Record<string, unknown> {
return {
...(status === 'failed' && failureClass ? { failureClass } : {}),
...(runEventData ?? {}),
};
}
function runtimeEventFailureClass(event: RuntimeEvent): string | undefined {
const stateDelta = event.actions?.stateDelta;
if (typeof stateDelta?.failureClass === 'string' && stateDelta.failureClass.length > 0) {
return stateDelta.failureClass;
}
if (event.content?.kind === 'error') {
return event.content.code ?? event.content.reason;
}
return undefined;
}
export function terminalRunStatusFromRuntimeEvent(
event: RuntimeEvent,
): TerminalAgentRunStatus | undefined {
if (event.status === 'completed') return 'completed';
if (event.status === 'failed') return 'failed';
if (event.status === 'aborted' || event.status === 'cancelled') return 'cancelled';
return undefined;
}
export function effectiveRunHeaderFromTerminalFact(
run: AgentRunHeader,
fact: RuntimeEventTerminalFact,
): AgentRunHeader {
const completedAt = run.completedAt ?? fact.terminalEvent.ts;
const base = { ...run };
delete base.failureClass;
delete base.failureMessage;
delete base.abortSource;
return {
...base,
status: fact.runStatus,
updatedAt: Math.max(run.updatedAt, completedAt),
completedAt,
...(fact.runStatus === 'failed' && fact.failureClass
? { failureClass: fact.failureClass }
: {}),
...(fact.runStatus === 'failed' && run.failureMessage
? { failureMessage: run.failureMessage }
: {}),
...(fact.runStatus === 'cancelled' && fact.abortSource
? { abortSource: fact.abortSource }
: {}),
};
}
export function terminalRunHeaderMatchesFact(
run: AgentRunHeader,
fact: RuntimeEventTerminalFact,
): boolean {
if (run.status !== fact.runStatus) return false;
if (fact.runStatus === 'failed' && run.failureClass !== fact.failureClass) return false;
if (fact.runStatus === 'cancelled' && run.abortSource !== fact.abortSource) return false;
return true;
}
export function matchingTerminalRuntimeEvents(
run: AgentRunHeader,
events: readonly RuntimeEvent[],
): RuntimeEvent[] {
return events.filter(
(event) =>
!isPartialRuntimeEvent(event) &&
event.sessionId === run.sessionId &&
event.runId === run.runId &&
event.turnId === run.turnId &&
isTerminalRuntimeEvent(event),
);
}