@deepseek-ai/dsh-session 0.2.2-rc.1 → 0.2.2-rc.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 +4 -4
- package/README.md +3 -1
- package/README.zh.md +3 -1
- package/lib/index.js +151 -79
- package/lib/types/fork.d.ts.map +1 -0
- package/lib/types/fork.js.map +1 -0
- package/lib/types/index.d.ts +17 -1
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +82 -63
- package/lib/types/index.js.map +1 -0
- package/lib/types/known-event-types.d.ts.map +1 -0
- package/lib/types/known-event-types.js +1 -0
- package/lib/types/known-event-types.js.map +1 -0
- package/lib/types/preparation.d.ts.map +1 -0
- package/lib/types/preparation.js.map +1 -0
- package/lib/types/repair.d.ts.map +1 -0
- package/lib/types/repair.js.map +1 -0
- package/lib/types/request-header.d.ts.map +1 -0
- package/lib/types/request-header.js.map +1 -0
- package/lib/types/seq-ranges.d.ts.map +1 -0
- package/lib/types/seq-ranges.js.map +1 -0
- package/lib/types/surface.d.ts +6 -0
- package/lib/types/surface.d.ts.map +1 -0
- package/lib/types/surface.js +57 -11
- package/lib/types/surface.js.map +1 -0
- package/lib/types/tool-history.d.ts.map +1 -0
- package/lib/types/tool-history.js.map +1 -0
- package/lib/types/types.d.ts +4 -0
- package/lib/types/types.d.ts.map +1 -0
- package/lib/types/types.js.map +1 -0
- package/package.json +12 -9
- package/src/fork.ts +30 -0
- package/src/index.ts +1555 -0
- package/src/known-event-types.ts +91 -0
- package/src/preparation.ts +49 -0
- package/src/repair.ts +211 -0
- package/src/request-header.ts +69 -0
- package/src/seq-ranges.ts +73 -0
- package/src/surface.ts +758 -0
- package/src/tool-history.ts +75 -0
- package/src/types.ts +574 -0
package/README.i18n.yaml
CHANGED
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
en: c4bf3103b9762b6c
|
|
28
28
|
zh: 369c734bb20de2ea
|
|
29
29
|
/deepseek-ai-dsh-session/use-this-package/write-experimental-plugin-records:
|
|
30
|
-
en:
|
|
31
|
-
zh:
|
|
30
|
+
en: 002b8240caa0c57c
|
|
31
|
+
zh: e0954dd62913a944
|
|
32
32
|
/deepseek-ai-dsh-session/use-this-package/fork-a-session:
|
|
33
|
-
en:
|
|
34
|
-
zh:
|
|
33
|
+
en: 4fcc9eb5c0278b51
|
|
34
|
+
zh: 2a26e81ebffbd9a6
|
|
35
35
|
/deepseek-ai-dsh-session/use-this-package/flush-durable-state:
|
|
36
36
|
en: 70a7673145c851ad
|
|
37
37
|
zh: 0dd1667d9518993d
|
package/README.md
CHANGED
|
@@ -71,7 +71,7 @@ Session log positions use two numeric types. `SessionSeq` identifies an existing
|
|
|
71
71
|
|
|
72
72
|
`appendPluginRecord(session, type, data)` appends one plugin record for a package under `packages/experimental/`; the `verify-plugin-record-callers` check rejects any other production caller in this repository. Plugins outside this repository must not call it either, although no repository check can inspect them.
|
|
73
73
|
|
|
74
|
-
Declare each record in `PluginRecordMap` through `@deepseek-ai/dsh-session/types`, with a description and an explicit payload type annotation in the owning experimental package's `src/`. Names use `plugin:<owner>/<record>`, where `owner` is the
|
|
74
|
+
Declare each record in `PluginRecordMap` through `@deepseek-ai/dsh-session/types`, with a description and an explicit payload type annotation in the owning experimental package's `src/`. Names use `plugin:<owner>/<record>`, where `owner` is the package name after removing the legacy `@deepseek-ai/dsh-experimental-` prefix when present, otherwise `@deepseek-ai/dsh-`; for example, `plugin:pi-extensions/entry`. TypeScript checks each name and payload against its declaration; the writer's runtime JSON snapshot rejects values that cannot be preserved losslessly. Put extension-defined or other dynamic names inside the payload of a declared record.
|
|
75
75
|
|
|
76
76
|
The [experimental persistence catalog](../../../docs/experimental-persistence-catalog.md) lists current plugin-record declarations with their owners, descriptions, payload type annotations, and source files. These declarations remain separate from `SessionEventMap`, expanded persistence schemas, released type history, and `KNOWN_SESSION_EVENT_TYPES`. Records carry `ignorable: true`: a build that does not recognize one retains and skips it on read. Records never enter the model-visible surface. Resume and fork carry them with the rest of the log; Session format migration keeps them on a best-effort basis.
|
|
77
77
|
|
|
@@ -83,6 +83,8 @@ The [experimental persistence catalog](../../../docs/experimental-persistence-ca
|
|
|
83
83
|
|
|
84
84
|
The logical `SessionHeader.isSeeded` field reports whether fork history exists without exposing a positional integer. `Session.inheritedEventCount` retains the exact checked `SessionLogOffset`; `ownEvents()` returns events at and after that cut, and `isOwnSeq(seq)` accepts only an existing child-owned position. A low-level seeded constructor must supply an explicit `seed` and `inheritedEventCount` because the constructor seed can contain child-owned setup events after the inherited prefix.
|
|
85
85
|
|
|
86
|
+
`session.append(type, data, ...opts)` appends one event and returns that event. `session.append(inputs)` accepts a readonly array of positional `[type, data, ...opts]` tuples (`SessionEventInput`) and returns the committed events in input order; an empty array appends nothing. Array admission validates every candidate and synchronous dispatch before changing the log or message surface, so observers read the complete committed group. Persistence listeners retain their own durability rules; array admission alone does not guarantee crash-atomic storage.
|
|
87
|
+
|
|
86
88
|
### Flush durable state
|
|
87
89
|
|
|
88
90
|
`ctx.sessions.flush(session)` dispatches the awaited durability checkpoint: every persistence listener flushes and the call settles after all of them. A producer that needs an immediate durability barrier awaits it instead of assuming the write-behind drained.
|
package/README.zh.md
CHANGED
|
@@ -71,7 +71,7 @@ session.deriveMessages() // the derived model history
|
|
|
71
71
|
|
|
72
72
|
`appendPluginRecord(session, type, data)` 为 `packages/experimental/` 下的包追加一条插件记录;`verify-plugin-record-callers` 检查拒绝本仓库中任何其他生产调用方。本仓库之外的插件同样不得调用它,尽管仓库检查无法检查它们。
|
|
73
73
|
|
|
74
|
-
在所属实验性包的 `src/` 中,通过 `@deepseek-ai/dsh-session/types` 的 `PluginRecordMap` 为每条记录声明描述与显式 payload 类型注解。名称使用 `plugin:<owner>/<record>`,其中 `owner`
|
|
74
|
+
在所属实验性包的 `src/` 中,通过 `@deepseek-ai/dsh-session/types` 的 `PluginRecordMap` 为每条记录声明描述与显式 payload 类型注解。名称使用 `plugin:<owner>/<record>`,其中 `owner` 是包名去掉前缀后的部分:带旧前缀时去掉 `@deepseek-ai/dsh-experimental-`,否则去掉 `@deepseek-ai/dsh-`;例如 `plugin:pi-extensions/entry`。TypeScript 按声明检查每个名称与 payload;写入方运行时的 JSON 快照拒绝无法无损保留的值。扩展自定义名称或其他动态名称放在已声明记录的 payload 中。
|
|
75
75
|
|
|
76
76
|
[实验性持久化目录](../../../docs/experimental-persistence-catalog.zh.md)列出当前插件记录声明及其所属包、描述、payload 类型注解与源文件。这些声明与 `SessionEventMap`、展开的持久化 schema、已发布类型历史及 `KNOWN_SESSION_EVENT_TYPES` 分开。记录带有 `ignorable: true`:不认识某条记录的构建在读取时保留并跳过它。记录从不进入模型可见的 surface。resume 与 fork 随日志其余部分一起携带它们;Session 格式迁移以尽力而为的方式保留它们。
|
|
77
77
|
|
|
@@ -83,6 +83,8 @@ session.deriveMessages() // the derived model history
|
|
|
83
83
|
|
|
84
84
|
逻辑 `SessionHeader.isSeeded` 字段报告是否存在 fork 历史,而不公开位置整数。`Session.inheritedEventCount` 保留经过校验的精确 `SessionLogOffset`;`ownEvents()` 返回从该切点开始的事件,`isOwnSeq(seq)` 只接受已存在且由子会话拥有的位置。底层带 seed 构造必须显式提供 `seed` 与 `inheritedEventCount`,因为构造 seed 可以在继承前缀之后包含子会话自有的设置事件。
|
|
85
85
|
|
|
86
|
+
`session.append(type, data, ...opts)` 追加一个事件并返回该事件。`session.append(inputs)` 接受位置参数元组 `[type, data, ...opts]`(`SessionEventInput`)的只读数组,并按输入顺序返回已提交事件;空数组不追加任何内容。数组准入会在改变日志或surface前验证所有候选和同步分发,因此观察者读取完整的已提交组。持久化监听器保留各自的持久性规则;仅数组准入不保证进程崩溃时整组存储的原子性。
|
|
87
|
+
|
|
86
88
|
### 刷新持久状态
|
|
87
89
|
|
|
88
90
|
`ctx.sessions.flush(session)` 分发需等待完成的持久性检查点:每个持久化监听器都会刷新,调用在所有监听器结算后完成。需要立即持久性屏障的生产方应等待它,而不是假定延后写入已经排空。
|
package/lib/index.js
CHANGED
|
@@ -123,6 +123,7 @@ const KNOWN_SESSION_EVENT_TYPES = new Set([
|
|
|
123
123
|
"team/member",
|
|
124
124
|
"team/message/delivered",
|
|
125
125
|
"team/message/queued",
|
|
126
|
+
"team/mode",
|
|
126
127
|
"team/task",
|
|
127
128
|
"todo/write",
|
|
128
129
|
"tool-workflow/agent-end",
|
|
@@ -331,12 +332,12 @@ function assertSourceEventReferences(event, shadowedSeqs) {
|
|
|
331
332
|
if (missing.length > 0) throw new Error(`surface replace: sourceEventSeqs must include every shadowed surface node; missing ${missing.join(", ")}`);
|
|
332
333
|
}
|
|
333
334
|
/** Resolve tool additions against their immutable historical request header. */
|
|
334
|
-
function assertDeveloperHeader(event,
|
|
335
|
+
function assertDeveloperHeader(event, read) {
|
|
335
336
|
if (event.type !== "developer/message") return;
|
|
336
337
|
validateSessionEventData(event, `developer/message at seq ${event.seq}`);
|
|
337
338
|
if (event.data.headerSeq === void 0) return;
|
|
338
339
|
const headerSeq = event.data.headerSeq;
|
|
339
|
-
const headerEvent =
|
|
340
|
+
const headerEvent = read(headerSeq);
|
|
340
341
|
if (headerSeq >= event.seq || headerEvent?.type !== "request/header") throw new Error("developer/message headerSeq must reference an earlier request/header");
|
|
341
342
|
for (const block of event.data.message.content) {
|
|
342
343
|
if (block.type !== "tool-addition") continue;
|
|
@@ -391,11 +392,11 @@ function isDeepEqualJson(a, b) {
|
|
|
391
392
|
return aKeys.every((key) => Object.hasOwn(b, key) && isDeepEqualJson(a[key], bRecord[key]));
|
|
392
393
|
}
|
|
393
394
|
/** Restrict a tool-result replacement to one current result's content. */
|
|
394
|
-
function assertToolResultRewrite(event, shadowedSeqs,
|
|
395
|
+
function assertToolResultRewrite(event, shadowedSeqs, read) {
|
|
395
396
|
if (event.type !== "tool/result") return;
|
|
396
397
|
if (shadowedSeqs.length !== 1) throw new Error("tool/result surface replacement must rewrite exactly one current node");
|
|
397
398
|
for (const originalSeq of shadowedSeqs) {
|
|
398
|
-
const original =
|
|
399
|
+
const original = read(originalSeq);
|
|
399
400
|
if (original?.type !== "tool/result") throw new Error("tool/result surface replacement must target a current tool/result");
|
|
400
401
|
const originalRest = { ...original.data };
|
|
401
402
|
const replacementRest = { ...event.data };
|
|
@@ -416,27 +417,36 @@ function assertToolResultRewrite(event, shadowedSeqs, events, baseSeq) {
|
|
|
416
417
|
* exactly that node; later system nodes carry no protection and a compaction
|
|
417
418
|
* range may shadow them.
|
|
418
419
|
*/
|
|
419
|
-
function assertSystemHeadRewrite(event, state, startIdx, shadowedSeqs,
|
|
420
|
+
function assertSystemHeadRewrite(event, state, startIdx, shadowedSeqs, read) {
|
|
420
421
|
if (startIdx !== 0) return;
|
|
421
|
-
if (
|
|
422
|
+
if (read(state.nodes[0])?.type !== "system/message") return;
|
|
422
423
|
if (event.type !== "system/message" || shadowedSeqs.length !== 1) throw new Error("surface replace: node 0 holds the system prompt and may be rewritten only by a system/message over exactly that node");
|
|
423
424
|
}
|
|
424
425
|
/** Validate one event at its replay boundary and prepare its atomic fold transition. */
|
|
425
|
-
function planSurfaceEvent(state, event, expectedSeq, events, baseSeq, projections) {
|
|
426
|
+
function planSurfaceEvent(state, event, expectedSeq, events, baseSeq, projections, candidates) {
|
|
426
427
|
if (event.seq !== expectedSeq) throw new Error(`session event seq ${event.seq} is not contiguous; expected ${expectedSeq}`);
|
|
427
428
|
const surfaceOp = validateSurfaceMetadata(event);
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
kind: "project",
|
|
432
|
-
projection,
|
|
433
|
-
messages: projection.project(event, {
|
|
434
|
-
nodes: state.nodes,
|
|
435
|
-
events,
|
|
436
|
-
baseSeq,
|
|
437
|
-
messages: state.projectedMessages
|
|
438
|
-
})
|
|
429
|
+
const read = (seq) => {
|
|
430
|
+
const index = seq - baseSeq;
|
|
431
|
+
return index < events.length ? events[index] : candidates?.[index - events.length];
|
|
439
432
|
};
|
|
433
|
+
assertDeveloperHeader(event, read);
|
|
434
|
+
const projection = projections.find((item) => item.type === event.type);
|
|
435
|
+
if (projection !== void 0) {
|
|
436
|
+
let projectionEvents;
|
|
437
|
+
return {
|
|
438
|
+
kind: "project",
|
|
439
|
+
projection,
|
|
440
|
+
messages: projection.project(event, {
|
|
441
|
+
nodes: state.nodes,
|
|
442
|
+
get events() {
|
|
443
|
+
return candidates === void 0 ? events : projectionEvents ??= [...events, ...candidates];
|
|
444
|
+
},
|
|
445
|
+
baseSeq,
|
|
446
|
+
messages: state.projectedMessages
|
|
447
|
+
})
|
|
448
|
+
};
|
|
449
|
+
}
|
|
440
450
|
if (MESSAGE_PROJECTION_EVENT_TYPES.has(event.type)) throw new Error(`session event "${event.type}" requires a message projection; load its owning plugin or supply its projection definition`);
|
|
441
451
|
if (surfaceOp === void 0) return;
|
|
442
452
|
if (surfaceOp === "append") return {
|
|
@@ -445,8 +455,8 @@ function planSurfaceEvent(state, event, expectedSeq, events, baseSeq, projection
|
|
|
445
455
|
};
|
|
446
456
|
const range = replacementRange(state, surfaceOp);
|
|
447
457
|
assertSourceEventReferences(event, range.shadowedSeqs);
|
|
448
|
-
assertToolResultRewrite(event, range.shadowedSeqs,
|
|
449
|
-
assertSystemHeadRewrite(event, state, range.startIdx, range.shadowedSeqs,
|
|
458
|
+
assertToolResultRewrite(event, range.shadowedSeqs, read);
|
|
459
|
+
assertSystemHeadRewrite(event, state, range.startIdx, range.shadowedSeqs, read);
|
|
450
460
|
return {
|
|
451
461
|
kind: "replace",
|
|
452
462
|
seq: event.seq,
|
|
@@ -535,6 +545,40 @@ var SurfaceManager = class {
|
|
|
535
545
|
plan: planSurfaceEvent(this._state, event, expectedSeq, this.log, this.baseSeq, this.projections)
|
|
536
546
|
};
|
|
537
547
|
}
|
|
548
|
+
/**
|
|
549
|
+
* Validate consecutive candidates against an isolated surface fold.
|
|
550
|
+
* @param events - candidates immediately following the committed log.
|
|
551
|
+
* @returns a callback adopting the validated fold after all candidates enter the log.
|
|
552
|
+
*/
|
|
553
|
+
prepareBatch(events) {
|
|
554
|
+
this._assertProjections();
|
|
555
|
+
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta();
|
|
556
|
+
const lastProcessedSeq = SessionSeq(this.baseSeq + this.log.length + events.length - 1);
|
|
557
|
+
if (events.every((event) => (event.surfaceOp === void 0 || event.surfaceOp === "append") && !this.projections.some((projection) => projection.type === event.type))) {
|
|
558
|
+
const plans = events.map((event, index) => planSurfaceEvent(this._state, event, SessionSeq(this.baseSeq + this.log.length + index), this.log, this.baseSeq, this.projections, events));
|
|
559
|
+
return () => {
|
|
560
|
+
for (const plan of plans) applySurfacePlan(this._state, plan);
|
|
561
|
+
this._lastProcessedSeq = lastProcessedSeq;
|
|
562
|
+
this._pendingPlan = void 0;
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
const state = {
|
|
566
|
+
...this._state,
|
|
567
|
+
nodes: [...this._state.nodes],
|
|
568
|
+
projectedMessages: new Map(this._state.projectedMessages),
|
|
569
|
+
projections: new Set(this._state.projections)
|
|
570
|
+
};
|
|
571
|
+
const plans = events.map((event, index) => {
|
|
572
|
+
const plan = planSurfaceEvent(state, event, SessionSeq(this.baseSeq + this.log.length + index), this.log, this.baseSeq, this.projections, events);
|
|
573
|
+
applySurfacePlan(state, plan);
|
|
574
|
+
return plan;
|
|
575
|
+
});
|
|
576
|
+
return () => {
|
|
577
|
+
for (const plan of plans) applySurfacePlan(this._state, plan);
|
|
578
|
+
this._lastProcessedSeq = lastProcessedSeq;
|
|
579
|
+
this._pendingPlan = void 0;
|
|
580
|
+
};
|
|
581
|
+
}
|
|
538
582
|
/** Monotonic count of folded positional replacements. */
|
|
539
583
|
get replaceGeneration() {
|
|
540
584
|
this._assertProjections();
|
|
@@ -1315,6 +1359,36 @@ function pluginRecordOf(event) {
|
|
|
1315
1359
|
data: event.data
|
|
1316
1360
|
};
|
|
1317
1361
|
}
|
|
1362
|
+
/** Copy live append inputs before sequence assignment. */
|
|
1363
|
+
function snapshotAppend(type, data, opts, subject) {
|
|
1364
|
+
const surfaceOp = opts?.surfaceOp;
|
|
1365
|
+
const sourceEventSeqs = opts?.sourceEventSeqs;
|
|
1366
|
+
const dataSnapshot = snapshotJsonValue(data);
|
|
1367
|
+
if (dataSnapshot === void 0) throw new Error(`${subject} carries non-JSON-serializable data`);
|
|
1368
|
+
const surface = snapshotJsonValue({
|
|
1369
|
+
...sourceEventSeqs === void 0 ? {} : { sourceEventSeqs },
|
|
1370
|
+
...surfaceOp === void 0 ? {} : { surfaceOp }
|
|
1371
|
+
});
|
|
1372
|
+
if (surface === void 0) throw new Error(`${subject} carries non-JSON-serializable surface metadata`);
|
|
1373
|
+
return {
|
|
1374
|
+
type,
|
|
1375
|
+
data: dataSnapshot,
|
|
1376
|
+
surface
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
/** Construct and validate one live event from detached inputs. */
|
|
1380
|
+
function createAppendEvent(snapshot, seq) {
|
|
1381
|
+
const { type, data, surface } = snapshot;
|
|
1382
|
+
const event = deepFreeze({
|
|
1383
|
+
type,
|
|
1384
|
+
seq,
|
|
1385
|
+
time: Date.now(),
|
|
1386
|
+
data,
|
|
1387
|
+
...surface
|
|
1388
|
+
});
|
|
1389
|
+
validateSessionEventData(event, `session event "${type}" at seq ${seq}`);
|
|
1390
|
+
return event;
|
|
1391
|
+
}
|
|
1318
1392
|
/**
|
|
1319
1393
|
* An event-sourced session: an append-only log of {@link SessionEvent}s.
|
|
1320
1394
|
*
|
|
@@ -1325,6 +1399,7 @@ function pluginRecordOf(event) {
|
|
|
1325
1399
|
*/
|
|
1326
1400
|
var Session = class Session {
|
|
1327
1401
|
log = [];
|
|
1402
|
+
batchPublication;
|
|
1328
1403
|
/** Single incremental owner of surface acceptance and projection state. */
|
|
1329
1404
|
surfaceManager;
|
|
1330
1405
|
/** The ordered surface over this session's event log. */
|
|
@@ -1479,71 +1554,68 @@ var Session = class Session {
|
|
|
1479
1554
|
get seq() {
|
|
1480
1555
|
return SessionLogOffset(this.log.length);
|
|
1481
1556
|
}
|
|
1482
|
-
/**
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
* the log, the append is committed: observer failures are logged and
|
|
1487
|
-
* contained per listener, so they do not change the return value or prevent
|
|
1488
|
-
* later listeners from observing the same accepted event.
|
|
1489
|
-
* Independent Sessions append without notifying store observers. Once a store
|
|
1490
|
-
* prepares or enters the instance, writes require a completed creation
|
|
1491
|
-
* announcement and remain disabled after removal.
|
|
1492
|
-
*
|
|
1493
|
-
* @param type - The event type (key of {@link SessionEventMap}).
|
|
1494
|
-
* @param data - The event payload; must be JSON-serializable.
|
|
1495
|
-
* @param opts - Surface metadata: `surfaceOp` controls how the event enters
|
|
1496
|
-
* the ordered surface; `sourceEventSeqs` lists the seq numbers of earlier
|
|
1497
|
-
* events this one derives from. REQUIRED for
|
|
1498
|
-
* {@link SurfaceEventType} events (every message-producing event must
|
|
1499
|
-
* declare how it joins the surface, the sole source of derived model
|
|
1500
|
-
* history) and
|
|
1501
|
-
* rejected by the compiler for non-surface types like `turn/start` or
|
|
1502
|
-
* `assistant/attempt`. Assistant messages embed their exact provider
|
|
1503
|
-
* stream and cannot cite top-level source events.
|
|
1504
|
-
* @returns the logged event — its assigned `seq`/`time` plus the SNAPSHOT of
|
|
1505
|
-
* `data` that entered the log, so reading `event.data` back sees the logged
|
|
1506
|
-
* value, never the caller's still-mutable input.
|
|
1507
|
-
* @throws if a store-owned Session is unentered or its creation announcement
|
|
1508
|
-
* has not completed, or if `data` or surface
|
|
1509
|
-
* metadata is not losslessly JSON-serializable
|
|
1510
|
-
* (BigInt, function, symbol, undefined, negative zero, non-finite number,
|
|
1511
|
-
* circular reference, sparse array, or an exotic object such as
|
|
1512
|
-
* Map/Set/Date/class instance), or when the candidate violates the
|
|
1513
|
-
* request-header empty-field or tool-error consistency rules, or the
|
|
1514
|
-
* canonical surface contract (marker shape and eligibility, unique
|
|
1515
|
-
* earlier source-event references, positional replacement validity, and complete
|
|
1516
|
-
* shadowed-node coverage). One iterative pass reads, validates, and
|
|
1517
|
-
* copies each nested value once, so a stateful getter cannot supply one value
|
|
1518
|
-
* to validation and another to storage. The event log is the durable source
|
|
1519
|
-
* of truth, so a bad event fails at the append site rather than later during
|
|
1520
|
-
* a backend flush. A synchronous internal dispatch validation failure or an
|
|
1521
|
-
* append reentered while this acceptance/publication boundary is open also
|
|
1522
|
-
* rejects before the log changes.
|
|
1523
|
-
*/
|
|
1557
|
+
/** Current event while synchronously notifying a batch's observers. */
|
|
1558
|
+
get currentBatchPublication() {
|
|
1559
|
+
return this.batchPublication;
|
|
1560
|
+
}
|
|
1524
1561
|
append(type, data, ...opts) {
|
|
1562
|
+
if (typeof type !== "string") return this.#appendEvents(type);
|
|
1525
1563
|
this.publicationEntry();
|
|
1526
|
-
const
|
|
1527
|
-
const surfaceMetadata = {
|
|
1528
|
-
...surfaceOpts?.sourceEventSeqs === void 0 ? {} : { sourceEventSeqs: surfaceOpts.sourceEventSeqs },
|
|
1529
|
-
...surfaceOpts?.surfaceOp === void 0 ? {} : { surfaceOp: surfaceOpts.surfaceOp }
|
|
1530
|
-
};
|
|
1531
|
-
const dataSnapshot = snapshotJsonValue(data);
|
|
1532
|
-
if (dataSnapshot === void 0) throw new Error(`session event "${type}" carries non-JSON-serializable data`);
|
|
1533
|
-
const surfaceMetadataSnapshot = snapshotJsonValue(surfaceMetadata);
|
|
1534
|
-
if (surfaceMetadataSnapshot === void 0) throw new Error(`session event "${type}" carries non-JSON-serializable surface metadata`);
|
|
1564
|
+
const snapshot = snapshotAppend(type, data, opts[0], `session event "${type}"`);
|
|
1535
1565
|
const entry = this.publicationEntry();
|
|
1536
|
-
const event =
|
|
1537
|
-
type,
|
|
1538
|
-
seq: SessionSeq(this.log.length),
|
|
1539
|
-
time: Date.now(),
|
|
1540
|
-
data: dataSnapshot,
|
|
1541
|
-
...surfaceMetadataSnapshot
|
|
1542
|
-
});
|
|
1543
|
-
validateSessionEventData(event, `session event "${type}" at seq ${event.seq}`);
|
|
1566
|
+
const event = createAppendEvent(snapshot, SessionSeq(this.log.length));
|
|
1544
1567
|
this.commit(event, entry);
|
|
1545
1568
|
return event;
|
|
1546
1569
|
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Validate and publish consecutive events together. Observers see the complete log.
|
|
1572
|
+
* @param inputs - event payloads and surface intents in commit order.
|
|
1573
|
+
* @returns immutable committed events in the same order.
|
|
1574
|
+
* @throws before changing the log if any candidate or synchronous dispatch validation fails.
|
|
1575
|
+
*/
|
|
1576
|
+
#appendEvents(inputs) {
|
|
1577
|
+
this.publicationEntry();
|
|
1578
|
+
if (inputs.length === 0) return [];
|
|
1579
|
+
const snapshots = Array.from({ length: inputs.length }, (_, index) => {
|
|
1580
|
+
const input = inputs.at(index);
|
|
1581
|
+
if (input === void 0) throw new Error(`session batch has an empty member at index ${index}`);
|
|
1582
|
+
const [type, data, surfaceOpts] = input;
|
|
1583
|
+
return snapshotAppend(type, data, surfaceOpts, `session batch member ${index} ("${type}")`);
|
|
1584
|
+
});
|
|
1585
|
+
const entry = this.publicationEntry();
|
|
1586
|
+
const startSeq = this.log.length;
|
|
1587
|
+
const events = snapshots.map((snapshot, index) => createAppendEvent(snapshot, SessionSeq(startSeq + index)));
|
|
1588
|
+
const acceptSurface = this.surfaceManager.prepareBatch(events);
|
|
1589
|
+
if (entry !== void 0) entry.appending = true;
|
|
1590
|
+
try {
|
|
1591
|
+
const publications = entry === void 0 ? [] : events.map((event) => {
|
|
1592
|
+
const args = [this, event];
|
|
1593
|
+
return {
|
|
1594
|
+
event,
|
|
1595
|
+
args,
|
|
1596
|
+
callbacks: collectSessionCallbacks(entry.emitCtx, [
|
|
1597
|
+
entry.carrier,
|
|
1598
|
+
"session/event",
|
|
1599
|
+
...args
|
|
1600
|
+
])
|
|
1601
|
+
};
|
|
1602
|
+
});
|
|
1603
|
+
for (const event of events) this.log.push(event);
|
|
1604
|
+
this.eventsSnapshot = void 0;
|
|
1605
|
+
acceptSurface();
|
|
1606
|
+
if (entry !== void 0) for (const { event, args, callbacks } of publications) {
|
|
1607
|
+
this.batchPublication = event;
|
|
1608
|
+
invokeContainedSessionObservers(entry.emitCtx, "session/event", entry.id, args, callbacks);
|
|
1609
|
+
}
|
|
1610
|
+
} finally {
|
|
1611
|
+
this.batchPublication = void 0;
|
|
1612
|
+
if (entry !== void 0) {
|
|
1613
|
+
entry.appending = false;
|
|
1614
|
+
if (entry.detachRequested) entry.detach();
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
return events;
|
|
1618
|
+
}
|
|
1547
1619
|
static {
|
|
1548
1620
|
commitPluginRecord = (session, type, data) => session.#appendRecord(type, data);
|
|
1549
1621
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fork.d.ts","sourceRoot":"","sources":["../../src/fork.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,YAAY,CAAA;AAE5E;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,EAAE,QAAQ,EAAE,cAAc,GAAG,YAAY,EAAE,CASvG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fork.js","sourceRoot":"","sources":["../../src/fork.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAGvC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,MAA+B,EAAE,QAAwB;IACrF,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAA;IAC5C,MAAM,CAAC,IAAI,CAAC;QACV,IAAI,EAAE,kBAAkB,EAAE,GAAG,EAAE,UAAU,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvD,6GAA6G;QAC7G,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAE,CAAC,IAAI;QAC5B,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE;KAC1B,CAAC,CAAA;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAA;AACnE,CAAC"}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { Scoped } from '@deepseek-ai/dsh-scope';
|
|
|
10
10
|
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
|
-
import type { CreateSessionOptions, EpochHeader, PluginRecord, PluginRecordMap, PluginRecordType, PrepareSessionOptions, RequestContext, SessionEvent, SessionEventMap, SessionEventType, SessionHeader, SessionId, SessionSeedEventState, SurfaceIntent, SurfaceEventType } from './types.ts';
|
|
13
|
+
import type { CreateSessionOptions, EpochHeader, PluginRecord, PluginRecordMap, PluginRecordType, PrepareSessionOptions, RequestContext, SessionEvent, SessionEventInput, SessionEventMap, SessionEventType, SessionHeader, SessionId, SessionSeedEventState, SurfaceIntent, SurfaceEventType } from './types.ts';
|
|
14
14
|
import type { SessionSurface, SessionMessageProjection } from './surface.ts';
|
|
15
15
|
import type { ToolHistory } from '@deepseek-ai/dsh-llm';
|
|
16
16
|
export { buildForkSeed } from './fork.ts';
|
|
@@ -54,6 +54,9 @@ declare module '@deepseek-ai/cordis' {
|
|
|
54
54
|
* Post-commit, fire-and-forget append feed. The listener snapshot resolves
|
|
55
55
|
* before the log push, but callbacks run after it; observer failures are
|
|
56
56
|
* logged and contained without making the committed append fail.
|
|
57
|
+
* Array append commits every member before publishing them in order;
|
|
58
|
+
* `session.seq` and `session.surface` already include the complete batch.
|
|
59
|
+
* `session.currentBatchPublication` identifies the member being delivered.
|
|
57
60
|
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners
|
|
58
61
|
* receive only events from sessions entered through that agent's context.
|
|
59
62
|
* @param session - the session whose log grew.
|
|
@@ -139,6 +142,7 @@ export declare function pluginRecordOf(event: SessionEvent): PluginRecord | unde
|
|
|
139
142
|
export declare class Session {
|
|
140
143
|
#private;
|
|
141
144
|
private log;
|
|
145
|
+
private batchPublication;
|
|
142
146
|
/** Single incremental owner of surface acceptance and projection state. */
|
|
143
147
|
private readonly surfaceManager;
|
|
144
148
|
/** The ordered surface over this session's event log. */
|
|
@@ -241,6 +245,18 @@ export declare class Session {
|
|
|
241
245
|
isOwnSeq(seq: SessionSeq): boolean;
|
|
242
246
|
/** The next event's sequence number — always the log length (the `seq = log.length` contiguity contract). */
|
|
243
247
|
get seq(): SessionLogOffset;
|
|
248
|
+
/** Current event while synchronously notifying a batch's observers. */
|
|
249
|
+
get currentBatchPublication(): SessionEvent | undefined;
|
|
250
|
+
/**
|
|
251
|
+
* Append consecutive events together after validating every candidate and dispatch.
|
|
252
|
+
* Observers read the complete committed log and final message surface.
|
|
253
|
+
* Listener registration changes during publication take effect on the next append.
|
|
254
|
+
* Persistence listeners retain their own durability rules.
|
|
255
|
+
* @param inputs - event payloads and surface intents in commit order. An empty array records no events.
|
|
256
|
+
* @returns immutable committed events in the same order.
|
|
257
|
+
* @throws if the Session cannot accept writes, including an empty append, or any candidate or synchronous dispatch validation fails.
|
|
258
|
+
*/
|
|
259
|
+
append(inputs: readonly SessionEventInput[]): readonly SessionEvent[];
|
|
244
260
|
/**
|
|
245
261
|
* Append one typed event to the log and synchronously notify observers via
|
|
246
262
|
* the store-owned, module-private publication hooks. The hot path never blocks
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/lib/types/index.js
CHANGED
|
@@ -397,6 +397,28 @@ export function pluginRecordOf(event) {
|
|
|
397
397
|
return undefined;
|
|
398
398
|
return { type, seq: event.seq, time: event.time, data: event.data };
|
|
399
399
|
}
|
|
400
|
+
/** Copy live append inputs before sequence assignment. */
|
|
401
|
+
function snapshotAppend(type, data, opts, subject) {
|
|
402
|
+
const surfaceOp = opts?.surfaceOp;
|
|
403
|
+
const sourceEventSeqs = opts?.sourceEventSeqs;
|
|
404
|
+
const dataSnapshot = snapshotJsonValue(data);
|
|
405
|
+
if (dataSnapshot === undefined)
|
|
406
|
+
throw new Error(`${subject} carries non-JSON-serializable data`);
|
|
407
|
+
const surface = snapshotJsonValue({
|
|
408
|
+
...sourceEventSeqs === undefined ? {} : { sourceEventSeqs },
|
|
409
|
+
...surfaceOp === undefined ? {} : { surfaceOp },
|
|
410
|
+
});
|
|
411
|
+
if (surface === undefined)
|
|
412
|
+
throw new Error(`${subject} carries non-JSON-serializable surface metadata`);
|
|
413
|
+
return { type, data: dataSnapshot, surface };
|
|
414
|
+
}
|
|
415
|
+
/** Construct and validate one live event from detached inputs. */
|
|
416
|
+
function createAppendEvent(snapshot, seq) {
|
|
417
|
+
const { type, data, surface } = snapshot;
|
|
418
|
+
const event = deepFreeze({ type, seq, time: Date.now(), data, ...surface });
|
|
419
|
+
validateSessionEventData(event, `session event "${type}" at seq ${seq}`);
|
|
420
|
+
return event;
|
|
421
|
+
}
|
|
400
422
|
/**
|
|
401
423
|
* An event-sourced session: an append-only log of {@link SessionEvent}s.
|
|
402
424
|
*
|
|
@@ -407,6 +429,7 @@ export function pluginRecordOf(event) {
|
|
|
407
429
|
*/
|
|
408
430
|
export class Session {
|
|
409
431
|
log = [];
|
|
432
|
+
batchPublication;
|
|
410
433
|
/** Single incremental owner of surface acceptance and projection state. */
|
|
411
434
|
surfaceManager;
|
|
412
435
|
/** The ordered surface over this session's event log. */
|
|
@@ -600,76 +623,72 @@ export class Session {
|
|
|
600
623
|
get seq() {
|
|
601
624
|
return SessionLogOffset(this.log.length);
|
|
602
625
|
}
|
|
603
|
-
/**
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
* the log, the append is committed: observer failures are logged and
|
|
608
|
-
* contained per listener, so they do not change the return value or prevent
|
|
609
|
-
* later listeners from observing the same accepted event.
|
|
610
|
-
* Independent Sessions append without notifying store observers. Once a store
|
|
611
|
-
* prepares or enters the instance, writes require a completed creation
|
|
612
|
-
* announcement and remain disabled after removal.
|
|
613
|
-
*
|
|
614
|
-
* @param type - The event type (key of {@link SessionEventMap}).
|
|
615
|
-
* @param data - The event payload; must be JSON-serializable.
|
|
616
|
-
* @param opts - Surface metadata: `surfaceOp` controls how the event enters
|
|
617
|
-
* the ordered surface; `sourceEventSeqs` lists the seq numbers of earlier
|
|
618
|
-
* events this one derives from. REQUIRED for
|
|
619
|
-
* {@link SurfaceEventType} events (every message-producing event must
|
|
620
|
-
* declare how it joins the surface, the sole source of derived model
|
|
621
|
-
* history) and
|
|
622
|
-
* rejected by the compiler for non-surface types like `turn/start` or
|
|
623
|
-
* `assistant/attempt`. Assistant messages embed their exact provider
|
|
624
|
-
* stream and cannot cite top-level source events.
|
|
625
|
-
* @returns the logged event — its assigned `seq`/`time` plus the SNAPSHOT of
|
|
626
|
-
* `data` that entered the log, so reading `event.data` back sees the logged
|
|
627
|
-
* value, never the caller's still-mutable input.
|
|
628
|
-
* @throws if a store-owned Session is unentered or its creation announcement
|
|
629
|
-
* has not completed, or if `data` or surface
|
|
630
|
-
* metadata is not losslessly JSON-serializable
|
|
631
|
-
* (BigInt, function, symbol, undefined, negative zero, non-finite number,
|
|
632
|
-
* circular reference, sparse array, or an exotic object such as
|
|
633
|
-
* Map/Set/Date/class instance), or when the candidate violates the
|
|
634
|
-
* request-header empty-field or tool-error consistency rules, or the
|
|
635
|
-
* canonical surface contract (marker shape and eligibility, unique
|
|
636
|
-
* earlier source-event references, positional replacement validity, and complete
|
|
637
|
-
* shadowed-node coverage). One iterative pass reads, validates, and
|
|
638
|
-
* copies each nested value once, so a stateful getter cannot supply one value
|
|
639
|
-
* to validation and another to storage. The event log is the durable source
|
|
640
|
-
* of truth, so a bad event fails at the append site rather than later during
|
|
641
|
-
* a backend flush. A synchronous internal dispatch validation failure or an
|
|
642
|
-
* append reentered while this acceptance/publication boundary is open also
|
|
643
|
-
* rejects before the log changes.
|
|
644
|
-
*/
|
|
626
|
+
/** Current event while synchronously notifying a batch's observers. */
|
|
627
|
+
get currentBatchPublication() {
|
|
628
|
+
return this.batchPublication;
|
|
629
|
+
}
|
|
645
630
|
append(type, data, ...opts) {
|
|
631
|
+
if (typeof type !== 'string')
|
|
632
|
+
return this.#appendEvents(type);
|
|
646
633
|
this.publicationEntry();
|
|
647
|
-
const
|
|
648
|
-
const surfaceMetadata = {
|
|
649
|
-
...surfaceOpts?.sourceEventSeqs === undefined ? {} : { sourceEventSeqs: surfaceOpts.sourceEventSeqs },
|
|
650
|
-
...surfaceOpts?.surfaceOp === undefined ? {} : { surfaceOp: surfaceOpts.surfaceOp },
|
|
651
|
-
};
|
|
652
|
-
const dataSnapshot = snapshotJsonValue(data);
|
|
653
|
-
if (dataSnapshot === undefined) {
|
|
654
|
-
throw new Error(`session event "${type}" carries non-JSON-serializable data`);
|
|
655
|
-
}
|
|
656
|
-
const surfaceMetadataSnapshot = snapshotJsonValue(surfaceMetadata);
|
|
657
|
-
if (surfaceMetadataSnapshot === undefined) {
|
|
658
|
-
throw new Error(`session event "${type}" carries non-JSON-serializable surface metadata`);
|
|
659
|
-
}
|
|
634
|
+
const snapshot = snapshotAppend(type, data, opts[0], `session event "${type}"`);
|
|
660
635
|
// Payload getters can detach the Session while the snapshots are read.
|
|
661
636
|
const entry = this.publicationEntry();
|
|
662
|
-
const event =
|
|
663
|
-
type,
|
|
664
|
-
seq: SessionSeq(this.log.length),
|
|
665
|
-
time: Date.now(),
|
|
666
|
-
data: dataSnapshot,
|
|
667
|
-
...surfaceMetadataSnapshot,
|
|
668
|
-
});
|
|
669
|
-
validateSessionEventData(event, `session event "${type}" at seq ${event.seq}`);
|
|
637
|
+
const event = createAppendEvent(snapshot, SessionSeq(this.log.length));
|
|
670
638
|
this.commit(event, entry);
|
|
671
639
|
return event;
|
|
672
640
|
}
|
|
641
|
+
/**
|
|
642
|
+
* Validate and publish consecutive events together. Observers see the complete log.
|
|
643
|
+
* @param inputs - event payloads and surface intents in commit order.
|
|
644
|
+
* @returns immutable committed events in the same order.
|
|
645
|
+
* @throws before changing the log if any candidate or synchronous dispatch validation fails.
|
|
646
|
+
*/
|
|
647
|
+
#appendEvents(inputs) {
|
|
648
|
+
this.publicationEntry();
|
|
649
|
+
if (inputs.length === 0)
|
|
650
|
+
return [];
|
|
651
|
+
const snapshots = Array.from({ length: inputs.length }, (_, index) => {
|
|
652
|
+
const input = inputs.at(index);
|
|
653
|
+
if (input === undefined)
|
|
654
|
+
throw new Error(`session batch has an empty member at index ${index}`);
|
|
655
|
+
const [type, data, surfaceOpts] = input;
|
|
656
|
+
return snapshotAppend(type, data, surfaceOpts, `session batch member ${index} ("${type}")`);
|
|
657
|
+
});
|
|
658
|
+
// Payload getters may append to or detach the Session.
|
|
659
|
+
const entry = this.publicationEntry();
|
|
660
|
+
const startSeq = this.log.length;
|
|
661
|
+
const events = snapshots.map((snapshot, index) => createAppendEvent(snapshot, SessionSeq(startSeq + index)));
|
|
662
|
+
const acceptSurface = this.surfaceManager.prepareBatch(events);
|
|
663
|
+
if (entry !== undefined)
|
|
664
|
+
entry.appending = true;
|
|
665
|
+
try {
|
|
666
|
+
const publications = entry === undefined ? [] : events.map((event) => {
|
|
667
|
+
const args = [this, event];
|
|
668
|
+
const callbacks = collectSessionCallbacks(entry.emitCtx, [entry.carrier, 'session/event', ...args]);
|
|
669
|
+
return { event, args, callbacks };
|
|
670
|
+
});
|
|
671
|
+
for (const event of events)
|
|
672
|
+
this.log.push(event);
|
|
673
|
+
this.eventsSnapshot = undefined;
|
|
674
|
+
acceptSurface();
|
|
675
|
+
if (entry !== undefined) {
|
|
676
|
+
for (const { event, args, callbacks } of publications) {
|
|
677
|
+
this.batchPublication = event;
|
|
678
|
+
invokeContainedSessionObservers(entry.emitCtx, 'session/event', entry.id, args, callbacks);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
finally {
|
|
683
|
+
this.batchPublication = undefined;
|
|
684
|
+
if (entry !== undefined) {
|
|
685
|
+
entry.appending = false;
|
|
686
|
+
if (entry.detachRequested)
|
|
687
|
+
entry.detach();
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return events;
|
|
691
|
+
}
|
|
673
692
|
static {
|
|
674
693
|
commitPluginRecord = (session, type, data) => session.#appendRecord(type, data);
|
|
675
694
|
}
|