@deepseek-ai/dsh-session 0.1.2-rc.1 → 0.1.5-alpha.1
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.
- package/README.i18n.yaml +2 -2
- package/README.md +15 -12
- package/README.zh.md +15 -12
- package/lib/index.js +254 -480
- package/lib/invariant.js +5 -2
- package/lib/types/index.d.ts +26 -21
- package/lib/types/index.js +109 -75
- package/lib/types/invariant.js +6 -2
- package/lib/types/known-event-types.js +6 -3
- package/lib/types/request-header.d.ts +4 -4
- package/lib/types/request-header.js +5 -7
- package/lib/types/surface.d.ts +18 -2
- package/lib/types/surface.js +110 -23
- package/lib/types/types.d.ts +101 -85
- package/lib/types/types.js +9 -10
- package/package.json +9 -13
- package/lib/types/chunk-rows.d.ts +0 -106
- package/lib/types/chunk-rows.js +0 -328
package/lib/types/surface.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { SessionEvent, SurfaceEvent, SurfaceOp } from './types.ts';
|
|
|
13
13
|
/**
|
|
14
14
|
* Whether an event type can join the model-visible surface.
|
|
15
15
|
* @param type - event type to test.
|
|
16
|
-
* @returns true for one of the
|
|
16
|
+
* @returns true for one of the four message-producing event types.
|
|
17
17
|
*/
|
|
18
18
|
export declare function isSurfaceEligibleType(type: string): boolean;
|
|
19
19
|
/**
|
|
@@ -50,7 +50,7 @@ export declare function isReplacementSurfaceEvent(event: SessionEvent): event is
|
|
|
50
50
|
};
|
|
51
51
|
/**
|
|
52
52
|
* Project a single event into the LLM message it derives to, or null when it
|
|
53
|
-
* produces none — a non-surface event (
|
|
53
|
+
* produces none — a non-surface event (attempt, boundary, log-only record) or an
|
|
54
54
|
* empty-content assistant/message (which exists only to host usage). This is
|
|
55
55
|
* THE per-node projection rule: `Session.deriveMessages` folds it over the
|
|
56
56
|
* live surface, external reconstructors and pure projections fold the same
|
|
@@ -62,6 +62,14 @@ export declare function isReplacementSurfaceEvent(event: SessionEvent): event is
|
|
|
62
62
|
* @returns the derived message, or null when the event produces none.
|
|
63
63
|
*/
|
|
64
64
|
export declare function deriveEventMessage(event: SessionEvent): Message | null;
|
|
65
|
+
/**
|
|
66
|
+
* Reject noncanonical request-header fields and contradictory tool failure metadata.
|
|
67
|
+
* This does not validate complete event payloads or embedded provider streams.
|
|
68
|
+
* @param event - event whose locally related payload fields are inspected.
|
|
69
|
+
* @param subject - event location to include in validation errors.
|
|
70
|
+
* @throws when request data/header is not an object, optional header fields are empty, or tool failure metadata contradicts its message.
|
|
71
|
+
*/
|
|
72
|
+
export declare function validateSessionEventData(event: Pick<SessionEvent, 'type' | 'data'>, subject: string): void;
|
|
65
73
|
/** One replacement operation observed while folding a session surface. */
|
|
66
74
|
export interface SurfaceFoldReplacement {
|
|
67
75
|
/** Seq of the event that replaced the prior surface range. */
|
|
@@ -87,6 +95,14 @@ export interface SessionSurface {
|
|
|
87
95
|
/** Monotonic count of committed positional replacements. */
|
|
88
96
|
readonly replaceGeneration: number;
|
|
89
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Validate one event's surface metadata without checking membership in a log or surface.
|
|
100
|
+
* @param event - event whose marker and source sequence values are inspected.
|
|
101
|
+
* Unknown ignorable records retain opaque metadata and never change the surface.
|
|
102
|
+
* @returns the validated operation, or undefined for a log-only or unknown ignorable event.
|
|
103
|
+
* @throws when metadata violates event-local eligibility, marker, or source-sequence rules.
|
|
104
|
+
*/
|
|
105
|
+
export declare function validateSurfaceMetadata(event: SessionEvent): SurfaceOp | undefined;
|
|
90
106
|
/**
|
|
91
107
|
* Replay a complete session log through the canonical surface fold.
|
|
92
108
|
* @param events - session events in contiguous seq order.
|
package/lib/types/surface.js
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
* @module @deepseek-ai/dsh-session/surface
|
|
9
9
|
*/
|
|
10
10
|
import { SessionLogOffset, SessionSeq } from "./types.js";
|
|
11
|
+
import { KNOWN_SESSION_EVENT_TYPES } from "./known-event-types.js";
|
|
11
12
|
/** Runtime counterpart of the message-producing event union. */
|
|
12
13
|
const SURFACE_EVENT_TYPES = new Set([
|
|
14
|
+
'system/message',
|
|
13
15
|
'user/message',
|
|
14
16
|
'assistant/message',
|
|
15
17
|
'tool/result',
|
|
@@ -17,7 +19,7 @@ const SURFACE_EVENT_TYPES = new Set([
|
|
|
17
19
|
/**
|
|
18
20
|
* Whether an event type can join the model-visible surface.
|
|
19
21
|
* @param type - event type to test.
|
|
20
|
-
* @returns true for one of the
|
|
22
|
+
* @returns true for one of the four message-producing event types.
|
|
21
23
|
*/
|
|
22
24
|
export function isSurfaceEligibleType(type) {
|
|
23
25
|
return SURFACE_EVENT_TYPES.has(type);
|
|
@@ -30,7 +32,8 @@ export function isSurfaceEligibleType(type) {
|
|
|
30
32
|
export function isSurfaceEvent(event) {
|
|
31
33
|
if (!SURFACE_EVENT_TYPES.has(event.type))
|
|
32
34
|
return false;
|
|
33
|
-
|
|
35
|
+
const candidate = event;
|
|
36
|
+
return candidate.surfaceOp !== undefined;
|
|
34
37
|
}
|
|
35
38
|
/**
|
|
36
39
|
* Narrow an event to an append-origin surface event: one that entered the
|
|
@@ -58,7 +61,7 @@ export function isReplacementSurfaceEvent(event) {
|
|
|
58
61
|
}
|
|
59
62
|
/**
|
|
60
63
|
* Project a single event into the LLM message it derives to, or null when it
|
|
61
|
-
* produces none — a non-surface event (
|
|
64
|
+
* produces none — a non-surface event (attempt, boundary, log-only record) or an
|
|
62
65
|
* empty-content assistant/message (which exists only to host usage). This is
|
|
63
66
|
* THE per-node projection rule: `Session.deriveMessages` folds it over the
|
|
64
67
|
* live surface, external reconstructors and pure projections fold the same
|
|
@@ -71,7 +74,7 @@ export function isReplacementSurfaceEvent(event) {
|
|
|
71
74
|
*/
|
|
72
75
|
export function deriveEventMessage(event) {
|
|
73
76
|
// Intentionally non-exhaustive: only message-producing events derive
|
|
74
|
-
// history; turn/step boundaries,
|
|
77
|
+
// history; turn/step boundaries, failed attempts, and errors are trace/replay
|
|
75
78
|
// data.
|
|
76
79
|
switch (event.type) {
|
|
77
80
|
// Ordinary prompts and injected context project in user role: the event's
|
|
@@ -85,10 +88,13 @@ export function deriveEventMessage(event) {
|
|
|
85
88
|
case 'user/message': {
|
|
86
89
|
return event.data;
|
|
87
90
|
}
|
|
91
|
+
// An empty-content message projects to no wire message. For
|
|
92
|
+
// system/message the node records "no system prompt" while keeping its
|
|
93
|
+
// surface position; for assistant/message the event exists only to host a
|
|
94
|
+
// max-tokens step's usage and must not inject a content-less assistant
|
|
95
|
+
// turn into the provider transcript.
|
|
96
|
+
case 'system/message':
|
|
88
97
|
case 'assistant/message': {
|
|
89
|
-
// Skip an empty-content assistant/message: it exists only to host a
|
|
90
|
-
// max-tokens step's usage and must not inject a content-less assistant
|
|
91
|
-
// turn into the provider transcript.
|
|
92
98
|
if (event.data.message.content.length === 0)
|
|
93
99
|
return null;
|
|
94
100
|
return event.data.message;
|
|
@@ -97,11 +103,53 @@ export function deriveEventMessage(event) {
|
|
|
97
103
|
return event.data.message;
|
|
98
104
|
}
|
|
99
105
|
default:
|
|
100
|
-
// A non-surface event (boundary,
|
|
106
|
+
// A non-surface event (boundary, attempt, log-only record) projects to
|
|
101
107
|
// no message. Merge-extensible union: no assertNever here.
|
|
102
108
|
return null;
|
|
103
109
|
}
|
|
104
110
|
}
|
|
111
|
+
/** Whether a payload field is a JSON object rather than an array or scalar. */
|
|
112
|
+
function isRecord(value) {
|
|
113
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Reject noncanonical request-header fields and contradictory tool failure metadata.
|
|
117
|
+
* This does not validate complete event payloads or embedded provider streams.
|
|
118
|
+
* @param event - event whose locally related payload fields are inspected.
|
|
119
|
+
* @param subject - event location to include in validation errors.
|
|
120
|
+
* @throws when request data/header is not an object, optional header fields are empty, or tool failure metadata contradicts its message.
|
|
121
|
+
*/
|
|
122
|
+
export function validateSessionEventData(event, subject) {
|
|
123
|
+
const data = event.data;
|
|
124
|
+
if (event.type === 'request/header') {
|
|
125
|
+
if (!isRecord(data))
|
|
126
|
+
throw new Error(`${subject} data must be an object`);
|
|
127
|
+
const header = data['header'];
|
|
128
|
+
if (!isRecord(header))
|
|
129
|
+
throw new Error(`${subject} header must be an object`);
|
|
130
|
+
if (Object.hasOwn(header, 'system'))
|
|
131
|
+
throw new Error(`${subject} must omit header.system; use system/message`);
|
|
132
|
+
if (Array.isArray(header['tools']) && header['tools'].length === 0) {
|
|
133
|
+
throw new Error(`${subject} must omit empty tools`);
|
|
134
|
+
}
|
|
135
|
+
const defaults = header['adapterDefaults'];
|
|
136
|
+
if (isRecord(defaults) && Object.keys(defaults).length === 0) {
|
|
137
|
+
throw new Error(`${subject} must omit empty adapterDefaults`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (event.type === 'tool/result') {
|
|
141
|
+
if (!isRecord(data))
|
|
142
|
+
throw new Error(`${subject} data must be an object`);
|
|
143
|
+
if (data['error'] === undefined)
|
|
144
|
+
return;
|
|
145
|
+
const message = data['message'];
|
|
146
|
+
const content = isRecord(message) ? message['content'] : undefined;
|
|
147
|
+
const block = Array.isArray(content) ? content[0] : undefined;
|
|
148
|
+
if (!isRecord(block) || block['isError'] !== true) {
|
|
149
|
+
throw new Error(`${subject} error requires message content[0].isError === true`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
105
153
|
/** Create an empty surface fold state. */
|
|
106
154
|
function createFoldState() {
|
|
107
155
|
return { nodes: [], replaceGeneration: 0 };
|
|
@@ -118,16 +166,19 @@ function isReplaceOp(value) {
|
|
|
118
166
|
const op = value;
|
|
119
167
|
return Object.keys(op).length === 3
|
|
120
168
|
&& Object.hasOwn(op, 'op')
|
|
121
|
-
&& Object.hasOwn(op, '
|
|
122
|
-
&& Object.hasOwn(op, '
|
|
169
|
+
&& Object.hasOwn(op, 'startSeq')
|
|
170
|
+
&& Object.hasOwn(op, 'endSeq')
|
|
123
171
|
&& op['op'] === 'replace'
|
|
124
|
-
&& isEventSeq(op['
|
|
125
|
-
&& isEventSeq(op['
|
|
172
|
+
&& isEventSeq(op['startSeq'])
|
|
173
|
+
&& isEventSeq(op['endSeq']);
|
|
126
174
|
}
|
|
127
175
|
/** Validate event-local surface eligibility and return its operation. */
|
|
128
176
|
function surfaceOpOf(event) {
|
|
129
177
|
const raw = event;
|
|
130
178
|
if (!isSurfaceEligibleType(event.type)) {
|
|
179
|
+
// Unknown ignorable records retain opaque metadata without affecting history.
|
|
180
|
+
if (!KNOWN_SESSION_EVENT_TYPES.has(event.type) && event.ignorable === true)
|
|
181
|
+
return;
|
|
131
182
|
if (raw.surfaceOp !== undefined) {
|
|
132
183
|
throw new Error(`session event "${event.type}" is not surface-eligible and cannot carry surfaceOp`);
|
|
133
184
|
}
|
|
@@ -153,13 +204,16 @@ function surfaceOpOf(event) {
|
|
|
153
204
|
/** Validate cited source-event seqs against prior log entries and the replacement range. */
|
|
154
205
|
function assertProvenance(event, shadowedSeqs) {
|
|
155
206
|
const raw = event.sourceEventSeqs;
|
|
207
|
+
if (event.type === 'assistant/message' && raw !== undefined) {
|
|
208
|
+
throw new Error('assistant/message embeds its source stream and cannot carry sourceEventSeqs');
|
|
209
|
+
}
|
|
156
210
|
const sources = new Set();
|
|
157
211
|
if (raw !== undefined) {
|
|
158
212
|
if (!Array.isArray(raw)) {
|
|
159
213
|
throw new Error(`sourceEventSeqs on event at seq ${event.seq} must be an array when present`);
|
|
160
214
|
}
|
|
161
|
-
if (raw.length === 0
|
|
162
|
-
throw new Error('sourceEventSeqs must not be empty
|
|
215
|
+
if (raw.length === 0) {
|
|
216
|
+
throw new Error('sourceEventSeqs must not be empty');
|
|
163
217
|
}
|
|
164
218
|
let nonEarlierSource;
|
|
165
219
|
for (const source of raw) {
|
|
@@ -182,18 +236,35 @@ function assertProvenance(event, shadowedSeqs) {
|
|
|
182
236
|
throw new Error(`surface replace: sourceEventSeqs must include every shadowed surface node; missing ${missing.join(', ')}`);
|
|
183
237
|
}
|
|
184
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Validate one event's surface metadata without checking membership in a log or surface.
|
|
241
|
+
* @param event - event whose marker and source sequence values are inspected.
|
|
242
|
+
* Unknown ignorable records retain opaque metadata and never change the surface.
|
|
243
|
+
* @returns the validated operation, or undefined for a log-only or unknown ignorable event.
|
|
244
|
+
* @throws when metadata violates event-local eligibility, marker, or source-sequence rules.
|
|
245
|
+
*/
|
|
246
|
+
export function validateSurfaceMetadata(event) {
|
|
247
|
+
const op = surfaceOpOf(event);
|
|
248
|
+
if (op !== undefined && op !== 'append'
|
|
249
|
+
&& (op.startSeq >= event.seq || op.endSeq >= event.seq)) {
|
|
250
|
+
throw new Error(`surface replace at seq ${event.seq}: startSeq and endSeq must reference earlier events`);
|
|
251
|
+
}
|
|
252
|
+
if (op !== undefined)
|
|
253
|
+
assertProvenance(event, []);
|
|
254
|
+
return op;
|
|
255
|
+
}
|
|
185
256
|
/** Locate one replacement range without mutating the current fold state. */
|
|
186
257
|
function replacementRange(state, op) {
|
|
187
|
-
const startIdx = state.nodes.indexOf(op.
|
|
258
|
+
const startIdx = state.nodes.indexOf(op.startSeq);
|
|
188
259
|
if (startIdx === -1) {
|
|
189
|
-
throw new Error(`surface replace: start seq ${op.
|
|
260
|
+
throw new Error(`surface replace: start seq ${op.startSeq} not found in surface`);
|
|
190
261
|
}
|
|
191
|
-
const endIdx = state.nodes.indexOf(op.
|
|
262
|
+
const endIdx = state.nodes.indexOf(op.endSeq);
|
|
192
263
|
if (endIdx === -1) {
|
|
193
|
-
throw new Error(`surface replace: end seq ${op.
|
|
264
|
+
throw new Error(`surface replace: end seq ${op.endSeq} not found in surface`);
|
|
194
265
|
}
|
|
195
266
|
if (startIdx > endIdx) {
|
|
196
|
-
throw new Error(`surface replace: start seq ${op.
|
|
267
|
+
throw new Error(`surface replace: start seq ${op.startSeq} (index ${startIdx}) is after end seq ${op.endSeq} (index ${endIdx})`);
|
|
197
268
|
}
|
|
198
269
|
return {
|
|
199
270
|
startIdx,
|
|
@@ -251,26 +322,42 @@ function assertToolResultRewrite(event, shadowedSeqs, events, baseSeq) {
|
|
|
251
322
|
}
|
|
252
323
|
}
|
|
253
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* Protect the system prompt at surface node 0. A replacement covering node 0
|
|
327
|
+
* while that node is a `system/message` must itself be a `system/message` over
|
|
328
|
+
* exactly that node; later system nodes carry no protection and a compaction
|
|
329
|
+
* range may shadow them.
|
|
330
|
+
*/
|
|
331
|
+
function assertSystemHeadRewrite(event, state, startIdx, shadowedSeqs, events, baseSeq) {
|
|
332
|
+
if (startIdx !== 0)
|
|
333
|
+
return;
|
|
334
|
+
const head = events[state.nodes[0] - baseSeq];
|
|
335
|
+
if (head?.type !== 'system/message')
|
|
336
|
+
return;
|
|
337
|
+
if (event.type !== 'system/message' || shadowedSeqs.length !== 1) {
|
|
338
|
+
throw new Error('surface replace: node 0 holds the system prompt and may be rewritten only by a system/message over exactly that node');
|
|
339
|
+
}
|
|
340
|
+
}
|
|
254
341
|
/** Validate one event at its replay boundary and prepare its atomic fold transition. */
|
|
255
342
|
function planSurfaceEvent(state, event, expectedSeq, events, baseSeq) {
|
|
256
343
|
if (event.seq !== expectedSeq) {
|
|
257
344
|
throw new Error(`session event seq ${event.seq} is not contiguous; expected ${expectedSeq}`);
|
|
258
345
|
}
|
|
259
|
-
const surfaceOp =
|
|
346
|
+
const surfaceOp = validateSurfaceMetadata(event);
|
|
260
347
|
if (surfaceOp === undefined)
|
|
261
348
|
return;
|
|
262
349
|
if (surfaceOp === 'append') {
|
|
263
|
-
assertProvenance(event, []);
|
|
264
350
|
return { kind: 'append', seq: event.seq };
|
|
265
351
|
}
|
|
266
352
|
const range = replacementRange(state, surfaceOp);
|
|
267
353
|
assertProvenance(event, range.shadowedSeqs);
|
|
268
354
|
assertToolResultRewrite(event, range.shadowedSeqs, events, baseSeq);
|
|
355
|
+
assertSystemHeadRewrite(event, state, range.startIdx, range.shadowedSeqs, events, baseSeq);
|
|
269
356
|
return {
|
|
270
357
|
kind: 'replace',
|
|
271
358
|
seq: event.seq,
|
|
272
|
-
start: surfaceOp.
|
|
273
|
-
end: surfaceOp.
|
|
359
|
+
start: surfaceOp.startSeq,
|
|
360
|
+
end: surfaceOp.endSeq,
|
|
274
361
|
...range,
|
|
275
362
|
};
|
|
276
363
|
}
|
package/lib/types/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Branded, type BrandedNumber } from '@deepseek-ai/dsh-brand';
|
|
2
|
-
import type { AssistantMessage, ToolCallId, LlmCallConfig, LlmCallConfigAdapterDefaults, LlmFailure,
|
|
2
|
+
import type { AssistantMessage, AssistantStreamRecord, ToolCallId, LlmCallConfig, LlmCallConfigAdapterDefaults, LlmFailure, SystemMessage, SystemPromptUpdate, 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
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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
|
|
51
|
-
*
|
|
52
|
-
*
|
|
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 =
|
|
54
|
+
export declare const SESSION_FORMAT_VERSION = 3;
|
|
56
55
|
/**
|
|
57
56
|
* Immutable validated storage metadata, kept outside the conversation event log.
|
|
58
57
|
*/
|
|
59
58
|
export interface SessionHeader {
|
|
60
59
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
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:
|
|
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.
|
|
108
|
-
* constructor seed
|
|
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
|
-
*
|
|
127
|
-
*
|
|
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
|
-
/**
|
|
134
|
+
/** Events that are independently owned or already deeply frozen. */
|
|
131
135
|
readonly seed: SessionEvent[];
|
|
132
|
-
/**
|
|
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
|
-
/**
|
|
137
|
-
readonly
|
|
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
|
|
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
|
|
188
|
-
*
|
|
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';
|
|
@@ -194,8 +200,9 @@ export interface TurnEndReasonMap {
|
|
|
194
200
|
/** The union over {@link TurnEndReasonMap} — why a turn ended; plugins extend it by merging variants into the map. */
|
|
195
201
|
export type TurnEndReason = TurnEndReasonMap[keyof TurnEndReasonMap];
|
|
196
202
|
/**
|
|
197
|
-
* Logged request state outside derived history: call config
|
|
198
|
-
*
|
|
203
|
+
* Logged request state outside derived history: call config and tools. The
|
|
204
|
+
* system prompt is derived history — surface node 0, a `system/message` event.
|
|
205
|
+
* The latest full `request/header` snapshot reconstructs the header; canonical
|
|
199
206
|
* empty optional fields are absent.
|
|
200
207
|
*/
|
|
201
208
|
export interface EpochHeader {
|
|
@@ -203,8 +210,6 @@ export interface EpochHeader {
|
|
|
203
210
|
config: LlmCallConfig;
|
|
204
211
|
/** Effective config fields materialized from the exact adapter rather than proposed by a caller. */
|
|
205
212
|
adapterDefaults?: LlmCallConfigAdapterDefaults;
|
|
206
|
-
/** Rendered system prompt text; absent for a system-less request. */
|
|
207
|
-
system?: string;
|
|
208
213
|
/** Assembled tool schemas; absent for a tool-less request. */
|
|
209
214
|
tools?: ToolSchema[];
|
|
210
215
|
}
|
|
@@ -216,6 +221,8 @@ export interface RequestContext {
|
|
|
216
221
|
model: string;
|
|
217
222
|
/** Maximum combined request and response context in tokens, when advertised. */
|
|
218
223
|
contextWindow?: number;
|
|
224
|
+
/** `'in-history'` when the route reads the latest `system` message at any position as the effective system prompt. */
|
|
225
|
+
systemPromptUpdate?: SystemPromptUpdate;
|
|
219
226
|
}
|
|
220
227
|
/**
|
|
221
228
|
* Why a `request/header` snapshot was appended: `'initial'` — the log's first
|
|
@@ -229,8 +236,8 @@ export type RequestHeaderReason = 'initial' | 'resume' | 'change' | 'series';
|
|
|
229
236
|
/**
|
|
230
237
|
* The merge-extensible, append-only source of truth for an agent interaction.
|
|
231
238
|
* Message history is derived from this log. Every event is lossless JSON and
|
|
232
|
-
* sequence numbers stay contiguous
|
|
233
|
-
*
|
|
239
|
+
* sequence numbers stay contiguous. Assistant attempt events embed their exact
|
|
240
|
+
* compact raw streams so persistence stores one durable settlement per attempt.
|
|
234
241
|
*/
|
|
235
242
|
export interface SessionEventMap {
|
|
236
243
|
/**
|
|
@@ -272,11 +279,22 @@ export interface SessionEventMap {
|
|
|
272
279
|
* project their `content` verbatim; `source` tells them apart.
|
|
273
280
|
*/
|
|
274
281
|
'user/message': UserMessage;
|
|
275
|
-
/**
|
|
276
|
-
|
|
282
|
+
/**
|
|
283
|
+
* The rendered system prompt on the model-visible surface. The loop appends
|
|
284
|
+
* the first one as surface node 0 before the step's first `user/message`.
|
|
285
|
+
* A prepared in-history route can append nonempty changes in a continuing
|
|
286
|
+
* series. An incapable route or new series normalizes text to the first system
|
|
287
|
+
* node. Normalization empties nonempty later nodes, then rewrites the head if
|
|
288
|
+
* needed, through logged per-node replacements. An empty rendering always
|
|
289
|
+
* clears all active system nodes, leaving no older instructions model-visible.
|
|
290
|
+
* Empty later nodes are dormant and project to no message; an empty head with
|
|
291
|
+
* no active later node records "no system prompt". Restored nonempty text follows
|
|
292
|
+
* the same route and series rule; empty nodes never restore older text.
|
|
293
|
+
*/
|
|
294
|
+
'system/message': {
|
|
277
295
|
turn: number;
|
|
278
296
|
step: number;
|
|
279
|
-
|
|
297
|
+
message: SystemMessage;
|
|
280
298
|
};
|
|
281
299
|
/**
|
|
282
300
|
* Assembled assistant message for one step (derived history uses this).
|
|
@@ -292,9 +310,21 @@ export interface SessionEventMap {
|
|
|
292
310
|
turn: number;
|
|
293
311
|
step: number;
|
|
294
312
|
message: AssistantMessage;
|
|
313
|
+
/** Exact timed model stream, compacted without joining delta boundaries. */
|
|
314
|
+
stream: AssistantStreamRecord[];
|
|
295
315
|
usage?: TokenUsage;
|
|
296
316
|
interrupted?: true;
|
|
297
317
|
};
|
|
318
|
+
/**
|
|
319
|
+
* One model attempt that committed no surface message. The embedded stream
|
|
320
|
+
* preserves a failed, retried, cancelled, or stream-error attempt that
|
|
321
|
+
* reached settlement without fabricating model-visible history.
|
|
322
|
+
*/
|
|
323
|
+
'assistant/attempt': {
|
|
324
|
+
turn: number;
|
|
325
|
+
step: number;
|
|
326
|
+
stream: AssistantStreamRecord[];
|
|
327
|
+
};
|
|
298
328
|
/**
|
|
299
329
|
* The model requested one tool invocation: `name` with the raw `arguments`
|
|
300
330
|
* JSON string exactly as the model produced it (unparsed). `callId` pairs the
|
|
@@ -322,6 +352,7 @@ export interface SessionEventMap {
|
|
|
322
352
|
turn: number;
|
|
323
353
|
step: number;
|
|
324
354
|
message: ToolResultMessage;
|
|
355
|
+
/** Optional failure identity; allowed only when the tool-result block has `isError: true`. */
|
|
325
356
|
error?: {
|
|
326
357
|
name: string;
|
|
327
358
|
code: string;
|
|
@@ -339,20 +370,22 @@ export interface SessionEventMap {
|
|
|
339
370
|
startsSeries?: true;
|
|
340
371
|
};
|
|
341
372
|
/**
|
|
342
|
-
* Route metadata for the next request, logged only when the route
|
|
343
|
-
* changes. It does not participate in request
|
|
373
|
+
* Route metadata for the next request, logged only when the route, capacity,
|
|
374
|
+
* or system prompt update mode changes. It does not participate in request
|
|
375
|
+
* reconstruction or header equality. Prompt admission uses the bound prepared
|
|
376
|
+
* call's capability, not this snapshot from an earlier request.
|
|
344
377
|
*/
|
|
345
378
|
'request/context': RequestContext;
|
|
346
379
|
/**
|
|
347
380
|
* Marks the end of a constructor seed. Events before it have smaller seq
|
|
348
381
|
* values and came from the seed (resume, fork, or replay); this lifecycle
|
|
349
382
|
* produced none of them. This log-only event is the durable projection of
|
|
350
|
-
* {@link Session.firstLiveSeq}.
|
|
351
|
-
* carry the meaning.
|
|
383
|
+
* {@link Session.firstLiveSeq}.
|
|
352
384
|
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
385
|
+
* A fresh fork child owns one `{ inherited: true }` marker at its exact
|
|
386
|
+
* inherited-prefix cut, even when that prefix ends in an ancestor marker.
|
|
387
|
+
* The last tagged marker is the current Session's cut; untagged markers keep
|
|
388
|
+
* ordinary restore and replay lifecycle boundaries.
|
|
356
389
|
*
|
|
357
390
|
* `Session`'s constructor is the only legitimate writer. The invariant
|
|
358
391
|
* companion deliberately constrains nothing here, so a plugin appending one
|
|
@@ -365,60 +398,52 @@ export interface SessionEventMap {
|
|
|
365
398
|
* writers — a concurrently live session holds its own boundary elsewhere,
|
|
366
399
|
* so tolerating concurrent writers needs a signal beyond the log.
|
|
367
400
|
*/
|
|
368
|
-
'session/end-seed':
|
|
401
|
+
'session/end-seed': {
|
|
402
|
+
inherited?: true;
|
|
403
|
+
};
|
|
369
404
|
}
|
|
370
405
|
/** The appendable event-type keys of {@link SessionEventMap}, plugin-merged extensions included. */
|
|
371
406
|
export type SessionEventType = keyof SessionEventMap;
|
|
372
407
|
/**
|
|
373
408
|
* The subset of {@link SessionEventType} values whose events produce LLM
|
|
374
409
|
* messages and are eligible to appear on the ordered surface. Only these
|
|
375
|
-
* event types may carry {@link SurfaceOp} and
|
|
410
|
+
* event types may carry {@link SurfaceOp}; system, user, and tool events may also cite
|
|
411
|
+
* earlier sources through {@link SessionEvent.sourceEventSeqs}.
|
|
376
412
|
*/
|
|
377
|
-
export type SurfaceEventType = 'user/message' | 'assistant/message' | 'tool/result';
|
|
378
|
-
/**
|
|
379
|
-
|
|
380
|
-
* `surfaceOp` is guaranteed present (mandatory), narrowed from a
|
|
381
|
-
* surface-eligible {@link SessionEvent} by checking both `type` and
|
|
382
|
-
* `surfaceOp` at runtime.
|
|
383
|
-
*
|
|
384
|
-
* Use the `isSurfaceEvent` type guard (in `surface.ts`) to narrow a
|
|
385
|
-
* `SessionEvent` to this type.
|
|
386
|
-
*/
|
|
387
|
-
export type SurfaceEvent = SessionEvent<SurfaceEventType> & {
|
|
388
|
-
surfaceOp: SurfaceOp;
|
|
389
|
-
};
|
|
413
|
+
export type SurfaceEventType = 'system/message' | 'user/message' | 'assistant/message' | 'tool/result';
|
|
414
|
+
/** A message-producing event carrying its required surface operation. */
|
|
415
|
+
export type SurfaceEvent = SessionEvent<SurfaceEventType>;
|
|
390
416
|
/**
|
|
391
417
|
* How a session event entered the ordered surface. Only valid on
|
|
392
418
|
* {@link SurfaceEventType} events.
|
|
393
419
|
*
|
|
394
420
|
* - `'append'`: added to the tail — normal path for user/assistant/tool
|
|
395
421
|
* messages.
|
|
396
|
-
* - `{ op: 'replace',
|
|
397
|
-
* (inclusive) through `
|
|
398
|
-
* surface nodes in the current surface. `
|
|
422
|
+
* - `{ op: 'replace', startSeq, endSeq }`: replaces surface nodes from `startSeq`
|
|
423
|
+
* (inclusive) through `endSeq` (inclusive) with this node. Both must exist as
|
|
424
|
+
* surface nodes in the current surface. `startSeq === endSeq` replaces a single
|
|
399
425
|
* node. The node's {@link SessionEvent.sourceEventSeqs} must include every
|
|
400
426
|
* shadowed surface node. Used by compaction; any surface-replacing producer
|
|
401
427
|
* may use it.
|
|
402
428
|
*/
|
|
403
429
|
export type SurfaceOp = 'append' | {
|
|
404
430
|
op: 'replace';
|
|
405
|
-
|
|
406
|
-
|
|
431
|
+
startSeq: SessionSeq;
|
|
432
|
+
endSeq: SessionSeq;
|
|
407
433
|
};
|
|
408
434
|
/**
|
|
409
435
|
* Surface placement and cited source-event seqs for {@link Session.append}. Required on
|
|
410
436
|
* message-producing events and forbidden on log-only events.
|
|
411
437
|
*/
|
|
412
|
-
export
|
|
438
|
+
export type SurfaceIntent<T extends SurfaceEventType = SurfaceEventType> = {
|
|
413
439
|
surfaceOp: SurfaceOp;
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
*/
|
|
440
|
+
} & (T extends 'assistant/message' ? {
|
|
441
|
+
/** Assistant messages embed their provider stream instead of citing source events. */
|
|
442
|
+
sourceEventSeqs?: never;
|
|
443
|
+
} : {
|
|
444
|
+
/** Complete non-empty set of known earlier source-event seqs. */
|
|
420
445
|
sourceEventSeqs?: SessionSeq[];
|
|
421
|
-
}
|
|
446
|
+
});
|
|
422
447
|
/**
|
|
423
448
|
* One immutable entry in the session log.
|
|
424
449
|
*
|
|
@@ -426,9 +451,9 @@ export interface SurfaceIntent {
|
|
|
426
451
|
* unions), so `switch (event.type)` narrows `event.data` without casts.
|
|
427
452
|
*
|
|
428
453
|
* The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional:
|
|
429
|
-
* they only exist on {@link SurfaceEventType} variants (`user/message`,
|
|
454
|
+
* they only exist on {@link SurfaceEventType} variants (`system/message`, `user/message`,
|
|
430
455
|
* `assistant/message`, `tool/result`).
|
|
431
|
-
* Non-surface events (boundary markers,
|
|
456
|
+
* Non-surface events (boundary markers, attempts, errors) never carry
|
|
432
457
|
* surface metadata — the compiler enforces this at `Session.append()`
|
|
433
458
|
* call sites.
|
|
434
459
|
*/
|
|
@@ -451,19 +476,10 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
|
|
451
476
|
* inconvenience) rather than silently resuming a gutted session.
|
|
452
477
|
*/
|
|
453
478
|
ignorable?: true;
|
|
454
|
-
} & (K extends SurfaceEventType ? {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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.
|
|
462
|
-
*/
|
|
463
|
-
sourceEventSeqs?: SessionSeq[];
|
|
464
|
-
/** How this event entered the surface; absent for non-surface events. */
|
|
465
|
-
surfaceOp?: SurfaceOp;
|
|
466
|
-
} : object);
|
|
479
|
+
} & (K extends SurfaceEventType ? SurfaceIntent<K> : {
|
|
480
|
+
surfaceOp?: never;
|
|
481
|
+
sourceEventSeqs?: never;
|
|
482
|
+
});
|
|
467
483
|
}[T];
|
|
468
484
|
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
469
485
|
interface RemoteErrorDetailsMap {
|