@deepseek-ai/dsh-session 0.1.5-rc.2 → 0.1.6-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.
@@ -11,13 +11,13 @@ import type { Message } from '@deepseek-ai/dsh-llm';
11
11
  import { SessionLogOffset, SessionSeq } from './types.ts';
12
12
  import type { TypertLookup } from '@deepseek-ai/dsh-typert-protocol';
13
13
  import type { CreateSessionOptions, EpochHeader, PrepareSessionOptions, RequestContext, SessionEvent, SessionEventMap, SessionEventType, SessionHeader, SessionId, SessionSeedEventState, SurfaceIntent, SurfaceEventType } from './types.ts';
14
- import type { SessionSurface } from './surface.ts';
14
+ import type { SessionSurface, SessionMessageProjection } from './surface.ts';
15
15
  export * from './types.ts';
16
16
  export { SessionPreparation } from './preparation.ts';
17
17
  export type { SessionPreparationOptions } from './preparation.ts';
18
18
  export type { AssistantMessage, SystemMessage, ToolResultMessage, UserMessage } from '@deepseek-ai/dsh-llm';
19
19
  export { interruptedTurnClosers, TOOL_NOT_STARTED, TOOL_OUTCOME_UNKNOWN } from './repair.ts';
20
- export type { SessionSurface, SurfaceFoldReplacement, SurfaceFoldResult } from './surface.ts';
20
+ export type { SessionSurface, SurfaceFoldReplacement, SurfaceFoldResult, SessionMessageProjection, SessionMessageProjectionContext } from './surface.ts';
21
21
  export { deriveEventMessage, foldSurface, isAppendSurfaceEvent, isReplacementSurfaceEvent, isSurfaceEvent, isSurfaceEligibleType } from './surface.ts';
22
22
  export { canonicalHeader, foldRequestHeader, headerEquals } from './request-header.ts';
23
23
  export { KNOWN_SESSION_EVENT_TYPES } from './known-event-types.ts';
@@ -150,9 +150,11 @@ export declare class Session {
150
150
  * @param seed - optional borrowed replay or fork events.
151
151
  * @param header - optional borrowed storage metadata.
152
152
  * @param inheritedEventCount - exact fork-inherited prefix length for a seeded header.
153
+ * @param projections - pure interpreters for plugin-owned message changes.
153
154
  * @returns a detached session.
155
+ * @throws when a seed event requires a missing message interpreter or fails validation.
154
156
  */
155
- static create(id: SessionId, seed?: readonly SessionEvent[], header?: SessionHeader, inheritedEventCount?: SessionLogOffset): Session;
157
+ static create(id: SessionId, seed?: readonly SessionEvent[], header?: SessionHeader, inheritedEventCount?: SessionLogOffset, projections?: readonly SessionMessageProjection[]): Session;
156
158
  /**
157
159
  * Restore a detached session by adopting an independently owned or deeply frozen seed.
158
160
  * Runtime-required event fields, event envelopes, sequence continuity, surface
@@ -164,14 +166,18 @@ export declare class Session {
164
166
  * @param header - independently owned storage metadata.
165
167
  * @param inheritedEventCount - exact fork-inherited prefix length decoded from storage.
166
168
  * @param eventState - aliasing state carried from the operation that produced the seed.
169
+ * @param projections - pure interpreters for plugin-owned message changes.
167
170
  * @returns a restored detached session.
171
+ * @throws when a seed event requires a missing message interpreter or fails validation.
168
172
  */
169
- static fromRestore(id: SessionId, seed: readonly SessionEvent[], header: SessionHeader, inheritedEventCount: SessionLogOffset, eventState: SessionSeedEventState): Session;
173
+ static fromRestore(id: SessionId, seed: readonly SessionEvent[], header: SessionHeader, inheritedEventCount: SessionLogOffset, eventState: SessionSeedEventState, projections?: readonly SessionMessageProjection[]): Session;
170
174
  private constructor();
171
175
  /** Cached immutable full snapshot of the private append-only log. */
172
176
  private eventsSnapshot;
173
177
  /**
174
178
  * Return the immutable event stored at one exact sequence number.
179
+ * @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
180
+ * See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
175
181
  * @param seq - event sequence number.
176
182
  * @returns the accepted event, or undefined when the log does not contain it.
177
183
  */
@@ -180,6 +186,8 @@ export declare class Session {
180
186
  * Materialize an immutable snapshot of a half-open event sequence range.
181
187
  * A full current snapshot is reused until the next append; every previously
182
188
  * returned snapshot remains stable after later appends.
189
+ * @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
190
+ * See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
183
191
  * @param fromSeq - non-negative inclusive sequence number; defaults to the log start.
184
192
  * @param toSeqExclusive - non-negative exclusive sequence number; defaults to the current end.
185
193
  * @returns a frozen array of the selected deeply frozen events.
@@ -187,6 +195,8 @@ export declare class Session {
187
195
  snapshotEvents(fromSeq?: SessionLogOffset, toSeqExclusive?: SessionLogOffset): readonly SessionEvent[];
188
196
  /**
189
197
  * Return this Session's events after its fork-inherited prefix.
198
+ * @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
199
+ * See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
190
200
  * @returns a fresh array containing child-owned events in log order.
191
201
  */
192
202
  ownEvents(): readonly SessionEvent[];
@@ -262,7 +272,7 @@ export declare class Session {
262
272
  private derived;
263
273
  /** Surface position (nodes projected) the cache has reached. */
264
274
  private derivedNodes;
265
- /** {@link SurfaceManager.replaceGeneration} the cache was built under. */
275
+ /** {@link SurfaceManager.contentGeneration} the cache was built under. */
266
276
  private derivedGeneration;
267
277
  /**
268
278
  * Derive the LLM message history by walking the ordered sequences of
@@ -271,21 +281,21 @@ export declare class Session {
271
281
  * append records its `surfaceOp`, so a raw event with no marker (a chunk, a
272
282
  * turn boundary) is correctly absent, and a compaction `replace` deletes the
273
283
  * shadowed nodes from the derivation. The projection rules are
274
- * {@link deriveEventMessage}, folded per node.
284
+ * {@link deriveEventMessage}, with logged message projections applied
285
+ * without changing node membership or message identity.
275
286
  *
276
- * CACHED: each surface node is projected exactly once, when first seen — a
277
- * call costs O(new nodes), and a surface rewrite (a `replace`;
278
- * {@link SessionSurface.replaceGeneration}) rebuilds. The returned array is
287
+ * CACHED: pure tail growth costs O(new nodes); a replacement or message projection
288
+ * ({@link SessionSurface.contentGeneration}) rebuilds. The returned array is
279
289
  * a fresh snapshot per call (later appends never grow an array a caller
280
290
  * already holds); the `Message` objects in it are SHARED and **deep-frozen**.
281
- * Their content reuses the already frozen durable event data, so the cache
282
- * needs no second deep clone and consumers still cannot mutate the log.
291
+ * Unchanged content reuses frozen event data; projected blocks are frozen
292
+ * derived copies. Consumers cannot mutate the log through either form.
283
293
  * @returns a fresh array of the shared, frozen derived history.
284
294
  */
285
295
  deriveMessages(): Message[];
286
296
  /**
287
- * Instance face of the pure per-node `deriveEventMessage` export from
288
- * `surface.ts`.
297
+ * Project one event with all committed message projections applied.
298
+ * The original durable event remains unchanged.
289
299
  * @param event - the event to project.
290
300
  * @returns the derived message, or null when the event produces none.
291
301
  */
@@ -317,6 +327,17 @@ export declare class SessionForkError extends Error {
317
327
  export declare class SessionStore extends Service {
318
328
  private store;
319
329
  private counter;
330
+ private readonly projections;
331
+ /** Borrowed definitions for detached replay; contributions live until their registering fibers unload. */
332
+ get messageProjections(): readonly SessionMessageProjection[];
333
+ /**
334
+ * Register one event interpreter for live creation, restore, and fork.
335
+ * Disposing the contribution makes sessions that used it refuse further derivation.
336
+ * @param projection - pure definition owned by the event's plugin.
337
+ * @returns the fiber-owned disposer.
338
+ * @throws when another definition already owns this event type.
339
+ */
340
+ registerMessageProjection(projection: SessionMessageProjection): () => Promise<void>;
320
341
  constructor(ctx: Context);
321
342
  /**
322
343
  * Create a session owned by the calling fiber: disposing that fiber stops
@@ -11,7 +11,7 @@ import { brandString } from '@deepseek-ai/dsh-brand';
11
11
  import { assertNever, deepFreeze, snapshotJsonValue } from '@deepseek-ai/dsh-util-values';
12
12
  import { scopeOf, scopeTarget } from '@deepseek-ai/dsh-scope';
13
13
  import { SESSION_FORMAT_VERSION, SessionLogOffset, SessionSeq } from "./types.js";
14
- import { deriveEventMessage, SurfaceManager, validateSessionEventData, validateSurfaceMetadata } from "./surface.js";
14
+ import { SurfaceManager, validateSessionEventData, validateSurfaceMetadata } from "./surface.js";
15
15
  import { foldRequestHeader } from "./request-header.js";
16
16
  export * from "./types.js";
17
17
  export { SessionPreparation } from "./preparation.js";
@@ -337,7 +337,7 @@ const attachments = new WeakMap();
337
337
  export class Session {
338
338
  log = [];
339
339
  /** Single incremental owner of surface acceptance and projection state. */
340
- surfaceManager = new SurfaceManager(this.log);
340
+ surfaceManager;
341
341
  /** The ordered surface over this session's event log. */
342
342
  get surface() {
343
343
  return this.surfaceManager;
@@ -388,10 +388,12 @@ export class Session {
388
388
  * @param seed - optional borrowed replay or fork events.
389
389
  * @param header - optional borrowed storage metadata.
390
390
  * @param inheritedEventCount - exact fork-inherited prefix length for a seeded header.
391
+ * @param projections - pure interpreters for plugin-owned message changes.
391
392
  * @returns a detached session.
393
+ * @throws when a seed event requires a missing message interpreter or fails validation.
392
394
  */
393
- static create(id, seed, header, inheritedEventCount) {
394
- return new Session(id, seed, header, 'snapshot', inheritedEventCount);
395
+ static create(id, seed, header, inheritedEventCount, projections) {
396
+ return new Session(id, seed, header, 'snapshot', inheritedEventCount, projections);
395
397
  }
396
398
  /**
397
399
  * Restore a detached session by adopting an independently owned or deeply frozen seed.
@@ -404,12 +406,15 @@ export class Session {
404
406
  * @param header - independently owned storage metadata.
405
407
  * @param inheritedEventCount - exact fork-inherited prefix length decoded from storage.
406
408
  * @param eventState - aliasing state carried from the operation that produced the seed.
409
+ * @param projections - pure interpreters for plugin-owned message changes.
407
410
  * @returns a restored detached session.
411
+ * @throws when a seed event requires a missing message interpreter or fails validation.
408
412
  */
409
- static fromRestore(id, seed, header, inheritedEventCount, eventState) {
410
- return new Session(id, seed, header, eventState, inheritedEventCount);
413
+ static fromRestore(id, seed, header, inheritedEventCount, eventState, projections) {
414
+ return new Session(id, seed, header, eventState, inheritedEventCount, projections);
411
415
  }
412
- constructor(id, seed, header, mode = 'snapshot', suppliedInheritedEventCount) {
416
+ constructor(id, seed, header, mode = 'snapshot', suppliedInheritedEventCount, projections = []) {
417
+ this.surfaceManager = new SurfaceManager(this.log, SessionLogOffset(0), projections);
413
418
  const restoredHeader = mode === 'snapshot' ? undefined : validateRestoredSessionHeader(id, header);
414
419
  if (seed !== undefined) {
415
420
  // Validate the seed to the SAME invariants `append` enforces, so a
@@ -475,6 +480,8 @@ export class Session {
475
480
  eventsSnapshot;
476
481
  /**
477
482
  * Return the immutable event stored at one exact sequence number.
483
+ * @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
484
+ * See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
478
485
  * @param seq - event sequence number.
479
486
  * @returns the accepted event, or undefined when the log does not contain it.
480
487
  */
@@ -485,6 +492,8 @@ export class Session {
485
492
  * Materialize an immutable snapshot of a half-open event sequence range.
486
493
  * A full current snapshot is reused until the next append; every previously
487
494
  * returned snapshot remains stable after later appends.
495
+ * @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
496
+ * See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
488
497
  * @param fromSeq - non-negative inclusive sequence number; defaults to the log start.
489
498
  * @param toSeqExclusive - non-negative exclusive sequence number; defaults to the current end.
490
499
  * @returns a frozen array of the selected deeply frozen events.
@@ -498,9 +507,12 @@ export class Session {
498
507
  }
499
508
  /**
500
509
  * Return this Session's events after its fork-inherited prefix.
510
+ * @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
511
+ * See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
501
512
  * @returns a fresh array containing child-owned events in log order.
502
513
  */
503
514
  ownEvents() {
515
+ // oxlint-disable-next-line typescript/no-deprecated -- Deprecated reader delegates to the deprecated range read.
504
516
  return this.snapshotEvents(this.inheritedEventCount);
505
517
  }
506
518
  /**
@@ -647,7 +659,7 @@ export class Session {
647
659
  derived = [];
648
660
  /** Surface position (nodes projected) the cache has reached. */
649
661
  derivedNodes = 0;
650
- /** {@link SurfaceManager.replaceGeneration} the cache was built under. */
662
+ /** {@link SurfaceManager.contentGeneration} the cache was built under. */
651
663
  derivedGeneration = 0;
652
664
  /**
653
665
  * Derive the LLM message history by walking the ordered sequences of
@@ -656,21 +668,21 @@ export class Session {
656
668
  * append records its `surfaceOp`, so a raw event with no marker (a chunk, a
657
669
  * turn boundary) is correctly absent, and a compaction `replace` deletes the
658
670
  * shadowed nodes from the derivation. The projection rules are
659
- * {@link deriveEventMessage}, folded per node.
671
+ * {@link deriveEventMessage}, with logged message projections applied
672
+ * without changing node membership or message identity.
660
673
  *
661
- * CACHED: each surface node is projected exactly once, when first seen — a
662
- * call costs O(new nodes), and a surface rewrite (a `replace`;
663
- * {@link SessionSurface.replaceGeneration}) rebuilds. The returned array is
674
+ * CACHED: pure tail growth costs O(new nodes); a replacement or message projection
675
+ * ({@link SessionSurface.contentGeneration}) rebuilds. The returned array is
664
676
  * a fresh snapshot per call (later appends never grow an array a caller
665
677
  * already holds); the `Message` objects in it are SHARED and **deep-frozen**.
666
- * Their content reuses the already frozen durable event data, so the cache
667
- * needs no second deep clone and consumers still cannot mutate the log.
678
+ * Unchanged content reuses frozen event data; projected blocks are frozen
679
+ * derived copies. Consumers cannot mutate the log through either form.
668
680
  * @returns a fresh array of the shared, frozen derived history.
669
681
  */
670
682
  deriveMessages() {
671
683
  const surface = this.surface;
672
684
  const nodes = surface.nodes;
673
- const generation = surface.replaceGeneration;
685
+ const generation = surface.contentGeneration;
674
686
  if (generation !== this.derivedGeneration) {
675
687
  this.derived = [];
676
688
  this.derivedNodes = 0;
@@ -691,13 +703,13 @@ export class Session {
691
703
  return [...this.derived];
692
704
  }
693
705
  /**
694
- * Instance face of the pure per-node `deriveEventMessage` export from
695
- * `surface.ts`.
706
+ * Project one event with all committed message projections applied.
707
+ * The original durable event remains unchanged.
696
708
  * @param event - the event to project.
697
709
  * @returns the derived message, or null when the event produces none.
698
710
  */
699
711
  deriveEventMessage(event) {
700
- return deriveEventMessage(event);
712
+ return this.surfaceManager.deriveEventMessage(event);
701
713
  }
702
714
  }
703
715
  /** Typed error for session fork rejections. */
@@ -719,6 +731,27 @@ export class SessionForkError extends Error {
719
731
  export class SessionStore extends Service {
720
732
  store = new Map();
721
733
  counter = 0;
734
+ projections = [];
735
+ /** Borrowed definitions for detached replay; contributions live until their registering fibers unload. */
736
+ get messageProjections() {
737
+ return this.projections;
738
+ }
739
+ /**
740
+ * Register one event interpreter for live creation, restore, and fork.
741
+ * Disposing the contribution makes sessions that used it refuse further derivation.
742
+ * @param projection - pure definition owned by the event's plugin.
743
+ * @returns the fiber-owned disposer.
744
+ * @throws when another definition already owns this event type.
745
+ */
746
+ registerMessageProjection(projection) {
747
+ if (this.projections.some(item => item.type === projection.type)) {
748
+ throw new Error(`session message projection "${projection.type}" is already registered`);
749
+ }
750
+ return this.ctx.effect(() => {
751
+ this.projections.push(projection);
752
+ return () => { this.projections.splice(this.projections.indexOf(projection), 1); };
753
+ }, 'sessions.registerMessageProjection()');
754
+ }
722
755
  constructor(ctx) {
723
756
  super(ctx, 'sessions');
724
757
  ctx.inject(['typert'], (typeCtx) => {
@@ -800,7 +833,7 @@ export class SessionStore extends Service {
800
833
  switch (eventState) {
801
834
  case 'detached':
802
835
  case 'shared-frozen':
803
- return Session.fromRestore(sessionId, options.seed, options.meta, options.inheritedEventCount, eventState);
836
+ return Session.fromRestore(sessionId, options.seed, options.meta, options.inheritedEventCount, eventState, this.projections);
804
837
  case undefined:
805
838
  break;
806
839
  /* v8 ignore next -- closed-union exhaustiveness guard */
@@ -821,7 +854,7 @@ export class SessionStore extends Service {
821
854
  ...meta?.delegationDepth === undefined ? {} : { delegationDepth: meta.delegationDepth },
822
855
  ...meta?.agentPreset === undefined ? {} : { agentPreset: meta.agentPreset },
823
856
  };
824
- return Session.create(sessionId, seed, header, options?.inheritedEventCount);
857
+ return Session.create(sessionId, seed, header, options?.inheritedEventCount, this.projections);
825
858
  }
826
859
  /**
827
860
  * Enter a {@link prepare}d session into the store: install the module-private
@@ -1032,6 +1065,7 @@ export class SessionStore extends Service {
1032
1065
  });
1033
1066
  }
1034
1067
  _forkSeed(session, requestedBoundary) {
1068
+ // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred.
1035
1069
  const lastEvent = session.snapshotEvents().at(-1);
1036
1070
  let boundary;
1037
1071
  if (requestedBoundary !== undefined) {
@@ -1049,10 +1083,12 @@ export class SessionStore extends Service {
1049
1083
  const lastSeq = lastEvent?.seq;
1050
1084
  throw new SessionForkError(`fork boundary ${boundary} does not exist in session "${session.id}" (last seq: ${lastSeq ?? 'none'})`, 'INVALID_BOUNDARY');
1051
1085
  }
1086
+ // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred.
1052
1087
  const boundaryEvent = session.eventAt(boundary);
1053
1088
  if (boundaryEvent === undefined || boundaryEvent.seq !== boundary) {
1054
1089
  throw new SessionForkError(`fork boundary ${boundary} does not match a contiguous event seq in session "${session.id}"`, 'INVALID_BOUNDARY');
1055
1090
  }
1091
+ // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred.
1056
1092
  const events = session.snapshotEvents(SessionLogOffset(0), SessionLogOffset(boundary + 1));
1057
1093
  const lastTurnBoundary = events
1058
1094
  .findLast(event => event.type === 'turn/start' || event.type === 'turn/end');
@@ -163,6 +163,7 @@ const install = Object.assign((ctx, fail) => {
163
163
  const seedSession = (session) => {
164
164
  const trace = freshTrace();
165
165
  traces.set(session, trace);
166
+ // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred.
166
167
  for (const event of session.snapshotEvents()) {
167
168
  applyTransition(trace, validateEvent(trace, event, fail));
168
169
  }
@@ -19,4 +19,6 @@
19
19
  * `.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.md`.
20
20
  */
21
21
  export declare const KNOWN_SESSION_EVENT_TYPES: ReadonlySet<string>;
22
+ /** Event types whose model-visible effects require an explicit pure interpreter. */
23
+ export declare const MESSAGE_PROJECTION_EVENT_TYPES: ReadonlySet<string>;
22
24
  //# sourceMappingURL=known-event-types.d.ts.map
@@ -39,6 +39,7 @@ export const KNOWN_SESSION_EVENT_TYPES = new Set([
39
39
  'goal/change',
40
40
  'hook/invoked',
41
41
  'hook/result',
42
+ 'image/offload',
42
43
  'llm/retry',
43
44
  'llm/retry-started',
44
45
  'model/selection',
@@ -75,5 +76,10 @@ export const KNOWN_SESSION_EVENT_TYPES = new Set([
75
76
  'turn/start',
76
77
  'user/message',
77
78
  'web/deepseek-search-llm-request',
79
+ 'workspace/changes',
80
+ ]);
81
+ /** Event types whose model-visible effects require an explicit pure interpreter. */
82
+ export const MESSAGE_PROJECTION_EVENT_TYPES = new Set([
83
+ 'image/offload',
78
84
  ]);
79
85
  //# sourceMappingURL=known-event-types.js.map
@@ -9,7 +9,32 @@
9
9
  */
10
10
  import type { Message } from '@deepseek-ai/dsh-llm';
11
11
  import { SessionLogOffset, SessionSeq } from './types.ts';
12
- import type { SessionEvent, SurfaceEvent, SurfaceOp } from './types.ts';
12
+ import type { SessionEvent, SessionEventType, SurfaceEvent, SurfaceOp } from './types.ts';
13
+ /** Readonly history immediately before a message-projection event. */
14
+ export interface SessionMessageProjectionContext {
15
+ /** Current message-producing event sequences in model-visible order. */
16
+ nodes: readonly SessionSeq[];
17
+ /** Contiguous event window; entries at or beyond the candidate seq are not committed inputs. */
18
+ events: readonly SessionEvent[];
19
+ /** Absolute sequence of the window's first event. */
20
+ baseSeq: SessionLogOffset;
21
+ /** Previously projected messages keyed by their original event sequences. */
22
+ messages: ReadonlyMap<SessionSeq, Message>;
23
+ }
24
+ /** Pure interpretation of one plugin-owned event that changes existing message content. */
25
+ export interface SessionMessageProjection<T extends SessionEventType = SessionEventType> {
26
+ /** Event interpreted by this definition; declare it with `@messageProjection` in SessionEventMap. */
27
+ type: T;
28
+ /**
29
+ * Validate the complete durable decision before returning any updates. Preserve
30
+ * message identities and publish immutable copies without mutating the input.
31
+ * @param event - candidate event, not yet applied to the supplied history.
32
+ * @param context - history preceding this decision.
33
+ * @returns changed current messages keyed by their original sequences.
34
+ * @throws when the durable decision cannot be applied to this history.
35
+ */
36
+ project(event: SessionEvent<T>, context: SessionMessageProjectionContext): ReadonlyMap<SessionSeq, Message>;
37
+ }
13
38
  /**
14
39
  * Whether an event type can join the model-visible surface.
15
40
  * @param type - event type to test.
@@ -51,17 +76,16 @@ export declare function isReplacementSurfaceEvent(event: SessionEvent): event is
51
76
  /**
52
77
  * Project a single event into the LLM message it derives to, or null when it
53
78
  * produces none — a non-surface event (attempt, boundary, log-only record) or an
54
- * empty-content assistant/message (which exists only to host usage). This is
55
- * THE per-node projection rule: `Session.deriveMessages` folds it over the
56
- * live surface, external reconstructors and pure projections fold the same
57
- * function over a log prefix's surface to rebuild the exact messages any
58
- * request was built from. The returned message is the already frozen message
59
- * nested in the event wrapper and shared by delivery, durable history, and
60
- * model requests.
79
+ * empty-content assistant/message (which exists only to host usage). A caller
80
+ * reconstructing model input supplies the same prefix's `projectedMessages`
81
+ * from {@link foldSurface}; without that map this function reads original
82
+ * event content. Session instance methods apply the live projection. Messages
83
+ * are immutable and unchanged content retains its durable identity.
61
84
  * @param event - the event to project.
85
+ * @param projectedMessages - message projections from the same log prefix's surface fold.
62
86
  * @returns the derived message, or null when the event produces none.
63
87
  */
64
- export declare function deriveEventMessage(event: SessionEvent): Message | null;
88
+ export declare function deriveEventMessage(event: SessionEvent, projectedMessages?: ReadonlyMap<SessionSeq, Message>): Message | null;
65
89
  /**
66
90
  * Reject noncanonical request-header fields and contradictory tool failure metadata.
67
91
  * This does not validate complete event payloads or embedded provider streams.
@@ -87,6 +111,8 @@ export interface SurfaceFoldResult {
87
111
  nodes: SessionSeq[];
88
112
  /** Replacement operations in event order. */
89
113
  replacements: SurfaceFoldReplacement[];
114
+ /** Immutable projected messages, keyed by their original event sequences. */
115
+ projectedMessages: ReadonlyMap<SessionSeq, Message>;
90
116
  }
91
117
  /** Readonly live projection of the message-producing session events. */
92
118
  export interface SessionSurface {
@@ -94,6 +120,8 @@ export interface SessionSurface {
94
120
  readonly nodes: readonly SessionSeq[];
95
121
  /** Monotonic count of committed positional replacements. */
96
122
  readonly replaceGeneration: number;
123
+ /** Monotonic count of committed replacements and plugin-owned message changes. */
124
+ readonly contentGeneration: number;
97
125
  }
98
126
  /**
99
127
  * Validate one event's surface metadata without checking membership in a log or surface.
@@ -106,14 +134,16 @@ export declare function validateSurfaceMetadata(event: SessionEvent): SurfaceOp
106
134
  /**
107
135
  * Replay a complete session log through the canonical surface fold.
108
136
  * @param events - session events in contiguous seq order.
137
+ * @param projections - pure interpreters for plugin-owned message changes; required definitions must be supplied.
109
138
  * @returns detached current sequences and replacement history.
110
- * @throws when an event violates surface metadata, source-event references, range, or tool-result rewrite rules.
139
+ * @throws when an interpreter is missing or an event violates its projection, surface metadata, source attribution, or replacement rules.
111
140
  */
112
- export declare function foldSurface(events: readonly SessionEvent[]): SurfaceFoldResult;
141
+ export declare function foldSurface(events: readonly SessionEvent[], projections?: readonly SessionMessageProjection[]): SurfaceFoldResult;
113
142
  /** Incremental ordered surface view and append-boundary validator. */
114
143
  export declare class SurfaceManager implements SessionSurface {
115
144
  private log;
116
145
  private readonly baseSeq;
146
+ private readonly projections;
117
147
  /** Shared transition state; replacement history is not retained. */
118
148
  private _state;
119
149
  /** Last processed absolute seq. */
@@ -123,8 +153,9 @@ export declare class SurfaceManager implements SessionSurface {
123
153
  /**
124
154
  * @param log - Contiguous complete log or loaded event window.
125
155
  * @param baseSeq - Absolute sequence of the window's first event.
156
+ * @param projections - live borrowed definitions; removing a used definition invalidates further reads.
126
157
  */
127
- constructor(log: readonly SessionEvent[], baseSeq?: SessionLogOffset);
158
+ constructor(log: readonly SessionEvent[], baseSeq?: SessionLogOffset, projections?: readonly SessionMessageProjection[]);
128
159
  /**
129
160
  * Validate the next candidate without mutating the committed surface.
130
161
  * @param event - candidate event that has not entered the log yet.
@@ -132,9 +163,19 @@ export declare class SurfaceManager implements SessionSurface {
132
163
  validateNext(event: SessionEvent): void;
133
164
  /** Monotonic count of folded positional replacements. */
134
165
  get replaceGeneration(): number;
166
+ /** Monotonic count of committed changes to existing model-visible content. */
167
+ get contentGeneration(): number;
168
+ /**
169
+ * Project one message with every committed message projection applied.
170
+ * @param event - message-producing or log-only event.
171
+ * @returns its immutable projected message, or null when it produces none.
172
+ */
173
+ deriveEventMessage(event: SessionEvent): Message | null;
135
174
  /** Surface event sequences in model-visible order. */
136
175
  get nodes(): readonly SessionSeq[];
137
176
  /** Fold events appended since the previous access. */
138
177
  private _processDelta;
178
+ /** Cached messages cannot outlive the definitions that interpreted their log. */
179
+ private _assertProjections;
139
180
  }
140
181
  //# sourceMappingURL=surface.d.ts.map