@deepseek-ai/dsh-session 0.1.2-alpha.5 → 0.1.3-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { type Branded, type BrandedNumber } from '@deepseek-ai/dsh-brand';
2
- import type { AssistantMessage, ToolCallId, LlmCallConfig, LlmCallConfigAdapterDefaults, LlmFailure, StreamChunk, TokenUsage, ToolResultMessage, ToolSchema, UserMessage } from '@deepseek-ai/dsh-llm';
2
+ import type { AssistantMessage, AssistantStreamRecord, ToolCallId, LlmCallConfig, LlmCallConfigAdapterDefaults, LlmFailure, TokenUsage, ToolResultMessage, ToolSchema, UserMessage } from '@deepseek-ai/dsh-llm';
3
3
  import type { JsonValue } from '@deepseek-ai/dsh-util-values';
4
4
  /** Identifies one session in the store (and its persistence artifacts). */
5
5
  export type SessionId = Branded<'SessionId'>;
@@ -30,11 +30,11 @@ export type SessionSeqCursor = SessionSeq | -1;
30
30
  /** One existing Session event position, or explicit absence. */
31
31
  export type OptionalSessionSeq = SessionSeq | null;
32
32
  /**
33
- * The on-disk session format version, stamped into every newly-written {@link SessionHeader}
34
- * and enforced by every persistence backend on load. The single source of truth for the
35
- * version write sites and the load-time check all read it.
36
- * While the harness is unreleased it is pinned at `0`: no compatibility is
37
- * implied, incompatible logs are rejected, and no migration is provided.
33
+ * Current logical Session format version, stamped into every newly written
34
+ * {@link SessionHeader}. Current Session and persistence code accept only this
35
+ * value; header-only readers classify supported historical formats, while an
36
+ * event-body read composes the build-static adjacent chain and publishes only
37
+ * this final generation before constructing a Session.
38
38
  *
39
39
  * The version is a single monotonic integer with no major/minor split. Whether
40
40
  * a bump is needed is decided by what the WRITER emits, never by what a newer
@@ -47,22 +47,20 @@ export type OptionalSessionSeq = SessionSeq | null;
47
47
  * Adding an ordinary event type does not bump — the per-event
48
48
  * {@link SessionEvent.ignorable} guard covers vocabulary growth instead. When
49
49
  * in doubt, bump: a near-identity upgrade step is almost free, a missed bump
50
- * makes older runtimes read new logs wrong silently. The full mechanism
51
- * (upgrade-step chain, in-memory view conversion, migrate-on-continue) is
52
- * recorded in the session-log-version-mechanism Agent Note
53
- * (`.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md`).
50
+ * makes older runtimes read new logs wrong silently. The released migration,
51
+ * immutable prior-generation, and current fast-path rules are recorded in
52
+ * `.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md`.
54
53
  */
55
- export declare const SESSION_FORMAT_VERSION = 0;
54
+ export declare const SESSION_FORMAT_VERSION = 2;
56
55
  /**
57
56
  * Immutable validated storage metadata, kept outside the conversation event log.
58
57
  */
59
58
  export interface SessionHeader {
60
59
  /**
61
- * On-disk format version, stamped from {@link SESSION_FORMAT_VERSION} when the
62
- * session is created. A persistence backend rejects any other version on load
63
- * (no migration — see the constant).
60
+ * Current logical format version, stamped from {@link SESSION_FORMAT_VERSION}.
61
+ * Historical physical headers are translated before entering this interface.
64
62
  */
65
- readonly version: number;
63
+ readonly version: typeof SESSION_FORMAT_VERSION;
66
64
  /** The session's id (mirrors the {@link Session}'s id). */
67
65
  readonly id: SessionId;
68
66
  /** Non-negative safe-integer Unix epoch milliseconds when the session was created. */
@@ -104,8 +102,9 @@ export interface CreateSessionOptions {
104
102
  /** Initial replay or fork history supplied at construction. */
105
103
  readonly seed?: readonly SessionEvent[];
106
104
  /**
107
- * Exact fork-inherited prefix length when `meta.isSeeded` is true. A
108
- * constructor seed may also contain child-owned setup events after this cut.
105
+ * Exact fork-inherited prefix length when `meta.isSeeded` is true. In v2 the
106
+ * constructor seed is exactly this inherited prefix; the constructor
107
+ * appends the child-owned tagged marker at the cut.
109
108
  */
110
109
  readonly inheritedEventCount?: SessionLogOffset;
111
110
  /**
@@ -123,22 +122,27 @@ export interface CreateSessionOptions {
123
122
  };
124
123
  }
125
124
  /**
126
- * Fresh storage values transferred to {@link SessionStore.prepare} without a
127
- * second serialization copy. Callers retain no mutable aliases.
125
+ * Aliasing state of an adoptable Session seed. `shared-frozen` permits deeply
126
+ * frozen aliases plus independently owned unfrozen values in the same seed.
127
+ */
128
+ export type SessionSeedEventState = 'detached' | 'shared-frozen';
129
+ /**
130
+ * Adoptable storage values transferred to {@link SessionStore.prepare}
131
+ * without another copy or freeze pass.
128
132
  */
129
133
  export interface RestoredSessionOptions {
130
- /** Fresh detached storage events to validate and freeze in place. */
134
+ /** Events that are independently owned or already deeply frozen. */
131
135
  readonly seed: SessionEvent[];
132
- /** Fresh detached storage metadata to validate and freeze in place. */
136
+ /** Independently owned storage metadata to validate and freeze in place. */
133
137
  readonly meta: SessionHeader;
134
138
  /** Exact number of fork-inherited leading events decoded from storage. */
135
139
  readonly inheritedEventCount: SessionLogOffset;
136
- /** Select the persistence ownership-transfer path. */
137
- readonly seedSource: 'persistence';
140
+ /** Aliasing state carried from the operation that produced the seed. */
141
+ readonly eventState: SessionSeedEventState;
138
142
  }
139
143
  /** Inputs accepted while constructing an unpublished Session. */
140
144
  export type PrepareSessionOptions = (CreateSessionOptions & {
141
- readonly seedSource?: undefined;
145
+ readonly eventState?: undefined;
142
146
  }) | RestoredSessionOptions;
143
147
  /** Why an active agent driver was cancelled. */
144
148
  export type AgentCancelCause = {
@@ -184,8 +188,10 @@ export interface TurnEndReasonMap {
184
188
  kind: 'max-tokens';
185
189
  };
186
190
  /**
187
- * A persistence backend closed a crash-orphaned turn on reload. The loop never
188
- * emits this marker, and the events recorded before the crash remain intact.
191
+ * A crash-orphaned turn was closed after the fact: agent-loop resume appends
192
+ * this closer for a stored log whose last turn never ended, and session-query
193
+ * synthesizes it on cold reads. The loop never emits this marker live, and
194
+ * the events recorded before the crash remain intact.
189
195
  */
190
196
  interrupted: {
191
197
  kind: 'interrupted';
@@ -229,8 +235,8 @@ export type RequestHeaderReason = 'initial' | 'resume' | 'change' | 'series';
229
235
  /**
230
236
  * The merge-extensible, append-only source of truth for an agent interaction.
231
237
  * Message history is derived from this log. Every event is lossless JSON and
232
- * sequence numbers stay contiguous, including raw chunks, so persistence can
233
- * store the canonical log verbatim.
238
+ * sequence numbers stay contiguous. Assistant attempt events embed their exact
239
+ * compact raw streams so persistence stores one durable settlement per attempt.
234
240
  */
235
241
  export interface SessionEventMap {
236
242
  /**
@@ -272,12 +278,6 @@ export interface SessionEventMap {
272
278
  * project their `content` verbatim; `source` tells them apart.
273
279
  */
274
280
  'user/message': UserMessage;
275
- /** Raw stream chunk — token-level replay fidelity. */
276
- 'assistant/chunk': {
277
- turn: number;
278
- step: number;
279
- chunk: StreamChunk;
280
- };
281
281
  /**
282
282
  * Assembled assistant message for one step (derived history uses this).
283
283
  * Carries the step's `usage` when the adapter reported token accounting, so
@@ -292,9 +292,21 @@ export interface SessionEventMap {
292
292
  turn: number;
293
293
  step: number;
294
294
  message: AssistantMessage;
295
+ /** Exact timed model stream, compacted without joining delta boundaries. */
296
+ stream: AssistantStreamRecord[];
295
297
  usage?: TokenUsage;
296
298
  interrupted?: true;
297
299
  };
300
+ /**
301
+ * One model attempt that committed no surface message. The embedded stream
302
+ * preserves a failed, retried, cancelled, or stream-error attempt that
303
+ * reached settlement without fabricating model-visible history.
304
+ */
305
+ 'assistant/attempt': {
306
+ turn: number;
307
+ step: number;
308
+ stream: AssistantStreamRecord[];
309
+ };
298
310
  /**
299
311
  * The model requested one tool invocation: `name` with the raw `arguments`
300
312
  * JSON string exactly as the model produced it (unparsed). `callId` pairs the
@@ -347,12 +359,12 @@ export interface SessionEventMap {
347
359
  * Marks the end of a constructor seed. Events before it have smaller seq
348
360
  * values and came from the seed (resume, fork, or replay); this lifecycle
349
361
  * produced none of them. This log-only event is the durable projection of
350
- * {@link Session.firstLiveSeq}. Its payload is empty — position and `time`
351
- * carry the meaning.
362
+ * {@link Session.firstLiveSeq}.
352
363
  *
353
- * Locate the LAST one in stored history. A seed already ending in one is not
354
- * re-marked, so reopening an untouched session does not grow its log per
355
- * pickup and the event need not be at the current `firstLiveSeq`.
364
+ * A fresh fork child owns one `{ inherited: true }` marker at its exact
365
+ * inherited-prefix cut, even when that prefix ends in an ancestor marker.
366
+ * The last tagged marker is the current Session's cut; untagged markers keep
367
+ * ordinary restore and replay lifecycle boundaries.
356
368
  *
357
369
  * `Session`'s constructor is the only legitimate writer. The invariant
358
370
  * companion deliberately constrains nothing here, so a plugin appending one
@@ -365,14 +377,17 @@ export interface SessionEventMap {
365
377
  * writers — a concurrently live session holds its own boundary elsewhere,
366
378
  * so tolerating concurrent writers needs a signal beyond the log.
367
379
  */
368
- 'session/end-seed': Record<string, never>;
380
+ 'session/end-seed': {
381
+ inherited?: true;
382
+ };
369
383
  }
370
384
  /** The appendable event-type keys of {@link SessionEventMap}, plugin-merged extensions included. */
371
385
  export type SessionEventType = keyof SessionEventMap;
372
386
  /**
373
387
  * The subset of {@link SessionEventType} values whose events produce LLM
374
388
  * messages and are eligible to appear on the ordered surface. Only these
375
- * event types may carry {@link SurfaceOp} and {@link SessionEvent.sourceEventSeqs}.
389
+ * event types may carry {@link SurfaceOp}; user and tool events may also cite
390
+ * earlier sources through {@link SessionEvent.sourceEventSeqs}.
376
391
  */
377
392
  export type SurfaceEventType = 'user/message' | 'assistant/message' | 'tool/result';
378
393
  /**
@@ -409,16 +424,15 @@ export type SurfaceOp = 'append' | {
409
424
  * Surface placement and cited source-event seqs for {@link Session.append}. Required on
410
425
  * message-producing events and forbidden on log-only events.
411
426
  */
412
- export interface SurfaceIntent {
427
+ export type SurfaceIntent<T extends SurfaceEventType = SurfaceEventType> = {
413
428
  surfaceOp: SurfaceOp;
414
- /**
415
- * Complete set of known source-event seqs. `assistant/message` may use a
416
- * present empty array for a known empty provider stream; when the field is
417
- * absent, the event does not record which earlier events produced the message.
418
- * Other surface events require a non-empty set when this field is present.
419
- */
429
+ } & (T extends 'assistant/message' ? {
430
+ /** V2 Assistant messages embed their provider stream instead of citing source events. */
431
+ sourceEventSeqs?: never;
432
+ } : {
433
+ /** Complete non-empty set of known earlier source-event seqs. */
420
434
  sourceEventSeqs?: SessionSeq[];
421
- }
435
+ });
422
436
  /**
423
437
  * One immutable entry in the session log.
424
438
  *
@@ -428,7 +442,7 @@ export interface SurfaceIntent {
428
442
  * The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional:
429
443
  * they only exist on {@link SurfaceEventType} variants (`user/message`,
430
444
  * `assistant/message`, `tool/result`).
431
- * Non-surface events (boundary markers, chunks, usage, errors) never carry
445
+ * Non-surface events (boundary markers, attempts, errors) never carry
432
446
  * surface metadata — the compiler enforces this at `Session.append()`
433
447
  * call sites.
434
448
  */
@@ -453,12 +467,9 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
453
467
  ignorable?: true;
454
468
  } & (K extends SurfaceEventType ? {
455
469
  /**
456
- * Seq numbers of earlier events that this event cites as sources
457
- * (e.g. the `assistant/chunk` seqs that built an `assistant/message`,
458
- * or the surface nodes shadowed by a compaction replace node). An
459
- * `assistant/message` may carry a present empty array for a known empty
460
- * provider stream; when the field is absent, the event does not record which
461
- * earlier events produced the message.
470
+ * Seq numbers of earlier events that this event cites as sources, such as
471
+ * the surface nodes shadowed by a compaction replacement. A v2
472
+ * `assistant/message` embeds its provider stream and cannot carry this field.
462
473
  */
463
474
  sourceEventSeqs?: SessionSeq[];
464
475
  /** How this event entered the surface; absent for non-surface events. */
@@ -30,11 +30,11 @@ export function SessionLogOffset(value) {
30
30
  return brandNumber(value);
31
31
  }
32
32
  /**
33
- * The on-disk session format version, stamped into every newly-written {@link SessionHeader}
34
- * and enforced by every persistence backend on load. The single source of truth for the
35
- * version write sites and the load-time check all read it.
36
- * While the harness is unreleased it is pinned at `0`: no compatibility is
37
- * implied, incompatible logs are rejected, and no migration is provided.
33
+ * Current logical Session format version, stamped into every newly written
34
+ * {@link SessionHeader}. Current Session and persistence code accept only this
35
+ * value; header-only readers classify supported historical formats, while an
36
+ * event-body read composes the build-static adjacent chain and publishes only
37
+ * this final generation before constructing a Session.
38
38
  *
39
39
  * The version is a single monotonic integer with no major/minor split. Whether
40
40
  * a bump is needed is decided by what the WRITER emits, never by what a newer
@@ -47,10 +47,9 @@ export function SessionLogOffset(value) {
47
47
  * Adding an ordinary event type does not bump — the per-event
48
48
  * {@link SessionEvent.ignorable} guard covers vocabulary growth instead. When
49
49
  * in doubt, bump: a near-identity upgrade step is almost free, a missed bump
50
- * makes older runtimes read new logs wrong silently. The full mechanism
51
- * (upgrade-step chain, in-memory view conversion, migrate-on-continue) is
52
- * recorded in the session-log-version-mechanism Agent Note
53
- * (`.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md`).
50
+ * makes older runtimes read new logs wrong silently. The released migration,
51
+ * immutable prior-generation, and current fast-path rules are recorded in
52
+ * `.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md`.
54
53
  */
55
- export const SESSION_FORMAT_VERSION = 0;
54
+ export const SESSION_FORMAT_VERSION = 2;
56
55
  //# sourceMappingURL=types.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-session",
3
3
  "description": "Event-sourced session store for the DeepSeek Harness",
4
- "version": "0.1.2-alpha.5",
4
+ "version": "0.1.3-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -26,10 +26,6 @@
26
26
  "types": "./lib/types/types.d.ts",
27
27
  "default": "./lib/types/types.js"
28
28
  },
29
- "./chunk-rows": {
30
- "types": "./lib/types/chunk-rows.d.ts",
31
- "default": "./lib/types/chunk-rows.js"
32
- },
33
29
  "./src/*": "./src/*",
34
30
  "./package.json": "./package.json",
35
31
  "./surface": {
@@ -45,19 +41,19 @@
45
41
  ],
46
42
  "license": "MIT",
47
43
  "peerDependencies": {
48
- "@deepseek-ai/dsh-scope": "^0.1.2-alpha.5",
44
+ "@deepseek-ai/dsh-scope": "^0.1.3-alpha.2",
49
45
  "@deepseek-ai/cordis": "^4.0.2"
50
46
  },
51
47
  "devDependencies": {
52
- "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.5",
53
- "@deepseek-ai/dsh-scope": "^0.1.2-alpha.5",
54
- "@deepseek-ai/dsh-typert-protocol": "^0.1.2-alpha.5",
55
- "@deepseek-ai/dsh-typert-registry": "^0.1.2-alpha.5",
48
+ "@deepseek-ai/dsh-scope": "^0.1.3-alpha.2",
49
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.3-alpha.2",
50
+ "@deepseek-ai/dsh-invariants": "^0.1.3-alpha.2",
51
+ "@deepseek-ai/dsh-typert-registry": "^0.1.3-alpha.2",
56
52
  "@deepseek-ai/cordis": "^4.0.2"
57
53
  },
58
54
  "dependencies": {
59
- "@deepseek-ai/dsh-brand": "^0.1.2-alpha.5",
60
- "@deepseek-ai/dsh-util-values": "^0.1.2-alpha.5",
61
- "@deepseek-ai/dsh-llm": "^0.1.2-alpha.5"
55
+ "@deepseek-ai/dsh-llm": "^0.1.3-alpha.2",
56
+ "@deepseek-ai/dsh-brand": "^0.1.3-alpha.2",
57
+ "@deepseek-ai/dsh-util-values": "^0.1.3-alpha.2"
62
58
  }
63
59
  }
@@ -1,106 +0,0 @@
1
- /**
2
- * Lossless row packing for `assistant/chunk` delta runs. Providers stream
3
- * token-sized deltas, so a log stores hundreds of near-identical event lines
4
- * whose JSON envelopes dwarf their payloads (~56× measured on a real DeepSeek
5
- * session). This module packs each run of consecutive same-block delta chunks
6
- * into ONE storage row — `text-chunks`, `reasoning-chunks`, or
7
- * `tool-call-chunks` — and expands rows back to the exact original events.
8
- *
9
- * Packed rows are an encoding vocabulary, NOT session events: they never enter
10
- * `Session.snapshotEvents()`, have no `SessionEventMap` entry, and use bare (slash-less)
11
- * type tags so a reader cannot confuse them with the event taxonomy
12
- * (precedent: the JSONL header line's `session` tag). Persistence and bounded
13
- * history transport both use the codec. The encoder whitelists exact shapes —
14
- * anything it does not fully recognize stays verbatim, so unknown fields or
15
- * future chunk variants lose compression, never data. The decoder validates
16
- * before expanding and fails loud on a malformed row-tagged value instead of
17
- * silently dropping a whole run.
18
- *
19
- * @module @deepseek-ai/dsh-session/chunk-rows
20
- */
21
- import type { ToolCallId } from '@deepseek-ai/dsh-llm/brand';
22
- import type { SessionEvent, SessionSeq as SessionSeqType } from './types.ts';
23
- /**
24
- * Fields shared by every packed run: placement, block correlation, and member
25
- * timestamps as gaps. Member `k` reconstructs as seq `seq0 + k` and time
26
- * `time0` plus the first `k` gaps; a gap may be negative when the wall clock
27
- * stepped backwards between events.
28
- */
29
- interface RunDataBase {
30
- turn: number;
31
- step: number;
32
- /** The stream block index every member shares. */
33
- index: number;
34
- /** Epoch-ms gaps between consecutive members; length is one less than the member count. */
35
- dt: number[];
36
- }
37
- /** Payload of a `text-chunks`/`reasoning-chunks` row: one entry per member, never joined — token boundaries are data. */
38
- interface TextRunData extends RunDataBase {
39
- texts: string[];
40
- }
41
- /** Payload of a `tool-call-chunks` row: the run-constant call identity plus each member's raw arguments fragment. */
42
- interface ToolCallRunData extends RunDataBase {
43
- id: ToolCallId;
44
- /** Present iff every member carried it, with one uniform value (a mixed run never packs). */
45
- name?: string;
46
- args: string[];
47
- }
48
- /**
49
- * A packed run of consecutive delta chunk events, discriminated on `type`.
50
- * `seq0`/`time0` anchor the first member; text and reasoning rows share the
51
- * {@link TextRunData} payload, tool-call rows carry {@link ToolCallRunData}.
52
- */
53
- export type ChunkRow = {
54
- type: 'text-chunks';
55
- seq0: SessionSeqType;
56
- time0: number;
57
- data: TextRunData;
58
- } | {
59
- type: 'reasoning-chunks';
60
- seq0: SessionSeqType;
61
- time0: number;
62
- data: TextRunData;
63
- } | {
64
- type: 'tool-call-chunks';
65
- seq0: SessionSeqType;
66
- time0: number;
67
- data: ToolCallRunData;
68
- };
69
- /** One durable log line's JSON value: a session event verbatim, or a packed chunk row. */
70
- export type StorageRecord = SessionEvent | ChunkRow;
71
- /**
72
- * Test whether an encoded record is a packed chunk row rather than a Session event.
73
- * @param record - one persistence or bounded-history encoding record.
74
- * @returns Whether the record is a packed chunk row.
75
- */
76
- export declare function isChunkRow(record: StorageRecord): record is ChunkRow;
77
- /**
78
- * Number of logical Session events represented by one packed row.
79
- * @param row - validated or encoder-produced packed row.
80
- * @returns Count of consecutive chunk events in the row.
81
- */
82
- export declare function chunkRowLength(row: ChunkRow): number;
83
- /**
84
- * Pack an event batch for storage: each run of at least {@link MIN_RUN}
85
- * consecutive whitelisted same-kind, same-block delta chunk events becomes one
86
- * {@link ChunkRow}; every other event passes through verbatim, in order.
87
- * Pure and stateless — safe over any array, including a batch whose runs were
88
- * split by flush boundaries (the split runs simply pack per batch).
89
- *
90
- * @param events - the batch to encode, in log order.
91
- * @returns the storage records to write, one JSONL line each.
92
- */
93
- export declare function packChunkRuns(events: readonly SessionEvent[]): StorageRecord[];
94
- /**
95
- * Decode one parsed JSONL line value into the session event(s) it stores.
96
- * Chunk-row-tagged values validate and expand (a malformed row throws — it is
97
- * corrupt storage, and treating it as an event would silently drop a whole
98
- * run); every other value passes through as a single event after admitting a
99
- * numeric `seq` through the Session-sequence constructor.
100
- *
101
- * @param value - one line's `JSON.parse` result.
102
- * @returns the stored events, in log order.
103
- */
104
- export declare function decodeStorageRecord(value: unknown): SessionEvent[];
105
- export {};
106
- //# sourceMappingURL=chunk-rows.d.ts.map