219 lines
5.5 KiB
TypeScript
219 lines
5.5 KiB
TypeScript
export type SessionRunQueuedPrompt = {
|
|
id: string;
|
|
text: string;
|
|
};
|
|
|
|
export type SessionRunPhase = 'idle' | 'posting' | 'running' | 'aborting';
|
|
|
|
export type SessionRunTerminalReason = 'completed' | 'aborted' | 'failed';
|
|
|
|
export type SessionRunState<TPrompt extends SessionRunQueuedPrompt = SessionRunQueuedPrompt> = {
|
|
phase: SessionRunPhase;
|
|
runId: number | null;
|
|
promptId: string | null;
|
|
queue: TPrompt[];
|
|
terminalReason: SessionRunTerminalReason | null;
|
|
error: string | null;
|
|
suppressNextAbortError: boolean;
|
|
};
|
|
|
|
export type SessionRunEvent<TPrompt extends SessionRunQueuedPrompt = SessionRunQueuedPrompt> =
|
|
| { type: 'send_started'; runId: number; promptId: string }
|
|
| { type: 'post_accepted'; runId: number }
|
|
| { type: 'remote_busy'; runId: number }
|
|
| { type: 'remote_idle'; runId: number }
|
|
| { type: 'remote_aborted'; runId: number }
|
|
| { type: 'remote_failed'; runId: number; error: string }
|
|
| { type: 'abort_requested'; runId: number }
|
|
| { type: 'abort_confirmed'; runId: number }
|
|
| { type: 'abort_failed'; runId: number; error: string }
|
|
| { type: 'queue_prompt'; prompt: TPrompt }
|
|
| { type: 'cancel_queued_prompt'; promptId: string };
|
|
|
|
export function createIdleSessionRunState<TPrompt extends SessionRunQueuedPrompt>(
|
|
queue: TPrompt[] = [],
|
|
): SessionRunState<TPrompt> {
|
|
return {
|
|
phase: 'idle',
|
|
runId: null,
|
|
promptId: null,
|
|
queue,
|
|
terminalReason: null,
|
|
error: null,
|
|
suppressNextAbortError: false,
|
|
};
|
|
}
|
|
|
|
export function isCurrentRunEvent<TPrompt extends SessionRunQueuedPrompt>(
|
|
state: SessionRunState<TPrompt>,
|
|
runId: number,
|
|
): boolean {
|
|
return state.runId === runId;
|
|
}
|
|
|
|
export function isSessionRunActive<TPrompt extends SessionRunQueuedPrompt>(
|
|
state: SessionRunState<TPrompt>,
|
|
): boolean {
|
|
return state.phase === 'posting' || state.phase === 'running';
|
|
}
|
|
|
|
export function canStartQueuedPrompt<TPrompt extends SessionRunQueuedPrompt>(
|
|
state: SessionRunState<TPrompt>,
|
|
): boolean {
|
|
return state.phase === 'idle'
|
|
&& state.terminalReason !== 'failed'
|
|
&& state.queue.length > 0;
|
|
}
|
|
|
|
export function takeNextQueuedPrompt<TPrompt extends SessionRunQueuedPrompt>(
|
|
state: SessionRunState<TPrompt>,
|
|
): {
|
|
prompt: TPrompt | null;
|
|
state: SessionRunState<TPrompt>;
|
|
} {
|
|
const [prompt, ...queue] = state.queue;
|
|
return {
|
|
prompt: prompt ?? null,
|
|
state: {
|
|
...state,
|
|
queue,
|
|
},
|
|
};
|
|
}
|
|
|
|
export function didSuppressAbortError<TPrompt extends SessionRunQueuedPrompt>(
|
|
previousState: SessionRunState<TPrompt>,
|
|
nextState: SessionRunState<TPrompt>,
|
|
): boolean {
|
|
return previousState.suppressNextAbortError
|
|
&& !nextState.suppressNextAbortError
|
|
&& previousState.phase === nextState.phase
|
|
&& previousState.runId === nextState.runId
|
|
&& previousState.promptId === nextState.promptId
|
|
&& nextState.terminalReason === previousState.terminalReason
|
|
&& nextState.error === null;
|
|
}
|
|
|
|
export function transitionSessionRunState<TPrompt extends SessionRunQueuedPrompt>(
|
|
state: SessionRunState<TPrompt>,
|
|
event: SessionRunEvent<TPrompt>,
|
|
): SessionRunState<TPrompt> {
|
|
if (event.type === 'send_started') {
|
|
return {
|
|
phase: 'posting',
|
|
runId: event.runId,
|
|
promptId: event.promptId,
|
|
queue: state.queue,
|
|
terminalReason: null,
|
|
error: null,
|
|
suppressNextAbortError: state.suppressNextAbortError,
|
|
};
|
|
}
|
|
|
|
if (event.type === 'queue_prompt') {
|
|
return {
|
|
...state,
|
|
queue: [...state.queue, event.prompt],
|
|
error: null,
|
|
};
|
|
}
|
|
|
|
if (event.type === 'cancel_queued_prompt') {
|
|
return {
|
|
...state,
|
|
queue: state.queue.filter((prompt) => prompt.id !== event.promptId),
|
|
};
|
|
}
|
|
|
|
if (!isCurrentRunEvent(state, event.runId)) {
|
|
return state;
|
|
}
|
|
|
|
if (
|
|
state.phase === 'idle'
|
|
&& state.terminalReason !== null
|
|
&& (
|
|
event.type === 'post_accepted'
|
|
|| event.type === 'remote_busy'
|
|
|| event.type === 'remote_idle'
|
|
)
|
|
) {
|
|
return state;
|
|
}
|
|
|
|
switch (event.type) {
|
|
case 'post_accepted':
|
|
return state;
|
|
case 'remote_busy':
|
|
return {
|
|
...state,
|
|
phase: 'running',
|
|
terminalReason: null,
|
|
error: null,
|
|
};
|
|
case 'remote_idle':
|
|
if (state.phase === 'posting') return state;
|
|
return {
|
|
...state,
|
|
phase: 'idle',
|
|
terminalReason: 'completed',
|
|
error: null,
|
|
suppressNextAbortError: false,
|
|
};
|
|
case 'remote_aborted':
|
|
if (state.suppressNextAbortError) {
|
|
return {
|
|
...state,
|
|
error: null,
|
|
suppressNextAbortError: false,
|
|
};
|
|
}
|
|
return {
|
|
...state,
|
|
phase: 'idle',
|
|
queue: [],
|
|
terminalReason: 'aborted',
|
|
error: null,
|
|
suppressNextAbortError: false,
|
|
};
|
|
case 'remote_failed':
|
|
return {
|
|
...state,
|
|
phase: 'idle',
|
|
queue: [],
|
|
terminalReason: 'failed',
|
|
error: event.error,
|
|
suppressNextAbortError: false,
|
|
};
|
|
case 'abort_requested':
|
|
return {
|
|
...state,
|
|
phase: 'aborting',
|
|
queue: [],
|
|
terminalReason: 'aborted',
|
|
error: null,
|
|
suppressNextAbortError: true,
|
|
};
|
|
case 'abort_confirmed':
|
|
return {
|
|
...state,
|
|
phase: 'idle',
|
|
queue: [],
|
|
terminalReason: 'aborted',
|
|
error: null,
|
|
suppressNextAbortError: false,
|
|
};
|
|
case 'abort_failed':
|
|
return {
|
|
...state,
|
|
phase: 'idle',
|
|
queue: [],
|
|
terminalReason: 'failed',
|
|
error: event.error,
|
|
suppressNextAbortError: false,
|
|
};
|
|
default:
|
|
return state;
|
|
}
|
|
}
|