@earendil-works/pi-server 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 +45 -0
- package/README.md +50 -0
- package/dist/connection.d.ts +28 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +4 -0
- package/dist/connection.js.map +1 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +45 -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 +6 -0
- package/dist/index.js.map +1 -0
- package/dist/listener.d.ts +10 -0
- package/dist/listener.d.ts.map +1 -0
- package/dist/listener.js +2 -0
- package/dist/listener.js.map +1 -0
- package/dist/protocol.d.ts +22 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +266 -0
- package/dist/protocol.js.map +1 -0
- package/dist/server.d.ts +36 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +365 -0
- package/dist/server.js.map +1 -0
- package/dist/sessions.d.ts +2174 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +305 -0
- package/dist/sessions.js.map +1 -0
- package/dist/snapshots.d.ts +24 -0
- package/dist/snapshots.d.ts.map +1 -0
- package/dist/snapshots.js +39 -0
- package/dist/snapshots.js.map +1 -0
- package/dist/testing/client.d.ts +31 -0
- package/dist/testing/client.d.ts.map +1 -0
- package/dist/testing/client.js +118 -0
- package/dist/testing/client.js.map +1 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +4 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/server.d.ts +12 -0
- package/dist/testing/server.d.ts.map +1 -0
- package/dist/testing/server.js +17 -0
- package/dist/testing/server.js.map +1 -0
- package/dist/testing/service.d.ts +59 -0
- package/dist/testing/service.d.ts.map +1 -0
- package/dist/testing/service.js +240 -0
- package/dist/testing/service.js.map +1 -0
- package/dist/transports/unix/index.d.ts +4 -0
- package/dist/transports/unix/index.d.ts.map +1 -0
- package/dist/transports/unix/index.js +3 -0
- package/dist/transports/unix/index.js.map +1 -0
- package/dist/transports/unix/listener.d.ts +25 -0
- package/dist/transports/unix/listener.d.ts.map +1 -0
- package/dist/transports/unix/listener.js +432 -0
- package/dist/transports/unix/listener.js.map +1 -0
- package/dist/transports/unix/preset.d.ts +6 -0
- package/dist/transports/unix/preset.d.ts.map +1 -0
- package/dist/transports/unix/preset.js +21 -0
- package/dist/transports/unix/preset.js.map +1 -0
- package/dist/transports/unix/types.d.ts +15 -0
- package/dist/transports/unix/types.d.ts.map +1 -0
- package/dist/transports/unix/types.js +2 -0
- package/dist/transports/unix/types.js.map +1 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { once } from "node:events";
|
|
2
|
+
import { createConnection } from "node:net";
|
|
3
|
+
import { encodeClientMessage, PROTOCOL_VERSION, ServerMessageDecoder, } from "@earendil-works/pi-protocol";
|
|
4
|
+
import { Deferred } from "./service.js";
|
|
5
|
+
export class ProtocolTestClient {
|
|
6
|
+
messages = [];
|
|
7
|
+
channel;
|
|
8
|
+
decoder = new ServerMessageDecoder();
|
|
9
|
+
waiters = new Set();
|
|
10
|
+
closedDeferred = new Deferred();
|
|
11
|
+
requestSequence = 0;
|
|
12
|
+
closedValue = false;
|
|
13
|
+
constructor(channel) {
|
|
14
|
+
this.channel = channel;
|
|
15
|
+
}
|
|
16
|
+
get closed() {
|
|
17
|
+
return this.closedValue;
|
|
18
|
+
}
|
|
19
|
+
hello(version = PROTOCOL_VERSION) {
|
|
20
|
+
const response = this.next((message) => message.type === "hello" || message.type === "hello_error");
|
|
21
|
+
void this.sendMessage({ type: "hello", version });
|
|
22
|
+
return response;
|
|
23
|
+
}
|
|
24
|
+
async request(command, id = `request-${++this.requestSequence}`) {
|
|
25
|
+
const response = this.next((message) => message.type === "response" && message.id === id);
|
|
26
|
+
await this.sendMessage({ type: "request", id, request: command });
|
|
27
|
+
return (await response);
|
|
28
|
+
}
|
|
29
|
+
sendMessage(message) {
|
|
30
|
+
return this.channel.send(encodeClientMessage(message));
|
|
31
|
+
}
|
|
32
|
+
sendBytes(chunk) {
|
|
33
|
+
return this.channel.send(chunk);
|
|
34
|
+
}
|
|
35
|
+
sendFragmentedMessage(message, splitAt) {
|
|
36
|
+
return this.channel.sendFragmented(encodeClientMessage(message), splitAt);
|
|
37
|
+
}
|
|
38
|
+
next(predicate) {
|
|
39
|
+
return this.nextFrom(0, predicate);
|
|
40
|
+
}
|
|
41
|
+
nextFrom(index, predicate) {
|
|
42
|
+
const existing = this.messages.slice(index).find(predicate);
|
|
43
|
+
if (existing)
|
|
44
|
+
return Promise.resolve(existing);
|
|
45
|
+
if (this.closedValue)
|
|
46
|
+
return Promise.reject(new Error("Wire client is closed"));
|
|
47
|
+
return new Promise((resolve, reject) => this.waiters.add({ predicate, resolve, reject }));
|
|
48
|
+
}
|
|
49
|
+
waitForClose() {
|
|
50
|
+
return this.closedValue ? Promise.resolve() : this.closedDeferred.promise;
|
|
51
|
+
}
|
|
52
|
+
close() {
|
|
53
|
+
return this.channel.close();
|
|
54
|
+
}
|
|
55
|
+
receive(chunk) {
|
|
56
|
+
try {
|
|
57
|
+
for (const message of this.decoder.push(chunk)) {
|
|
58
|
+
this.messages.push(message);
|
|
59
|
+
for (const waiter of this.waiters) {
|
|
60
|
+
if (!waiter.predicate(message))
|
|
61
|
+
continue;
|
|
62
|
+
this.waiters.delete(waiter);
|
|
63
|
+
waiter.resolve(message);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
this.fail(error instanceof Error ? error : new Error(String(error)));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
markClosed() {
|
|
72
|
+
if (this.closedValue)
|
|
73
|
+
return;
|
|
74
|
+
this.closedValue = true;
|
|
75
|
+
this.closedDeferred.resolve(undefined);
|
|
76
|
+
this.fail(new Error("Wire connection closed"));
|
|
77
|
+
}
|
|
78
|
+
fail(error) {
|
|
79
|
+
for (const waiter of this.waiters)
|
|
80
|
+
waiter.reject(error);
|
|
81
|
+
this.waiters.clear();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export async function connectUnixTestClient(path) {
|
|
85
|
+
const socket = createConnection(path);
|
|
86
|
+
await once(socket, "connect");
|
|
87
|
+
const client = new ProtocolTestClient({
|
|
88
|
+
send: (chunk) => writeSocket(socket, chunk),
|
|
89
|
+
async sendFragmented(chunk, splitAt) {
|
|
90
|
+
await writeSocket(socket, chunk.subarray(0, splitAt));
|
|
91
|
+
await writeSocket(socket, chunk.subarray(splitAt));
|
|
92
|
+
},
|
|
93
|
+
async close() {
|
|
94
|
+
if (socket.destroyed)
|
|
95
|
+
return;
|
|
96
|
+
const closed = once(socket, "close");
|
|
97
|
+
socket.destroy();
|
|
98
|
+
await closed;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
socket.on("data", (chunk) => {
|
|
102
|
+
client.receive(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));
|
|
103
|
+
});
|
|
104
|
+
socket.on("error", (error) => client.fail(error));
|
|
105
|
+
socket.once("close", () => client.markClosed());
|
|
106
|
+
return client;
|
|
107
|
+
}
|
|
108
|
+
function writeSocket(socket, chunk) {
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
socket.write(chunk, (error) => {
|
|
111
|
+
if (error)
|
|
112
|
+
reject(error);
|
|
113
|
+
else
|
|
114
|
+
resolve();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/testing/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAe,MAAM,UAAU,CAAC;AACzD,OAAO,EAGN,mBAAmB,EACnB,gBAAgB,EAGhB,oBAAoB,GACpB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAcxC,MAAM,OAAO,kBAAkB;IACrB,QAAQ,GAAoB,EAAE,CAAC;IACvB,OAAO,CAAc;IACrB,OAAO,GAAG,IAAI,oBAAoB,EAAE,CAAC;IACrC,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAC;IACnC,cAAc,GAAG,IAAI,QAAQ,EAAQ,CAAC;IAC/C,eAAe,GAAG,CAAC,CAAC;IACpB,WAAW,GAAG,KAAK,CAAC;IAE5B,YAAY,OAAoB,EAAE;QACjC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAAA,CACvB;IAED,IAAI,MAAM,GAAY;QACrB,OAAO,IAAI,CAAC,WAAW,CAAC;IAAA,CACxB;IAED,KAAK,CAAC,OAAO,GAAW,gBAAgB,EAA0B;QACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC;QACpG,KAAK,IAAI,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QAClD,OAAO,QAAQ,CAAC;IAAA,CAChB;IAED,KAAK,CAAC,OAAO,CAAC,OAAgB,EAAE,EAAE,GAAG,WAAW,EAAE,IAAI,CAAC,eAAe,EAAE,EAA6B;QACpG,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CACzB,CAAC,OAAO,EAA+B,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,CAAC,EAAE,KAAK,EAAE,CAC1F,CAAC;QACF,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QAClE,OAAO,CAAC,MAAM,QAAQ,CAAqB,CAAC;IAAA,CAC5C;IAED,WAAW,CAAC,OAAsB,EAAiB;QAClD,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;IAAA,CACvD;IAED,SAAS,CAAC,KAAiB,EAAiB;QAC3C,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAAA,CAChC;IAED,qBAAqB,CAAC,OAAsB,EAAE,OAAe,EAAiB;QAC7E,OAAO,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;IAAA,CAC1E;IAED,IAAI,CAAC,SAA8C,EAA0B;QAC5E,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAAA,CACnC;IAED,QAAQ,CAAC,KAAa,EAAE,SAA8C,EAA0B;QAC/F,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,QAAQ;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC/C,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;QAChF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAAA,CAC1F;IAED,YAAY,GAAkB;QAC7B,OAAO,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC;IAAA,CAC1E;IAED,KAAK,GAAkB;QACtB,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IAAA,CAC5B;IAED,OAAO,CAAC,KAAiB,EAAQ;QAChC,IAAI,CAAC;YACJ,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBAChD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBAC5B,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;oBACnC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC;wBAAE,SAAS;oBACzC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;oBAC5B,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBACzB,CAAC;YACF,CAAC;QACF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACtE,CAAC;IAAA,CACD;IAED,UAAU,GAAS;QAClB,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO;QAC7B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC,CAAC;IAAA,CAC/C;IAED,IAAI,CAAC,KAAY,EAAQ;QACxB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO;YAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACxD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IAAA,CACrB;CACD;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAY,EAA+B;IACtF,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC9B,MAAM,MAAM,GAAG,IAAI,kBAAkB,CAAC;QACrC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC;QAC3C,KAAK,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE;YACpC,MAAM,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;YACtD,MAAM,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QAAA,CACnD;QACD,KAAK,CAAC,KAAK,GAAG;YACb,IAAI,MAAM,CAAC,SAAS;gBAAE,OAAO;YAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YACrC,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,MAAM,CAAC;QAAA,CACb;KACD,CAAC,CAAC;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;QAC5B,MAAM,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;IAAA,CACjF,CAAC,CAAC;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAClD,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;IAChD,OAAO,MAAM,CAAC;AAAA,CACd;AAED,SAAS,WAAW,CAAC,MAAc,EAAE,KAAiB,EAAiB;IACtE,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;YAC9B,IAAI,KAAK;gBAAE,MAAM,CAAC,KAAK,CAAC,CAAC;;gBACpB,OAAO,EAAE,CAAC;QAAA,CACf,CAAC,CAAC;IAAA,CACH,CAAC,CAAC;AAAA,CACH","sourcesContent":["import { once } from \"node:events\";\nimport { createConnection, type Socket } from \"node:net\";\nimport {\n\ttype ClientMessage,\n\ttype Command,\n\tencodeClientMessage,\n\tPROTOCOL_VERSION,\n\ttype ResponseEnvelope,\n\ttype ServerMessage,\n\tServerMessageDecoder,\n} from \"@earendil-works/pi-protocol\";\nimport { Deferred } from \"./service.ts\";\n\ninterface MessageWaiter {\n\tpredicate: (message: ServerMessage) => boolean;\n\tresolve: (message: ServerMessage) => void;\n\treject: (error: Error) => void;\n}\n\nexport interface WireChannel {\n\tsend(chunk: Uint8Array): Promise<void>;\n\tsendFragmented(chunk: Uint8Array, splitAt: number): Promise<void>;\n\tclose(): Promise<void>;\n}\n\nexport class ProtocolTestClient {\n\treadonly messages: ServerMessage[] = [];\n\tprivate readonly channel: WireChannel;\n\tprivate readonly decoder = new ServerMessageDecoder();\n\tprivate readonly waiters = new Set<MessageWaiter>();\n\tprivate readonly closedDeferred = new Deferred<void>();\n\tprivate requestSequence = 0;\n\tprivate closedValue = false;\n\n\tconstructor(channel: WireChannel) {\n\t\tthis.channel = channel;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.closedValue;\n\t}\n\n\thello(version: number = PROTOCOL_VERSION): Promise<ServerMessage> {\n\t\tconst response = this.next((message) => message.type === \"hello\" || message.type === \"hello_error\");\n\t\tvoid this.sendMessage({ type: \"hello\", version });\n\t\treturn response;\n\t}\n\n\tasync request(command: Command, id = `request-${++this.requestSequence}`): Promise<ResponseEnvelope> {\n\t\tconst response = this.next(\n\t\t\t(message): message is ResponseEnvelope => message.type === \"response\" && message.id === id,\n\t\t);\n\t\tawait this.sendMessage({ type: \"request\", id, request: command });\n\t\treturn (await response) as ResponseEnvelope;\n\t}\n\n\tsendMessage(message: ClientMessage): Promise<void> {\n\t\treturn this.channel.send(encodeClientMessage(message));\n\t}\n\n\tsendBytes(chunk: Uint8Array): Promise<void> {\n\t\treturn this.channel.send(chunk);\n\t}\n\n\tsendFragmentedMessage(message: ClientMessage, splitAt: number): Promise<void> {\n\t\treturn this.channel.sendFragmented(encodeClientMessage(message), splitAt);\n\t}\n\n\tnext(predicate: (message: ServerMessage) => boolean): Promise<ServerMessage> {\n\t\treturn this.nextFrom(0, predicate);\n\t}\n\n\tnextFrom(index: number, predicate: (message: ServerMessage) => boolean): Promise<ServerMessage> {\n\t\tconst existing = this.messages.slice(index).find(predicate);\n\t\tif (existing) return Promise.resolve(existing);\n\t\tif (this.closedValue) return Promise.reject(new Error(\"Wire client is closed\"));\n\t\treturn new Promise((resolve, reject) => this.waiters.add({ predicate, resolve, reject }));\n\t}\n\n\twaitForClose(): Promise<void> {\n\t\treturn this.closedValue ? Promise.resolve() : this.closedDeferred.promise;\n\t}\n\n\tclose(): Promise<void> {\n\t\treturn this.channel.close();\n\t}\n\n\treceive(chunk: Uint8Array): void {\n\t\ttry {\n\t\t\tfor (const message of this.decoder.push(chunk)) {\n\t\t\t\tthis.messages.push(message);\n\t\t\t\tfor (const waiter of this.waiters) {\n\t\t\t\t\tif (!waiter.predicate(message)) continue;\n\t\t\t\t\tthis.waiters.delete(waiter);\n\t\t\t\t\twaiter.resolve(message);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.fail(error instanceof Error ? error : new Error(String(error)));\n\t\t}\n\t}\n\n\tmarkClosed(): void {\n\t\tif (this.closedValue) return;\n\t\tthis.closedValue = true;\n\t\tthis.closedDeferred.resolve(undefined);\n\t\tthis.fail(new Error(\"Wire connection closed\"));\n\t}\n\n\tfail(error: Error): void {\n\t\tfor (const waiter of this.waiters) waiter.reject(error);\n\t\tthis.waiters.clear();\n\t}\n}\n\nexport async function connectUnixTestClient(path: string): Promise<ProtocolTestClient> {\n\tconst socket = createConnection(path);\n\tawait once(socket, \"connect\");\n\tconst client = new ProtocolTestClient({\n\t\tsend: (chunk) => writeSocket(socket, chunk),\n\t\tasync sendFragmented(chunk, splitAt) {\n\t\t\tawait writeSocket(socket, chunk.subarray(0, splitAt));\n\t\t\tawait writeSocket(socket, chunk.subarray(splitAt));\n\t\t},\n\t\tasync close() {\n\t\t\tif (socket.destroyed) return;\n\t\t\tconst closed = once(socket, \"close\");\n\t\t\tsocket.destroy();\n\t\t\tawait closed;\n\t\t},\n\t});\n\tsocket.on(\"data\", (chunk) => {\n\t\tclient.receive(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));\n\t});\n\tsocket.on(\"error\", (error) => client.fail(error));\n\tsocket.once(\"close\", () => client.markClosed());\n\treturn client;\n}\n\nfunction writeSocket(socket: Socket, chunk: Uint8Array): Promise<void> {\n\treturn new Promise<void>((resolve, reject) => {\n\t\tsocket.write(chunk, (error) => {\n\t\t\tif (error) reject(error);\n\t\t\telse resolve();\n\t\t});\n\t});\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { WireChannel } from "./client.ts";
|
|
2
|
+
export { connectUnixTestClient, ProtocolTestClient } from "./client.ts";
|
|
3
|
+
export type { TestServer, TestServerOptions } from "./server.ts";
|
|
4
|
+
export { createTestServer } from "./server.ts";
|
|
5
|
+
export { Deferred, TEST_MODEL, TestServerService, TestSessionRuntime } from "./service.ts";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACxE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC","sourcesContent":["export type { WireChannel } from \"./client.ts\";\nexport { connectUnixTestClient, ProtocolTestClient } from \"./client.ts\";\nexport type { TestServer, TestServerOptions } from \"./server.ts\";\nexport { createTestServer } from \"./server.ts\";\nexport { Deferred, TEST_MODEL, TestServerService, TestSessionRuntime } from \"./service.ts\";\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAExE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC","sourcesContent":["export type { WireChannel } from \"./client.ts\";\nexport { connectUnixTestClient, ProtocolTestClient } from \"./client.ts\";\nexport type { TestServer, TestServerOptions } from \"./server.ts\";\nexport { createTestServer } from \"./server.ts\";\nexport { Deferred, TEST_MODEL, TestServerService, TestSessionRuntime } from \"./service.ts\";\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PiServer } from "../server.ts";
|
|
2
|
+
import type { PiServerOptions, PiServerService } from "../types.ts";
|
|
3
|
+
export interface TestServerOptions extends PiServerOptions {
|
|
4
|
+
service?: PiServerService;
|
|
5
|
+
}
|
|
6
|
+
export interface TestServer {
|
|
7
|
+
server: PiServer;
|
|
8
|
+
service: PiServerService;
|
|
9
|
+
}
|
|
10
|
+
/** Create an unstarted PiServer with deterministic defaults for transport conformance tests. */
|
|
11
|
+
export declare function createTestServer(options: TestServerOptions): TestServer;
|
|
12
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/testing/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGpE,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACzD,OAAO,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,QAAQ,CAAC;IACjB,OAAO,EAAE,eAAe,CAAC;CACzB;AAED,gGAAgG;AAChG,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAYvE","sourcesContent":["import { PiServer } from \"../server.ts\";\nimport type { PiServerOptions, PiServerService } from \"../types.ts\";\nimport { TestServerService } from \"./service.ts\";\n\nexport interface TestServerOptions extends PiServerOptions {\n\tservice?: PiServerService;\n}\n\nexport interface TestServer {\n\tserver: PiServer;\n\tservice: PiServerService;\n}\n\n/** Create an unstarted PiServer with deterministic defaults for transport conformance tests. */\nexport function createTestServer(options: TestServerOptions): TestServer {\n\tconst service = options.service ?? new TestServerService();\n\treturn {\n\t\tserver: new PiServer(service, {\n\t\t\tlisteners: options.listeners,\n\t\t\tmaxFrameLength: options.maxFrameLength,\n\t\t\thandshakeTimeoutMs: options.handshakeTimeoutMs,\n\t\t\tserverId: options.serverId,\n\t\t\tonError: options.onError,\n\t\t}),\n\t\tservice,\n\t};\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PiServer } from "../server.js";
|
|
2
|
+
import { TestServerService } from "./service.js";
|
|
3
|
+
/** Create an unstarted PiServer with deterministic defaults for transport conformance tests. */
|
|
4
|
+
export function createTestServer(options) {
|
|
5
|
+
const service = options.service ?? new TestServerService();
|
|
6
|
+
return {
|
|
7
|
+
server: new PiServer(service, {
|
|
8
|
+
listeners: options.listeners,
|
|
9
|
+
maxFrameLength: options.maxFrameLength,
|
|
10
|
+
handshakeTimeoutMs: options.handshakeTimeoutMs,
|
|
11
|
+
serverId: options.serverId,
|
|
12
|
+
onError: options.onError,
|
|
13
|
+
}),
|
|
14
|
+
service,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/testing/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAWjD,gGAAgG;AAChG,MAAM,UAAU,gBAAgB,CAAC,OAA0B,EAAc;IACxE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,IAAI,iBAAiB,EAAE,CAAC;IAC3D,OAAO;QACN,MAAM,EAAE,IAAI,QAAQ,CAAC,OAAO,EAAE;YAC7B,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;YAC9C,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,OAAO,EAAE,OAAO,CAAC,OAAO;SACxB,CAAC;QACF,OAAO;KACP,CAAC;AAAA,CACF","sourcesContent":["import { PiServer } from \"../server.ts\";\nimport type { PiServerOptions, PiServerService } from \"../types.ts\";\nimport { TestServerService } from \"./service.ts\";\n\nexport interface TestServerOptions extends PiServerOptions {\n\tservice?: PiServerService;\n}\n\nexport interface TestServer {\n\tserver: PiServer;\n\tservice: PiServerService;\n}\n\n/** Create an unstarted PiServer with deterministic defaults for transport conformance tests. */\nexport function createTestServer(options: TestServerOptions): TestServer {\n\tconst service = options.service ?? new TestServerService();\n\treturn {\n\t\tserver: new PiServer(service, {\n\t\t\tlisteners: options.listeners,\n\t\t\tmaxFrameLength: options.maxFrameLength,\n\t\t\thandshakeTimeoutMs: options.handshakeTimeoutMs,\n\t\t\tserverId: options.serverId,\n\t\t\tonError: options.onError,\n\t\t}),\n\t\tservice,\n\t};\n}\n"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ModelMetadata, ModelRef, SessionMetadata, SessionPhase, SessionSnapshot, ThinkingLevel, TranscriptProgress } from "@earendil-works/pi-protocol";
|
|
2
|
+
import { PiServerError } from "../errors.ts";
|
|
3
|
+
import type { CreateSessionOptions, PiServerService, PiSessionRuntime, PiSessionRuntimeEvent, PromptInput } from "../types.ts";
|
|
4
|
+
export declare const TEST_MODEL: ModelMetadata;
|
|
5
|
+
export declare class Deferred<T> {
|
|
6
|
+
readonly promise: Promise<T>;
|
|
7
|
+
private resolvePromise;
|
|
8
|
+
constructor();
|
|
9
|
+
resolve(value: T): void;
|
|
10
|
+
}
|
|
11
|
+
interface StoredSession {
|
|
12
|
+
snapshot: SessionSnapshot;
|
|
13
|
+
}
|
|
14
|
+
export declare class TestSessionRuntime implements PiSessionRuntime {
|
|
15
|
+
readonly disposed: Deferred<void>;
|
|
16
|
+
disposeCount: number;
|
|
17
|
+
readonly steers: PromptInput[];
|
|
18
|
+
private readonly stored;
|
|
19
|
+
private readonly onDispose;
|
|
20
|
+
private readonly listeners;
|
|
21
|
+
private pendingPrompt?;
|
|
22
|
+
constructor(stored: StoredSession, onDispose: () => void);
|
|
23
|
+
snapshot(): SessionSnapshot;
|
|
24
|
+
getPhase(): SessionPhase;
|
|
25
|
+
prompt(input: PromptInput): Promise<void>;
|
|
26
|
+
steer(input: PromptInput): Promise<void>;
|
|
27
|
+
abort(): Promise<void>;
|
|
28
|
+
setModel(model: ModelRef): Promise<void>;
|
|
29
|
+
setThinking(thinkingLevel: ThinkingLevel): Promise<void>;
|
|
30
|
+
subscribe(listener: (event: PiSessionRuntimeEvent) => void): () => void;
|
|
31
|
+
dispose(): Promise<void>;
|
|
32
|
+
setPhase(phase: SessionPhase): void;
|
|
33
|
+
finishPrompt(): void;
|
|
34
|
+
emitProgress(progress: TranscriptProgress): void;
|
|
35
|
+
emitError(error: PiServerError): void;
|
|
36
|
+
emitSnapshot(): void;
|
|
37
|
+
private update;
|
|
38
|
+
}
|
|
39
|
+
interface ListDelay {
|
|
40
|
+
entered: Deferred<void>;
|
|
41
|
+
release: Deferred<void>;
|
|
42
|
+
}
|
|
43
|
+
export declare class TestServerService implements PiServerService {
|
|
44
|
+
readonly sessions: Map<string, StoredSession>;
|
|
45
|
+
readonly runtimes: Map<string, TestSessionRuntime[]>;
|
|
46
|
+
readonly locked: Set<string>;
|
|
47
|
+
lastCreatedId?: string;
|
|
48
|
+
private nextListDelay?;
|
|
49
|
+
listSessions(): Promise<SessionMetadata[]>;
|
|
50
|
+
listModels(): Promise<ModelMetadata[]>;
|
|
51
|
+
createSession(options: CreateSessionOptions): Promise<PiSessionRuntime>;
|
|
52
|
+
openSession(sessionId: string): Promise<PiSessionRuntime>;
|
|
53
|
+
seed(id?: string, name?: string, cwd?: string, model?: ModelRef, thinkingLevel?: ThinkingLevel): void;
|
|
54
|
+
delayNextList(): ListDelay;
|
|
55
|
+
latestRuntime(id: string): TestSessionRuntime;
|
|
56
|
+
private acquire;
|
|
57
|
+
}
|
|
58
|
+
export {};
|
|
59
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","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"]}
|
|
@@ -0,0 +1,240 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/transports/unix/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC","sourcesContent":["export { createUnixListener } from \"./listener.ts\";\nexport { createUnixServer } from \"./preset.ts\";\nexport type { UnixListenerOptions, UnixServerOptions } from \"./types.ts\";\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/transports/unix/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC","sourcesContent":["export { createUnixListener } from \"./listener.ts\";\nexport { createUnixServer } from \"./preset.ts\";\nexport type { UnixListenerOptions, UnixServerOptions } from \"./types.ts\";\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Socket } from "node:net";
|
|
2
|
+
import type { ByteConnection } from "../../connection.ts";
|
|
3
|
+
import type { PiServerListener } from "../../listener.ts";
|
|
4
|
+
import type { UnixListenerOptions } from "./types.ts";
|
|
5
|
+
export declare function validateUnixSocketPath(path: string, description?: string): void;
|
|
6
|
+
/** @internal Exported only for transport-level verification. */
|
|
7
|
+
export declare class UnixByteConnection implements ByteConnection {
|
|
8
|
+
private readonly socket;
|
|
9
|
+
private readonly gracefulCloseTimeoutMs;
|
|
10
|
+
private readonly maxPendingBytes;
|
|
11
|
+
private pendingBytes;
|
|
12
|
+
private closedValue;
|
|
13
|
+
private closing;
|
|
14
|
+
private writeTail;
|
|
15
|
+
private closePromise?;
|
|
16
|
+
private resolveClose?;
|
|
17
|
+
constructor(socket: Socket, gracefulCloseTimeoutMs: number, maxPendingBytes: number);
|
|
18
|
+
get closed(): boolean;
|
|
19
|
+
send(chunk: Uint8Array): Promise<void>;
|
|
20
|
+
close(finalChunk?: Uint8Array): Promise<void>;
|
|
21
|
+
markClosed(): void;
|
|
22
|
+
private write;
|
|
23
|
+
}
|
|
24
|
+
export declare function createUnixListener(options: UnixListenerOptions): PiServerListener;
|
|
25
|
+
//# sourceMappingURL=listener.d.ts.map
|