@deepseek-ai/dsh-session 0.1.6-alpha.2 → 0.1.7-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.
- package/README.i18n.yaml +2 -2
- package/README.md +11 -9
- package/README.zh.md +12 -8
- package/lib/index.js +215 -125
- package/lib/invariant.js +4 -1
- package/lib/types/fork.d.ts +18 -0
- package/lib/types/fork.js +28 -0
- package/lib/types/index.d.ts +27 -29
- package/lib/types/index.js +56 -61
- package/lib/types/invariant.js +5 -1
- package/lib/types/known-event-types.js +1 -0
- package/lib/types/repair.d.ts +42 -7
- package/lib/types/repair.js +52 -23
- package/lib/types/surface.d.ts +4 -4
- package/lib/types/surface.js +73 -17
- package/lib/types/types.d.ts +38 -15
- package/lib/types/types.js +1 -1
- package/package.json +15 -11
package/lib/types/surface.js
CHANGED
|
@@ -12,6 +12,7 @@ import { KNOWN_SESSION_EVENT_TYPES, MESSAGE_PROJECTION_EVENT_TYPES } from "./kno
|
|
|
12
12
|
/** Runtime counterpart of the message-producing event union. */
|
|
13
13
|
const SURFACE_EVENT_TYPES = new Set([
|
|
14
14
|
'system/message',
|
|
15
|
+
'developer/message',
|
|
15
16
|
'user/message',
|
|
16
17
|
'assistant/message',
|
|
17
18
|
'tool/result',
|
|
@@ -19,7 +20,7 @@ const SURFACE_EVENT_TYPES = new Set([
|
|
|
19
20
|
/**
|
|
20
21
|
* Whether an event type can join the model-visible surface.
|
|
21
22
|
* @param type - event type to test.
|
|
22
|
-
* @returns true for one of the
|
|
23
|
+
* @returns true for one of the message-producing event types.
|
|
23
24
|
*/
|
|
24
25
|
export function isSurfaceEligibleType(type) {
|
|
25
26
|
return SURFACE_EVENT_TYPES.has(type);
|
|
@@ -62,7 +63,7 @@ export function isReplacementSurfaceEvent(event) {
|
|
|
62
63
|
/**
|
|
63
64
|
* Project a single event into the LLM message it derives to, or null when it
|
|
64
65
|
* produces none — a non-surface event (attempt, boundary, log-only record) or an
|
|
65
|
-
* empty-content
|
|
66
|
+
* empty-content system, developer, or assistant message. A caller
|
|
66
67
|
* reconstructing model input supplies the same prefix's `projectedMessages`
|
|
67
68
|
* from {@link foldSurface}; without that map this function reads original
|
|
68
69
|
* event content. Session instance methods apply the live projection. Messages
|
|
@@ -90,12 +91,11 @@ export function deriveEventMessage(event, projectedMessages) {
|
|
|
90
91
|
case 'user/message': {
|
|
91
92
|
return event.data;
|
|
92
93
|
}
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
// max-tokens step's usage and must not inject a content-less assistant
|
|
97
|
-
// turn into the provider transcript.
|
|
94
|
+
// Empty system and developer nodes retain their surface positions without
|
|
95
|
+
// adding wire messages. An empty assistant event hosts a max-tokens step's
|
|
96
|
+
// usage and must not inject a content-less turn into the provider transcript.
|
|
98
97
|
case 'system/message':
|
|
98
|
+
case 'developer/message':
|
|
99
99
|
case 'assistant/message': {
|
|
100
100
|
if (event.data.message.content.length === 0)
|
|
101
101
|
return null;
|
|
@@ -115,14 +115,45 @@ function isRecord(value) {
|
|
|
115
115
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
116
116
|
}
|
|
117
117
|
/**
|
|
118
|
-
* Reject noncanonical request-header fields and contradictory tool failure metadata.
|
|
118
|
+
* Reject noncanonical request-header fields, developer roles/content, and contradictory tool failure metadata.
|
|
119
119
|
* This does not validate complete event payloads or embedded provider streams.
|
|
120
120
|
* @param event - event whose locally related payload fields are inspected.
|
|
121
121
|
* @param subject - event location to include in validation errors.
|
|
122
|
-
* @throws when request
|
|
122
|
+
* @throws when request-header fields, developer roles/content, or tool failure metadata are invalid.
|
|
123
123
|
*/
|
|
124
124
|
export function validateSessionEventData(event, subject) {
|
|
125
125
|
const data = event.data;
|
|
126
|
+
if (SURFACE_EVENT_TYPES.has(event.type) && isRecord(data)) {
|
|
127
|
+
const message = event.type === 'user/message' ? data : data['message'];
|
|
128
|
+
if (isRecord(message)) {
|
|
129
|
+
if ((event.type === 'developer/message') !== (message['role'] === 'developer')) {
|
|
130
|
+
throw new Error(`${subject} developer/message and developer role must occur together`);
|
|
131
|
+
}
|
|
132
|
+
if (message['role'] !== 'developer' && Array.isArray(message['content'])
|
|
133
|
+
&& message['content'].some((block) => isRecord(block)
|
|
134
|
+
&& (block['type'] === 'tool-addition' || block['type'] === 'tool-removal'))) {
|
|
135
|
+
throw new Error(`${subject} tool-change blocks require developer role`);
|
|
136
|
+
}
|
|
137
|
+
if (event.type === 'developer/message' && Array.isArray(message['content'])) {
|
|
138
|
+
let hasAdditions = false;
|
|
139
|
+
for (const block of message['content']) {
|
|
140
|
+
if (!isRecord(block) || (block['type'] !== 'tool-addition' && block['type'] !== 'tool-removal'))
|
|
141
|
+
continue;
|
|
142
|
+
if (typeof block['toolName'] !== 'string' || block['toolName'].length === 0) {
|
|
143
|
+
throw new Error(`${subject} ${block['type']} requires a nonempty toolName`);
|
|
144
|
+
}
|
|
145
|
+
if (block['type'] === 'tool-addition') {
|
|
146
|
+
hasAdditions = true;
|
|
147
|
+
if (Object.hasOwn(block, 'tool'))
|
|
148
|
+
throw new Error(`${subject} tool-addition must omit inline tool definitions`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (hasAdditions ? !isEventSeq(data['headerSeq']) : Object.hasOwn(data, 'headerSeq')) {
|
|
152
|
+
throw new Error(`${subject} requires headerSeq exactly when tool additions are present`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
126
157
|
if (event.type === 'request/header') {
|
|
127
158
|
if (!isRecord(data))
|
|
128
159
|
throw new Error(`${subject} data must be an object`);
|
|
@@ -145,10 +176,8 @@ export function validateSessionEventData(event, subject) {
|
|
|
145
176
|
if (data['error'] === undefined)
|
|
146
177
|
return;
|
|
147
178
|
const message = data['message'];
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (!isRecord(block) || block['isError'] !== true) {
|
|
151
|
-
throw new Error(`${subject} error requires message content[0].isError === true`);
|
|
179
|
+
if (!isRecord(message) || message['isError'] !== true) {
|
|
180
|
+
throw new Error(`${subject} error requires message.isError === true`);
|
|
152
181
|
}
|
|
153
182
|
}
|
|
154
183
|
}
|
|
@@ -238,6 +267,34 @@ function assertSourceEventReferences(event, shadowedSeqs) {
|
|
|
238
267
|
throw new Error(`surface replace: sourceEventSeqs must include every shadowed surface node; missing ${missing.join(', ')}`);
|
|
239
268
|
}
|
|
240
269
|
}
|
|
270
|
+
/** Resolve tool additions against their immutable historical request header. */
|
|
271
|
+
function assertDeveloperHeader(event, events, baseSeq) {
|
|
272
|
+
if (event.type !== 'developer/message')
|
|
273
|
+
return;
|
|
274
|
+
validateSessionEventData(event, `developer/message at seq ${event.seq}`);
|
|
275
|
+
if (event.data.headerSeq === undefined)
|
|
276
|
+
return;
|
|
277
|
+
const headerSeq = event.data.headerSeq;
|
|
278
|
+
const headerEvent = events[headerSeq - baseSeq];
|
|
279
|
+
if (headerSeq >= event.seq || headerEvent?.type !== 'request/header') {
|
|
280
|
+
throw new Error('developer/message headerSeq must reference an earlier request/header');
|
|
281
|
+
}
|
|
282
|
+
for (const block of event.data.message.content) {
|
|
283
|
+
if (block.type !== 'tool-addition')
|
|
284
|
+
continue;
|
|
285
|
+
const definitions = headerEvent.data.header.tools?.filter(tool => tool.name === block.toolName) ?? [];
|
|
286
|
+
if (definitions.length !== 1) {
|
|
287
|
+
throw new Error(`developer/message tool-addition "${block.toolName}" must name exactly one tool in headerSeq ${headerSeq}`);
|
|
288
|
+
}
|
|
289
|
+
const definition = definitions[0];
|
|
290
|
+
if (typeof definition.description !== 'string' || !isRecord(definition.parameters)) {
|
|
291
|
+
throw new Error(`developer/message tool-addition "${block.toolName}" requires a complete tool definition in headerSeq ${headerSeq}`);
|
|
292
|
+
}
|
|
293
|
+
if (Object.hasOwn(definition, 'deferLoading') && definition.deferLoading !== true) {
|
|
294
|
+
throw new Error('developer/message referenced tool deferLoading must be true when present');
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
241
298
|
/**
|
|
242
299
|
* Validate one event's surface metadata without checking membership in a log or surface.
|
|
243
300
|
* @param event - event whose marker and source sequence values are inspected.
|
|
@@ -309,15 +366,13 @@ function assertToolResultRewrite(event, shadowedSeqs, events, baseSeq) {
|
|
|
309
366
|
}
|
|
310
367
|
const originalRest = { ...original.data };
|
|
311
368
|
const replacementRest = { ...event.data };
|
|
312
|
-
const originalResult = original.data.message.content[0];
|
|
313
|
-
const replacementResult = event.data.message.content[0];
|
|
314
369
|
originalRest['message'] = {
|
|
315
370
|
...original.data.message,
|
|
316
|
-
content:
|
|
371
|
+
content: null,
|
|
317
372
|
};
|
|
318
373
|
replacementRest['message'] = {
|
|
319
374
|
...event.data.message,
|
|
320
|
-
content:
|
|
375
|
+
content: null,
|
|
321
376
|
};
|
|
322
377
|
if (!isDeepEqualJson(originalRest, replacementRest)) {
|
|
323
378
|
throw new Error('tool/result surface replacement may change only content');
|
|
@@ -346,6 +401,7 @@ function planSurfaceEvent(state, event, expectedSeq, events, baseSeq, projection
|
|
|
346
401
|
throw new Error(`session event seq ${event.seq} is not contiguous; expected ${expectedSeq}`);
|
|
347
402
|
}
|
|
348
403
|
const surfaceOp = validateSurfaceMetadata(event);
|
|
404
|
+
assertDeveloperHeader(event, events, baseSeq);
|
|
349
405
|
const projection = projections.find(item => item.type === event.type);
|
|
350
406
|
if (projection !== undefined) {
|
|
351
407
|
return { kind: 'project', projection, messages: projection.project(event, {
|
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, AssistantStreamRecord, ToolCallId, LlmCallConfig, LlmCallConfigAdapterDefaults, LlmFailure, SystemMessage, SystemPromptUpdate, TokenUsage, ToolResultMessage, ToolSchema, UserMessage } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import type { AssistantMessage, DeveloperMessage, 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'>;
|
|
@@ -51,7 +51,7 @@ export type OptionalSessionSeq = SessionSeq | null;
|
|
|
51
51
|
* immutable prior-generation, and current fast-path rules are recorded in
|
|
52
52
|
* `.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md`.
|
|
53
53
|
*/
|
|
54
|
-
export declare const SESSION_FORMAT_VERSION =
|
|
54
|
+
export declare const SESSION_FORMAT_VERSION = 4;
|
|
55
55
|
/**
|
|
56
56
|
* Immutable validated storage metadata, kept outside the conversation event log.
|
|
57
57
|
*/
|
|
@@ -103,8 +103,8 @@ export interface CreateSessionOptions {
|
|
|
103
103
|
readonly seed?: readonly SessionEvent[];
|
|
104
104
|
/**
|
|
105
105
|
* Exact fork-inherited prefix length when `meta.isSeeded` is true. The
|
|
106
|
-
* constructor
|
|
107
|
-
*
|
|
106
|
+
* constructor appends the child-owned tagged marker at the cut unless
|
|
107
|
+
* the seed already includes it followed by child-owned fork closers.
|
|
108
108
|
*/
|
|
109
109
|
readonly inheritedEventCount?: SessionLogOffset;
|
|
110
110
|
/**
|
|
@@ -196,6 +196,15 @@ export interface TurnEndReasonMap {
|
|
|
196
196
|
interrupted: {
|
|
197
197
|
kind: 'interrupted';
|
|
198
198
|
};
|
|
199
|
+
/**
|
|
200
|
+
* Fork-seed construction closed a turn that was still open at the fork
|
|
201
|
+
* boundary in the source session. Only fork seeds carry this marker — the
|
|
202
|
+
* loop never emits it — and the source events before the boundary remain
|
|
203
|
+
* intact in the child.
|
|
204
|
+
*/
|
|
205
|
+
forked: {
|
|
206
|
+
kind: 'forked';
|
|
207
|
+
};
|
|
199
208
|
}
|
|
200
209
|
/** The union over {@link TurnEndReasonMap} — why a turn ended; plugins extend it by merging variants into the map. */
|
|
201
210
|
export type TurnEndReason = TurnEndReasonMap[keyof TurnEndReasonMap];
|
|
@@ -212,6 +221,10 @@ export interface EpochHeader {
|
|
|
212
221
|
adapterDefaults?: LlmCallConfigAdapterDefaults;
|
|
213
222
|
/** Assembled tool schemas; absent for a tool-less request. */
|
|
214
223
|
tools?: ToolSchema[];
|
|
224
|
+
/** Retired request text; system prompts belong to system/message events.
|
|
225
|
+
* @persistenceReserved
|
|
226
|
+
*/
|
|
227
|
+
system?: never;
|
|
215
228
|
}
|
|
216
229
|
/** Registration-bound metadata for one resolved model route. */
|
|
217
230
|
export interface RequestContext {
|
|
@@ -279,6 +292,14 @@ export interface SessionEventMap {
|
|
|
279
292
|
* project their `content` verbatim; `source` tells them apart.
|
|
280
293
|
*/
|
|
281
294
|
'user/message': UserMessage;
|
|
295
|
+
/** An incremental agent session change admitted at the named turn and step. */
|
|
296
|
+
'developer/message': {
|
|
297
|
+
turn: number;
|
|
298
|
+
step: number;
|
|
299
|
+
message: DeveloperMessage;
|
|
300
|
+
/** Earlier request/header defining every tool addition; required exactly when additions are present. */
|
|
301
|
+
headerSeq?: SessionSeq;
|
|
302
|
+
};
|
|
282
303
|
/**
|
|
283
304
|
* The rendered system prompt on the model-visible surface. The loop appends
|
|
284
305
|
* the first one as surface node 0 before the step's first `user/message`.
|
|
@@ -356,7 +377,7 @@ export interface SessionEventMap {
|
|
|
356
377
|
message: ToolResultMessage;
|
|
357
378
|
/**
|
|
358
379
|
* Optional failure identity and raw user-facing reason, outside model content;
|
|
359
|
-
* allowed only when the
|
|
380
|
+
* allowed only when the message has `isError: true`.
|
|
360
381
|
*/
|
|
361
382
|
error?: {
|
|
362
383
|
name: string;
|
|
@@ -383,19 +404,21 @@ export interface SessionEventMap {
|
|
|
383
404
|
*/
|
|
384
405
|
'request/context': RequestContext;
|
|
385
406
|
/**
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
407
|
+
* Separates inherited or restored history from later lifecycle-owned work.
|
|
408
|
+
* This log-only marker need not be at {@link Session.firstLiveSeq}: a fork
|
|
409
|
+
* seed can already contain its tagged marker and child-owned synthetic
|
|
410
|
+
* closers before construction.
|
|
390
411
|
*
|
|
391
412
|
* A fresh fork child owns one `{ inherited: true }` marker at its exact
|
|
392
413
|
* inherited-prefix cut, even when that prefix ends in an ancestor marker.
|
|
393
|
-
*
|
|
394
|
-
*
|
|
414
|
+
* `buildForkSeed` appends that marker before any synthetic closers; the
|
|
415
|
+
* `Session` constructor supplies it when given only the inherited prefix.
|
|
416
|
+
* The last tagged marker is the current Session's cut; untagged markers
|
|
417
|
+
* keep ordinary restore and replay lifecycle boundaries.
|
|
395
418
|
*
|
|
396
|
-
* `Session`
|
|
397
|
-
* companion deliberately constrains nothing here, so a plugin
|
|
398
|
-
* would silently classify every live bracket before it as seed history.
|
|
419
|
+
* Only the `Session` constructor and `buildForkSeed` may create this marker.
|
|
420
|
+
* The invariant companion deliberately constrains nothing here, so a plugin
|
|
421
|
+
* appending one would silently classify every live bracket before it as seed history.
|
|
399
422
|
*
|
|
400
423
|
* An owner of a standalone open/close bracket (`compaction/start` …
|
|
401
424
|
* `compaction/end`) reads it because seed history and live work are otherwise
|
|
@@ -416,7 +439,7 @@ export type SessionEventType = keyof SessionEventMap;
|
|
|
416
439
|
* event types may carry {@link SurfaceOp}; system, user, and tool events may also cite
|
|
417
440
|
* earlier sources through {@link SessionEvent.sourceEventSeqs}.
|
|
418
441
|
*/
|
|
419
|
-
export type SurfaceEventType = 'system/message' | 'user/message' | 'assistant/message' | 'tool/result';
|
|
442
|
+
export type SurfaceEventType = 'system/message' | 'developer/message' | 'user/message' | 'assistant/message' | 'tool/result';
|
|
420
443
|
/** A message-producing event carrying its required surface operation. */
|
|
421
444
|
export type SurfaceEvent = SessionEvent<SurfaceEventType>;
|
|
422
445
|
/**
|
package/lib/types/types.js
CHANGED
|
@@ -51,5 +51,5 @@ export function SessionLogOffset(value) {
|
|
|
51
51
|
* immutable prior-generation, and current fast-path rules are recorded in
|
|
52
52
|
* `.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md`.
|
|
53
53
|
*/
|
|
54
|
-
export const SESSION_FORMAT_VERSION =
|
|
54
|
+
export const SESSION_FORMAT_VERSION = 4;
|
|
55
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.
|
|
4
|
+
"version": "0.1.7-alpha.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
"types": "./lib/types/types.d.ts",
|
|
27
27
|
"default": "./lib/types/types.js"
|
|
28
28
|
},
|
|
29
|
+
"./fork": {
|
|
30
|
+
"types": "./lib/types/fork.d.ts",
|
|
31
|
+
"default": "./lib/types/fork.js"
|
|
32
|
+
},
|
|
29
33
|
"./src/*": "./src/*",
|
|
30
34
|
"./package.json": "./package.json",
|
|
31
35
|
"./surface": {
|
|
@@ -41,19 +45,19 @@
|
|
|
41
45
|
],
|
|
42
46
|
"license": "MIT",
|
|
43
47
|
"peerDependencies": {
|
|
44
|
-
"@deepseek-ai/
|
|
45
|
-
"@deepseek-ai/
|
|
48
|
+
"@deepseek-ai/cordis": "~4.0.4",
|
|
49
|
+
"@deepseek-ai/dsh-scope": "0.1.7-alpha.2"
|
|
46
50
|
},
|
|
47
51
|
"devDependencies": {
|
|
48
|
-
"@deepseek-ai/dsh-invariants": "
|
|
49
|
-
"@deepseek-ai/dsh-scope": "
|
|
50
|
-
"@deepseek-ai/dsh-typert-protocol": "
|
|
51
|
-
"@deepseek-ai/dsh-typert-registry": "
|
|
52
|
-
"@deepseek-ai/cordis": "
|
|
52
|
+
"@deepseek-ai/dsh-invariants": "0.1.7-alpha.2",
|
|
53
|
+
"@deepseek-ai/dsh-scope": "0.1.7-alpha.2",
|
|
54
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.7-alpha.2",
|
|
55
|
+
"@deepseek-ai/dsh-typert-registry": "0.1.7-alpha.2",
|
|
56
|
+
"@deepseek-ai/cordis": "~4.0.4"
|
|
53
57
|
},
|
|
54
58
|
"dependencies": {
|
|
55
|
-
"@deepseek-ai/dsh-llm": "
|
|
56
|
-
"@deepseek-ai/dsh-
|
|
57
|
-
"@deepseek-ai/dsh-
|
|
59
|
+
"@deepseek-ai/dsh-llm": "0.1.7-alpha.2",
|
|
60
|
+
"@deepseek-ai/dsh-brand": "0.1.7-alpha.2",
|
|
61
|
+
"@deepseek-ai/dsh-util-values": "0.1.7-alpha.2"
|
|
58
62
|
}
|
|
59
63
|
}
|