@deepseek-ai/dsh-session-format-v2-to-v3 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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +207 -0
- package/README.zh.md +207 -0
- package/lib/index.js +870 -0
- package/lib/types/codec.d.ts +43 -0
- package/lib/types/index.d.ts +6 -0
- package/lib/types/migration.d.ts +4 -0
- package/lib/types/payload.d.ts +51 -0
- package/lib/types/references.d.ts +11 -0
- package/lib/types/validation.d.ts +21 -0
- package/package.json +52 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
import { RELEASED_V2_EVENT_DISPOSITIONS, assertReleasedV2Header, releasedV2SessionFormatCodec, releasedV2SessionFormatCodec as releasedV2SessionFormatCodec$1, restoreReleasedV2Artifact } from "@deepseek-ai/dsh-session-format-v1-to-v2";
|
|
2
|
+
import { SessionFormatError, SessionFormatUnsupportedMigrationError, defineSessionFormatMigration, isSessionFormatJsonObject, sessionFormatCount, sessionFormatSafeInteger, snapshotSessionFormatJson } from "@deepseek-ai/dsh-session-format";
|
|
3
|
+
import { assertReleasedPayloadSemantics, assertReleasedSurfaceMetadata } from "@deepseek-ai/dsh-session-format-v0-to-v1";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
//#region lib/types/payload.js
|
|
6
|
+
/** Audited V2 migration admission and V3 payload validation, independent of installed core Session types. */
|
|
7
|
+
/** Audited surface event names; all other admitted events are log-only. */
|
|
8
|
+
const SURFACE_TYPES = new Set([
|
|
9
|
+
"system/message",
|
|
10
|
+
"user/message",
|
|
11
|
+
"assistant/message",
|
|
12
|
+
"tool/result"
|
|
13
|
+
]);
|
|
14
|
+
const SOURCE_KINDS = new Set([
|
|
15
|
+
"user",
|
|
16
|
+
"plugin",
|
|
17
|
+
"model",
|
|
18
|
+
"tool",
|
|
19
|
+
"agent-instructions",
|
|
20
|
+
"session-reference",
|
|
21
|
+
"team-message",
|
|
22
|
+
"goal",
|
|
23
|
+
"skill-invocation",
|
|
24
|
+
"skill-catalog",
|
|
25
|
+
"coordinator",
|
|
26
|
+
"subagent-report",
|
|
27
|
+
"subagent-settled",
|
|
28
|
+
"webhook",
|
|
29
|
+
"agent-message"
|
|
30
|
+
]);
|
|
31
|
+
/**
|
|
32
|
+
* Require a JSON object at the durable input boundary.
|
|
33
|
+
* @param value - decoded value.
|
|
34
|
+
* @param label - diagnostic subject.
|
|
35
|
+
* @returns the narrowed object.
|
|
36
|
+
*/
|
|
37
|
+
function record(value, label) {
|
|
38
|
+
if (!isSessionFormatJsonObject(value)) throw new SessionFormatError(label + " must be an object");
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Reject missing and unaudited members rather than guessing whether they contain coordinates.
|
|
43
|
+
* @param value - decoded record.
|
|
44
|
+
* @param required - required member names.
|
|
45
|
+
* @param optional - additional admitted names.
|
|
46
|
+
* @param label - diagnostic subject.
|
|
47
|
+
*/
|
|
48
|
+
function keys(value, required, optional, label) {
|
|
49
|
+
const missing = required.find((key) => !Object.hasOwn(value, key));
|
|
50
|
+
const unexpected = Object.keys(value).find((key) => !required.includes(key) && !optional.includes(key));
|
|
51
|
+
if (missing !== void 0) throw new SessionFormatError(label + " lacks required field " + missing);
|
|
52
|
+
if (unexpected !== void 0) throw new SessionFormatError(label + " has unexpected field " + unexpected);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validate classified payloads before migration, or native V3 system/header payloads.
|
|
56
|
+
* @param event - decoded logical event.
|
|
57
|
+
* @param version - source or target generation.
|
|
58
|
+
*/
|
|
59
|
+
function assertEvent(event, version) {
|
|
60
|
+
if (version === 3) {
|
|
61
|
+
assertV3Event(event);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const disposition = RELEASED_V2_EVENT_DISPOSITIONS[event.type];
|
|
65
|
+
const feedback = event.type === "feedback/message-put" || event.type === "feedback/message-delete";
|
|
66
|
+
if (disposition === void 0 && !feedback) throw new SessionFormatUnsupportedMigrationError("format v2 to v3 cannot safely transform unclassified event " + event.type);
|
|
67
|
+
const surface = SURFACE_TYPES.has(event.type);
|
|
68
|
+
keys(event, [
|
|
69
|
+
"type",
|
|
70
|
+
"seq",
|
|
71
|
+
"time",
|
|
72
|
+
"data"
|
|
73
|
+
], surface ? [
|
|
74
|
+
"ignorable",
|
|
75
|
+
"sourceEventSeqs",
|
|
76
|
+
"surfaceOp"
|
|
77
|
+
] : ["ignorable"], event.type);
|
|
78
|
+
sessionFormatCount(event.seq, "event seq");
|
|
79
|
+
sessionFormatSafeInteger(event.time, "event time");
|
|
80
|
+
if (event["ignorable"] !== void 0 && event["ignorable"] !== true) throw new SessionFormatError("ignorable must be true");
|
|
81
|
+
if (surface) {
|
|
82
|
+
assertReleasedSurfaceMetadata(event, event.seq, event.type, "forbid-assistant");
|
|
83
|
+
if (event["surfaceOp"] === void 0) throw new SessionFormatError(event.type + " requires surfaceOp");
|
|
84
|
+
}
|
|
85
|
+
const data = record(event.data, event.type + " data");
|
|
86
|
+
if (feedback) {
|
|
87
|
+
assertFeedback(event.type, data);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const admitted = disposition;
|
|
91
|
+
keys(data, admitted.required, admitted.optional, event.type + " data");
|
|
92
|
+
assertOwnedContent(event, data);
|
|
93
|
+
if (event.type !== "assistant/attempt") assertReleasedPayloadSemantics(event, version);
|
|
94
|
+
if (event.type === "assistant/message" || event.type === "assistant/attempt") {
|
|
95
|
+
for (const coordinate of ["turn", "step"]) if (sessionFormatCount(data[coordinate], coordinate) === 0) throw new SessionFormatError(coordinate + " must be positive");
|
|
96
|
+
}
|
|
97
|
+
if (event.type === "session/end-seed" && data["inherited"] !== void 0 && data["inherited"] !== true) throw new SessionFormatError("session/end-seed inherited must be true");
|
|
98
|
+
if (event.type === "user/message") assertSource(data);
|
|
99
|
+
if (event.type === "assistant/message" || event.type === "tool/result") assertSource(record(data["message"], "message"));
|
|
100
|
+
if (event.type === "tool/result" && isSessionFormatJsonObject(data["error"]) && data["error"]["code"] === "TOOL_NOT_STARTED") {
|
|
101
|
+
const message = record(data["message"], "tool result message");
|
|
102
|
+
const source = record(message["source"], "tool result source");
|
|
103
|
+
if (!isRepairIdentity(message["id"], source["callId"])) throw new SessionFormatError("TOOL_NOT_STARTED repair requires its canonical historical message id");
|
|
104
|
+
}
|
|
105
|
+
if (event.type === "agent/inbox/spliced" || event.type === "session/title-llm-request") {
|
|
106
|
+
const messages = data[event.type === "agent/inbox/spliced" ? "inserted" : "messages"];
|
|
107
|
+
for (const message of messages) assertSource(message);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Recognize stable generated repair IDs without interpreting their historical suffix as a current coordinate.
|
|
112
|
+
* @param id - durable message identity.
|
|
113
|
+
* @param callId - advertised tool identity.
|
|
114
|
+
* @returns whether the identity has the canonical historical repair form.
|
|
115
|
+
*/
|
|
116
|
+
function isRepairIdentity(id, callId) {
|
|
117
|
+
if (typeof callId !== "string") return false;
|
|
118
|
+
const prefix = "interrupted-tool-result-" + callId + "-";
|
|
119
|
+
if (typeof id !== "string" || !id.startsWith(prefix)) return false;
|
|
120
|
+
const suffix = id.slice(prefix.length);
|
|
121
|
+
return /^(0|[1-9]\d*)$/.test(suffix) && Number.isSafeInteger(Number(suffix));
|
|
122
|
+
}
|
|
123
|
+
function assertSource(message) {
|
|
124
|
+
const source = record(message["source"], "message source");
|
|
125
|
+
if (typeof source["kind"] !== "string" || !SOURCE_KINDS.has(source["kind"])) throw new SessionFormatUnsupportedMigrationError("cannot safely transform unclassified message source");
|
|
126
|
+
if (source["kind"] === "agent-message") {
|
|
127
|
+
keys(source, [
|
|
128
|
+
"kind",
|
|
129
|
+
"form",
|
|
130
|
+
"senderSessionId"
|
|
131
|
+
], [], "agent-message source");
|
|
132
|
+
if (source["form"] !== "relay" || typeof source["senderSessionId"] !== "string" || source["senderSessionId"].length === 0) throw new SessionFormatError("agent-message source requires relay form and senderSessionId");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const CONTENT_KINDS = new Set([
|
|
136
|
+
"text",
|
|
137
|
+
"reasoning",
|
|
138
|
+
"image",
|
|
139
|
+
"file",
|
|
140
|
+
"tool-call",
|
|
141
|
+
"tool-result"
|
|
142
|
+
]);
|
|
143
|
+
function contentArray(value, label) {
|
|
144
|
+
if (!Array.isArray(value)) throw new SessionFormatError(label + ": content must be an array");
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
function assertOwnedContent(event, data) {
|
|
148
|
+
const label = "format v2 " + event.type + " at seq " + String(event.seq) + " data";
|
|
149
|
+
switch (event.type) {
|
|
150
|
+
case "user/message":
|
|
151
|
+
case "tool/code-dispatch":
|
|
152
|
+
assertContentKinds(data["content"], label + ".content");
|
|
153
|
+
break;
|
|
154
|
+
case "assistant/message":
|
|
155
|
+
case "tool/result":
|
|
156
|
+
case "team/message/queued":
|
|
157
|
+
assertContentKinds(record(data["message"], label + ".message")["content"], label + ".message.content");
|
|
158
|
+
break;
|
|
159
|
+
case "agent/inbox/spliced":
|
|
160
|
+
case "session/title-llm-request": {
|
|
161
|
+
const field = event.type === "agent/inbox/spliced" ? "inserted" : "messages";
|
|
162
|
+
for (const [index, value] of contentArray(data[field], label + "." + field).entries()) {
|
|
163
|
+
const path = label + "." + field + "[" + String(index) + "]";
|
|
164
|
+
assertContentKinds(record(value, path)["content"], path + ".content");
|
|
165
|
+
}
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
case "compaction/summary":
|
|
169
|
+
assertContentKinds(data["summary"], label + ".summary");
|
|
170
|
+
if (data["rawOutput"] !== void 0) assertContentKinds(data["rawOutput"], label + ".rawOutput");
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
if (event.type === "assistant/message" || event.type === "assistant/attempt") for (const [index, value] of contentArray(data["stream"], label + ".stream").entries()) {
|
|
174
|
+
const path = label + ".stream[" + String(index) + "]";
|
|
175
|
+
const entry = record(value, path);
|
|
176
|
+
if (entry["type"] !== "chunk") continue;
|
|
177
|
+
const chunk = record(entry["chunk"], path + ".chunk");
|
|
178
|
+
if (chunk["type"] === "block-end") assertContentBlock(chunk["block"], path + ".chunk.block");
|
|
179
|
+
if (chunk["type"] === "block-start") assertContentKind(chunk["blockType"], path + ".chunk.blockType");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function assertContentKind(kind, label) {
|
|
183
|
+
if (typeof kind !== "string" || !CONTENT_KINDS.has(kind)) throw new SessionFormatUnsupportedMigrationError(label + ": cannot safely transform unclassified message content kind " + JSON.stringify(kind));
|
|
184
|
+
}
|
|
185
|
+
function assertContentKinds(content, label) {
|
|
186
|
+
for (const [index, value] of contentArray(content, label).entries()) assertContentBlock(value, label + "[" + String(index) + "]");
|
|
187
|
+
}
|
|
188
|
+
function assertContentBlock(value, label) {
|
|
189
|
+
const block = record(value, label);
|
|
190
|
+
assertContentKind(block["type"], label);
|
|
191
|
+
if (block["type"] === "tool-result") {
|
|
192
|
+
if (!Array.isArray(block["content"])) throw new SessionFormatError(label + ".content: invalid message content kind \"tool-result\": content must be an array");
|
|
193
|
+
assertContentKinds(block["content"], label + ".content");
|
|
194
|
+
}
|
|
195
|
+
if (block["type"] === "file") {
|
|
196
|
+
keys(block, ["type", "attachment"], [], label + " kind \"file\"");
|
|
197
|
+
const attachment = record(block["attachment"], label + " kind \"file\" attachment");
|
|
198
|
+
keys(attachment, [
|
|
199
|
+
"attachmentId",
|
|
200
|
+
"name",
|
|
201
|
+
"bytes"
|
|
202
|
+
], [], label + " kind \"file\" attachment");
|
|
203
|
+
if (typeof attachment["attachmentId"] !== "string" || attachment["attachmentId"].length === 0 || typeof attachment["name"] !== "string") throw new SessionFormatError(label + " kind \"file\": file attachment requires attachmentId and name");
|
|
204
|
+
sessionFormatCount(attachment["bytes"], label + " kind \"file\" attachment bytes");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const probe = {
|
|
208
|
+
type: "user/message",
|
|
209
|
+
seq: 0,
|
|
210
|
+
time: 0,
|
|
211
|
+
data: {
|
|
212
|
+
id: "content-admission",
|
|
213
|
+
role: "user",
|
|
214
|
+
source: { kind: "user" },
|
|
215
|
+
content: [block["type"] === "tool-result" ? {
|
|
216
|
+
...block,
|
|
217
|
+
content: []
|
|
218
|
+
} : block]
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
try {
|
|
222
|
+
assertReleasedPayloadSemantics(probe, 2);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
throw new SessionFormatError(label + ": invalid message content kind " + JSON.stringify(block["type"]) + ": " + String(error));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Reject V3 structural payload violations even beyond a recoverable physical-row failure.
|
|
229
|
+
* @param value - raw physical row; ordinary rows retain the frozen decoder's recovery policy.
|
|
230
|
+
*/
|
|
231
|
+
function assertV3StructuralRow(value) {
|
|
232
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return;
|
|
233
|
+
const row = value;
|
|
234
|
+
if (row["type"] === "request/header") {
|
|
235
|
+
const data = record(row["data"], "request/header data");
|
|
236
|
+
if (Object.hasOwn(record(data["header"], "request header"), "system")) throw new SessionFormatUnsupportedMigrationError("format v3 request/header rejects retired header.system");
|
|
237
|
+
} else if (row["type"] === "system/message") {
|
|
238
|
+
const data = record(row["data"], "system/message data");
|
|
239
|
+
assertSystem({
|
|
240
|
+
type: "system/message",
|
|
241
|
+
seq: 0,
|
|
242
|
+
time: 0,
|
|
243
|
+
data
|
|
244
|
+
}, data);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function assertSystem(event, data) {
|
|
248
|
+
keys(data, [
|
|
249
|
+
"turn",
|
|
250
|
+
"step",
|
|
251
|
+
"message"
|
|
252
|
+
], [], "system/message data");
|
|
253
|
+
for (const coordinate of ["turn", "step"]) if (sessionFormatCount(data[coordinate], coordinate) === 0) throw new SessionFormatError(coordinate + " must be positive");
|
|
254
|
+
const message = record(data["message"], "system message");
|
|
255
|
+
keys(message, [
|
|
256
|
+
"id",
|
|
257
|
+
"role",
|
|
258
|
+
"source",
|
|
259
|
+
"content"
|
|
260
|
+
], [], "system message");
|
|
261
|
+
if (typeof message["id"] !== "string" || message["id"].length === 0 || message["role"] !== "system") throw new SessionFormatError("system message requires an id and system role");
|
|
262
|
+
const source = record(message["source"], "system source");
|
|
263
|
+
if (source["kind"] !== "plugin" || typeof source["plugin"] !== "string" || source["plugin"].length === 0) throw new SessionFormatError("system message requires plugin source");
|
|
264
|
+
assertReleasedPayloadSemantics({
|
|
265
|
+
...event,
|
|
266
|
+
type: "user/message",
|
|
267
|
+
data: {
|
|
268
|
+
...message,
|
|
269
|
+
role: "user"
|
|
270
|
+
}
|
|
271
|
+
}, 3);
|
|
272
|
+
}
|
|
273
|
+
function assertFeedback(type, data) {
|
|
274
|
+
keys(data, type === "feedback/message-put" ? ["sessionId", "item"] : ["sessionId", "messageId"], [], type);
|
|
275
|
+
if (typeof data["sessionId"] !== "string") throw new SessionFormatError("feedback sessionId must be a string");
|
|
276
|
+
if (type === "feedback/message-delete") {
|
|
277
|
+
if (typeof data["messageId"] !== "string") throw new SessionFormatError("feedback messageId must be a string");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const item = record(data["item"], "feedback item");
|
|
281
|
+
keys(item, [
|
|
282
|
+
"messageId",
|
|
283
|
+
"rating",
|
|
284
|
+
"version",
|
|
285
|
+
"createdAt",
|
|
286
|
+
"updatedAt"
|
|
287
|
+
], ["note"], "feedback item");
|
|
288
|
+
for (const key of ["messageId", "version"]) if (typeof item[key] !== "string") throw new SessionFormatError("feedback " + key + " must be a string");
|
|
289
|
+
if (item["rating"] !== "positive" && item["rating"] !== "negative") throw new SessionFormatError("invalid feedback rating");
|
|
290
|
+
if (item["note"] !== void 0 && typeof item["note"] !== "string") throw new SessionFormatError("feedback note must be a string");
|
|
291
|
+
sessionFormatCount(item["createdAt"], "feedback createdAt");
|
|
292
|
+
sessionFormatCount(item["updatedAt"], "feedback updatedAt");
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Validate one canonical V3 event without interpreting plugin-owned payloads or log relationships.
|
|
296
|
+
* Unclassified metadata is deferred to vocabulary-aware restoration; unknown required types must not become recoverable corruption.
|
|
297
|
+
* @param event - decoded logical event.
|
|
298
|
+
* @param knownEventTypes - additional installed event types whose envelopes are interpreted.
|
|
299
|
+
*/
|
|
300
|
+
function assertV3Event(event, knownEventTypes) {
|
|
301
|
+
const value = record(event, "format v3 event");
|
|
302
|
+
const subject = `format v3 ${event.type} at seq ${event.seq}`;
|
|
303
|
+
const opaque = !(!(event.type === "tool/code-dispatch-start" || event.type === "tool/code-dispatch") && (SURFACE_TYPES.has(event.type) || RELEASED_V2_EVENT_DISPOSITIONS[event.type] !== void 0 || event.type === "tool/ptc-dispatch-start" || event.type === "tool/ptc-dispatch" || event.type === "feedback/message-put" || event.type === "feedback/message-delete" || knownEventTypes?.has(event.type) === true));
|
|
304
|
+
keys(value, [
|
|
305
|
+
"type",
|
|
306
|
+
"seq",
|
|
307
|
+
"time",
|
|
308
|
+
"data"
|
|
309
|
+
], SURFACE_TYPES.has(event.type) || opaque ? [
|
|
310
|
+
"ignorable",
|
|
311
|
+
"surfaceOp",
|
|
312
|
+
"sourceEventSeqs"
|
|
313
|
+
] : ["ignorable"], subject);
|
|
314
|
+
if (typeof event.type !== "string") throw new SessionFormatError(`${subject} type must be a string`);
|
|
315
|
+
sessionFormatCount(event.seq, `${subject} seq`);
|
|
316
|
+
sessionFormatSafeInteger(event.time, `${subject} time`);
|
|
317
|
+
if (Object.hasOwn(value, "ignorable") && value["ignorable"] !== true) throw new SessionFormatError(`${subject} ignorable must be true when present`);
|
|
318
|
+
if (SURFACE_TYPES.has(event.type)) {
|
|
319
|
+
const operation = value["surfaceOp"];
|
|
320
|
+
if (operation === void 0) throw new SessionFormatError(`${subject} requires a surfaceOp marker`);
|
|
321
|
+
if (operation !== "append") {
|
|
322
|
+
const replace = record(operation, `${subject} surfaceOp`);
|
|
323
|
+
if (Object.keys(replace).length !== 3 || replace["op"] !== "replace" || !Object.hasOwn(replace, "startSeq") || !Object.hasOwn(replace, "endSeq")) throw new SessionFormatError(`${subject} requires exact replace fields op/startSeq/endSeq`);
|
|
324
|
+
for (const key of ["startSeq", "endSeq"]) if (sessionFormatCount(replace[key], `${subject} surfaceOp ${key}`) >= event.seq) throw new SessionFormatError(`${subject} replacement endpoints must reference earlier events`);
|
|
325
|
+
}
|
|
326
|
+
const sources = value["sourceEventSeqs"];
|
|
327
|
+
if (event.type === "assistant/message" && sources !== void 0) throw new SessionFormatError(`${subject} embeds its stream and cannot carry sourceEventSeqs`);
|
|
328
|
+
if (sources !== void 0) {
|
|
329
|
+
if (!Array.isArray(sources) || sources.length === 0) throw new SessionFormatError(`${subject} sourceEventSeqs must be a non-empty array`);
|
|
330
|
+
const seen = /* @__PURE__ */ new Set();
|
|
331
|
+
for (const source of sources) {
|
|
332
|
+
const seq = sessionFormatCount(source, `${subject} sourceEventSeqs member`);
|
|
333
|
+
if (seq >= event.seq || seen.has(seq)) throw new SessionFormatError(`${subject} sourceEventSeqs must be unique earlier seqs`);
|
|
334
|
+
seen.add(seq);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
assertV3StructuralRow(event);
|
|
339
|
+
assertCanonicalPayload(event);
|
|
340
|
+
}
|
|
341
|
+
function assertCanonicalPayload(event) {
|
|
342
|
+
const subject = `format v3 ${event.type} at seq ${event.seq}`;
|
|
343
|
+
if (event.type === "request/header") {
|
|
344
|
+
const header = record(record(event.data, `${subject} data`)["header"], `${subject} header`);
|
|
345
|
+
if (Array.isArray(header["tools"]) && header["tools"].length === 0 || isSessionFormatJsonObject(header["adapterDefaults"]) && Object.keys(header["adapterDefaults"]).length === 0) throw new SessionFormatError(`${subject} empty optional header fields must be omitted`);
|
|
346
|
+
}
|
|
347
|
+
if (event.type !== "tool/result") return;
|
|
348
|
+
const data = record(event.data, `${subject} data`);
|
|
349
|
+
if (data["error"] === void 0) return;
|
|
350
|
+
const content = record(data["message"], `${subject} message`)["content"];
|
|
351
|
+
if (!Array.isArray(content) || content.length !== 1 || !isSessionFormatJsonObject(content[0]) || content[0]["type"] !== "tool-result" || content[0]["isError"] !== true) throw new SessionFormatError(`${subject} carries error metadata for a non-error tool result`);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Canonicalize structurally transformed events without changing their target coordinates.
|
|
355
|
+
* @param event - transformed event using released replacement names and target coordinates.
|
|
356
|
+
* @returns a V3 event sharing all unchanged payloads and reference values.
|
|
357
|
+
*/
|
|
358
|
+
function canonicalizeTransformedEvent(event) {
|
|
359
|
+
let target = event;
|
|
360
|
+
const operation = event["surfaceOp"];
|
|
361
|
+
if (operation !== void 0 && operation !== "append") {
|
|
362
|
+
const replace = record(operation, `format v2 ${event.type} at seq ${event.seq} surfaceOp`);
|
|
363
|
+
if (Object.keys(replace).length !== 3 || replace["op"] !== "replace" || !Object.hasOwn(replace, "start") || !Object.hasOwn(replace, "end")) throw new SessionFormatError(`format v2 ${event.type} at seq ${event.seq} requires exact replace fields op/start/end`);
|
|
364
|
+
target = {
|
|
365
|
+
...event,
|
|
366
|
+
surfaceOp: {
|
|
367
|
+
op: "replace",
|
|
368
|
+
startSeq: sessionFormatCount(replace["start"], `format v2 ${event.type} at seq ${event.seq} replace start`),
|
|
369
|
+
endSeq: sessionFormatCount(replace["end"], `format v2 ${event.type} at seq ${event.seq} replace end`)
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
if (event.type === "request/header") {
|
|
374
|
+
const data = record(event.data, `format v2 request/header at seq ${event.seq} data`);
|
|
375
|
+
const header = record(data["header"], `format v2 request/header at seq ${event.seq} header`);
|
|
376
|
+
const empty = Object.keys(header).filter((key) => key === "tools" && Array.isArray(header[key]) && header[key].length === 0 || key === "adapterDefaults" && isSessionFormatJsonObject(header[key]) && Object.keys(header[key]).length === 0);
|
|
377
|
+
if (empty.length > 0) {
|
|
378
|
+
const canonical = Object.fromEntries(Object.entries(header).filter(([key]) => !empty.includes(key)));
|
|
379
|
+
target = {
|
|
380
|
+
...target,
|
|
381
|
+
data: {
|
|
382
|
+
...data,
|
|
383
|
+
header: canonical
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
assertV3Event(target);
|
|
389
|
+
return target;
|
|
390
|
+
}
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region lib/types/validation.js
|
|
393
|
+
/** Native V3 system-head validation with a private view for frozen non-system relationships. */
|
|
394
|
+
/**
|
|
395
|
+
* Validate v3 logical metadata with the released-v2 fields.
|
|
396
|
+
* @param header - decoded v3 Session header.
|
|
397
|
+
*/
|
|
398
|
+
function assertReleasedV3Header(header) {
|
|
399
|
+
if (header.version !== 3) throw new SessionFormatError("expected format v3 header");
|
|
400
|
+
assertReleasedV2Header({
|
|
401
|
+
...header,
|
|
402
|
+
version: 2
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Validate system ownership, protected-head operations, ordinary relationships, and inherited cut.
|
|
407
|
+
* The private relationship view never escapes; the returned artifact and its messages are unchanged.
|
|
408
|
+
* @param artifact - detached v3 artifact.
|
|
409
|
+
* @param knownEventTypes - event types understood by the installed Session package.
|
|
410
|
+
* @returns the same validated artifact.
|
|
411
|
+
*/
|
|
412
|
+
function restoreReleasedV3Artifact(artifact, knownEventTypes) {
|
|
413
|
+
assertReleasedV3Header(artifact.header);
|
|
414
|
+
let step;
|
|
415
|
+
let head;
|
|
416
|
+
let hasSurface = false;
|
|
417
|
+
const events = artifact.events.map((event) => {
|
|
418
|
+
assertV3EventAdmission(event);
|
|
419
|
+
assertV3Event(event, knownEventTypes);
|
|
420
|
+
const system = event.type === "system/message";
|
|
421
|
+
if (event.type === "step/start") {
|
|
422
|
+
const data = record(event.data, event.type);
|
|
423
|
+
step = {
|
|
424
|
+
turn: data["turn"],
|
|
425
|
+
step: data["step"]
|
|
426
|
+
};
|
|
427
|
+
} else if (event.type === "step/end" || event.type === "turn/end") step = void 0;
|
|
428
|
+
if (system) {
|
|
429
|
+
const data = record(event.data, "system/message");
|
|
430
|
+
if (step === void 0 || step.turn !== data["turn"] || step.step !== data["step"]) throw new SessionFormatError("system/message does not match an open step");
|
|
431
|
+
const operation = event["surfaceOp"];
|
|
432
|
+
if (hasSurface && head === void 0) throw new SessionFormatError("system/message requires a protected first surface head");
|
|
433
|
+
if (operation === "append") {
|
|
434
|
+
if (!hasSurface) head = event.seq;
|
|
435
|
+
} else {
|
|
436
|
+
const replace = record(operation, "system replacement");
|
|
437
|
+
if (replace["startSeq"] === head || replace["endSeq"] === head) {
|
|
438
|
+
if (replace["startSeq"] !== head || replace["endSeq"] !== head) throw new SessionFormatError("system/message must replace exactly the current system head");
|
|
439
|
+
head = event.seq;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
} else if (SURFACE_TYPES.has(event.type) && event["surfaceOp"] !== "append") {
|
|
443
|
+
const replace = record(event["surfaceOp"], "surface replacement");
|
|
444
|
+
if (replace["startSeq"] === head || replace["endSeq"] === head) throw new SessionFormatError("surface replacement cannot shadow the protected system head");
|
|
445
|
+
}
|
|
446
|
+
if (event.type === "compaction/prune" || event.type === "compaction/summary") {
|
|
447
|
+
const seqs = record(event.data, event.type)["shadowedSeqs"];
|
|
448
|
+
if (Array.isArray(seqs) && seqs.some((seq) => seq === head)) throw new SessionFormatError("compaction cannot shadow the protected system head");
|
|
449
|
+
}
|
|
450
|
+
if (SURFACE_TYPES.has(event.type)) hasSurface = true;
|
|
451
|
+
const projected = relationshipEvent(event);
|
|
452
|
+
if (!SURFACE_TYPES.has(event.type) || event["surfaceOp"] === "append") return projected;
|
|
453
|
+
const replacement = event["surfaceOp"];
|
|
454
|
+
return {
|
|
455
|
+
...projected,
|
|
456
|
+
surfaceOp: {
|
|
457
|
+
op: "replace",
|
|
458
|
+
start: replacement.startSeq,
|
|
459
|
+
end: replacement.endSeq
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
});
|
|
463
|
+
restoreReleasedV2Artifact({
|
|
464
|
+
...artifact,
|
|
465
|
+
header: {
|
|
466
|
+
...artifact.header,
|
|
467
|
+
version: 2
|
|
468
|
+
},
|
|
469
|
+
events
|
|
470
|
+
}, knownEventTypes, 3);
|
|
471
|
+
return artifact;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Refuse required predecessor PTC tags without interpreting native extension payloads.
|
|
475
|
+
* @param event - event envelope whose type and ignorable admission markers are available.
|
|
476
|
+
*/
|
|
477
|
+
function assertV3EventAdmission(event) {
|
|
478
|
+
if ((event.type === "tool/code-dispatch-start" || event.type === "tool/code-dispatch") && event["ignorable"] !== true) throw new SessionFormatUnsupportedMigrationError("format v3 contains unknown event type " + JSON.stringify(event.type) + " at seq " + String(event.seq));
|
|
479
|
+
}
|
|
480
|
+
function relationshipEvent(event) {
|
|
481
|
+
switch (event.type) {
|
|
482
|
+
case "tool/ptc-dispatch-start": return {
|
|
483
|
+
...event,
|
|
484
|
+
type: "tool/code-dispatch-start"
|
|
485
|
+
};
|
|
486
|
+
case "tool/ptc-dispatch": return {
|
|
487
|
+
...event,
|
|
488
|
+
type: "tool/code-dispatch"
|
|
489
|
+
};
|
|
490
|
+
case "tool/code-dispatch-start":
|
|
491
|
+
case "tool/code-dispatch":
|
|
492
|
+
assertV3EventAdmission(event);
|
|
493
|
+
return {
|
|
494
|
+
...event,
|
|
495
|
+
type: "v3/opaque-released-event"
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
if (event.type === "system/message") {
|
|
499
|
+
const message = record(record(event.data, "system data")["message"], "system message");
|
|
500
|
+
return {
|
|
501
|
+
...event,
|
|
502
|
+
type: "user/message",
|
|
503
|
+
data: {
|
|
504
|
+
...message,
|
|
505
|
+
role: "user"
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
if (event.type !== "tool/result") return event;
|
|
510
|
+
const data = record(event.data, "tool result");
|
|
511
|
+
if (data["error"] === void 0) return event;
|
|
512
|
+
if (record(data["error"], "tool error")["code"] !== "TOOL_NOT_STARTED") return event;
|
|
513
|
+
const message = record(data["message"], "tool message");
|
|
514
|
+
const callId = record(message["source"], "tool source")["callId"];
|
|
515
|
+
const id = message["id"];
|
|
516
|
+
if (!isRepairIdentity(id, callId)) return event;
|
|
517
|
+
const prefix = "interrupted-tool-result-" + callId + "-";
|
|
518
|
+
return {
|
|
519
|
+
...event,
|
|
520
|
+
data: {
|
|
521
|
+
...data,
|
|
522
|
+
message: {
|
|
523
|
+
...message,
|
|
524
|
+
id: `${prefix}${event.seq}`
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
//#endregion
|
|
530
|
+
//#region lib/types/codec.js
|
|
531
|
+
/** V3 framing with hard structural admission and recoverable canonical event validation. */
|
|
532
|
+
/** V3 codec validates structural rows before recovery and logical envelopes after provenance decoding. */
|
|
533
|
+
const releasedV3SessionFormatCodec = Object.freeze({
|
|
534
|
+
version: 3,
|
|
535
|
+
decodeHeader(value) {
|
|
536
|
+
return {
|
|
537
|
+
...releasedV2SessionFormatCodec$1.decodeHeader(v2PhysicalHeader(value)),
|
|
538
|
+
version: 3
|
|
539
|
+
};
|
|
540
|
+
},
|
|
541
|
+
createDecoder(value, recovery) {
|
|
542
|
+
const decoder = releasedV2SessionFormatCodec$1.createDecoder(v2PhysicalHeader(value), recovery);
|
|
543
|
+
let issue;
|
|
544
|
+
let acceptedInheritedCut;
|
|
545
|
+
return {
|
|
546
|
+
header: {
|
|
547
|
+
...decoder.header,
|
|
548
|
+
version: 3
|
|
549
|
+
},
|
|
550
|
+
decodeRow(row, context) {
|
|
551
|
+
assertV3RowAdmission(row);
|
|
552
|
+
decoder.decodeRow(row, {
|
|
553
|
+
emitRun: context.emitRun.bind(context),
|
|
554
|
+
emitEvent(event) {
|
|
555
|
+
assertV3EventAdmission(event);
|
|
556
|
+
if (issue === void 0) try {
|
|
557
|
+
assertV3Event(event);
|
|
558
|
+
} catch (error) {
|
|
559
|
+
const invalid = error;
|
|
560
|
+
if (recovery === "strict") throw invalid;
|
|
561
|
+
issue = invalid;
|
|
562
|
+
}
|
|
563
|
+
if (issue !== void 0) {
|
|
564
|
+
if (event.type === "turn/end") throw issue;
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
if (event.type === "session/end-seed" && isSessionFormatJsonObject(event.data) && event.data["inherited"] === true) acceptedInheritedCut = event.seq;
|
|
568
|
+
context.emitEvent(event);
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
},
|
|
572
|
+
finish(context) {
|
|
573
|
+
if (issue === void 0) return decoder.finish(context);
|
|
574
|
+
if (decoder.header.isSeeded && acceptedInheritedCut === void 0) throw new SessionFormatError("format v3 seeded Session lacks an accepted inherited end-seed marker");
|
|
575
|
+
if (!decoder.header.isSeeded && acceptedInheritedCut !== void 0) throw new SessionFormatError("format v3 unseeded Session contains an inherited end-seed marker");
|
|
576
|
+
return acceptedInheritedCut ?? 0;
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
},
|
|
580
|
+
encodeHeader(header, inheritedEventCount) {
|
|
581
|
+
assertReleasedV3Header(header);
|
|
582
|
+
return {
|
|
583
|
+
...releasedV2SessionFormatCodec$1.encodeHeader({
|
|
584
|
+
...header,
|
|
585
|
+
version: 2
|
|
586
|
+
}, inheritedEventCount),
|
|
587
|
+
version: 3
|
|
588
|
+
};
|
|
589
|
+
},
|
|
590
|
+
encodeEvent(event) {
|
|
591
|
+
assertV3EventAdmission(event);
|
|
592
|
+
assertV3Event(event);
|
|
593
|
+
return releasedV2SessionFormatCodec$1.encodeEvent(event);
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
/**
|
|
597
|
+
* Validate owned V3 admission rules before a scanner or codec can discard a recoverable tail.
|
|
598
|
+
* This checks only identified structural payloads; physical provenance still belongs to decoding.
|
|
599
|
+
* @param row - parsed physical row, before envelope or compressed-range decoding.
|
|
600
|
+
*/
|
|
601
|
+
function assertV3RowAdmission(row) {
|
|
602
|
+
assertV3StructuralRow(row);
|
|
603
|
+
if (typeof row === "object" && row !== null && !Array.isArray(row)) assertV3EventAdmission(row);
|
|
604
|
+
}
|
|
605
|
+
function v2PhysicalHeader(value) {
|
|
606
|
+
const header = snapshotSessionFormatJson(value, "format v3 physical header");
|
|
607
|
+
if (!isSessionFormatJsonObject(header) || header["version"] !== 3) throw new SessionFormatError("expected format v3 physical Session header");
|
|
608
|
+
return {
|
|
609
|
+
...header,
|
|
610
|
+
version: 2
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
//#endregion
|
|
614
|
+
//#region lib/types/references.js
|
|
615
|
+
/** Explicit local-coordinate remapping; captured generations and owner-local counters remain opaque. */
|
|
616
|
+
/**
|
|
617
|
+
* Remap only audited same-artifact references, preserving IDs and embedded model input.
|
|
618
|
+
* @param event - validated source event.
|
|
619
|
+
* @param seq - output event position.
|
|
620
|
+
* @param mapping - earlier source positions mapped to output positions.
|
|
621
|
+
* @returns the event in target coordinates.
|
|
622
|
+
*/
|
|
623
|
+
function remapEvent(event, seq, mapping) {
|
|
624
|
+
const one = (value) => {
|
|
625
|
+
const source = sessionFormatCount(value, "source event reference");
|
|
626
|
+
const target = mapping[source];
|
|
627
|
+
if (source >= event.seq || target === void 0) throw new SessionFormatError("reference must name an earlier source event");
|
|
628
|
+
return target;
|
|
629
|
+
};
|
|
630
|
+
const list = (value) => {
|
|
631
|
+
if (!Array.isArray(value)) throw new SessionFormatError("sequence references must be an array");
|
|
632
|
+
return value.map(one);
|
|
633
|
+
};
|
|
634
|
+
const range = (value) => {
|
|
635
|
+
const source = record(value, "sequence range");
|
|
636
|
+
return {
|
|
637
|
+
...source,
|
|
638
|
+
start: one(source["start"]),
|
|
639
|
+
end: one(source["end"])
|
|
640
|
+
};
|
|
641
|
+
};
|
|
642
|
+
let data = record(event.data, event.type);
|
|
643
|
+
switch (event.type) {
|
|
644
|
+
case "command/done":
|
|
645
|
+
if (data["sourceEventSeq"] !== void 0) data = {
|
|
646
|
+
...data,
|
|
647
|
+
sourceEventSeq: one(data["sourceEventSeq"])
|
|
648
|
+
};
|
|
649
|
+
break;
|
|
650
|
+
case "compaction/summary":
|
|
651
|
+
case "compaction/prune":
|
|
652
|
+
data = {
|
|
653
|
+
...data,
|
|
654
|
+
shadowedRange: range(data["shadowedRange"]),
|
|
655
|
+
shadowedSeqs: list(data["shadowedSeqs"])
|
|
656
|
+
};
|
|
657
|
+
break;
|
|
658
|
+
case "session/title":
|
|
659
|
+
case "session/title-llm-request":
|
|
660
|
+
data = {
|
|
661
|
+
...data,
|
|
662
|
+
messageSeqs: list(data["messageSeqs"])
|
|
663
|
+
};
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
return {
|
|
667
|
+
...event,
|
|
668
|
+
seq,
|
|
669
|
+
data,
|
|
670
|
+
...event["sourceEventSeqs"] === void 0 ? {} : { sourceEventSeqs: list(event["sourceEventSeqs"]) },
|
|
671
|
+
...event["surfaceOp"] === void 0 || event["surfaceOp"] === "append" ? {} : { surfaceOp: range(event["surfaceOp"]) }
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
//#endregion
|
|
675
|
+
//#region lib/types/migration.js
|
|
676
|
+
/** Streaming system-prompt promotion followed by canonical V3 envelope conversion. */
|
|
677
|
+
/** Promote system prompts, remap audited references, and canonicalize envelopes and PTC vocabulary. */
|
|
678
|
+
const sessionFormatV2ToV3 = defineSessionFormatMigration({
|
|
679
|
+
name: "@deepseek-ai/dsh-session-format-v2-to-v3",
|
|
680
|
+
fromVersion: 2,
|
|
681
|
+
toVersion: 3,
|
|
682
|
+
migrateHeader(header) {
|
|
683
|
+
assertReleasedV2Header(header);
|
|
684
|
+
return {
|
|
685
|
+
...header,
|
|
686
|
+
version: 3,
|
|
687
|
+
...header.agentPreset === "code" ? { agentPreset: "ptc" } : {}
|
|
688
|
+
};
|
|
689
|
+
},
|
|
690
|
+
createStage(input) {
|
|
691
|
+
return new ReleasedV2ToV3Stage(input);
|
|
692
|
+
},
|
|
693
|
+
validateTargetHeader: assertReleasedV3Header
|
|
694
|
+
});
|
|
695
|
+
var ReleasedV2ToV3Stage = class {
|
|
696
|
+
input;
|
|
697
|
+
headerInheritedEventCount;
|
|
698
|
+
mapping = [];
|
|
699
|
+
originalIds = /* @__PURE__ */ new Set();
|
|
700
|
+
generatedIds = /* @__PURE__ */ new Set();
|
|
701
|
+
targetSeq = 0;
|
|
702
|
+
sourceCut;
|
|
703
|
+
targetCut;
|
|
704
|
+
lastForeignDeliverySeq;
|
|
705
|
+
step;
|
|
706
|
+
head;
|
|
707
|
+
prompt = "";
|
|
708
|
+
constructor(input) {
|
|
709
|
+
this.input = input;
|
|
710
|
+
assertReleasedV2Header(input.sourceHeader);
|
|
711
|
+
this.sourceCut = input.sourceHeader.isSeeded ? void 0 : 0;
|
|
712
|
+
this.targetCut = input.sourceHeader.isSeeded ? void 0 : 0;
|
|
713
|
+
if (!input.sourceHeader.isSeeded) this.headerInheritedEventCount = 0;
|
|
714
|
+
}
|
|
715
|
+
transformEvent(event, context) {
|
|
716
|
+
if (event.seq !== this.mapping.length) throw new SessionFormatError("format v2 source events must be dense");
|
|
717
|
+
assertEvent(event, 2);
|
|
718
|
+
this.observeMessageIds(event);
|
|
719
|
+
let source = event;
|
|
720
|
+
const data = record(event.data, event.type);
|
|
721
|
+
if (event.type === "request/header") {
|
|
722
|
+
const { system, ...header } = record(data["header"], "request header");
|
|
723
|
+
const prompt = typeof system === "string" ? system : "";
|
|
724
|
+
if (prompt !== this.prompt) this.emitSystem(prompt, event, context);
|
|
725
|
+
source = {
|
|
726
|
+
...event,
|
|
727
|
+
data: {
|
|
728
|
+
...data,
|
|
729
|
+
header
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
if (SURFACE_TYPES.has(event.type) && this.head === void 0) throw new SessionFormatUnsupportedMigrationError("format v2 surface before first step cannot acquire a system head without changing chronology");
|
|
734
|
+
if (event.type === "session/end-seed" && data["inherited"] === true) {
|
|
735
|
+
if (!this.input.sourceHeader.isSeeded) throw new SessionFormatError("format v2 unseeded Session contains an inherited end-seed marker");
|
|
736
|
+
this.sourceCut = event.seq;
|
|
737
|
+
this.targetCut = this.targetSeq;
|
|
738
|
+
}
|
|
739
|
+
if (event.type === "session-log-deepseek/delivery-accepted") {
|
|
740
|
+
if (data["sessionFormatVersion"] === 3) throw new SessionFormatError("format v2 delivery marker claims target format v3");
|
|
741
|
+
if (data["sessionFormatVersion"] === 2 && data["sessionId"] !== this.input.sourceHeader.id) this.lastForeignDeliverySeq = event.seq;
|
|
742
|
+
}
|
|
743
|
+
const target = remapEvent(source, this.targetSeq, this.mapping);
|
|
744
|
+
this.mapping.push(this.targetSeq++);
|
|
745
|
+
context.emitEvent(canonicalizeTransformedEvent(renamePtcEvent(target)));
|
|
746
|
+
if (event.type === "step/start") {
|
|
747
|
+
this.step = {
|
|
748
|
+
turn: data["turn"],
|
|
749
|
+
step: data["step"]
|
|
750
|
+
};
|
|
751
|
+
if (this.head === void 0) this.emitSystem("", event, context);
|
|
752
|
+
} else if (event.type === "step/end" || event.type === "turn/end") this.step = void 0;
|
|
753
|
+
}
|
|
754
|
+
transformRun(run, context) {
|
|
755
|
+
for (const event of run.expand()) this.transformEvent(event, context);
|
|
756
|
+
}
|
|
757
|
+
finish(_context) {
|
|
758
|
+
const cut = sessionFormatCount(this.sourceCut, "format v2 inherited end-seed marker");
|
|
759
|
+
if (this.input.sourceInheritedEventCount !== void 0 && this.input.sourceInheritedEventCount !== cut) throw new SessionFormatError("format v2 inherited end-seed marker disagrees with its source cut");
|
|
760
|
+
if (this.lastForeignDeliverySeq !== void 0 && (this.input.sourceHeader.parentSession === void 0 || this.lastForeignDeliverySeq >= cut)) throw new SessionFormatError("current-generation delivery marker names the wrong Session");
|
|
761
|
+
return sessionFormatCount(this.targetCut, "format v3 inherited event count");
|
|
762
|
+
}
|
|
763
|
+
observeMessageIds(event) {
|
|
764
|
+
const data = record(event.data, event.type);
|
|
765
|
+
const messages = event.type === "user/message" ? [data] : event.type === "assistant/message" || event.type === "tool/result" ? [record(data["message"], "message")] : event.type === "agent/inbox/spliced" ? data["inserted"] : event.type === "session/title-llm-request" ? data["messages"] : [];
|
|
766
|
+
for (const message of messages) {
|
|
767
|
+
const id = message["id"];
|
|
768
|
+
if (this.generatedIds.has(id)) throw new SessionFormatUnsupportedMigrationError("source message id collides with a generated system message id");
|
|
769
|
+
this.originalIds.add(id);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
emitSystem(prompt, anchor, context) {
|
|
773
|
+
if (this.step === void 0) throw new SessionFormatUnsupportedMigrationError("format v2 changed request prompt outside an open step cannot retain source chronology");
|
|
774
|
+
const identity = JSON.stringify([
|
|
775
|
+
"session-format-v2-to-v3",
|
|
776
|
+
this.input.sourceHeader.id,
|
|
777
|
+
anchor.seq,
|
|
778
|
+
anchor.type
|
|
779
|
+
]);
|
|
780
|
+
const id = "v2-to-v3-system-" + createHash("sha256").update(identity).digest("hex");
|
|
781
|
+
if (this.originalIds.has(id) || this.generatedIds.has(id)) throw new SessionFormatUnsupportedMigrationError("generated system message id collides with an existing message id");
|
|
782
|
+
this.generatedIds.add(id);
|
|
783
|
+
const seq = this.targetSeq++;
|
|
784
|
+
context.emitEvent(canonicalizeTransformedEvent({
|
|
785
|
+
type: "system/message",
|
|
786
|
+
seq,
|
|
787
|
+
time: anchor.time,
|
|
788
|
+
data: {
|
|
789
|
+
...this.step,
|
|
790
|
+
message: {
|
|
791
|
+
id,
|
|
792
|
+
role: "system",
|
|
793
|
+
source: {
|
|
794
|
+
kind: "plugin",
|
|
795
|
+
plugin: "@deepseek-ai/dsh-system-prompt"
|
|
796
|
+
},
|
|
797
|
+
content: prompt === "" ? [] : [{
|
|
798
|
+
type: "text",
|
|
799
|
+
text: prompt
|
|
800
|
+
}]
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
...this.head === void 0 ? { surfaceOp: "append" } : {
|
|
804
|
+
surfaceOp: {
|
|
805
|
+
op: "replace",
|
|
806
|
+
start: this.head,
|
|
807
|
+
end: this.head
|
|
808
|
+
},
|
|
809
|
+
sourceEventSeqs: [this.head]
|
|
810
|
+
}
|
|
811
|
+
}));
|
|
812
|
+
this.head = seq;
|
|
813
|
+
this.prompt = prompt;
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
/** Source admission precedes renaming, so these payloads have exact audited fields. */
|
|
817
|
+
function renamePtcEvent(event) {
|
|
818
|
+
switch (event.type) {
|
|
819
|
+
case "agent-preset/selected": return event.data["agentPreset"] === "code" ? {
|
|
820
|
+
...event,
|
|
821
|
+
data: {
|
|
822
|
+
...event.data,
|
|
823
|
+
agentPreset: "ptc"
|
|
824
|
+
}
|
|
825
|
+
} : event;
|
|
826
|
+
case "tool/code-dispatch-start": return {
|
|
827
|
+
...event,
|
|
828
|
+
type: "tool/ptc-dispatch-start"
|
|
829
|
+
};
|
|
830
|
+
case "tool/code-dispatch": return {
|
|
831
|
+
...event,
|
|
832
|
+
type: "tool/ptc-dispatch"
|
|
833
|
+
};
|
|
834
|
+
case "user/message": {
|
|
835
|
+
const data = renameMessageSource(event.data);
|
|
836
|
+
return data === event.data ? event : {
|
|
837
|
+
...event,
|
|
838
|
+
data
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
case "agent/inbox/spliced":
|
|
842
|
+
case "session/title-llm-request": {
|
|
843
|
+
const data = event.data;
|
|
844
|
+
const key = event.type === "agent/inbox/spliced" ? "inserted" : "messages";
|
|
845
|
+
const messages = data[key];
|
|
846
|
+
const renamed = messages.map(renameMessageSource);
|
|
847
|
+
return renamed.every((message, index) => message === messages[index]) ? event : {
|
|
848
|
+
...event,
|
|
849
|
+
data: {
|
|
850
|
+
...data,
|
|
851
|
+
[key]: renamed
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
default: return event;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
function renameMessageSource(message) {
|
|
859
|
+
const source = message["source"];
|
|
860
|
+
if (source["kind"] !== "plugin" || source["plugin"] !== "tools-code-mode") return message;
|
|
861
|
+
return {
|
|
862
|
+
...message,
|
|
863
|
+
source: {
|
|
864
|
+
...source,
|
|
865
|
+
plugin: "tools-ptc"
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
//#endregion
|
|
870
|
+
export { assertReleasedV3Header, assertV3EventAdmission, assertV3RowAdmission, releasedV2SessionFormatCodec, releasedV3SessionFormatCodec, restoreReleasedV3Artifact, sessionFormatV2ToV3 };
|