SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
4e23b01 verified
Raw
History Blame Contribute Delete
21 kB
import type { AttachmentId, InteractionId, PromptId, TaskId, TodoId, TurnId } from '../model/ids';
import { turnOrdinal } from '../model/ids';
import type { TranscriptAttachment } from '../model/attachment';
import type { TranscriptFrame } from '../model/frame';
import type { TranscriptInteraction } from '../model/interaction';
import type { TranscriptItem } from '../model/item';
import type { TranscriptMeta, TranscriptMetaMerge } from '../model/meta';
import type { TranscriptPrompt } from '../model/prompt';
import type { TranscriptTask } from '../model/task';
import type { TranscriptTodo } from '../model/todo';
import type { TranscriptStep, TranscriptTurn } from '../model/turn';
import type {
AppendOp,
TranscriptOperation,
TurnHeader,
StepHeader,
} from './operation';
export interface AgentState {
readonly items: readonly TranscriptItem[];
readonly tasks: ReadonlyMap<TaskId, TranscriptTask>;
readonly interactions: ReadonlyMap<InteractionId, TranscriptInteraction>;
readonly attachments: ReadonlyMap<AttachmentId, TranscriptAttachment>;
readonly todos: ReadonlyMap<TodoId, TranscriptTodo>;
readonly prompts: ReadonlyMap<PromptId, TranscriptPrompt>;
readonly meta: TranscriptMeta;
readonly pendingInteractions: ReadonlySet<InteractionId>;
readonly hasMoreOlder: boolean;
}
export const EMPTY_AGENT_STATE: AgentState = {
items: [],
tasks: new Map(),
interactions: new Map(),
attachments: new Map(),
todos: new Map(),
prompts: new Map(),
meta: {},
pendingInteractions: new Set(),
hasMoreOlder: false,
};
export interface ApplyResult {
readonly state: AgentState;
readonly changed: boolean;
readonly gap?: { readonly expected: number; readonly got: number };
}
export function applyOperation(state: AgentState, op: TranscriptOperation): ApplyResult {
switch (op.op) {
case 'reset':
return applyReset(state, op);
case 'turn.upsert':
return applyTurnUpsert(state, op.turn);
case 'step.upsert':
return applyStepUpsert(state, op.turnId, op.step);
case 'frame.upsert':
return applyFrameUpsert(state, op);
case 'append':
return applyAppend(state, op);
case 'marker.upsert':
return applyItemUpsert(state, op.item, op.item.markerId, op.beforeTurn);
case 'taskref.upsert':
return applyItemUpsert(state, op.item, op.item.refId, op.beforeTurn);
case 'task.upsert':
return applyTaskUpsert(state, op.task);
case 'interaction.upsert':
return applyInteractionUpsert(state, op.interaction);
case 'attachment.upsert':
return applyAttachmentUpsert(state, op.attachment);
case 'todo.upsert':
return applyTodoUpsert(state, op.todo);
case 'prompt.upsert':
return applyPromptUpsert(state, op.prompt);
case 'meta.merge':
return applyMetaMerge(state, op.meta);
case 'items.remove':
return applyItemsRemove(state, op.ids);
}
}
function applyReset(state: AgentState, op: Extract<TranscriptOperation, { op: 'reset' }>): ApplyResult {
const pending = new Set<InteractionId>();
for (const interaction of op.snapshot.interactions) {
if (interaction.state === 'pending') pending.add(interaction.interactionId);
}
return {
state: {
items: op.snapshot.items,
tasks: new Map(op.snapshot.tasks.map((task) => [task.taskId, task])),
interactions: new Map(
op.snapshot.interactions.map((interaction) => [interaction.interactionId, interaction]),
),
attachments: new Map(
op.snapshot.attachments.map((attachment) => [attachment.attachmentId, attachment]),
),
todos: new Map(op.snapshot.todos.map((todo) => [todo.todoId, todo])),
prompts: new Map(op.snapshot.prompts.map((prompt) => [prompt.promptId, prompt])),
meta: op.snapshot.meta,
pendingInteractions: pending,
hasMoreOlder: op.snapshot.hasMoreOlder ?? false,
},
changed: true,
};
}
function turnHeaderToTurn(header: TurnHeader, steps: readonly TranscriptStep[]): TranscriptTurn {
return { ...header, kind: 'turn', steps: [...steps] };
}
function skeletonTurn(turnId: TurnId): TranscriptTurn {
return {
kind: 'turn',
turnId,
ordinal: turnOrdinal(turnId),
state: 'running',
origin: { kind: 'other' },
steps: [],
};
}
function skeletonStep(stepId: string, turnId: TurnId): TranscriptStep {
const ordinal = Number(stepId.slice(turnId.length + 1)) || 0;
return { kind: 'step', stepId, turnId, ordinal, state: 'running', frames: [] };
}
function getTurn(state: AgentState, turnId: TurnId): TranscriptTurn | undefined {
const item = state.items.find((entry) => entry.kind === 'turn' && entry.turnId === turnId);
return item?.kind === 'turn' ? item : undefined;
}
function insertTurn(items: readonly TranscriptItem[], turn: TranscriptTurn): readonly TranscriptItem[] {
const next = [...items];
let at = next.length;
for (let i = 0; i < next.length; i += 1) {
const entry = next[i];
if (entry?.kind === 'turn' && entry.ordinal > turn.ordinal) {
at = i;
break;
}
}
next.splice(at, 0, turn);
return next;
}
function replaceTurn(
items: readonly TranscriptItem[],
turnId: TurnId,
fn: (turn: TranscriptTurn) => TranscriptTurn,
): readonly TranscriptItem[] {
return items.map((entry) =>
entry.kind === 'turn' && entry.turnId === turnId ? fn(entry) : entry,
);
}
function applyTurnUpsert(state: AgentState, header: TurnHeader): ApplyResult {
const existing = getTurn(state, header.turnId);
if (existing) {
if (turnEquals(existing, header)) return { state, changed: false };
return {
state: {
...state,
items: replaceTurn(state.items, header.turnId, (turn) =>
turnHeaderToTurn(header, turn.steps),
),
},
changed: true,
};
}
return {
state: { ...state, items: insertTurn(state.items, turnHeaderToTurn(header, [])) },
changed: true,
};
}
function turnEquals(turn: TranscriptTurn, header: TurnHeader): boolean {
return (
turn.ordinal === header.ordinal &&
turn.triggerPromptId === header.triggerPromptId &&
turn.state === header.state &&
turn.prompt === header.prompt &&
turn.attachmentIds === header.attachmentIds &&
turn.startedAt === header.startedAt &&
turn.endedAt === header.endedAt &&
turn.origin.kind === header.origin.kind &&
turn.origin.payload === header.origin.payload &&
turn.usage === header.usage &&
turn.durationMs === header.durationMs &&
turn.error === header.error
);
}
function applyStepUpsert(state: AgentState, turnId: TurnId, header: StepHeader): ApplyResult {
const turn = getTurn(state, turnId) ?? skeletonTurn(turnId);
const stepIndex = turn.steps.findIndex((step) => step.stepId === header.stepId);
let steps: readonly TranscriptStep[];
let changed = true;
if (stepIndex >= 0) {
const current = turn.steps[stepIndex];
if (current && stepEquals(current, header)) {
changed = false;
steps = turn.steps;
} else {
steps = turn.steps.map((step) =>
step.stepId === header.stepId ? { ...header, kind: 'step' as const, frames: step.frames } : step,
);
}
} else {
steps = [...turn.steps, { ...header, kind: 'step' as const, frames: [] }].toSorted(
(a, b) => a.ordinal - b.ordinal,
);
}
if (!changed) return { state, changed: false };
const nextTurn: TranscriptTurn = { ...turn, steps: [...steps] };
const items = getTurn(state, turnId)
? replaceTurn(state.items, turnId, () => nextTurn)
: insertTurn(state.items, nextTurn);
return { state: { ...state, items }, changed: true };
}
function stepEquals(step: TranscriptStep, header: StepHeader): boolean {
return (
step.ordinal === header.ordinal &&
step.state === header.state &&
step.startedAt === header.startedAt &&
step.endedAt === header.endedAt &&
step.usage === header.usage &&
step.finishReason === header.finishReason &&
step.timing === header.timing &&
step.retry === header.retry &&
step.endReason === header.endReason &&
step.endMessage === header.endMessage
);
}
function applyFrameUpsert(
state: AgentState,
op: Extract<TranscriptOperation, { op: 'frame.upsert' }>,
): ApplyResult {
const turn = getTurn(state, op.turnId) ?? skeletonTurn(op.turnId);
const step = turn.steps.find((entry) => entry.stepId === op.stepId) ?? skeletonStep(op.stepId, op.turnId);
const existing = step.frames.findIndex((frame) => frame.frameId === op.frame.frameId);
let frames: readonly TranscriptFrame[];
if (existing >= 0) {
const current = step.frames[existing];
if (current !== undefined && frameEquals(current, op.frame)) {
return { state, changed: false };
}
frames = step.frames.map((frame) => (frame.frameId === op.frame.frameId ? op.frame : frame));
} else {
frames = [...step.frames, op.frame];
}
const nextStep: TranscriptStep = { ...step, frames: [...frames] };
const steps = turn.steps.some((entry) => entry.stepId === op.stepId)
? turn.steps.map((entry) => (entry.stepId === op.stepId ? nextStep : entry))
: [...turn.steps, nextStep].toSorted((a, b) => a.ordinal - b.ordinal);
const nextTurn: TranscriptTurn = { ...turn, steps };
const items = getTurn(state, op.turnId)
? replaceTurn(state.items, op.turnId, () => nextTurn)
: insertTurn(state.items, nextTurn);
return {
state: { ...state, items },
changed: true,
};
}
function frameEquals(a: TranscriptFrame, b: TranscriptFrame): boolean {
if (a.kind !== b.kind) return false;
if (a.kind === 'text' && b.kind === 'text') {
return (
a.text === b.text &&
a.role === b.role &&
a.attachmentIds === b.attachmentIds &&
a.taskId === b.taskId
);
}
if (a.kind === 'thinking' && b.kind === 'thinking') return a.text === b.text;
if (a.kind === 'tool' && b.kind === 'tool') {
return (
a.state === b.state &&
a.toolCallId === b.toolCallId &&
a.name === b.name &&
a.view === b.view &&
a.input === b.input &&
a.output === b.output &&
a.display === b.display &&
a.error === b.error &&
a.inputText === b.inputText &&
a.progress === b.progress &&
a.taskId === b.taskId &&
a.approvalId === b.approvalId &&
a.todoId === b.todoId &&
a.agentRefs === b.agentRefs
);
}
if (a.kind === 'notice' && b.kind === 'notice') {
return a.message === b.message && a.level === b.level && a.detail === b.detail;
}
return false;
}
function applyAppend(state: AgentState, op: AppendOp): ApplyResult {
if (op.target.type === 'task') return applyTaskAppend(state, op);
const { turnId, stepId, frameId } = op.target;
const turn = getTurn(state, turnId);
const step = turn?.steps.find((entry) => entry.stepId === stepId);
const frame = step?.frames.find((entry) => entry.frameId === frameId);
if (!turn || !step || !frame || (frame.kind !== 'text' && frame.kind !== 'thinking')) {
return { state, changed: false, gap: { expected: 0, got: op.offset } };
}
const merged = appendAtOffset(frame.text, op.offset, op.text);
if (merged.gap) return { state, changed: false, gap: merged.gap };
if (!merged.changed) return { state, changed: false };
const nextFrame = { ...frame, text: merged.text };
const nextStep: TranscriptStep = {
...step,
frames: step.frames.map((entry) => (entry.frameId === frameId ? nextFrame : entry)),
};
const nextTurn: TranscriptTurn = {
...turn,
steps: turn.steps.map((entry) => (entry.stepId === stepId ? nextStep : entry)),
};
return {
state: { ...state, items: replaceTurn(state.items, turnId, () => nextTurn) },
changed: true,
};
}
function applyTaskAppend(state: AgentState, op: AppendOp): ApplyResult {
if (op.target.type !== 'task') throw new Error('unreachable');
const taskId = op.target.taskId;
const task = state.tasks.get(taskId);
const current = task?.outputTail ?? '';
const merged = appendAtOffset(current, op.offset, op.text);
if (merged.gap) return { state, changed: false, gap: merged.gap };
if (!merged.changed) return { state, changed: false };
const nextTask: TranscriptTask = task
? { ...task, outputTail: merged.text }
: { taskId, kind: 'other', state: 'running', detached: false, outputTail: merged.text };
const tasks = new Map(state.tasks);
tasks.set(taskId, nextTask);
return { state: { ...state, tasks }, changed: true };
}
export function appendAtOffset(
local: string,
offset: number,
chunk: string,
): { text: string; changed: boolean; gap?: { expected: number; got: number } } {
if (offset > local.length) return { text: local, changed: false, gap: { expected: local.length, got: offset } };
if (local.slice(offset, offset + chunk.length) === chunk) {
return { text: local, changed: false };
}
const overlap = local.length - offset;
if (local.slice(offset) !== chunk.slice(0, overlap)) {
return { text: local, changed: false, gap: { expected: local.length, got: offset } };
}
const novel = overlap > 0 ? chunk.slice(overlap) : chunk;
if (novel.length === 0) return { text: local, changed: false };
return { text: local.slice(0, offset) + chunk, changed: true };
}
function applyItemUpsert(
state: AgentState,
item: TranscriptItem,
id: string,
beforeTurn?: number,
): ApplyResult {
const exists = state.items.some((entry) => itemIdOf(entry) === id);
if (exists) {
let changed = false;
const items = state.items.map((entry) => {
if (itemIdOf(entry) !== id) return entry;
if (entry === item) return entry;
changed = true;
return item;
});
if (!changed) return { state, changed: false };
return { state: { ...state, items }, changed: true };
}
if (beforeTurn !== undefined) {
const items = [...state.items];
let at = items.length;
for (let i = 0; i < items.length; i += 1) {
const entry = items[i];
if (entry?.kind === 'turn' && entry.ordinal >= beforeTurn) {
at = i;
break;
}
}
items.splice(at, 0, item);
return { state: { ...state, items }, changed: true };
}
return { state: { ...state, items: [...state.items, item] }, changed: true };
}
function itemIdOf(item: TranscriptItem): string {
switch (item.kind) {
case 'turn':
return item.turnId;
case 'marker':
return item.markerId;
case 'taskref':
return item.refId;
}
}
function applyItemsRemove(state: AgentState, ids: readonly string[]): ApplyResult {
const drop = new Set(ids);
const removedTurns = state.items.filter(
(entry): entry is TranscriptTurn => entry.kind === 'turn' && drop.has(entry.turnId),
);
const items = state.items.filter((entry) => !drop.has(itemIdOf(entry)));
if (items.length === state.items.length) return { state, changed: false };
let pending = state.pendingInteractions;
let interactions = state.interactions;
if (removedTurns.length > 0) {
const anchoredToolCallIds = new Set<string>();
const nextPending = new Set(pending);
const deadEntityIds = new Set<InteractionId>();
for (const turn of removedTurns) {
for (const step of turn.steps) {
for (const frame of step.frames) {
if (frame.kind === 'tool') anchoredToolCallIds.add(frame.toolCallId);
}
}
}
for (const interaction of interactions.values()) {
if (interaction.toolCallId !== undefined && anchoredToolCallIds.has(interaction.toolCallId)) {
deadEntityIds.add(interaction.interactionId);
nextPending.delete(interaction.interactionId);
}
}
if (deadEntityIds.size > 0) {
const nextInteractions = new Map(interactions);
for (const id of deadEntityIds) nextInteractions.delete(id);
interactions = nextInteractions;
}
pending = nextPending;
}
return { state: { ...state, items, interactions, pendingInteractions: pending }, changed: true };
}
function applyTaskUpsert(state: AgentState, task: TranscriptTask): ApplyResult {
const current = state.tasks.get(task.taskId);
if (current && taskEquals(current, task)) return { state, changed: false };
const tasks = new Map(state.tasks);
tasks.set(task.taskId, task);
return { state: { ...state, tasks }, changed: true };
}
function applyInteractionUpsert(
state: AgentState,
interaction: TranscriptInteraction,
): ApplyResult {
const current = state.interactions.get(interaction.interactionId);
if (current && interactionEquals(current, interaction)) return { state, changed: false };
const interactions = new Map(state.interactions);
interactions.set(interaction.interactionId, interaction);
let pending = state.pendingInteractions;
if (interaction.state === 'pending') {
if (!pending.has(interaction.interactionId)) {
const next = new Set(pending);
next.add(interaction.interactionId);
pending = next;
}
} else if (pending.has(interaction.interactionId)) {
const next = new Set(pending);
next.delete(interaction.interactionId);
pending = next;
}
return { state: { ...state, interactions, pendingInteractions: pending }, changed: true };
}
function interactionEquals(a: TranscriptInteraction, b: TranscriptInteraction): boolean {
return (
a.interactionKind === b.interactionKind &&
a.toolCallId === b.toolCallId &&
a.state === b.state &&
a.request === b.request &&
a.response === b.response
);
}
function applyAttachmentUpsert(
state: AgentState,
attachment: TranscriptAttachment,
): ApplyResult {
const current = state.attachments.get(attachment.attachmentId);
if (current && attachmentEquals(current, attachment)) return { state, changed: false };
const attachments = new Map(state.attachments);
attachments.set(attachment.attachmentId, attachment);
return { state: { ...state, attachments }, changed: true };
}
function attachmentEquals(a: TranscriptAttachment, b: TranscriptAttachment): boolean {
return (
a.mediaType === b.mediaType &&
a.name === b.name &&
a.size === b.size &&
a.source === b.source &&
a.placeholder === b.placeholder
);
}
function applyTodoUpsert(state: AgentState, todo: TranscriptTodo): ApplyResult {
const current = state.todos.get(todo.todoId);
if (current && todoEquals(current, todo)) return { state, changed: false };
const todos = new Map(state.todos);
todos.set(todo.todoId, todo);
return { state: { ...state, todos }, changed: true };
}
function todoEquals(a: TranscriptTodo, b: TranscriptTodo): boolean {
return a.items === b.items && a.updatedAt === b.updatedAt;
}
function applyPromptUpsert(state: AgentState, prompt: TranscriptPrompt): ApplyResult {
const current = state.prompts.get(prompt.promptId);
if (current && promptEquals(current, prompt)) return { state, changed: false };
const prompts = new Map(state.prompts);
prompts.set(prompt.promptId, prompt);
return { state: { ...state, prompts }, changed: true };
}
function promptEquals(a: TranscriptPrompt, b: TranscriptPrompt): boolean {
return (
a.status === b.status &&
a.userMessageId === b.userMessageId &&
a.content === b.content &&
a.createdAt === b.createdAt &&
a.finishedAt === b.finishedAt &&
a.steeredAt === b.steeredAt
);
}
function taskEquals(a: TranscriptTask, b: TranscriptTask): boolean {
return (
a.kind === b.kind &&
a.state === b.state &&
a.detached === b.detached &&
a.description === b.description &&
a.agentId === b.agentId &&
a.outputTail === b.outputTail &&
a.startedAt === b.startedAt &&
a.endedAt === b.endedAt &&
a.resultSummary === b.resultSummary &&
a.error === b.error &&
a.stateReason === b.stateReason &&
a.usage === b.usage
);
}
function applyMetaMerge(state: AgentState, meta: TranscriptMetaMerge): ApplyResult {
const modes =
meta.modes !== undefined
? {
plan: meta.modes.plan === null ? undefined : (meta.modes.plan ?? state.meta.modes?.plan),
swarm: meta.modes.swarm === null ? undefined : (meta.modes.swarm ?? state.meta.modes?.swarm),
tower: meta.modes.tower === null ? undefined : (meta.modes.tower ?? state.meta.modes?.tower),
}
: state.meta.modes;
const agent =
meta.agent !== undefined ? { ...state.meta.agent, ...meta.agent } : state.meta.agent;
const next: TranscriptMeta = {
goal: meta.goal === null ? undefined : (meta.goal ?? state.meta.goal),
activity: meta.activity ?? state.meta.activity,
modes:
modes !== undefined &&
modes.plan === undefined &&
modes.swarm === undefined &&
modes.tower === undefined
? undefined
: modes,
agent,
};
if (
next.goal === state.meta.goal &&
next.activity === state.meta.activity &&
next.modes === state.meta.modes &&
next.agent === state.meta.agent
) {
return { state, changed: false };
}
return { state: { ...state, meta: next }, changed: true };
}