@earendil-works/pi-client 0.84.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/CHANGELOG.md +13 -0
- package/README.md +63 -0
- package/dist/client.d.ts +25 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +385 -0
- package/dist/client.js.map +1 -0
- package/dist/connection.d.ts +24 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +204 -0
- package/dist/connection.js.map +1 -0
- package/dist/errors.d.ts +23 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +46 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/promise.d.ts +8 -0
- package/dist/promise.d.ts.map +1 -0
- package/dist/promise.js +11 -0
- package/dist/promise.js.map +1 -0
- package/dist/session-handle.d.ts +54 -0
- package/dist/session-handle.d.ts.map +1 -0
- package/dist/session-handle.js +51 -0
- package/dist/session-handle.js.map +1 -0
- package/dist/state.d.ts +22 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +145 -0
- package/dist/state.js.map +1 -0
- package/dist/transport.d.ts +17 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +2 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/unix.d.ts +8 -0
- package/dist/unix.d.ts.map +1 -0
- package/dist/unix.js +151 -0
- package/dist/unix.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { DEFAULT_MAX_FRAME_LENGTH, encodeClientMessage, PROTOCOL_VERSION, ProtocolValidationError, ServerMessageDecoder, } from "@earendil-works/pi-protocol";
|
|
2
|
+
import { PiDisconnectedError, PiServerError, toDisconnectedError, toError } from "./errors.js";
|
|
3
|
+
import { createPromiseResolvers } from "./promise.js";
|
|
4
|
+
const MAX_UINT32 = 0xffff_ffff;
|
|
5
|
+
export class Connection {
|
|
6
|
+
#options;
|
|
7
|
+
#maxFrameLength;
|
|
8
|
+
#lifecycle = { state: "disconnected" };
|
|
9
|
+
#sequence = 0;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.#options = options;
|
|
12
|
+
this.#maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;
|
|
13
|
+
if (!Number.isSafeInteger(this.#maxFrameLength) ||
|
|
14
|
+
this.#maxFrameLength <= 0 ||
|
|
15
|
+
this.#maxFrameLength > MAX_UINT32) {
|
|
16
|
+
throw new TypeError(`PiClient maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
get state() {
|
|
20
|
+
return this.#lifecycle.state;
|
|
21
|
+
}
|
|
22
|
+
get maxFrameLength() {
|
|
23
|
+
return this.#maxFrameLength;
|
|
24
|
+
}
|
|
25
|
+
connect() {
|
|
26
|
+
if (this.#lifecycle.state !== "disconnected") {
|
|
27
|
+
return Promise.reject(new PiDisconnectedError(`PiClient is already ${this.#lifecycle.state}`));
|
|
28
|
+
}
|
|
29
|
+
const id = ++this.#sequence;
|
|
30
|
+
const handshake = createPromiseResolvers();
|
|
31
|
+
this.#lifecycle = {
|
|
32
|
+
state: "connecting",
|
|
33
|
+
id,
|
|
34
|
+
decoder: new ServerMessageDecoder({ maxFrameLength: this.#maxFrameLength }),
|
|
35
|
+
handshake,
|
|
36
|
+
};
|
|
37
|
+
this.#options.onStateChange({ state: "connecting" });
|
|
38
|
+
const handlers = {
|
|
39
|
+
onData: (chunk) => this.#handleData(id, chunk),
|
|
40
|
+
onClose: () => {
|
|
41
|
+
if (this.#isCurrent(id))
|
|
42
|
+
this.#handleClose();
|
|
43
|
+
},
|
|
44
|
+
onError: (error) => {
|
|
45
|
+
if (this.#isCurrent(id))
|
|
46
|
+
this.#failAndClose(toDisconnectedError(error));
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
void this.#openTransport(id, handlers);
|
|
50
|
+
return handshake.promise;
|
|
51
|
+
}
|
|
52
|
+
disconnect(reason = "Client disconnected") {
|
|
53
|
+
if (this.#lifecycle.state === "disconnected")
|
|
54
|
+
return;
|
|
55
|
+
this.#failAndClose(typeof reason === "string" ? new PiDisconnectedError(reason) : reason);
|
|
56
|
+
}
|
|
57
|
+
fail(error) {
|
|
58
|
+
this.#failAndClose(error);
|
|
59
|
+
}
|
|
60
|
+
send(frame) {
|
|
61
|
+
const lifecycle = this.#lifecycle;
|
|
62
|
+
if (lifecycle.state !== "connected")
|
|
63
|
+
throw new PiDisconnectedError();
|
|
64
|
+
let sending;
|
|
65
|
+
try {
|
|
66
|
+
sending = lifecycle.transport.send(frame);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
this.#failAndClose(toDisconnectedError(error));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
void sending.catch((error) => {
|
|
73
|
+
const current = this.#lifecycle;
|
|
74
|
+
if (current.state !== "disconnected" && current.transport === lifecycle.transport) {
|
|
75
|
+
this.#failAndClose(toDisconnectedError(error));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async #openTransport(id, handlers) {
|
|
80
|
+
let transport;
|
|
81
|
+
try {
|
|
82
|
+
transport = await this.#options.transportFactory(handlers);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (this.#isCurrent(id))
|
|
86
|
+
this.#fail(toDisconnectedError(error));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const lifecycle = this.#lifecycle;
|
|
90
|
+
if (lifecycle.state !== "connecting" || lifecycle.id !== id) {
|
|
91
|
+
transport.close();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
this.#lifecycle = { ...lifecycle, transport };
|
|
95
|
+
try {
|
|
96
|
+
await transport.send(encodeClientMessage({ type: "hello", version: PROTOCOL_VERSION }, { maxFrameLength: this.#maxFrameLength }));
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (this.#isCurrent(id))
|
|
100
|
+
this.#failAndClose(toDisconnectedError(error));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
#handleData(id, chunk) {
|
|
104
|
+
const lifecycle = this.#lifecycle;
|
|
105
|
+
if (lifecycle.state === "disconnected" || lifecycle.id !== id)
|
|
106
|
+
return;
|
|
107
|
+
if (lifecycle.state === "connecting" && !lifecycle.transport) {
|
|
108
|
+
this.#failAndClose(new ProtocolValidationError("Received server data before the client hello was sent"));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
let messages;
|
|
112
|
+
try {
|
|
113
|
+
messages = lifecycle.decoder.push(chunk);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
this.#failAndClose(toError(error));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
for (const message of messages) {
|
|
120
|
+
if (this.#lifecycle.state === "disconnected")
|
|
121
|
+
return;
|
|
122
|
+
this.#handleMessage(message);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
#handleMessage(message) {
|
|
126
|
+
const lifecycle = this.#lifecycle;
|
|
127
|
+
if (lifecycle.state === "connecting") {
|
|
128
|
+
if (message.type === "hello_error") {
|
|
129
|
+
this.#failAndClose(new PiServerError(message.error));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (message.type !== "hello") {
|
|
133
|
+
this.#failAndClose(new ProtocolValidationError("Expected server hello as first message"));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (!lifecycle.transport) {
|
|
137
|
+
this.#failAndClose(new ProtocolValidationError("Received server hello before the client hello was sent"));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const connected = {
|
|
141
|
+
state: "connected",
|
|
142
|
+
id: lifecycle.id,
|
|
143
|
+
decoder: lifecycle.decoder,
|
|
144
|
+
transport: lifecycle.transport,
|
|
145
|
+
handshake: lifecycle.handshake,
|
|
146
|
+
};
|
|
147
|
+
this.#lifecycle = connected;
|
|
148
|
+
try {
|
|
149
|
+
this.#options.onHandshake(message.snapshot);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (this.#lifecycle === connected)
|
|
153
|
+
this.#failAndClose(toError(error));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (this.#lifecycle !== connected)
|
|
157
|
+
return;
|
|
158
|
+
this.#options.onStateChange({ state: "connected" });
|
|
159
|
+
if (this.#lifecycle !== connected)
|
|
160
|
+
return;
|
|
161
|
+
this.#lifecycle = { ...connected, handshake: undefined };
|
|
162
|
+
lifecycle.handshake.resolve(message.snapshot);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (lifecycle.state !== "connected")
|
|
166
|
+
return;
|
|
167
|
+
if (message.type === "hello" || message.type === "hello_error") {
|
|
168
|
+
this.#failAndClose(new ProtocolValidationError("Unexpected handshake message"));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.#options.onMessage(message);
|
|
172
|
+
}
|
|
173
|
+
#handleClose() {
|
|
174
|
+
const lifecycle = this.#lifecycle;
|
|
175
|
+
if (lifecycle.state === "disconnected")
|
|
176
|
+
return;
|
|
177
|
+
let error = new PiDisconnectedError("Byte transport closed");
|
|
178
|
+
try {
|
|
179
|
+
lifecycle.decoder.end();
|
|
180
|
+
}
|
|
181
|
+
catch (decoderError) {
|
|
182
|
+
error = toError(decoderError);
|
|
183
|
+
}
|
|
184
|
+
this.#fail(error);
|
|
185
|
+
}
|
|
186
|
+
#failAndClose(error) {
|
|
187
|
+
const lifecycle = this.#lifecycle;
|
|
188
|
+
const transport = lifecycle.state === "disconnected" ? undefined : lifecycle.transport;
|
|
189
|
+
this.#fail(error);
|
|
190
|
+
transport?.close();
|
|
191
|
+
}
|
|
192
|
+
#fail(error) {
|
|
193
|
+
const lifecycle = this.#lifecycle;
|
|
194
|
+
if (lifecycle.state === "disconnected")
|
|
195
|
+
return;
|
|
196
|
+
this.#lifecycle = { state: "disconnected" };
|
|
197
|
+
lifecycle.handshake?.reject(error);
|
|
198
|
+
this.#options.onStateChange({ state: "disconnected", error });
|
|
199
|
+
}
|
|
200
|
+
#isCurrent(id) {
|
|
201
|
+
return this.#lifecycle.state !== "disconnected" && this.#lifecycle.id === id;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tDEFAULT_MAX_FRAME_LENGTH,\n\tencodeClientMessage,\n\tPROTOCOL_VERSION,\n\tProtocolValidationError,\n\ttype ServerMessage,\n\tServerMessageDecoder,\n\ttype ServerSnapshot,\n} from \"@earendil-works/pi-protocol\";\nimport { PiDisconnectedError, PiServerError, toDisconnectedError, toError } from \"./errors.ts\";\nimport { createPromiseResolvers, type PromiseResolvers } from \"./promise.ts\";\nimport type { ByteTransport, ByteTransportFactory, ByteTransportHandlers } from \"./transport.ts\";\nimport type { ConnectionState, ConnectionStateChange } from \"./types.ts\";\n\nconst MAX_UINT32 = 0xffff_ffff;\n\ntype ActiveConnection = {\n\tid: number;\n\tdecoder: ServerMessageDecoder;\n\ttransport?: ByteTransport;\n};\n\ntype ConnectionLifecycle =\n\t| { state: \"disconnected\" }\n\t| ({ state: \"connecting\"; handshake: PromiseResolvers<ServerSnapshot> } & ActiveConnection)\n\t| ({\n\t\t\tstate: \"connected\";\n\t\t\ttransport: ByteTransport;\n\t\t\thandshake: PromiseResolvers<ServerSnapshot> | undefined;\n\t } & ActiveConnection);\n\ninterface ConnectionOptions {\n\ttransportFactory: ByteTransportFactory;\n\tmaxFrameLength?: number;\n\tonHandshake(snapshot: ServerSnapshot): void;\n\tonMessage(message: Exclude<ServerMessage, { type: \"hello\" | \"hello_error\" }>): void;\n\tonStateChange(change: ConnectionStateChange): void;\n}\n\nexport class Connection {\n\treadonly #options: ConnectionOptions;\n\treadonly #maxFrameLength: number;\n\t#lifecycle: ConnectionLifecycle = { state: \"disconnected\" };\n\t#sequence = 0;\n\n\tconstructor(options: ConnectionOptions) {\n\t\tthis.#options = options;\n\t\tthis.#maxFrameLength = options.maxFrameLength ?? DEFAULT_MAX_FRAME_LENGTH;\n\t\tif (\n\t\t\t!Number.isSafeInteger(this.#maxFrameLength) ||\n\t\t\tthis.#maxFrameLength <= 0 ||\n\t\t\tthis.#maxFrameLength > MAX_UINT32\n\t\t) {\n\t\t\tthrow new TypeError(`PiClient maxFrameLength must be an integer between 1 and ${MAX_UINT32}`);\n\t\t}\n\t}\n\n\tget state(): ConnectionState {\n\t\treturn this.#lifecycle.state;\n\t}\n\n\tget maxFrameLength(): number {\n\t\treturn this.#maxFrameLength;\n\t}\n\n\tconnect(): Promise<ServerSnapshot> {\n\t\tif (this.#lifecycle.state !== \"disconnected\") {\n\t\t\treturn Promise.reject(new PiDisconnectedError(`PiClient is already ${this.#lifecycle.state}`));\n\t\t}\n\t\tconst id = ++this.#sequence;\n\t\tconst handshake = createPromiseResolvers<ServerSnapshot>();\n\t\tthis.#lifecycle = {\n\t\t\tstate: \"connecting\",\n\t\t\tid,\n\t\t\tdecoder: new ServerMessageDecoder({ maxFrameLength: this.#maxFrameLength }),\n\t\t\thandshake,\n\t\t};\n\t\tthis.#options.onStateChange({ state: \"connecting\" });\n\t\tconst handlers = {\n\t\t\tonData: (chunk) => this.#handleData(id, chunk),\n\t\t\tonClose: () => {\n\t\t\t\tif (this.#isCurrent(id)) this.#handleClose();\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\tif (this.#isCurrent(id)) this.#failAndClose(toDisconnectedError(error));\n\t\t\t},\n\t\t} satisfies ByteTransportHandlers;\n\t\tvoid this.#openTransport(id, handlers);\n\t\treturn handshake.promise;\n\t}\n\n\tdisconnect(reason: string | Error = \"Client disconnected\"): void {\n\t\tif (this.#lifecycle.state === \"disconnected\") return;\n\t\tthis.#failAndClose(typeof reason === \"string\" ? new PiDisconnectedError(reason) : reason);\n\t}\n\n\tfail(error: Error): void {\n\t\tthis.#failAndClose(error);\n\t}\n\n\tsend(frame: Uint8Array): void {\n\t\tconst lifecycle = this.#lifecycle;\n\t\tif (lifecycle.state !== \"connected\") throw new PiDisconnectedError();\n\t\tlet sending: Promise<void>;\n\t\ttry {\n\t\t\tsending = lifecycle.transport.send(frame);\n\t\t} catch (error) {\n\t\t\tthis.#failAndClose(toDisconnectedError(error));\n\t\t\treturn;\n\t\t}\n\t\tvoid sending.catch((error: unknown) => {\n\t\t\tconst current = this.#lifecycle;\n\t\t\tif (current.state !== \"disconnected\" && current.transport === lifecycle.transport) {\n\t\t\t\tthis.#failAndClose(toDisconnectedError(error));\n\t\t\t}\n\t\t});\n\t}\n\n\tasync #openTransport(id: number, handlers: ByteTransportHandlers): Promise<void> {\n\t\tlet transport: ByteTransport;\n\t\ttry {\n\t\t\ttransport = await this.#options.transportFactory(handlers);\n\t\t} catch (error) {\n\t\t\tif (this.#isCurrent(id)) this.#fail(toDisconnectedError(error));\n\t\t\treturn;\n\t\t}\n\t\tconst lifecycle = this.#lifecycle;\n\t\tif (lifecycle.state !== \"connecting\" || lifecycle.id !== id) {\n\t\t\ttransport.close();\n\t\t\treturn;\n\t\t}\n\t\tthis.#lifecycle = { ...lifecycle, transport };\n\t\ttry {\n\t\t\tawait transport.send(\n\t\t\t\tencodeClientMessage({ type: \"hello\", version: PROTOCOL_VERSION }, { maxFrameLength: this.#maxFrameLength }),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (this.#isCurrent(id)) this.#failAndClose(toDisconnectedError(error));\n\t\t}\n\t}\n\n\t#handleData(id: number, chunk: Uint8Array): void {\n\t\tconst lifecycle = this.#lifecycle;\n\t\tif (lifecycle.state === \"disconnected\" || lifecycle.id !== id) return;\n\t\tif (lifecycle.state === \"connecting\" && !lifecycle.transport) {\n\t\t\tthis.#failAndClose(new ProtocolValidationError(\"Received server data before the client hello was sent\"));\n\t\t\treturn;\n\t\t}\n\t\tlet messages: ServerMessage[];\n\t\ttry {\n\t\t\tmessages = lifecycle.decoder.push(chunk);\n\t\t} catch (error) {\n\t\t\tthis.#failAndClose(toError(error));\n\t\t\treturn;\n\t\t}\n\t\tfor (const message of messages) {\n\t\t\tif (this.#lifecycle.state === \"disconnected\") return;\n\t\t\tthis.#handleMessage(message);\n\t\t}\n\t}\n\n\t#handleMessage(message: ServerMessage): void {\n\t\tconst lifecycle = this.#lifecycle;\n\t\tif (lifecycle.state === \"connecting\") {\n\t\t\tif (message.type === \"hello_error\") {\n\t\t\t\tthis.#failAndClose(new PiServerError(message.error));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (message.type !== \"hello\") {\n\t\t\t\tthis.#failAndClose(new ProtocolValidationError(\"Expected server hello as first message\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!lifecycle.transport) {\n\t\t\t\tthis.#failAndClose(new ProtocolValidationError(\"Received server hello before the client hello was sent\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst connected = {\n\t\t\t\tstate: \"connected\",\n\t\t\t\tid: lifecycle.id,\n\t\t\t\tdecoder: lifecycle.decoder,\n\t\t\t\ttransport: lifecycle.transport,\n\t\t\t\thandshake: lifecycle.handshake,\n\t\t\t} satisfies Extract<ConnectionLifecycle, { state: \"connected\" }>;\n\t\t\tthis.#lifecycle = connected;\n\t\t\ttry {\n\t\t\t\tthis.#options.onHandshake(message.snapshot);\n\t\t\t} catch (error) {\n\t\t\t\tif (this.#lifecycle === connected) this.#failAndClose(toError(error));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (this.#lifecycle !== connected) return;\n\t\t\tthis.#options.onStateChange({ state: \"connected\" });\n\t\t\tif (this.#lifecycle !== connected) return;\n\t\t\tthis.#lifecycle = { ...connected, handshake: undefined };\n\t\t\tlifecycle.handshake.resolve(message.snapshot);\n\t\t\treturn;\n\t\t}\n\t\tif (lifecycle.state !== \"connected\") return;\n\t\tif (message.type === \"hello\" || message.type === \"hello_error\") {\n\t\t\tthis.#failAndClose(new ProtocolValidationError(\"Unexpected handshake message\"));\n\t\t\treturn;\n\t\t}\n\t\tthis.#options.onMessage(message);\n\t}\n\n\t#handleClose(): void {\n\t\tconst lifecycle = this.#lifecycle;\n\t\tif (lifecycle.state === \"disconnected\") return;\n\t\tlet error: Error = new PiDisconnectedError(\"Byte transport closed\");\n\t\ttry {\n\t\t\tlifecycle.decoder.end();\n\t\t} catch (decoderError) {\n\t\t\terror = toError(decoderError);\n\t\t}\n\t\tthis.#fail(error);\n\t}\n\n\t#failAndClose(error: Error): void {\n\t\tconst lifecycle = this.#lifecycle;\n\t\tconst transport = lifecycle.state === \"disconnected\" ? undefined : lifecycle.transport;\n\t\tthis.#fail(error);\n\t\ttransport?.close();\n\t}\n\n\t#fail(error: Error): void {\n\t\tconst lifecycle = this.#lifecycle;\n\t\tif (lifecycle.state === \"disconnected\") return;\n\t\tthis.#lifecycle = { state: \"disconnected\" };\n\t\tlifecycle.handshake?.reject(error);\n\t\tthis.#options.onStateChange({ state: \"disconnected\", error });\n\t}\n\n\t#isCurrent(id: number): boolean {\n\t\treturn this.#lifecycle.state !== \"disconnected\" && this.#lifecycle.id === id;\n\t}\n}\n"]}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { JsonValue, ProtocolError, ProtocolErrorCode } from "@earendil-works/pi-protocol";
|
|
2
|
+
export declare class PiServerError extends Error {
|
|
3
|
+
readonly code: ProtocolErrorCode;
|
|
4
|
+
readonly details: JsonValue | undefined;
|
|
5
|
+
constructor(error: ProtocolError);
|
|
6
|
+
}
|
|
7
|
+
export declare class PiDisconnectedError extends Error {
|
|
8
|
+
constructor(message?: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class PiClientDisposedError extends Error {
|
|
11
|
+
constructor();
|
|
12
|
+
}
|
|
13
|
+
export declare class PiSessionOwnershipError extends Error {
|
|
14
|
+
readonly sessionId: string;
|
|
15
|
+
constructor(sessionId: string, message: string);
|
|
16
|
+
}
|
|
17
|
+
export declare class PiSessionDetachedError extends Error {
|
|
18
|
+
readonly sessionId: string;
|
|
19
|
+
constructor(sessionId: string);
|
|
20
|
+
}
|
|
21
|
+
export declare function toError(error: unknown): Error;
|
|
22
|
+
export declare function toDisconnectedError(error: unknown): PiDisconnectedError;
|
|
23
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAE/F,qBAAa,aAAc,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC;IAExC,YAAY,KAAK,EAAE,aAAa,EAK/B;CACD;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC7C,YAAY,OAAO,SAA8B,EAGhD;CACD;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC/C,cAGC;CACD;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,YAAY,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAI7C;CACD;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,YAAY,SAAS,EAAE,MAAM,EAI5B;CACD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAE7C;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CAGvE","sourcesContent":["import type { JsonValue, ProtocolError, ProtocolErrorCode } from \"@earendil-works/pi-protocol\";\n\nexport class PiServerError extends Error {\n\treadonly code: ProtocolErrorCode;\n\treadonly details: JsonValue | undefined;\n\n\tconstructor(error: ProtocolError) {\n\t\tsuper(error.message);\n\t\tthis.name = \"PiServerError\";\n\t\tthis.code = error.code;\n\t\tthis.details = error.details;\n\t}\n}\n\nexport class PiDisconnectedError extends Error {\n\tconstructor(message = \"Pi client is disconnected\") {\n\t\tsuper(message);\n\t\tthis.name = \"PiDisconnectedError\";\n\t}\n}\n\nexport class PiClientDisposedError extends Error {\n\tconstructor() {\n\t\tsuper(\"Pi client is disposed\");\n\t\tthis.name = \"PiClientDisposedError\";\n\t}\n}\n\nexport class PiSessionOwnershipError extends Error {\n\treadonly sessionId: string;\n\n\tconstructor(sessionId: string, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"PiSessionOwnershipError\";\n\t\tthis.sessionId = sessionId;\n\t}\n}\n\nexport class PiSessionDetachedError extends Error {\n\treadonly sessionId: string;\n\n\tconstructor(sessionId: string) {\n\t\tsuper(`Session ${sessionId} is not attached`);\n\t\tthis.name = \"PiSessionDetachedError\";\n\t\tthis.sessionId = sessionId;\n\t}\n}\n\nexport function toError(error: unknown): Error {\n\treturn error instanceof Error ? error : new Error(String(error));\n}\n\nexport function toDisconnectedError(error: unknown): PiDisconnectedError {\n\tconst cause = toError(error);\n\treturn cause instanceof PiDisconnectedError ? cause : new PiDisconnectedError(cause.message);\n}\n"]}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export class PiServerError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
details;
|
|
4
|
+
constructor(error) {
|
|
5
|
+
super(error.message);
|
|
6
|
+
this.name = "PiServerError";
|
|
7
|
+
this.code = error.code;
|
|
8
|
+
this.details = error.details;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class PiDisconnectedError extends Error {
|
|
12
|
+
constructor(message = "Pi client is disconnected") {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "PiDisconnectedError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class PiClientDisposedError extends Error {
|
|
18
|
+
constructor() {
|
|
19
|
+
super("Pi client is disposed");
|
|
20
|
+
this.name = "PiClientDisposedError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class PiSessionOwnershipError extends Error {
|
|
24
|
+
sessionId;
|
|
25
|
+
constructor(sessionId, message) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "PiSessionOwnershipError";
|
|
28
|
+
this.sessionId = sessionId;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class PiSessionDetachedError extends Error {
|
|
32
|
+
sessionId;
|
|
33
|
+
constructor(sessionId) {
|
|
34
|
+
super(`Session ${sessionId} is not attached`);
|
|
35
|
+
this.name = "PiSessionDetachedError";
|
|
36
|
+
this.sessionId = sessionId;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function toError(error) {
|
|
40
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
41
|
+
}
|
|
42
|
+
export function toDisconnectedError(error) {
|
|
43
|
+
const cause = toError(error);
|
|
44
|
+
return cause instanceof PiDisconnectedError ? cause : new PiDisconnectedError(cause.message);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,aAAc,SAAQ,KAAK;IAC9B,IAAI,CAAoB;IACxB,OAAO,CAAwB;IAExC,YAAY,KAAoB,EAAE;QACjC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAAA,CAC7B;CACD;AAED,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC7C,YAAY,OAAO,GAAG,2BAA2B,EAAE;QAClD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IAAA,CAClC;CACD;AAED,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC/C,cAAc;QACb,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IAAA,CACpC;CACD;AAED,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACxC,SAAS,CAAS;IAE3B,YAAY,SAAiB,EAAE,OAAe,EAAE;QAC/C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;QACtC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAAA,CAC3B;CACD;AAED,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IACvC,SAAS,CAAS;IAE3B,YAAY,SAAiB,EAAE;QAC9B,KAAK,CAAC,WAAW,SAAS,kBAAkB,CAAC,CAAC;QAC9C,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAAA,CAC3B;CACD;AAED,MAAM,UAAU,OAAO,CAAC,KAAc,EAAS;IAC9C,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAAA,CACjE;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAc,EAAuB;IACxE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,OAAO,KAAK,YAAY,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,mBAAmB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AAAA,CAC7F","sourcesContent":["import type { JsonValue, ProtocolError, ProtocolErrorCode } from \"@earendil-works/pi-protocol\";\n\nexport class PiServerError extends Error {\n\treadonly code: ProtocolErrorCode;\n\treadonly details: JsonValue | undefined;\n\n\tconstructor(error: ProtocolError) {\n\t\tsuper(error.message);\n\t\tthis.name = \"PiServerError\";\n\t\tthis.code = error.code;\n\t\tthis.details = error.details;\n\t}\n}\n\nexport class PiDisconnectedError extends Error {\n\tconstructor(message = \"Pi client is disconnected\") {\n\t\tsuper(message);\n\t\tthis.name = \"PiDisconnectedError\";\n\t}\n}\n\nexport class PiClientDisposedError extends Error {\n\tconstructor() {\n\t\tsuper(\"Pi client is disposed\");\n\t\tthis.name = \"PiClientDisposedError\";\n\t}\n}\n\nexport class PiSessionOwnershipError extends Error {\n\treadonly sessionId: string;\n\n\tconstructor(sessionId: string, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"PiSessionOwnershipError\";\n\t\tthis.sessionId = sessionId;\n\t}\n}\n\nexport class PiSessionDetachedError extends Error {\n\treadonly sessionId: string;\n\n\tconstructor(sessionId: string) {\n\t\tsuper(`Session ${sessionId} is not attached`);\n\t\tthis.name = \"PiSessionDetachedError\";\n\t\tthis.sessionId = sessionId;\n\t}\n}\n\nexport function toError(error: unknown): Error {\n\treturn error instanceof Error ? error : new Error(String(error));\n}\n\nexport function toDisconnectedError(error: unknown): PiDisconnectedError {\n\tconst cause = toError(error);\n\treturn cause instanceof PiDisconnectedError ? cause : new PiDisconnectedError(cause.message);\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { PiClient } from "./client.ts";
|
|
2
|
+
export { PiClientDisposedError, PiDisconnectedError, PiServerError, PiSessionDetachedError, PiSessionOwnershipError, } from "./errors.ts";
|
|
3
|
+
export type { AcquireSessionOptions, PiSessionHandle, SessionLease, SessionLeaseMode } from "./session-handle.ts";
|
|
4
|
+
export type { ByteTransport, ByteTransportFactory, ByteTransportHandlers } from "./transport.ts";
|
|
5
|
+
export type { ConnectionState, ConnectionStateChange, CreateSessionOptions, ListenerErrorHandler, PiClientOptions, Unsubscribe, } from "./types.ts";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EACN,qBAAqB,EACrB,mBAAmB,EACnB,aAAa,EACb,sBAAsB,EACtB,uBAAuB,GACvB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAClH,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACjG,YAAY,EACX,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,EACf,WAAW,GACX,MAAM,YAAY,CAAC","sourcesContent":["export { PiClient } from \"./client.ts\";\nexport {\n\tPiClientDisposedError,\n\tPiDisconnectedError,\n\tPiServerError,\n\tPiSessionDetachedError,\n\tPiSessionOwnershipError,\n} from \"./errors.ts\";\nexport type { AcquireSessionOptions, PiSessionHandle, SessionLease, SessionLeaseMode } from \"./session-handle.ts\";\nexport type { ByteTransport, ByteTransportFactory, ByteTransportHandlers } from \"./transport.ts\";\nexport type {\n\tConnectionState,\n\tConnectionStateChange,\n\tCreateSessionOptions,\n\tListenerErrorHandler,\n\tPiClientOptions,\n\tUnsubscribe,\n} from \"./types.ts\";\n"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EACN,qBAAqB,EACrB,mBAAmB,EACnB,aAAa,EACb,sBAAsB,EACtB,uBAAuB,GACvB,MAAM,aAAa,CAAC","sourcesContent":["export { PiClient } from \"./client.ts\";\nexport {\n\tPiClientDisposedError,\n\tPiDisconnectedError,\n\tPiServerError,\n\tPiSessionDetachedError,\n\tPiSessionOwnershipError,\n} from \"./errors.ts\";\nexport type { AcquireSessionOptions, PiSessionHandle, SessionLease, SessionLeaseMode } from \"./session-handle.ts\";\nexport type { ByteTransport, ByteTransportFactory, ByteTransportHandlers } from \"./transport.ts\";\nexport type {\n\tConnectionState,\n\tConnectionStateChange,\n\tCreateSessionOptions,\n\tListenerErrorHandler,\n\tPiClientOptions,\n\tUnsubscribe,\n} from \"./types.ts\";\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface PromiseResolvers<T> {
|
|
2
|
+
promise: Promise<T>;
|
|
3
|
+
resolve(value: T | PromiseLike<T>): void;
|
|
4
|
+
reject(reason?: unknown): void;
|
|
5
|
+
}
|
|
6
|
+
/** Remove in favor of `Promise.withResolvers()` when the repository's TypeScript lib baseline moves to ES2024. */
|
|
7
|
+
export declare function createPromiseResolvers<T>(): PromiseResolvers<T>;
|
|
8
|
+
//# sourceMappingURL=promise.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promise.d.ts","sourceRoot":"","sources":["../src/promise.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB,CAAC,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACpB,OAAO,CAAC,KAAK,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACzC,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC/B;AAED,kHAAkH;AAClH,wBAAgB,sBAAsB,CAAC,CAAC,KAAK,gBAAgB,CAAC,CAAC,CAAC,CAQ/D","sourcesContent":["export interface PromiseResolvers<T> {\n\tpromise: Promise<T>;\n\tresolve(value: T | PromiseLike<T>): void;\n\treject(reason?: unknown): void;\n}\n\n/** Remove in favor of `Promise.withResolvers()` when the repository's TypeScript lib baseline moves to ES2024. */\nexport function createPromiseResolvers<T>(): PromiseResolvers<T> {\n\tlet resolve!: PromiseResolvers<T>[\"resolve\"];\n\tlet reject!: PromiseResolvers<T>[\"reject\"];\n\tconst promise = new Promise<T>((resolvePromise, rejectPromise) => {\n\t\tresolve = resolvePromise;\n\t\treject = rejectPromise;\n\t});\n\treturn { promise, resolve, reject };\n}\n"]}
|
package/dist/promise.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Remove in favor of `Promise.withResolvers()` when the repository's TypeScript lib baseline moves to ES2024. */
|
|
2
|
+
export function createPromiseResolvers() {
|
|
3
|
+
let resolve;
|
|
4
|
+
let reject;
|
|
5
|
+
const promise = new Promise((resolvePromise, rejectPromise) => {
|
|
6
|
+
resolve = resolvePromise;
|
|
7
|
+
reject = rejectPromise;
|
|
8
|
+
});
|
|
9
|
+
return { promise, resolve, reject };
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=promise.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promise.js","sourceRoot":"","sources":["../src/promise.ts"],"names":[],"mappings":"AAMA,kHAAkH;AAClH,MAAM,UAAU,sBAAsB,GAA2B;IAChE,IAAI,OAAwC,CAAC;IAC7C,IAAI,MAAsC,CAAC;IAC3C,MAAM,OAAO,GAAG,IAAI,OAAO,CAAI,CAAC,cAAc,EAAE,aAAa,EAAE,EAAE,CAAC;QACjE,OAAO,GAAG,cAAc,CAAC;QACzB,MAAM,GAAG,aAAa,CAAC;IAAA,CACvB,CAAC,CAAC;IACH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AAAA,CACpC","sourcesContent":["export interface PromiseResolvers<T> {\n\tpromise: Promise<T>;\n\tresolve(value: T | PromiseLike<T>): void;\n\treject(reason?: unknown): void;\n}\n\n/** Remove in favor of `Promise.withResolvers()` when the repository's TypeScript lib baseline moves to ES2024. */\nexport function createPromiseResolvers<T>(): PromiseResolvers<T> {\n\tlet resolve!: PromiseResolvers<T>[\"resolve\"];\n\tlet reject!: PromiseResolvers<T>[\"reject\"];\n\tconst promise = new Promise<T>((resolvePromise, rejectPromise) => {\n\t\tresolve = resolvePromise;\n\t\treject = rejectPromise;\n\t});\n\treturn { promise, resolve, reject };\n}\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Command, ModelRef, ResultForCommand, ServerEvent, SessionSnapshot, ThinkingLevel } from "@earendil-works/pi-protocol";
|
|
2
|
+
import type { Unsubscribe } from "./types.ts";
|
|
3
|
+
type SessionCommand = Extract<Command, {
|
|
4
|
+
sessionId: string;
|
|
5
|
+
}>;
|
|
6
|
+
export type SessionLeaseMode = "shared" | "exclusive";
|
|
7
|
+
export interface AcquireSessionOptions {
|
|
8
|
+
mode: SessionLeaseMode;
|
|
9
|
+
}
|
|
10
|
+
export interface SessionLease extends AsyncDisposable {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly active: boolean;
|
|
13
|
+
readonly attached: boolean;
|
|
14
|
+
readonly snapshot: SessionSnapshot | undefined;
|
|
15
|
+
subscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe;
|
|
16
|
+
onEvent(listener: (event: ServerEvent) => void): Unsubscribe;
|
|
17
|
+
detach(): Promise<void>;
|
|
18
|
+
dispose(): Promise<void>;
|
|
19
|
+
prompt(text: string): Promise<SessionSnapshot>;
|
|
20
|
+
steer(text: string): Promise<SessionSnapshot>;
|
|
21
|
+
abort(): Promise<SessionSnapshot>;
|
|
22
|
+
setModel(model: ModelRef): Promise<SessionSnapshot>;
|
|
23
|
+
setThinking(thinkingLevel: ThinkingLevel): Promise<SessionSnapshot>;
|
|
24
|
+
}
|
|
25
|
+
export type PiSessionHandle = SessionLease;
|
|
26
|
+
export interface SessionHandleCallbacks {
|
|
27
|
+
isAttached(): boolean;
|
|
28
|
+
getSnapshot(): SessionSnapshot | undefined;
|
|
29
|
+
subscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe;
|
|
30
|
+
onEvent(listener: (event: ServerEvent) => void): Unsubscribe;
|
|
31
|
+
detach(): Promise<void>;
|
|
32
|
+
dispose(): Promise<void>;
|
|
33
|
+
request<const TCommand extends SessionCommand>(command: TCommand): Promise<ResultForCommand<TCommand>>;
|
|
34
|
+
}
|
|
35
|
+
export declare class SessionHandle implements SessionLease {
|
|
36
|
+
#private;
|
|
37
|
+
readonly id: string;
|
|
38
|
+
constructor(id: string, callbacks: SessionHandleCallbacks);
|
|
39
|
+
get attached(): boolean;
|
|
40
|
+
get active(): boolean;
|
|
41
|
+
get snapshot(): SessionSnapshot | undefined;
|
|
42
|
+
subscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe;
|
|
43
|
+
onEvent(listener: (event: ServerEvent) => void): Unsubscribe;
|
|
44
|
+
detach(): Promise<void>;
|
|
45
|
+
dispose(): Promise<void>;
|
|
46
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
47
|
+
prompt(text: string): Promise<SessionSnapshot>;
|
|
48
|
+
steer(text: string): Promise<SessionSnapshot>;
|
|
49
|
+
abort(): Promise<SessionSnapshot>;
|
|
50
|
+
setModel(model: ModelRef): Promise<SessionSnapshot>;
|
|
51
|
+
setThinking(thinkingLevel: ThinkingLevel): Promise<SessionSnapshot>;
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
54
|
+
//# sourceMappingURL=session-handle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-handle.d.ts","sourceRoot":"","sources":["../src/session-handle.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tCommand,\n\tModelRef,\n\tResultForCommand,\n\tServerEvent,\n\tSessionSnapshot,\n\tThinkingLevel,\n} from \"@earendil-works/pi-protocol\";\nimport type { Unsubscribe } from \"./types.ts\";\n\ntype SessionCommand = Extract<Command, { sessionId: string }>;\n\nexport type SessionLeaseMode = \"shared\" | \"exclusive\";\n\nexport interface AcquireSessionOptions {\n\tmode: SessionLeaseMode;\n}\n\nexport interface SessionLease extends AsyncDisposable {\n\treadonly id: string;\n\treadonly active: boolean;\n\treadonly attached: boolean;\n\treadonly snapshot: SessionSnapshot | undefined;\n\tsubscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe;\n\tonEvent(listener: (event: ServerEvent) => void): Unsubscribe;\n\tdetach(): Promise<void>;\n\tdispose(): Promise<void>;\n\tprompt(text: string): Promise<SessionSnapshot>;\n\tsteer(text: string): Promise<SessionSnapshot>;\n\tabort(): Promise<SessionSnapshot>;\n\tsetModel(model: ModelRef): Promise<SessionSnapshot>;\n\tsetThinking(thinkingLevel: ThinkingLevel): Promise<SessionSnapshot>;\n}\n\nexport type PiSessionHandle = SessionLease;\n\nexport interface SessionHandleCallbacks {\n\tisAttached(): boolean;\n\tgetSnapshot(): SessionSnapshot | undefined;\n\tsubscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe;\n\tonEvent(listener: (event: ServerEvent) => void): Unsubscribe;\n\tdetach(): Promise<void>;\n\tdispose(): Promise<void>;\n\trequest<const TCommand extends SessionCommand>(command: TCommand): Promise<ResultForCommand<TCommand>>;\n}\n\nexport class SessionHandle implements SessionLease {\n\treadonly id: string;\n\treadonly #callbacks: SessionHandleCallbacks;\n\n\tconstructor(id: string, callbacks: SessionHandleCallbacks) {\n\t\tthis.id = id;\n\t\tthis.#callbacks = callbacks;\n\t}\n\n\tget attached(): boolean {\n\t\treturn this.#callbacks.isAttached();\n\t}\n\n\tget active(): boolean {\n\t\treturn this.attached;\n\t}\n\n\tget snapshot(): SessionSnapshot | undefined {\n\t\treturn this.#callbacks.getSnapshot();\n\t}\n\n\tsubscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe {\n\t\treturn this.#callbacks.subscribe(listener);\n\t}\n\n\tonEvent(listener: (event: ServerEvent) => void): Unsubscribe {\n\t\treturn this.#callbacks.onEvent(listener);\n\t}\n\n\tasync detach(): Promise<void> {\n\t\tawait this.#callbacks.detach();\n\t}\n\n\tdispose(): Promise<void> {\n\t\treturn this.#callbacks.dispose();\n\t}\n\n\t[Symbol.asyncDispose](): Promise<void> {\n\t\treturn this.dispose();\n\t}\n\n\tasync prompt(text: string): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"prompt\", sessionId: this.id, text })).session;\n\t}\n\n\tasync steer(text: string): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"steer\", sessionId: this.id, text })).session;\n\t}\n\n\tasync abort(): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"abort\", sessionId: this.id })).session;\n\t}\n\n\tasync setModel(model: ModelRef): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"set_model\", sessionId: this.id, model })).session;\n\t}\n\n\tasync setThinking(thinkingLevel: ThinkingLevel): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"set_thinking\", sessionId: this.id, thinkingLevel })).session;\n\t}\n\n\t#request<const TCommand extends SessionCommand>(command: TCommand): Promise<ResultForCommand<TCommand>> {\n\t\treturn this.#callbacks.request(command);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export class SessionHandle {
|
|
2
|
+
id;
|
|
3
|
+
#callbacks;
|
|
4
|
+
constructor(id, callbacks) {
|
|
5
|
+
this.id = id;
|
|
6
|
+
this.#callbacks = callbacks;
|
|
7
|
+
}
|
|
8
|
+
get attached() {
|
|
9
|
+
return this.#callbacks.isAttached();
|
|
10
|
+
}
|
|
11
|
+
get active() {
|
|
12
|
+
return this.attached;
|
|
13
|
+
}
|
|
14
|
+
get snapshot() {
|
|
15
|
+
return this.#callbacks.getSnapshot();
|
|
16
|
+
}
|
|
17
|
+
subscribe(listener) {
|
|
18
|
+
return this.#callbacks.subscribe(listener);
|
|
19
|
+
}
|
|
20
|
+
onEvent(listener) {
|
|
21
|
+
return this.#callbacks.onEvent(listener);
|
|
22
|
+
}
|
|
23
|
+
async detach() {
|
|
24
|
+
await this.#callbacks.detach();
|
|
25
|
+
}
|
|
26
|
+
dispose() {
|
|
27
|
+
return this.#callbacks.dispose();
|
|
28
|
+
}
|
|
29
|
+
[Symbol.asyncDispose]() {
|
|
30
|
+
return this.dispose();
|
|
31
|
+
}
|
|
32
|
+
async prompt(text) {
|
|
33
|
+
return (await this.#request({ command: "prompt", sessionId: this.id, text })).session;
|
|
34
|
+
}
|
|
35
|
+
async steer(text) {
|
|
36
|
+
return (await this.#request({ command: "steer", sessionId: this.id, text })).session;
|
|
37
|
+
}
|
|
38
|
+
async abort() {
|
|
39
|
+
return (await this.#request({ command: "abort", sessionId: this.id })).session;
|
|
40
|
+
}
|
|
41
|
+
async setModel(model) {
|
|
42
|
+
return (await this.#request({ command: "set_model", sessionId: this.id, model })).session;
|
|
43
|
+
}
|
|
44
|
+
async setThinking(thinkingLevel) {
|
|
45
|
+
return (await this.#request({ command: "set_thinking", sessionId: this.id, thinkingLevel })).session;
|
|
46
|
+
}
|
|
47
|
+
#request(command) {
|
|
48
|
+
return this.#callbacks.request(command);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=session-handle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-handle.js","sourceRoot":"","sources":["../src/session-handle.ts"],"names":[],"mappings":"AA8CA,MAAM,OAAO,aAAa;IAChB,EAAE,CAAS;IACX,UAAU,CAAyB;IAE5C,YAAY,EAAU,EAAE,SAAiC,EAAE;QAC1D,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;IAAA,CAC5B;IAED,IAAI,QAAQ,GAAY;QACvB,OAAO,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;IAAA,CACpC;IAED,IAAI,MAAM,GAAY;QACrB,OAAO,IAAI,CAAC,QAAQ,CAAC;IAAA,CACrB;IAED,IAAI,QAAQ,GAAgC;QAC3C,OAAO,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;IAAA,CACrC;IAED,SAAS,CAAC,QAA6C,EAAe;QACrE,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAAA,CAC3C;IAED,OAAO,CAAC,QAAsC,EAAe;QAC5D,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAAA,CACzC;IAED,KAAK,CAAC,MAAM,GAAkB;QAC7B,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;IAAA,CAC/B;IAED,OAAO,GAAkB;QACxB,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;IAAA,CACjC;IAED,CAAC,MAAM,CAAC,YAAY,CAAC,GAAkB;QACtC,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IAAA,CACtB;IAED,KAAK,CAAC,MAAM,CAAC,IAAY,EAA4B;QACpD,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAAA,CACtF;IAED,KAAK,CAAC,KAAK,CAAC,IAAY,EAA4B;QACnD,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAAA,CACrF;IAED,KAAK,CAAC,KAAK,GAA6B;QACvC,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAAA,CAC/E;IAED,KAAK,CAAC,QAAQ,CAAC,KAAe,EAA4B;QACzD,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAAA,CAC1F;IAED,KAAK,CAAC,WAAW,CAAC,aAA4B,EAA4B;QACzE,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAAA,CACrG;IAED,QAAQ,CAAwC,OAAiB,EAAuC;QACvG,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAAA,CACxC;CACD","sourcesContent":["import type {\n\tCommand,\n\tModelRef,\n\tResultForCommand,\n\tServerEvent,\n\tSessionSnapshot,\n\tThinkingLevel,\n} from \"@earendil-works/pi-protocol\";\nimport type { Unsubscribe } from \"./types.ts\";\n\ntype SessionCommand = Extract<Command, { sessionId: string }>;\n\nexport type SessionLeaseMode = \"shared\" | \"exclusive\";\n\nexport interface AcquireSessionOptions {\n\tmode: SessionLeaseMode;\n}\n\nexport interface SessionLease extends AsyncDisposable {\n\treadonly id: string;\n\treadonly active: boolean;\n\treadonly attached: boolean;\n\treadonly snapshot: SessionSnapshot | undefined;\n\tsubscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe;\n\tonEvent(listener: (event: ServerEvent) => void): Unsubscribe;\n\tdetach(): Promise<void>;\n\tdispose(): Promise<void>;\n\tprompt(text: string): Promise<SessionSnapshot>;\n\tsteer(text: string): Promise<SessionSnapshot>;\n\tabort(): Promise<SessionSnapshot>;\n\tsetModel(model: ModelRef): Promise<SessionSnapshot>;\n\tsetThinking(thinkingLevel: ThinkingLevel): Promise<SessionSnapshot>;\n}\n\nexport type PiSessionHandle = SessionLease;\n\nexport interface SessionHandleCallbacks {\n\tisAttached(): boolean;\n\tgetSnapshot(): SessionSnapshot | undefined;\n\tsubscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe;\n\tonEvent(listener: (event: ServerEvent) => void): Unsubscribe;\n\tdetach(): Promise<void>;\n\tdispose(): Promise<void>;\n\trequest<const TCommand extends SessionCommand>(command: TCommand): Promise<ResultForCommand<TCommand>>;\n}\n\nexport class SessionHandle implements SessionLease {\n\treadonly id: string;\n\treadonly #callbacks: SessionHandleCallbacks;\n\n\tconstructor(id: string, callbacks: SessionHandleCallbacks) {\n\t\tthis.id = id;\n\t\tthis.#callbacks = callbacks;\n\t}\n\n\tget attached(): boolean {\n\t\treturn this.#callbacks.isAttached();\n\t}\n\n\tget active(): boolean {\n\t\treturn this.attached;\n\t}\n\n\tget snapshot(): SessionSnapshot | undefined {\n\t\treturn this.#callbacks.getSnapshot();\n\t}\n\n\tsubscribe(listener: (snapshot: SessionSnapshot) => void): Unsubscribe {\n\t\treturn this.#callbacks.subscribe(listener);\n\t}\n\n\tonEvent(listener: (event: ServerEvent) => void): Unsubscribe {\n\t\treturn this.#callbacks.onEvent(listener);\n\t}\n\n\tasync detach(): Promise<void> {\n\t\tawait this.#callbacks.detach();\n\t}\n\n\tdispose(): Promise<void> {\n\t\treturn this.#callbacks.dispose();\n\t}\n\n\t[Symbol.asyncDispose](): Promise<void> {\n\t\treturn this.dispose();\n\t}\n\n\tasync prompt(text: string): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"prompt\", sessionId: this.id, text })).session;\n\t}\n\n\tasync steer(text: string): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"steer\", sessionId: this.id, text })).session;\n\t}\n\n\tasync abort(): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"abort\", sessionId: this.id })).session;\n\t}\n\n\tasync setModel(model: ModelRef): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"set_model\", sessionId: this.id, model })).session;\n\t}\n\n\tasync setThinking(thinkingLevel: ThinkingLevel): Promise<SessionSnapshot> {\n\t\treturn (await this.#request({ command: \"set_thinking\", sessionId: this.id, thinkingLevel })).session;\n\t}\n\n\t#request<const TCommand extends SessionCommand>(command: TCommand): Promise<ResultForCommand<TCommand>> {\n\t\treturn this.#callbacks.request(command);\n\t}\n}\n"]}
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CommandResult, ServerEvent, ServerSnapshot, SessionSnapshot } from "@earendil-works/pi-protocol";
|
|
2
|
+
import type { ListenerErrorHandler, Unsubscribe } from "./types.ts";
|
|
3
|
+
export declare class ClientState {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(onListenerError?: ListenerErrorHandler);
|
|
6
|
+
get snapshot(): ServerSnapshot | undefined;
|
|
7
|
+
reset(): void;
|
|
8
|
+
clearAttachments(): void;
|
|
9
|
+
dispose(): void;
|
|
10
|
+
getSessionSnapshot(sessionId: string): SessionSnapshot | undefined;
|
|
11
|
+
isSessionAttached(sessionId: string): boolean;
|
|
12
|
+
forgetSessionSnapshot(sessionId: string): SessionSnapshot | undefined;
|
|
13
|
+
restoreSessionSnapshot(snapshot: SessionSnapshot): void;
|
|
14
|
+
subscribe(listener: (snapshot: ServerSnapshot) => void): Unsubscribe;
|
|
15
|
+
onEvent(listener: (event: ServerEvent) => void): Unsubscribe;
|
|
16
|
+
subscribeSession(sessionId: string, listener: (snapshot: SessionSnapshot) => void): Unsubscribe;
|
|
17
|
+
onSessionEvent(sessionId: string, listener: (event: ServerEvent) => void): Unsubscribe;
|
|
18
|
+
applyResult(result: CommandResult): void;
|
|
19
|
+
applyEvent(event: ServerEvent): void;
|
|
20
|
+
applyServerSnapshot(snapshot: ServerSnapshot): void;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE/G,OAAO,KAAK,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEpE,qBAAa,WAAW;;IAUvB,YAAY,eAAe,CAAC,EAAE,oBAAoB,EAEjD;IAED,IAAI,QAAQ,IAAI,cAAc,GAAG,SAAS,CAEzC;IAED,KAAK,IAAI,IAAI,CAIZ;IAED,gBAAgB,IAAI,IAAI,CAEvB;IAED,OAAO,IAAI,IAAI,CAMd;IAED,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEjE;IAED,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE5C;IAED,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAIpE;IAED,sBAAsB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAEtD;IAED,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,GAAG,WAAW,CAGnE;IAED,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,WAAW,CAG3D;IAED,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,IAAI,GAAG,WAAW,CAE9F;IAED,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,WAAW,CAErF;IAED,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CASvC;IAED,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAUnC;IAED,mBAAmB,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAIlD;CA6BD","sourcesContent":["import type { CommandResult, ServerEvent, ServerSnapshot, SessionSnapshot } from \"@earendil-works/pi-protocol\";\nimport { toError } from \"./errors.ts\";\nimport type { ListenerErrorHandler, Unsubscribe } from \"./types.ts\";\n\nexport class ClientState {\n\treadonly #sessionSnapshots = new Map<string, SessionSnapshot>();\n\treadonly #attachedSessionIds = new Set<string>();\n\treadonly #snapshotListeners = new Set<(snapshot: ServerSnapshot) => void>();\n\treadonly #eventListeners = new Set<(event: ServerEvent) => void>();\n\treadonly #sessionSnapshotListeners = new Map<string, Set<(snapshot: SessionSnapshot) => void>>();\n\treadonly #sessionEventListeners = new Map<string, Set<(event: ServerEvent) => void>>();\n\treadonly #onListenerError: ListenerErrorHandler | undefined;\n\t#snapshot: ServerSnapshot | undefined;\n\n\tconstructor(onListenerError?: ListenerErrorHandler) {\n\t\tthis.#onListenerError = onListenerError;\n\t}\n\n\tget snapshot(): ServerSnapshot | undefined {\n\t\treturn this.#snapshot;\n\t}\n\n\treset(): void {\n\t\tthis.#snapshot = undefined;\n\t\tthis.#sessionSnapshots.clear();\n\t\tthis.#attachedSessionIds.clear();\n\t}\n\n\tclearAttachments(): void {\n\t\tthis.#attachedSessionIds.clear();\n\t}\n\n\tdispose(): void {\n\t\tthis.reset();\n\t\tthis.#snapshotListeners.clear();\n\t\tthis.#eventListeners.clear();\n\t\tthis.#sessionSnapshotListeners.clear();\n\t\tthis.#sessionEventListeners.clear();\n\t}\n\n\tgetSessionSnapshot(sessionId: string): SessionSnapshot | undefined {\n\t\treturn this.#sessionSnapshots.get(sessionId);\n\t}\n\n\tisSessionAttached(sessionId: string): boolean {\n\t\treturn this.#attachedSessionIds.has(sessionId);\n\t}\n\n\tforgetSessionSnapshot(sessionId: string): SessionSnapshot | undefined {\n\t\tconst previous = this.#sessionSnapshots.get(sessionId);\n\t\tthis.#sessionSnapshots.delete(sessionId);\n\t\treturn previous;\n\t}\n\n\trestoreSessionSnapshot(snapshot: SessionSnapshot): void {\n\t\tif (!this.#sessionSnapshots.has(snapshot.id)) this.#sessionSnapshots.set(snapshot.id, snapshot);\n\t}\n\n\tsubscribe(listener: (snapshot: ServerSnapshot) => void): Unsubscribe {\n\t\tthis.#snapshotListeners.add(listener);\n\t\treturn () => this.#snapshotListeners.delete(listener);\n\t}\n\n\tonEvent(listener: (event: ServerEvent) => void): Unsubscribe {\n\t\tthis.#eventListeners.add(listener);\n\t\treturn () => this.#eventListeners.delete(listener);\n\t}\n\n\tsubscribeSession(sessionId: string, listener: (snapshot: SessionSnapshot) => void): Unsubscribe {\n\t\treturn addMappedListener(this.#sessionSnapshotListeners, sessionId, listener);\n\t}\n\n\tonSessionEvent(sessionId: string, listener: (event: ServerEvent) => void): Unsubscribe {\n\t\treturn addMappedListener(this.#sessionEventListeners, sessionId, listener);\n\t}\n\n\tapplyResult(result: CommandResult): void {\n\t\tif (result.command === \"list\") return;\n\t\tif (result.command === \"detach\") {\n\t\t\tthis.#attachedSessionIds.delete(result.sessionId);\n\t\t\tconst snapshot = this.#sessionSnapshots.get(result.sessionId);\n\t\t\tif (snapshot) this.#applySessionSnapshot({ ...snapshot, attached: false }, true);\n\t\t\treturn;\n\t\t}\n\t\tthis.#applySessionSnapshot(result.session);\n\t}\n\n\tapplyEvent(event: ServerEvent): void {\n\t\tif (event.type === \"server_snapshot\") this.applyServerSnapshot(event.snapshot);\n\t\tif (event.type === \"session_snapshot\") this.#applySessionSnapshot(event.snapshot);\n\t\tif (event.type === \"session_removed\") {\n\t\t\tthis.#sessionSnapshots.delete(event.sessionId);\n\t\t\tthis.#attachedSessionIds.delete(event.sessionId);\n\t\t}\n\t\tthis.#notify(this.#eventListeners, event);\n\t\tconst sessionId = getEventSessionId(event);\n\t\tif (sessionId) this.#notify(this.#sessionEventListeners.get(sessionId), event);\n\t}\n\n\tapplyServerSnapshot(snapshot: ServerSnapshot): void {\n\t\tif (this.#snapshot && snapshot.revision < this.#snapshot.revision) return;\n\t\tthis.#snapshot = snapshot;\n\t\tthis.#notify(this.#snapshotListeners, snapshot);\n\t}\n\n\t#applySessionSnapshot(snapshot: SessionSnapshot, force = false): void {\n\t\tconst current = this.#sessionSnapshots.get(snapshot.id);\n\t\tif (!force && current && snapshot.revision < current.revision) return;\n\t\tthis.#sessionSnapshots.set(snapshot.id, snapshot);\n\t\tif (snapshot.attached) this.#attachedSessionIds.add(snapshot.id);\n\t\telse this.#attachedSessionIds.delete(snapshot.id);\n\t\tthis.#notify(this.#sessionSnapshotListeners.get(snapshot.id), snapshot);\n\t}\n\n\t#notify<T>(listeners: Iterable<(value: T) => void> | undefined, value: T): void {\n\t\tfor (const listener of listeners ?? []) {\n\t\t\ttry {\n\t\t\t\tlistener(value);\n\t\t\t} catch (error) {\n\t\t\t\tthis.#reportListenerError(error);\n\t\t\t}\n\t\t}\n\t}\n\n\t#reportListenerError(error: unknown): void {\n\t\tif (!this.#onListenerError) return;\n\t\ttry {\n\t\t\tthis.#onListenerError(toError(error));\n\t\t} catch {\n\t\t\t// Diagnostics cannot affect client state.\n\t\t}\n\t}\n}\n\nfunction addMappedListener<T>(\n\tlistenersById: Map<string, Set<(value: T) => void>>,\n\tid: string,\n\tlistener: (value: T) => void,\n): Unsubscribe {\n\tlet listeners = listenersById.get(id);\n\tif (!listeners) {\n\t\tlisteners = new Set();\n\t\tlistenersById.set(id, listeners);\n\t}\n\tlisteners.add(listener);\n\treturn () => {\n\t\tlisteners.delete(listener);\n\t\tif (listeners.size === 0) listenersById.delete(id);\n\t};\n}\n\nfunction getEventSessionId(event: ServerEvent): string | undefined {\n\tif (event.type === \"session_snapshot\") return event.snapshot.id;\n\tif (event.type === \"session_progress\" || event.type === \"session_removed\") return event.sessionId;\n\treturn undefined;\n}\n"]}
|