@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.
- package/LICENSE +18 -0
- package/dist/.build-fingerprint +1 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +142 -0
- package/dist/config.js.map +1 -0
- package/dist/envelope.d.ts +28 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +43 -0
- package/dist/envelope.js.map +1 -0
- package/dist/export/bundle.d.ts +28 -0
- package/dist/export/bundle.d.ts.map +1 -0
- package/dist/export/bundle.js +78 -0
- package/dist/export/bundle.js.map +1 -0
- package/dist/export/handler.d.ts +24 -0
- package/dist/export/handler.d.ts.map +1 -0
- package/dist/export/handler.js +102 -0
- package/dist/export/handler.js.map +1 -0
- package/dist/export/index.d.ts +3 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +3 -0
- package/dist/export/index.js.map +1 -0
- package/dist/handler.d.ts +24 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +622 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/ocas/index.d.ts +3 -0
- package/dist/ocas/index.d.ts.map +1 -0
- package/dist/ocas/index.js +3 -0
- package/dist/ocas/index.js.map +1 -0
- package/dist/ocas/schemas.d.ts +41 -0
- package/dist/ocas/schemas.d.ts.map +1 -0
- package/dist/ocas/schemas.js +108 -0
- package/dist/ocas/schemas.js.map +1 -0
- package/dist/ocas/store.d.ts +58 -0
- package/dist/ocas/store.d.ts.map +1 -0
- package/dist/ocas/store.js +139 -0
- package/dist/ocas/store.js.map +1 -0
- package/dist/search/handler.d.ts +54 -0
- package/dist/search/handler.d.ts.map +1 -0
- package/dist/search/handler.js +178 -0
- package/dist/search/handler.js.map +1 -0
- package/dist/search/index.d.ts +4 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +3 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/sqlite-index.d.ts +49 -0
- package/dist/search/sqlite-index.d.ts.map +1 -0
- package/dist/search/sqlite-index.js +508 -0
- package/dist/search/sqlite-index.js.map +1 -0
- package/dist/search/types.d.ts +143 -0
- package/dist/search/types.d.ts.map +1 -0
- package/dist/search/types.js +10 -0
- package/dist/search/types.js.map +1 -0
- package/dist/session/cwd.d.ts +31 -0
- package/dist/session/cwd.d.ts.map +1 -0
- package/dist/session/cwd.js +54 -0
- package/dist/session/cwd.js.map +1 -0
- package/dist/session/id.d.ts +12 -0
- package/dist/session/id.d.ts.map +1 -0
- package/dist/session/id.js +76 -0
- package/dist/session/id.js.map +1 -0
- package/dist/session/index.d.ts +5 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +4 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/store.d.ts +89 -0
- package/dist/session/store.d.ts.map +1 -0
- package/dist/session/store.js +258 -0
- package/dist/session/store.js.map +1 -0
- package/dist/sse/buffer.d.ts +53 -0
- package/dist/sse/buffer.d.ts.map +1 -0
- package/dist/sse/buffer.js +119 -0
- package/dist/sse/buffer.js.map +1 -0
- package/dist/sse/index.d.ts +3 -0
- package/dist/sse/index.d.ts.map +1 -0
- package/dist/sse/index.js +3 -0
- package/dist/sse/index.js.map +1 -0
- package/dist/sse/messages.d.ts +30 -0
- package/dist/sse/messages.d.ts.map +1 -0
- package/dist/sse/messages.js +489 -0
- package/dist/sse/messages.js.map +1 -0
- package/dist/start.d.ts +22 -0
- package/dist/start.d.ts.map +1 -0
- package/dist/start.js +86 -0
- package/dist/start.js.map +1 -0
- package/dist/types.d.ts +252 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +31 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 5 — search index types.
|
|
3
|
+
*
|
|
4
|
+
* These types are shared by the index module and the HTTP endpoints. The
|
|
5
|
+
* `SearchIndex` is a closure carrying the SQLite handle; consumers obtain
|
|
6
|
+
* one from `openSumeruOcas` via the new `searchIndex` slice on the result
|
|
7
|
+
* (mirrored on `OcasConfig`).
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/search/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a per-session `config.cwd` against the instance `workspaceRoot`.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the cwd-resolution policy used by
|
|
5
|
+
* `POST /gateways/:name/sessions`. Both the HTTP handler and tests import
|
|
6
|
+
* this helper.
|
|
7
|
+
*
|
|
8
|
+
* Rules:
|
|
9
|
+
*
|
|
10
|
+
* - `rawCwd` is `undefined` / `null` → `{ ok: true, cwd: null }`
|
|
11
|
+
* - `rawCwd` is the empty string → `{ ok: true, cwd: null }`
|
|
12
|
+
* - `rawCwd` is not a string (and not absent) → `{ ok: false, … }`
|
|
13
|
+
* - non-empty string AND workspaceRoot set → `path.resolve(root, raw)`,
|
|
14
|
+
* rejected if it escapes root
|
|
15
|
+
* - non-empty string AND workspaceRoot null → must be absolute (verbatim);
|
|
16
|
+
* relative is rejected
|
|
17
|
+
*
|
|
18
|
+
* The resolved value is the absolute path the adapter should be told to use
|
|
19
|
+
* (replacing the user-supplied `cwd` in the opaque config blob). The wire
|
|
20
|
+
* envelope returned to the client is left untouched — only the adapter sees
|
|
21
|
+
* the resolved form.
|
|
22
|
+
*/
|
|
23
|
+
export type ResolveCwdResult = {
|
|
24
|
+
ok: true;
|
|
25
|
+
cwd: string | null;
|
|
26
|
+
} | {
|
|
27
|
+
ok: false;
|
|
28
|
+
message: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function resolveSessionCwd(workspaceRoot: string | null, rawCwd: unknown): ResolveCwdResult;
|
|
31
|
+
//# sourceMappingURL=cwd.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cwd.d.ts","sourceRoot":"","sources":["../../src/session/cwd.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,MAAM,MAAM,gBAAgB,GACzB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAChC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAElC,wBAAgB,iBAAiB,CAChC,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,MAAM,EAAE,OAAO,GACb,gBAAgB,CA+BlB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a per-session `config.cwd` against the instance `workspaceRoot`.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the cwd-resolution policy used by
|
|
5
|
+
* `POST /gateways/:name/sessions`. Both the HTTP handler and tests import
|
|
6
|
+
* this helper.
|
|
7
|
+
*
|
|
8
|
+
* Rules:
|
|
9
|
+
*
|
|
10
|
+
* - `rawCwd` is `undefined` / `null` → `{ ok: true, cwd: null }`
|
|
11
|
+
* - `rawCwd` is the empty string → `{ ok: true, cwd: null }`
|
|
12
|
+
* - `rawCwd` is not a string (and not absent) → `{ ok: false, … }`
|
|
13
|
+
* - non-empty string AND workspaceRoot set → `path.resolve(root, raw)`,
|
|
14
|
+
* rejected if it escapes root
|
|
15
|
+
* - non-empty string AND workspaceRoot null → must be absolute (verbatim);
|
|
16
|
+
* relative is rejected
|
|
17
|
+
*
|
|
18
|
+
* The resolved value is the absolute path the adapter should be told to use
|
|
19
|
+
* (replacing the user-supplied `cwd` in the opaque config blob). The wire
|
|
20
|
+
* envelope returned to the client is left untouched — only the adapter sees
|
|
21
|
+
* the resolved form.
|
|
22
|
+
*/
|
|
23
|
+
import { isAbsolute, resolve as pathResolve, sep } from "node:path";
|
|
24
|
+
export function resolveSessionCwd(workspaceRoot, rawCwd) {
|
|
25
|
+
if (rawCwd === undefined || rawCwd === null) {
|
|
26
|
+
return { ok: true, cwd: null };
|
|
27
|
+
}
|
|
28
|
+
if (typeof rawCwd !== "string") {
|
|
29
|
+
return { ok: false, message: "config.cwd must be a string" };
|
|
30
|
+
}
|
|
31
|
+
if (rawCwd.length === 0) {
|
|
32
|
+
return { ok: true, cwd: null };
|
|
33
|
+
}
|
|
34
|
+
if (workspaceRoot !== null) {
|
|
35
|
+
const root = pathResolve(workspaceRoot);
|
|
36
|
+
const resolved = pathResolve(root, rawCwd);
|
|
37
|
+
if (resolved !== root && !resolved.startsWith(root + sep)) {
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
message: `config.cwd '${rawCwd}' resolves outside workspaceRoot '${workspaceRoot}'`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { ok: true, cwd: resolved };
|
|
44
|
+
}
|
|
45
|
+
// No workspaceRoot configured — only absolute paths are accepted.
|
|
46
|
+
if (!isAbsolute(rawCwd)) {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
message: `config.cwd '${rawCwd}' must be absolute when no workspaceRoot is configured`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return { ok: true, cwd: rawCwd };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=cwd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cwd.js","sourceRoot":"","sources":["../../src/session/cwd.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,UAAU,EAAE,OAAO,IAAI,WAAW,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAMpE,MAAM,UAAU,iBAAiB,CAChC,aAA4B,EAC5B,MAAe;IAEf,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAC7C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IAChC,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,6BAA6B,EAAE,CAAC;IAC9D,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IAChC,CAAC;IAED,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,WAAW,CAAC,aAAa,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC3C,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YAC3D,OAAO;gBACN,EAAE,EAAE,KAAK;gBACT,OAAO,EAAE,eAAe,MAAM,qCAAqC,aAAa,GAAG;aACnF,CAAC;QACH,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;IACpC,CAAC;IAED,kEAAkE;IAClE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,OAAO;YACN,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,eAAe,MAAM,wDAAwD;SACtF,CAAC;IACH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a Sumeru session ID: `ses_` followed by a 26-character ULID
|
|
3
|
+
* (Crockford Base32, uppercase).
|
|
4
|
+
*
|
|
5
|
+
* - Total length: 30 (4 prefix + 26 body).
|
|
6
|
+
* - Body matches `^[0-9A-HJKMNP-TV-Z]{26}$`.
|
|
7
|
+
* - First 10 body characters encode milliseconds since the Unix epoch.
|
|
8
|
+
* - Subsequent calls within the same millisecond emit a strictly increasing
|
|
9
|
+
* randomness component (monotonic ULID), guaranteeing distinct ids.
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateSessionId(): string;
|
|
12
|
+
//# sourceMappingURL=id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/session/id.ts"],"names":[],"mappings":"AAkBA;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Crockford Base32 alphabet (excludes I, L, O, U).
|
|
4
|
+
* Used by ULID for both timestamp and randomness components.
|
|
5
|
+
*/
|
|
6
|
+
const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
7
|
+
/** Length of the timestamp portion of a ULID (in Crockford chars). */
|
|
8
|
+
const TIME_LEN = 10;
|
|
9
|
+
/** Length of the randomness portion of a ULID. */
|
|
10
|
+
const RAND_LEN = 16;
|
|
11
|
+
/** State for monotonic ULID generation when timestamps collide. */
|
|
12
|
+
let lastTime = 0;
|
|
13
|
+
let lastRandom = new Array(RAND_LEN).fill(0);
|
|
14
|
+
/**
|
|
15
|
+
* Generate a Sumeru session ID: `ses_` followed by a 26-character ULID
|
|
16
|
+
* (Crockford Base32, uppercase).
|
|
17
|
+
*
|
|
18
|
+
* - Total length: 30 (4 prefix + 26 body).
|
|
19
|
+
* - Body matches `^[0-9A-HJKMNP-TV-Z]{26}$`.
|
|
20
|
+
* - First 10 body characters encode milliseconds since the Unix epoch.
|
|
21
|
+
* - Subsequent calls within the same millisecond emit a strictly increasing
|
|
22
|
+
* randomness component (monotonic ULID), guaranteeing distinct ids.
|
|
23
|
+
*/
|
|
24
|
+
export function generateSessionId() {
|
|
25
|
+
return `ses_${generateUlid()}`;
|
|
26
|
+
}
|
|
27
|
+
function generateUlid() {
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
const random = now === lastTime ? incrementRandom(lastRandom) : freshRandom();
|
|
30
|
+
lastTime = now;
|
|
31
|
+
lastRandom = random;
|
|
32
|
+
return `${encodeTime(now)}${encodeRandom(random)}`;
|
|
33
|
+
}
|
|
34
|
+
function encodeTime(ms) {
|
|
35
|
+
const out = new Array(TIME_LEN).fill("0");
|
|
36
|
+
let value = ms;
|
|
37
|
+
for (let i = TIME_LEN - 1; i >= 0; i -= 1) {
|
|
38
|
+
const mod = value % 32;
|
|
39
|
+
// Crockford alphabet has length 32, so mod is always in range.
|
|
40
|
+
out[i] = CROCKFORD.charAt(mod);
|
|
41
|
+
value = Math.floor(value / 32);
|
|
42
|
+
}
|
|
43
|
+
return out.join("");
|
|
44
|
+
}
|
|
45
|
+
function freshRandom() {
|
|
46
|
+
const buf = randomBytes(RAND_LEN);
|
|
47
|
+
const out = new Array(RAND_LEN).fill(0);
|
|
48
|
+
for (let i = 0; i < RAND_LEN; i += 1) {
|
|
49
|
+
// 0–31 (5-bit) values for Crockford Base32.
|
|
50
|
+
out[i] = (buf[i] ?? 0) & 0x1f;
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
function incrementRandom(prev) {
|
|
55
|
+
const out = prev.slice();
|
|
56
|
+
for (let i = RAND_LEN - 1; i >= 0; i -= 1) {
|
|
57
|
+
const v = out[i] ?? 0;
|
|
58
|
+
if (v < 31) {
|
|
59
|
+
out[i] = v + 1;
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
out[i] = 0;
|
|
63
|
+
}
|
|
64
|
+
// Overflow within a single millisecond is astronomically unlikely; fall back
|
|
65
|
+
// to a fresh random vector rather than failing the call.
|
|
66
|
+
return freshRandom();
|
|
67
|
+
}
|
|
68
|
+
function encodeRandom(values) {
|
|
69
|
+
const out = new Array(RAND_LEN).fill("0");
|
|
70
|
+
for (let i = 0; i < RAND_LEN; i += 1) {
|
|
71
|
+
const v = values[i] ?? 0;
|
|
72
|
+
out[i] = CROCKFORD.charAt(v);
|
|
73
|
+
}
|
|
74
|
+
return out.join("");
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/session/id.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { type ResolveCwdResult, resolveSessionCwd } from "./cwd.js";
|
|
2
|
+
export { generateSessionId } from "./id.js";
|
|
3
|
+
export type { SessionStore, TransitionResult } from "./store.js";
|
|
4
|
+
export { createSessionStore } from "./store.js";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { Hash } from "@ocas/core";
|
|
2
|
+
import type { NativeSessionRef } from "@sumeru/core";
|
|
3
|
+
import type { OcasConfig, Session, SessionWire, UserSessionConfig } from "../types.js";
|
|
4
|
+
/**
|
|
5
|
+
* In-memory session store.
|
|
6
|
+
*
|
|
7
|
+
* Phase 2: persistence is per-process and lost on restart. Adapter-level
|
|
8
|
+
* persistence lands in a later phase when the message endpoint is wired up.
|
|
9
|
+
*
|
|
10
|
+
* Phase 3: each session also tracks an internal `NativeSessionRef` produced
|
|
11
|
+
* by the adapter when the session was created. The `NativeSessionRef` is
|
|
12
|
+
* NEVER exposed in HTTP envelopes — only `getNativeRef` makes it visible
|
|
13
|
+
* to internal callers (the message handler, the close path).
|
|
14
|
+
*
|
|
15
|
+
* Phase 4: each session carries a `metaHash` (set on create) and a
|
|
16
|
+
* `turnHashes` array that grows as turns are recorded. Both are internal
|
|
17
|
+
* — they are NEVER serialized into HTTP envelopes; `toWire` strips them.
|
|
18
|
+
*
|
|
19
|
+
* Sessions are scoped to their gateway: lookups always include the gateway
|
|
20
|
+
* name, and a session created on `hermes` is invisible from `claude-code`.
|
|
21
|
+
*
|
|
22
|
+
* The store is a closure over a `Map<gateway, Map<id, Session>>`. Insertion
|
|
23
|
+
* order is preserved on the inner maps, which makes listings deterministic
|
|
24
|
+
* (chronological by createdAt ascending).
|
|
25
|
+
*/
|
|
26
|
+
export type SessionStore = {
|
|
27
|
+
/**
|
|
28
|
+
* Create a new session on `gateway`. The id is generated server-side.
|
|
29
|
+
* Writes the corresponding `@sumeru/session-meta` node to ocas BEFORE
|
|
30
|
+
* the in-memory session is registered. Throws if the meta-write fails.
|
|
31
|
+
*
|
|
32
|
+
* `resolvedCwd` is the absolute path produced by `resolveSessionCwd`
|
|
33
|
+
* (issue #27) — the value the server already forwarded to the adapter
|
|
34
|
+
* under `config.cwd`. `null` means no cwd hint was supplied. The opaque
|
|
35
|
+
* `config` blob is preserved verbatim and is NOT mutated here.
|
|
36
|
+
*/
|
|
37
|
+
create: (gateway: string, adapter: string, config: UserSessionConfig, nativeRef: NativeSessionRef | null, resolvedCwd: string | null) => Session;
|
|
38
|
+
/** List sessions on a gateway in insertion order (chronological). */
|
|
39
|
+
list: (gateway: string) => Session[];
|
|
40
|
+
/** Look up a single session, scoped by gateway. */
|
|
41
|
+
get: (gateway: string, id: string) => Session | null;
|
|
42
|
+
/** Internal-only: retrieve the NativeSessionRef recorded at create time. */
|
|
43
|
+
getNativeRef: (gateway: string, id: string) => NativeSessionRef | null;
|
|
44
|
+
/**
|
|
45
|
+
* Append a turn hash to a session's turn list. Used by the message
|
|
46
|
+
* endpoint as turns are recorded in ocas. No-op if the session is gone.
|
|
47
|
+
*/
|
|
48
|
+
appendTurnHash: (gateway: string, id: string, hash: Hash) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Mark a session closed. Returns:
|
|
51
|
+
* - `closed` if the status flipped from idle/active to closed
|
|
52
|
+
* - `already_closed` if it was already closed (idempotent no-op)
|
|
53
|
+
* - `not_found` if no such session exists on the gateway
|
|
54
|
+
*/
|
|
55
|
+
close: (gateway: string, id: string) => "closed" | "already_closed" | "not_found";
|
|
56
|
+
/** Number of non-closed sessions on a gateway. */
|
|
57
|
+
activeCount: (gateway: string) => number;
|
|
58
|
+
/**
|
|
59
|
+
* Try to mark a session active (idle → active). Reserved for the future
|
|
60
|
+
* message endpoint; exposed now so the 409 contract is testable.
|
|
61
|
+
*/
|
|
62
|
+
tryActivate: (gateway: string, id: string) => TransitionResult<"busy" | "closed" | "not_found">;
|
|
63
|
+
/**
|
|
64
|
+
* Mark a session idle (active → idle). Used at the end of a message
|
|
65
|
+
* exchange. Forward-compat for the message endpoint.
|
|
66
|
+
*/
|
|
67
|
+
markIdle: (gateway: string, id: string) => TransitionResult<"not_active" | "not_found">;
|
|
68
|
+
};
|
|
69
|
+
export type TransitionResult<R extends string> = {
|
|
70
|
+
ok: true;
|
|
71
|
+
session: Session;
|
|
72
|
+
} | {
|
|
73
|
+
ok: false;
|
|
74
|
+
reason: R;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Strip internal-only fields (`metaHash`, `turnHashes`) from a `Session` so
|
|
78
|
+
* the result matches the HTTP wire envelope.
|
|
79
|
+
*/
|
|
80
|
+
export declare function toWire(session: Session): SessionWire;
|
|
81
|
+
/**
|
|
82
|
+
* Build a fresh, empty session store.
|
|
83
|
+
*
|
|
84
|
+
* The store needs `ocas` so it can write `@sumeru/session-meta` nodes on
|
|
85
|
+
* `create`. The hash is recorded on the in-memory session for later
|
|
86
|
+
* cross-reference by the `/ocas/:hash` endpoint.
|
|
87
|
+
*/
|
|
88
|
+
export declare function createSessionStore(ocas: OcasConfig): SessionStore;
|
|
89
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/session/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,KAAK,EACX,UAAU,EACV,OAAO,EAEP,WAAW,EACX,iBAAiB,EACjB,MAAM,aAAa,CAAC;AAGrB;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,MAAM,YAAY,GAAG;IAC1B;;;;;;;;;OASG;IACH,MAAM,EAAE,CACP,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,iBAAiB,EACzB,SAAS,EAAE,gBAAgB,GAAG,IAAI,EAClC,WAAW,EAAE,MAAM,GAAG,IAAI,KACtB,OAAO,CAAC;IACb,qEAAqE;IACrE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,EAAE,CAAC;IACrC,mDAAmD;IACnD,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC;IACrD,4EAA4E;IAC5E,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,gBAAgB,GAAG,IAAI,CAAC;IACvE;;;OAGG;IACH,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAClE;;;;;OAKG;IACH,KAAK,EAAE,CACN,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,KACN,QAAQ,GAAG,gBAAgB,GAAG,WAAW,CAAC;IAC/C,kDAAkD;IAClD,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC;;;OAGG;IACH,WAAW,EAAE,CACZ,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,KACN,gBAAgB,CAAC,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC,CAAC;IACvD;;;OAGG;IACH,QAAQ,EAAE,CACT,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,KACN,gBAAgB,CAAC,YAAY,GAAG,WAAW,CAAC,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,MAAM,IAC1C;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAC9B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC;AAE5B;;;GAGG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,CAQpD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,YAAY,CAwPjE"}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { recordPayload } from "../ocas/index.js";
|
|
2
|
+
import { generateSessionId } from "./id.js";
|
|
3
|
+
/**
|
|
4
|
+
* Strip internal-only fields (`metaHash`, `turnHashes`) from a `Session` so
|
|
5
|
+
* the result matches the HTTP wire envelope.
|
|
6
|
+
*/
|
|
7
|
+
export function toWire(session) {
|
|
8
|
+
return {
|
|
9
|
+
id: session.id,
|
|
10
|
+
gateway: session.gateway,
|
|
11
|
+
status: session.status,
|
|
12
|
+
createdAt: session.createdAt,
|
|
13
|
+
config: session.config,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build a fresh, empty session store.
|
|
18
|
+
*
|
|
19
|
+
* The store needs `ocas` so it can write `@sumeru/session-meta` nodes on
|
|
20
|
+
* `create`. The hash is recorded on the in-memory session for later
|
|
21
|
+
* cross-reference by the `/ocas/:hash` endpoint.
|
|
22
|
+
*/
|
|
23
|
+
export function createSessionStore(ocas) {
|
|
24
|
+
const byGateway = new Map();
|
|
25
|
+
const nativeRefs = new Map();
|
|
26
|
+
function ensureGatewayMap(gateway) {
|
|
27
|
+
let inner = byGateway.get(gateway);
|
|
28
|
+
if (inner === undefined) {
|
|
29
|
+
inner = new Map();
|
|
30
|
+
byGateway.set(gateway, inner);
|
|
31
|
+
}
|
|
32
|
+
return inner;
|
|
33
|
+
}
|
|
34
|
+
function nowIso() {
|
|
35
|
+
return new Date().toISOString();
|
|
36
|
+
}
|
|
37
|
+
function refKey(gateway, id) {
|
|
38
|
+
return `${gateway}\u0000${id}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Phase 6 (Refs #399): rebuild `byGateway` from disk BEFORE serving any
|
|
42
|
+
* request, so a restarted process recovers previously-recorded sessions
|
|
43
|
+
* and their ordered turn-list pointers. Reads:
|
|
44
|
+
* 1. every `sumeru_session_index` row (ordered by `created_at ASC`), and
|
|
45
|
+
* 2. the per-session turn hashes in one bulk query.
|
|
46
|
+
* `config` is recovered from the immutable `@sumeru/session-meta` node at
|
|
47
|
+
* `metaHash`; a missing/unreadable meta node falls back to `config: {}`
|
|
48
|
+
* with a structured warning. `nativeRef` is intentionally NOT restored —
|
|
49
|
+
* a rehydrated session is read-complete but not resumable for new sends.
|
|
50
|
+
*/
|
|
51
|
+
function rehydrate() {
|
|
52
|
+
let sessionCount = 0;
|
|
53
|
+
let turnCount = 0;
|
|
54
|
+
let rows;
|
|
55
|
+
let turnsBySession;
|
|
56
|
+
try {
|
|
57
|
+
rows = ocas.searchIndex.loadSessionRows();
|
|
58
|
+
turnsBySession = ocas.searchIndex.loadSessionTurnsBulk();
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
62
|
+
console.warn(`[sumeru] session rehydrate failed: ${cause}`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
for (const row of rows) {
|
|
66
|
+
const inner = ensureGatewayMap(row.gateway);
|
|
67
|
+
const config = recoverConfig(row.sessionId, row.metaHash);
|
|
68
|
+
const turnHashes = turnsBySession.get(row.sessionId) ?? [];
|
|
69
|
+
const session = {
|
|
70
|
+
id: row.sessionId,
|
|
71
|
+
gateway: row.gateway,
|
|
72
|
+
status: row.status,
|
|
73
|
+
createdAt: row.createdAt,
|
|
74
|
+
config,
|
|
75
|
+
metaHash: row.metaHash ?? "",
|
|
76
|
+
turnHashes: [...turnHashes],
|
|
77
|
+
};
|
|
78
|
+
inner.set(session.id, session);
|
|
79
|
+
sessionCount += 1;
|
|
80
|
+
turnCount += turnHashes.length;
|
|
81
|
+
}
|
|
82
|
+
if (sessionCount > 0) {
|
|
83
|
+
console.log(`[sumeru] rehydrated ${sessionCount} sessions, ${turnCount} turns`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Read a session's opaque `config` from its immutable `@sumeru/session-meta`
|
|
88
|
+
* node. Falls back to `{}` (with a warning) when `metaHash` is `null` or the
|
|
89
|
+
* node is missing/malformed — turn history is the priority, not config.
|
|
90
|
+
*/
|
|
91
|
+
function recoverConfig(sessionId, metaHash) {
|
|
92
|
+
if (metaHash === null) {
|
|
93
|
+
console.warn(`[sumeru] session ${sessionId} has no meta_hash; config falls back to {}`);
|
|
94
|
+
return {};
|
|
95
|
+
}
|
|
96
|
+
const node = ocas.store.cas.get(metaHash);
|
|
97
|
+
if (node === null) {
|
|
98
|
+
console.warn(`[sumeru] session ${sessionId} meta node ${metaHash} missing; config falls back to {}`);
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
const payload = node.payload;
|
|
102
|
+
const config = payload.config;
|
|
103
|
+
if (config === undefined || config === null || typeof config !== "object") {
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
106
|
+
return config;
|
|
107
|
+
}
|
|
108
|
+
function create(gateway, adapter, config, nativeRef, resolvedCwd) {
|
|
109
|
+
const inner = ensureGatewayMap(gateway);
|
|
110
|
+
const id = generateSessionId();
|
|
111
|
+
const createdAt = nowIso();
|
|
112
|
+
// Record session-meta to ocas FIRST. If validation/IO fails, the
|
|
113
|
+
// in-memory session is never created — the caller propagates a 500.
|
|
114
|
+
const metaHash = recordPayload(ocas.store, ocas.sessionMetaSchemaHash, {
|
|
115
|
+
id,
|
|
116
|
+
gateway,
|
|
117
|
+
adapter,
|
|
118
|
+
createdAt,
|
|
119
|
+
config,
|
|
120
|
+
resolvedCwd,
|
|
121
|
+
});
|
|
122
|
+
// Phase 5: seed the search index. Failures here do NOT roll back the
|
|
123
|
+
// ocas write — the index can always be rebuilt from the meta node.
|
|
124
|
+
// Phase 6 (Refs #399): persist `metaHash` so a restart can re-read
|
|
125
|
+
// `config` from the immutable `@sumeru/session-meta` node.
|
|
126
|
+
try {
|
|
127
|
+
ocas.searchIndex.indexSessionMeta({
|
|
128
|
+
sessionId: id,
|
|
129
|
+
gateway,
|
|
130
|
+
adapter,
|
|
131
|
+
createdAt,
|
|
132
|
+
metaHash,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
const cause = err instanceof Error ? err.message : String(err);
|
|
137
|
+
console.warn(`[sumeru] search index seed failed: ${cause}`);
|
|
138
|
+
}
|
|
139
|
+
const session = {
|
|
140
|
+
id,
|
|
141
|
+
gateway,
|
|
142
|
+
status: "idle",
|
|
143
|
+
createdAt,
|
|
144
|
+
config,
|
|
145
|
+
metaHash,
|
|
146
|
+
turnHashes: [],
|
|
147
|
+
};
|
|
148
|
+
inner.set(session.id, session);
|
|
149
|
+
if (nativeRef !== null) {
|
|
150
|
+
nativeRefs.set(refKey(gateway, session.id), nativeRef);
|
|
151
|
+
}
|
|
152
|
+
return session;
|
|
153
|
+
}
|
|
154
|
+
function list(gateway) {
|
|
155
|
+
const inner = byGateway.get(gateway);
|
|
156
|
+
if (inner === undefined)
|
|
157
|
+
return [];
|
|
158
|
+
return Array.from(inner.values());
|
|
159
|
+
}
|
|
160
|
+
function get(gateway, id) {
|
|
161
|
+
const inner = byGateway.get(gateway);
|
|
162
|
+
if (inner === undefined)
|
|
163
|
+
return null;
|
|
164
|
+
return inner.get(id) ?? null;
|
|
165
|
+
}
|
|
166
|
+
function getNativeRef(gateway, id) {
|
|
167
|
+
return nativeRefs.get(refKey(gateway, id)) ?? null;
|
|
168
|
+
}
|
|
169
|
+
function appendTurnHash(gateway, id, hash) {
|
|
170
|
+
const session = get(gateway, id);
|
|
171
|
+
if (session === null)
|
|
172
|
+
return;
|
|
173
|
+
// Phase 6 (Refs #399): persist the list pointer BEFORE mutating memory
|
|
174
|
+
// so disk never lags behind memory. `turn_index` is the 0-based append
|
|
175
|
+
// position (the array length before the push). The write is idempotent
|
|
176
|
+
// on (session_id, turn_index); a persistence failure propagates so the
|
|
177
|
+
// caller's existing error handling (sse/messages.ts) can react — we do
|
|
178
|
+
// NOT silently diverge memory from disk.
|
|
179
|
+
const turnIndex = session.turnHashes.length;
|
|
180
|
+
ocas.searchIndex.appendSessionTurn(session.id, turnIndex, hash);
|
|
181
|
+
session.turnHashes.push(hash);
|
|
182
|
+
}
|
|
183
|
+
function close(gateway, id) {
|
|
184
|
+
const session = get(gateway, id);
|
|
185
|
+
if (session === null)
|
|
186
|
+
return "not_found";
|
|
187
|
+
if (session.status === "closed")
|
|
188
|
+
return "already_closed";
|
|
189
|
+
session.status = "closed";
|
|
190
|
+
// Best-effort: mark the search-index row closed too. Failures are
|
|
191
|
+
// logged inside markSessionClosed; the in-memory flip is the source
|
|
192
|
+
// of truth on the wire.
|
|
193
|
+
ocas.searchIndex.markSessionClosed(id);
|
|
194
|
+
return "closed";
|
|
195
|
+
}
|
|
196
|
+
function activeCount(gateway) {
|
|
197
|
+
const inner = byGateway.get(gateway);
|
|
198
|
+
if (inner === undefined)
|
|
199
|
+
return 0;
|
|
200
|
+
let n = 0;
|
|
201
|
+
for (const s of inner.values()) {
|
|
202
|
+
if (s.status !== "closed")
|
|
203
|
+
n += 1;
|
|
204
|
+
}
|
|
205
|
+
return n;
|
|
206
|
+
}
|
|
207
|
+
function tryActivate(gateway, id) {
|
|
208
|
+
const session = get(gateway, id);
|
|
209
|
+
if (session === null)
|
|
210
|
+
return { ok: false, reason: "not_found" };
|
|
211
|
+
const next = transitionTo(session.status, "active");
|
|
212
|
+
if (next === "busy" || next === "closed") {
|
|
213
|
+
return { ok: false, reason: next };
|
|
214
|
+
}
|
|
215
|
+
session.status = "active";
|
|
216
|
+
return { ok: true, session };
|
|
217
|
+
}
|
|
218
|
+
function markIdle(gateway, id) {
|
|
219
|
+
const session = get(gateway, id);
|
|
220
|
+
if (session === null)
|
|
221
|
+
return { ok: false, reason: "not_found" };
|
|
222
|
+
if (session.status !== "active") {
|
|
223
|
+
return { ok: false, reason: "not_active" };
|
|
224
|
+
}
|
|
225
|
+
session.status = "idle";
|
|
226
|
+
return { ok: true, session };
|
|
227
|
+
}
|
|
228
|
+
// Phase 6 (Refs #399): recover persisted sessions + turn-list pointers from
|
|
229
|
+
// disk before the store serves any request.
|
|
230
|
+
rehydrate();
|
|
231
|
+
return {
|
|
232
|
+
create,
|
|
233
|
+
list,
|
|
234
|
+
get,
|
|
235
|
+
getNativeRef,
|
|
236
|
+
appendTurnHash,
|
|
237
|
+
close,
|
|
238
|
+
activeCount,
|
|
239
|
+
tryActivate,
|
|
240
|
+
markIdle,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Transition table for `tryActivate`-style edges. Returns the literal `"ok"`
|
|
245
|
+
* if the transition is allowed, or a string reason if it is not.
|
|
246
|
+
*/
|
|
247
|
+
function transitionTo(from, to) {
|
|
248
|
+
if (to === "active") {
|
|
249
|
+
if (from === "idle")
|
|
250
|
+
return "ok";
|
|
251
|
+
if (from === "active")
|
|
252
|
+
return "busy";
|
|
253
|
+
return "closed"; // from === "closed"
|
|
254
|
+
}
|
|
255
|
+
// Exhaustive: only "active" is a valid `to` here.
|
|
256
|
+
return "ok";
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/session/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-send in-memory ring buffer for SSE resume support.
|
|
3
|
+
*
|
|
4
|
+
* Each `POST /gateways/:name/sessions/:id/messages` call creates a fresh
|
|
5
|
+
* buffer keyed by `(gateway, sessionId, sendNonce)`. Events are appended in
|
|
6
|
+
* id order; the most-recent N events are retained (N = `sseBufferSize`,
|
|
7
|
+
* default 1024). After `event: done` the buffer is held for `retentionMs`
|
|
8
|
+
* (default 30_000) to allow late clients to resume.
|
|
9
|
+
*
|
|
10
|
+
* Resume is a pure replay — `adapter.send` is NOT called twice. The buffer
|
|
11
|
+
* is the canonical record of what the original send produced.
|
|
12
|
+
*/
|
|
13
|
+
export type SseEvent = {
|
|
14
|
+
id: number;
|
|
15
|
+
event: string;
|
|
16
|
+
/** Stringified data line — already JSON-serialized envelope. */
|
|
17
|
+
data: string;
|
|
18
|
+
};
|
|
19
|
+
export type SseBuffer = {
|
|
20
|
+
gateway: string;
|
|
21
|
+
sessionId: string;
|
|
22
|
+
nonce: string;
|
|
23
|
+
events: SseEvent[];
|
|
24
|
+
maxId: number;
|
|
25
|
+
doneAt: number | null;
|
|
26
|
+
maxSize: number;
|
|
27
|
+
/** Resolved when the underlying send is finished (success or failure). */
|
|
28
|
+
finished: boolean;
|
|
29
|
+
};
|
|
30
|
+
export type SseBufferStore = {
|
|
31
|
+
create: (gateway: string, sessionId: string) => SseBuffer;
|
|
32
|
+
getLatestForSession: (gateway: string, sessionId: string) => SseBuffer | null;
|
|
33
|
+
finish: (buf: SseBuffer) => void;
|
|
34
|
+
purgeExpired: (now: number) => void;
|
|
35
|
+
wasRecentlyExpired: (gateway: string, sessionId: string) => boolean;
|
|
36
|
+
};
|
|
37
|
+
export type SseBufferOptions = {
|
|
38
|
+
maxSize: number;
|
|
39
|
+
retentionMs: number;
|
|
40
|
+
};
|
|
41
|
+
export declare function createSseBufferStore(options: SseBufferOptions): SseBufferStore;
|
|
42
|
+
/** Append an event to a buffer, maintaining `maxSize` ring semantics. */
|
|
43
|
+
export declare function appendEvent(buf: SseBuffer, event: string, data: string): SseEvent;
|
|
44
|
+
/** Wire-format an event: `id: ...\nevent: ...\ndata: ...\n\n`. */
|
|
45
|
+
export declare function formatEvent(evt: SseEvent): string;
|
|
46
|
+
/** Range-replay events whose id is strictly greater than `since`. */
|
|
47
|
+
export declare function eventsAfter(buf: SseBuffer, since: number): SseEvent[];
|
|
48
|
+
/**
|
|
49
|
+
* Lowest still-buffered event id, or 0 when empty. Useful when computing
|
|
50
|
+
* whether a Last-Event-ID predates the ring's current window.
|
|
51
|
+
*/
|
|
52
|
+
export declare function lowestId(buf: SseBuffer): number;
|
|
53
|
+
//# sourceMappingURL=buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buffer.d.ts","sourceRoot":"","sources":["../../src/sse/buffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,QAAQ,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC5B,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,SAAS,CAAC;IAC1D,mBAAmB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,SAAS,GAAG,IAAI,CAAC;IAC9E,MAAM,EAAE,CAAC,GAAG,EAAE,SAAS,KAAK,IAAI,CAAC;IACjC,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;CACpE,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACpB,CAAC;AASF,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,gBAAgB,GACvB,cAAc,CAiFhB;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAC1B,GAAG,EAAE,SAAS,EACd,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GACV,QAAQ,CASV;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,CAEjD;AAED,qEAAqE;AACrE,wBAAgB,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,EAAE,CAGrE;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAG/C"}
|