import type { Branded } from '@deepseek-ai/dsh-brand'; import type { AssistantMessage, CallId, LlmCallConfig, LlmCallConfigAdapterDefaults, LlmFailure, StreamChunk, TokenUsage, ToolResultMessage, ToolSchema, UserMessage } from '@deepseek-ai/dsh-llm'; import type { JsonValue } from './json.ts'; /** Identifies one session in the store (and its persistence artifacts). */ export type SessionId = Branded<'SessionId'>; /** * Brand a string as a {@link SessionId}. * @param id - the raw session id string. * @returns the same string, branded (a compile-time cast — no runtime cost). */ export declare function SessionId(id: string): SessionId; /** * The on-disk session format version, stamped into every newly-written {@link SessionHeader} * and enforced by every persistence backend on load. The single source of truth for the * version — write sites and the load-time check all read it. * While the harness is unreleased it is pinned at `0`: no compatibility is * implied, incompatible logs are rejected, and no migration is provided. A * monotonic version policy starts with the first tagged release. */ export declare const SESSION_FORMAT_VERSION = 0; /** * Immutable validated storage metadata, kept outside the conversation event log. */ export interface SessionHeader { /** * On-disk format version, stamped from {@link SESSION_FORMAT_VERSION} when the * session is created. A persistence backend rejects any other version on load * (no migration — see the constant). */ readonly version: number; /** The session's id (mirrors the {@link Session}'s id). */ readonly id: SessionId; /** Non-negative safe-integer Unix epoch milliseconds when the session was created. */ readonly createdAt: number; /** Absolute working directory the session was created in (if any). */ readonly cwd?: string; /** The session this one was forked from (seed lineage), if any. */ readonly parentSession?: SessionId; /** * How many leading events were inherited through a seed. Persisting this * boundary lets resume and replay distinguish parent history from child work. */ readonly seedLength?: number; /** * Coarse product classification for a session created as a subagent child. * This is presentation metadata, not proof that the child is continuable. */ readonly origin?: 'subagent'; /** * Delegation depth: absent (zero) for a top-level session, parent depth + 1 * for a subagent child. Persisted so a recursion budget survives restart and * resume — a runtime-only depth would reset a resumed child to top-level. */ readonly delegationDepth?: number; /** * Id of the agent preset this session's agent was composed from, when the * deployment composes per session. Durable because the preset decides the * session's tools and prompt: a resume that restored a different composition * would replay history the model can no longer act on. */ readonly agentPreset?: string; } /** * Options for creating a {@link Session} via the store. `seed` replays/forks * an existing event log; `meta` carries the caller-supplied storage fields the * store folds into a {@link SessionHeader}. */ export interface CreateSessionOptions { /** Initial replay or fork history supplied at construction. */ readonly seed?: readonly SessionEvent[]; /** * Storage metadata read once before publication. `seedLength` is explicit * because a resumed seed contains the full stored log, not only its inherited prefix. */ readonly meta?: { readonly cwd?: string; readonly parentSession?: SessionId; readonly createdAt?: number; readonly seedLength?: number; readonly origin?: 'subagent'; readonly delegationDepth?: number; readonly agentPreset?: string; }; } /** * Fresh storage values transferred to {@link SessionStore.prepare} without a * second serialization copy. Callers retain no mutable aliases. */ export interface RestoredSessionOptions { /** Fresh detached storage events to validate and freeze in place. */ readonly seed: SessionEvent[]; /** Fresh detached storage metadata to validate and freeze in place. */ readonly meta: SessionHeader; /** Select the persistence ownership-transfer path. */ readonly seedSource: 'persistence'; } /** Inputs accepted while constructing an unpublished Session. */ export type PrepareSessionOptions = (CreateSessionOptions & { readonly seedSource?: undefined; }) | RestoredSessionOptions; /** Why an active agent driver was cancelled. */ export type AgentCancelCause = { readonly kind: 'user'; } | { readonly kind: 'parent'; } | { readonly kind: 'hook'; readonly reason: string; } | { readonly kind: 'disposed'; }; /** Durable cancellation cause, including imports whose original coarse record carried no cause. */ export type TurnEndCancelCause = AgentCancelCause | { readonly kind: 'legacy'; }; /** * Why a turn ended. Merge-extensible sum type. */ export interface TurnEndReasonMap { completed: { kind: 'completed'; }; /** A cancellation request interrupted the live turn. */ aborted: { kind: 'aborted'; reason: TurnEndCancelCause; }; blocked: { kind: 'blocked'; }; /** * The turn failed. `error` is always a structured failure: the `LlmError` * facts verbatim, or `{ message: errorChain(error), code: 'UNKNOWN' }` * flattened from any other error. */ error: { kind: 'error'; error: LlmFailure; }; /** At least one step reached its output-token ceiling, even if a plugin continued the turn. */ 'max-tokens': { kind: 'max-tokens'; }; /** * A persistence backend closed a crash-orphaned turn on reload. The loop never * emits this marker, and the events recorded before the crash remain intact. */ interrupted: { kind: 'interrupted'; }; } /** The union over {@link TurnEndReasonMap} — why a turn ended; plugins extend it by merging variants into the map. */ export type TurnEndReason = TurnEndReasonMap[keyof TurnEndReasonMap]; /** * One entry in an agent's todo list — the unit of the `todo/write` * {@link SessionEventMap} event's whole-list snapshot. * * Deliberately minimal: a human-readable `content` line and a three-state * `status`. No id, priority, or `activeForm` — the list is replaced wholesale * on every write (last-write-wins), so entries need no stable identity. The * three statuses describe the complete portable lifecycle needed by model and * UI consumers. */ export interface TodoItem { /** What this task is — a short imperative line shown in the UI. */ content: string; /** Lifecycle state. `in_progress` marks a task being worked now; parallel work may mark several. */ status: 'pending' | 'in_progress' | 'completed'; } /** * Logged request state outside derived history: call config, system prompt, and * tools. The latest full `request/header` snapshot reconstructs it; canonical * empty optional fields are absent. */ export interface EpochHeader { /** The conversation's call configuration (provider, model, reasoning effort, and sampling scalars). */ config: LlmCallConfig; /** Effective config fields materialized from the exact adapter rather than proposed by a caller. */ adapterDefaults?: LlmCallConfigAdapterDefaults; /** Rendered system prompt text; absent for a system-less request. */ system?: string; /** Assembled tool schemas; absent for a tool-less request. */ tools?: ToolSchema[]; } /** Registration-bound metadata for one resolved model route. */ export interface RequestContext { /** Registered provider route the metadata belongs to. */ provider: string; /** Provider-owned model id the metadata belongs to. */ model: string; /** Maximum combined request and response context in tokens, when advertised. */ contextWindow?: number; } /** * Why a `request/header` snapshot was appended: `'initial'` — the log's first * header (a new conversation); `'resume'` — a loop instance's first request * over a log that already has header events (process restart, fork seed); * `'change'` — a later request used a different header. */ export type RequestHeaderReason = 'initial' | 'resume' | 'change'; /** * The merge-extensible, append-only source of truth for an agent interaction. * Message history is derived from this log. Every event is lossless JSON and * sequence numbers stay contiguous, including raw chunks, so persistence can * store the canonical log verbatim. */ export interface SessionEventMap { /** * Opens turn `turn` before the loop claims queued input or runs pre-step. * Rejection, empty input, cancellation, or failure may close it with no * step; otherwise the following identified `user/message` event or batch * records the messages entering the step. */ 'turn/start': { turn: number; }; /** * Closes turn `turn` with the {@link TurnEndReason} that ended it. A turn * with no entered step has no `step/start` or `step/end`. The loop does not await a * flush at turn boundaries: `dsh-session-checkpoint-policy` owns the * per-request durability checkpoint, and consumers that read storage after * `whenIdle()` flush themselves. Success commits the turn; rejection is * reported live and does not prevent later work. */ 'turn/end': { turn: number; reason: TurnEndReason; }; /** Opens step `step` of turn `turn` — one model call plus the tool executions it requested. */ 'step/start': { turn: number; step: number; }; /** Closes step `step` of turn `turn`. */ 'step/end': { turn: number; step: number; }; /** * A user-role message on the model-visible surface: a direct human prompt * (the queued message claimed for this turn), a synthetic `agent.inject()` * context (file-change notices, subdir AGENTS.md, skill content, cron * notifications, …), or an entered goal continuation round. All three * project their `content` verbatim; `source` tells them apart. */ 'user/message': UserMessage; /** Raw stream chunk — token-level replay fidelity. */ 'assistant/chunk': { turn: number; step: number; chunk: StreamChunk; }; /** * Assembled assistant message for one step (derived history uses this). * Carries the step's `usage` when the adapter reported token accounting, so * the model output and its accounting travel together (there is no separate * usage record). `usage` is absent when the adapter reported none. */ 'assistant/message': { turn: number; step: number; message: AssistantMessage; usage?: TokenUsage; }; /** * The model requested one tool invocation: `name` with the raw `arguments` * JSON string exactly as the model produced it (unparsed). `callId` pairs the * call with its `tool/result`. */ 'tool/call': { turn: number; step: number; callId: CallId; name: string; arguments: string; }; /** * A completed tool call's model-facing result, optional internal failure * identity, and optional tool-private `meta` presentation payload. `meta` is * opaque to the core (the producing tool owns its shape and reads it back in * `presentResult`) but MUST be JSON-serializable: `Session.append` * runtime-validates all event data with `isJsonValue`, so a non-serializable * `meta` is rejected at the source, and the durable log reproduces the * identical card on replay. Absent * unless the tool attaches one (e.g. `dsh-tool-fs` carries its result-time * contextual diff here). */ 'tool/result': { turn: number; step: number; message: ToolResultMessage; error?: { name: string; code: string; }; meta?: JsonValue; }; /** Whole-list snapshot; latest write wins on replay. Log-only UI state; never derived history. */ 'todo/write': { todos: TodoItem[]; }; /** * Full header for the next request, appended inside its step before dispatch. * It is log-only; the latest snapshot reconstructs the request header. */ 'request/header': { header: EpochHeader; reason: RequestHeaderReason; }; /** * Route metadata for the next request, logged only when the route or capacity * changes. It does not participate in request reconstruction or header equality. */ 'request/context': RequestContext; /** * Marks the end of a constructor seed. Events before it have smaller seq * values and came from the seed (resume, fork, or replay); this lifecycle * produced none of them. This log-only event is the durable projection of * {@link Session.firstLiveSeq}. Its payload is empty — position and `time` * carry the meaning. * * Locate the LAST one in stored history. A seed already ending in one is not * re-marked, so reopening an untouched session does not grow its log per * pickup and the event need not be at the current `firstLiveSeq`. * * `Session`'s constructor is the only legitimate writer. The invariant * companion deliberately constrains nothing here, so a plugin appending one * would silently classify every live bracket before it as seed history. * * An owner of a standalone open/close bracket (`compact/start` … * `compact/end`) reads it because seed history and live work are otherwise * byte-identical: an unmatched opening marker before this event belongs to * an ended lifecycle, whatever ended it. NOT a liveness signal about other * writers — a concurrently live session holds its own boundary elsewhere, * so tolerating concurrent writers needs a signal beyond the log. */ 'session/end-seed': Record; } /** The appendable event-type keys of {@link SessionEventMap}, plugin-merged extensions included. */ export type SessionEventType = keyof SessionEventMap; /** * The subset of {@link SessionEventType} values whose events produce LLM * messages and are eligible to appear on the ordered surface. Only these * event types may carry {@link SurfaceOp} and {@link SessionEvent.sourceEventSeqs}. */ export type SurfaceEventType = 'user/message' | 'assistant/message' | 'tool/result'; /** * A {@link SessionEvent} that is **on** the ordered surface — its * `surfaceOp` is guaranteed present (mandatory), narrowed from a * surface-eligible {@link SessionEvent} by checking both `type` and * `surfaceOp` at runtime. * * Use the `isSurfaceEvent` type guard (in `surface.ts`) to narrow a * `SessionEvent` to this type. */ export type SurfaceEvent = SessionEvent & { surfaceOp: SurfaceOp; }; /** * How a session event entered the ordered surface. Only valid on * {@link SurfaceEventType} events. * * - `'append'`: added to the tail — normal path for user/assistant/tool * messages. * - `{ op: 'replace', start, end }`: replaces surface nodes from `start` * (inclusive) through `end` (inclusive) with this node. Both must exist as * surface nodes in the current surface. `start === end` replaces a single * node. The node's {@link SessionEvent.sourceEventSeqs} must include every * shadowed surface node. Used by compaction; any surface-replacing producer * may use it. */ export type SurfaceOp = 'append' | { op: 'replace'; start: number; end: number; }; /** * Surface placement and cited source-event seqs for {@link Session.append}. Required on * message-producing events and forbidden on log-only events. */ export interface SurfaceIntent { surfaceOp: SurfaceOp; /** * Complete set of known source-event seqs. `assistant/message` may use a * present empty array for a known empty provider stream; when the field is * absent, the event does not record which earlier events produced the message. * Other surface events require a non-empty set when this field is present. */ sourceEventSeqs?: number[]; } /** * One immutable entry in the session log. * * A proper discriminated union over `type` (not independent `type`/`data` * unions), so `switch (event.type)` narrows `event.data` without casts. * * The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional: * they only exist on {@link SurfaceEventType} variants (`user/message`, * `assistant/message`, `tool/result`). * Non-surface events (boundary markers, chunks, usage, errors) never carry * surface metadata — the compiler enforces this at `Session.append()` * call sites. */ export type SessionEvent = { [K in SessionEventType]: { type: K; /** Monotonic sequence number within the session. */ seq: number; /** Unix epoch milliseconds. */ time: number; data: SessionEventMap[K]; } & (K extends SurfaceEventType ? { /** * Seq numbers of earlier events that this event cites as sources * (e.g. the `assistant/chunk` seqs that built an `assistant/message`, * or the surface nodes shadowed by a compaction replace node). An * `assistant/message` may carry a present empty array for a known empty * provider stream; when the field is absent, the event does not record which * earlier events produced the message. */ sourceEventSeqs?: number[]; /** How this event entered the surface; absent for non-surface events. */ surfaceOp?: SurfaceOp; } : object); }[T]; //# sourceMappingURL=types.d.ts.map