@sumeru/server 0.1.0

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 (95) hide show
  1. package/LICENSE +18 -0
  2. package/dist/.build-fingerprint +1 -0
  3. package/dist/config.d.ts +14 -0
  4. package/dist/config.d.ts.map +1 -0
  5. package/dist/config.js +142 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/envelope.d.ts +28 -0
  8. package/dist/envelope.d.ts.map +1 -0
  9. package/dist/envelope.js +43 -0
  10. package/dist/envelope.js.map +1 -0
  11. package/dist/export/bundle.d.ts +28 -0
  12. package/dist/export/bundle.d.ts.map +1 -0
  13. package/dist/export/bundle.js +78 -0
  14. package/dist/export/bundle.js.map +1 -0
  15. package/dist/export/handler.d.ts +24 -0
  16. package/dist/export/handler.d.ts.map +1 -0
  17. package/dist/export/handler.js +102 -0
  18. package/dist/export/handler.js.map +1 -0
  19. package/dist/export/index.d.ts +3 -0
  20. package/dist/export/index.d.ts.map +1 -0
  21. package/dist/export/index.js +3 -0
  22. package/dist/export/index.js.map +1 -0
  23. package/dist/handler.d.ts +24 -0
  24. package/dist/handler.d.ts.map +1 -0
  25. package/dist/handler.js +622 -0
  26. package/dist/handler.js.map +1 -0
  27. package/dist/index.d.ts +12 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +10 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/ocas/index.d.ts +3 -0
  32. package/dist/ocas/index.d.ts.map +1 -0
  33. package/dist/ocas/index.js +3 -0
  34. package/dist/ocas/index.js.map +1 -0
  35. package/dist/ocas/schemas.d.ts +41 -0
  36. package/dist/ocas/schemas.d.ts.map +1 -0
  37. package/dist/ocas/schemas.js +108 -0
  38. package/dist/ocas/schemas.js.map +1 -0
  39. package/dist/ocas/store.d.ts +58 -0
  40. package/dist/ocas/store.d.ts.map +1 -0
  41. package/dist/ocas/store.js +139 -0
  42. package/dist/ocas/store.js.map +1 -0
  43. package/dist/search/handler.d.ts +54 -0
  44. package/dist/search/handler.d.ts.map +1 -0
  45. package/dist/search/handler.js +178 -0
  46. package/dist/search/handler.js.map +1 -0
  47. package/dist/search/index.d.ts +4 -0
  48. package/dist/search/index.d.ts.map +1 -0
  49. package/dist/search/index.js +3 -0
  50. package/dist/search/index.js.map +1 -0
  51. package/dist/search/sqlite-index.d.ts +49 -0
  52. package/dist/search/sqlite-index.d.ts.map +1 -0
  53. package/dist/search/sqlite-index.js +508 -0
  54. package/dist/search/sqlite-index.js.map +1 -0
  55. package/dist/search/types.d.ts +143 -0
  56. package/dist/search/types.d.ts.map +1 -0
  57. package/dist/search/types.js +10 -0
  58. package/dist/search/types.js.map +1 -0
  59. package/dist/session/cwd.d.ts +31 -0
  60. package/dist/session/cwd.d.ts.map +1 -0
  61. package/dist/session/cwd.js +54 -0
  62. package/dist/session/cwd.js.map +1 -0
  63. package/dist/session/id.d.ts +12 -0
  64. package/dist/session/id.d.ts.map +1 -0
  65. package/dist/session/id.js +76 -0
  66. package/dist/session/id.js.map +1 -0
  67. package/dist/session/index.d.ts +5 -0
  68. package/dist/session/index.d.ts.map +1 -0
  69. package/dist/session/index.js +4 -0
  70. package/dist/session/index.js.map +1 -0
  71. package/dist/session/store.d.ts +89 -0
  72. package/dist/session/store.d.ts.map +1 -0
  73. package/dist/session/store.js +258 -0
  74. package/dist/session/store.js.map +1 -0
  75. package/dist/sse/buffer.d.ts +53 -0
  76. package/dist/sse/buffer.d.ts.map +1 -0
  77. package/dist/sse/buffer.js +119 -0
  78. package/dist/sse/buffer.js.map +1 -0
  79. package/dist/sse/index.d.ts +3 -0
  80. package/dist/sse/index.d.ts.map +1 -0
  81. package/dist/sse/index.js +3 -0
  82. package/dist/sse/index.js.map +1 -0
  83. package/dist/sse/messages.d.ts +30 -0
  84. package/dist/sse/messages.d.ts.map +1 -0
  85. package/dist/sse/messages.js +489 -0
  86. package/dist/sse/messages.js.map +1 -0
  87. package/dist/start.d.ts +22 -0
  88. package/dist/start.d.ts.map +1 -0
  89. package/dist/start.js +86 -0
  90. package/dist/start.js.map +1 -0
  91. package/dist/types.d.ts +252 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/dist/types.js +10 -0
  94. package/dist/types.js.map +1 -0
  95. package/package.json +31 -0
package/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 shazhou
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ 373997122015c31c248b73e9425555b23e813903bd21d21de38331e39e85fc1c
@@ -0,0 +1,14 @@
1
+ import type { InstanceConfig } from "./types.js";
2
+ /**
3
+ * Load and validate a `sumeru.yaml` file.
4
+ *
5
+ * Returns a fully-typed `InstanceConfig` on success. On any error — missing
6
+ * file, malformed YAML, missing required fields, wrong shapes — throws an
7
+ * `Error` whose message includes the offending field name (where applicable)
8
+ * and the source file path.
9
+ *
10
+ * Unknown keys at the top level and inside individual gateway entries are
11
+ * tolerated for forward-compatibility.
12
+ */
13
+ export declare function loadConfig(path: string): Promise<InstanceConfig>;
14
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGX,cAAc,EACd,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;GAUG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAItE"}
package/dist/config.js ADDED
@@ -0,0 +1,142 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { parse as parseYaml } from "yaml";
3
+ /**
4
+ * Load and validate a `sumeru.yaml` file.
5
+ *
6
+ * Returns a fully-typed `InstanceConfig` on success. On any error — missing
7
+ * file, malformed YAML, missing required fields, wrong shapes — throws an
8
+ * `Error` whose message includes the offending field name (where applicable)
9
+ * and the source file path.
10
+ *
11
+ * Unknown keys at the top level and inside individual gateway entries are
12
+ * tolerated for forward-compatibility.
13
+ */
14
+ export async function loadConfig(path) {
15
+ const raw = await readFileSafely(path);
16
+ const doc = parseYamlSafely(raw, path);
17
+ return validateConfig(doc, path);
18
+ }
19
+ async function readFileSafely(path) {
20
+ try {
21
+ return await readFile(path, "utf-8");
22
+ }
23
+ catch (err) {
24
+ const code = err instanceof Error && "code" in err
25
+ ? err.code
26
+ : null;
27
+ if (code === "ENOENT") {
28
+ throw new Error(`Config file not found: ${path}`);
29
+ }
30
+ const msg = err instanceof Error ? err.message : String(err);
31
+ throw new Error(`Cannot read config file ${path}: ${msg}`);
32
+ }
33
+ }
34
+ function parseYamlSafely(raw, path) {
35
+ try {
36
+ return parseYaml(raw);
37
+ }
38
+ catch (err) {
39
+ const msg = err instanceof Error ? err.message : String(err);
40
+ throw new Error(`Invalid YAML in ${path}: ${msg}`);
41
+ }
42
+ }
43
+ function validateConfig(doc, path) {
44
+ if (doc === null || typeof doc !== "object" || Array.isArray(doc)) {
45
+ throw new Error(`Config ${path} must be a YAML mapping at the top level`);
46
+ }
47
+ const obj = doc;
48
+ const name = obj.name;
49
+ if (typeof name !== "string" || name.length === 0) {
50
+ throw new Error(`Config ${path} is missing required field "name" (must be a non-empty string)`);
51
+ }
52
+ const workspaceRoot = validateWorkspaceRoot(obj.workspaceRoot, path);
53
+ const gatewaysRaw = obj.gateways;
54
+ const gateways = {};
55
+ if (gatewaysRaw !== undefined && gatewaysRaw !== null) {
56
+ if (typeof gatewaysRaw !== "object" || Array.isArray(gatewaysRaw)) {
57
+ throw new Error(`Config ${path} field "gateways" must be a mapping (got ${describeShape(gatewaysRaw)})`);
58
+ }
59
+ for (const [key, entry] of Object.entries(gatewaysRaw)) {
60
+ gateways[key] = validateGatewayEntry(entry, key, path);
61
+ }
62
+ }
63
+ return { name, workspaceRoot, gateways };
64
+ }
65
+ /**
66
+ * Validate the optional top-level `workspaceRoot` field.
67
+ *
68
+ * Absent / undefined / null → `null`. Empty string → `null` (treated as
69
+ * "operator did not configure one"). Non-empty string → returned verbatim
70
+ * (no path resolution at this layer). Any other type → throws with the
71
+ * field name and source path.
72
+ */
73
+ function validateWorkspaceRoot(raw, path) {
74
+ if (raw === undefined || raw === null)
75
+ return null;
76
+ if (typeof raw !== "string") {
77
+ throw new Error(`Config ${path} field "workspaceRoot" must be a string (got ${describeShape(raw)})`);
78
+ }
79
+ if (raw.length === 0)
80
+ return null;
81
+ return raw;
82
+ }
83
+ function validateGatewayEntry(entry, key, path) {
84
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
85
+ throw new Error(`Config ${path} gateway "${key}" must be a mapping (got ${describeShape(entry)})`);
86
+ }
87
+ const obj = entry;
88
+ const adapter = obj.adapter;
89
+ if (typeof adapter !== "string" || adapter.length === 0) {
90
+ throw new Error(`Config ${path} gateway "${key}" is missing required field "adapter" (must be a non-empty string)`);
91
+ }
92
+ const capsRaw = obj.capabilities;
93
+ if (capsRaw === undefined || capsRaw === null) {
94
+ throw new Error(`Config ${path} gateway "${key}" is missing required field "capabilities"`);
95
+ }
96
+ const capabilities = validateCapabilities(capsRaw, key, path);
97
+ const config = validateGatewayConfigBlob(obj.config, key, path);
98
+ return { adapter, capabilities, config };
99
+ }
100
+ /**
101
+ * Validate the optional adapter-specific `config:` blob on a gateway entry.
102
+ *
103
+ * Absent / undefined → `null`. Explicit `null` → `null`. Mapping (object) →
104
+ * passed through verbatim with no key validation. Anything else (number,
105
+ * string, boolean, array) → throws with the path / gateway / field name and
106
+ * the actual shape.
107
+ *
108
+ * The contents of a mapping are NOT validated here — each adapter validates
109
+ * its own option keys at construction time. See
110
+ * `specs/config-load-gateway-config-blob.md`.
111
+ */
112
+ function validateGatewayConfigBlob(raw, key, path) {
113
+ if (raw === undefined || raw === null)
114
+ return null;
115
+ if (typeof raw !== "object" || Array.isArray(raw)) {
116
+ throw new Error(`Config ${path} gateway "${key}" field "config" must be a mapping (got ${describeShape(raw)})`);
117
+ }
118
+ return raw;
119
+ }
120
+ function validateCapabilities(raw, key, path) {
121
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
122
+ throw new Error(`Config ${path} gateway "${key}" field "capabilities" must be a mapping (got ${describeShape(raw)})`);
123
+ }
124
+ const obj = raw;
125
+ const resume = obj.resume;
126
+ if (typeof resume !== "boolean") {
127
+ throw new Error(`Config ${path} gateway "${key}" field "capabilities.resume" must be a boolean`);
128
+ }
129
+ const streaming = obj.streaming;
130
+ if (typeof streaming !== "boolean") {
131
+ throw new Error(`Config ${path} gateway "${key}" field "capabilities.streaming" must be a boolean`);
132
+ }
133
+ return { resume, streaming };
134
+ }
135
+ function describeShape(value) {
136
+ if (value === null)
137
+ return "null";
138
+ if (Array.isArray(value))
139
+ return "array";
140
+ return typeof value;
141
+ }
142
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { Envelope, ErrorValue, Gateway, Instance, SearchResultValue, SessionListEntry, SessionWire } from "./types.js";
2
+ /** Wrap a value in the ocas envelope shape. */
3
+ export declare function envelope<T>(type: string, value: T): Envelope<T>;
4
+ /** Build the `@sumeru/instance` envelope for `GET /`. */
5
+ export declare function instanceEnvelope(instance: Instance): Envelope<Instance>;
6
+ /** Build the `@sumeru/gateway-list` envelope for `GET /gateways`. */
7
+ export declare function gatewayListEnvelope(gateways: Gateway[]): Envelope<Gateway[]>;
8
+ /** Build the `@sumeru/gateway` envelope for `GET /gateways/:name`. */
9
+ export declare function gatewayEnvelope(gateway: Gateway): Envelope<Gateway>;
10
+ /**
11
+ * Build the `@sumeru/session` envelope for
12
+ * `POST /gateways/:name/sessions` (201) and
13
+ * `GET /gateways/:name/sessions/:id` (200).
14
+ */
15
+ export declare function sessionEnvelope(session: SessionWire): Envelope<SessionWire>;
16
+ /**
17
+ * Build the `@sumeru/session-list` envelope for `GET /gateways/:name/sessions`.
18
+ * List entries omit `config` to keep listings compact.
19
+ */
20
+ export declare function sessionListEnvelope(sessions: SessionListEntry[]): Envelope<SessionListEntry[]>;
21
+ /** Build a `@sumeru/error` envelope for non-2xx responses. */
22
+ export declare function errorEnvelope(error: string, message: string): Envelope<ErrorValue>;
23
+ /**
24
+ * Build the `@sumeru/search-result` envelope for `GET /sessions?q=...` and
25
+ * `GET /gateways/:name/sessions?q=...`.
26
+ */
27
+ export declare function searchResultEnvelope(value: SearchResultValue): Envelope<SearchResultValue>;
28
+ //# sourceMappingURL=envelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,QAAQ,EACR,UAAU,EACV,OAAO,EACP,QAAQ,EACR,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,EACX,MAAM,YAAY,CAAC;AAEpB,+CAA+C;AAC/C,wBAAgB,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAE/D;AAED,yDAAyD;AACzD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAEvE;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,CAE5E;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAEnE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,QAAQ,CAAC,WAAW,CAAC,CAE3E;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAClC,QAAQ,EAAE,gBAAgB,EAAE,GAC1B,QAAQ,CAAC,gBAAgB,EAAE,CAAC,CAE9B;AAED,8DAA8D;AAC9D,wBAAgB,aAAa,CAC5B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACb,QAAQ,CAAC,UAAU,CAAC,CAEtB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CACnC,KAAK,EAAE,iBAAiB,GACtB,QAAQ,CAAC,iBAAiB,CAAC,CAE7B"}
@@ -0,0 +1,43 @@
1
+ /** Wrap a value in the ocas envelope shape. */
2
+ export function envelope(type, value) {
3
+ return { type, value };
4
+ }
5
+ /** Build the `@sumeru/instance` envelope for `GET /`. */
6
+ export function instanceEnvelope(instance) {
7
+ return envelope("@sumeru/instance", instance);
8
+ }
9
+ /** Build the `@sumeru/gateway-list` envelope for `GET /gateways`. */
10
+ export function gatewayListEnvelope(gateways) {
11
+ return envelope("@sumeru/gateway-list", gateways);
12
+ }
13
+ /** Build the `@sumeru/gateway` envelope for `GET /gateways/:name`. */
14
+ export function gatewayEnvelope(gateway) {
15
+ return envelope("@sumeru/gateway", gateway);
16
+ }
17
+ /**
18
+ * Build the `@sumeru/session` envelope for
19
+ * `POST /gateways/:name/sessions` (201) and
20
+ * `GET /gateways/:name/sessions/:id` (200).
21
+ */
22
+ export function sessionEnvelope(session) {
23
+ return envelope("@sumeru/session", session);
24
+ }
25
+ /**
26
+ * Build the `@sumeru/session-list` envelope for `GET /gateways/:name/sessions`.
27
+ * List entries omit `config` to keep listings compact.
28
+ */
29
+ export function sessionListEnvelope(sessions) {
30
+ return envelope("@sumeru/session-list", sessions);
31
+ }
32
+ /** Build a `@sumeru/error` envelope for non-2xx responses. */
33
+ export function errorEnvelope(error, message) {
34
+ return envelope("@sumeru/error", { error, message });
35
+ }
36
+ /**
37
+ * Build the `@sumeru/search-result` envelope for `GET /sessions?q=...` and
38
+ * `GET /gateways/:name/sessions?q=...`.
39
+ */
40
+ export function searchResultEnvelope(value) {
41
+ return envelope("@sumeru/search-result", value);
42
+ }
43
+ //# sourceMappingURL=envelope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope.js","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"AAUA,+CAA+C;AAC/C,MAAM,UAAU,QAAQ,CAAI,IAAY,EAAE,KAAQ;IACjD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACxB,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,gBAAgB,CAAC,QAAkB;IAClD,OAAO,QAAQ,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;AAC/C,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,mBAAmB,CAAC,QAAmB;IACtD,OAAO,QAAQ,CAAC,sBAAsB,EAAE,QAAQ,CAAC,CAAC;AACnD,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC/C,OAAO,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;AAC7C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAAoB;IACnD,OAAO,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;AAC7C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAClC,QAA4B;IAE5B,OAAO,QAAQ,CAAC,sBAAsB,EAAE,QAAQ,CAAC,CAAC;AACnD,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,aAAa,CAC5B,KAAa,EACb,OAAe;IAEf,OAAO,QAAQ,CAAC,eAAe,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;AACtD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CACnC,KAAwB;IAExB,OAAO,QAAQ,CAAC,uBAAuB,EAAE,KAAK,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Phase 5 — session export.
3
+ *
4
+ * Builds a self-contained `tar.gz` from a session's recording (session-meta
5
+ * + every turn + their schema chain) using `@ocas/core.exportBundle`. The
6
+ * resulting bundle can be re-imported into another ocas store via
7
+ * `importBundle` to reproduce the recording bit-for-bit.
8
+ */
9
+ import type { ServerResponse } from "node:http";
10
+ import type { OcasConfig, Session } from "../types.js";
11
+ /**
12
+ * Build a session export tar.gz on disk and return its path + node count.
13
+ *
14
+ * Caller is responsible for cleaning up the returned `tempDir`.
15
+ */
16
+ export declare function buildSessionExport(session: Session, ocas: OcasConfig): Promise<{
17
+ tarGzPath: string;
18
+ tempDir: string;
19
+ nodes: number;
20
+ }>;
21
+ /**
22
+ * Stream a previously-built tar.gz to an HTTP response with the spec headers.
23
+ *
24
+ * The temp dir is removed after the response `finish` (or `close`) event so
25
+ * concurrent exports never leak.
26
+ */
27
+ export declare function streamExportResponse(res: ServerResponse, sessionId: string, tarGzPath: string, tempDir: string, nodes: number, method: "POST" | "HEAD"): Promise<void>;
28
+ //# sourceMappingURL=bundle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../src/export/bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAKhD,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEvD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACvC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,UAAU,GACd,OAAO,CAAC;IACV,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACd,CAAC,CAaD;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACzC,GAAG,EAAE,cAAc,EACnB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GAAG,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CA6Cf"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Phase 5 — session export.
3
+ *
4
+ * Builds a self-contained `tar.gz` from a session's recording (session-meta
5
+ * + every turn + their schema chain) using `@ocas/core.exportBundle`. The
6
+ * resulting bundle can be re-imported into another ocas store via
7
+ * `importBundle` to reproduce the recording bit-for-bit.
8
+ */
9
+ import { createReadStream, mkdtempSync, statSync } from "node:fs";
10
+ import { readFile, rm, writeFile } from "node:fs/promises";
11
+ import { tmpdir } from "node:os";
12
+ import { join } from "node:path";
13
+ import { gzipSync } from "node:zlib";
14
+ import { exportBundle } from "@ocas/core";
15
+ /**
16
+ * Build a session export tar.gz on disk and return its path + node count.
17
+ *
18
+ * Caller is responsible for cleaning up the returned `tempDir`.
19
+ */
20
+ export async function buildSessionExport(session, ocas) {
21
+ const tempDir = mkdtempSync(join(tmpdir(), "sumeru-export-"));
22
+ const tarPath = join(tempDir, "bundle.tar");
23
+ const tarGzPath = `${tarPath}.gz`;
24
+ const roots = [session.metaHash, ...session.turnHashes];
25
+ const stats = await exportBundle(ocas.store, roots, tarPath);
26
+ const tarBytes = await readFile(tarPath);
27
+ const gzBytes = gzipSync(tarBytes, { level: 6 });
28
+ await writeFile(tarGzPath, gzBytes);
29
+ return { tarGzPath, tempDir, nodes: stats.nodes };
30
+ }
31
+ /**
32
+ * Stream a previously-built tar.gz to an HTTP response with the spec headers.
33
+ *
34
+ * The temp dir is removed after the response `finish` (or `close`) event so
35
+ * concurrent exports never leak.
36
+ */
37
+ export async function streamExportResponse(res, sessionId, tarGzPath, tempDir, nodes, method) {
38
+ const size = statSync(tarGzPath).size;
39
+ res.statusCode = 200;
40
+ res.setHeader("Content-Type", "application/gzip");
41
+ res.setHeader("Content-Disposition", `attachment; filename="${sessionId}.tar.gz"`);
42
+ res.setHeader("Cache-Control", "no-store");
43
+ res.setHeader("X-Sumeru-Export-Nodes", nodes.toString());
44
+ res.setHeader("X-Sumeru-Export-Session", sessionId);
45
+ res.setHeader("Content-Length", size.toString());
46
+ const cleanup = async () => {
47
+ try {
48
+ await rm(tempDir, { recursive: true, force: true });
49
+ }
50
+ catch {
51
+ // Best-effort.
52
+ }
53
+ };
54
+ res.once("close", () => {
55
+ void cleanup();
56
+ });
57
+ if (method === "HEAD") {
58
+ res.end();
59
+ await cleanup();
60
+ return;
61
+ }
62
+ await new Promise((resolve) => {
63
+ const stream = createReadStream(tarGzPath);
64
+ stream.on("error", () => {
65
+ res.end();
66
+ resolve();
67
+ });
68
+ res.on("finish", () => {
69
+ resolve();
70
+ });
71
+ res.on("close", () => {
72
+ resolve();
73
+ });
74
+ stream.pipe(res);
75
+ });
76
+ await cleanup();
77
+ }
78
+ //# sourceMappingURL=bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../../src/export/bundle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Phase 5 — session export HTTP handler.
3
+ *
4
+ * Wired by `createHandler` for `POST /gateways/:name/sessions/:id/export`.
5
+ * The route returns a `tar.gz` of the session's recording (session-meta
6
+ * + every turn + the schema chain) built from the ocas store via
7
+ * `@ocas/core.exportBundle`.
8
+ */
9
+ import type { IncomingMessage, ServerResponse } from "node:http";
10
+ import type { SessionStore } from "../session/index.js";
11
+ import type { GatewayConfig, OcasConfig } from "../types.js";
12
+ /**
13
+ * Handle `POST /gateways/:name/sessions/:id/export` (also `HEAD`).
14
+ */
15
+ export declare function handleSessionExport(req: IncomingMessage, res: ServerResponse, method: string, path: string, parts: {
16
+ gatewayRaw: string;
17
+ idRaw: string;
18
+ }, gateways: Record<string, GatewayConfig>, sessions: SessionStore, ocas: OcasConfig): Promise<void>;
19
+ /** Path matcher for `/gateways/<name>/sessions/<id>/export` (with optional trailing slash). */
20
+ export declare function matchSessionExport(path: string): {
21
+ gatewayRaw: string;
22
+ idRaw: string;
23
+ } | null;
24
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/export/handler.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK7D;;GAEG;AACH,wBAAsB,mBAAmB,CACxC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAC5C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,EACvC,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,UAAU,GACd,OAAO,CAAC,IAAI,CAAC,CA6Ff;AAED,+FAA+F;AAC/F,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,MAAM,GACV;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAa9C"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Phase 5 — session export HTTP handler.
3
+ *
4
+ * Wired by `createHandler` for `POST /gateways/:name/sessions/:id/export`.
5
+ * The route returns a `tar.gz` of the session's recording (session-meta
6
+ * + every turn + the schema chain) built from the ocas store via
7
+ * `@ocas/core.exportBundle`.
8
+ */
9
+ import { errorEnvelope } from "../envelope.js";
10
+ import { buildSessionExport, streamExportResponse } from "./bundle.js";
11
+ const SOFT_NODE_CAP = 100_000;
12
+ /**
13
+ * Handle `POST /gateways/:name/sessions/:id/export` (also `HEAD`).
14
+ */
15
+ export async function handleSessionExport(req, res, method, path, parts, gateways, sessions, ocas) {
16
+ const gatewayName = decodePathSegment(parts.gatewayRaw);
17
+ if (gatewayName === null) {
18
+ writeJson(res, 404, errorEnvelope("gateway_not_found", `Gateway ${parts.gatewayRaw} not found`));
19
+ return;
20
+ }
21
+ if (gateways[gatewayName] === undefined) {
22
+ writeJson(res, 404, errorEnvelope("gateway_not_found", `Gateway ${gatewayName} not found`));
23
+ return;
24
+ }
25
+ const id = decodePathSegment(parts.idRaw);
26
+ if (id === null) {
27
+ writeJson(res, 404, errorEnvelope("session_not_found", `Session ${parts.idRaw} not found on gateway ${gatewayName}`));
28
+ return;
29
+ }
30
+ if (method !== "POST" && method !== "HEAD") {
31
+ res.setHeader("Allow", "POST");
32
+ writeJson(res, 405, errorEnvelope("method_not_allowed", `Method ${method} not allowed on ${path}`));
33
+ return;
34
+ }
35
+ const session = sessions.get(gatewayName, id);
36
+ if (session === null) {
37
+ writeJson(res, 404, errorEnvelope("session_not_found", `Session ${id} not found on gateway ${gatewayName}`));
38
+ return;
39
+ }
40
+ // Drain any incoming body so keep-alive works correctly. We read no fields.
41
+ await drainBody(req);
42
+ let exported;
43
+ try {
44
+ exported = await buildSessionExport(session, ocas);
45
+ }
46
+ catch (err) {
47
+ const cause = err instanceof Error ? err.message : String(err);
48
+ writeJson(res, 500, errorEnvelope("export_failed", `Failed to build session export: ${truncate(cause, 500)}`));
49
+ return;
50
+ }
51
+ if (exported.nodes > SOFT_NODE_CAP) {
52
+ console.warn(`[sumeru] large export: ${session.id} nodes=${exported.nodes}`);
53
+ }
54
+ await streamExportResponse(res, session.id, exported.tarGzPath, exported.tempDir, exported.nodes, method === "HEAD" ? "HEAD" : "POST");
55
+ }
56
+ /** Path matcher for `/gateways/<name>/sessions/<id>/export` (with optional trailing slash). */
57
+ export function matchSessionExport(path) {
58
+ const prefix = "/gateways/";
59
+ if (!path.startsWith(prefix))
60
+ return null;
61
+ const rest = path.slice(prefix.length);
62
+ const stripped = rest.endsWith("/") ? rest.slice(0, -1) : rest;
63
+ const parts = stripped.split("/");
64
+ if (parts.length !== 4)
65
+ return null;
66
+ const [gatewayRaw, sessionsLiteral, idRaw, exportLiteral] = parts;
67
+ if (gatewayRaw === undefined || sessionsLiteral !== "sessions")
68
+ return null;
69
+ if (idRaw === undefined || idRaw.length === 0)
70
+ return null;
71
+ if (exportLiteral !== "export")
72
+ return null;
73
+ if (gatewayRaw.length === 0)
74
+ return null;
75
+ return { gatewayRaw, idRaw };
76
+ }
77
+ function decodePathSegment(segment) {
78
+ try {
79
+ return decodeURIComponent(segment);
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
85
+ async function drainBody(req) {
86
+ for await (const _chunk of req) {
87
+ // discard
88
+ }
89
+ }
90
+ function truncate(s, max) {
91
+ if (s.length <= max)
92
+ return s;
93
+ return `${s.slice(0, max - 1)}…`;
94
+ }
95
+ function writeJson(res, status, body) {
96
+ const payload = JSON.stringify(body);
97
+ res.statusCode = status;
98
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
99
+ res.setHeader("Content-Length", Buffer.byteLength(payload).toString());
100
+ res.end(payload);
101
+ }
102
+ //# sourceMappingURL=handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/export/handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { buildSessionExport, streamExportResponse } from "./bundle.js";
2
+ export { handleSessionExport, matchSessionExport } from "./handler.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/export/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { buildSessionExport, streamExportResponse } from "./bundle.js";
2
+ export { handleSessionExport, matchSessionExport } from "./handler.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/export/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { ServerConfig } from "./types.js";
3
+ /**
4
+ * Build the request handler for a server with the given config.
5
+ *
6
+ * Phase 1 routes:
7
+ * GET / → 200 @sumeru/instance envelope
8
+ * GET /gateways → 200 @sumeru/gateway-list envelope
9
+ * GET /gateways/:name → 200 @sumeru/gateway envelope OR 404
10
+ *
11
+ * Phase 2 routes:
12
+ * POST /gateways/:name/sessions → 201 @sumeru/session envelope OR 400/404
13
+ * GET /gateways/:name/sessions → 200 @sumeru/session-list envelope OR 404
14
+ * GET /gateways/:name/sessions/:id → 200 @sumeru/session OR 404
15
+ * DELETE /gateways/:name/sessions/:id → 204 No Content OR 404
16
+ *
17
+ * Phase 3 routes:
18
+ * POST /gateways/:name/sessions/:id/messages → SSE (turn / heartbeat / done / error)
19
+ *
20
+ * All non-success bodies are `@sumeru/error` envelopes. Method mismatches
21
+ * return 405 with a populated `Allow` header.
22
+ */
23
+ export declare function createHandler(config: ServerConfig): (req: IncomingMessage, res: ServerResponse) => void;
24
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAyBjE,OAAO,KAAK,EAKX,YAAY,EAKZ,MAAM,YAAY,CAAC;AAKpB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAC5B,MAAM,EAAE,YAAY,GAClB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,IAAI,CAqLrD"}