@zetaloop/chappie 0.5.0 → 1.0.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/.codex-plugin/plugin.json +13 -0
- package/README.md +26 -29
- package/dist/chunk-5CKVVDZ2.js +1042 -0
- package/dist/chunk-DM523QRR.js +148 -0
- package/dist/chunk-DN7OHZK6.js +110 -0
- package/dist/chunk-DQWC5GFN.js +1745 -0
- package/dist/chunk-EIPDEA3I.js +286 -0
- package/dist/chunk-GWYP7B66.js +8479 -0
- package/dist/chunk-GX4JWLHH.js +98 -0
- package/dist/chunk-K2VE2MDU.js +14916 -0
- package/dist/chunk-OWAAWZNT.js +56 -0
- package/dist/chunk-UOFCCHBS.js +94 -0
- package/dist/cli.js +47 -0
- package/dist/codex-RI2XL4HV.js +5144 -0
- package/dist/codex-plugin-NSKWB3WS.js +179 -0
- package/dist/codex.json +23 -0
- package/dist/index.js +4752 -0
- package/dist/instructions.md +13 -0
- package/dist/omp.js +240 -0
- package/dist/opencode.js +904 -0
- package/dist/promise-OHH5J44X.js +72 -0
- package/dist/stdio-KJVCYPXJ.js +1279 -0
- package/docs/setup.md +82 -0
- package/docs/tools.md +106 -96
- package/package.json +100 -56
- package/src/activity.ts +0 -21
- package/src/broker.ts +0 -780
- package/src/config.ts +0 -20
- package/src/delivery.ts +0 -54
- package/src/history.ts +0 -136
- package/src/index.ts +0 -42
- package/src/instructions.md +0 -17
- package/src/ipc.ts +0 -492
- package/src/provider.ts +0 -194
- package/src/questions.ts +0 -87
- package/src/resources.ts +0 -209
- package/src/server.ts +0 -566
- package/src/session.ts +0 -848
- package/src/state.ts +0 -163
- package/src/stdio.ts +0 -58
- package/src/tools.ts +0 -91
- package/src/transfer.ts +0 -313
- /package/{src → dist}/question.html +0 -0
package/src/ipc.ts
DELETED
|
@@ -1,492 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { lstat, unlink } from "node:fs/promises";
|
|
3
|
-
import {
|
|
4
|
-
createConnection,
|
|
5
|
-
createServer,
|
|
6
|
-
type Server,
|
|
7
|
-
type Socket,
|
|
8
|
-
} from "node:net";
|
|
9
|
-
import { join, resolve } from "node:path";
|
|
10
|
-
import type {
|
|
11
|
-
AssistantMessage,
|
|
12
|
-
ToolCall,
|
|
13
|
-
ToolResultMessage,
|
|
14
|
-
UserMessage,
|
|
15
|
-
} from "@earendil-works/pi-ai";
|
|
16
|
-
import type {
|
|
17
|
-
SlashCommandInfo,
|
|
18
|
-
ToolInfo,
|
|
19
|
-
} from "@earendil-works/pi-coding-agent";
|
|
20
|
-
import type { Activity } from "./activity.ts";
|
|
21
|
-
import type { DeliveryRecord } from "./delivery.ts";
|
|
22
|
-
import type { HistoryRange, HistoryResult } from "./history.ts";
|
|
23
|
-
import type { ResourceData, ResourceDescriptor } from "./resources.ts";
|
|
24
|
-
import type { TransferDetails } from "./transfer.ts";
|
|
25
|
-
|
|
26
|
-
const defaultPort = 24274;
|
|
27
|
-
|
|
28
|
-
export type SessionStatus = "idle" | "ready" | "executing";
|
|
29
|
-
|
|
30
|
-
export interface SessionDescription {
|
|
31
|
-
id: string;
|
|
32
|
-
cwd: string;
|
|
33
|
-
device: string;
|
|
34
|
-
name?: string;
|
|
35
|
-
status: SessionStatus;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface SessionInspection {
|
|
39
|
-
session: SessionDescription;
|
|
40
|
-
tools: ToolInfo[];
|
|
41
|
-
skills: SlashCommandInfo[];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface SessionInput {
|
|
45
|
-
id: string;
|
|
46
|
-
sessionId: string;
|
|
47
|
-
message: UserMessage;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export type SessionResult =
|
|
51
|
-
| {
|
|
52
|
-
inspection: SessionInspection;
|
|
53
|
-
inputs: SessionInput[];
|
|
54
|
-
globalAgents?: { path: string };
|
|
55
|
-
}
|
|
56
|
-
| { message: AssistantMessage; cwd: string; inputs: SessionInput[] }
|
|
57
|
-
| {
|
|
58
|
-
message: AssistantMessage;
|
|
59
|
-
cwd: string;
|
|
60
|
-
toolResults: ToolResultMessage[];
|
|
61
|
-
inputs: SessionInput[];
|
|
62
|
-
}
|
|
63
|
-
| { history: HistoryResult; cwd: string }
|
|
64
|
-
| { resource: ResourceData }
|
|
65
|
-
| { transfer: TransferDetails }
|
|
66
|
-
| { error: string };
|
|
67
|
-
|
|
68
|
-
export type SessionRequest =
|
|
69
|
-
| { type: "inspect"; sessionId: string }
|
|
70
|
-
| { type: "readResource"; sessionId: string; uri: string; offset?: number }
|
|
71
|
-
| {
|
|
72
|
-
type: "copy";
|
|
73
|
-
sessionId: string;
|
|
74
|
-
resources: ResourceDescriptor[];
|
|
75
|
-
paths: string[];
|
|
76
|
-
overwrite?: boolean;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export type SessionMessage =
|
|
80
|
-
| { type: "sync"; id: number; session: SessionDescription }
|
|
81
|
-
| { type: "unregister"; sessionId: string }
|
|
82
|
-
| { type: "delivery"; delivery: DeliveryRecord }
|
|
83
|
-
| { type: "request"; id: number; request: SessionRequest }
|
|
84
|
-
| { type: "cancelRequest"; id: number }
|
|
85
|
-
| ({ type: "result"; id: number } & SessionResult);
|
|
86
|
-
|
|
87
|
-
export type BrokerMessage =
|
|
88
|
-
| { type: "synced"; id: number; sessionId: string }
|
|
89
|
-
| { type: "stored"; id: string }
|
|
90
|
-
| { type: "notice"; sessionId: string; message: string; activity?: Activity }
|
|
91
|
-
| {
|
|
92
|
-
type: "history";
|
|
93
|
-
id: number;
|
|
94
|
-
sessionId: string;
|
|
95
|
-
range: HistoryRange;
|
|
96
|
-
chatId: string;
|
|
97
|
-
requestId?: string;
|
|
98
|
-
}
|
|
99
|
-
| {
|
|
100
|
-
type: "chat";
|
|
101
|
-
id: number;
|
|
102
|
-
chatId: string;
|
|
103
|
-
requestId?: string;
|
|
104
|
-
sessionId: string;
|
|
105
|
-
text: string;
|
|
106
|
-
}
|
|
107
|
-
| {
|
|
108
|
-
type: "call";
|
|
109
|
-
id: number;
|
|
110
|
-
chatId: string;
|
|
111
|
-
requestId?: string;
|
|
112
|
-
sessionId: string;
|
|
113
|
-
calls: ToolCall[];
|
|
114
|
-
}
|
|
115
|
-
| { type: "cancel"; id: number; sessionId: string; reason: string }
|
|
116
|
-
| (SessionRequest & { id: number })
|
|
117
|
-
| ({ type: "response"; id: number } & SessionResult)
|
|
118
|
-
| { type: "ackInputs"; sessionId: string; ids: string[] };
|
|
119
|
-
|
|
120
|
-
export function ipcEndpoint(agentDir: string): string {
|
|
121
|
-
const directory = resolve(agentDir);
|
|
122
|
-
if (process.platform !== "win32") return join(directory, "chappie.sock");
|
|
123
|
-
const identity = directory.replaceAll("\\", "/").toLowerCase();
|
|
124
|
-
return String.raw`\\.\pipe\chappie-${createHash("sha256").update(identity).digest("hex").slice(0, 16)}`;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export class JsonLinePeer<Incoming, Outgoing> {
|
|
128
|
-
readonly #socket: Socket;
|
|
129
|
-
readonly #onMessage: (message: Incoming) => Promise<void> | void;
|
|
130
|
-
readonly #onClose: () => void;
|
|
131
|
-
#buffer = "";
|
|
132
|
-
#messages = Promise.resolve();
|
|
133
|
-
#writes = Promise.resolve();
|
|
134
|
-
#closed = false;
|
|
135
|
-
|
|
136
|
-
constructor(
|
|
137
|
-
socket: Socket,
|
|
138
|
-
onMessage: (message: Incoming) => Promise<void> | void,
|
|
139
|
-
onClose: () => void,
|
|
140
|
-
) {
|
|
141
|
-
this.#socket = socket;
|
|
142
|
-
this.#onMessage = onMessage;
|
|
143
|
-
this.#onClose = onClose;
|
|
144
|
-
socket.setEncoding("utf8");
|
|
145
|
-
socket.on("data", (chunk: string) => this.#receive(chunk));
|
|
146
|
-
socket.once("close", () => {
|
|
147
|
-
this.#closed = true;
|
|
148
|
-
this.#onClose();
|
|
149
|
-
});
|
|
150
|
-
socket.on("error", () => {});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
get closed(): boolean {
|
|
154
|
-
return this.#closed;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
send(message: Outgoing): Promise<void> {
|
|
158
|
-
const line = `${JSON.stringify(message)}\n`;
|
|
159
|
-
const sent = this.#writes.then(
|
|
160
|
-
() =>
|
|
161
|
-
new Promise<void>((resolveWrite, rejectWrite) => {
|
|
162
|
-
if (this.#closed) {
|
|
163
|
-
rejectWrite(new Error("Chappie IPC connection is closed"));
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
this.#socket.write(line, (error) => {
|
|
167
|
-
if (error) rejectWrite(error);
|
|
168
|
-
else resolveWrite();
|
|
169
|
-
});
|
|
170
|
-
}),
|
|
171
|
-
);
|
|
172
|
-
this.#writes = sent.catch(() => {});
|
|
173
|
-
return sent;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
close(): void {
|
|
177
|
-
if (!this.#closed) this.#socket.end();
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
#receive(chunk: string): void {
|
|
181
|
-
this.#buffer += chunk;
|
|
182
|
-
let end = this.#buffer.indexOf("\n");
|
|
183
|
-
while (end !== -1) {
|
|
184
|
-
const line = this.#buffer.slice(0, end);
|
|
185
|
-
this.#buffer = this.#buffer.slice(end + 1);
|
|
186
|
-
if (line.length > 0) {
|
|
187
|
-
this.#messages = this.#messages
|
|
188
|
-
.then(() => this.#onMessage(JSON.parse(line) as Incoming))
|
|
189
|
-
.catch((error: unknown) => {
|
|
190
|
-
this.#socket.destroy(
|
|
191
|
-
error instanceof Error ? error : new Error(String(error)),
|
|
192
|
-
);
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
end = this.#buffer.indexOf("\n");
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export class IpcServer {
|
|
201
|
-
readonly #endpoint: string;
|
|
202
|
-
readonly #onMessage: (
|
|
203
|
-
peer: JsonLinePeer<SessionMessage, BrokerMessage>,
|
|
204
|
-
message: SessionMessage,
|
|
205
|
-
) => Promise<void> | void;
|
|
206
|
-
readonly #onClose: (
|
|
207
|
-
peer: JsonLinePeer<SessionMessage, BrokerMessage>,
|
|
208
|
-
) => void;
|
|
209
|
-
readonly #peers = new Set<JsonLinePeer<SessionMessage, BrokerMessage>>();
|
|
210
|
-
readonly #servers = new Set<Server>();
|
|
211
|
-
|
|
212
|
-
constructor(
|
|
213
|
-
agentDir: string,
|
|
214
|
-
onMessage: (
|
|
215
|
-
peer: JsonLinePeer<SessionMessage, BrokerMessage>,
|
|
216
|
-
message: SessionMessage,
|
|
217
|
-
) => Promise<void> | void,
|
|
218
|
-
onClose: (peer: JsonLinePeer<SessionMessage, BrokerMessage>) => void,
|
|
219
|
-
) {
|
|
220
|
-
this.#endpoint = ipcEndpoint(agentDir);
|
|
221
|
-
this.#onMessage = onMessage;
|
|
222
|
-
this.#onClose = onClose;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
async start(network: boolean | number = false): Promise<void> {
|
|
226
|
-
if (this.#servers.size > 0) return;
|
|
227
|
-
if (process.platform !== "win32") await prepareUnixSocket(this.#endpoint);
|
|
228
|
-
try {
|
|
229
|
-
const local = this.#createServer();
|
|
230
|
-
await listenServer(local, this.#endpoint);
|
|
231
|
-
this.#servers.add(local);
|
|
232
|
-
if (!network) return;
|
|
233
|
-
|
|
234
|
-
const remote = this.#createServer();
|
|
235
|
-
await listenServer(remote, network === true ? defaultPort : network);
|
|
236
|
-
this.#servers.add(remote);
|
|
237
|
-
} catch (error) {
|
|
238
|
-
await this.close();
|
|
239
|
-
throw error;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
async close(): Promise<void> {
|
|
244
|
-
for (const peer of this.#peers) peer.close();
|
|
245
|
-
this.#peers.clear();
|
|
246
|
-
const servers = [...this.#servers];
|
|
247
|
-
this.#servers.clear();
|
|
248
|
-
await Promise.all(servers.map(closeServer));
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
#createServer(): Server {
|
|
252
|
-
return createServer((socket) => {
|
|
253
|
-
let peer: JsonLinePeer<SessionMessage, BrokerMessage>;
|
|
254
|
-
peer = new JsonLinePeer(
|
|
255
|
-
socket,
|
|
256
|
-
(message) => this.#onMessage(peer, message),
|
|
257
|
-
() => {
|
|
258
|
-
this.#peers.delete(peer);
|
|
259
|
-
this.#onClose(peer);
|
|
260
|
-
},
|
|
261
|
-
);
|
|
262
|
-
this.#peers.add(peer);
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
interface ConnectionCallbacks {
|
|
268
|
-
onOpen(): Promise<void> | void;
|
|
269
|
-
onMessage(message: BrokerMessage): Promise<void> | void;
|
|
270
|
-
onClose(error: Error): void;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
export class IpcClient {
|
|
274
|
-
readonly #endpoint: string;
|
|
275
|
-
readonly #remote: string | undefined;
|
|
276
|
-
readonly #callbacks: ConnectionCallbacks;
|
|
277
|
-
#peer: JsonLinePeer<BrokerMessage, SessionMessage> | undefined;
|
|
278
|
-
#opening: Promise<void> | undefined;
|
|
279
|
-
#controller: AbortController | undefined;
|
|
280
|
-
#retry: NodeJS.Timeout | undefined;
|
|
281
|
-
#closed = false;
|
|
282
|
-
|
|
283
|
-
constructor(
|
|
284
|
-
agentDir: string,
|
|
285
|
-
remote: string | undefined,
|
|
286
|
-
callbacks: ConnectionCallbacks,
|
|
287
|
-
) {
|
|
288
|
-
this.#endpoint = ipcEndpoint(agentDir);
|
|
289
|
-
this.#remote = remote;
|
|
290
|
-
this.#callbacks = callbacks;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
get connected(): boolean {
|
|
294
|
-
return this.#peer !== undefined && !this.#peer.closed;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
start(): void {
|
|
298
|
-
void this.connect().catch(() => {});
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
connect(): Promise<void> {
|
|
302
|
-
if (this.#closed)
|
|
303
|
-
return Promise.reject(new Error("Chappie IPC client is closed"));
|
|
304
|
-
if (this.connected) return Promise.resolve();
|
|
305
|
-
if (this.#opening) return this.#opening;
|
|
306
|
-
clearTimeout(this.#retry);
|
|
307
|
-
this.#retry = undefined;
|
|
308
|
-
const controller = new AbortController();
|
|
309
|
-
this.#controller = controller;
|
|
310
|
-
this.#opening = this.#open(controller.signal).finally(() => {
|
|
311
|
-
this.#opening = undefined;
|
|
312
|
-
if (this.#controller === controller) this.#controller = undefined;
|
|
313
|
-
});
|
|
314
|
-
return this.#opening;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
send(message: SessionMessage): Promise<void> {
|
|
318
|
-
const peer = this.#peer;
|
|
319
|
-
if (!peer || peer.closed)
|
|
320
|
-
return Promise.reject(new Error("Chappie broker is not running"));
|
|
321
|
-
return peer.send(message);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
close(): void {
|
|
325
|
-
this.#closed = true;
|
|
326
|
-
clearTimeout(this.#retry);
|
|
327
|
-
this.#retry = undefined;
|
|
328
|
-
this.#controller?.abort(new Error("Chappie IPC client is closed"));
|
|
329
|
-
this.#controller = undefined;
|
|
330
|
-
this.#peer?.close();
|
|
331
|
-
this.#peer = undefined;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
async #open(signal: AbortSignal): Promise<void> {
|
|
335
|
-
let socket: Socket | undefined;
|
|
336
|
-
let failure = new Error("Chappie disconnected");
|
|
337
|
-
try {
|
|
338
|
-
socket = this.#remote
|
|
339
|
-
? createConnection(networkEndpoint(this.#remote))
|
|
340
|
-
: createConnection(this.#endpoint);
|
|
341
|
-
await connectSocket(socket, signal);
|
|
342
|
-
} catch (error) {
|
|
343
|
-
socket?.destroy();
|
|
344
|
-
this.#scheduleReconnect();
|
|
345
|
-
throw error;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
let peer: JsonLinePeer<BrokerMessage, SessionMessage>;
|
|
349
|
-
peer = new JsonLinePeer(
|
|
350
|
-
socket,
|
|
351
|
-
(message) => this.#callbacks.onMessage(message),
|
|
352
|
-
() => {
|
|
353
|
-
if (this.#peer !== peer) return;
|
|
354
|
-
this.#peer = undefined;
|
|
355
|
-
if (!this.#closed) {
|
|
356
|
-
this.#callbacks.onClose(failure);
|
|
357
|
-
this.#scheduleReconnect();
|
|
358
|
-
}
|
|
359
|
-
},
|
|
360
|
-
);
|
|
361
|
-
this.#peer = peer;
|
|
362
|
-
try {
|
|
363
|
-
await this.#callbacks.onOpen();
|
|
364
|
-
} catch (error) {
|
|
365
|
-
failure = error instanceof Error ? error : new Error(String(error));
|
|
366
|
-
peer.close();
|
|
367
|
-
throw failure;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
#scheduleReconnect(): void {
|
|
372
|
-
if (this.#closed || this.#retry) return;
|
|
373
|
-
this.#retry = setTimeout(() => {
|
|
374
|
-
this.#retry = undefined;
|
|
375
|
-
void this.connect().catch(() => {});
|
|
376
|
-
}, 500);
|
|
377
|
-
this.#retry.unref();
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function listenServer(
|
|
382
|
-
server: Server,
|
|
383
|
-
endpoint: string | number,
|
|
384
|
-
): Promise<void> {
|
|
385
|
-
return new Promise<void>((resolveListen, rejectListen) => {
|
|
386
|
-
const onError = (error: Error): void => {
|
|
387
|
-
server.off("listening", onListening);
|
|
388
|
-
rejectListen(error);
|
|
389
|
-
};
|
|
390
|
-
const onListening = (): void => {
|
|
391
|
-
server.off("error", onError);
|
|
392
|
-
resolveListen();
|
|
393
|
-
};
|
|
394
|
-
server.once("error", onError);
|
|
395
|
-
server.once("listening", onListening);
|
|
396
|
-
server.listen(endpoint);
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function closeServer(server: Server): Promise<void> {
|
|
401
|
-
return new Promise<void>((resolveClose, rejectClose) =>
|
|
402
|
-
server.close((error) => (error ? rejectClose(error) : resolveClose())),
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
interface NetworkEndpoint {
|
|
407
|
-
host: string;
|
|
408
|
-
port: number;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function networkEndpoint(value: string): NetworkEndpoint {
|
|
412
|
-
const url = new URL(`tcp://${value}`);
|
|
413
|
-
if (url.username || url.password || url.pathname || url.search || url.hash)
|
|
414
|
-
throw new Error(`Invalid Chappie broker address: ${value}`);
|
|
415
|
-
const host = url.hostname.startsWith("[")
|
|
416
|
-
? url.hostname.slice(1, -1)
|
|
417
|
-
: url.hostname;
|
|
418
|
-
if (!host) throw new Error(`Invalid Chappie broker address: ${value}`);
|
|
419
|
-
return { host, port: url.port ? Number(url.port) : defaultPort };
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function connectSocket(socket: Socket, signal: AbortSignal): Promise<void> {
|
|
423
|
-
return new Promise<void>((resolveConnect, rejectConnect) => {
|
|
424
|
-
const cleanup = (): void => {
|
|
425
|
-
signal.removeEventListener("abort", onAbort);
|
|
426
|
-
socket.off("connect", onConnect);
|
|
427
|
-
socket.off("error", onError);
|
|
428
|
-
};
|
|
429
|
-
const onConnect = (): void => {
|
|
430
|
-
cleanup();
|
|
431
|
-
resolveConnect();
|
|
432
|
-
};
|
|
433
|
-
const onError = (error: Error): void => {
|
|
434
|
-
cleanup();
|
|
435
|
-
rejectConnect(error);
|
|
436
|
-
};
|
|
437
|
-
const onAbort = (): void => {
|
|
438
|
-
cleanup();
|
|
439
|
-
socket.destroy();
|
|
440
|
-
rejectConnect(abortError(signal));
|
|
441
|
-
};
|
|
442
|
-
if (signal.aborted) {
|
|
443
|
-
onAbort();
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
447
|
-
socket.once("connect", onConnect);
|
|
448
|
-
socket.once("error", onError);
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function abortError(signal: AbortSignal): Error {
|
|
453
|
-
return signal.reason instanceof Error
|
|
454
|
-
? signal.reason
|
|
455
|
-
: new Error(
|
|
456
|
-
typeof signal.reason === "string" ? signal.reason : "Request cancelled",
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
async function prepareUnixSocket(endpoint: string): Promise<void> {
|
|
461
|
-
try {
|
|
462
|
-
await lstat(endpoint);
|
|
463
|
-
} catch (error) {
|
|
464
|
-
if ((error as NodeJS.ErrnoException).code === "ENOENT") return;
|
|
465
|
-
throw error;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (await endpointAcceptsConnections(endpoint)) {
|
|
469
|
-
const error = new Error(
|
|
470
|
-
`Chappie broker is already listening at ${endpoint}`,
|
|
471
|
-
) as NodeJS.ErrnoException;
|
|
472
|
-
error.code = "EADDRINUSE";
|
|
473
|
-
throw error;
|
|
474
|
-
}
|
|
475
|
-
await unlink(endpoint);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function endpointAcceptsConnections(endpoint: string): Promise<boolean> {
|
|
479
|
-
return new Promise<boolean>((resolveProbe, rejectProbe) => {
|
|
480
|
-
const socket = createConnection(endpoint);
|
|
481
|
-
socket.once("connect", () => {
|
|
482
|
-
socket.end();
|
|
483
|
-
resolveProbe(true);
|
|
484
|
-
});
|
|
485
|
-
socket.once("error", (error: NodeJS.ErrnoException) => {
|
|
486
|
-
socket.destroy();
|
|
487
|
-
if (error.code === "ECONNREFUSED" || error.code === "ENOENT")
|
|
488
|
-
resolveProbe(false);
|
|
489
|
-
else rejectProbe(error);
|
|
490
|
-
});
|
|
491
|
-
});
|
|
492
|
-
}
|
package/src/provider.ts
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Api,
|
|
3
|
-
type AssistantMessage,
|
|
4
|
-
type AssistantMessageEventStream,
|
|
5
|
-
type Context,
|
|
6
|
-
createAssistantMessageEventStream,
|
|
7
|
-
createProvider,
|
|
8
|
-
type Model,
|
|
9
|
-
type StreamOptions,
|
|
10
|
-
type ToolCall,
|
|
11
|
-
} from "@earendil-works/pi-ai";
|
|
12
|
-
import type { Source } from "./activity.ts";
|
|
13
|
-
|
|
14
|
-
export class ProviderOutput {
|
|
15
|
-
readonly stream: AssistantMessageEventStream;
|
|
16
|
-
readonly message: AssistantMessage & { chappie?: Source };
|
|
17
|
-
readonly finished: Promise<void>;
|
|
18
|
-
#resolveFinished: () => void;
|
|
19
|
-
#removeAbort?: () => void;
|
|
20
|
-
#started = false;
|
|
21
|
-
#closed = false;
|
|
22
|
-
|
|
23
|
-
constructor(model: Model<Api>, signal?: AbortSignal) {
|
|
24
|
-
this.stream = createAssistantMessageEventStream();
|
|
25
|
-
this.message = {
|
|
26
|
-
role: "assistant",
|
|
27
|
-
content: [],
|
|
28
|
-
api: model.api,
|
|
29
|
-
provider: model.provider,
|
|
30
|
-
model: model.id,
|
|
31
|
-
usage: {
|
|
32
|
-
input: 0,
|
|
33
|
-
output: 0,
|
|
34
|
-
cacheRead: 0,
|
|
35
|
-
cacheWrite: 0,
|
|
36
|
-
totalTokens: 0,
|
|
37
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
38
|
-
},
|
|
39
|
-
stopReason: "pending",
|
|
40
|
-
timestamp: Date.now(),
|
|
41
|
-
};
|
|
42
|
-
const completion = Promise.withResolvers<void>();
|
|
43
|
-
this.finished = completion.promise;
|
|
44
|
-
this.#resolveFinished = completion.resolve;
|
|
45
|
-
if (signal) {
|
|
46
|
-
const abort = (): void =>
|
|
47
|
-
this.fail(new Error("Chappie provider request was cancelled"), true);
|
|
48
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
49
|
-
this.#removeAbort = () => signal.removeEventListener("abort", abort);
|
|
50
|
-
if (signal.aborted) abort();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
get closed(): boolean {
|
|
55
|
-
return this.#closed;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
begin(): void {
|
|
59
|
-
if (this.#started || this.#closed) return;
|
|
60
|
-
this.#started = true;
|
|
61
|
-
this.stream.push({ type: "start", partial: this.message });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
text(text: string): void {
|
|
65
|
-
if (this.#closed)
|
|
66
|
-
throw new Error("Chappie provider response is already complete");
|
|
67
|
-
this.begin();
|
|
68
|
-
const contentIndex = this.message.content.length;
|
|
69
|
-
const block = { type: "text" as const, text: "" };
|
|
70
|
-
this.message.content.push(block);
|
|
71
|
-
this.stream.push({
|
|
72
|
-
type: "text_start",
|
|
73
|
-
contentIndex,
|
|
74
|
-
partial: this.message,
|
|
75
|
-
});
|
|
76
|
-
block.text = text;
|
|
77
|
-
this.stream.push({
|
|
78
|
-
type: "text_delta",
|
|
79
|
-
contentIndex,
|
|
80
|
-
delta: text,
|
|
81
|
-
partial: this.message,
|
|
82
|
-
});
|
|
83
|
-
this.stream.push({
|
|
84
|
-
type: "text_end",
|
|
85
|
-
contentIndex,
|
|
86
|
-
content: text,
|
|
87
|
-
partial: this.message,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
toolCalls(calls: ToolCall[]): void {
|
|
92
|
-
if (this.#closed)
|
|
93
|
-
throw new Error("Chappie provider response is already complete");
|
|
94
|
-
this.begin();
|
|
95
|
-
for (const call of calls) {
|
|
96
|
-
const contentIndex = this.message.content.length;
|
|
97
|
-
const block: ToolCall = { ...call, arguments: {} };
|
|
98
|
-
this.message.content.push(block);
|
|
99
|
-
this.stream.push({
|
|
100
|
-
type: "toolcall_start",
|
|
101
|
-
contentIndex,
|
|
102
|
-
partial: this.message,
|
|
103
|
-
});
|
|
104
|
-
block.arguments = call.arguments;
|
|
105
|
-
this.stream.push({
|
|
106
|
-
type: "toolcall_delta",
|
|
107
|
-
contentIndex,
|
|
108
|
-
delta: JSON.stringify(call.arguments),
|
|
109
|
-
partial: this.message,
|
|
110
|
-
});
|
|
111
|
-
this.stream.push({
|
|
112
|
-
type: "toolcall_end",
|
|
113
|
-
contentIndex,
|
|
114
|
-
toolCall: block,
|
|
115
|
-
partial: this.message,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
done(reason: "stop" | "toolUse" = "stop"): void {
|
|
121
|
-
if (this.#closed) return;
|
|
122
|
-
this.begin();
|
|
123
|
-
this.message.stopReason = reason;
|
|
124
|
-
this.stream.push({ type: "done", reason, message: this.message });
|
|
125
|
-
this.#finish();
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
fail(error: unknown, aborted = false): void {
|
|
129
|
-
if (this.#closed) return;
|
|
130
|
-
this.begin();
|
|
131
|
-
this.message.stopReason = aborted ? "aborted" : "error";
|
|
132
|
-
this.message.errorMessage =
|
|
133
|
-
error instanceof Error ? error.message : String(error);
|
|
134
|
-
this.stream.push({
|
|
135
|
-
type: "error",
|
|
136
|
-
reason: this.message.stopReason,
|
|
137
|
-
error: this.message,
|
|
138
|
-
});
|
|
139
|
-
this.#finish();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
#finish(): void {
|
|
143
|
-
this.#closed = true;
|
|
144
|
-
this.#removeAbort?.();
|
|
145
|
-
this.stream.end();
|
|
146
|
-
this.#resolveFinished();
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export function createChappieProvider(
|
|
151
|
-
start: (output: ProviderOutput) => Promise<void>,
|
|
152
|
-
) {
|
|
153
|
-
const stream = (
|
|
154
|
-
model: Model<Api>,
|
|
155
|
-
_context: Context,
|
|
156
|
-
options?: StreamOptions,
|
|
157
|
-
) => {
|
|
158
|
-
const output = new ProviderOutput(model, options?.signal);
|
|
159
|
-
if (!output.closed) {
|
|
160
|
-
queueMicrotask(() => {
|
|
161
|
-
void start(output).catch((error: unknown) => output.fail(error));
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
return output.stream;
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
return createProvider({
|
|
168
|
-
id: "chappie",
|
|
169
|
-
name: "Chappie",
|
|
170
|
-
auth: {
|
|
171
|
-
apiKey: {
|
|
172
|
-
name: "Local Chappie",
|
|
173
|
-
async resolve() {
|
|
174
|
-
return { auth: { headers: {} }, source: "local" };
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
models: [
|
|
179
|
-
{
|
|
180
|
-
id: "chatgpt",
|
|
181
|
-
name: "ChatGPT",
|
|
182
|
-
api: "chappie",
|
|
183
|
-
provider: "chappie",
|
|
184
|
-
baseUrl: "",
|
|
185
|
-
reasoning: false,
|
|
186
|
-
input: ["text", "image"],
|
|
187
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
188
|
-
contextWindow: 1_000_000_000,
|
|
189
|
-
maxTokens: 1_000_000_000,
|
|
190
|
-
},
|
|
191
|
-
],
|
|
192
|
-
api: { stream, streamSimple: stream },
|
|
193
|
-
});
|
|
194
|
-
}
|