@x1a0f3n9/dsh-session-persistence-jsonl 0.1.7-rc.2 → 0.1.7-rc.3

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.
Files changed (2) hide show
  1. package/lib/index.js +3 -3874
  2. package/package.json +10 -10
package/lib/index.js CHANGED
@@ -7,10 +7,10 @@ import { performance } from "node:perf_hooks";
7
7
  import { scheduler } from "node:timers/promises";
8
8
  import { createHash, randomBytes } from "node:crypto";
9
9
  import { SessionAlreadyExistsError, SessionAlreadyOwnedError, SessionFormatUnsupportedError, SessionHandleClosedError, SessionPersistence, SessionPersistenceCorruptionError, SessionPersistenceNotFoundError, SessionPersistenceRevision, SessionReadOnlyError, assertContiguous, assertStoredId, materializeAppendBatch, materializeCreateHeader, sessionFormatVersionRefusal, validateStoredEvents } from "@x1a0f3n9/dsh-session-persistence";
10
- import { AssistantStreamAccumulator, BlockAssembler, errorChain, expandAssistantStream } from "@x1a0f3n9/dsh-llm";
10
+ import { BlockAssembler, errorChain, expandAssistantStream } from "@x1a0f3n9/dsh-llm";
11
11
  import { tryLockExclusive } from "@deepseek-ai/node-addon-system/flock";
12
12
  import { KNOWN_SESSION_EVENT_TYPES, SESSION_FORMAT_VERSION, Session, SessionId, SessionLogOffset, SessionSeq, decodeSeqRanges, interruptedTurnClosers, isAppendSurfaceEvent } from "@x1a0f3n9/dsh-session";
13
- import { SessionFormatError, SessionFormatUnsupportedMigrationError as SessionFormatUnsupportedMigrationError$1, defineSessionFormatMigration, isSessionFormatJsonObject, parseSessionFormatLogFilename, sessionFormatCount, sessionFormatLogFilename, sessionFormatSafeInteger, snapshotSessionFormatJson } from "@x1a0f3n9/dsh-session-format";
13
+ import { SessionFormatError, SessionFormatUnsupportedMigrationError as SessionFormatUnsupportedMigrationError$1, parseSessionFormatLogFilename, sessionFormatLogFilename } from "@x1a0f3n9/dsh-session-format";
14
14
  import { assertReleasedV4Relationships, assertV4RowAdmission, historicalChildCatalogSource } from "@x1a0f3n9/dsh-session-format-v3-to-v4";
15
15
  import { constants, createZstdCompress, createZstdDecompress, zstdCompress, zstdDecompress, zstdDecompressSync } from "node:zlib";
16
16
  import { isDeepStrictEqual, promisify } from "node:util";
@@ -1514,3877 +1514,6 @@ async function scanZstdFramesFromReader(fileSize, readRange, signal) {
1514
1514
  return { frames };
1515
1515
  }
1516
1516
  //#endregion
1517
- //#region ../../util/values/src/index.ts
1518
- /** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
1519
- function hasIntrinsicConstructor(prototype, name) {
1520
- const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
1521
- if (typeof constructor !== "function") return false;
1522
- try {
1523
- return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
1524
- } catch {
1525
- return false;
1526
- }
1527
- }
1528
- /** Whether a candidate is one realm's intrinsic `Object.prototype`. */
1529
- function isIntrinsicObjectPrototype(value) {
1530
- return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
1531
- }
1532
- /** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
1533
- function hasPlainArrayPrototype(value) {
1534
- const prototype = Object.getPrototypeOf(value);
1535
- if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
1536
- const objectPrototype = Object.getPrototypeOf(prototype);
1537
- return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
1538
- }
1539
- /** Whether an object is a plain or null-prototype record from any JavaScript realm. */
1540
- function hasPlainObjectPrototype(value) {
1541
- const prototype = Object.getPrototypeOf(value);
1542
- return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
1543
- }
1544
- /** Return every JSON-visible object key, or reject own data JSON would discard. */
1545
- function enumerableStringKeys(value) {
1546
- const keys = Reflect.ownKeys(value);
1547
- if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
1548
- return keys;
1549
- }
1550
- /** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
1551
- function walkJsonValue(value, detach) {
1552
- const ancestors = /* @__PURE__ */ new Set();
1553
- let root;
1554
- const assign = (destination, item) => {
1555
- if (destination === void 0) return;
1556
- if (destination.kind === "root") root = item;
1557
- else if (destination.kind === "array") destination.target[destination.index] = item;
1558
- else Object.defineProperty(destination.target, destination.key, {
1559
- value: item,
1560
- enumerable: true,
1561
- configurable: true,
1562
- writable: true
1563
- });
1564
- };
1565
- const tasks = [{
1566
- kind: "visit",
1567
- value,
1568
- ...detach ? { destination: { kind: "root" } } : {}
1569
- }];
1570
- for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
1571
- if (task.kind === "leave") {
1572
- ancestors.delete(task.source);
1573
- continue;
1574
- }
1575
- if (task.kind === "array-item") {
1576
- if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
1577
- tasks.push({
1578
- kind: "visit",
1579
- value: task.source[task.index],
1580
- ...task.target === void 0 ? {} : { destination: {
1581
- kind: "array",
1582
- target: task.target,
1583
- index: task.index
1584
- } }
1585
- });
1586
- continue;
1587
- }
1588
- if (task.kind === "object-property") {
1589
- tasks.push({
1590
- kind: "visit",
1591
- value: task.source[task.key],
1592
- ...task.target === void 0 ? {} : { destination: {
1593
- kind: "object",
1594
- target: task.target,
1595
- key: task.key
1596
- } }
1597
- });
1598
- continue;
1599
- }
1600
- const current = task.value;
1601
- if (current === null) {
1602
- assign(task.destination, null);
1603
- continue;
1604
- }
1605
- if (typeof current === "boolean" || typeof current === "string") {
1606
- assign(task.destination, current);
1607
- continue;
1608
- }
1609
- if (typeof current === "number") {
1610
- if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
1611
- assign(task.destination, current);
1612
- continue;
1613
- }
1614
- if (typeof current !== "object") return void 0;
1615
- if (ancestors.has(current)) return void 0;
1616
- if (Array.isArray(current)) {
1617
- if (!hasPlainArrayPrototype(current)) return void 0;
1618
- const length = current.length;
1619
- if (Reflect.ownKeys(current).length !== length + 1) return void 0;
1620
- const target = detach ? [] : void 0;
1621
- if (target !== void 0) assign(task.destination, target);
1622
- ancestors.add(current);
1623
- tasks.push({
1624
- kind: "leave",
1625
- source: current
1626
- });
1627
- for (let index = length - 1; index >= 0; index--) tasks.push({
1628
- kind: "array-item",
1629
- source: current,
1630
- index,
1631
- ...target === void 0 ? {} : { target }
1632
- });
1633
- continue;
1634
- }
1635
- if (!hasPlainObjectPrototype(current)) return void 0;
1636
- const keys = enumerableStringKeys(current);
1637
- if (keys === void 0) return void 0;
1638
- const target = detach ? {} : void 0;
1639
- if (target !== void 0) assign(task.destination, target);
1640
- ancestors.add(current);
1641
- tasks.push({
1642
- kind: "leave",
1643
- source: current
1644
- });
1645
- for (let index = keys.length - 1; index >= 0; index--) {
1646
- const key = keys[index];
1647
- /* v8 ignore next -- the loop is bounded by the captured key count. */
1648
- if (key === void 0) return void 0;
1649
- tasks.push({
1650
- kind: "object-property",
1651
- source: current,
1652
- key,
1653
- ...target === void 0 ? {} : { target }
1654
- });
1655
- }
1656
- }
1657
- return detach ? root : true;
1658
- }
1659
- /**
1660
- * Test the same lossless JSON rules as {@link snapshotJsonValue} without detaching the value.
1661
- * @param value - candidate value to test.
1662
- * @returns whether the value survives a JSON round trip without loss.
1663
- */
1664
- function isJsonValue(value) {
1665
- return walkJsonValue(value, false) === true;
1666
- }
1667
- /**
1668
- * Compare JSON-compatible values structurally.
1669
- * @param a - one JSON-compatible value.
1670
- * @param b - the other JSON-compatible value.
1671
- * @returns whether both values contain the same JSON data.
1672
- */
1673
- function deepEqualJson(a, b) {
1674
- if (a === b) return true;
1675
- if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
1676
- if (Array.isArray(a) || Array.isArray(b)) {
1677
- if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
1678
- return a.every((entry, index) => deepEqualJson(entry, b[index]));
1679
- }
1680
- const left = a;
1681
- const right = b;
1682
- const keys = Object.keys(left);
1683
- if (keys.length !== Object.keys(right).length) return false;
1684
- return keys.every((key) => key in right && deepEqualJson(left[key], right[key]));
1685
- }
1686
- //#endregion
1687
- //#region ../session-format-v0-to-v1/src/dispositions.ts
1688
- /**
1689
- * Freeze one exact released payload-member disposition for adjacent format validators.
1690
- * @param required - members that must be present.
1691
- * @param optional - additional admitted members.
1692
- * @param opaque - members retained as lossless JSON without nested semantic inspection.
1693
- * @returns the detached frozen disposition.
1694
- */
1695
- function defineReleasedPayloadDisposition(required, optional = [], opaque = []) {
1696
- return Object.freeze({
1697
- required: Object.freeze([...required]),
1698
- optional: Object.freeze([...optional]),
1699
- opaque: Object.freeze([...opaque])
1700
- });
1701
- }
1702
- const disposition = defineReleasedPayloadDisposition;
1703
- /**
1704
- * Frozen released-v0 event and payload-member inventory.
1705
- * Every listed member is preserved by the identity edge. Members in `opaque`
1706
- * remain lossless JSON without nested Session-sequence interpretation. Nested
1707
- * merge-extensible discriminants validate known variants and preserve
1708
- * unknown variants as owner-opaque JSON.
1709
- */
1710
- const RELEASED_V0_EVENT_DISPOSITIONS = Object.freeze({
1711
- "agent-preset/selected": disposition(["agentPreset"]),
1712
- "agent/inbox/spliced": disposition([
1713
- "target",
1714
- "start",
1715
- "inserted"
1716
- ], ["removedCount", "outcome"]),
1717
- "approval/asked": disposition(["id", "toolName"], ["callId", "reason"]),
1718
- "approval/decided": disposition(["id", "outcome"]),
1719
- "approval/policy": disposition(["policy"], ["source"]),
1720
- "assistant/chunk": disposition([
1721
- "turn",
1722
- "step",
1723
- "chunk"
1724
- ]),
1725
- "assistant/message": disposition([
1726
- "turn",
1727
- "step",
1728
- "message"
1729
- ], ["usage", "interrupted"]),
1730
- "command/done": disposition(["commandId", "kind"], ["text", "sourceEventSeq"]),
1731
- "command/run": disposition([
1732
- "commandId",
1733
- "name",
1734
- "source"
1735
- ], ["args"]),
1736
- "compaction/end": disposition(["compactionId", "turn"], ["sourceCommandId", "error"]),
1737
- "compaction/prune": disposition([
1738
- "shadowedRange",
1739
- "shadowedSeqs",
1740
- "shadowedTokenCount"
1741
- ]),
1742
- "compaction/start": disposition(["compactionId", "turn"], ["sourceCommandId"]),
1743
- "compaction/summary": disposition([
1744
- "compactionId",
1745
- "summary",
1746
- "shadowedRange",
1747
- "shadowedSeqs",
1748
- "shadowedTokenCount",
1749
- "provider",
1750
- "model"
1751
- ], [
1752
- "sourceCommandId",
1753
- "maxTokens",
1754
- "usage",
1755
- "rawOutput",
1756
- "llmStreamCall"
1757
- ]),
1758
- "feedback/record": disposition(["text"]),
1759
- "goal/change": disposition([
1760
- "kind",
1761
- "version",
1762
- "operation"
1763
- ], [
1764
- "goal",
1765
- "roundsStarted",
1766
- "createdAt",
1767
- "updatedAt",
1768
- "cleared",
1769
- "clearedAt"
1770
- ]),
1771
- "hook/invoked": disposition([
1772
- "turn",
1773
- "point",
1774
- "dialect",
1775
- "handlerId"
1776
- ], ["matcher"]),
1777
- "hook/result": disposition([
1778
- "turn",
1779
- "point",
1780
- "handlerId",
1781
- "decision",
1782
- "durationMs"
1783
- ], ["exitCode", "stderrSummary"]),
1784
- "llm/retry": disposition([
1785
- "retryId",
1786
- "turn",
1787
- "step",
1788
- "provider",
1789
- "mode",
1790
- "policyKey",
1791
- "retry",
1792
- "delayMs",
1793
- "failure"
1794
- ], ["maxRetries"]),
1795
- "llm/retry-started": disposition([
1796
- "retryId",
1797
- "turn",
1798
- "step",
1799
- "retry"
1800
- ]),
1801
- "model/selection": disposition(["provider", "model"], ["reasoningEffort"]),
1802
- "permission/preset": disposition(["preset"]),
1803
- "plan/mode": disposition(["active"]),
1804
- "request/context": disposition(["provider", "model"], ["contextWindow"]),
1805
- "request/header": disposition(["header", "reason"], ["startsSeries"]),
1806
- "sandbox/mode": disposition(["mode"], ["source"]),
1807
- "schedule/change": disposition(["version", "operation"], [
1808
- "schedule",
1809
- "id",
1810
- "acceptedAt"
1811
- ]),
1812
- "session-log-deepseek/delivery-accepted": disposition(["sessionId", "throughSeq"]),
1813
- "session/end-seed": disposition([]),
1814
- "session/title": disposition([
1815
- "title",
1816
- "messageSeqs",
1817
- "source"
1818
- ]),
1819
- "session/title-llm-request": disposition([
1820
- "titleProvider",
1821
- "messageSeqs",
1822
- "route",
1823
- "system",
1824
- "messages",
1825
- "maxTokens"
1826
- ]),
1827
- "step/end": disposition(["turn", "step"]),
1828
- "step/start": disposition(["turn", "step"]),
1829
- "subagent/descriptor": disposition([
1830
- "mode",
1831
- "version",
1832
- "provider"
1833
- ], [
1834
- "label",
1835
- "agentProvider",
1836
- "agentModel",
1837
- "agentReasoningEffort",
1838
- "persona",
1839
- "toolFilter"
1840
- ]),
1841
- "subagent/model-selection-policy": disposition(["allowedModels"]),
1842
- "team/member": disposition([
1843
- "version",
1844
- "teamId",
1845
- "member"
1846
- ]),
1847
- "team/message/delivered": disposition([
1848
- "version",
1849
- "teamId",
1850
- "messageId",
1851
- "targetId"
1852
- ]),
1853
- "team/message/queued": disposition([
1854
- "version",
1855
- "teamId",
1856
- "message"
1857
- ]),
1858
- "team/task": disposition([
1859
- "version",
1860
- "teamId",
1861
- "task"
1862
- ]),
1863
- "todo/write": disposition(["todos"]),
1864
- "tool-workflow/agent-end": disposition([
1865
- "runId",
1866
- "seq",
1867
- "outcome"
1868
- ]),
1869
- "tool-workflow/agent-start": disposition([
1870
- "runId",
1871
- "seq",
1872
- "label",
1873
- "childId"
1874
- ], ["phase"]),
1875
- "tool-workflow/run-end": disposition(["runId", "stopReason"]),
1876
- "tool-workflow/run-start": disposition(["runId", "name"]),
1877
- "tool/call": disposition([
1878
- "turn",
1879
- "step",
1880
- "callId",
1881
- "name",
1882
- "arguments"
1883
- ]),
1884
- "tool/code-dispatch": disposition([
1885
- "rootCallId",
1886
- "parentCallId",
1887
- "subCallId",
1888
- "name",
1889
- "arguments",
1890
- "isError",
1891
- "content"
1892
- ], [], ["arguments"]),
1893
- "tool/code-dispatch-start": disposition([
1894
- "rootCallId",
1895
- "parentCallId",
1896
- "subCallId",
1897
- "name",
1898
- "arguments"
1899
- ], [], ["arguments"]),
1900
- "tool/result": disposition([
1901
- "turn",
1902
- "step",
1903
- "message"
1904
- ], ["error", "meta"], ["meta"]),
1905
- "turn/end": disposition(["turn", "reason"]),
1906
- "turn/start": disposition(["turn"]),
1907
- "user/message": disposition([
1908
- "role",
1909
- "id",
1910
- "content",
1911
- "source"
1912
- ]),
1913
- "web/deepseek-search-llm-request": disposition([
1914
- "endpoint",
1915
- "apiVersion",
1916
- "body"
1917
- ])
1918
- });
1919
- Object.freeze(Object.keys(RELEASED_V0_EVENT_DISPOSITIONS).sort((left, right) => left.localeCompare(right, "en")));
1920
- //#endregion
1921
- //#region ../session-format-v0-to-v1/src/validation-helpers.ts
1922
- /**
1923
- * Require one plain JSON object.
1924
- * @param value - candidate JSON value.
1925
- * @param label - diagnostic subject.
1926
- * @returns validated object record.
1927
- */
1928
- function releasedV0Record(value, label) {
1929
- if (!isSessionFormatJsonObject(value)) throw new SessionFormatError(`${label} must be a JSON object`);
1930
- return value;
1931
- }
1932
- /**
1933
- * Require every named member and no member outside the optional list.
1934
- * @param record - candidate object.
1935
- * @param required - members that must exist.
1936
- * @param optional - additional admitted members.
1937
- * @param label - diagnostic subject.
1938
- */
1939
- function assertReleasedV0Keys(record, required, optional = [], label) {
1940
- const allowed = new Set([...required, ...optional]);
1941
- const unexpected = Object.keys(record).find((key) => !allowed.has(key));
1942
- if (unexpected !== void 0) throw new SessionFormatError(`${label} has unexpected member ${JSON.stringify(unexpected)}`);
1943
- const missing = required.find((key) => !Object.hasOwn(record, key));
1944
- if (missing !== void 0) throw new SessionFormatError(`${label} lacks required member ${JSON.stringify(missing)}`);
1945
- }
1946
- //#endregion
1947
- //#region ../session-format-v0-to-v1/src/payload-validation.ts
1948
- /**
1949
- * Validate nested released payload semantics for one known event.
1950
- * @param event - known event with exact top-level members.
1951
- * @param version - source or current payload generation.
1952
- */
1953
- function assertReleasedPayloadSemantics(event, version) {
1954
- const data = releasedV0Record(event.data, `${event.type} ${event.seq} data`);
1955
- const label = `${event.type} ${event.seq}`;
1956
- switch (event.type) {
1957
- case "agent-preset/selected":
1958
- stringValue(data["agentPreset"], `${label} agentPreset`);
1959
- return;
1960
- case "agent/inbox/spliced":
1961
- literalValue(data["target"], ["next-turn", "next-step"], `${label} target`);
1962
- countValue(data["start"], `${label} start`);
1963
- if (data["removedCount"] !== void 0) countValue(data["removedCount"], `${label} removedCount`);
1964
- arrayValue(data["inserted"], `${label} inserted`, (value) => {
1965
- messageValue(value, `${label} inserted message`, version, "user");
1966
- });
1967
- if (data["outcome"] !== void 0) literalValue(data["outcome"], ["canceled"], `${label} outcome`);
1968
- return;
1969
- case "approval/asked":
1970
- nonEmptyString(data["id"], `${label} id`);
1971
- nonEmptyString(data["toolName"], `${label} toolName`);
1972
- if (data["callId"] !== void 0) nonEmptyString(data["callId"], `${label} callId`);
1973
- if (data["reason"] !== void 0) stringValue(data["reason"], `${label} reason`);
1974
- return;
1975
- case "approval/decided":
1976
- nonEmptyString(data["id"], `${label} id`);
1977
- literalValue(data["outcome"], [
1978
- "allowed-once",
1979
- "rejected",
1980
- "cancelled",
1981
- "unavailable"
1982
- ], `${label} outcome`);
1983
- return;
1984
- case "approval/policy":
1985
- literalValue(data["policy"], ["ask", "never"], `${label} policy`);
1986
- if (data["source"] !== void 0) literalValue(data["source"], ["delegation"], `${label} source`);
1987
- return;
1988
- case "assistant/chunk":
1989
- coordinatePair(data, label);
1990
- streamChunkValue(data["chunk"], `${label} chunk`);
1991
- return;
1992
- case "assistant/message":
1993
- coordinatePair(data, label);
1994
- messageValue(data["message"], `${label} message`, version, "assistant");
1995
- if (data["usage"] !== void 0) tokenUsageValue(data["usage"], `${label} usage`);
1996
- if (data["interrupted"] !== void 0) literalValue(data["interrupted"], [true], `${label} interrupted`);
1997
- return;
1998
- case "command/done":
1999
- nonEmptyString(data["commandId"], `${label} commandId`);
2000
- literalValue(data["kind"], ["success", "error"], `${label} kind`);
2001
- if (data["text"] !== void 0) stringValue(data["text"], `${label} text`);
2002
- if (data["sourceEventSeq"] !== void 0) earlierSeq(data["sourceEventSeq"], event.seq, `${label} sourceEventSeq`);
2003
- return;
2004
- case "command/run":
2005
- nonEmptyString(data["commandId"], `${label} commandId`);
2006
- nonEmptyString(data["name"], `${label} name`);
2007
- if (data["args"] !== void 0) stringValue(data["args"], `${label} args`);
2008
- literalValue(exactRecord(data["source"], `${label} source`, ["kind"])["kind"], ["user"], `${label} source kind`);
2009
- return;
2010
- case "compaction/start":
2011
- case "compaction/end":
2012
- nonEmptyString(data["compactionId"], `${label} compactionId`);
2013
- if (data["sourceCommandId"] !== void 0) nonEmptyString(data["sourceCommandId"], `${label} sourceCommandId`);
2014
- nullableValue(data["turn"], `${label} turn`, countValue);
2015
- if (data["error"] !== void 0) stringValue(data["error"], `${label} error`);
2016
- return;
2017
- case "compaction/prune":
2018
- shadowedValue(data, event.seq, label);
2019
- return;
2020
- case "compaction/summary":
2021
- if (data["llmStreamCall"] === true && data["rawOutput"] === void 0) throw new SessionFormatError(`${label} llmStreamCall requires rawOutput`);
2022
- nonEmptyString(data["compactionId"], `${label} compactionId`);
2023
- if (data["sourceCommandId"] !== void 0) nonEmptyString(data["sourceCommandId"], `${label} sourceCommandId`);
2024
- contentBlocksValue(data["summary"], `${label} summary`, version);
2025
- shadowedValue(data, event.seq, label);
2026
- nonEmptyString(data["provider"], `${label} provider`);
2027
- nonEmptyString(data["model"], `${label} model`);
2028
- if (data["maxTokens"] !== void 0) countValue(data["maxTokens"], `${label} maxTokens`);
2029
- if (data["usage"] !== void 0) tokenUsageValue(data["usage"], `${label} usage`);
2030
- if (data["rawOutput"] !== void 0) contentBlocksValue(data["rawOutput"], `${label} rawOutput`, version);
2031
- if (data["llmStreamCall"] !== void 0) literalValue(data["llmStreamCall"], [true], `${label} llmStreamCall`);
2032
- return;
2033
- case "feedback/record":
2034
- nonEmptyString(data["text"], `${label} text`);
2035
- return;
2036
- case "goal/change":
2037
- goalChangeValue(data, label);
2038
- return;
2039
- case "hook/invoked":
2040
- countValue(data["turn"], `${label} turn`);
2041
- nonEmptyString(data["point"], `${label} point`);
2042
- literalValue(data["dialect"], ["claude-code", "codex"], `${label} dialect`);
2043
- if (data["matcher"] !== void 0) stringValue(data["matcher"], `${label} matcher`);
2044
- nonEmptyString(data["handlerId"], `${label} handlerId`);
2045
- return;
2046
- case "hook/result":
2047
- countValue(data["turn"], `${label} turn`);
2048
- nonEmptyString(data["point"], `${label} point`);
2049
- nonEmptyString(data["handlerId"], `${label} handlerId`);
2050
- nonEmptyString(data["decision"], `${label} decision`);
2051
- if (data["exitCode"] !== void 0) safeIntegerValue(data["exitCode"], `${label} exitCode`);
2052
- if (data["stderrSummary"] !== void 0) stringValue(data["stderrSummary"], `${label} stderrSummary`);
2053
- if (finiteNumberValue(data["durationMs"], `${label} durationMs`) < 0) throw new SessionFormatError(`${label} durationMs must be non-negative`);
2054
- return;
2055
- case "llm/retry":
2056
- nonEmptyString(data["retryId"], `${label} retryId`);
2057
- coordinatePair(data, label);
2058
- nonEmptyString(data["provider"], `${label} provider`);
2059
- literalValue(data["mode"], ["normal", "always"], `${label} mode`);
2060
- nonEmptyString(data["policyKey"], `${label} policyKey`);
2061
- positiveIntegerValue(data["retry"], `${label} retry`);
2062
- if (data["mode"] === "normal") {
2063
- const maxRetries = positiveIntegerValue(data["maxRetries"], `${label} maxRetries`);
2064
- if (data["retry"] > maxRetries) throw new SessionFormatError(`${label} retry exceeds maxRetries`);
2065
- } else if (data["maxRetries"] !== void 0) throw new SessionFormatError(`${label} always mode must omit maxRetries`);
2066
- const delayMs = finiteNumberValue(data["delayMs"], `${label} delayMs`);
2067
- if (delayMs < 0) throw new SessionFormatError(`${label} delayMs must be non-negative`);
2068
- if (delayMs > 2147483647) throw new SessionFormatError(`${label} delayMs exceeds the timer range`);
2069
- llmFailureValue(data["failure"], `${label} failure`);
2070
- return;
2071
- case "llm/retry-started":
2072
- nonEmptyString(data["retryId"], `${label} retryId`);
2073
- coordinatePair(data, label);
2074
- positiveIntegerValue(data["retry"], `${label} retry`);
2075
- return;
2076
- case "model/selection":
2077
- nonEmptyString(data["provider"], `${label} provider`);
2078
- nonEmptyString(data["model"], `${label} model`);
2079
- if (data["reasoningEffort"] !== void 0) nonEmptyString(data["reasoningEffort"], `${label} reasoningEffort`);
2080
- return;
2081
- case "permission/preset":
2082
- nonEmptyString(data["preset"], `${label} preset`);
2083
- return;
2084
- case "plan/mode":
2085
- booleanValue(data["active"], `${label} active`);
2086
- return;
2087
- case "request/context":
2088
- nonEmptyString(data["provider"], `${label} provider`);
2089
- nonEmptyString(data["model"], `${label} model`);
2090
- if (data["contextWindow"] !== void 0) positiveIntegerValue(data["contextWindow"], `${label} contextWindow`);
2091
- return;
2092
- case "request/header":
2093
- requestHeaderValue(data["header"], `${label} header`);
2094
- literalValue(data["reason"], [
2095
- "initial",
2096
- "resume",
2097
- "change",
2098
- "series"
2099
- ], `${label} reason`);
2100
- if (data["startsSeries"] !== void 0) literalValue(data["startsSeries"], [true], `${label} startsSeries`);
2101
- return;
2102
- case "sandbox/mode":
2103
- literalValue(data["mode"], [
2104
- "read-only",
2105
- "workspace-write",
2106
- "danger-full-access"
2107
- ], `${label} mode`);
2108
- if (data["source"] !== void 0) literalValue(data["source"], ["delegation"], `${label} source`);
2109
- return;
2110
- case "schedule/change":
2111
- scheduleChangeValue(data, label);
2112
- return;
2113
- case "session-log-deepseek/delivery-accepted":
2114
- if ((data["sessionFormatVersion"] === void 0 ? 0 : countValue(data["sessionFormatVersion"], `${label} sessionFormatVersion`)) !== version) return;
2115
- nonEmptyString(data["sessionId"], `${label} sessionId`);
2116
- earlierSeq(data["throughSeq"], event.seq, `${label} throughSeq`);
2117
- return;
2118
- case "session/end-seed": return;
2119
- case "session/title":
2120
- nonEmptyString(data["title"], `${label} title`);
2121
- seqArray(data["messageSeqs"], event.seq, `${label} messageSeqs`, false);
2122
- titleSourceValue(data["source"], `${label} source`);
2123
- return;
2124
- case "session/title-llm-request":
2125
- nonEmptyString(data["titleProvider"], `${label} titleProvider`);
2126
- seqArray(data["messageSeqs"], event.seq, `${label} messageSeqs`, true);
2127
- modelRouteValue(data["route"], `${label} route`);
2128
- stringValue(data["system"], `${label} system`);
2129
- arrayValue(data["messages"], `${label} messages`, (value) => {
2130
- messageValue(value, `${label} message`, version);
2131
- });
2132
- positiveIntegerValue(data["maxTokens"], `${label} maxTokens`);
2133
- return;
2134
- case "step/end":
2135
- case "step/start":
2136
- coordinatePair(data, label);
2137
- return;
2138
- case "subagent/descriptor":
2139
- subagentDescriptorValue(data, label);
2140
- return;
2141
- case "subagent/model-selection-policy":
2142
- allowedModelsValue(data["allowedModels"], `${label} allowedModels`);
2143
- return;
2144
- case "team/member":
2145
- teamSelector(data, label);
2146
- teamMemberValue(data["member"], `${label} member`);
2147
- return;
2148
- case "team/message/delivered":
2149
- teamSelector(data, label);
2150
- nonEmptyString(data["messageId"], `${label} messageId`);
2151
- nonEmptyString(data["targetId"], `${label} targetId`);
2152
- return;
2153
- case "team/message/queued":
2154
- teamSelector(data, label);
2155
- teamMessageValue(data["message"], `${label} message`, version);
2156
- return;
2157
- case "team/task":
2158
- teamSelector(data, label);
2159
- teamTaskValue(data["task"], `${label} task`);
2160
- return;
2161
- case "todo/write":
2162
- arrayValue(data["todos"], `${label} todos`, (value, itemLabel) => {
2163
- const item = exactRecord(value, itemLabel, ["content", "status"]);
2164
- stringValue(item["content"], `${itemLabel} content`);
2165
- literalValue(item["status"], [
2166
- "pending",
2167
- "in_progress",
2168
- "completed"
2169
- ], `${itemLabel} status`);
2170
- });
2171
- return;
2172
- case "tool-workflow/agent-end":
2173
- workflowIdentity(data, label);
2174
- literalValue(data["outcome"], [
2175
- "completed",
2176
- "failed",
2177
- "cancelled"
2178
- ], `${label} outcome`);
2179
- return;
2180
- case "tool-workflow/agent-start":
2181
- workflowIdentity(data, label);
2182
- stringValue(data["label"], `${label} label`);
2183
- if (data["phase"] !== void 0) stringValue(data["phase"], `${label} phase`);
2184
- nonEmptyString(data["childId"], `${label} childId`);
2185
- return;
2186
- case "tool-workflow/run-end":
2187
- nonEmptyString(data["runId"], `${label} runId`);
2188
- literalValue(data["stopReason"], [
2189
- "completed",
2190
- "cancelled",
2191
- "error"
2192
- ], `${label} stopReason`);
2193
- return;
2194
- case "tool-workflow/run-start":
2195
- nonEmptyString(data["runId"], `${label} runId`);
2196
- nonEmptyString(data["name"], `${label} name`);
2197
- return;
2198
- case "tool/call":
2199
- coordinatePair(data, label);
2200
- nonEmptyString(data["callId"], `${label} callId`);
2201
- nonEmptyString(data["name"], `${label} name`);
2202
- stringValue(data["arguments"], `${label} arguments`);
2203
- return;
2204
- case "tool/code-dispatch":
2205
- case "tool/code-dispatch-start":
2206
- nonEmptyString(data["rootCallId"], `${label} rootCallId`);
2207
- nonEmptyString(data["parentCallId"], `${label} parentCallId`);
2208
- nonEmptyString(data["subCallId"], `${label} subCallId`);
2209
- nonEmptyString(data["name"], `${label} name`);
2210
- if (event.type === "tool/code-dispatch") {
2211
- booleanValue(data["isError"], `${label} isError`);
2212
- contentBlocksValue(data["content"], `${label} content`, version);
2213
- }
2214
- return;
2215
- case "tool/result":
2216
- coordinatePair(data, label);
2217
- messageValue(data["message"], `${label} message`, version, "tool");
2218
- if (data["error"] !== void 0) {
2219
- const error = exactRecord(data["error"], `${label} error`, ["name", "code"]);
2220
- nonEmptyString(error["name"], `${label} error name`);
2221
- nonEmptyString(error["code"], `${label} error code`);
2222
- }
2223
- return;
2224
- case "turn/end":
2225
- countValue(data["turn"], `${label} turn`);
2226
- turnEndReasonValue(data["reason"], `${label} reason`);
2227
- return;
2228
- case "turn/start":
2229
- countValue(data["turn"], `${label} turn`);
2230
- return;
2231
- case "user/message":
2232
- messageValue(data, label, version, "user");
2233
- return;
2234
- case "web/deepseek-search-llm-request":
2235
- nonEmptyString(data["endpoint"], `${label} endpoint`);
2236
- nonEmptyString(data["apiVersion"], `${label} apiVersion`);
2237
- deepSeekSearchBodyValue(data["body"], `${label} body`);
2238
- return;
2239
- /* v8 ignore next -- the frozen disposition rejects unknown types before semantic dispatch. */
2240
- default: throw new SessionFormatError(`released payload validator is missing event ${JSON.stringify(event.type)}`);
2241
- }
2242
- }
2243
- function exactRecord(value, label, required, optional = []) {
2244
- const record = releasedV0Record(value, label);
2245
- assertReleasedV0Keys(record, required, optional, label);
2246
- return record;
2247
- }
2248
- function stringValue(value, label) {
2249
- if (typeof value !== "string") throw new SessionFormatError(`${label} must be a string`);
2250
- }
2251
- function nonEmptyString(value, label) {
2252
- if (typeof value !== "string" || value.length === 0) throw new SessionFormatError(`${label} must be a non-empty string`);
2253
- }
2254
- function booleanValue(value, label) {
2255
- if (typeof value !== "boolean") throw new SessionFormatError(`${label} must be a boolean`);
2256
- }
2257
- function safeIntegerValue(value, label) {
2258
- return sessionFormatSafeInteger(value, label);
2259
- }
2260
- function countValue(value, label) {
2261
- return sessionFormatCount(value, label);
2262
- }
2263
- function positiveIntegerValue(value, label) {
2264
- const result = countValue(value, label);
2265
- if (result === 0) throw new SessionFormatError(`${label} must be positive`);
2266
- return result;
2267
- }
2268
- function finiteNumberValue(value, label) {
2269
- if (typeof value !== "number" || !Number.isFinite(value) || Object.is(value, -0)) throw new SessionFormatError(`${label} must be a finite number`);
2270
- return value;
2271
- }
2272
- function literalValue(value, allowed, label) {
2273
- if (!allowed.some((candidate) => candidate === value)) throw new SessionFormatError(`${label} must be one of ${allowed.map(String).join(", ")}`);
2274
- }
2275
- function nullableValue(value, label, validate) {
2276
- if (value !== null) validate(value, label);
2277
- }
2278
- function arrayValue(value, label, validate) {
2279
- if (!Array.isArray(value)) throw new SessionFormatError(`${label} must be an array`);
2280
- const members = value;
2281
- members.forEach((member, index) => {
2282
- validate(member, `${label}[${index}]`);
2283
- });
2284
- return members;
2285
- }
2286
- function coordinatePair(data, label) {
2287
- countValue(data["turn"], `${label} turn`);
2288
- countValue(data["step"], `${label} step`);
2289
- }
2290
- function earlierSeq(value, eventSeq, label) {
2291
- const seq = countValue(value, label);
2292
- if (seq >= eventSeq) throw new SessionFormatError(`${label} must identify an earlier event`);
2293
- return seq;
2294
- }
2295
- function seqArray(value, eventSeq, label, requireNonEmpty) {
2296
- const seen = /* @__PURE__ */ new Set();
2297
- const values = arrayValue(value, label, (member, memberLabel) => {
2298
- const seq = earlierSeq(member, eventSeq, memberLabel);
2299
- if (seen.has(seq)) throw new SessionFormatError(`${label} repeats seq ${seq}`);
2300
- seen.add(seq);
2301
- });
2302
- if (requireNonEmpty && values.length === 0) throw new SessionFormatError(`${label} must be non-empty`);
2303
- return values;
2304
- }
2305
- function llmFailureValue(value, label) {
2306
- const failure = exactRecord(value, label, ["message", "code"], [
2307
- "status",
2308
- "providerRetryAfterMs",
2309
- "requestId"
2310
- ]);
2311
- nonEmptyString(failure["message"], `${label} message`);
2312
- nonEmptyString(failure["code"], `${label} code`);
2313
- if (failure["status"] !== void 0) {
2314
- const status = safeIntegerValue(failure["status"], `${label} status`);
2315
- if (status < 100 || status > 599) throw new SessionFormatError(`${label} status must be 100 through 599`);
2316
- }
2317
- if (failure["providerRetryAfterMs"] !== void 0 && finiteNumberValue(failure["providerRetryAfterMs"], `${label} providerRetryAfterMs`) <= 0) throw new SessionFormatError(`${label} providerRetryAfterMs must be positive`);
2318
- if (failure["requestId"] !== void 0) nonEmptyString(failure["requestId"], `${label} requestId`);
2319
- }
2320
- function tokenUsageValue(value, label) {
2321
- const usage = exactRecord(value, label, ["inputTokens", "outputTokens"], [
2322
- "totalTokens",
2323
- "cacheReadTokens",
2324
- "cacheWriteTokens",
2325
- "reasoningTokens"
2326
- ]);
2327
- for (const key of Object.keys(usage)) countValue(usage[key], `${label} ${key}`);
2328
- }
2329
- function contentBlocksValue(value, label, version) {
2330
- arrayValue(value, label, (member, memberLabel) => {
2331
- contentBlockValue(member, memberLabel, version);
2332
- });
2333
- }
2334
- function contentBlockValue(value, label, version) {
2335
- const block = releasedV0Record(value, label);
2336
- switch (block["type"]) {
2337
- case "text":
2338
- case "reasoning":
2339
- assertReleasedV0Keys(block, ["type", "text"], [], label);
2340
- stringValue(block["text"], `${label} text`);
2341
- return;
2342
- case "image":
2343
- assertReleasedV0Keys(block, ["type", "attachment"], [], label);
2344
- imageAttachmentValue(block["attachment"], `${label} attachment`);
2345
- return;
2346
- case "tool-call":
2347
- assertReleasedV0Keys(block, [
2348
- "type",
2349
- "id",
2350
- "name",
2351
- "arguments"
2352
- ], [], label);
2353
- nonEmptyString(block["id"], `${label} id`);
2354
- nonEmptyString(block["name"], `${label} name`);
2355
- stringValue(block["arguments"], `${label} arguments`);
2356
- return;
2357
- case "tool-result":
2358
- assertReleasedV0Keys(block, [
2359
- "type",
2360
- "toolCallId",
2361
- "content"
2362
- ], ["isError"], label);
2363
- nonEmptyString(block["toolCallId"], `${label} toolCallId`);
2364
- contentBlocksValue(block["content"], `${label} content`, version);
2365
- if (block["isError"] !== void 0) booleanValue(block["isError"], `${label} isError`);
2366
- return;
2367
- default:
2368
- nonEmptyString(block["type"], `${label} type`);
2369
- return;
2370
- }
2371
- }
2372
- function imageAttachmentValue(value, label) {
2373
- const attachment = exactRecord(value, label, [
2374
- "attachmentId",
2375
- "mediaType",
2376
- "bytes",
2377
- "width",
2378
- "height"
2379
- ], ["name", "originalDimensions"]);
2380
- nonEmptyString(attachment["attachmentId"], `${label} attachmentId`);
2381
- literalValue(attachment["mediaType"], [
2382
- "image/png",
2383
- "image/jpeg",
2384
- "image/webp",
2385
- "image/gif"
2386
- ], `${label} mediaType`);
2387
- countValue(attachment["bytes"], `${label} bytes`);
2388
- positiveIntegerValue(attachment["width"], `${label} width`);
2389
- positiveIntegerValue(attachment["height"], `${label} height`);
2390
- if (attachment["name"] !== void 0) stringValue(attachment["name"], `${label} name`);
2391
- if (attachment["originalDimensions"] !== void 0) {
2392
- const dimensions = exactRecord(attachment["originalDimensions"], `${label} originalDimensions`, ["width", "height"]);
2393
- positiveIntegerValue(dimensions["width"], `${label} original width`);
2394
- positiveIntegerValue(dimensions["height"], `${label} original height`);
2395
- }
2396
- }
2397
- function messageValue(value, label, version, expected) {
2398
- const message = exactRecord(value, label, [
2399
- "id",
2400
- "role",
2401
- "content",
2402
- "source"
2403
- ]);
2404
- nonEmptyString(message["id"], `${label} id`);
2405
- const role = expected === "assistant" ? "assistant" : expected === "user" || expected === "tool" ? "user" : void 0;
2406
- if (role === void 0) literalValue(message["role"], [
2407
- "system",
2408
- "user",
2409
- "assistant"
2410
- ], `${label} role`);
2411
- else literalValue(message["role"], [role], `${label} role`);
2412
- contentBlocksValue(message["content"], `${label} content`, version);
2413
- const source = releasedV0Record(message["source"], `${label} source`);
2414
- if (version < 2 && expected === "user" && source["kind"] === "goal" && source["change"] !== void 0) legacyGoalMessageValue(message, source, label);
2415
- else messageSourceValue(source, `${label} source`, version, expected);
2416
- if (expected === "tool") {
2417
- const content = message["content"];
2418
- const block = Array.isArray(content) && content.length === 1 ? releasedV0Record(content[0], `${label} tool result`) : void 0;
2419
- const source = releasedV0Record(message["source"], `${label} source`);
2420
- if (block?.["type"] !== "tool-result" || block["toolCallId"] !== source["callId"]) throw new SessionFormatError(`${label} must contain exactly one tool-result block`);
2421
- }
2422
- }
2423
- function legacyGoalMessageValue(message, source, label) {
2424
- assertReleasedV0Keys(source, [
2425
- "kind",
2426
- "goalId",
2427
- "revision",
2428
- "round",
2429
- "change"
2430
- ], [], `${label} source`);
2431
- nonEmptyString(source["goalId"], `${label} source goalId`);
2432
- positiveIntegerValue(source["revision"], `${label} source revision`);
2433
- if (source["round"] !== 0) throw new SessionFormatError(`${label} legacy goal source round must be 0`);
2434
- const change = releasedV0Record(source["change"], `${label} source change`);
2435
- goalChangeValue(change, `${label} source change`);
2436
- const ref = releasedV0Record(change["operation"] === "clear" ? change["cleared"] : change["goal"], `${label} source change ref`);
2437
- if (source["goalId"] !== ref["id"] || source["revision"] !== ref["revision"]) throw new SessionFormatError(`${label} legacy goal source does not match its change`);
2438
- const payload = change["operation"] === "clear" ? {
2439
- cleared: change["cleared"],
2440
- clearedAt: change["clearedAt"]
2441
- } : {
2442
- goal: change["goal"],
2443
- roundsStarted: change["roundsStarted"],
2444
- createdAt: change["createdAt"],
2445
- updatedAt: change["updatedAt"]
2446
- };
2447
- const expected = [{
2448
- type: "text",
2449
- text: `<goal_state>${JSON.stringify(payload)}</goal_state>`
2450
- }];
2451
- if (!deepEqualJson(message["content"], expected)) throw new SessionFormatError(`${label} legacy goal content does not match its change`);
2452
- }
2453
- function messageSourceValue(value, label, version, expected) {
2454
- const source = releasedV0Record(value, label);
2455
- if (expected === "assistant" && source["kind"] !== "model") throw new SessionFormatError(`${label} must be model source`);
2456
- if (expected === "tool" && source["kind"] !== "tool") throw new SessionFormatError(`${label} must be tool source`);
2457
- switch (source["kind"]) {
2458
- case "user":
2459
- assertReleasedV0Keys(source, ["kind"], ["rpcId", "clientTimeZone"], label);
2460
- if (source["rpcId"] !== void 0) nonEmptyString(source["rpcId"], `${label} rpcId`);
2461
- if (source["clientTimeZone"] !== void 0) nonEmptyString(source["clientTimeZone"], `${label} clientTimeZone`);
2462
- return;
2463
- case "plugin":
2464
- pluginSourceValue(source, label);
2465
- return;
2466
- case "model":
2467
- assertReleasedV0Keys(source, [
2468
- "kind",
2469
- "provider",
2470
- "model"
2471
- ], ["replayState"], label);
2472
- nonEmptyString(source["provider"], `${label} provider`);
2473
- nonEmptyString(source["model"], `${label} model`);
2474
- return;
2475
- case "tool":
2476
- assertReleasedV0Keys(source, ["kind", "callId"], [], label);
2477
- nonEmptyString(source["callId"], `${label} callId`);
2478
- return;
2479
- case "agent-instructions":
2480
- assertReleasedV0Keys(source, [
2481
- "kind",
2482
- "form",
2483
- "changes"
2484
- ], ["baseline", "baselineIdentity"], label);
2485
- literalValue(source["form"], ["instructions"], `${label} form`);
2486
- if (source["baseline"] !== void 0) literalValue(source["baseline"], [true], `${label} baseline`);
2487
- if (source["baselineIdentity"] !== void 0) nonEmptyString(source["baselineIdentity"], `${label} baselineIdentity`);
2488
- arrayValue(source["changes"], `${label} changes`, (member, memberLabel) => {
2489
- const change = exactRecord(member, memberLabel, [
2490
- "action",
2491
- "scope",
2492
- "path"
2493
- ], ["digest"]);
2494
- literalValue(change["action"], [
2495
- "set",
2496
- "replace",
2497
- "remove"
2498
- ], `${memberLabel} action`);
2499
- stringValue(change["scope"], `${memberLabel} scope`);
2500
- stringValue(change["path"], `${memberLabel} path`);
2501
- if (change["digest"] !== void 0) stringValue(change["digest"], `${memberLabel} digest`);
2502
- });
2503
- return;
2504
- case "session-reference":
2505
- sessionReferenceSourceValue(source, label, version);
2506
- return;
2507
- case "team-message":
2508
- assertReleasedV0Keys(source, [
2509
- "kind",
2510
- "teamId",
2511
- "messageId",
2512
- "senderId",
2513
- "senderName"
2514
- ], [], label);
2515
- for (const key of [
2516
- "teamId",
2517
- "messageId",
2518
- "senderId"
2519
- ]) nonEmptyString(source[key], `${label} ${key}`);
2520
- stringValue(source["senderName"], `${label} senderName`);
2521
- return;
2522
- case "goal":
2523
- assertReleasedV0Keys(source, [
2524
- "kind",
2525
- "goalId",
2526
- "revision",
2527
- "round"
2528
- ], [], label);
2529
- nonEmptyString(source["goalId"], `${label} goalId`);
2530
- positiveIntegerValue(source["revision"], `${label} revision`);
2531
- positiveIntegerValue(source["round"], `${label} round`);
2532
- return;
2533
- case "skill-invocation":
2534
- assertReleasedV0Keys(source, [
2535
- "kind",
2536
- "name",
2537
- "form"
2538
- ], [], label);
2539
- nonEmptyString(source["name"], `${label} name`);
2540
- literalValue(source["form"], ["instructions"], `${label} form`);
2541
- return;
2542
- case "skill-catalog":
2543
- assertReleasedV0Keys(source, [
2544
- "kind",
2545
- "form",
2546
- "entries"
2547
- ], ["update"], label);
2548
- literalValue(source["form"], ["catalog"], `${label} form`);
2549
- if (source["update"] !== void 0) literalValue(source["update"], [true], `${label} update`);
2550
- arrayValue(source["entries"], `${label} entries`, (member, memberLabel) => {
2551
- const entry = exactRecord(member, memberLabel, ["name", "description"]);
2552
- nonEmptyString(entry["name"], `${memberLabel} name`);
2553
- stringValue(entry["description"], `${memberLabel} description`);
2554
- });
2555
- return;
2556
- case "coordinator":
2557
- case "subagent-report":
2558
- assertReleasedV0Keys(source, [
2559
- "kind",
2560
- "form",
2561
- "senderSessionId"
2562
- ], [], label);
2563
- literalValue(source["form"], ["relay"], `${label} form`);
2564
- nonEmptyString(source["senderSessionId"], `${label} senderSessionId`);
2565
- return;
2566
- case "subagent-settled":
2567
- assertReleasedV0Keys(source, [
2568
- "kind",
2569
- "form",
2570
- "summary",
2571
- "senderSessionId"
2572
- ], [], label);
2573
- literalValue(source["form"], ["notice"], `${label} form`);
2574
- stringValue(source["summary"], `${label} summary`);
2575
- nonEmptyString(source["senderSessionId"], `${label} senderSessionId`);
2576
- return;
2577
- case "webhook":
2578
- assertReleasedV0Keys(source, [
2579
- "kind",
2580
- "provider",
2581
- "source",
2582
- "deliveryId",
2583
- "ruleId",
2584
- "form",
2585
- "summary"
2586
- ], [], label);
2587
- for (const key of [
2588
- "provider",
2589
- "source",
2590
- "deliveryId",
2591
- "ruleId"
2592
- ]) nonEmptyString(source[key], `${label} ${key}`);
2593
- literalValue(source["form"], ["notice"], `${label} form`);
2594
- stringValue(source["summary"], `${label} summary`);
2595
- return;
2596
- default:
2597
- nonEmptyString(source["kind"], `${label} kind`);
2598
- return;
2599
- }
2600
- }
2601
- function pluginSourceValue(source, label) {
2602
- const optional = [
2603
- "form",
2604
- "sections",
2605
- "summary"
2606
- ];
2607
- if (source["plugin"] === "compact") optional.push("compactionId", "sourceCommandId");
2608
- assertReleasedV0Keys(source, ["kind", "plugin"], optional, label);
2609
- nonEmptyString(source["plugin"], `${label} plugin`);
2610
- if (source["plugin"] === "compact") {
2611
- nonEmptyString(source["compactionId"], `${label} compactionId`);
2612
- if (source["sourceCommandId"] !== void 0) nonEmptyString(source["sourceCommandId"], `${label} sourceCommandId`);
2613
- }
2614
- const form = source["form"];
2615
- if (form === void 0) return;
2616
- literalValue(form, [
2617
- "instructions",
2618
- "catalog",
2619
- "snapshot",
2620
- "notice",
2621
- "relay",
2622
- "recall"
2623
- ], `${label} form`);
2624
- if (form === "snapshot") arrayValue(source["sections"], `${label} sections`, (member, memberLabel) => {
2625
- const section = exactRecord(member, memberLabel, ["name", "text"]);
2626
- nonEmptyString(section["name"], `${memberLabel} name`);
2627
- stringValue(section["text"], `${memberLabel} text`);
2628
- });
2629
- else if (source["sections"] !== void 0) throw new SessionFormatError(`${label} sections require snapshot form`);
2630
- if (form === "notice") stringValue(source["summary"], `${label} summary`);
2631
- else if (source["summary"] !== void 0) throw new SessionFormatError(`${label} summary requires notice form`);
2632
- }
2633
- function sessionReferenceSourceValue(source, label, version) {
2634
- assertReleasedV0Keys(source, [
2635
- "kind",
2636
- "form",
2637
- "version",
2638
- "references"
2639
- ], [], label);
2640
- literalValue(source["form"], ["recall"], `${label} form`);
2641
- literalValue(source["version"], [1], `${label} version`);
2642
- let expectedInputIndex = 0;
2643
- const sessionIds = /* @__PURE__ */ new Set();
2644
- if (arrayValue(source["references"], `${label} references`, (member, memberLabel) => {
2645
- const reference = exactRecord(member, memberLabel, [
2646
- "sessionId",
2647
- "label",
2648
- "capturedThroughSeq",
2649
- "compacted",
2650
- "originalMessages",
2651
- "retainedMessages",
2652
- "omittedMessages",
2653
- "omittedBytes",
2654
- "truncated",
2655
- "inputIndex"
2656
- ], version >= 1 ? ["capturedFormatVersion"] : []);
2657
- nonEmptyString(reference["sessionId"], `${memberLabel} sessionId`);
2658
- stringValue(reference["label"], `${memberLabel} label`);
2659
- if (reference["capturedThroughSeq"] !== null) countValue(reference["capturedThroughSeq"], `${memberLabel} capturedThroughSeq`);
2660
- if (reference["capturedFormatVersion"] !== void 0) {
2661
- const capturedVersion = countValue(reference["capturedFormatVersion"], `${memberLabel} capturedFormatVersion`);
2662
- if (capturedVersion < 1 || capturedVersion > version) throw new SessionFormatError(`${memberLabel} capturedFormatVersion must be between 1 and ${version}`);
2663
- }
2664
- booleanValue(reference["compacted"], `${memberLabel} compacted`);
2665
- const original = countValue(reference["originalMessages"], `${memberLabel} originalMessages`);
2666
- const retained = countValue(reference["retainedMessages"], `${memberLabel} retainedMessages`);
2667
- const omitted = countValue(reference["omittedMessages"], `${memberLabel} omittedMessages`);
2668
- const omittedBytes = countValue(reference["omittedBytes"], `${memberLabel} omittedBytes`);
2669
- const inputIndex = countValue(reference["inputIndex"], `${memberLabel} inputIndex`);
2670
- const truncated = reference["truncated"];
2671
- booleanValue(truncated, `${memberLabel} truncated`);
2672
- if (retained > original || omitted !== original - retained) throw new SessionFormatError(`${memberLabel} message counts are inconsistent`);
2673
- if (truncated !== (omitted > 0 || omittedBytes > 0)) throw new SessionFormatError(`${memberLabel} truncated disagrees with omitted content`);
2674
- if (inputIndex !== expectedInputIndex) throw new SessionFormatError(`${label} inputIndex must match reference position`);
2675
- expectedInputIndex += 1;
2676
- const sessionId = reference["sessionId"];
2677
- if (sessionIds.has(sessionId)) throw new SessionFormatError(`${label} repeats sessionId ${sessionId}`);
2678
- sessionIds.add(sessionId);
2679
- }).length === 0) throw new SessionFormatError(`${label} references must be non-empty`);
2680
- }
2681
- function streamChunkValue(value, label) {
2682
- const chunk = releasedV0Record(value, label);
2683
- switch (chunk["type"]) {
2684
- case "block-start":
2685
- assertReleasedV0Keys(chunk, [
2686
- "type",
2687
- "index",
2688
- "blockType"
2689
- ], [], label);
2690
- countValue(chunk["index"], `${label} index`);
2691
- nonEmptyString(chunk["blockType"], `${label} blockType`);
2692
- return;
2693
- case "text-delta":
2694
- case "reasoning-delta":
2695
- assertReleasedV0Keys(chunk, [
2696
- "type",
2697
- "index",
2698
- "text"
2699
- ], [], label);
2700
- countValue(chunk["index"], `${label} index`);
2701
- stringValue(chunk["text"], `${label} text`);
2702
- return;
2703
- case "tool-call-delta":
2704
- assertReleasedV0Keys(chunk, [
2705
- "type",
2706
- "index",
2707
- "id",
2708
- "argumentsDelta"
2709
- ], ["name"], label);
2710
- countValue(chunk["index"], `${label} index`);
2711
- nonEmptyString(chunk["id"], `${label} id`);
2712
- if (chunk["name"] !== void 0) stringValue(chunk["name"], `${label} name`);
2713
- stringValue(chunk["argumentsDelta"], `${label} argumentsDelta`);
2714
- return;
2715
- case "block-end":
2716
- assertReleasedV0Keys(chunk, [
2717
- "type",
2718
- "index",
2719
- "block"
2720
- ], [], label);
2721
- countValue(chunk["index"], `${label} index`);
2722
- contentBlockValue(chunk["block"], `${label} block`, 1);
2723
- return;
2724
- case "usage":
2725
- assertReleasedV0Keys(chunk, ["type", "usage"], [], label);
2726
- tokenUsageValue(chunk["usage"], `${label} usage`);
2727
- return;
2728
- case "finish":
2729
- assertReleasedV0Keys(chunk, ["type", "reason"], ["replayState"], label);
2730
- finishReasonValue(chunk["reason"], `${label} reason`);
2731
- if (chunk["replayState"] !== void 0) replayEnvelopeValue(chunk["replayState"], `${label} replayState`);
2732
- return;
2733
- default: throw new SessionFormatError(`${label} has unknown stream chunk type ${JSON.stringify(chunk["type"])}`);
2734
- }
2735
- }
2736
- function finishReasonValue(value, label) {
2737
- const reason = releasedV0Record(value, label);
2738
- if (reason["kind"] === "aborted" || reason["kind"] === "error") {
2739
- assertReleasedV0Keys(reason, ["kind", "failure"], [], label);
2740
- llmFailureValue(reason["failure"], `${label} failure`);
2741
- return;
2742
- }
2743
- if (reason["kind"] === "stop" || reason["kind"] === "tool-calls" || reason["kind"] === "max-tokens") assertReleasedV0Keys(reason, ["kind"], [], label);
2744
- nonEmptyString(reason["kind"], `${label} kind`);
2745
- }
2746
- function replayEnvelopeValue(value, label) {
2747
- const replay = exactRecord(value, label, ["response"], ["blocks"]);
2748
- if (replay["blocks"] !== void 0 && !Array.isArray(replay["blocks"])) throw new SessionFormatError(`${label} blocks must be an array`);
2749
- }
2750
- function turnEndReasonValue(value, label) {
2751
- const reason = releasedV0Record(value, label);
2752
- switch (reason["kind"]) {
2753
- case "completed":
2754
- case "blocked":
2755
- case "max-tokens":
2756
- case "interrupted":
2757
- assertReleasedV0Keys(reason, ["kind"], [], label);
2758
- return;
2759
- case "aborted": {
2760
- assertReleasedV0Keys(reason, ["kind", "reason"], [], label);
2761
- const cause = releasedV0Record(reason["reason"], `${label} abort cause`);
2762
- if (cause["kind"] === "hook") {
2763
- assertReleasedV0Keys(cause, ["kind", "reason"], [], `${label} abort cause`);
2764
- stringValue(cause["reason"], `${label} abort reason`);
2765
- } else {
2766
- assertReleasedV0Keys(cause, ["kind"], [], `${label} abort cause`);
2767
- literalValue(cause["kind"], [
2768
- "user",
2769
- "parent",
2770
- "disposed",
2771
- "legacy"
2772
- ], `${label} abort kind`);
2773
- }
2774
- return;
2775
- }
2776
- case "error":
2777
- assertReleasedV0Keys(reason, ["kind", "error"], [], label);
2778
- llmFailureValue(reason["error"], `${label} error`);
2779
- return;
2780
- default:
2781
- nonEmptyString(reason["kind"], `${label} kind`);
2782
- return;
2783
- }
2784
- }
2785
- function requestHeaderValue(value, label) {
2786
- const header = exactRecord(value, label, ["config"], [
2787
- "adapterDefaults",
2788
- "system",
2789
- "tools"
2790
- ]);
2791
- const config = exactRecord(header["config"], `${label} config`, ["provider", "model"], [
2792
- "reasoningEffort",
2793
- "temperature",
2794
- "maxTokens",
2795
- "stop"
2796
- ]);
2797
- nonEmptyString(config["provider"], `${label} provider`);
2798
- nonEmptyString(config["model"], `${label} model`);
2799
- if (config["reasoningEffort"] !== void 0) nonEmptyString(config["reasoningEffort"], `${label} reasoningEffort`);
2800
- if (config["temperature"] !== void 0) finiteNumberValue(config["temperature"], `${label} temperature`);
2801
- if (config["maxTokens"] !== void 0) positiveIntegerValue(config["maxTokens"], `${label} maxTokens`);
2802
- if (config["stop"] !== void 0) arrayValue(config["stop"], `${label} stop`, stringValue);
2803
- if (header["adapterDefaults"] !== void 0) {
2804
- const defaults = exactRecord(header["adapterDefaults"], `${label} adapterDefaults`, [], ["reasoningEffort", "maxTokens"]);
2805
- for (const [key, marker] of Object.entries(defaults)) {
2806
- literalValue(marker, [true], `${label} adapterDefaults ${key}`);
2807
- if (!Object.hasOwn(config, key)) throw new SessionFormatError(`${label} adapter default ${key} lacks config value`);
2808
- }
2809
- }
2810
- if (header["system"] !== void 0) stringValue(header["system"], `${label} system`);
2811
- if (header["tools"] !== void 0) arrayValue(header["tools"], `${label} tools`, toolSchemaValue);
2812
- }
2813
- function toolSchemaValue(value, label) {
2814
- const schema = exactRecord(value, label, [
2815
- "name",
2816
- "description",
2817
- "parameters"
2818
- ]);
2819
- nonEmptyString(schema["name"], `${label} name`);
2820
- stringValue(schema["description"], `${label} description`);
2821
- releasedV0Record(schema["parameters"], `${label} parameters`);
2822
- }
2823
- function shadowedValue(data, eventSeq, label) {
2824
- const range = exactRecord(data["shadowedRange"], `${label} shadowedRange`, ["start", "end"]);
2825
- const start = earlierSeq(range["start"], eventSeq, `${label} shadowedRange start`);
2826
- const end = earlierSeq(range["end"], eventSeq, `${label} shadowedRange end`);
2827
- const seqs = seqArray(data["shadowedSeqs"], eventSeq, `${label} shadowedSeqs`, true);
2828
- if (seqs[0] !== start || seqs.at(-1) !== end) throw new SessionFormatError(`${label} shadowedRange must match shadowedSeqs endpoints`);
2829
- countValue(data["shadowedTokenCount"], `${label} shadowedTokenCount`);
2830
- }
2831
- function goalChangeValue(data, label) {
2832
- literalValue(data["kind"], ["goal/change"], `${label} kind`);
2833
- literalValue(data["version"], [1], `${label} version`);
2834
- if (data["operation"] === "clear") {
2835
- assertReleasedV0Keys(data, [
2836
- "kind",
2837
- "version",
2838
- "operation",
2839
- "cleared",
2840
- "clearedAt"
2841
- ], [], `${label} data`);
2842
- goalRefValue(data["cleared"], `${label} cleared`);
2843
- countValue(data["clearedAt"], `${label} clearedAt`);
2844
- return;
2845
- }
2846
- assertReleasedV0Keys(data, [
2847
- "kind",
2848
- "version",
2849
- "operation",
2850
- "goal",
2851
- "roundsStarted",
2852
- "createdAt",
2853
- "updatedAt"
2854
- ], [], `${label} data`);
2855
- literalValue(data["operation"], [
2856
- "create",
2857
- "edit",
2858
- "pause",
2859
- "resume",
2860
- "complete",
2861
- "block"
2862
- ], `${label} operation`);
2863
- goalSnapshotValue(data["goal"], `${label} goal`);
2864
- countValue(data["roundsStarted"], `${label} roundsStarted`);
2865
- countValue(data["createdAt"], `${label} createdAt`);
2866
- countValue(data["updatedAt"], `${label} updatedAt`);
2867
- }
2868
- function goalRefValue(value, label) {
2869
- const ref = exactRecord(value, label, ["id", "revision"]);
2870
- nonEmptyString(ref["id"], `${label} id`);
2871
- positiveIntegerValue(ref["revision"], `${label} revision`);
2872
- }
2873
- function goalSnapshotValue(value, label) {
2874
- const goal = exactRecord(value, label, [
2875
- "id",
2876
- "revision",
2877
- "objective",
2878
- "phase",
2879
- "maxGoalRounds"
2880
- ], ["blockedReason"]);
2881
- nonEmptyString(goal["id"], `${label} id`);
2882
- positiveIntegerValue(goal["revision"], `${label} revision`);
2883
- nonEmptyString(goal["objective"], `${label} objective`);
2884
- literalValue(goal["phase"], [
2885
- "active",
2886
- "paused",
2887
- "blocked",
2888
- "complete"
2889
- ], `${label} phase`);
2890
- positiveIntegerValue(goal["maxGoalRounds"], `${label} maxGoalRounds`);
2891
- if (goal["phase"] === "blocked") {
2892
- const reason = exactRecord(goal["blockedReason"], `${label} blockedReason`, ["code", "message"]);
2893
- nonEmptyString(reason["code"], `${label} blocked code`);
2894
- nonEmptyString(reason["message"], `${label} blocked message`);
2895
- } else if (goal["blockedReason"] !== void 0) throw new SessionFormatError(`${label} blockedReason requires blocked phase`);
2896
- }
2897
- function scheduleChangeValue(data, label) {
2898
- literalValue(data["version"], [1], `${label} version`);
2899
- if (data["operation"] === "create") {
2900
- assertReleasedV0Keys(data, [
2901
- "version",
2902
- "operation",
2903
- "schedule"
2904
- ], [], `${label} data`);
2905
- scheduleRecordValue(data["schedule"], `${label} schedule`);
2906
- return;
2907
- }
2908
- assertReleasedV0Keys(data, [
2909
- "version",
2910
- "operation",
2911
- "id"
2912
- ], data["operation"] === "dispatch" ? ["acceptedAt"] : [], `${label} data`);
2913
- literalValue(data["operation"], ["delete", "dispatch"], `${label} operation`);
2914
- scheduleIdValue(data["id"], `${label} id`);
2915
- if (data["acceptedAt"] !== void 0) instantValue(data["acceptedAt"], `${label} acceptedAt`);
2916
- }
2917
- function scheduleRecordValue(value, label) {
2918
- const record = releasedV0Record(value, label);
2919
- if (record["kind"] === "after") {
2920
- assertReleasedV0Keys(record, [
2921
- "id",
2922
- "kind",
2923
- "prompt",
2924
- "afterSeconds",
2925
- "scheduledAt"
2926
- ], [], label);
2927
- positiveIntegerValue(record["afterSeconds"], `${label} afterSeconds`);
2928
- } else if (record["kind"] === "at") assertReleasedV0Keys(record, [
2929
- "id",
2930
- "kind",
2931
- "prompt",
2932
- "scheduledAt"
2933
- ], [], label);
2934
- else if (record["kind"] === "every") {
2935
- assertReleasedV0Keys(record, [
2936
- "id",
2937
- "kind",
2938
- "prompt",
2939
- "everySeconds",
2940
- "scheduledAt"
2941
- ], [], label);
2942
- if (positiveIntegerValue(record["everySeconds"], `${label} everySeconds`) < 300) throw new SessionFormatError(`${label} everySeconds must be at least 300`);
2943
- } else throw new SessionFormatError(`${label} has unknown schedule kind`);
2944
- scheduleIdValue(record["id"], `${label} id`);
2945
- nonEmptyString(record["prompt"], `${label} prompt`);
2946
- instantValue(record["scheduledAt"], `${label} scheduledAt`);
2947
- }
2948
- function scheduleIdValue(value, label) {
2949
- nonEmptyString(value, label);
2950
- if (value.trim() !== value) throw new SessionFormatError(`${label} must not have surrounding whitespace`);
2951
- }
2952
- function instantValue(value, label) {
2953
- if (typeof value !== "string" || !/^(?!0000)\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d\.\d{3}Z$/u.test(value) || !Number.isFinite(Date.parse(value)) || new Date(Date.parse(value)).toISOString() !== value) throw new SessionFormatError(`${label} must be a canonical UTC instant`);
2954
- }
2955
- function titleSourceValue(value, label) {
2956
- const source = releasedV0Record(value, label);
2957
- if (source["kind"] === "provider") {
2958
- assertReleasedV0Keys(source, ["kind", "provider"], ["model"], label);
2959
- nonEmptyString(source["provider"], `${label} provider`);
2960
- if (source["model"] !== void 0) modelRouteValue(source["model"], `${label} model`);
2961
- return;
2962
- }
2963
- assertReleasedV0Keys(source, ["kind"], [], label);
2964
- literalValue(source["kind"], ["fallback", "user"], `${label} kind`);
2965
- }
2966
- function modelRouteValue(value, label) {
2967
- const route = exactRecord(value, label, ["provider", "model"]);
2968
- nonEmptyString(route["provider"], `${label} provider`);
2969
- nonEmptyString(route["model"], `${label} model`);
2970
- }
2971
- function subagentDescriptorValue(data, label) {
2972
- literalValue(data["version"], [3], `${label} version`);
2973
- nonEmptyString(data["provider"], `${label} provider`);
2974
- if (data["mode"] === "one-shot") {
2975
- assertReleasedV0Keys(data, [
2976
- "mode",
2977
- "version",
2978
- "provider"
2979
- ], ["label"], `${label} data`);
2980
- if (data["label"] !== void 0) stringValue(data["label"], `${label} label`);
2981
- return;
2982
- }
2983
- literalValue(data["mode"], ["continuable"], `${label} mode`);
2984
- nonEmptyString(data["label"], `${label} label`);
2985
- for (const key of [
2986
- "agentProvider",
2987
- "agentModel",
2988
- "agentReasoningEffort",
2989
- "persona"
2990
- ]) if (data[key] !== void 0) nonEmptyString(data[key], `${label} ${key}`);
2991
- if (data["agentProvider"] === void 0 !== (data["agentModel"] === void 0)) throw new SessionFormatError(`${label} agentProvider and agentModel must be paired`);
2992
- if (data["toolFilter"] !== void 0) {
2993
- const filter = exactRecord(data["toolFilter"], `${label} toolFilter`, [], ["allow", "deny"]);
2994
- if (filter["allow"] === void 0 && filter["deny"] === void 0) throw new SessionFormatError(`${label} toolFilter requires allow or deny`);
2995
- if (filter["allow"] !== void 0) arrayValue(filter["allow"], `${label} allow`, nonEmptyString);
2996
- if (filter["deny"] !== void 0) arrayValue(filter["deny"], `${label} deny`, nonEmptyString);
2997
- }
2998
- }
2999
- function allowedModelsValue(value, label) {
3000
- const seen = /* @__PURE__ */ new Set();
3001
- if (arrayValue(value, label, (member, memberLabel) => {
3002
- const route = exactRecord(member, memberLabel, ["provider", "model"]);
3003
- nonEmptyString(route["provider"], `${memberLabel} provider`);
3004
- nonEmptyString(route["model"], `${memberLabel} model`);
3005
- const key = `${route["provider"]}\0${route["model"]}`;
3006
- if (seen.has(key)) throw new SessionFormatError(`${label} repeats route ${key}`);
3007
- seen.add(key);
3008
- }).length === 0) throw new SessionFormatError(`${label} must be non-empty`);
3009
- }
3010
- function teamSelector(data, label) {
3011
- literalValue(data["version"], [1], `${label} version`);
3012
- nonEmptyString(data["teamId"], `${label} teamId`);
3013
- }
3014
- function teamMemberValue(value, label) {
3015
- const member = exactRecord(value, label, [
3016
- "id",
3017
- "name",
3018
- "description",
3019
- "provider",
3020
- "context",
3021
- "phase"
3022
- ], ["error"]);
3023
- nonEmptyString(member["id"], `${label} id`);
3024
- stringValue(member["name"], `${label} name`);
3025
- stringValue(member["description"], `${label} description`);
3026
- stringValue(member["provider"], `${label} provider`);
3027
- literalValue(member["context"], ["fresh", "fork"], `${label} context`);
3028
- literalValue(member["phase"], [
3029
- "provisioning",
3030
- "active",
3031
- "failed"
3032
- ], `${label} phase`);
3033
- if (member["error"] !== void 0) stringValue(member["error"], `${label} error`);
3034
- }
3035
- function teamTaskValue(value, label) {
3036
- const task = exactRecord(value, label, [
3037
- "id",
3038
- "revision",
3039
- "subject",
3040
- "description",
3041
- "status",
3042
- "blockedBy",
3043
- "writeScopes"
3044
- ], ["ownerId"]);
3045
- nonEmptyString(task["id"], `${label} id`);
3046
- positiveIntegerValue(task["revision"], `${label} revision`);
3047
- stringValue(task["subject"], `${label} subject`);
3048
- stringValue(task["description"], `${label} description`);
3049
- literalValue(task["status"], [
3050
- "pending",
3051
- "in_progress",
3052
- "completed",
3053
- "deleted"
3054
- ], `${label} status`);
3055
- if (task["ownerId"] !== void 0) nonEmptyString(task["ownerId"], `${label} ownerId`);
3056
- arrayValue(task["blockedBy"], `${label} blockedBy`, nonEmptyString);
3057
- arrayValue(task["writeScopes"], `${label} writeScopes`, stringValue);
3058
- }
3059
- function teamMessageValue(value, label, version) {
3060
- const message = exactRecord(value, label, [
3061
- "id",
3062
- "senderId",
3063
- "senderName",
3064
- "targetId",
3065
- "delivery",
3066
- "content"
3067
- ]);
3068
- for (const key of [
3069
- "id",
3070
- "senderId",
3071
- "targetId"
3072
- ]) nonEmptyString(message[key], `${label} ${key}`);
3073
- stringValue(message["senderName"], `${label} senderName`);
3074
- literalValue(message["delivery"], ["quiet", "wakeup"], `${label} delivery`);
3075
- contentBlocksValue(message["content"], `${label} content`, version);
3076
- }
3077
- function workflowIdentity(data, label) {
3078
- nonEmptyString(data["runId"], `${label} runId`);
3079
- positiveIntegerValue(data["seq"], `${label} seq`);
3080
- }
3081
- function deepSeekSearchBodyValue(value, label) {
3082
- const body = exactRecord(value, label, [
3083
- "model",
3084
- "max_tokens",
3085
- "messages",
3086
- "tools"
3087
- ]);
3088
- nonEmptyString(body["model"], `${label} model`);
3089
- positiveIntegerValue(body["max_tokens"], `${label} max_tokens`);
3090
- if (arrayValue(body["messages"], `${label} messages`, (member, memberLabel) => {
3091
- const message = exactRecord(member, memberLabel, ["role", "content"]);
3092
- literalValue(message["role"], ["user"], `${memberLabel} role`);
3093
- if (arrayValue(message["content"], `${memberLabel} content`, (block, blockLabel) => {
3094
- const text = exactRecord(block, blockLabel, ["type", "text"]);
3095
- literalValue(text["type"], ["text"], `${blockLabel} type`);
3096
- stringValue(text["text"], `${blockLabel} text`);
3097
- }).length !== 1) throw new SessionFormatError(`${memberLabel} content must contain one text block`);
3098
- }).length !== 1) throw new SessionFormatError(`${label} messages must contain one user message`);
3099
- if (arrayValue(body["tools"], `${label} tools`, (member, memberLabel) => {
3100
- const tool = exactRecord(member, memberLabel, [
3101
- "type",
3102
- "name",
3103
- "max_uses"
3104
- ]);
3105
- literalValue(tool["type"], ["web_search_20250305"], `${memberLabel} type`);
3106
- literalValue(tool["name"], ["web_search"], `${memberLabel} name`);
3107
- positiveIntegerValue(tool["max_uses"], `${memberLabel} max_uses`);
3108
- }).length !== 1) throw new SessionFormatError(`${label} tools must contain one web search tool`);
3109
- }
3110
- //#endregion
3111
- //#region ../session-format-v0-to-v1/src/validation.ts
3112
- const HEADER_REQUIRED$2 = [
3113
- "version",
3114
- "id",
3115
- "createdAt",
3116
- "isSeeded",
3117
- "delegationDepth"
3118
- ];
3119
- const HEADER_OPTIONAL$2 = [
3120
- "cwd",
3121
- "parentSession",
3122
- "origin",
3123
- "agentPreset"
3124
- ];
3125
- /**
3126
- * Validate the logical header shared by released v0 and v1.
3127
- * @param header - detached logical header.
3128
- * @param version - exact expected generation.
3129
- */
3130
- function assertReleasedSessionFormatHeader(header, version) {
3131
- const record = releasedV0Record(header, `format v${version} header`);
3132
- assertReleasedV0Keys(record, HEADER_REQUIRED$2, HEADER_OPTIONAL$2, `format v${version} header`);
3133
- if (record["version"] !== version) throw new SessionFormatError(`expected format v${version} header`);
3134
- if (typeof record["id"] !== "string") throw new SessionFormatError(`format v${version} header id must be a string`);
3135
- sessionFormatCount(record["createdAt"], `format v${version} header createdAt`);
3136
- if (typeof record["isSeeded"] !== "boolean") throw new SessionFormatError(`format v${version} header isSeeded must be a boolean`);
3137
- sessionFormatCount(record["delegationDepth"], `format v${version} header delegationDepth`);
3138
- for (const key of [
3139
- "cwd",
3140
- "parentSession",
3141
- "agentPreset"
3142
- ]) if (record[key] !== void 0 && typeof record[key] !== "string") throw new SessionFormatError(`format v${version} header ${key} must be a string`);
3143
- if (typeof record["cwd"] === "string" && !isAbsolute(record["cwd"])) throw new SessionFormatError(`format v${version} header cwd must be absolute`);
3144
- if (record["origin"] !== void 0 && record["origin"] !== "subagent") throw new SessionFormatError(`format v${version} header origin must be "subagent"`);
3145
- }
3146
- /**
3147
- * Validate one released-v1 logical header.
3148
- * @param header - detached logical header.
3149
- */
3150
- function assertReleasedV1Header(header) {
3151
- assertReleasedSessionFormatHeader(header, 1);
3152
- }
3153
- /**
3154
- * Validate shared-layout surface references for one released generation.
3155
- * @param record - exact event envelope.
3156
- * @param seq - event position used for earlier-reference checks.
3157
- * @param type - surface event type used in diagnostics.
3158
- * @param assistantSources - whether this generation admits empty Assistant chunk references.
3159
- */
3160
- function assertReleasedSurfaceMetadata(record, seq, type, assistantSources) {
3161
- const sources = record["sourceEventSeqs"];
3162
- if (type === "assistant/message" && sources !== void 0 && assistantSources === "forbid-assistant") throw new SessionFormatError(`assistant/message ${seq} retains obsolete chunk references`);
3163
- if (sources !== void 0) {
3164
- if (!Array.isArray(sources)) throw new SessionFormatError(`${type} ${seq} sourceEventSeqs must be an array`);
3165
- const seen = /* @__PURE__ */ new Set();
3166
- for (const source of sources) {
3167
- const current = sessionFormatCount(source, `${type} ${seq} sourceEventSeqs member`);
3168
- if (current >= seq || seen.has(current)) throw new SessionFormatError(`${type} ${seq} sourceEventSeqs must be unique earlier seqs`);
3169
- seen.add(current);
3170
- }
3171
- if (sources.length === 0 && type !== "assistant/message") throw new SessionFormatError(`${type} ${seq} sourceEventSeqs must be non-empty`);
3172
- }
3173
- const operation = record["surfaceOp"];
3174
- if (operation === void 0 || operation === "append") return;
3175
- const replacement = releasedV0Record(operation, `${type} ${seq} surfaceOp`);
3176
- assertReleasedV0Keys(replacement, [
3177
- "op",
3178
- "start",
3179
- "end"
3180
- ], [], `${type} ${seq} surfaceOp`);
3181
- if (replacement["op"] !== "replace") throw new SessionFormatError(`${type} ${seq} surfaceOp must replace`);
3182
- const start = sessionFormatCount(replacement["start"], `${type} ${seq} surface start`);
3183
- const end = sessionFormatCount(replacement["end"], `${type} ${seq} surface end`);
3184
- if (start >= seq || end >= seq) throw new SessionFormatError(`${type} ${seq} has an invalid surface replacement`);
3185
- }
3186
- /**
3187
- * Validate one exact known payload after legacy normalization.
3188
- * @param event - known event to validate.
3189
- * @param version - payload generation controlling versioned members.
3190
- */
3191
- function assertReleasedEventPayload(event, version) {
3192
- const disposition = RELEASED_V0_EVENT_DISPOSITIONS[event.type];
3193
- /* v8 ignore next -- artifact coordinate validation admits only the frozen inventory before payload validation. */
3194
- if (disposition === void 0) throw new SessionFormatUnsupportedMigrationError$1(`format v0 contains unknown historical event type ${JSON.stringify(event.type)} at seq ${event.seq}; migration refuses unknown historical events even when ignorable`);
3195
- const data = releasedV0Record(event.data, `${event.type} ${event.seq} data`);
3196
- if (event.type === "subagent/descriptor" && data["version"] !== 3) {
3197
- const descriptorVersion = sessionFormatCount(data["version"], `${event.type} ${event.seq} version`);
3198
- if (version === 0) throw new SessionFormatUnsupportedMigrationError$1(`${event.type} ${event.seq} uses unsupported descriptor version ${descriptorVersion}`);
3199
- return;
3200
- }
3201
- const versionOptional = version === 1 && event.type === "session-log-deepseek/delivery-accepted" ? [...disposition.optional, "sessionFormatVersion"] : disposition.optional;
3202
- assertReleasedV0Keys(data, disposition.required, versionOptional, `${event.type} ${event.seq} data`);
3203
- for (const key of disposition.opaque) if (Object.hasOwn(data, key) && !isJsonValue(data[key])) throw new SessionFormatError(`${event.type} ${event.seq} opaque ${key} is not lossless JSON`);
3204
- assertReleasedPayloadSemantics(event, version);
3205
- }
3206
- //#endregion
3207
- //#region ../session-format-v0-to-v1/src/codec.ts
3208
- const PHYSICAL_HEADER_REQUIRED = [
3209
- "type",
3210
- "version",
3211
- "id",
3212
- "createdAt",
3213
- "delegationDepth"
3214
- ];
3215
- const PHYSICAL_HEADER_OPTIONAL = [
3216
- "cwd",
3217
- "parentSession",
3218
- "seedLength",
3219
- "origin",
3220
- "agentPreset"
3221
- ];
3222
- const PACKED_TAGS = new Set([
3223
- "text-chunks",
3224
- "reasoning-chunks",
3225
- "tool-call-chunks"
3226
- ]);
3227
- /**
3228
- * Test whether a compact migration item is a released packed Assistant row.
3229
- * @param run - compact migration item to classify.
3230
- * @returns whether the item carries the released Assistant chunk representation.
3231
- */
3232
- function isReleasedAssistantChunkRun(run) {
3233
- return run.runType === "released-assistant-chunks";
3234
- }
3235
- createReleasedCodec(0);
3236
- createReleasedCodec(1);
3237
- function createReleasedCodec(version) {
3238
- return Object.freeze({
3239
- version,
3240
- decodeHeader: (value) => decodeHeader(value, version),
3241
- createDecoder(headerValue, recovery) {
3242
- const physical = decodePhysicalHeader$1(headerValue, version);
3243
- const scanner = scanRows(recovery === "recoverable");
3244
- return {
3245
- header: physical.header,
3246
- headerInheritedEventCount: physical.inheritedEventCount,
3247
- decodeRow: (rowValue, context) => {
3248
- scanner.decodeRow(rowValue, context);
3249
- },
3250
- finish(_context) {
3251
- scanner.finish(physical.inheritedEventCount);
3252
- return physical.inheritedEventCount;
3253
- }
3254
- };
3255
- }
3256
- });
3257
- }
3258
- function scanRows(recoverable) {
3259
- let rowIndex = 0;
3260
- let eventCount = 0;
3261
- let issue;
3262
- return {
3263
- decodeRow(rowValue, context) {
3264
- const currentRow = rowIndex;
3265
- rowIndex += 1;
3266
- let packed = false;
3267
- let decoded;
3268
- try {
3269
- const record = releasedV0Record(rowValue, `released Session row ${currentRow}`);
3270
- const type = record["type"];
3271
- if (typeof type === "string" && PACKED_TAGS.has(type)) {
3272
- packed = true;
3273
- decoded = decodePackedRun(record, type, currentRow);
3274
- } else decoded = decodeEvent$1(record, currentRow);
3275
- } catch (error) {
3276
- const current = error instanceof SessionFormatError ? error : new SessionFormatError(`released Session row ${currentRow} is malformed`, { cause: error });
3277
- if (!recoverable) throw current;
3278
- issue ??= current;
3279
- return;
3280
- }
3281
- if (issue !== void 0) {
3282
- if (!packed && decoded.type === "turn/end") throw issue;
3283
- return;
3284
- }
3285
- const seq = packed ? decoded.firstSeq : decoded.seq;
3286
- if (seq !== eventCount) {
3287
- const gap = new SessionFormatError(`released Session row ${currentRow} has seq gap (expected ${eventCount}, got ${seq})`);
3288
- if (!recoverable) throw gap;
3289
- issue = gap;
3290
- if (!packed && decoded.type === "turn/end") throw gap;
3291
- return;
3292
- }
3293
- if (packed) {
3294
- const run = decoded;
3295
- eventCount += run.eventCount;
3296
- context.emitRun(run);
3297
- } else {
3298
- eventCount += 1;
3299
- context.emitEvent(decoded);
3300
- }
3301
- },
3302
- finish(inheritedEventCount) {
3303
- if (inheritedEventCount > eventCount) throw new SessionFormatError("Session inheritedEventCount exceeds its event count");
3304
- }
3305
- };
3306
- }
3307
- function decodeHeader(value, version) {
3308
- return decodePhysicalHeader$1(value, version).header;
3309
- }
3310
- function decodePhysicalHeader$1(value, version) {
3311
- const record = releasedV0Record(snapshotSessionFormatJson(value, `released v${version} physical header`), `released v${version} physical header`);
3312
- assertReleasedV0Keys(record, PHYSICAL_HEADER_REQUIRED, PHYSICAL_HEADER_OPTIONAL, `released v${version} physical header`);
3313
- if (record["type"] !== "session" || record["version"] !== version) throw new SessionFormatError(`expected released v${version} physical Session header`);
3314
- if (typeof record["id"] !== "string") throw new SessionFormatError(`released v${version} header id must be a string`);
3315
- const createdAt = sessionFormatCount(record["createdAt"], `released v${version} header createdAt`);
3316
- const delegationDepth = sessionFormatCount(record["delegationDepth"], `released v${version} header delegationDepth`);
3317
- const seedLength = record["seedLength"] === void 0 ? 0 : sessionFormatCount(record["seedLength"], `released v${version} header seedLength`);
3318
- for (const key of [
3319
- "cwd",
3320
- "parentSession",
3321
- "agentPreset"
3322
- ]) if (record[key] !== void 0 && typeof record[key] !== "string") throw new SessionFormatError(`released v${version} header ${key} must be a string`);
3323
- if (record["origin"] !== void 0 && record["origin"] !== "subagent") throw new SessionFormatError(`released v${version} header origin must be "subagent"`);
3324
- const header = {
3325
- version,
3326
- id: record["id"],
3327
- createdAt,
3328
- ...record["cwd"] === void 0 ? {} : { cwd: record["cwd"] },
3329
- ...record["parentSession"] === void 0 ? {} : { parentSession: record["parentSession"] },
3330
- isSeeded: record["seedLength"] !== void 0,
3331
- ...record["origin"] === void 0 ? {} : { origin: record["origin"] },
3332
- delegationDepth,
3333
- ...record["agentPreset"] === void 0 ? {} : { agentPreset: record["agentPreset"] }
3334
- };
3335
- assertReleasedSessionFormatHeader(header, version);
3336
- return {
3337
- header,
3338
- inheritedEventCount: seedLength
3339
- };
3340
- }
3341
- function decodeEvent$1(record, rowIndex) {
3342
- if (record["sourceEventSeqs"] !== void 0) {
3343
- const seq = sessionFormatCount(record["seq"], `released Session row ${rowIndex} seq`);
3344
- return {
3345
- ...record,
3346
- sourceEventSeqs: decodeSeqRanges$2(record["sourceEventSeqs"], seq)
3347
- };
3348
- }
3349
- return record;
3350
- }
3351
- function decodePackedRun(row, type, rowIndex) {
3352
- const label = `released ${type} row ${rowIndex}`;
3353
- assertReleasedV0Keys(row, [
3354
- "type",
3355
- "seq0",
3356
- "time0",
3357
- "data"
3358
- ], [], label);
3359
- const seq0 = sessionFormatCount(row["seq0"], `${label} seq0`);
3360
- const time0 = sessionFormatSafeInteger(row["time0"], `${label} time0`);
3361
- const data = releasedV0Record(row["data"], `${label} data`);
3362
- const isTool = type === "tool-call-chunks";
3363
- assertReleasedV0Keys(data, isTool ? [
3364
- "turn",
3365
- "step",
3366
- "index",
3367
- "id",
3368
- "dt",
3369
- "args"
3370
- ] : [
3371
- "turn",
3372
- "step",
3373
- "index",
3374
- "dt",
3375
- "texts"
3376
- ], isTool ? ["name"] : [], `${label} data`);
3377
- const payload = data[isTool ? "args" : "texts"];
3378
- if (!Array.isArray(payload) || payload.length === 0 || payload.some((member) => typeof member !== "string")) throw new SessionFormatError(`${label} payload must be a non-empty string array`);
3379
- const gaps = data["dt"];
3380
- if (!Array.isArray(gaps) || gaps.length !== payload.length - 1) throw new SessionFormatError(`${label} dt length must match its payload`);
3381
- let lastTime = time0;
3382
- for (const gap of gaps) {
3383
- const validGap = sessionFormatSafeInteger(gap, `${label} dt member`);
3384
- lastTime = sessionFormatSafeInteger(lastTime + validGap, `${label} member time`);
3385
- }
3386
- const turn = sessionFormatCount(data["turn"], `${label} turn`);
3387
- const step = sessionFormatCount(data["step"], `${label} step`);
3388
- const chunkIndex = sessionFormatCount(data["index"], `${label} index`);
3389
- if (isTool && (typeof data["id"] !== "string" || data["id"].length === 0 || data["name"] !== void 0 && typeof data["name"] !== "string")) throw new SessionFormatError(`${label} id and optional name must be strings`);
3390
- const lastSeq = sessionFormatCount(seq0 + payload.length - 1, `${label} final seq`);
3391
- const stream = type === "tool-call-chunks" ? {
3392
- type,
3393
- time0,
3394
- index: chunkIndex,
3395
- dt: gaps,
3396
- id: data["id"],
3397
- ...data["name"] === void 0 ? {} : { name: data["name"] },
3398
- args: payload
3399
- } : {
3400
- type,
3401
- time0,
3402
- index: chunkIndex,
3403
- dt: gaps,
3404
- texts: payload
3405
- };
3406
- const run = {
3407
- runType: "released-assistant-chunks",
3408
- firstSeq: seq0,
3409
- eventCount: payload.length,
3410
- turn,
3411
- step,
3412
- lastSeq,
3413
- lastTime,
3414
- stream,
3415
- expand: () => expandAssistantChunkRun(run)
3416
- };
3417
- return run;
3418
- }
3419
- function* expandAssistantChunkRun(run) {
3420
- const stream = run.stream;
3421
- const gaps = stream["dt"];
3422
- const members = stream["type"] === "tool-call-chunks" ? stream["args"] : stream["texts"];
3423
- let time = run.stream["time0"];
3424
- for (let index = 0; index < members.length; index += 1) {
3425
- if (index > 0) time += gaps[index - 1];
3426
- const member = members[index];
3427
- const chunk = stream["type"] === "text-chunks" ? {
3428
- type: "text-delta",
3429
- index: stream["index"],
3430
- text: member
3431
- } : stream["type"] === "reasoning-chunks" ? {
3432
- type: "reasoning-delta",
3433
- index: stream["index"],
3434
- text: member
3435
- } : {
3436
- type: "tool-call-delta",
3437
- index: stream["index"],
3438
- id: stream["id"],
3439
- ...stream["name"] === void 0 ? {} : { name: stream["name"] },
3440
- argumentsDelta: member
3441
- };
3442
- yield {
3443
- type: "assistant/chunk",
3444
- seq: run.firstSeq + index,
3445
- time,
3446
- data: {
3447
- turn: run.turn,
3448
- step: run.step,
3449
- chunk
3450
- }
3451
- };
3452
- }
3453
- }
3454
- function decodeSeqRanges$2(value, maxEntries) {
3455
- if (!Array.isArray(value)) throw new SessionFormatError("sourceEventSeqs must be an array");
3456
- const output = [];
3457
- let hasRange = false;
3458
- for (const entry of value) {
3459
- if (typeof entry === "number") {
3460
- if (output.length >= maxEntries) throw new SessionFormatError("sourceEventSeqs exceeds its event seq");
3461
- output.push(sessionFormatCount(entry, "sourceEventSeqs member"));
3462
- continue;
3463
- }
3464
- if (!Array.isArray(entry) || entry.length !== 2) throw new SessionFormatError("sourceEventSeqs range must be a [start, end] pair");
3465
- const start = sessionFormatCount(entry[0], "sourceEventSeqs range start");
3466
- const end = sessionFormatCount(entry[1], "sourceEventSeqs range end");
3467
- if (end < start || end - start + 1 > maxEntries - output.length) throw new SessionFormatError("sourceEventSeqs range exceeds its event seq");
3468
- for (let seq = start; seq <= end; seq += 1) output.push(seq);
3469
- hasRange = true;
3470
- }
3471
- if (hasRange && output.some((member, index) => index > 0 && member <= output[index - 1])) throw new SessionFormatError("sourceEventSeqs ranges must be strictly increasing");
3472
- return output;
3473
- }
3474
- //#endregion
3475
- //#region ../session-format-v0-to-v1/src/migration.ts
3476
- const LEGACY_ASSISTANT_SOURCE_KEY = ["pro", "venance"].join("");
3477
- defineSessionFormatMigration({
3478
- name: "@x1a0f3n9/dsh-session-format-v0-to-v1",
3479
- fromVersion: 0,
3480
- toVersion: 1,
3481
- migrateHeader(header) {
3482
- assertHeaderVersion(header, 0);
3483
- return {
3484
- ...header,
3485
- version: 1
3486
- };
3487
- },
3488
- createStage(input) {
3489
- return new ReleasedV0ToV1Stage(input);
3490
- },
3491
- validateTargetHeader: assertReleasedV1Header
3492
- });
3493
- var ReleasedV0ToV1Stage = class {
3494
- input;
3495
- headerInheritedEventCount;
3496
- state = {
3497
- messageIds: /* @__PURE__ */ new Map(),
3498
- retryIds: /* @__PURE__ */ new Map()
3499
- };
3500
- constructor(input) {
3501
- this.input = input;
3502
- assertHeaderVersion(input.sourceHeader, 0);
3503
- this.headerInheritedEventCount = sessionFormatCount(input.sourceInheritedEventCount, "format v0 inherited event count");
3504
- }
3505
- transformEvent(event, context) {
3506
- const normalized = normalizeReleasedV0Event(event, this.input.sourceHeader.id, this.state);
3507
- assertSourceDeliveryMarker$1(normalized, this.input);
3508
- context.emitEvent(normalized);
3509
- }
3510
- transformRun(run, context) {
3511
- if (isReleasedAssistantChunkRun(run)) {
3512
- context.emitRun(run);
3513
- return;
3514
- }
3515
- for (const event of run.expand()) this.transformEvent(event, context);
3516
- }
3517
- finish(_context) {
3518
- return this.headerInheritedEventCount;
3519
- }
3520
- };
3521
- function assertHeaderVersion(header, version) {
3522
- if (header.version !== version) throw new SessionFormatError(`expected format v${version} header`);
3523
- }
3524
- function normalizeReleasedV0Event(event, sessionId, state) {
3525
- const named = normalizeLegacyCompactionType(event);
3526
- assertSupportedLegacyType(named, sessionId);
3527
- const descriptor = normalizeLegacySubagentDescriptor(normalizeLegacyMessage(normalizeLegacyCompaction(normalizeLegacyRetry(normalizeLegacySteering(normalizeLegacyRequestHeader(normalizeLegacyTurnEnd(normalizeLegacyTurnStart(named, sessionId), sessionId), sessionId), sessionId), sessionId, state.retryIds), sessionId, state), sessionId, state.messageIds));
3528
- if (descriptor.type !== "assistant/chunk") assertReleasedEventPayload(descriptor, 0);
3529
- const messageId = eventMessageId(descriptor);
3530
- if (messageId !== void 0) state.messageIds.set(descriptor.seq, messageId);
3531
- return descriptor;
3532
- }
3533
- function normalizeLegacySubagentDescriptor(event) {
3534
- if (event.type !== "subagent/descriptor") return event;
3535
- const data = releasedV0Record(event.data, `${event.type} ${event.seq} data`);
3536
- if (data["version"] !== 2) return event;
3537
- return {
3538
- ...event,
3539
- data: {
3540
- ...data,
3541
- version: 3
3542
- }
3543
- };
3544
- }
3545
- function normalizeLegacyCompactionType(event) {
3546
- switch (event.type) {
3547
- case "compact/start": return {
3548
- ...event,
3549
- type: "compaction/start"
3550
- };
3551
- case "compact/summary": return {
3552
- ...event,
3553
- type: "compaction/summary"
3554
- };
3555
- case "compact/end": return {
3556
- ...event,
3557
- type: "compaction/end"
3558
- };
3559
- case "compact/prune": return {
3560
- ...event,
3561
- type: "compaction/prune"
3562
- };
3563
- default: return event;
3564
- }
3565
- }
3566
- function assertSourceDeliveryMarker$1(event, input) {
3567
- if (event.type !== "session-log-deepseek/delivery-accepted") return;
3568
- const data = releasedV0Record(event.data, `${event.type} ${event.seq} data`);
3569
- const acceptedVersion = data["sessionFormatVersion"] ?? 0;
3570
- const inherited = input.sourceHeader.parentSession !== void 0 && event.seq < sessionFormatCount(input.sourceInheritedEventCount, "format v0 inherited event count");
3571
- if (acceptedVersion === 0 && !inherited && data["sessionId"] !== input.sourceHeader.id) throw new SessionFormatError("current-generation delivery marker names the wrong Session");
3572
- }
3573
- function normalizeLegacyRetry(event, sessionId, retryIds) {
3574
- if (event.type !== "llm/retry") return event;
3575
- const data = releasedV0Record(event.data, `llm/retry ${event.seq} data`);
3576
- const chain = [
3577
- data["turn"],
3578
- data["step"],
3579
- data["provider"],
3580
- data["policyKey"]
3581
- ].map((value) => JSON.stringify(value)).join("\0");
3582
- const retryId = data["retryId"];
3583
- if (typeof retryId === "string" && retryId.length > 0) {
3584
- retryIds.set(chain, retryId);
3585
- return event;
3586
- }
3587
- if (Object.hasOwn(data, "retryId")) return event;
3588
- const migratedRetryId = retryIds.get(chain) ?? `legacy-retry:${sessionId}:${event.seq}`;
3589
- retryIds.set(chain, migratedRetryId);
3590
- return {
3591
- ...event,
3592
- data: {
3593
- ...data,
3594
- retryId: migratedRetryId
3595
- }
3596
- };
3597
- }
3598
- function normalizeLegacyCompaction(event, sessionId, state) {
3599
- if (event.type === "session/end-seed") {
3600
- delete state.compactionId;
3601
- return event;
3602
- }
3603
- if (event.type === "compaction/start") {
3604
- const data = releasedV0Record(event.data, `compaction/start ${event.seq} data`);
3605
- const existing = data["compactionId"];
3606
- if (typeof existing === "string" && existing.length > 0) {
3607
- state.compactionId = existing;
3608
- return event;
3609
- }
3610
- if (Object.hasOwn(data, "compactionId")) return event;
3611
- const id = `legacy-compaction:${sessionId}:${event.seq}`;
3612
- state.compactionId = id;
3613
- return {
3614
- ...event,
3615
- data: {
3616
- ...data,
3617
- compactionId: id
3618
- }
3619
- };
3620
- }
3621
- const compactionId = state.compactionId;
3622
- if (compactionId === void 0) return event;
3623
- if (event.type === "compaction/summary" || event.type === "compaction/end") {
3624
- const normalized = addLegacyCompactionId(event, compactionId);
3625
- if (event.type === "compaction/end") delete state.compactionId;
3626
- return normalized;
3627
- }
3628
- if (event.type !== "user/message") return event;
3629
- const data = releasedV0Record(event.data, `user/message ${event.seq} data`);
3630
- const source = data["source"];
3631
- if (!releasedIsRecord(source) || source["kind"] !== "plugin" || source["plugin"] !== "compact" || Object.hasOwn(source, "compactionId")) return event;
3632
- return {
3633
- ...event,
3634
- data: {
3635
- ...data,
3636
- source: {
3637
- ...source,
3638
- compactionId
3639
- }
3640
- }
3641
- };
3642
- }
3643
- function addLegacyCompactionId(event, compactionId) {
3644
- const data = releasedV0Record(event.data, `${event.type} ${event.seq} data`);
3645
- if (Object.hasOwn(data, "compactionId")) return event;
3646
- return {
3647
- ...event,
3648
- data: {
3649
- ...data,
3650
- compactionId
3651
- }
3652
- };
3653
- }
3654
- function normalizeLegacyRequestHeader(event, sessionId) {
3655
- if (event.type !== "request/header") return event;
3656
- const data = releasedV0Record(event.data, `request/header ${event.seq} data`);
3657
- const header = releasedV0Record(data["header"], `request/header ${event.seq} header`);
3658
- if (!Object.hasOwn(header, "messagePrefix")) return event;
3659
- if (!Array.isArray(header["messagePrefix"])) throw new SessionFormatError(`session ${JSON.stringify(sessionId)} contains malformed request/header messagePrefix at seq ${event.seq}`);
3660
- const { messagePrefix: _messagePrefix, ...currentHeader } = header;
3661
- return {
3662
- ...event,
3663
- data: {
3664
- ...data,
3665
- header: currentHeader
3666
- }
3667
- };
3668
- }
3669
- function assertSupportedLegacyType(event, sessionId) {
3670
- if (event.type === "request/header-delta" || event.type === "mode/set") throw new SessionFormatUnsupportedMigrationError$1(`session ${JSON.stringify(sessionId)} contains unsupported legacy ${event.type} event at seq ${event.seq}`);
3671
- if (event.type === "request/header") {
3672
- if (releasedV0Record(event.data, `request/header ${event.seq} data`)["reason"] === "fallback") throw new SessionFormatUnsupportedMigrationError$1(`session ${JSON.stringify(sessionId)} contains unsupported request/header reason "fallback" at seq ${event.seq}`);
3673
- }
3674
- }
3675
- function normalizeLegacySteering(event, sessionId) {
3676
- if (event.type !== "steering/message") return event;
3677
- const data = releasedV0Record(event.data, `steering/message ${event.seq} data`);
3678
- const wrapped = data["message"];
3679
- if (wrapped !== void 0) {
3680
- assertReleasedV0Keys(data, ["turn", "message"], [], `steering/message ${event.seq} data`);
3681
- sessionFormatCount(data["turn"], `steering/message ${event.seq} turn`);
3682
- return {
3683
- ...event,
3684
- type: "user/message",
3685
- data: wrapped
3686
- };
3687
- }
3688
- assertReleasedV0Keys(data, [
3689
- "turn",
3690
- "content",
3691
- "source"
3692
- ], [], `steering/message ${event.seq} data`);
3693
- sessionFormatCount(data["turn"], `steering/message ${event.seq} turn`);
3694
- const { turn: _turn, ...message } = data;
3695
- return {
3696
- ...event,
3697
- type: "user/message",
3698
- data: {
3699
- ...message,
3700
- id: legacyMessageId(sessionId, event.seq),
3701
- role: "user"
3702
- }
3703
- };
3704
- }
3705
- function normalizeLegacyTurnStart(event, sessionId) {
3706
- if (event.type !== "turn/start") return event;
3707
- const data = releasedV0Record(event.data, `turn/start ${event.seq} data`);
3708
- if (!Object.hasOwn(data, "trigger")) return event;
3709
- assertReleasedV0Keys(data, ["turn", "trigger"], [], `turn/start ${event.seq} data`);
3710
- const turn = sessionFormatCount(data["turn"], `turn/start ${event.seq} turn`);
3711
- const trigger = releasedV0Record(data["trigger"], `turn/start ${event.seq} trigger`);
3712
- if (turn < 1 || typeof trigger["kind"] !== "string" || trigger["kind"].length === 0) throw malformedLegacy(sessionId, "turn/start", event.seq);
3713
- return {
3714
- ...event,
3715
- data: { turn }
3716
- };
3717
- }
3718
- function normalizeLegacyTurnEnd(event, sessionId) {
3719
- if (event.type !== "turn/end") return event;
3720
- const data = releasedV0Record(event.data, `turn/end ${event.seq} data`);
3721
- assertReleasedV0Keys(data, ["turn", "reason"], [], `turn/end ${event.seq} data`);
3722
- if (sessionFormatCount(data["turn"], `turn/end ${event.seq} turn`) < 1) throw malformedLegacy(sessionId, "turn/end", event.seq);
3723
- const reason = releasedV0Record(data["reason"], `turn/end ${event.seq} reason`);
3724
- if (typeof reason["kind"] !== "string") throw malformedLegacy(sessionId, "turn/end", event.seq);
3725
- let current;
3726
- switch (reason["kind"]) {
3727
- case "completed":
3728
- case "blocked":
3729
- case "max-tokens":
3730
- case "interrupted":
3731
- assertReleasedV0Keys(reason, ["kind"], [], `turn/end ${event.seq} reason`);
3732
- return event;
3733
- case "aborted":
3734
- if (Object.hasOwn(reason, "reason")) return event;
3735
- assertReleasedV0Keys(reason, ["kind"], [], `turn/end ${event.seq} reason`);
3736
- current = {
3737
- kind: "aborted",
3738
- reason: { kind: "legacy" }
3739
- };
3740
- break;
3741
- case "disposed":
3742
- assertReleasedV0Keys(reason, ["kind"], [], `turn/end ${event.seq} reason`);
3743
- current = {
3744
- kind: "aborted",
3745
- reason: { kind: "disposed" }
3746
- };
3747
- break;
3748
- case "error":
3749
- if (Object.hasOwn(reason, "error")) return event;
3750
- current = normalizeLegacyErrorReason(reason, event.seq, sessionId);
3751
- break;
3752
- default: return event;
3753
- }
3754
- return {
3755
- ...event,
3756
- data: {
3757
- ...data,
3758
- reason: current
3759
- }
3760
- };
3761
- }
3762
- function normalizeLegacyErrorReason(reason, seq, sessionId) {
3763
- sessionFormatCount(reason["step"], `turn/end ${seq} error step`);
3764
- const failure = reason["failure"];
3765
- if (failure !== void 0) {
3766
- assertReleasedV0Keys(reason, [
3767
- "kind",
3768
- "step",
3769
- "failure"
3770
- ], [], `turn/end ${seq} reason`);
3771
- const record = releasedV0Record(failure, `turn/end ${seq} failure`);
3772
- assertReleasedV0Keys(record, ["message", "code"], [
3773
- "status",
3774
- "providerRetryAfterMs",
3775
- "requestId"
3776
- ], `turn/end ${seq} failure`);
3777
- if (typeof record["message"] !== "string" || typeof record["code"] !== "string") throw malformedLegacy(sessionId, "turn/end", seq);
3778
- return {
3779
- kind: "error",
3780
- error: record
3781
- };
3782
- }
3783
- assertReleasedV0Keys(reason, [
3784
- "kind",
3785
- "step",
3786
- "message"
3787
- ], ["code"], `turn/end ${seq} reason`);
3788
- if (typeof reason["message"] !== "string" || reason["code"] !== void 0 && typeof reason["code"] !== "string") throw malformedLegacy(sessionId, "turn/end", seq);
3789
- return {
3790
- kind: "error",
3791
- error: {
3792
- message: reason["message"],
3793
- code: typeof reason["code"] === "string" ? reason["code"] : "UNKNOWN"
3794
- }
3795
- };
3796
- }
3797
- function normalizeLegacyMessage(event, sessionId, messageIds) {
3798
- const data = releasedV0Record(event.data, `${event.type} ${event.seq} data`);
3799
- switch (event.type) {
3800
- case "user/message":
3801
- if (Object.hasOwn(data, "id") || Object.hasOwn(data, "role") || Object.hasOwn(data, "message") || !Object.hasOwn(data, "content") || !Object.hasOwn(data, "source")) return event;
3802
- return {
3803
- ...event,
3804
- data: {
3805
- ...data,
3806
- id: legacyMessageId(sessionId, event.seq),
3807
- role: "user"
3808
- }
3809
- };
3810
- case "assistant/message": {
3811
- if (Object.hasOwn(data, "message") || !Object.hasOwn(data, "content") || !Object.hasOwn(data, LEGACY_ASSISTANT_SOURCE_KEY)) return event;
3812
- const content = data["content"];
3813
- const eventData = { ...data };
3814
- delete eventData["content"];
3815
- const source = releasedV0Record(eventData[LEGACY_ASSISTANT_SOURCE_KEY], `assistant/message ${event.seq} legacy source`);
3816
- Reflect.deleteProperty(eventData, LEGACY_ASSISTANT_SOURCE_KEY);
3817
- return {
3818
- ...event,
3819
- data: {
3820
- ...eventData,
3821
- message: {
3822
- id: legacyMessageId(sessionId, event.seq),
3823
- role: "assistant",
3824
- content,
3825
- source: {
3826
- ...source,
3827
- kind: "model"
3828
- }
3829
- }
3830
- }
3831
- };
3832
- }
3833
- case "tool/result": {
3834
- if (Object.hasOwn(data, "message") || !Object.hasOwn(data, "callId") || !Object.hasOwn(data, "content") || !Object.hasOwn(data, "isError")) return event;
3835
- const { callId, content, isError, ...eventData } = data;
3836
- if (typeof callId !== "string" || typeof isError !== "boolean" || content === void 0) return event;
3837
- const inheritedId = replacementStart(event);
3838
- const messageId = inheritedId === void 0 ? legacyMessageId(sessionId, event.seq) : messageIds.get(inheritedId);
3839
- if (messageId === void 0) throw new SessionFormatError(`tool/result ${event.seq} replacement cites a message without identity`);
3840
- return {
3841
- ...event,
3842
- data: {
3843
- ...eventData,
3844
- message: {
3845
- id: messageId,
3846
- role: "user",
3847
- content: [{
3848
- type: "tool-result",
3849
- toolCallId: callId,
3850
- content,
3851
- isError
3852
- }],
3853
- source: {
3854
- kind: "tool",
3855
- callId
3856
- }
3857
- }
3858
- }
3859
- };
3860
- }
3861
- default: return event;
3862
- }
3863
- }
3864
- function replacementStart(event) {
3865
- const operation = event["surfaceOp"];
3866
- if (operation === void 0 || !releasedIsRecord(operation) || operation["op"] !== "replace") return void 0;
3867
- return operation["start"];
3868
- }
3869
- function eventMessageId(event) {
3870
- const data = releasedV0Record(event.data, `${event.type} ${event.seq} data`);
3871
- const message = event.type === "user/message" ? data : releasedIsRecord(data["message"]) ? data["message"] : void 0;
3872
- return typeof message?.["id"] === "string" ? message["id"] : void 0;
3873
- }
3874
- function releasedIsRecord(value) {
3875
- return typeof value === "object" && value !== null && !Array.isArray(value);
3876
- }
3877
- function legacyMessageId(sessionId, seq) {
3878
- return `legacy-message:${sessionId}:${seq}`;
3879
- }
3880
- function malformedLegacy(sessionId, type, seq) {
3881
- return new SessionFormatError(`session ${JSON.stringify(sessionId)} contains malformed pre-react-loop ${type} at seq ${seq}`);
3882
- }
3883
- //#endregion
3884
- //#region ../session-format-v1-to-v2/src/dispositions.ts
3885
- const retained = Object.fromEntries(Object.entries(RELEASED_V0_EVENT_DISPOSITIONS).filter(([type]) => type !== "assistant/chunk" && type !== "assistant/message" && type !== "session-log-deepseek/delivery-accepted" && type !== "session/end-seed"));
3886
- /** Exact top-level event and payload-member inventory frozen for released v2. */
3887
- const RELEASED_V2_EVENT_DISPOSITIONS = Object.freeze({
3888
- ...retained,
3889
- "assistant/attempt": defineReleasedPayloadDisposition([
3890
- "turn",
3891
- "step",
3892
- "stream"
3893
- ]),
3894
- "assistant/message": defineReleasedPayloadDisposition([
3895
- "turn",
3896
- "step",
3897
- "message",
3898
- "stream"
3899
- ], ["usage", "interrupted"]),
3900
- "session-log-deepseek/delivery-accepted": defineReleasedPayloadDisposition(["sessionId", "throughSeq"], ["sessionFormatVersion"]),
3901
- "session/end-seed": defineReleasedPayloadDisposition([], ["inherited"])
3902
- });
3903
- Object.freeze(Object.keys(RELEASED_V2_EVENT_DISPOSITIONS).sort((left, right) => left.localeCompare(right, "en")));
3904
- //#endregion
3905
- //#region ../session-format-v1-to-v2/src/validation.ts
3906
- const HEADER_REQUIRED$1 = [
3907
- "version",
3908
- "id",
3909
- "createdAt",
3910
- "isSeeded",
3911
- "delegationDepth"
3912
- ];
3913
- const HEADER_OPTIONAL$1 = [
3914
- "cwd",
3915
- "parentSession",
3916
- "origin",
3917
- "agentPreset"
3918
- ];
3919
- /**
3920
- * Validate the exact logical header written by released v2.
3921
- * @param header - decoded released-v2 Session header.
3922
- * @throws {SessionFormatError} when the header is not an exact released-v2 value.
3923
- */
3924
- function assertReleasedV2Header(header) {
3925
- const record = releasedV2Record(header, "format v2 header");
3926
- assertReleasedV2Keys(record, HEADER_REQUIRED$1, HEADER_OPTIONAL$1, "format v2 header");
3927
- if (record["version"] !== 2) throw new SessionFormatError("expected format v2 header");
3928
- if (typeof record["id"] !== "string") throw new SessionFormatError("format v2 header id must be a string");
3929
- sessionFormatCount(record["createdAt"], "format v2 header createdAt");
3930
- sessionFormatCount(record["delegationDepth"], "format v2 header delegationDepth");
3931
- if (typeof record["isSeeded"] !== "boolean") throw new SessionFormatError("format v2 header isSeeded must be boolean");
3932
- if (record["cwd"] !== void 0 && (typeof record["cwd"] !== "string" || !isAbsolute(record["cwd"]))) throw new SessionFormatError("format v2 header cwd must be absolute");
3933
- for (const key of ["parentSession", "agentPreset"]) if (record[key] !== void 0 && typeof record[key] !== "string") throw new SessionFormatError(`format v2 header ${key} must be a string`);
3934
- if (record["origin"] !== void 0 && record["origin"] !== "subagent") throw new SessionFormatError("format v2 header origin must be \"subagent\"");
3935
- }
3936
- /**
3937
- * Require one released-v2 value to be a JSON object.
3938
- * @param value - value to narrow.
3939
- * @param label - diagnostic subject.
3940
- * @returns the narrowed object.
3941
- */
3942
- function releasedV2Record(value, label) {
3943
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new SessionFormatError(`${label} must be an object`);
3944
- return value;
3945
- }
3946
- /**
3947
- * Require one released-v2 object to contain exactly the admitted keys.
3948
- * @param value - object to inspect.
3949
- * @param required - keys that must be present.
3950
- * @param optional - additional keys that may be present.
3951
- * @param label - diagnostic subject.
3952
- */
3953
- function assertReleasedV2Keys(value, required, optional, label) {
3954
- const allowed = new Set([...required, ...optional]);
3955
- const missing = required.find((key) => !Object.hasOwn(value, key));
3956
- if (missing !== void 0) throw new SessionFormatError(`${label} lacks required field ${missing}`);
3957
- const unexpected = Object.keys(value).find((key) => !allowed.has(key));
3958
- if (unexpected !== void 0) throw new SessionFormatError(`${label} has unexpected field ${unexpected}`);
3959
- }
3960
- //#endregion
3961
- //#region ../session-format-v1-to-v2/src/codec.ts
3962
- const HEADER_REQUIRED = [
3963
- "type",
3964
- "version",
3965
- "id",
3966
- "createdAt",
3967
- "isSeeded",
3968
- "delegationDepth"
3969
- ];
3970
- const HEADER_OPTIONAL = [
3971
- "cwd",
3972
- "parentSession",
3973
- "origin",
3974
- "agentPreset"
3975
- ];
3976
- const EVENT_REQUIRED = [
3977
- "type",
3978
- "seq",
3979
- "time",
3980
- "data"
3981
- ];
3982
- const EVENT_OPTIONAL = [
3983
- "ignorable",
3984
- "sourceEventSeqs",
3985
- "surfaceOp"
3986
- ];
3987
- const EVENT_KEYS = new Set([...EVENT_REQUIRED, ...EVENT_OPTIONAL]);
3988
- /** Frozen physical JSON codec for released v2. */
3989
- const releasedV2SessionFormatCodec = Object.freeze({
3990
- version: 2,
3991
- decodeHeader(value) {
3992
- return decodePhysicalHeader(value);
3993
- },
3994
- createDecoder(headerValue, recovery) {
3995
- return createDecoder(headerValue, recovery);
3996
- },
3997
- encodeHeader(header, inheritedEventCount) {
3998
- return encodeHeader(header, inheritedEventCount);
3999
- },
4000
- encodeEvent(event) {
4001
- return encodeSourceEventRanges(event);
4002
- }
4003
- });
4004
- function decodePhysicalHeader(value) {
4005
- const record = jsonRecord(snapshotSessionFormatJson(value, "released v2 physical header"), "released v2 physical header");
4006
- exactKeys(record, HEADER_REQUIRED, HEADER_OPTIONAL, "released v2 physical header");
4007
- if (record["type"] !== "session" || record["version"] !== 2) throw new SessionFormatError("expected released v2 physical Session header");
4008
- if (typeof record["id"] !== "string") throw new SessionFormatError("released v2 header id must be a string");
4009
- const createdAt = sessionFormatCount(record["createdAt"], "released v2 header createdAt");
4010
- const delegationDepth = sessionFormatCount(record["delegationDepth"], "released v2 header delegationDepth");
4011
- if (typeof record["isSeeded"] !== "boolean") throw new SessionFormatError("released v2 header isSeeded must be boolean");
4012
- for (const key of [
4013
- "cwd",
4014
- "parentSession",
4015
- "agentPreset"
4016
- ]) if (record[key] !== void 0 && typeof record[key] !== "string") throw new SessionFormatError(`released v2 header ${key} must be a string`);
4017
- if (record["origin"] !== void 0 && record["origin"] !== "subagent") throw new SessionFormatError("released v2 header origin must be \"subagent\"");
4018
- const header = snapshotSessionFormatJson({
4019
- version: 2,
4020
- id: record["id"],
4021
- createdAt,
4022
- ...record["cwd"] === void 0 ? {} : { cwd: record["cwd"] },
4023
- ...record["parentSession"] === void 0 ? {} : { parentSession: record["parentSession"] },
4024
- isSeeded: record["isSeeded"],
4025
- ...record["origin"] === void 0 ? {} : { origin: record["origin"] },
4026
- delegationDepth,
4027
- ...record["agentPreset"] === void 0 ? {} : { agentPreset: record["agentPreset"] }
4028
- }, "released v2 logical header");
4029
- assertReleasedV2Header(header);
4030
- return header;
4031
- }
4032
- function createDecoder(headerValue, recovery) {
4033
- const header = decodePhysicalHeader(headerValue);
4034
- let rowIndex = 0;
4035
- let eventCount = 0;
4036
- let inheritedEventCount;
4037
- let issue;
4038
- return {
4039
- header,
4040
- decodeRow(value, context) {
4041
- const currentRow = rowIndex;
4042
- rowIndex += 1;
4043
- let event;
4044
- try {
4045
- event = decodeEvent(value, currentRow);
4046
- } catch (error) {
4047
- const current = error instanceof SessionFormatError ? error : new SessionFormatError(`released v2 row ${currentRow} is malformed`, { cause: error });
4048
- if (recovery === "strict") throw current;
4049
- issue ??= current;
4050
- return;
4051
- }
4052
- if (issue !== void 0) {
4053
- if (event.type === "turn/end") throw issue;
4054
- return;
4055
- }
4056
- if (event.seq !== eventCount) {
4057
- const gap = new SessionFormatError(`released v2 row ${currentRow} has seq gap (expected ${eventCount}, got ${event.seq})`);
4058
- if (recovery === "strict") throw gap;
4059
- issue = gap;
4060
- if (event.type === "turn/end") throw issue;
4061
- return;
4062
- }
4063
- eventCount += 1;
4064
- if (event.type === "session/end-seed") {
4065
- if (jsonRecord(event.data, `session/end-seed ${event.seq} data`)["inherited"] === true) inheritedEventCount = event.seq;
4066
- }
4067
- context.emitEvent(event);
4068
- },
4069
- finish(_context) {
4070
- if (header.isSeeded && inheritedEventCount === void 0) throw new SessionFormatError("released v2 seeded Session lacks an inherited end-seed marker");
4071
- if (!header.isSeeded && inheritedEventCount !== void 0) throw new SessionFormatError("released v2 unseeded Session contains an inherited end-seed marker");
4072
- return inheritedEventCount ?? 0;
4073
- }
4074
- };
4075
- }
4076
- function decodeEvent(value, rowIndex) {
4077
- const record = jsonRecord(value, `released v2 row ${rowIndex}`);
4078
- const missing = EVENT_REQUIRED.find((key) => !Object.hasOwn(record, key));
4079
- if (missing !== void 0) throw new SessionFormatError(`released v2 row ${rowIndex} lacks required field ${missing}`);
4080
- const unexpected = Object.keys(record).find((key) => !EVENT_KEYS.has(key));
4081
- if (unexpected !== void 0) throw new SessionFormatError(`released v2 row ${rowIndex} has unexpected field ${unexpected}`);
4082
- if (typeof record["type"] !== "string") throw new SessionFormatError(`released v2 row ${rowIndex} type must be a string`);
4083
- sessionFormatSafeInteger(record["time"], `released v2 row ${rowIndex} time`);
4084
- if (record["ignorable"] !== void 0 && record["ignorable"] !== true) throw new SessionFormatError(`released v2 row ${rowIndex} ignorable must be true when present`);
4085
- if (record["sourceEventSeqs"] === void 0) return record;
4086
- const seq = sessionFormatCount(record["seq"], `released v2 row ${rowIndex} seq`);
4087
- return {
4088
- ...record,
4089
- sourceEventSeqs: decodeSeqRanges$1(record["sourceEventSeqs"], seq)
4090
- };
4091
- }
4092
- function encodeHeader(header, inheritedEventCount) {
4093
- assertReleasedV2Header(header);
4094
- const cut = sessionFormatCount(inheritedEventCount, "format v2 inherited event count");
4095
- if (!header.isSeeded && cut !== 0) throw new SessionFormatError("unseeded format v2 Session has inherited events");
4096
- return {
4097
- type: "session",
4098
- version: 2,
4099
- id: header.id,
4100
- createdAt: header.createdAt,
4101
- ...header.cwd === void 0 ? {} : { cwd: header.cwd },
4102
- ...header.parentSession === void 0 ? {} : { parentSession: header.parentSession },
4103
- isSeeded: header.isSeeded,
4104
- ...header.origin === void 0 ? {} : { origin: header.origin },
4105
- delegationDepth: header.delegationDepth,
4106
- ...header.agentPreset === void 0 ? {} : { agentPreset: header.agentPreset }
4107
- };
4108
- }
4109
- function encodeSourceEventRanges(event) {
4110
- if (event.sourceEventSeqs === void 0) return event;
4111
- return {
4112
- ...event,
4113
- sourceEventSeqs: encodeSeqRanges(event.sourceEventSeqs)
4114
- };
4115
- }
4116
- function decodeSeqRanges$1(value, maxEntries) {
4117
- if (!Array.isArray(value)) throw new SessionFormatError("sourceEventSeqs must be an array");
4118
- const output = [];
4119
- let hasRange = false;
4120
- for (const entry of value) {
4121
- if (!Array.isArray(entry)) {
4122
- output.push(sessionFormatCount(entry, "sourceEventSeqs member"));
4123
- continue;
4124
- }
4125
- if (entry.length !== 2) throw new SessionFormatError("sourceEventSeqs range must be a [start, end] pair");
4126
- const start = sessionFormatCount(entry[0], "sourceEventSeqs range start");
4127
- const end = sessionFormatCount(entry[1], "sourceEventSeqs range end");
4128
- if (start > end || end >= maxEntries || end - start + 1 > maxEntries - output.length) throw new SessionFormatError("sourceEventSeqs range exceeds its event seq");
4129
- for (let current = start; current <= end; current += 1) output.push(current);
4130
- hasRange = true;
4131
- }
4132
- const seen = /* @__PURE__ */ new Set();
4133
- for (const source of output) {
4134
- if (source >= maxEntries || seen.has(source)) throw new SessionFormatError("sourceEventSeqs ranges must contain unique earlier seqs");
4135
- seen.add(source);
4136
- }
4137
- if (hasRange && output.some((source, index) => index > 0 && source <= output[index - 1])) throw new SessionFormatError("sourceEventSeqs ranges must be strictly increasing");
4138
- return output;
4139
- }
4140
- function encodeSeqRanges(values) {
4141
- if (values.some((value, index) => index > 0 && value <= values[index - 1])) return [...values];
4142
- const output = [];
4143
- for (let index = 0; index < values.length;) {
4144
- const start = values[index];
4145
- let end = start;
4146
- while (index + 1 < values.length && values[index + 1] === end + 1) {
4147
- index += 1;
4148
- end += 1;
4149
- }
4150
- output.push(end - start >= 2 ? [start, end] : start);
4151
- if (end - start === 1) output.push(end);
4152
- index += 1;
4153
- }
4154
- return output;
4155
- }
4156
- function jsonRecord(value, label) {
4157
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new SessionFormatError(`${label} must be an object`);
4158
- return value;
4159
- }
4160
- function exactKeys(record, required, optional, label) {
4161
- const allowed = new Set([...required, ...optional]);
4162
- const missing = required.find((key) => !Object.hasOwn(record, key));
4163
- if (missing !== void 0) throw new SessionFormatError(`${label} lacks ${missing}`);
4164
- const unexpected = Object.keys(record).find((key) => !allowed.has(key));
4165
- if (unexpected !== void 0) throw new SessionFormatError(`${label} has unexpected field ${unexpected}`);
4166
- }
4167
- //#endregion
4168
- //#region ../session-format-v1-to-v2/src/migration.ts
4169
- const CHUNK_EVENT_REQUIRED = [
4170
- "type",
4171
- "seq",
4172
- "time",
4173
- "data"
4174
- ];
4175
- const CHUNK_EVENT_OPTIONAL = [
4176
- "ignorable",
4177
- "sourceEventSeqs",
4178
- "surfaceOp"
4179
- ];
4180
- const CHUNK_EVENT_KEYS = new Set([...CHUNK_EVENT_REQUIRED, ...CHUNK_EVENT_OPTIONAL]);
4181
- defineSessionFormatMigration({
4182
- name: "@x1a0f3n9/dsh-session-format-v1-to-v2",
4183
- fromVersion: 1,
4184
- toVersion: 2,
4185
- migrateHeader(header) {
4186
- assertReleasedV1Header(header);
4187
- return {
4188
- ...header,
4189
- version: 2
4190
- };
4191
- },
4192
- createStage(input) {
4193
- return input.sourceKind === "decoded" ? new DecodedReleasedV1ToV2Stage(input) : new TransformedReleasedV1ToV2Stage(input);
4194
- },
4195
- validateTargetHeader: assertReleasedV2Header
4196
- });
4197
- var TransformedReleasedV1ToV2Stage = class {
4198
- state;
4199
- constructor(input) {
4200
- assertReleasedV1Header(input.sourceHeader);
4201
- this.state = {
4202
- sourceHeader: input.sourceHeader,
4203
- sourceCut: sessionFormatCount(input.sourceInheritedEventCount, "format v1 inherited event count"),
4204
- mapping: /* @__PURE__ */ new Map(),
4205
- legacyTurns: legacyTurnState(),
4206
- pending: void 0,
4207
- targetSeq: 0,
4208
- targetCut: input.sourceHeader.isSeeded ? void 0 : 0,
4209
- lastTime: input.sourceHeader.createdAt
4210
- };
4211
- }
4212
- transformEvent(event, context) {
4213
- transformReleasedEvent(this.state, event, context);
4214
- }
4215
- transformRun(run, context) {
4216
- transformReleasedRun(this.state, run, context);
4217
- }
4218
- finish(context) {
4219
- return finishMigration(this.state, context);
4220
- }
4221
- };
4222
- var DecodedReleasedV1ToV2Stage = class extends TransformedReleasedV1ToV2Stage {
4223
- transformEvent(event, context) {
4224
- if (event.type !== "assistant/chunk" && RELEASED_V0_EVENT_DISPOSITIONS[event.type] !== void 0) assertReleasedEventPayload(event, 1);
4225
- super.transformEvent(event, context);
4226
- }
4227
- };
4228
- function transformReleasedEvent(state, event, context) {
4229
- if (event.type === "assistant/chunk") assertChunkEnvelope(event);
4230
- if (RELEASED_V0_EVENT_DISPOSITIONS[event.type] === void 0) throw refusal(`format v1 contains unknown event type ${JSON.stringify(event.type)} at seq ${event.seq}`);
4231
- const interrupted = legacyInterruptedTurn(state.legacyTurns, event);
4232
- if (event.type === "turn/start" && state.legacyTurns.openTurn !== null && interrupted === void 0) throw refusal(`turn/start ${JSON.stringify(record$1(event.data)["turn"])} does not close the prior turn`);
4233
- assertSourceDeliveryMarker(state, event);
4234
- observeLegacyTurn(state.legacyTurns, event);
4235
- state.lastTime = event.time;
4236
- if (interrupted !== void 0) {
4237
- finishAttempt(state, context);
4238
- emitGenerated(state, event.seq, interrupted, context);
4239
- }
4240
- const legacyGoal = splitLegacyGoalChange(event);
4241
- if (legacyGoal !== void 0) {
4242
- emitGenerated(state, event.seq, legacyGoal.change, context);
4243
- emitSource(state, legacyGoal.message, context);
4244
- return;
4245
- }
4246
- if (event.type === "assistant/chunk") {
4247
- transformChunk(state, event, context);
4248
- return;
4249
- }
4250
- if (event.type === "assistant/message") {
4251
- transformMessage(state, event, context);
4252
- return;
4253
- }
4254
- if (closesAttempt(event)) {
4255
- finishAttempt(state, context);
4256
- emitSource(state, event, context);
4257
- return;
4258
- }
4259
- if (state.pending !== void 0) {
4260
- state.pending.afterLastChunk.push(event);
4261
- return;
4262
- }
4263
- emitSource(state, event, context);
4264
- }
4265
- function assertChunkEnvelope(event) {
4266
- const unexpected = Object.keys(event).find((key) => !CHUNK_EVENT_KEYS.has(key));
4267
- if (unexpected !== void 0) throw refusal(`assistant/chunk ${event.seq} has unexpected member ${unexpected}`);
4268
- const missing = CHUNK_EVENT_REQUIRED.find((key) => !Object.hasOwn(event, key));
4269
- if (missing !== void 0) throw refusal(`assistant/chunk ${event.seq} lacks required member ${missing}`);
4270
- if (event.ignorable !== void 0 && event.ignorable !== true) throw refusal(`assistant/chunk ${event.seq} ignorable must be true when present`);
4271
- }
4272
- function assertSourceDeliveryMarker(state, event) {
4273
- if (event.type !== "session-log-deepseek/delivery-accepted") return;
4274
- const data = record$1(event.data);
4275
- const inherited = state.sourceHeader.parentSession !== void 0 && event.seq < state.sourceCut;
4276
- if (data["sessionFormatVersion"] === 1 && !inherited && data["sessionId"] !== state.sourceHeader.id) throw refusal("current-generation delivery marker names the wrong Session");
4277
- }
4278
- function transformReleasedRun(state, run, context) {
4279
- if (!isReleasedAssistantChunkRun(run)) {
4280
- for (const event of run.expand()) transformReleasedEvent(state, event, context);
4281
- return;
4282
- }
4283
- state.legacyTurns.previous = void 0;
4284
- state.lastTime = run.lastTime;
4285
- if (state.pending !== void 0 && (state.pending.group.terminal || state.pending.group.turn !== run.turn || state.pending.group.step !== run.step)) finishAttempt(state, context);
4286
- else if (state.pending !== void 0) flushBuffered(state, state.pending, context);
4287
- state.pending ??= {
4288
- group: attemptGroup(run.turn, run.step),
4289
- afterLastChunk: []
4290
- };
4291
- assertAttemptRange(state, run.firstSeq, run.lastSeq);
4292
- flushAccumulator(state.pending.group);
4293
- appendStreamRecord(state.pending.group, run.stream, run.lastTime);
4294
- recordChunkSpan(state.pending.group, run.firstSeq, run.eventCount, run.lastTime);
4295
- }
4296
- function finishMigration(state, context) {
4297
- finishAttempt(state, context);
4298
- if (state.sourceHeader.isSeeded && state.targetCut === void 0) {
4299
- state.targetCut = state.targetSeq;
4300
- context.emitEvent({
4301
- type: "session/end-seed",
4302
- seq: state.targetSeq,
4303
- time: state.lastTime,
4304
- data: { inherited: true }
4305
- });
4306
- state.targetSeq += 1;
4307
- }
4308
- return state.targetCut;
4309
- }
4310
- function transformChunk(state, event, context) {
4311
- const data = record$1(event.data);
4312
- const turn = coordinate(data["turn"]);
4313
- const step = coordinate(data["step"]);
4314
- const chunk = record$1(data["chunk"]);
4315
- if (state.pending !== void 0 && (state.pending.group.terminal || state.pending.group.turn !== turn || state.pending.group.step !== step)) finishAttempt(state, context);
4316
- else if (state.pending !== void 0) flushBuffered(state, state.pending, context);
4317
- state.pending ??= {
4318
- group: attemptGroup(turn, step),
4319
- afterLastChunk: []
4320
- };
4321
- assertAttemptCut(state, state.pending.group, event.seq);
4322
- state.pending.group.accumulator ??= new AssistantStreamAccumulator();
4323
- state.pending.group.accumulator.push({
4324
- time: event.time,
4325
- chunk: data["chunk"]
4326
- });
4327
- recordChunkSpan(state.pending.group, event.seq, 1, event.time);
4328
- if (chunk["type"] === "finish") state.pending.group.terminal = true;
4329
- }
4330
- function transformMessage(state, event, context) {
4331
- const data = record$1(event.data);
4332
- const turn = coordinate(data["turn"]);
4333
- const step = coordinate(data["step"]);
4334
- const sources = event.sourceEventSeqs;
4335
- const pending = state.pending;
4336
- if (pending !== void 0 && (pending.group.turn !== turn || pending.group.step !== step)) {
4337
- finishAttempt(state, context);
4338
- emitSource(state, messageEvent(event, attemptGroup(turn, step)), context);
4339
- return;
4340
- }
4341
- if (!Array.isArray(sources)) {
4342
- if (pending !== void 0) throw refusal(`assistant/message ${event.seq} does not cite its complete v1 chunk attempt`);
4343
- emitSource(state, messageEvent(event, attemptGroup(turn, step)), context);
4344
- return;
4345
- }
4346
- if (sources.length === 0) {
4347
- finishAttempt(state, context);
4348
- emitSource(state, messageEvent(event, attemptGroup(turn, step)), context);
4349
- return;
4350
- }
4351
- if (pending === void 0 || !matchesChunkSources(pending.group, sources)) throw refusal(`assistant/message ${event.seq} chunk references are not one complete ordered attempt`);
4352
- assertAttemptCut(state, pending.group, event.seq);
4353
- pending.group.terminal = true;
4354
- flushBuffered(state, pending, context);
4355
- emitSource(state, messageEvent(event, pending.group), context);
4356
- state.pending = void 0;
4357
- }
4358
- function finishAttempt(state, context) {
4359
- const pending = state.pending;
4360
- if (pending === void 0) return;
4361
- emitGenerated(state, pending.group.lastChunkSeq, attemptEvent(pending.group), context);
4362
- flushBuffered(state, pending, context);
4363
- state.pending = void 0;
4364
- }
4365
- function flushBuffered(state, pending, context) {
4366
- for (const event of pending.afterLastChunk) emitSource(state, event, context);
4367
- pending.afterLastChunk.length = 0;
4368
- }
4369
- function emitSource(state, event, context) {
4370
- let source = event;
4371
- if (state.sourceHeader.isSeeded && event.seq === state.sourceCut && event.type === "session/end-seed") source = {
4372
- ...event,
4373
- data: { inherited: true }
4374
- };
4375
- ensureTargetCut(state, event.seq, event.time, source.type, context);
4376
- state.mapping.set(event.seq, state.targetSeq);
4377
- context.emitEvent(remapReferences(source, state.targetSeq, state.mapping));
4378
- state.targetSeq += 1;
4379
- }
4380
- function emitGenerated(state, origin, event, context) {
4381
- ensureTargetCut(state, origin, event.time, event.type, context);
4382
- context.emitEvent(remapReferences(event, state.targetSeq, state.mapping));
4383
- state.targetSeq += 1;
4384
- }
4385
- function ensureTargetCut(state, origin, time, type, context) {
4386
- if (!state.sourceHeader.isSeeded || state.targetCut !== void 0 || origin < state.sourceCut) return;
4387
- state.targetCut = state.targetSeq;
4388
- if (origin === state.sourceCut && type === "session/end-seed") return;
4389
- context.emitEvent({
4390
- type: "session/end-seed",
4391
- seq: state.targetSeq,
4392
- time,
4393
- data: { inherited: true }
4394
- });
4395
- state.targetSeq += 1;
4396
- }
4397
- function assertAttemptCut(state, group, member) {
4398
- if ((group.spans[0]?.firstSeq ?? member) < state.sourceCut !== member < state.sourceCut) throw refusal(`inherited Session cut ${state.sourceCut} splits one Assistant attempt`);
4399
- }
4400
- function assertAttemptRange(state, first, last) {
4401
- if (first < state.sourceCut !== last < state.sourceCut) throw refusal(`inherited Session cut ${state.sourceCut} splits one Assistant attempt`);
4402
- }
4403
- function legacyTurnState() {
4404
- return {
4405
- openTurn: null,
4406
- openStep: null,
4407
- previous: void 0
4408
- };
4409
- }
4410
- function legacyInterruptedTurn(state, event) {
4411
- if (event.type !== "turn/start" || state.openTurn === null || state.openStep !== null || coordinate(record$1(event.data)["turn"]) !== state.openTurn + 1 || state.previous?.type !== "agent/inbox/spliced") return void 0;
4412
- const splice = record$1(state.previous.data);
4413
- if (splice["target"] !== "next-turn" || !Array.isArray(splice["inserted"]) || splice["inserted"].length === 0) return;
4414
- return {
4415
- type: "turn/end",
4416
- seq: event.seq,
4417
- time: event.time,
4418
- data: {
4419
- turn: state.openTurn,
4420
- reason: { kind: "interrupted" }
4421
- }
4422
- };
4423
- }
4424
- function observeLegacyTurn(state, event) {
4425
- const data = record$1(event.data);
4426
- if (event.type === "turn/start") {
4427
- state.openTurn = coordinate(data["turn"]);
4428
- state.openStep = null;
4429
- } else if (event.type === "turn/end") {
4430
- state.openTurn = null;
4431
- state.openStep = null;
4432
- } else if (event.type === "step/start") state.openStep = coordinate(data["step"]);
4433
- else if (event.type === "step/end") state.openStep = null;
4434
- state.previous = event;
4435
- }
4436
- function splitLegacyGoalChange(event) {
4437
- if (event.type !== "user/message") return void 0;
4438
- const data = record$1(event.data);
4439
- const source = record$1(data["source"]);
4440
- if (source["kind"] !== "goal" || source["change"] === void 0) return void 0;
4441
- return {
4442
- change: {
4443
- type: "goal/change",
4444
- seq: event.seq,
4445
- time: event.time,
4446
- data: source["change"]
4447
- },
4448
- message: {
4449
- ...event,
4450
- data: {
4451
- ...data,
4452
- source: {
4453
- kind: "plugin",
4454
- plugin: "goal"
4455
- }
4456
- }
4457
- }
4458
- };
4459
- }
4460
- function closesAttempt(event) {
4461
- return event.type === "turn/end" || event.type === "step/end" || event.type === "llm/retry" || event.type === "llm/retry-started";
4462
- }
4463
- function attemptGroup(turn, step) {
4464
- return {
4465
- turn,
4466
- step,
4467
- spans: [],
4468
- stream: [],
4469
- chunkCount: 0,
4470
- terminal: false
4471
- };
4472
- }
4473
- function recordChunkSpan(group, firstSeq, eventCount, lastTime) {
4474
- const previous = group.spans.at(-1);
4475
- if (previous !== void 0 && previous.firstSeq + previous.eventCount === firstSeq) previous.eventCount += eventCount;
4476
- else group.spans.push({
4477
- firstSeq,
4478
- eventCount
4479
- });
4480
- group.chunkCount += eventCount;
4481
- group.lastChunkSeq = firstSeq + eventCount - 1;
4482
- group.lastChunkTime = lastTime;
4483
- }
4484
- function matchesChunkSources(group, sources) {
4485
- if (sources.length !== group.chunkCount) return false;
4486
- let index = 0;
4487
- for (const span of group.spans) for (let offset = 0; offset < span.eventCount; offset += 1) {
4488
- if (sources[index] !== span.firstSeq + offset) return false;
4489
- index += 1;
4490
- }
4491
- return true;
4492
- }
4493
- function recordLastTime(record) {
4494
- if (record.type === "chunk") return record.time;
4495
- return record.dt.reduce((time, gap) => time + gap, record.time0);
4496
- }
4497
- function mutableRecord(record) {
4498
- if (record.type === "chunk") return record;
4499
- if (record.type === "tool-call-chunks") return {
4500
- ...record,
4501
- dt: [...record.dt],
4502
- args: [...record.args]
4503
- };
4504
- return {
4505
- ...record,
4506
- dt: [...record.dt],
4507
- texts: [...record.texts]
4508
- };
4509
- }
4510
- function appendStreamRecord(group, source, lastTime, owned = true) {
4511
- const previous = group.stream.at(-1);
4512
- if (previous === void 0 || source.type === "chunk" || previous.record.type !== source.type) {
4513
- group.stream.push({
4514
- record: owned ? source : mutableRecord(source),
4515
- lastTime
4516
- });
4517
- return;
4518
- }
4519
- const gap = source.time0 - previous.lastTime;
4520
- if (previous.record.index !== source.index || !Number.isSafeInteger(gap)) {
4521
- group.stream.push({
4522
- record: owned ? source : mutableRecord(source),
4523
- lastTime
4524
- });
4525
- return;
4526
- }
4527
- if (source.type === "tool-call-chunks") {
4528
- const target = previous.record;
4529
- if (target.id !== source.id || target.name !== source.name) {
4530
- group.stream.push({
4531
- record: owned ? source : mutableRecord(source),
4532
- lastTime
4533
- });
4534
- return;
4535
- }
4536
- target.dt.push(gap);
4537
- for (const value of source.dt) target.dt.push(value);
4538
- for (const value of source.args) target.args.push(value);
4539
- } else {
4540
- const target = previous.record;
4541
- target.dt.push(gap);
4542
- for (const value of source.dt) target.dt.push(value);
4543
- for (const value of source.texts) target.texts.push(value);
4544
- }
4545
- previous.lastTime = lastTime;
4546
- }
4547
- function flushAccumulator(group) {
4548
- const accumulator = group.accumulator;
4549
- if (accumulator === void 0) return;
4550
- for (const record of accumulator.snapshot()) appendStreamRecord(group, record, recordLastTime(record), false);
4551
- delete group.accumulator;
4552
- }
4553
- function streamOf(group) {
4554
- flushAccumulator(group);
4555
- return group.stream.map(({ record }) => record);
4556
- }
4557
- function messageEvent(source, group) {
4558
- const data = record$1(source.data);
4559
- const { sourceEventSeqs: _sourceEventSeqs, ...event } = source;
4560
- return {
4561
- ...event,
4562
- data: {
4563
- ...data,
4564
- stream: streamOf(group)
4565
- }
4566
- };
4567
- }
4568
- function attemptEvent(group) {
4569
- return {
4570
- type: "assistant/attempt",
4571
- seq: group.lastChunkSeq,
4572
- time: group.lastChunkTime,
4573
- data: {
4574
- turn: group.turn,
4575
- step: group.step,
4576
- stream: streamOf(group)
4577
- }
4578
- };
4579
- }
4580
- function remapReferences(source, targetSeq, mapping) {
4581
- const { sourceEventSeqs, surfaceOp, ...event } = source;
4582
- const sources = sourceEventSeqs === void 0 ? {} : { sourceEventSeqs: mapList(numberArray(sourceEventSeqs), mapping, `${source.type} ${source.seq} sources`) };
4583
- let operation = surfaceOp;
4584
- if (surfaceOp !== void 0 && surfaceOp !== "append") {
4585
- const replacement = record$1(surfaceOp);
4586
- operation = {
4587
- op: "replace",
4588
- start: mapOne(coordinate(replacement["start"]), mapping, `${source.type} ${source.seq} surface start`),
4589
- end: mapOne(coordinate(replacement["end"]), mapping, `${source.type} ${source.seq} surface end`)
4590
- };
4591
- }
4592
- return {
4593
- ...event,
4594
- seq: targetSeq,
4595
- data: remapPayloadReferences(source, mapping),
4596
- ...sources,
4597
- ...operation === void 0 ? {} : { surfaceOp: operation }
4598
- };
4599
- }
4600
- function remapPayloadReferences(event, mapping) {
4601
- const data = record$1(event.data);
4602
- switch (event.type) {
4603
- case "command/done": return data["sourceEventSeq"] === void 0 ? data : {
4604
- ...data,
4605
- sourceEventSeq: mapOne(coordinate(data["sourceEventSeq"]), mapping, `command/done ${event.seq} sourceEventSeq`)
4606
- };
4607
- case "compaction/prune":
4608
- case "compaction/summary": {
4609
- const range = record$1(data["shadowedRange"]);
4610
- return {
4611
- ...data,
4612
- shadowedRange: {
4613
- start: mapOne(coordinate(range["start"]), mapping, `${event.type} ${event.seq} shadowedRange start`),
4614
- end: mapOne(coordinate(range["end"]), mapping, `${event.type} ${event.seq} shadowedRange end`)
4615
- },
4616
- shadowedSeqs: mapList(numberArray(data["shadowedSeqs"]), mapping, `${event.type} ${event.seq} shadowedSeqs`)
4617
- };
4618
- }
4619
- case "session/title":
4620
- case "session/title-llm-request": return {
4621
- ...data,
4622
- messageSeqs: mapList(numberArray(data["messageSeqs"]), mapping, `${event.type} ${event.seq} messageSeqs`)
4623
- };
4624
- default: return data;
4625
- }
4626
- }
4627
- function mapList(values, mapping, label) {
4628
- return values.map((value) => mapOne(value, mapping, label));
4629
- }
4630
- function mapOne(value, mapping, label) {
4631
- const mapped = mapping.get(value);
4632
- if (mapped === void 0) throw refusal(`${label} targets consumed assistant/chunk ${value}`);
4633
- return mapped;
4634
- }
4635
- function record$1(value) {
4636
- return value;
4637
- }
4638
- function numberArray(value) {
4639
- return value;
4640
- }
4641
- function coordinate(value) {
4642
- return value;
4643
- }
4644
- function refusal(message) {
4645
- return new SessionFormatUnsupportedMigrationError$1(message);
4646
- }
4647
- //#endregion
4648
- //#region ../session-format-v2-to-v3/src/payload.ts
4649
- /** Audited V2 migration admission and V3 payload validation, independent of installed core Session types. */
4650
- /** Audited surface event names; all other admitted events are log-only. */
4651
- const SURFACE_TYPES = new Set([
4652
- "system/message",
4653
- "user/message",
4654
- "assistant/message",
4655
- "tool/result"
4656
- ]);
4657
- const SOURCE_KINDS = new Set([
4658
- "user",
4659
- "plugin",
4660
- "model",
4661
- "tool",
4662
- "agent-instructions",
4663
- "session-reference",
4664
- "team-message",
4665
- "goal",
4666
- "skill-invocation",
4667
- "skill-catalog",
4668
- "coordinator",
4669
- "subagent-report",
4670
- "subagent-settled",
4671
- "webhook",
4672
- "agent-message"
4673
- ]);
4674
- /**
4675
- * Require a JSON object at the durable input boundary.
4676
- * @param value - decoded value.
4677
- * @param label - diagnostic subject.
4678
- * @returns the narrowed object.
4679
- */
4680
- function record(value, label) {
4681
- if (!isSessionFormatJsonObject(value)) throw new SessionFormatError(label + " must be an object");
4682
- return value;
4683
- }
4684
- /**
4685
- * Reject missing and unaudited members rather than guessing whether they contain coordinates.
4686
- * @param value - decoded record.
4687
- * @param required - required member names.
4688
- * @param optional - additional admitted names.
4689
- * @param label - diagnostic subject.
4690
- */
4691
- function keys(value, required, optional, label) {
4692
- const missing = required.find((key) => !Object.hasOwn(value, key));
4693
- const unexpected = Object.keys(value).find((key) => !required.includes(key) && !optional.includes(key));
4694
- if (missing !== void 0) throw new SessionFormatError(label + " lacks required field " + missing);
4695
- if (unexpected !== void 0) throw new SessionFormatError(label + " has unexpected field " + unexpected);
4696
- }
4697
- /**
4698
- * Validate classified payloads before migration, or native V3 system/header payloads.
4699
- * @param event - decoded logical event.
4700
- * @param version - source or target generation.
4701
- */
4702
- function assertEvent(event, version) {
4703
- if (version === 3) {
4704
- assertV3Event(event);
4705
- return;
4706
- }
4707
- const disposition = RELEASED_V2_EVENT_DISPOSITIONS[event.type];
4708
- const feedback = event.type === "feedback/message-put" || event.type === "feedback/message-delete";
4709
- if (disposition === void 0 && !feedback) throw new SessionFormatUnsupportedMigrationError$1("format v2 to v3 cannot safely transform unclassified event " + event.type);
4710
- const surface = SURFACE_TYPES.has(event.type);
4711
- keys(event, [
4712
- "type",
4713
- "seq",
4714
- "time",
4715
- "data"
4716
- ], surface ? [
4717
- "ignorable",
4718
- "sourceEventSeqs",
4719
- "surfaceOp"
4720
- ] : ["ignorable"], event.type);
4721
- sessionFormatCount(event.seq, "event seq");
4722
- sessionFormatSafeInteger(event.time, "event time");
4723
- if (event["ignorable"] !== void 0 && event["ignorable"] !== true) throw new SessionFormatError("ignorable must be true");
4724
- if (surface) {
4725
- assertReleasedSurfaceMetadata(event, event.seq, event.type, "forbid-assistant");
4726
- if (event["surfaceOp"] === void 0) throw new SessionFormatError(event.type + " requires surfaceOp");
4727
- }
4728
- const data = record(event.data, event.type + " data");
4729
- if (feedback) {
4730
- assertFeedback(event.type, data);
4731
- return;
4732
- }
4733
- const admitted = disposition;
4734
- keys(data, admitted.required, admitted.optional, event.type + " data");
4735
- assertOwnedContent(event, data);
4736
- if (event.type !== "assistant/attempt") assertReleasedPayloadSemantics(event, version);
4737
- if (event.type === "assistant/message" || event.type === "assistant/attempt") {
4738
- for (const coordinate of ["turn", "step"]) if (sessionFormatCount(data[coordinate], coordinate) === 0) throw new SessionFormatError(coordinate + " must be positive");
4739
- }
4740
- if (event.type === "session/end-seed" && data["inherited"] !== void 0 && data["inherited"] !== true) throw new SessionFormatError("session/end-seed inherited must be true");
4741
- if (event.type === "user/message") assertSource(data);
4742
- if (event.type === "assistant/message" || event.type === "tool/result") assertSource(record(data["message"], "message"));
4743
- if (event.type === "tool/result" && isSessionFormatJsonObject(data["error"]) && data["error"]["code"] === "TOOL_NOT_STARTED") {
4744
- const message = record(data["message"], "tool result message");
4745
- const source = record(message["source"], "tool result source");
4746
- if (!isRepairIdentity(message["id"], source["callId"])) throw new SessionFormatError("TOOL_NOT_STARTED repair requires its canonical historical message id");
4747
- }
4748
- if (event.type === "agent/inbox/spliced" || event.type === "session/title-llm-request") {
4749
- const messages = data[event.type === "agent/inbox/spliced" ? "inserted" : "messages"];
4750
- for (const message of messages) assertSource(message);
4751
- }
4752
- }
4753
- /**
4754
- * Recognize stable generated repair IDs without interpreting their historical suffix as a current coordinate.
4755
- * @param id - durable message identity.
4756
- * @param callId - advertised tool identity.
4757
- * @returns whether the identity has the canonical historical repair form.
4758
- */
4759
- function isRepairIdentity(id, callId) {
4760
- if (typeof callId !== "string") return false;
4761
- const prefix = "interrupted-tool-result-" + callId + "-";
4762
- if (typeof id !== "string" || !id.startsWith(prefix)) return false;
4763
- const suffix = id.slice(prefix.length);
4764
- return /^(0|[1-9]\d*)$/.test(suffix) && Number.isSafeInteger(Number(suffix));
4765
- }
4766
- function assertSource(message) {
4767
- const source = record(message["source"], "message source");
4768
- if (typeof source["kind"] !== "string" || !SOURCE_KINDS.has(source["kind"])) throw new SessionFormatUnsupportedMigrationError$1("cannot safely transform unclassified message source");
4769
- if (source["kind"] === "agent-message") {
4770
- keys(source, [
4771
- "kind",
4772
- "form",
4773
- "senderSessionId"
4774
- ], [], "agent-message source");
4775
- if (source["form"] !== "relay" || typeof source["senderSessionId"] !== "string" || source["senderSessionId"].length === 0) throw new SessionFormatError("agent-message source requires relay form and senderSessionId");
4776
- }
4777
- }
4778
- const CONTENT_KINDS = new Set([
4779
- "text",
4780
- "reasoning",
4781
- "image",
4782
- "file",
4783
- "tool-call",
4784
- "tool-result"
4785
- ]);
4786
- function contentArray(value, label) {
4787
- if (!Array.isArray(value)) throw new SessionFormatError(label + ": content must be an array");
4788
- return value;
4789
- }
4790
- function assertOwnedContent(event, data) {
4791
- const label = "format v2 " + event.type + " at seq " + String(event.seq) + " data";
4792
- switch (event.type) {
4793
- case "user/message":
4794
- case "tool/code-dispatch":
4795
- assertContentKinds(data["content"], label + ".content");
4796
- break;
4797
- case "assistant/message":
4798
- case "tool/result":
4799
- case "team/message/queued":
4800
- assertContentKinds(record(data["message"], label + ".message")["content"], label + ".message.content");
4801
- break;
4802
- case "agent/inbox/spliced":
4803
- case "session/title-llm-request": {
4804
- const field = event.type === "agent/inbox/spliced" ? "inserted" : "messages";
4805
- for (const [index, value] of contentArray(data[field], label + "." + field).entries()) {
4806
- const path = label + "." + field + "[" + String(index) + "]";
4807
- assertContentKinds(record(value, path)["content"], path + ".content");
4808
- }
4809
- break;
4810
- }
4811
- case "compaction/summary":
4812
- assertContentKinds(data["summary"], label + ".summary");
4813
- if (data["rawOutput"] !== void 0) assertContentKinds(data["rawOutput"], label + ".rawOutput");
4814
- break;
4815
- }
4816
- if (event.type === "assistant/message" || event.type === "assistant/attempt") for (const [index, value] of contentArray(data["stream"], label + ".stream").entries()) {
4817
- const path = label + ".stream[" + String(index) + "]";
4818
- const entry = record(value, path);
4819
- if (entry["type"] !== "chunk") continue;
4820
- const chunk = record(entry["chunk"], path + ".chunk");
4821
- if (chunk["type"] === "block-end") assertContentBlock(chunk["block"], path + ".chunk.block");
4822
- if (chunk["type"] === "block-start") assertContentKind(chunk["blockType"], path + ".chunk.blockType");
4823
- }
4824
- }
4825
- function assertContentKind(kind, label) {
4826
- if (typeof kind !== "string" || !CONTENT_KINDS.has(kind)) throw new SessionFormatUnsupportedMigrationError$1(label + ": cannot safely transform unclassified message content kind " + JSON.stringify(kind));
4827
- }
4828
- function assertContentKinds(content, label) {
4829
- for (const [index, value] of contentArray(content, label).entries()) assertContentBlock(value, label + "[" + String(index) + "]");
4830
- }
4831
- function assertContentBlock(value, label) {
4832
- const block = record(value, label);
4833
- assertContentKind(block["type"], label);
4834
- if (block["type"] === "tool-result") {
4835
- if (!Array.isArray(block["content"])) throw new SessionFormatError(label + ".content: invalid message content kind \"tool-result\": content must be an array");
4836
- assertContentKinds(block["content"], label + ".content");
4837
- }
4838
- if (block["type"] === "file") {
4839
- keys(block, ["type", "attachment"], [], label + " kind \"file\"");
4840
- const attachment = record(block["attachment"], label + " kind \"file\" attachment");
4841
- keys(attachment, [
4842
- "attachmentId",
4843
- "name",
4844
- "bytes"
4845
- ], [], label + " kind \"file\" attachment");
4846
- 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");
4847
- sessionFormatCount(attachment["bytes"], label + " kind \"file\" attachment bytes");
4848
- return;
4849
- }
4850
- const probe = {
4851
- type: "user/message",
4852
- seq: 0,
4853
- time: 0,
4854
- data: {
4855
- id: "content-admission",
4856
- role: "user",
4857
- source: { kind: "user" },
4858
- content: [block["type"] === "tool-result" ? {
4859
- ...block,
4860
- content: []
4861
- } : block]
4862
- }
4863
- };
4864
- try {
4865
- assertReleasedPayloadSemantics(probe, 2);
4866
- } catch (error) {
4867
- throw new SessionFormatError(label + ": invalid message content kind " + JSON.stringify(block["type"]) + ": " + String(error));
4868
- }
4869
- }
4870
- /**
4871
- * Reject V3 structural payload violations even beyond a recoverable physical-row failure.
4872
- * @param value - raw physical row; ordinary rows retain the frozen decoder's recovery policy.
4873
- */
4874
- function assertV3StructuralRow(value) {
4875
- if (typeof value !== "object" || value === null || Array.isArray(value)) return;
4876
- const row = value;
4877
- if (row["type"] === "request/header") {
4878
- const data = record(row["data"], "request/header data");
4879
- if (Object.hasOwn(record(data["header"], "request header"), "system")) throw new SessionFormatUnsupportedMigrationError$1("format v3 request/header rejects retired header.system");
4880
- } else if (row["type"] === "system/message") {
4881
- const data = record(row["data"], "system/message data");
4882
- assertSystem({
4883
- type: "system/message",
4884
- seq: 0,
4885
- time: 0,
4886
- data
4887
- }, data);
4888
- }
4889
- }
4890
- function assertSystem(event, data) {
4891
- keys(data, [
4892
- "turn",
4893
- "step",
4894
- "message"
4895
- ], [], "system/message data");
4896
- for (const coordinate of ["turn", "step"]) if (sessionFormatCount(data[coordinate], coordinate) === 0) throw new SessionFormatError(coordinate + " must be positive");
4897
- const message = record(data["message"], "system message");
4898
- keys(message, [
4899
- "id",
4900
- "role",
4901
- "source",
4902
- "content"
4903
- ], [], "system message");
4904
- if (typeof message["id"] !== "string" || message["id"].length === 0 || message["role"] !== "system") throw new SessionFormatError("system message requires an id and system role");
4905
- const source = record(message["source"], "system source");
4906
- if (source["kind"] !== "plugin" || typeof source["plugin"] !== "string" || source["plugin"].length === 0) throw new SessionFormatError("system message requires plugin source");
4907
- assertReleasedPayloadSemantics({
4908
- ...event,
4909
- type: "user/message",
4910
- data: {
4911
- ...message,
4912
- role: "user"
4913
- }
4914
- }, 3);
4915
- }
4916
- function assertFeedback(type, data) {
4917
- keys(data, type === "feedback/message-put" ? ["sessionId", "item"] : ["sessionId", "messageId"], [], type);
4918
- if (typeof data["sessionId"] !== "string") throw new SessionFormatError("feedback sessionId must be a string");
4919
- if (type === "feedback/message-delete") {
4920
- if (typeof data["messageId"] !== "string") throw new SessionFormatError("feedback messageId must be a string");
4921
- return;
4922
- }
4923
- const item = record(data["item"], "feedback item");
4924
- keys(item, [
4925
- "messageId",
4926
- "rating",
4927
- "version",
4928
- "createdAt",
4929
- "updatedAt"
4930
- ], ["note"], "feedback item");
4931
- for (const key of ["messageId", "version"]) if (typeof item[key] !== "string") throw new SessionFormatError("feedback " + key + " must be a string");
4932
- if (item["rating"] !== "positive" && item["rating"] !== "negative") throw new SessionFormatError("invalid feedback rating");
4933
- if (item["note"] !== void 0 && typeof item["note"] !== "string") throw new SessionFormatError("feedback note must be a string");
4934
- sessionFormatCount(item["createdAt"], "feedback createdAt");
4935
- sessionFormatCount(item["updatedAt"], "feedback updatedAt");
4936
- }
4937
- /**
4938
- * Validate one canonical V3 event without interpreting plugin-owned payloads or log relationships.
4939
- * Unclassified metadata is deferred to vocabulary-aware restoration; unknown required types must not become recoverable corruption.
4940
- * @param event - decoded logical event.
4941
- * @param knownEventTypes - additional installed event types whose envelopes are interpreted.
4942
- */
4943
- function assertV3Event(event, knownEventTypes) {
4944
- const value = record(event, "format v3 event");
4945
- const subject = `format v3 ${event.type} at seq ${event.seq}`;
4946
- 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));
4947
- keys(value, [
4948
- "type",
4949
- "seq",
4950
- "time",
4951
- "data"
4952
- ], SURFACE_TYPES.has(event.type) || opaque ? [
4953
- "ignorable",
4954
- "surfaceOp",
4955
- "sourceEventSeqs"
4956
- ] : ["ignorable"], subject);
4957
- if (typeof event.type !== "string") throw new SessionFormatError(`${subject} type must be a string`);
4958
- sessionFormatCount(event.seq, `${subject} seq`);
4959
- sessionFormatSafeInteger(event.time, `${subject} time`);
4960
- if (Object.hasOwn(value, "ignorable") && value["ignorable"] !== true) throw new SessionFormatError(`${subject} ignorable must be true when present`);
4961
- if (SURFACE_TYPES.has(event.type)) {
4962
- const operation = value["surfaceOp"];
4963
- if (operation === void 0) throw new SessionFormatError(`${subject} requires a surfaceOp marker`);
4964
- if (operation !== "append") {
4965
- const replace = record(operation, `${subject} surfaceOp`);
4966
- 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`);
4967
- 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`);
4968
- }
4969
- const sources = value["sourceEventSeqs"];
4970
- if (event.type === "assistant/message" && sources !== void 0) throw new SessionFormatError(`${subject} embeds its stream and cannot carry sourceEventSeqs`);
4971
- if (sources !== void 0) {
4972
- if (!Array.isArray(sources) || sources.length === 0) throw new SessionFormatError(`${subject} sourceEventSeqs must be a non-empty array`);
4973
- const seen = /* @__PURE__ */ new Set();
4974
- for (const source of sources) {
4975
- const seq = sessionFormatCount(source, `${subject} sourceEventSeqs member`);
4976
- if (seq >= event.seq || seen.has(seq)) throw new SessionFormatError(`${subject} sourceEventSeqs must be unique earlier seqs`);
4977
- seen.add(seq);
4978
- }
4979
- }
4980
- }
4981
- assertV3StructuralRow(event);
4982
- assertCanonicalPayload(event);
4983
- }
4984
- function assertCanonicalPayload(event) {
4985
- const subject = `format v3 ${event.type} at seq ${event.seq}`;
4986
- if (event.type === "request/header") {
4987
- const header = record(record(event.data, `${subject} data`)["header"], `${subject} header`);
4988
- 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`);
4989
- }
4990
- if (event.type !== "tool/result") return;
4991
- const data = record(event.data, `${subject} data`);
4992
- if (data["error"] === void 0) return;
4993
- const content = record(data["message"], `${subject} message`)["content"];
4994
- 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`);
4995
- }
4996
- /**
4997
- * Canonicalize structurally transformed events without changing their target coordinates.
4998
- * @param event - transformed event using released replacement names and target coordinates.
4999
- * @returns a V3 event sharing all unchanged payloads and reference values.
5000
- */
5001
- function canonicalizeTransformedEvent(event) {
5002
- let target = event;
5003
- const operation = event["surfaceOp"];
5004
- if (operation !== void 0 && operation !== "append") {
5005
- const replace = record(operation, `format v2 ${event.type} at seq ${event.seq} surfaceOp`);
5006
- 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`);
5007
- target = {
5008
- ...event,
5009
- surfaceOp: {
5010
- op: "replace",
5011
- startSeq: sessionFormatCount(replace["start"], `format v2 ${event.type} at seq ${event.seq} replace start`),
5012
- endSeq: sessionFormatCount(replace["end"], `format v2 ${event.type} at seq ${event.seq} replace end`)
5013
- }
5014
- };
5015
- }
5016
- if (event.type === "request/header") {
5017
- const data = record(event.data, `format v2 request/header at seq ${event.seq} data`);
5018
- const header = record(data["header"], `format v2 request/header at seq ${event.seq} header`);
5019
- 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);
5020
- if (empty.length > 0) {
5021
- const canonical = Object.fromEntries(Object.entries(header).filter(([key]) => !empty.includes(key)));
5022
- target = {
5023
- ...target,
5024
- data: {
5025
- ...data,
5026
- header: canonical
5027
- }
5028
- };
5029
- }
5030
- }
5031
- assertV3Event(target);
5032
- return target;
5033
- }
5034
- //#endregion
5035
- //#region ../session-format-v2-to-v3/src/validation.ts
5036
- /** Native V3 system-head validation with a private view for frozen non-system relationships. */
5037
- /**
5038
- * Validate v3 logical metadata with the released-v2 fields.
5039
- * @param header - decoded v3 Session header.
5040
- */
5041
- function assertReleasedV3Header(header) {
5042
- if (header.version !== 3) throw new SessionFormatError("expected format v3 header");
5043
- assertReleasedV2Header({
5044
- ...header,
5045
- version: 2
5046
- });
5047
- }
5048
- /**
5049
- * Refuse required predecessor PTC tags without interpreting native extension payloads.
5050
- * @param event - event envelope whose type and ignorable admission markers are available.
5051
- */
5052
- function assertV3EventAdmission(event) {
5053
- if ((event.type === "tool/code-dispatch-start" || event.type === "tool/code-dispatch") && event["ignorable"] !== true) throw new SessionFormatUnsupportedMigrationError$1("format v3 contains unknown event type " + JSON.stringify(event.type) + " at seq " + String(event.seq));
5054
- }
5055
- Object.freeze({
5056
- version: 3,
5057
- decodeHeader(value) {
5058
- return {
5059
- ...releasedV2SessionFormatCodec.decodeHeader(v2PhysicalHeader(value)),
5060
- version: 3
5061
- };
5062
- },
5063
- createDecoder(value, recovery) {
5064
- const decoder = releasedV2SessionFormatCodec.createDecoder(v2PhysicalHeader(value), recovery);
5065
- let issue;
5066
- let acceptedInheritedCut;
5067
- return {
5068
- header: {
5069
- ...decoder.header,
5070
- version: 3
5071
- },
5072
- decodeRow(row, context) {
5073
- assertV3RowAdmission(row);
5074
- decoder.decodeRow(row, {
5075
- emitRun: context.emitRun.bind(context),
5076
- emitEvent(event) {
5077
- assertV3EventAdmission(event);
5078
- if (issue === void 0) try {
5079
- assertV3Event(event);
5080
- } catch (error) {
5081
- const invalid = error;
5082
- if (recovery === "strict") throw invalid;
5083
- issue = invalid;
5084
- }
5085
- if (issue !== void 0) {
5086
- if (event.type === "turn/end") throw issue;
5087
- return;
5088
- }
5089
- if (event.type === "session/end-seed" && isSessionFormatJsonObject(event.data) && event.data["inherited"] === true) acceptedInheritedCut = event.seq;
5090
- context.emitEvent(event);
5091
- }
5092
- });
5093
- },
5094
- finish(context) {
5095
- if (issue === void 0) return decoder.finish(context);
5096
- if (decoder.header.isSeeded && acceptedInheritedCut === void 0) throw new SessionFormatError("format v3 seeded Session lacks an accepted inherited end-seed marker");
5097
- if (!decoder.header.isSeeded && acceptedInheritedCut !== void 0) throw new SessionFormatError("format v3 unseeded Session contains an inherited end-seed marker");
5098
- return acceptedInheritedCut ?? 0;
5099
- }
5100
- };
5101
- },
5102
- encodeHeader(header, inheritedEventCount) {
5103
- assertReleasedV3Header(header);
5104
- return {
5105
- ...releasedV2SessionFormatCodec.encodeHeader({
5106
- ...header,
5107
- version: 2
5108
- }, inheritedEventCount),
5109
- version: 3
5110
- };
5111
- },
5112
- encodeEvent(event) {
5113
- assertV3EventAdmission(event);
5114
- assertV3Event(event);
5115
- return releasedV2SessionFormatCodec.encodeEvent(event);
5116
- }
5117
- });
5118
- /**
5119
- * Validate owned V3 admission rules before a scanner or codec can discard a recoverable tail.
5120
- * This checks only identified structural payloads; physical source-event ranges still belong to decoding.
5121
- * @param row - parsed physical row, before envelope or compressed-range decoding.
5122
- */
5123
- function assertV3RowAdmission(row) {
5124
- assertV3StructuralRow(row);
5125
- if (typeof row === "object" && row !== null && !Array.isArray(row)) assertV3EventAdmission(row);
5126
- }
5127
- function v2PhysicalHeader(value) {
5128
- const header = snapshotSessionFormatJson(value, "format v3 physical header");
5129
- if (!isSessionFormatJsonObject(header) || header["version"] !== 3) throw new SessionFormatError("expected format v3 physical Session header");
5130
- return {
5131
- ...header,
5132
- version: 2
5133
- };
5134
- }
5135
- //#endregion
5136
- //#region ../session-format-v2-to-v3/src/references.ts
5137
- /** Explicit local-coordinate remapping; captured generations and owner-local counters remain opaque. */
5138
- /**
5139
- * Remap only audited same-artifact references, preserving IDs and embedded model input.
5140
- * @param event - validated source event.
5141
- * @param seq - output event position.
5142
- * @param mapping - earlier source positions mapped to output positions.
5143
- * @returns the event in target coordinates.
5144
- */
5145
- function remapEvent(event, seq, mapping) {
5146
- const one = (value) => {
5147
- const source = sessionFormatCount(value, "source event reference");
5148
- const target = mapping[source];
5149
- if (source >= event.seq || target === void 0) throw new SessionFormatError("reference must name an earlier source event");
5150
- return target;
5151
- };
5152
- const list = (value) => {
5153
- if (!Array.isArray(value)) throw new SessionFormatError("sequence references must be an array");
5154
- return value.map(one);
5155
- };
5156
- const range = (value) => {
5157
- const source = record(value, "sequence range");
5158
- return {
5159
- ...source,
5160
- start: one(source["start"]),
5161
- end: one(source["end"])
5162
- };
5163
- };
5164
- let data = record(event.data, event.type);
5165
- switch (event.type) {
5166
- case "command/done":
5167
- if (data["sourceEventSeq"] !== void 0) data = {
5168
- ...data,
5169
- sourceEventSeq: one(data["sourceEventSeq"])
5170
- };
5171
- break;
5172
- case "compaction/summary":
5173
- case "compaction/prune":
5174
- data = {
5175
- ...data,
5176
- shadowedRange: range(data["shadowedRange"]),
5177
- shadowedSeqs: list(data["shadowedSeqs"])
5178
- };
5179
- break;
5180
- case "session/title":
5181
- case "session/title-llm-request":
5182
- data = {
5183
- ...data,
5184
- messageSeqs: list(data["messageSeqs"])
5185
- };
5186
- break;
5187
- }
5188
- return {
5189
- ...event,
5190
- seq,
5191
- data,
5192
- ...event["sourceEventSeqs"] === void 0 ? {} : { sourceEventSeqs: list(event["sourceEventSeqs"]) },
5193
- ...event["surfaceOp"] === void 0 || event["surfaceOp"] === "append" ? {} : { surfaceOp: range(event["surfaceOp"]) }
5194
- };
5195
- }
5196
- defineSessionFormatMigration({
5197
- name: "@x1a0f3n9/dsh-session-format-v2-to-v3",
5198
- fromVersion: 2,
5199
- toVersion: 3,
5200
- migrateHeader(header) {
5201
- assertReleasedV2Header(header);
5202
- return {
5203
- ...header,
5204
- version: 3,
5205
- ...header.agentPreset === "code" ? { agentPreset: "ptc" } : {}
5206
- };
5207
- },
5208
- createStage(input) {
5209
- return new ReleasedV2ToV3Stage(input);
5210
- },
5211
- validateTargetHeader: assertReleasedV3Header
5212
- });
5213
- var ReleasedV2ToV3Stage = class {
5214
- input;
5215
- headerInheritedEventCount;
5216
- mapping = [];
5217
- originalIds = /* @__PURE__ */ new Set();
5218
- generatedIds = /* @__PURE__ */ new Set();
5219
- targetSeq = 0;
5220
- sourceCut;
5221
- targetCut;
5222
- lastForeignDeliverySeq;
5223
- step;
5224
- head;
5225
- prompt = "";
5226
- constructor(input) {
5227
- this.input = input;
5228
- assertReleasedV2Header(input.sourceHeader);
5229
- this.sourceCut = input.sourceHeader.isSeeded ? void 0 : 0;
5230
- this.targetCut = input.sourceHeader.isSeeded ? void 0 : 0;
5231
- if (!input.sourceHeader.isSeeded) this.headerInheritedEventCount = 0;
5232
- }
5233
- transformEvent(event, context) {
5234
- if (event.seq !== this.mapping.length) throw new SessionFormatError("format v2 source events must be dense");
5235
- assertEvent(event, 2);
5236
- this.observeMessageIds(event);
5237
- let source = event;
5238
- const data = record(event.data, event.type);
5239
- if (event.type === "request/header") {
5240
- const { system, ...header } = record(data["header"], "request header");
5241
- const prompt = typeof system === "string" ? system : "";
5242
- if (prompt !== this.prompt) this.emitSystem(prompt, event, context);
5243
- source = {
5244
- ...event,
5245
- data: {
5246
- ...data,
5247
- header
5248
- }
5249
- };
5250
- }
5251
- if (SURFACE_TYPES.has(event.type) && this.head === void 0) throw new SessionFormatUnsupportedMigrationError$1("format v2 surface before first step cannot acquire a system head without changing chronology");
5252
- if (event.type === "session/end-seed" && data["inherited"] === true) {
5253
- if (!this.input.sourceHeader.isSeeded) throw new SessionFormatError("format v2 unseeded Session contains an inherited end-seed marker");
5254
- this.sourceCut = event.seq;
5255
- this.targetCut = this.targetSeq;
5256
- }
5257
- if (event.type === "session-log-deepseek/delivery-accepted") {
5258
- if (data["sessionFormatVersion"] === 3) throw new SessionFormatError("format v2 delivery marker claims target format v3");
5259
- if (data["sessionFormatVersion"] === 2 && data["sessionId"] !== this.input.sourceHeader.id) this.lastForeignDeliverySeq = event.seq;
5260
- }
5261
- const target = remapEvent(source, this.targetSeq, this.mapping);
5262
- this.mapping.push(this.targetSeq++);
5263
- context.emitEvent(canonicalizeTransformedEvent(renamePtcEvent(target)));
5264
- if (event.type === "step/start") {
5265
- this.step = {
5266
- turn: data["turn"],
5267
- step: data["step"]
5268
- };
5269
- if (this.head === void 0) this.emitSystem("", event, context);
5270
- } else if (event.type === "step/end" || event.type === "turn/end") this.step = void 0;
5271
- }
5272
- transformRun(run, context) {
5273
- for (const event of run.expand()) this.transformEvent(event, context);
5274
- }
5275
- finish(_context) {
5276
- const cut = sessionFormatCount(this.sourceCut, "format v2 inherited end-seed marker");
5277
- if (this.input.sourceInheritedEventCount !== void 0 && this.input.sourceInheritedEventCount !== cut) throw new SessionFormatError("format v2 inherited end-seed marker disagrees with its source cut");
5278
- 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");
5279
- return sessionFormatCount(this.targetCut, "format v3 inherited event count");
5280
- }
5281
- observeMessageIds(event) {
5282
- const data = record(event.data, event.type);
5283
- 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"] : [];
5284
- for (const message of messages) {
5285
- const id = message["id"];
5286
- if (this.generatedIds.has(id)) throw new SessionFormatUnsupportedMigrationError$1("source message id collides with a generated system message id");
5287
- this.originalIds.add(id);
5288
- }
5289
- }
5290
- emitSystem(prompt, anchor, context) {
5291
- if (this.step === void 0) throw new SessionFormatUnsupportedMigrationError$1("format v2 changed request prompt outside an open step cannot retain source chronology");
5292
- const identity = JSON.stringify([
5293
- "session-format-v2-to-v3",
5294
- this.input.sourceHeader.id,
5295
- anchor.seq,
5296
- anchor.type
5297
- ]);
5298
- const id = "v2-to-v3-system-" + createHash("sha256").update(identity).digest("hex");
5299
- if (this.originalIds.has(id) || this.generatedIds.has(id)) throw new SessionFormatUnsupportedMigrationError$1("generated system message id collides with an existing message id");
5300
- this.generatedIds.add(id);
5301
- const seq = this.targetSeq++;
5302
- context.emitEvent(canonicalizeTransformedEvent({
5303
- type: "system/message",
5304
- seq,
5305
- time: anchor.time,
5306
- data: {
5307
- ...this.step,
5308
- message: {
5309
- id,
5310
- role: "system",
5311
- source: {
5312
- kind: "plugin",
5313
- plugin: "@x1a0f3n9/dsh-system-prompt"
5314
- },
5315
- content: prompt === "" ? [] : [{
5316
- type: "text",
5317
- text: prompt
5318
- }]
5319
- }
5320
- },
5321
- ...this.head === void 0 ? { surfaceOp: "append" } : {
5322
- surfaceOp: {
5323
- op: "replace",
5324
- start: this.head,
5325
- end: this.head
5326
- },
5327
- sourceEventSeqs: [this.head]
5328
- }
5329
- }));
5330
- this.head = seq;
5331
- this.prompt = prompt;
5332
- }
5333
- };
5334
- /** Source admission precedes renaming, so these payloads have exact audited fields. */
5335
- function renamePtcEvent(event) {
5336
- switch (event.type) {
5337
- case "agent-preset/selected": return event.data["agentPreset"] === "code" ? {
5338
- ...event,
5339
- data: {
5340
- ...event.data,
5341
- agentPreset: "ptc"
5342
- }
5343
- } : event;
5344
- case "tool/code-dispatch-start": return {
5345
- ...event,
5346
- type: "tool/ptc-dispatch-start"
5347
- };
5348
- case "tool/code-dispatch": return {
5349
- ...event,
5350
- type: "tool/ptc-dispatch"
5351
- };
5352
- case "user/message": {
5353
- const data = renameMessageSource(event.data);
5354
- return data === event.data ? event : {
5355
- ...event,
5356
- data
5357
- };
5358
- }
5359
- case "agent/inbox/spliced":
5360
- case "session/title-llm-request": {
5361
- const data = event.data;
5362
- const key = event.type === "agent/inbox/spliced" ? "inserted" : "messages";
5363
- const messages = data[key];
5364
- const renamed = messages.map(renameMessageSource);
5365
- return renamed.every((message, index) => message === messages[index]) ? event : {
5366
- ...event,
5367
- data: {
5368
- ...data,
5369
- [key]: renamed
5370
- }
5371
- };
5372
- }
5373
- default: return event;
5374
- }
5375
- }
5376
- function renameMessageSource(message) {
5377
- const source = message["source"];
5378
- if (source["kind"] !== "plugin" || source["plugin"] !== "tools-code-mode") return message;
5379
- return {
5380
- ...message,
5381
- source: {
5382
- ...source,
5383
- plugin: "tools-ptc"
5384
- }
5385
- };
5386
- }
5387
- //#endregion
5388
1517
  //#region lib/types/history-suffix.js
5389
1518
  /**
5390
1519
  * Tail-page decoder for JSONL session logs. It scans cheap Zstandard frame
@@ -5700,7 +1829,7 @@ function parseEventRecords(plaintext) {
5700
1829
  * @returns the logical event used for pagination.
5701
1830
  */
5702
1831
  function decodeSuffixEvent(parsed) {
5703
- assertV3RowAdmission(parsed);
1832
+ assertV4RowAdmission(parsed, KNOWN_SESSION_EVENT_TYPES);
5704
1833
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("corrupt session log: event row is not a JSON object");
5705
1834
  const record = parsed;
5706
1835
  if (record["sourceEventSeqs"] === void 0) return record;