kimi-code / packages /transcript /src /store /agentTranscript.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
4e23b01 verified
Raw
History Blame Contribute Delete
4.84 kB
import type { AgentId, AttachmentId, InteractionId, PromptId, TaskId, TodoId, TurnId } from '../model/ids';
import type { TranscriptAttachment } from '../model/attachment';
import type { TranscriptInteraction } from '../model/interaction';
import type { TranscriptItem } from '../model/item';
import type { TranscriptMeta } from '../model/meta';
import type { TranscriptPrompt } from '../model/prompt';
import type { TranscriptTask } from '../model/task';
import type { TranscriptTodo } from '../model/todo';
import type { TranscriptTurn } from '../model/turn';
import {
EMPTY_AGENT_STATE,
applyOperation,
type AgentState,
} from '../ops/apply';
import type {
AgentTranscriptSnapshot,
AppendTarget,
AppliedOps,
TranscriptChangeEvent,
TranscriptOperation,
} from '../ops/operation';
export type TranscriptListener = (event: TranscriptChangeEvent) => void;
export interface Disposable {
dispose(): void;
}
export class AgentTranscript {
#state: AgentState = EMPTY_AGENT_STATE;
readonly #listeners = new Set<TranscriptListener>();
constructor(readonly agentId: AgentId) {}
receive(ops: readonly TranscriptOperation[]): AppliedOps {
return this.apply(ops);
}
apply(ops: readonly TranscriptOperation[]): AppliedOps {
const accepted: TranscriptOperation[] = [];
let gap: AppliedOps['gap'];
let state = this.#state;
for (const op of ops) {
const result = applyOperation(state, op);
if (result.gap) {
gap = { target: (op as { target: AppendTarget }).target, ...result.gap };
continue;
}
if (!result.changed) continue;
state = result.state;
accepted.push(op);
}
this.#state = state;
if (accepted.length > 0) {
const event: TranscriptChangeEvent = { agentId: this.agentId, ops: accepted };
for (const listener of this.#listeners) listener(event);
}
return { accepted, gap };
}
onChange(listener: TranscriptListener): Disposable {
this.#listeners.add(listener);
return { dispose: () => void this.#listeners.delete(listener) };
}
getItems(): readonly TranscriptItem[] {
return this.#state.items;
}
getTurn(turnId: TurnId): TranscriptTurn | undefined {
const item = this.#state.items.find(
(entry) => entry.kind === 'turn' && entry.turnId === turnId,
);
return item?.kind === 'turn' ? item : undefined;
}
getTasks(): ReadonlyMap<TaskId, TranscriptTask> {
return this.#state.tasks;
}
getTask(taskId: TaskId): TranscriptTask | undefined {
return this.#state.tasks.get(taskId);
}
getInteractions(): ReadonlyMap<InteractionId, TranscriptInteraction> {
return this.#state.interactions;
}
getInteraction(interactionId: InteractionId): TranscriptInteraction | undefined {
return this.#state.interactions.get(interactionId);
}
getAttachments(): ReadonlyMap<AttachmentId, TranscriptAttachment> {
return this.#state.attachments;
}
getAttachment(attachmentId: AttachmentId): TranscriptAttachment | undefined {
return this.#state.attachments.get(attachmentId);
}
getTodos(): ReadonlyMap<TodoId, TranscriptTodo> {
return this.#state.todos;
}
getTodo(todoId: TodoId): TranscriptTodo | undefined {
return this.#state.todos.get(todoId);
}
getPrompts(): ReadonlyMap<PromptId, TranscriptPrompt> {
return this.#state.prompts;
}
getPrompt(promptId: PromptId): TranscriptPrompt | undefined {
return this.#state.prompts.get(promptId);
}
getMeta(): TranscriptMeta {
return this.#state.meta;
}
listPendingInteractions(): readonly InteractionId[] {
return [...this.#state.pendingInteractions];
}
get hasMoreOlder(): boolean {
return this.#state.hasMoreOlder;
}
snapshot(window?: { tailTurns: number }): AgentTranscriptSnapshot {
let items = this.#state.items;
let hasMoreOlder = this.#state.hasMoreOlder;
if (window !== undefined) {
const turnCount = items.reduce((n, entry) => (entry.kind === 'turn' ? n + 1 : n), 0);
if (turnCount > window.tailTurns) {
const skip = turnCount - window.tailTurns;
const kept: TranscriptItem[] = [];
let seen = 0;
for (const entry of items) {
if (entry.kind === 'turn') {
seen += 1;
if (seen <= skip) continue;
kept.push(entry);
} else if (seen > skip) {
kept.push(entry);
}
}
items = kept;
hasMoreOlder = true;
}
}
return {
items,
tasks: [...this.#state.tasks.values()],
interactions: [...this.#state.interactions.values()],
attachments: [...this.#state.attachments.values()],
todos: [...this.#state.todos.values()],
prompts: [...this.#state.prompts.values()],
meta: this.#state.meta,
hasMoreOlder,
};
}
}