@earendil-works/pi-server 0.84.3 → 0.85.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 (79) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/README.md +66 -37
  3. package/dist/connection.d.ts +9 -5
  4. package/dist/connection.d.ts.map +1 -1
  5. package/dist/connection.js.map +1 -1
  6. package/dist/errors.d.ts +16 -18
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js +23 -27
  9. package/dist/errors.js.map +1 -1
  10. package/dist/index.d.ts +0 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +0 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/listener.d.ts +1 -3
  15. package/dist/listener.d.ts.map +1 -1
  16. package/dist/listener.js.map +1 -1
  17. package/dist/server.d.ts +16 -6
  18. package/dist/server.d.ts.map +1 -1
  19. package/dist/server.js +268 -86
  20. package/dist/server.js.map +1 -1
  21. package/dist/session-router.d.ts +40 -0
  22. package/dist/session-router.d.ts.map +1 -0
  23. package/dist/session-router.js +264 -0
  24. package/dist/session-router.js.map +1 -0
  25. package/dist/testing/client.d.ts +6 -2
  26. package/dist/testing/client.d.ts.map +1 -1
  27. package/dist/testing/client.js +23 -3
  28. package/dist/testing/client.js.map +1 -1
  29. package/dist/testing/host.d.ts +57 -0
  30. package/dist/testing/host.d.ts.map +1 -0
  31. package/dist/testing/host.js +189 -0
  32. package/dist/testing/host.js.map +1 -0
  33. package/dist/testing/index.d.ts +1 -1
  34. package/dist/testing/index.d.ts.map +1 -1
  35. package/dist/testing/index.js +1 -1
  36. package/dist/testing/index.js.map +1 -1
  37. package/dist/testing/server.d.ts +8 -7
  38. package/dist/testing/server.d.ts.map +1 -1
  39. package/dist/testing/server.js +7 -7
  40. package/dist/testing/server.js.map +1 -1
  41. package/dist/transports/unix/address.d.ts +3 -0
  42. package/dist/transports/unix/address.d.ts.map +1 -0
  43. package/dist/transports/unix/address.js +9 -0
  44. package/dist/transports/unix/address.js.map +1 -0
  45. package/dist/transports/unix/index.d.ts +1 -0
  46. package/dist/transports/unix/index.d.ts.map +1 -1
  47. package/dist/transports/unix/index.js +1 -0
  48. package/dist/transports/unix/index.js.map +1 -1
  49. package/dist/transports/unix/listener.d.ts +2 -3
  50. package/dist/transports/unix/listener.d.ts.map +1 -1
  51. package/dist/transports/unix/listener.js +13 -24
  52. package/dist/transports/unix/listener.js.map +1 -1
  53. package/dist/transports/unix/preset.d.ts +5 -4
  54. package/dist/transports/unix/preset.d.ts.map +1 -1
  55. package/dist/transports/unix/preset.js +5 -4
  56. package/dist/transports/unix/preset.js.map +1 -1
  57. package/dist/transports/unix/types.d.ts +2 -2
  58. package/dist/transports/unix/types.d.ts.map +1 -1
  59. package/dist/transports/unix/types.js.map +1 -1
  60. package/dist/types.d.ts +40 -47
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/types.js.map +1 -1
  63. package/package.json +4 -3
  64. package/dist/protocol.d.ts +0 -22
  65. package/dist/protocol.d.ts.map +0 -1
  66. package/dist/protocol.js +0 -266
  67. package/dist/protocol.js.map +0 -1
  68. package/dist/sessions.d.ts +0 -2174
  69. package/dist/sessions.d.ts.map +0 -1
  70. package/dist/sessions.js +0 -305
  71. package/dist/sessions.js.map +0 -1
  72. package/dist/snapshots.d.ts +0 -24
  73. package/dist/snapshots.d.ts.map +0 -1
  74. package/dist/snapshots.js +0 -39
  75. package/dist/snapshots.js.map +0 -1
  76. package/dist/testing/service.d.ts +0 -59
  77. package/dist/testing/service.d.ts.map +0 -1
  78. package/dist/testing/service.js +0 -240
  79. package/dist/testing/service.js.map +0 -1
@@ -1,240 +0,0 @@
1
- import { PiServerError } from "../errors.js";
2
- export const TEST_MODEL = {
3
- provider: "test",
4
- id: "small",
5
- name: "Test Small",
6
- api: "test-api",
7
- reasoning: true,
8
- input: ["text", "image"],
9
- contextWindow: 16_000,
10
- maxTokens: 2_000,
11
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
12
- supportedThinkingLevels: ["off", "medium", "high"],
13
- authenticated: true,
14
- };
15
- export class Deferred {
16
- promise;
17
- resolvePromise;
18
- constructor() {
19
- this.promise = new Promise((resolve) => {
20
- this.resolvePromise = resolve;
21
- });
22
- }
23
- resolve(value) {
24
- this.resolvePromise(value);
25
- }
26
- }
27
- export class TestSessionRuntime {
28
- disposed = new Deferred();
29
- disposeCount = 0;
30
- steers = [];
31
- stored;
32
- onDispose;
33
- listeners = new Set();
34
- pendingPrompt;
35
- constructor(stored, onDispose) {
36
- this.stored = stored;
37
- this.onDispose = onDispose;
38
- }
39
- snapshot() {
40
- return structuredClone(this.stored.snapshot);
41
- }
42
- getPhase() {
43
- return this.stored.snapshot.phase;
44
- }
45
- async prompt(input) {
46
- if (this.getPhase() !== "idle")
47
- throw new PiServerError("busy", "A prompt is already running");
48
- const done = new Deferred();
49
- this.pendingPrompt = { input, done };
50
- this.update({
51
- phase: "turn",
52
- transcript: [
53
- ...this.stored.snapshot.transcript,
54
- {
55
- id: `user-${this.stored.snapshot.revision + 1}`,
56
- role: "user",
57
- content: [{ type: "text", text: input.text }],
58
- timestamp: this.stored.snapshot.revision + 1,
59
- },
60
- ],
61
- });
62
- const outcome = await done.promise;
63
- const assistant = outcome === "complete"
64
- ? {
65
- id: `assistant-${this.stored.snapshot.revision + 1}`,
66
- role: "assistant",
67
- content: [{ type: "text", text: `reply:${input.text}` }],
68
- status: "complete",
69
- model: this.stored.snapshot.model,
70
- stopReason: "stop",
71
- timestamp: this.stored.snapshot.revision + 1,
72
- }
73
- : {
74
- id: `assistant-${this.stored.snapshot.revision + 1}`,
75
- role: "assistant",
76
- content: [{ type: "text", text: "" }],
77
- status: "aborted",
78
- model: this.stored.snapshot.model,
79
- stopReason: "aborted",
80
- timestamp: this.stored.snapshot.revision + 1,
81
- };
82
- this.update({
83
- phase: "idle",
84
- transcript: [...this.stored.snapshot.transcript, assistant],
85
- });
86
- this.pendingPrompt = undefined;
87
- }
88
- async steer(input) {
89
- if (this.getPhase() === "idle")
90
- throw new PiServerError("busy", "There is no active prompt to steer");
91
- this.steers.push(input);
92
- this.update({
93
- queuedSteerCount: this.stored.snapshot.queuedSteerCount + 1,
94
- queuedSteer: [
95
- ...this.stored.snapshot.queuedSteer,
96
- {
97
- id: `steer-${this.stored.snapshot.revision + 1}`,
98
- role: "user",
99
- content: [{ type: "text", text: input.text }],
100
- timestamp: this.stored.snapshot.revision + 1,
101
- },
102
- ],
103
- });
104
- }
105
- async abort() {
106
- if (!this.pendingPrompt)
107
- throw new PiServerError("busy", "There is no active prompt to abort");
108
- this.pendingPrompt.done.resolve("aborted");
109
- }
110
- async setModel(model) {
111
- if (this.getPhase() !== "idle")
112
- throw new PiServerError("busy", "Session is busy");
113
- this.update({ model });
114
- }
115
- async setThinking(thinkingLevel) {
116
- if (this.getPhase() !== "idle")
117
- throw new PiServerError("busy", "Session is busy");
118
- this.update({ thinkingLevel });
119
- }
120
- subscribe(listener) {
121
- this.listeners.add(listener);
122
- return () => this.listeners.delete(listener);
123
- }
124
- async dispose() {
125
- this.disposeCount += 1;
126
- this.onDispose();
127
- this.disposed.resolve(undefined);
128
- }
129
- setPhase(phase) {
130
- this.stored.snapshot = { ...this.stored.snapshot, phase };
131
- }
132
- finishPrompt() {
133
- if (!this.pendingPrompt)
134
- throw new Error("No prompt is pending");
135
- this.pendingPrompt.done.resolve("complete");
136
- }
137
- emitProgress(progress) {
138
- for (const listener of this.listeners)
139
- listener({ type: "progress", progress });
140
- }
141
- emitError(error) {
142
- for (const listener of this.listeners)
143
- listener({ type: "error", error });
144
- }
145
- emitSnapshot() {
146
- for (const listener of this.listeners)
147
- listener({ type: "snapshot" });
148
- }
149
- update(updates) {
150
- this.stored.snapshot = {
151
- ...this.stored.snapshot,
152
- ...updates,
153
- revision: this.stored.snapshot.revision + 1,
154
- updatedAt: this.stored.snapshot.updatedAt + 1,
155
- };
156
- this.emitSnapshot();
157
- }
158
- }
159
- export class TestServerService {
160
- sessions = new Map();
161
- runtimes = new Map();
162
- locked = new Set();
163
- lastCreatedId;
164
- nextListDelay;
165
- async listSessions() {
166
- const delay = this.nextListDelay;
167
- if (delay) {
168
- this.nextListDelay = undefined;
169
- delay.entered.resolve(undefined);
170
- await delay.release.promise;
171
- }
172
- return [...this.sessions.values()].map(({ snapshot }) => ({
173
- id: snapshot.id,
174
- createdAt: snapshot.createdAt,
175
- updatedAt: snapshot.updatedAt,
176
- sessionName: snapshot.name,
177
- cwd: snapshot.cwd,
178
- }));
179
- }
180
- async listModels() {
181
- return [TEST_MODEL];
182
- }
183
- async createSession(options) {
184
- this.lastCreatedId = options.id;
185
- if (this.sessions.has(options.id))
186
- throw new PiServerError("session_locked", "Session already exists");
187
- this.seed(options.id, options.name, options.cwd, options.model, options.thinkingLevel);
188
- return this.acquire(options.id);
189
- }
190
- async openSession(sessionId) {
191
- if (!this.sessions.has(sessionId))
192
- throw new PiServerError("not_found", `Unknown session: ${sessionId}`);
193
- if (this.locked.has(sessionId))
194
- throw new PiServerError("session_locked", `Session is locked: ${sessionId}`);
195
- return this.acquire(sessionId);
196
- }
197
- seed(id = "session-1", name = `Session ${id}`, cwd = "/tmp/pi-server-conformance", model = { provider: TEST_MODEL.provider, id: TEST_MODEL.id }, thinkingLevel = "off") {
198
- this.sessions.set(id, {
199
- snapshot: {
200
- id,
201
- name,
202
- cwd,
203
- createdAt: 1,
204
- updatedAt: 1,
205
- phase: "idle",
206
- model,
207
- thinkingLevel,
208
- attached: false,
209
- locked: false,
210
- revision: 0,
211
- transcript: [],
212
- queuedSteer: [],
213
- queuedSteerCount: 0,
214
- },
215
- });
216
- }
217
- delayNextList() {
218
- const delay = { entered: new Deferred(), release: new Deferred() };
219
- this.nextListDelay = delay;
220
- return delay;
221
- }
222
- latestRuntime(id) {
223
- const runtimes = this.runtimes.get(id);
224
- if (!runtimes?.length)
225
- throw new Error(`No runtime for ${id}`);
226
- return runtimes.at(-1);
227
- }
228
- acquire(id) {
229
- const stored = this.sessions.get(id);
230
- if (!stored)
231
- throw new Error(`Unknown session: ${id}`);
232
- this.locked.add(id);
233
- const runtime = new TestSessionRuntime(stored, () => this.locked.delete(id));
234
- const runtimes = this.runtimes.get(id) ?? [];
235
- runtimes.push(runtime);
236
- this.runtimes.set(id, runtimes);
237
- return runtime;
238
- }
239
- }
240
- //# sourceMappingURL=service.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/testing/service.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tModelMetadata,\n\tModelRef,\n\tSessionMetadata,\n\tSessionPhase,\n\tSessionSnapshot,\n\tThinkingLevel,\n\tTranscriptProgress,\n} from \"@earendil-works/pi-protocol\";\nimport { PiServerError } from \"../errors.ts\";\nimport type {\n\tCreateSessionOptions,\n\tPiServerService,\n\tPiSessionRuntime,\n\tPiSessionRuntimeEvent,\n\tPromptInput,\n} from \"../types.ts\";\n\nexport const TEST_MODEL: ModelMetadata = {\n\tprovider: \"test\",\n\tid: \"small\",\n\tname: \"Test Small\",\n\tapi: \"test-api\",\n\treasoning: true,\n\tinput: [\"text\", \"image\"],\n\tcontextWindow: 16_000,\n\tmaxTokens: 2_000,\n\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n\tsupportedThinkingLevels: [\"off\", \"medium\", \"high\"],\n\tauthenticated: true,\n};\n\nexport class Deferred<T> {\n\treadonly promise: Promise<T>;\n\tprivate resolvePromise!: (value: T) => void;\n\n\tconstructor() {\n\t\tthis.promise = new Promise<T>((resolve) => {\n\t\t\tthis.resolvePromise = resolve;\n\t\t});\n\t}\n\n\tresolve(value: T): void {\n\t\tthis.resolvePromise(value);\n\t}\n}\n\ninterface StoredSession {\n\tsnapshot: SessionSnapshot;\n}\n\nexport class TestSessionRuntime implements PiSessionRuntime {\n\treadonly disposed = new Deferred<void>();\n\tdisposeCount = 0;\n\treadonly steers: PromptInput[] = [];\n\tprivate readonly stored: StoredSession;\n\tprivate readonly onDispose: () => void;\n\tprivate readonly listeners = new Set<(event: PiSessionRuntimeEvent) => void>();\n\tprivate pendingPrompt?: { input: PromptInput; done: Deferred<\"complete\" | \"aborted\"> };\n\n\tconstructor(stored: StoredSession, onDispose: () => void) {\n\t\tthis.stored = stored;\n\t\tthis.onDispose = onDispose;\n\t}\n\n\tsnapshot(): SessionSnapshot {\n\t\treturn structuredClone(this.stored.snapshot);\n\t}\n\n\tgetPhase(): SessionPhase {\n\t\treturn this.stored.snapshot.phase;\n\t}\n\n\tasync prompt(input: PromptInput): Promise<void> {\n\t\tif (this.getPhase() !== \"idle\") throw new PiServerError(\"busy\", \"A prompt is already running\");\n\t\tconst done = new Deferred<\"complete\" | \"aborted\">();\n\t\tthis.pendingPrompt = { input, done };\n\t\tthis.update({\n\t\t\tphase: \"turn\",\n\t\t\ttranscript: [\n\t\t\t\t...this.stored.snapshot.transcript,\n\t\t\t\t{\n\t\t\t\t\tid: `user-${this.stored.snapshot.revision + 1}`,\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: [{ type: \"text\", text: input.text }],\n\t\t\t\t\ttimestamp: this.stored.snapshot.revision + 1,\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t\tconst outcome = await done.promise;\n\t\tconst assistant =\n\t\t\toutcome === \"complete\"\n\t\t\t\t? {\n\t\t\t\t\t\tid: `assistant-${this.stored.snapshot.revision + 1}`,\n\t\t\t\t\t\trole: \"assistant\" as const,\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: `reply:${input.text}` }],\n\t\t\t\t\t\tstatus: \"complete\" as const,\n\t\t\t\t\t\tmodel: this.stored.snapshot.model,\n\t\t\t\t\t\tstopReason: \"stop\" as const,\n\t\t\t\t\t\ttimestamp: this.stored.snapshot.revision + 1,\n\t\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\t\tid: `assistant-${this.stored.snapshot.revision + 1}`,\n\t\t\t\t\t\trole: \"assistant\" as const,\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: \"\" }],\n\t\t\t\t\t\tstatus: \"aborted\" as const,\n\t\t\t\t\t\tmodel: this.stored.snapshot.model,\n\t\t\t\t\t\tstopReason: \"aborted\" as const,\n\t\t\t\t\t\ttimestamp: this.stored.snapshot.revision + 1,\n\t\t\t\t\t};\n\t\tthis.update({\n\t\t\tphase: \"idle\",\n\t\t\ttranscript: [...this.stored.snapshot.transcript, assistant],\n\t\t});\n\t\tthis.pendingPrompt = undefined;\n\t}\n\n\tasync steer(input: PromptInput): Promise<void> {\n\t\tif (this.getPhase() === \"idle\") throw new PiServerError(\"busy\", \"There is no active prompt to steer\");\n\t\tthis.steers.push(input);\n\t\tthis.update({\n\t\t\tqueuedSteerCount: this.stored.snapshot.queuedSteerCount + 1,\n\t\t\tqueuedSteer: [\n\t\t\t\t...this.stored.snapshot.queuedSteer,\n\t\t\t\t{\n\t\t\t\t\tid: `steer-${this.stored.snapshot.revision + 1}`,\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: [{ type: \"text\", text: input.text }],\n\t\t\t\t\ttimestamp: this.stored.snapshot.revision + 1,\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t}\n\n\tasync abort(): Promise<void> {\n\t\tif (!this.pendingPrompt) throw new PiServerError(\"busy\", \"There is no active prompt to abort\");\n\t\tthis.pendingPrompt.done.resolve(\"aborted\");\n\t}\n\n\tasync setModel(model: ModelRef): Promise<void> {\n\t\tif (this.getPhase() !== \"idle\") throw new PiServerError(\"busy\", \"Session is busy\");\n\t\tthis.update({ model });\n\t}\n\n\tasync setThinking(thinkingLevel: ThinkingLevel): Promise<void> {\n\t\tif (this.getPhase() !== \"idle\") throw new PiServerError(\"busy\", \"Session is busy\");\n\t\tthis.update({ thinkingLevel });\n\t}\n\n\tsubscribe(listener: (event: PiSessionRuntimeEvent) => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tthis.disposeCount += 1;\n\t\tthis.onDispose();\n\t\tthis.disposed.resolve(undefined);\n\t}\n\n\tsetPhase(phase: SessionPhase): void {\n\t\tthis.stored.snapshot = { ...this.stored.snapshot, phase };\n\t}\n\n\tfinishPrompt(): void {\n\t\tif (!this.pendingPrompt) throw new Error(\"No prompt is pending\");\n\t\tthis.pendingPrompt.done.resolve(\"complete\");\n\t}\n\n\temitProgress(progress: TranscriptProgress): void {\n\t\tfor (const listener of this.listeners) listener({ type: \"progress\", progress });\n\t}\n\n\temitError(error: PiServerError): void {\n\t\tfor (const listener of this.listeners) listener({ type: \"error\", error });\n\t}\n\n\temitSnapshot(): void {\n\t\tfor (const listener of this.listeners) listener({ type: \"snapshot\" });\n\t}\n\n\tprivate update(updates: Partial<SessionSnapshot>): void {\n\t\tthis.stored.snapshot = {\n\t\t\t...this.stored.snapshot,\n\t\t\t...updates,\n\t\t\trevision: this.stored.snapshot.revision + 1,\n\t\t\tupdatedAt: this.stored.snapshot.updatedAt + 1,\n\t\t};\n\t\tthis.emitSnapshot();\n\t}\n}\n\ninterface ListDelay {\n\tentered: Deferred<void>;\n\trelease: Deferred<void>;\n}\n\nexport class TestServerService implements PiServerService {\n\treadonly sessions = new Map<string, StoredSession>();\n\treadonly runtimes = new Map<string, TestSessionRuntime[]>();\n\treadonly locked = new Set<string>();\n\tlastCreatedId?: string;\n\tprivate nextListDelay?: ListDelay;\n\n\tasync listSessions(): Promise<SessionMetadata[]> {\n\t\tconst delay = this.nextListDelay;\n\t\tif (delay) {\n\t\t\tthis.nextListDelay = undefined;\n\t\t\tdelay.entered.resolve(undefined);\n\t\t\tawait delay.release.promise;\n\t\t}\n\t\treturn [...this.sessions.values()].map(({ snapshot }) => ({\n\t\t\tid: snapshot.id,\n\t\t\tcreatedAt: snapshot.createdAt,\n\t\t\tupdatedAt: snapshot.updatedAt,\n\t\t\tsessionName: snapshot.name,\n\t\t\tcwd: snapshot.cwd,\n\t\t}));\n\t}\n\n\tasync listModels(): Promise<ModelMetadata[]> {\n\t\treturn [TEST_MODEL];\n\t}\n\n\tasync createSession(options: CreateSessionOptions): Promise<PiSessionRuntime> {\n\t\tthis.lastCreatedId = options.id;\n\t\tif (this.sessions.has(options.id)) throw new PiServerError(\"session_locked\", \"Session already exists\");\n\t\tthis.seed(options.id, options.name, options.cwd, options.model, options.thinkingLevel);\n\t\treturn this.acquire(options.id);\n\t}\n\n\tasync openSession(sessionId: string): Promise<PiSessionRuntime> {\n\t\tif (!this.sessions.has(sessionId)) throw new PiServerError(\"not_found\", `Unknown session: ${sessionId}`);\n\t\tif (this.locked.has(sessionId)) throw new PiServerError(\"session_locked\", `Session is locked: ${sessionId}`);\n\t\treturn this.acquire(sessionId);\n\t}\n\n\tseed(\n\t\tid = \"session-1\",\n\t\tname = `Session ${id}`,\n\t\tcwd = \"/tmp/pi-server-conformance\",\n\t\tmodel: ModelRef = { provider: TEST_MODEL.provider, id: TEST_MODEL.id },\n\t\tthinkingLevel: ThinkingLevel = \"off\",\n\t): void {\n\t\tthis.sessions.set(id, {\n\t\t\tsnapshot: {\n\t\t\t\tid,\n\t\t\t\tname,\n\t\t\t\tcwd,\n\t\t\t\tcreatedAt: 1,\n\t\t\t\tupdatedAt: 1,\n\t\t\t\tphase: \"idle\",\n\t\t\t\tmodel,\n\t\t\t\tthinkingLevel,\n\t\t\t\tattached: false,\n\t\t\t\tlocked: false,\n\t\t\t\trevision: 0,\n\t\t\t\ttranscript: [],\n\t\t\t\tqueuedSteer: [],\n\t\t\t\tqueuedSteerCount: 0,\n\t\t\t},\n\t\t});\n\t}\n\n\tdelayNextList(): ListDelay {\n\t\tconst delay = { entered: new Deferred<void>(), release: new Deferred<void>() };\n\t\tthis.nextListDelay = delay;\n\t\treturn delay;\n\t}\n\n\tlatestRuntime(id: string): TestSessionRuntime {\n\t\tconst runtimes = this.runtimes.get(id);\n\t\tif (!runtimes?.length) throw new Error(`No runtime for ${id}`);\n\t\treturn runtimes.at(-1)!;\n\t}\n\n\tprivate acquire(id: string): TestSessionRuntime {\n\t\tconst stored = this.sessions.get(id);\n\t\tif (!stored) throw new Error(`Unknown session: ${id}`);\n\t\tthis.locked.add(id);\n\t\tconst runtime = new TestSessionRuntime(stored, () => this.locked.delete(id));\n\t\tconst runtimes = this.runtimes.get(id) ?? [];\n\t\truntimes.push(runtime);\n\t\tthis.runtimes.set(id, runtimes);\n\t\treturn runtime;\n\t}\n}\n"]}