blob: 388c106c77ccf91e17d77d2c928f2d242e3c0e15 [file]
import type { AgentRunHeader } from '@maka/core';
import { failureClassFromCompleteStopReason, type SessionEvent } from '@maka/core';
import type {
SessionBlockedReason,
SessionHeader,
SessionStatus,
StoredMessage,
TurnRecord,
TurnStateMessage,
} from '@maka/core';
export type TurnStateLineage = Partial<
Pick<
TurnStateMessage,
| 'parentTurnId'
| 'retriedFromTurnId'
| 'regeneratedFromTurnId'
| 'branchOfTurnId'
| 'parentSessionId'
>
>;
export interface BuildTurnStateMessageInput {
id: string;
turnId: string;
ts: number;
status: TurnRecord['status'];
lineage?: TurnStateLineage;
errorClass?: string;
abortSource?: string;
partialOutputRetained: boolean;
}
export function buildStatusPatch(
status: SessionStatus,
ts: number,
blockedReason?: SessionBlockedReason,
): Pick<SessionHeader, 'status' | 'blockedReason' | 'statusUpdatedAt'> {
return {
status,
blockedReason: status === 'blocked' ? (blockedReason ?? 'unknown') : undefined,
statusUpdatedAt: ts,
};
}
export function buildTurnStateMessage(input: BuildTurnStateMessageInput): TurnStateMessage {
const lineage = input.lineage ?? {};
return {
type: 'turn_state',
id: input.id,
turnId: input.turnId,
ts: input.ts,
status: input.status,
...(lineage.parentTurnId ? { parentTurnId: lineage.parentTurnId } : {}),
...(lineage.retriedFromTurnId ? { retriedFromTurnId: lineage.retriedFromTurnId } : {}),
...(lineage.regeneratedFromTurnId
? { regeneratedFromTurnId: lineage.regeneratedFromTurnId }
: {}),
...(lineage.branchOfTurnId ? { branchOfTurnId: lineage.branchOfTurnId } : {}),
...(lineage.parentSessionId ? { parentSessionId: lineage.parentSessionId } : {}),
...(input.status === 'aborted' ? { abortedAt: input.ts } : {}),
...(input.status === 'aborted' && input.abortSource ? { abortSource: input.abortSource } : {}),
...(input.status === 'failed' ? { errorClass: input.errorClass ?? 'unknown' } : {}),
partialOutputRetained: input.partialOutputRetained,
};
}
export function turnHasRetainedOutput(messages: readonly StoredMessage[], turnId: string): boolean {
return messages.some(
(message) =>
(message.type === 'assistant' &&
message.turnId === turnId &&
message.text.trim().length > 0) ||
(message.type === 'tool_result' && message.turnId === turnId),
);
}
export function normalizeStopSessionSource(
source: 'stop_button' | 'benchmark_deadline' | 'graph_supervisor' | undefined,
): string | undefined {
switch (source) {
case 'stop_button':
return 'renderer.stop_button';
case 'benchmark_deadline':
return 'benchmark.deadline';
case 'graph_supervisor':
return 'graph.supervisor';
case undefined:
return undefined;
}
}
export function isTerminalRunStatus(status: AgentRunHeader['status']): boolean {
return status === 'completed' || status === 'failed' || status === 'cancelled';
}
export function statusFromEvent(
event: SessionEvent,
options: { allowInteractionResume?: boolean } = {},
): { status: SessionStatus; blockedReason?: SessionBlockedReason } | undefined {
switch (event.type) {
case 'sandbox_boundary_request':
return { status: 'waiting_for_user', blockedReason: 'permission_required' };
case 'user_question_request':
return { status: 'waiting_for_user' };
case 'sandbox_boundary_decision_ack':
if (options.allowInteractionResume === false) return undefined;
return { status: 'running' };
case 'user_question_answer_ack':
if (options.allowInteractionResume === false) return undefined;
return { status: 'running' };
case 'error':
return { status: 'blocked', blockedReason: blockedReasonFromErrorReason(event.reason) };
case 'abort':
return { status: 'aborted' };
case 'complete':
if (event.stopReason === 'user_stop') return { status: 'aborted' };
if (event.stopReason === 'error') return { status: 'blocked', blockedReason: 'unknown' };
return { status: 'active' };
default:
return undefined;
}
}
export function turnStatusFromEvent(
event: SessionEvent,
): { status: TurnRecord['status']; errorClass?: string } | undefined {
switch (event.type) {
case 'abort':
return { status: 'aborted' };
case 'error':
return { status: 'failed', errorClass: event.reason ?? event.code ?? 'unknown' };
case 'complete': {
if (event.stopReason === 'user_stop') return { status: 'aborted' };
const errorClass = failureClassFromCompleteStopReason(event.stopReason);
if (errorClass) return { status: 'failed', errorClass };
return { status: 'completed' };
}
default:
return undefined;
}
}
function blockedReasonFromErrorReason(reason: string | undefined): SessionBlockedReason {
if (!reason) return 'unknown';
if (reason === 'permission_required') return 'permission_required';
if (reason === 'tool_failed') return 'tool_failed';
if (reason === 'auth' || reason.includes('api_key') || reason.includes('connection'))
return 'NO_REAL_CONNECTION';
return 'unknown';
}