import type { SessionTokenBudget } from '../../store/budget/index.js'; import type { SessionCheckpointStore } from '../../store/checkpoint/index.js'; import { type SessionLease, type SessionLog, type SessionLogEntry, type SessionLogHead } from '../../store/session-log/index.js'; import { type CostInfo, type TokenUsage } from '../../types/common/index.js'; import type { MessageId, SessionId, TenantId, TurnId } from '../../types/ids/index.js'; import type { Message } from '../../types/message/index.js'; import type { ProviderErrorInfo } from '../../types/provider/index.js'; import type { AuditEvent, AuditEventInput } from '../../types/session/audit.js'; import type { TurnRecorderConfig } from '../../types/session/config.js'; import { type SessionEvent } from '../../types/session/events.js'; import type { ProjectId, TopicId } from '../../types/session/ids.js'; import type { StepResult } from '../../types/session/step.js'; import type { StopReason } from '../../types/session/stop-reason.js'; import { type Origin, type Turn, type TurnConfigSnapshot, type TurnForkOrigin, type TurnResultSource, type TurnSettlement } from '../../types/session/turn.js'; /** Which provider and model a cost is priced against. */ export interface PricingSubject { readonly providerId: string; readonly model: string | undefined; } /** One message of a session's folded history, with the id its record gave it. */ export interface RecordedMessage { readonly message: Message; /** Absent for a compaction summary message, which has no record of its own. */ readonly messageId?: MessageId; } /** How {@link TurnRecorder.open} takes the session's writer lease and starts an empty log. */ export interface TurnRecorderOpenOptions { /** A lease the caller already holds. Absent: the recorder claims one and releases it at the end. */ readonly lease?: SessionLease; /** The holder name for a lease the recorder claims. */ readonly holder?: string; /** Lease time-to-live for a lease the recorder claims. Renewed while the turn runs. */ readonly leaseTtlMs?: number; /** Written into `session_started` when the log is empty. */ readonly session: { readonly cwd: string; readonly origin?: Origin; readonly agentType?: string; /** The session this one was forked from (`session_started.forkedFrom`). */ readonly forkedFrom?: TurnForkOrigin; }; } /** The payload of `turn_started` a caller supplies; the rest comes from the recorder. */ export interface TurnBeginDraft { readonly systemPrompt?: string; readonly origin?: Origin; /** Close an interrupted active turn first (`beginTurn({ abandonInterrupted })`). */ readonly abandonInterrupted?: boolean; } /** Default lease time-to-live: renewed at half-life while the turn runs. */ export declare const DEFAULT_TURN_LEASE_TTL_MS: number; /** * Records one turn into its session log: `turn_started`, every `message` * when the message ends (not when the turn ends), checkpoints, audit * entries, and the settling `turn_completed`/`turn_failed`. When the final * `result` differs from the text of the turn's last assistant message it * appends `message_replaced` first, so every fold shows the answer the host * was given. * * It writes under the session lease and holds no other durable state: no * `run.json`, `messages.json` or `report.md`. * * ## Messages * * The context (`messages`) is a plain array the loop pushes to and, during * compaction, rewrites in place. The recorder keeps a view of what the log * already holds and reconciles the array against it before every append: * messages added at the end become `message` records; any other change — * a compaction, a pinned slot rewritten, a message removed — becomes one * `compaction` record whose fold is exactly the new array. The rebuilt * system prompt is pushed as transient and never recorded: `turn_started` * carries it. * * Message appends are queued in order with every other record; a failed * append is reported by the next awaited write ({@link flush}). */ export declare class TurnRecorder { #private; readonly sessionId: SessionId; readonly turnId: TurnId; readonly budget?: SessionTokenBudget; readonly log: SessionLog; constructor(config: TurnRecorderConfig); get topicId(): TopicId; get tenantId(): TenantId; get projectId(): ProjectId; get parentSessionId(): SessionId | undefined; get parentTurnId(): TurnId | undefined; get checkpointStore(): SessionCheckpointStore | undefined; /** The lease the turn's records are written under, once {@link open} has run. */ get lease(): SessionLease | undefined; /** The id of the prompt message `turn_started` names. */ get userMessageId(): MessageId | undefined; /** Seq of the last record this recorder appended. */ get lastSeq(): number; /** Whether the turn has begun (or resumed) and not paused or settled. */ get isActive(): boolean; /** Whether the turn's last segment record is `turn_paused`. */ get isPaused(): boolean; /** Whether the turn has a terminal record. */ get isClosed(): boolean; get status(): import("../../types/session/turn.js").TurnExecutionStatus; get stopReason(): StopReason | undefined; get messages(): Message[]; get tokenUsage(): TokenUsage; get costInfo(): CostInfo; get currentIteration(): number; /** The turn as it stands. */ get turn(): Turn; getTurn(): Readonly; markRunning(): void; markCompleted(stopReason?: StopReason): void; markFailed(error: string, providerError?: ProviderErrorInfo): void; markCancelled(): void; setStopReason(reason: StopReason): void; setLastError(error: string, providerError?: ProviderErrorInfo): void; incrementIteration(): number; /** * Override the turn's final text. Sticky: `markCompleted` re-derives the * result from the message tail, and would otherwise put the raw model * text back. `source` names which override decided it; anything but * `model` makes settling append `message_replaced`. */ setResult(result: string, source: TurnResultSource): void; /** Which override decided `result` (`model` when none did). */ get resultSource(): TurnResultSource; /** Invalidate a structured value without replacing the host's textual result. */ clearStructuredOutput(): void; /** Record the schema-validated answer, and make `result` agree with it. */ setStructuredOutput(value: unknown): void; setAbandonedTaskIds(taskIds: readonly string[]): void; setAbandonedJobIds(jobIds: readonly string[]): void; setSteps(steps: readonly StepResult[]): void; /** Record that a provider chain advanced, so the turn stops naming a member that did not serve. */ setServingProvider(providerId: string): void; /** Who is serving right now, for a side-channel call with no provenance of its own. */ get servingProviderId(): string; accumulateUsage(usage: TokenUsage, servedBy: PricingSubject): void; /** * Accumulate usage from a main-loop request, and remember its prompt size: * the provider's own measurement of the context it just received, which * compaction needs. Side-channel calls use {@link accumulateUsage}. */ recordTurnUsage(usage: TokenUsage, servedBy: PricingSubject): void; /** Forget the last prompt measurement (after compaction replaced the context it described). */ clearLastPromptTokens(): void; get lastPromptTokens(): number | undefined; get lastPromptMessageCount(): number | undefined; /** Seed the spend counters from a checkpoint so a resumed turn continues its budget. */ restoreUsage(tokenUsage: TokenUsage, costInfo: CostInfo, currentIteration: number): void; /** Assemble the final assistant output WITHOUT settling the turn. */ materializeResult(): string; /** The settlement a terminal record carries, from the turn as it stands. */ settlement(status: TurnSettlement['status']): TurnSettlement; /** * Take the writer lease (or adopt the caller's) and start an empty log * with `session_started`. Returns the session's folded history. */ open(options: TurnRecorderOpenOptions): Promise; /** Give up a lease the recorder claimed. Never throws. */ release(): Promise; /** Wait for every queued append; rethrow the first that failed. */ flush(): Promise; /** The log's last record once every queued append has landed: what a checkpoint is taken through. */ head(): Promise; /** * Begin the turn: `turn_started`, then the messages pushed so far. The * prompt that opened it is the last new user message pushed before this. */ begin(draft?: TurnBeginDraft): Promise; /** Continue a paused or interrupted turn: `turn_resuming`, same `turnId`. */ resume(fromCheckpointId: string, resolvedDecisionId?: string): Promise; /** * When the turn began. For a resumed turn this is its `turn_started` * record's time, so "closed in this turn" still counts what the turn did * before it paused, in whichever process that was. */ get turnStartedAt(): number; /** * Append a record-only draft (a checkpoint, a decision, an audit entry) * after every queued message. */ appendRecord(draft: Parameters[1]): Promise; /** * Append a live event as its record. Resolves `undefined` for an event * the log does not hold: an ephemeral one, or one bound to a turn that is * paused or settled. */ appendEvent(event: SessionEvent): Promise; /** * Record a child session's lifecycle event from this session's own * delegation: `child_session_spawned` inside the spawning turn, and on * `child_session_idled` the idle record plus `child_session_ended`, read * from the child's own terminal record so the two cannot disagree * (`childLog`; absent when the child's log is not reachable, and then no * ended record is written). * * The spawning turn's id is carried only while that turn is still open. * Queued in order with every other record, so a child that settles before * its parent's turn does is recorded before the parent's `turn_completed`. * Resolves `undefined` for an event of another session, or once the lease * is given up. */ recordChildSessionEvent(event: Extract, childLog?: SessionLog): Promise; /** * Add a message to the context. A new message is appended to the log now * (queued). `messageId` marks a message the log already holds (history * from the fold); `transient` marks one that is never recorded. */ pushMessage(message: Message, options?: { readonly messageId?: MessageId; readonly transient?: boolean; }): void; /** Replace the context without replacing the turn; recorded as one `compaction`. */ replaceMessages(messages: readonly Message[]): void; /** * The id of the record a message was written as, or `undefined` when it * has no record (yet). A message a compaction has since dropped from the * fold keeps its id: its record is still in the log. */ recordedIdOf(message: Message): MessageId | undefined; /** Mark a message as never recorded (the rebuilt system prompt floor). */ markTransient(message: Message): void; /** * Append an `audit` record. Refuses rather than dropping the entry: an * audit trail nobody can point at is not a degraded feature. A rejection * propagates to the caller, because an audit write failing must fail the * operation it was recording. */ recordAudit(input: AuditEventInput): Promise; /** * Wait for every queued record and flush the ledger. The durable half of * settling; the terminal record itself is the `turn_completed` or * `turn_failed` event. */ persist(): Promise; /** Whether the turn settled with a terminal status. */ get isTerminal(): boolean; } /** The durable subset of the turn config `turn_started` records. */ export declare function snapshotTurnConfig(config: TurnRecorderConfig['turnConfig']): TurnConfigSnapshot; /** A live event as a record draft: the payload minus the live-only fields. */ export declare function eventDraft(event: SessionEvent, turnId: TurnId | undefined): Parameters[1]; /** When `turnId` began, from its `turn_started` record; `undefined` when the log has none. */ export declare function recordedTurnStart(log: SessionLog, turnId: TurnId): Promise; /** * The session's folded context with the id each message's record gave it, * spilled bodies read back. */ export declare function readFoldedHistory(log: SessionLog, options?: { readonly throughSeq?: number; }): Promise; /** * A session's audit trail: its `audit` records as {@link AuditEvent}s, in log * order, each numbered among the session's audit entries. What * `replayAudit` reads. */ export declare function readAuditTrail(log: SessionLog): Promise; //# sourceMappingURL=turn-recorder.d.ts.map