blob: 2aefb29ec665e8db192cf32bd64ea23aa188a662 [file]
import {
isAgentRunInspectDocument,
isSessionInspectDocument,
type AgentRunInspectDocument,
type SessionInspectDocument,
} from '@maka/core/execution-inspect';
import {
isSessionTrace,
isTurnTrace,
SESSION_TRACE_SCHEMA_VERSION,
type SessionTraceCoverage,
type TurnTrace,
} from '@maka/core/session-trace';
import {
requireEncodedByteLimit,
requireEntityId,
requireExactRecord,
requireShapedRecord,
requireUtf8String,
} from './codec.js';
import { invalidProtocolFrame } from './errors.js';
import { defineOperation } from './operation-spec.js';
export const EXECUTION_INSPECT_CANDIDATE_MAX_ITEMS = 32;
export const EXECUTION_INSPECT_SESSION_MAX_RUNS = 64;
export const EXECUTION_INSPECT_TRACE_PAGE_MAX_TURNS = 16;
export const EXECUTION_INSPECT_RESULT_MAX_BYTES = 48 * 1024;
export const EXECUTION_INSPECT_EVIDENCE_MAX_RECORDS = 4096;
export const EXECUTION_INSPECT_EVIDENCE_MAX_BYTES = 512 * 1024;
const QUERY_ERRORS = [
'host_not_ready',
'host_draining',
'operation_unavailable',
'not_found',
'invalid_request',
'persistence_failed',
'internal_failure',
] as const;
export type ExecutionInspectEntityKind = 'session' | 'agent_run';
export type ExecutionInspectCandidate =
| { readonly kind: 'session'; readonly id: string }
| { readonly kind: 'agent_run'; readonly id: string; readonly sessionId: string };
export interface ExecutionInspectResolveInput {
readonly id: string;
readonly requestedKind?: ExecutionInspectEntityKind;
readonly sessionId?: string;
}
export interface ExecutionInspectResolveResult {
readonly status: 'resolved' | 'not_found' | 'ambiguous';
readonly candidates: readonly ExecutionInspectCandidate[];
readonly truncated: boolean;
}
export type ExecutionInspectQueryInput =
| { readonly kind: 'session'; readonly sessionId: string }
| {
readonly kind: 'agent_run';
readonly sessionId: string;
readonly agentRunId: string;
}
| { readonly kind: 'session_trace_start'; readonly sessionId: string }
| { readonly kind: 'turn_trace'; readonly sessionId: string; readonly turnId: string }
| {
readonly kind: 'session_trace_continue';
readonly sessionId: string;
readonly cursor: string;
};
export type ExecutionInspectQueryResult =
| { readonly kind: 'session'; readonly document: SessionInspectDocument }
| { readonly kind: 'agent_run'; readonly document: AgentRunInspectDocument }
| {
readonly kind: 'turn_trace';
readonly sessionId: string;
readonly turn: TurnTrace;
}
| {
readonly kind: 'session_trace_page';
readonly schemaVersion: typeof SESSION_TRACE_SCHEMA_VERSION;
readonly sessionId: string;
readonly turns: readonly TurnTrace[];
readonly coverage: SessionTraceCoverage;
readonly nextCursor: string | null;
};
export const EXECUTION_INSPECT_OPERATION_SPECS = {
'execution.inspect.resolve': defineOperation<
ExecutionInspectResolveInput,
ExecutionInspectResolveResult,
(typeof QUERY_ERRORS)[number]
>({
mode: 'query',
availability: 'ready',
errors: QUERY_ERRORS,
decodeInput: decodeExecutionInspectResolveInput,
decodeOutput: decodeExecutionInspectResolveResult,
assertOutputForInput: assertResolveOutputForInput,
}),
'execution.inspect.query': defineOperation<
ExecutionInspectQueryInput,
ExecutionInspectQueryResult,
(typeof QUERY_ERRORS)[number]
>({
mode: 'query',
availability: 'ready',
errors: QUERY_ERRORS,
decodeInput: decodeExecutionInspectQueryInput,
decodeOutput: decodeExecutionInspectQueryResult,
assertOutputForInput: assertQueryOutputForInput,
}),
} as const;
export function decodeExecutionInspectResolveInput(value: unknown): ExecutionInspectResolveInput {
const record = requireShapedRecord(
value,
'execution.inspect.resolve input',
['id'],
['requestedKind', 'sessionId'],
);
const requestedKind =
record.requestedKind === undefined
? undefined
: requireExecutionInspectEntityKind(record.requestedKind);
const sessionId =
record.sessionId === undefined
? undefined
: requireEntityId(record.sessionId, 'inspect Session id');
if (sessionId !== undefined && requestedKind !== 'agent_run') {
throw invalidProtocolFrame('Inspect Session filter requires AgentRun kind');
}
return {
id: requireEntityId(record.id, 'inspect target id'),
...(requestedKind ? { requestedKind } : {}),
...(sessionId ? { sessionId } : {}),
};
}
export function decodeExecutionInspectResolveResult(value: unknown): ExecutionInspectResolveResult {
requireEncodedByteLimit(
value,
'execution.inspect.resolve result',
EXECUTION_INSPECT_RESULT_MAX_BYTES,
);
const record = requireExactRecord(value, 'execution.inspect.resolve result', [
'status',
'candidates',
'truncated',
]);
if (!Array.isArray(record.candidates)) {
throw invalidProtocolFrame('Invalid execution inspect candidates');
}
if (record.candidates.length > EXECUTION_INSPECT_CANDIDATE_MAX_ITEMS) {
throw invalidProtocolFrame('Execution inspect candidate page exceeds its item limit');
}
if (typeof record.truncated !== 'boolean') {
throw invalidProtocolFrame('Invalid execution inspect candidate truncation');
}
const status = requireResolveStatus(record.status);
const candidates = record.candidates.map(decodeExecutionInspectCandidate);
const expectedStatus =
candidates.length === 0 && !record.truncated
? 'not_found'
: candidates.length === 1 && !record.truncated
? 'resolved'
: 'ambiguous';
if (status !== expectedStatus) {
throw invalidProtocolFrame('Execution inspect resolution status does not match candidates');
}
return { status, candidates, truncated: record.truncated };
}
export function decodeExecutionInspectQueryInput(value: unknown): ExecutionInspectQueryInput {
const record = requireShapedRecord(
value,
'execution.inspect.query input',
['kind', 'sessionId'],
['agentRunId', 'turnId', 'cursor'],
);
const sessionId = requireEntityId(record.sessionId, 'inspect Session id');
if (record.kind === 'turn_trace') {
requireExactRecord(record, 'Turn trace query', ['kind', 'sessionId', 'turnId']);
return {
kind: 'turn_trace',
sessionId,
turnId: requireEntityId(record.turnId, 'trace Turn id'),
};
}
if (record.kind === 'session_trace_start') {
requireExactRecord(record, 'Session trace start query', ['kind', 'sessionId']);
return { kind: 'session_trace_start', sessionId };
}
if (record.kind === 'session_trace_continue') {
const exact = requireExactRecord(record, 'Session trace continuation query', [
'kind',
'sessionId',
'cursor',
]);
return {
kind: 'session_trace_continue',
sessionId,
cursor: requireTraceCursor(exact.cursor),
};
}
if (record.kind === 'session') {
requireExactRecord(record, 'Session inspect query', ['kind', 'sessionId']);
return { kind: 'session', sessionId };
}
if (record.kind === 'agent_run') {
requireExactRecord(record, 'AgentRun inspect query', ['kind', 'sessionId', 'agentRunId']);
return {
kind: 'agent_run',
sessionId,
agentRunId: requireEntityId(record.agentRunId, 'inspect AgentRun id'),
};
}
throw invalidProtocolFrame('Invalid execution inspect query kind');
}
export function decodeExecutionInspectQueryResult(value: unknown): ExecutionInspectQueryResult {
requireEncodedByteLimit(
value,
'execution.inspect.query result',
EXECUTION_INSPECT_RESULT_MAX_BYTES,
);
const shaped = requireShapedRecord(
value,
'execution.inspect.query result',
['kind'],
['document', 'schemaVersion', 'sessionId', 'turns', 'coverage', 'nextCursor', 'turn'],
);
if (shaped.kind === 'turn_trace') {
const record = requireExactRecord(shaped, 'Turn trace result', ['kind', 'sessionId', 'turn']);
const sessionId = requireEntityId(record.sessionId, 'trace Session id');
if (!isTurnTrace(record.turn)) {
throw invalidProtocolFrame('Invalid Turn trace');
}
return { kind: 'turn_trace', sessionId, turn: record.turn };
}
if (shaped.kind === 'session_trace_page') {
const record = requireExactRecord(shaped, 'Session trace page result', [
'kind',
'schemaVersion',
'sessionId',
'turns',
'coverage',
'nextCursor',
]);
const sessionId = requireEntityId(record.sessionId, 'trace Session id');
const decodedTrace = {
schemaVersion: record.schemaVersion,
sessionId,
turns: record.turns,
coverage: record.coverage,
};
if (
!Array.isArray(record.turns) ||
record.turns.length > EXECUTION_INSPECT_TRACE_PAGE_MAX_TURNS ||
!isSessionTrace(decodedTrace)
) {
throw invalidProtocolFrame('Invalid Session trace page');
}
const nextCursor = record.nextCursor === null ? null : requireTraceCursor(record.nextCursor);
return {
kind: 'session_trace_page',
schemaVersion: SESSION_TRACE_SCHEMA_VERSION,
sessionId,
turns: decodedTrace.turns,
coverage: decodedTrace.coverage,
nextCursor,
};
}
const record = requireExactRecord(shaped, 'execution.inspect.query result', ['kind', 'document']);
if (record.kind === 'session') {
if (
!isSessionInspectDocument(record.document) ||
record.document.agentRuns.length > EXECUTION_INSPECT_SESSION_MAX_RUNS
) {
throw invalidProtocolFrame('Invalid Session inspect document');
}
return { kind: 'session', document: record.document };
}
if (record.kind === 'agent_run') {
if (!isAgentRunInspectDocument(record.document)) {
throw invalidProtocolFrame('Invalid AgentRun inspect document');
}
return { kind: 'agent_run', document: record.document };
}
throw invalidProtocolFrame('Invalid execution inspect query result kind');
}
function decodeExecutionInspectCandidate(value: unknown): ExecutionInspectCandidate {
const record = requireShapedRecord(
value,
'execution inspect candidate',
['kind', 'id'],
['sessionId'],
);
const kind = requireExecutionInspectEntityKind(record.kind);
const sessionId =
record.sessionId === undefined
? undefined
: requireEntityId(record.sessionId, 'candidate Session id');
const id = requireEntityId(record.id, 'candidate id');
if (kind === 'agent_run') {
if (sessionId === undefined) {
throw invalidProtocolFrame('AgentRun inspect candidate requires a Session identity');
}
return { kind, id, sessionId };
}
if (sessionId !== undefined) {
throw invalidProtocolFrame(
'Session inspect candidate cannot include a parent Session identity',
);
}
return { kind, id };
}
function assertResolveOutputForInput(
input: ExecutionInspectResolveInput,
output: ExecutionInspectResolveResult,
): void {
for (const candidate of output.candidates) {
if (
candidate.id !== input.id ||
(input.requestedKind !== undefined && candidate.kind !== input.requestedKind) ||
(input.sessionId !== undefined &&
(candidate.kind !== 'agent_run' || candidate.sessionId !== input.sessionId))
) {
throw invalidProtocolFrame('Execution inspect resolution changed request identity');
}
}
}
function assertQueryOutputForInput(
input: ExecutionInspectQueryInput,
output: ExecutionInspectQueryResult,
): void {
if (input.kind === 'turn_trace') {
if (
output.kind !== 'turn_trace' ||
output.sessionId !== input.sessionId ||
output.turn.turnId !== input.turnId
) {
throw invalidProtocolFrame('Turn trace result changed request identity');
}
return;
}
if (input.kind === 'session_trace_start' || input.kind === 'session_trace_continue') {
if (output.kind !== 'session_trace_page' || output.sessionId !== input.sessionId) {
throw invalidProtocolFrame('Session trace result changed request identity');
}
return;
}
if (input.kind === 'session') {
if (output.kind !== 'session' || output.document.session.sessionId !== input.sessionId) {
throw invalidProtocolFrame('Session inspect result changed request identity');
}
return;
}
if (
output.kind !== 'agent_run' ||
output.document.agentRun.sessionId !== input.sessionId ||
output.document.agentRun.agentRunId !== input.agentRunId
) {
throw invalidProtocolFrame('AgentRun inspect result changed request identity');
}
}
function requireTraceCursor(value: unknown): string {
const cursor = requireUtf8String(value, 'Session trace cursor', 512);
if (!/^[A-Za-z0-9_-]+$/.test(cursor)) {
throw invalidProtocolFrame('Invalid Session trace cursor');
}
return cursor;
}
function requireExecutionInspectEntityKind(value: unknown): ExecutionInspectEntityKind {
if (value === 'session' || value === 'agent_run') return value;
throw invalidProtocolFrame('Invalid execution inspect entity kind');
}
function requireResolveStatus(value: unknown): ExecutionInspectResolveResult['status'] {
if (value === 'resolved' || value === 'not_found' || value === 'ambiguous') return value;
throw invalidProtocolFrame('Invalid execution inspect resolution status');
}