pinomad 0.7.0 → 0.9.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/README.md +7 -58
- package/dist/{chunk-8yme2wep.js → chunk-014b7ne7.js} +1 -1
- package/dist/{chunk-07769pqx.js → chunk-0tesv2sr.js} +8 -8
- package/dist/{chunk-ywmm63ks.js → chunk-3p1eysm9.js} +4 -2
- package/dist/chunk-4sepcfpy.js +2994 -0
- package/dist/{chunk-0xh6cy9v.js → chunk-5gp9svzs.js} +5 -3
- package/dist/{chunk-12kaxggg.js → chunk-76pvsg2d.js} +2 -2
- package/dist/{chunk-1nyzr0mg.js → chunk-ctzgjctt.js} +2395 -839
- package/dist/{chunk-e4ahjbmz.js → chunk-jgd38rna.js} +210 -7
- package/dist/{chunk-7qb9tq54.js → chunk-khcpv54k.js} +64 -310
- package/dist/{chunk-1t58fh0k.js → chunk-m824ds0q.js} +5 -5
- package/dist/{chunk-sz3rzk55.js → chunk-qgexy01f.js} +5 -5
- package/dist/chunk-sygeb9qb.js +430 -0
- package/dist/{chunk-b1h374jr.js → chunk-v414b5pp.js} +8 -2
- package/dist/{chunk-m4c5gvce.js → chunk-vax4zspw.js} +22 -5
- package/dist/{chunk-6ysezfy5.js → chunk-vxmj0200.js} +305 -23
- package/dist/{chunk-rtwxjb90.js → chunk-vyvyaavr.js} +1 -1
- package/dist/{chunk-r6w5aqkw.js → chunk-wb10efyq.js} +17 -0
- package/dist/{chunk-fbkd73zr.js → chunk-yhf7bj5q.js} +9 -9
- package/dist/pinomad.js +10 -10
- package/package.json +12 -8
- package/web/assets/index-1g60bhv2.js +395 -0
- package/web/assets/{index-C0rwAo0H.css → index-DoyHMPwf.css} +1 -1
- package/web/index.html +2 -2
- package/dist/chunk-sj7rnvh4.js +0 -719
- package/dist/chunk-znhn8af7.js +0 -33
- package/web/assets/index-DGee2kNU.js +0 -394
package/dist/chunk-sj7rnvh4.js
DELETED
|
@@ -1,719 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
HOST_STREAM_NAMES,
|
|
3
|
-
INVALID_FRAME_CLOSE_CODE,
|
|
4
|
-
MAX_ATTACHMENTS_PER_MESSAGE,
|
|
5
|
-
PROTOCOL,
|
|
6
|
-
UNAUTHORIZED_CLOSE_CODE,
|
|
7
|
-
checkoutOf,
|
|
8
|
-
conversationStream,
|
|
9
|
-
docStream,
|
|
10
|
-
homeOf,
|
|
11
|
-
hostStreams,
|
|
12
|
-
hostSupportsCall,
|
|
13
|
-
hostSupportsFeature,
|
|
14
|
-
organize
|
|
15
|
-
} from "./chunk-e4ahjbmz.js";
|
|
16
|
-
|
|
17
|
-
// packages/protocol/src/remote-durable.ts
|
|
18
|
-
import { applyImmutable } from "@earendil-works/chord/delta";
|
|
19
|
-
|
|
20
|
-
// packages/protocol/src/transport.ts
|
|
21
|
-
function webSocketTransport(url, token) {
|
|
22
|
-
return {
|
|
23
|
-
label: url,
|
|
24
|
-
open(handlers) {
|
|
25
|
-
const address = new URL(url);
|
|
26
|
-
address.searchParams.set("token", token);
|
|
27
|
-
const socket = new WebSocket(address);
|
|
28
|
-
socket.onmessage = (event) => handlers.message(String(event.data));
|
|
29
|
-
socket.onclose = (event) => handlers.closed(event.code);
|
|
30
|
-
return { send: (data) => socket.send(data), close: () => socket.close() };
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// packages/protocol/src/remote-durable.ts
|
|
36
|
-
function connectRemoteDurable(options) {
|
|
37
|
-
return new RemoteClient(options).ready;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
class UnauthorizedError extends Error {
|
|
41
|
-
constructor() {
|
|
42
|
-
super("Unauthorized: this client is not allowed (wrong token, or the device is not paired or was revoked)");
|
|
43
|
-
this.name = "UnauthorizedError";
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
class ProtocolMismatchError extends Error {
|
|
48
|
-
hostMajor;
|
|
49
|
-
clientMajor;
|
|
50
|
-
hostVersion;
|
|
51
|
-
direction;
|
|
52
|
-
constructor(hostMajor, clientMajor, hostVersion) {
|
|
53
|
-
const direction = hostMajor > clientMajor ? "client-older" : "host-older";
|
|
54
|
-
super(direction === "client-older" ? `Client out of date: the host speaks protocol major ${hostMajor}, this client speaks ${clientMajor}` : `Host out of date: this client speaks protocol major ${clientMajor}, the host speaks ${hostMajor}`);
|
|
55
|
-
this.name = "ProtocolMismatchError";
|
|
56
|
-
this.hostMajor = hostMajor;
|
|
57
|
-
this.clientMajor = clientMajor;
|
|
58
|
-
if (hostVersion !== undefined)
|
|
59
|
-
this.hostVersion = hostVersion;
|
|
60
|
-
this.direction = direction;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function needsImageRefs(images) {
|
|
64
|
-
return images.some((image) => {
|
|
65
|
-
const data = "data" in image ? image.data : "";
|
|
66
|
-
const ref = "ref" in image ? image.ref : "";
|
|
67
|
-
return data === "" && ref !== "";
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
class RemoteClient {
|
|
72
|
-
ready;
|
|
73
|
-
#options;
|
|
74
|
-
#listeners = new Set;
|
|
75
|
-
#values = new Map;
|
|
76
|
-
#wanted = new Set;
|
|
77
|
-
#fresh = new Set;
|
|
78
|
-
#docs = [];
|
|
79
|
-
#toolPresentations = {};
|
|
80
|
-
#snapshotWaiters = new Map;
|
|
81
|
-
#pending = new Map;
|
|
82
|
-
#transport;
|
|
83
|
-
#connection;
|
|
84
|
-
#state;
|
|
85
|
-
#current;
|
|
86
|
-
#side;
|
|
87
|
-
#nextCall = 1;
|
|
88
|
-
#nextNotice = 1;
|
|
89
|
-
#hostMinor = 0;
|
|
90
|
-
#helloReceived = false;
|
|
91
|
-
#helloModels = [];
|
|
92
|
-
#helloDefaults = {};
|
|
93
|
-
#hostVersion;
|
|
94
|
-
#attempt = 0;
|
|
95
|
-
#closed = false;
|
|
96
|
-
#notifying = false;
|
|
97
|
-
#commands = Promise.resolve();
|
|
98
|
-
#resolveReady;
|
|
99
|
-
#rejectReady;
|
|
100
|
-
constructor(options) {
|
|
101
|
-
this.#options = options;
|
|
102
|
-
this.#transport = "transport" in options ? options.transport : webSocketTransport(options.url, options.token);
|
|
103
|
-
this.ready = new Promise((resolve, reject) => {
|
|
104
|
-
this.#resolveReady = resolve;
|
|
105
|
-
this.#rejectReady = reject;
|
|
106
|
-
});
|
|
107
|
-
this.#open();
|
|
108
|
-
}
|
|
109
|
-
#open() {
|
|
110
|
-
this.#helloReceived = false;
|
|
111
|
-
const connection = this.#transport.open({
|
|
112
|
-
message: (data) => {
|
|
113
|
-
if (this.#connection === connection)
|
|
114
|
-
this.#receive(JSON.parse(data));
|
|
115
|
-
},
|
|
116
|
-
closed: (code, reason) => this.#lost(connection, code, reason)
|
|
117
|
-
});
|
|
118
|
-
this.#connection = connection;
|
|
119
|
-
}
|
|
120
|
-
#lost(connection, code, reason) {
|
|
121
|
-
if (this.#connection !== connection)
|
|
122
|
-
return;
|
|
123
|
-
this.#connection = undefined;
|
|
124
|
-
for (const pending of this.#pending.values())
|
|
125
|
-
pending.reject(new Error("Disconnected from host"));
|
|
126
|
-
this.#pending.clear();
|
|
127
|
-
if (this.#state === undefined) {
|
|
128
|
-
this.#closed = true;
|
|
129
|
-
this.#rejectReady(code === UNAUTHORIZED_CLOSE_CODE ? new UnauthorizedError : new Error(reason ?? `Could not connect to ${this.#transport.label}`));
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
if (this.#closed)
|
|
133
|
-
return;
|
|
134
|
-
if (code === UNAUTHORIZED_CLOSE_CODE) {
|
|
135
|
-
this.#closed = true;
|
|
136
|
-
this.#update({ connection: "closed", unauthorized: true });
|
|
137
|
-
this.#notice("error", new UnauthorizedError().message);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
if (code === INVALID_FRAME_CLOSE_CODE) {
|
|
141
|
-
this.#closed = true;
|
|
142
|
-
this.#update({ connection: "closed" });
|
|
143
|
-
this.#notice("error", `The host rejected a malformed frame from this client${reason === undefined ? "" : `: ${reason}`}`);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
this.#update({ connection: "reconnecting" });
|
|
147
|
-
const { min, max } = this.#options.reconnectDelayMs ?? { min: 200, max: 2000 };
|
|
148
|
-
const delay = Math.min(max, min * 2 ** this.#attempt++);
|
|
149
|
-
setTimeout(() => {
|
|
150
|
-
if (!this.#closed)
|
|
151
|
-
this.#open();
|
|
152
|
-
}, delay);
|
|
153
|
-
}
|
|
154
|
-
#send(frame) {
|
|
155
|
-
this.#connection?.send(JSON.stringify(frame));
|
|
156
|
-
}
|
|
157
|
-
#receive(frame) {
|
|
158
|
-
if (frame === null || typeof frame !== "object")
|
|
159
|
-
return;
|
|
160
|
-
switch (frame.type) {
|
|
161
|
-
case "hello": {
|
|
162
|
-
const announced = frame.protocol;
|
|
163
|
-
const hostMajor = typeof announced === "number" ? announced : announced !== null && typeof announced === "object" ? announced.major : undefined;
|
|
164
|
-
if (hostMajor !== PROTOCOL.major) {
|
|
165
|
-
const mismatch = new ProtocolMismatchError(typeof hostMajor === "number" ? hostMajor : Number.NaN, PROTOCOL.major, frame.hostVersion);
|
|
166
|
-
this.#closed = true;
|
|
167
|
-
if (this.#state === undefined) {
|
|
168
|
-
this.#rejectReady(mismatch);
|
|
169
|
-
} else {
|
|
170
|
-
this.#update({
|
|
171
|
-
connection: "outdated",
|
|
172
|
-
protocolMismatch: {
|
|
173
|
-
hostMajor: mismatch.hostMajor,
|
|
174
|
-
clientMajor: mismatch.clientMajor,
|
|
175
|
-
...mismatch.hostVersion === undefined ? {} : { hostVersion: mismatch.hostVersion },
|
|
176
|
-
direction: mismatch.direction
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
this.#notice("error", mismatch.message);
|
|
180
|
-
}
|
|
181
|
-
this.#connection?.close();
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
const hostMinor = announced !== null && typeof announced === "object" && typeof announced.minor === "number" ? announced.minor : 0;
|
|
185
|
-
this.#hostMinor = hostMinor;
|
|
186
|
-
this.#helloReceived = true;
|
|
187
|
-
this.#attempt = 0;
|
|
188
|
-
this.#docs = frame.docs;
|
|
189
|
-
this.#toolPresentations = frame.toolPresentations;
|
|
190
|
-
this.#helloModels = frame.models;
|
|
191
|
-
this.#helloDefaults = frame.defaults ?? {};
|
|
192
|
-
this.#hostVersion = frame.hostVersion;
|
|
193
|
-
this.#fresh.clear();
|
|
194
|
-
const covered = new Set(hostStreams(hostMinor));
|
|
195
|
-
for (const name of HOST_STREAM_NAMES) {
|
|
196
|
-
if (covered.has(name)) {
|
|
197
|
-
this.#wanted.add(name);
|
|
198
|
-
} else {
|
|
199
|
-
this.#wanted.delete(name);
|
|
200
|
-
this.#values.delete(name);
|
|
201
|
-
this.#fresh.delete(name);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if (this.#current !== undefined)
|
|
205
|
-
for (const stream of this.#conversationStreams(this.#current))
|
|
206
|
-
this.#wanted.add(stream);
|
|
207
|
-
if (this.#side !== undefined)
|
|
208
|
-
for (const stream of this.#conversationStreams(this.#side))
|
|
209
|
-
this.#wanted.add(stream);
|
|
210
|
-
for (const stream of this.#wanted)
|
|
211
|
-
this.#subscribe(stream);
|
|
212
|
-
if (this.#state !== undefined) {
|
|
213
|
-
this.#update({
|
|
214
|
-
session: frame.session,
|
|
215
|
-
toolPresentations: frame.toolPresentations,
|
|
216
|
-
connection: "connected",
|
|
217
|
-
host: this.#hostInfo()
|
|
218
|
-
});
|
|
219
|
-
this.#refresh();
|
|
220
|
-
} else
|
|
221
|
-
this.#awaitFirstView(frame, hostMinor);
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
case "snapshot":
|
|
225
|
-
if (!this.#wanted.has(frame.stream))
|
|
226
|
-
return;
|
|
227
|
-
this.#values.set(frame.stream, frame.value);
|
|
228
|
-
this.#fresh.add(frame.stream);
|
|
229
|
-
this.#refresh();
|
|
230
|
-
for (const waiter of this.#snapshotWaiters.get(frame.stream)?.splice(0) ?? [])
|
|
231
|
-
waiter.resolve();
|
|
232
|
-
return;
|
|
233
|
-
case "ops":
|
|
234
|
-
if (!this.#wanted.has(frame.stream) || !this.#values.has(frame.stream))
|
|
235
|
-
return;
|
|
236
|
-
this.#values.set(frame.stream, applyImmutable(this.#values.get(frame.stream), frame.ops));
|
|
237
|
-
this.#refresh();
|
|
238
|
-
return;
|
|
239
|
-
case "ended":
|
|
240
|
-
this.#wanted.delete(frame.stream);
|
|
241
|
-
this.#values.delete(frame.stream);
|
|
242
|
-
this.#fresh.delete(frame.stream);
|
|
243
|
-
for (const waiter of this.#snapshotWaiters.get(frame.stream)?.splice(0) ?? []) {
|
|
244
|
-
waiter.reject(new Error(`Stream ${frame.stream} ended: ${frame.reason}`));
|
|
245
|
-
}
|
|
246
|
-
this.#refresh();
|
|
247
|
-
return;
|
|
248
|
-
case "result": {
|
|
249
|
-
const pending = this.#pending.get(frame.id);
|
|
250
|
-
this.#pending.delete(frame.id);
|
|
251
|
-
if (frame.ok)
|
|
252
|
-
pending?.resolve(frame.value);
|
|
253
|
-
else
|
|
254
|
-
pending?.reject(new Error(frame.error));
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
case "notice":
|
|
258
|
-
this.#notice(frame.level, frame.message);
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
#awaitFirstView(hello, hostMinor) {
|
|
263
|
-
Promise.all(hostStreams(hostMinor).map((stream) => this.#snapshot(stream))).then(() => {
|
|
264
|
-
this.#state = {
|
|
265
|
-
session: hello.session,
|
|
266
|
-
organized: { chats: [], projects: [], archived: [] },
|
|
267
|
-
models: this.#host("models") ?? hello.models,
|
|
268
|
-
defaults: this.#effectiveDefaults(hello.defaults ?? {}),
|
|
269
|
-
notices: [],
|
|
270
|
-
connection: "connected",
|
|
271
|
-
docs: [],
|
|
272
|
-
toolPresentations: hello.toolPresentations,
|
|
273
|
-
devices: [],
|
|
274
|
-
mcp: null,
|
|
275
|
-
auth: null,
|
|
276
|
-
settings: null,
|
|
277
|
-
agents: null,
|
|
278
|
-
host: this.#hostInfo()
|
|
279
|
-
};
|
|
280
|
-
this.#refresh();
|
|
281
|
-
this.#resolveReady({ view: this.#viewSource(), controller: this.#controller(), close: () => this.#close() });
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
#snapshot(stream) {
|
|
285
|
-
return new Promise((resolve, reject) => {
|
|
286
|
-
const waiters = this.#snapshotWaiters.get(stream) ?? [];
|
|
287
|
-
waiters.push({ resolve, reject });
|
|
288
|
-
this.#snapshotWaiters.set(stream, waiters);
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
#conversationStreams(id) {
|
|
292
|
-
return [conversationStream(id), ...this.#docs.map((doc) => docStream(doc.kind, id))];
|
|
293
|
-
}
|
|
294
|
-
async#acquire(id) {
|
|
295
|
-
const missing = this.#conversationStreams(id).filter((stream) => !this.#wanted.has(stream));
|
|
296
|
-
const stale = this.#conversationStreams(id).filter((stream) => this.#wanted.has(stream) && !this.#fresh.has(stream));
|
|
297
|
-
const shown = Promise.all([...missing, ...stale].map((stream) => this.#snapshot(stream)));
|
|
298
|
-
for (const stream of missing) {
|
|
299
|
-
this.#wanted.add(stream);
|
|
300
|
-
if (this.#helloReceived)
|
|
301
|
-
this.#subscribe(stream);
|
|
302
|
-
}
|
|
303
|
-
await shown;
|
|
304
|
-
}
|
|
305
|
-
#release(id, other) {
|
|
306
|
-
const keep = new Set(other === undefined ? [] : this.#conversationStreams(other));
|
|
307
|
-
for (const stream of this.#conversationStreams(id)) {
|
|
308
|
-
if (keep.has(stream))
|
|
309
|
-
continue;
|
|
310
|
-
this.#wanted.delete(stream);
|
|
311
|
-
this.#values.delete(stream);
|
|
312
|
-
this.#fresh.delete(stream);
|
|
313
|
-
this.#send({ type: "unsubscribe", stream });
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
#subscribe(stream) {
|
|
317
|
-
const refs = stream.startsWith("conversation:") && hostSupportsFeature(this.#hostMinor, "imageRefs");
|
|
318
|
-
this.#send({ type: "subscribe", stream, ...refs ? { images: "ref" } : {} });
|
|
319
|
-
}
|
|
320
|
-
#host(name) {
|
|
321
|
-
return this.#values.get(name);
|
|
322
|
-
}
|
|
323
|
-
#hostInfo() {
|
|
324
|
-
return {
|
|
325
|
-
protocol: { major: PROTOCOL.major, minor: this.#hostMinor },
|
|
326
|
-
...this.#hostVersion === undefined ? {} : { version: this.#hostVersion }
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
#effectiveDefaults(fallback) {
|
|
330
|
-
const settings = this.#host("settings");
|
|
331
|
-
if (settings === undefined)
|
|
332
|
-
return fallback;
|
|
333
|
-
return {
|
|
334
|
-
...settings.defaultModel === null ? {} : { model: settings.defaultModel },
|
|
335
|
-
...settings.defaultThinkingLevel === null ? {} : { thinkingLevel: settings.defaultThinkingLevel }
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
#descendsFrom(id, ancestor) {
|
|
339
|
-
const summaries = this.#host("conversations") ?? [];
|
|
340
|
-
const parentOf = new Map(summaries.map((summary) => [summary.id, summary.parent]));
|
|
341
|
-
let cursor = id;
|
|
342
|
-
for (let depth = 0;cursor !== undefined && depth < 8; depth++) {
|
|
343
|
-
if (cursor === ancestor)
|
|
344
|
-
return true;
|
|
345
|
-
cursor = parentOf.get(cursor);
|
|
346
|
-
}
|
|
347
|
-
return false;
|
|
348
|
-
}
|
|
349
|
-
#docsOf(id) {
|
|
350
|
-
return this.#docs.flatMap((doc) => {
|
|
351
|
-
const stream = docStream(doc.kind, id);
|
|
352
|
-
return this.#values.has(stream) ? [{ kind: doc.kind, presentation: doc.presentation, value: this.#values.get(stream) }] : [];
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
#refresh() {
|
|
356
|
-
if (this.#state === undefined)
|
|
357
|
-
return;
|
|
358
|
-
const current = this.#current;
|
|
359
|
-
const side = this.#side;
|
|
360
|
-
const conversation = current === undefined ? undefined : this.#values.get(conversationStream(current));
|
|
361
|
-
const summaries = this.#host("conversations") ?? [];
|
|
362
|
-
const index = this.#host("index") ?? { projects: [], conversations: [] };
|
|
363
|
-
const devices = this.#host("devices")?.devices ?? [];
|
|
364
|
-
const tasks = this.#wanted.has("tasks") ? this.#values.get("tasks") : undefined;
|
|
365
|
-
this.#update({
|
|
366
|
-
conversation,
|
|
367
|
-
models: this.#host("models") ?? this.#helloModels,
|
|
368
|
-
organized: organize(index, summaries),
|
|
369
|
-
home: current === undefined ? undefined : homeOf(index, summaries, current),
|
|
370
|
-
checkout: current === undefined ? undefined : checkoutOf(index, summaries, current),
|
|
371
|
-
tasks,
|
|
372
|
-
docs: current === undefined ? [] : this.#docsOf(current),
|
|
373
|
-
side: side === undefined ? undefined : {
|
|
374
|
-
id: side,
|
|
375
|
-
conversation: this.#values.get(conversationStream(side)),
|
|
376
|
-
docs: this.#docsOf(side)
|
|
377
|
-
},
|
|
378
|
-
devices,
|
|
379
|
-
mcp: this.#host("mcp") ?? null,
|
|
380
|
-
auth: this.#host("auth") ?? null,
|
|
381
|
-
settings: this.#host("settings") ?? null,
|
|
382
|
-
agents: this.#host("agents") ?? null,
|
|
383
|
-
defaults: this.#effectiveDefaults(this.#helloDefaults)
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
#update(patch) {
|
|
387
|
-
if (this.#state === undefined)
|
|
388
|
-
return;
|
|
389
|
-
this.#state = { ...this.#state, ...patch };
|
|
390
|
-
if (this.#notifying)
|
|
391
|
-
return;
|
|
392
|
-
this.#notifying = true;
|
|
393
|
-
queueMicrotask(() => {
|
|
394
|
-
this.#notifying = false;
|
|
395
|
-
for (const listener of this.#listeners)
|
|
396
|
-
listener();
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
#boundSkills(skills) {
|
|
400
|
-
return skills === undefined || !hostSupportsFeature(this.#hostMinor, "boundSkills") ? {} : { skills };
|
|
401
|
-
}
|
|
402
|
-
#notice(level, message) {
|
|
403
|
-
if (this.#state === undefined)
|
|
404
|
-
return;
|
|
405
|
-
this.#update({ notices: [...this.#state.notices, { id: this.#nextNotice++, level, message }].slice(-20) });
|
|
406
|
-
}
|
|
407
|
-
#call(method, args) {
|
|
408
|
-
if (this.#connection === undefined || this.#state?.connection !== "connected") {
|
|
409
|
-
return Promise.reject(new Error("Not connected to the host"));
|
|
410
|
-
}
|
|
411
|
-
const id = this.#nextCall++;
|
|
412
|
-
return new Promise((resolve, reject) => {
|
|
413
|
-
this.#pending.set(id, { resolve, reject });
|
|
414
|
-
this.#send({ type: "call", id, method, args });
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
#command(operation) {
|
|
418
|
-
this.#commands = this.#commands.then(operation).then(() => {}, (error) => this.#notice("error", error instanceof Error ? error.message : String(error)));
|
|
419
|
-
return this.#commands;
|
|
420
|
-
}
|
|
421
|
-
#request(operation) {
|
|
422
|
-
const request = this.#commands.then(operation);
|
|
423
|
-
this.#commands = request.then(() => {}, () => {});
|
|
424
|
-
return request;
|
|
425
|
-
}
|
|
426
|
-
#viewSource() {
|
|
427
|
-
return {
|
|
428
|
-
current: () => this.#state,
|
|
429
|
-
subscribe: (listener) => {
|
|
430
|
-
this.#listeners.add(listener);
|
|
431
|
-
return () => this.#listeners.delete(listener);
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
#controller() {
|
|
436
|
-
const newRequestId = () => Array.from(crypto.getRandomValues(new Uint8Array(16)), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
437
|
-
const conversationId = () => {
|
|
438
|
-
if (this.#current === undefined)
|
|
439
|
-
throw new Error("No conversation selected");
|
|
440
|
-
return this.#current;
|
|
441
|
-
};
|
|
442
|
-
return {
|
|
443
|
-
addProject: (path) => this.#request(() => this.#call("addProject", { path })),
|
|
444
|
-
listFolders: (path) => this.#request(() => {
|
|
445
|
-
if (!hostSupportsCall(this.#hostMinor, "listFolders")) {
|
|
446
|
-
throw new Error("The host is too old for this (needs protocol 5.3); run `pinomad upgrade` on the host");
|
|
447
|
-
}
|
|
448
|
-
return this.#call("listFolders", path === undefined ? {} : { path });
|
|
449
|
-
}),
|
|
450
|
-
removeProject: (path) => this.#command(() => this.#call("removeProject", { path })),
|
|
451
|
-
renameConversation: (id, title) => this.#request(async () => {
|
|
452
|
-
if (!hostSupportsCall(this.#hostMinor, "renameConversation")) {
|
|
453
|
-
throw new Error("The host is too old for this (needs protocol 5.4); run `pinomad upgrade` on the host");
|
|
454
|
-
}
|
|
455
|
-
await this.#call("renameConversation", { conversationId: id, title });
|
|
456
|
-
}),
|
|
457
|
-
createConversation: (home, text, options) => this.#command(async () => {
|
|
458
|
-
const images = options?.images ?? [];
|
|
459
|
-
if (images.length > 0 && !hostSupportsFeature(this.#hostMinor, "images")) {
|
|
460
|
-
throw new Error("The host is too old for images (needs protocol 5.5); run `pinomad upgrade` on the host");
|
|
461
|
-
}
|
|
462
|
-
if (needsImageRefs(images) && !hostSupportsFeature(this.#hostMinor, "imageRefs")) {
|
|
463
|
-
throw new Error("The host is too old for image refs (needs protocol 5.15); run `pinomad upgrade` on the host");
|
|
464
|
-
}
|
|
465
|
-
const attachments = options?.attachments ?? [];
|
|
466
|
-
if (attachments.length > MAX_ATTACHMENTS_PER_MESSAGE) {
|
|
467
|
-
throw new Error(`At most ${MAX_ATTACHMENTS_PER_MESSAGE} attachments per message`);
|
|
468
|
-
}
|
|
469
|
-
if (attachments.length > 0 && !hostSupportsFeature(this.#hostMinor, "attachments")) {
|
|
470
|
-
throw new Error("The host is too old for attachments (needs protocol 5.19); run `pinomad upgrade` on the host");
|
|
471
|
-
}
|
|
472
|
-
if (options?.agent !== undefined && !hostSupportsFeature(this.#hostMinor, "agentConversations")) {
|
|
473
|
-
throw new Error("The host is too old for named agents (needs protocol 5.10); run `pinomad upgrade` on the host");
|
|
474
|
-
}
|
|
475
|
-
const { conversationId: conversationId2 } = await this.#call("createConversation", {
|
|
476
|
-
home,
|
|
477
|
-
text,
|
|
478
|
-
requestId: newRequestId(),
|
|
479
|
-
...options?.checkout === undefined ? {} : { checkout: options.checkout },
|
|
480
|
-
...options?.model === undefined ? {} : { model: options.model },
|
|
481
|
-
...options?.thinkingLevel === undefined ? {} : { thinkingLevel: options.thinkingLevel },
|
|
482
|
-
...options?.agent === undefined ? {} : { agent: options.agent },
|
|
483
|
-
...images.length === 0 ? {} : { images },
|
|
484
|
-
...this.#boundSkills(options?.skills),
|
|
485
|
-
...attachments.length === 0 ? {} : { attachments }
|
|
486
|
-
});
|
|
487
|
-
await this.#switch(conversationId2);
|
|
488
|
-
}),
|
|
489
|
-
archive: (id, archived) => this.#command(async () => {
|
|
490
|
-
await this.#call("archive", { conversationId: id, archived });
|
|
491
|
-
if (archived && id === this.#current)
|
|
492
|
-
this.#unshow();
|
|
493
|
-
if (archived && this.#side !== undefined && this.#descendsFrom(this.#side, id))
|
|
494
|
-
await this.#showSide(undefined);
|
|
495
|
-
}),
|
|
496
|
-
submit: (text, whenBusy, options) => this.#command(() => {
|
|
497
|
-
const images = options?.images ?? [];
|
|
498
|
-
if (images.length > 0 && !hostSupportsFeature(this.#hostMinor, "images")) {
|
|
499
|
-
throw new Error("The host is too old for images (needs protocol 5.5); run `pinomad upgrade` on the host");
|
|
500
|
-
}
|
|
501
|
-
if (needsImageRefs(images) && !hostSupportsFeature(this.#hostMinor, "imageRefs")) {
|
|
502
|
-
throw new Error("The host is too old for image refs (needs protocol 5.15); run `pinomad upgrade` on the host");
|
|
503
|
-
}
|
|
504
|
-
const attachments = options?.attachments ?? [];
|
|
505
|
-
if (attachments.length > MAX_ATTACHMENTS_PER_MESSAGE) {
|
|
506
|
-
throw new Error(`At most ${MAX_ATTACHMENTS_PER_MESSAGE} attachments per message`);
|
|
507
|
-
}
|
|
508
|
-
if (attachments.length > 0 && !hostSupportsFeature(this.#hostMinor, "attachments")) {
|
|
509
|
-
throw new Error("The host is too old for attachments (needs protocol 5.19); run `pinomad upgrade` on the host");
|
|
510
|
-
}
|
|
511
|
-
return this.#call("submit", {
|
|
512
|
-
conversationId: options?.target ?? conversationId(),
|
|
513
|
-
text,
|
|
514
|
-
whenBusy,
|
|
515
|
-
requestId: options?.requestId ?? newRequestId(),
|
|
516
|
-
...images.length === 0 ? {} : { images },
|
|
517
|
-
...this.#boundSkills(options?.skills),
|
|
518
|
-
...attachments.length === 0 ? {} : { attachments }
|
|
519
|
-
});
|
|
520
|
-
}),
|
|
521
|
-
compact: (instructions) => this.#command(() => this.#call("compact", { conversationId: conversationId(), ...instructions === undefined ? {} : { instructions } })),
|
|
522
|
-
runCommand: (name, args, target) => this.#command(() => {
|
|
523
|
-
if (!hostSupportsCall(this.#hostMinor, "runCommand")) {
|
|
524
|
-
throw new Error("The host is too old for this (needs protocol 5.17); run `pinomad upgrade` on the host");
|
|
525
|
-
}
|
|
526
|
-
return this.#call("runCommand", {
|
|
527
|
-
conversationId: target ?? conversationId(),
|
|
528
|
-
name,
|
|
529
|
-
...args === undefined ? {} : { args }
|
|
530
|
-
});
|
|
531
|
-
}),
|
|
532
|
-
abort: (target) => {
|
|
533
|
-
const targetConversation = target ?? this.#current;
|
|
534
|
-
if (targetConversation === undefined) {
|
|
535
|
-
this.#notice("error", "No conversation selected");
|
|
536
|
-
return Promise.resolve();
|
|
537
|
-
}
|
|
538
|
-
return this.#call("abort", { conversationId: targetConversation }).then(() => {}, (error) => this.#notice("error", error instanceof Error ? error.message : String(error)));
|
|
539
|
-
},
|
|
540
|
-
withdrawQueued: (submissionId, target) => this.#request(async () => {
|
|
541
|
-
if (!hostSupportsCall(this.#hostMinor, "withdrawQueued")) {
|
|
542
|
-
throw new Error("The host is too old for this (needs protocol 5.14); run `pinomad upgrade` on the host");
|
|
543
|
-
}
|
|
544
|
-
const { outcome } = await this.#call("withdrawQueued", {
|
|
545
|
-
conversationId: target ?? conversationId(),
|
|
546
|
-
submissionId
|
|
547
|
-
});
|
|
548
|
-
return outcome;
|
|
549
|
-
}),
|
|
550
|
-
steerQueued: (submissionId, target) => this.#request(async () => {
|
|
551
|
-
if (!hostSupportsCall(this.#hostMinor, "steerQueued")) {
|
|
552
|
-
throw new Error("The host is too old for this (needs protocol 5.14); run `pinomad upgrade` on the host");
|
|
553
|
-
}
|
|
554
|
-
const { outcome } = await this.#call("steerQueued", {
|
|
555
|
-
conversationId: target ?? conversationId(),
|
|
556
|
-
submissionId
|
|
557
|
-
});
|
|
558
|
-
return outcome;
|
|
559
|
-
}),
|
|
560
|
-
setThinkingLevel: (level) => this.#command(() => this.#call("setThinkingLevel", { conversationId: conversationId(), level })),
|
|
561
|
-
setModel: (model) => this.#command(() => this.#call("setModel", { conversationId: conversationId(), model })),
|
|
562
|
-
setAgent: (agent) => this.#command(() => {
|
|
563
|
-
if (!hostSupportsCall(this.#hostMinor, "setAgent")) {
|
|
564
|
-
throw new Error("The host is too old for this (needs protocol 5.18); run `pinomad upgrade` on the host");
|
|
565
|
-
}
|
|
566
|
-
return this.#call("setAgent", { conversationId: conversationId(), agent });
|
|
567
|
-
}),
|
|
568
|
-
toggleTasks: () => this.#command(async () => {
|
|
569
|
-
if (this.#wanted.delete("tasks")) {
|
|
570
|
-
this.#send({ type: "unsubscribe", stream: "tasks" });
|
|
571
|
-
this.#values.delete("tasks");
|
|
572
|
-
this.#refresh();
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
this.#wanted.add("tasks");
|
|
576
|
-
const shown = this.#snapshot("tasks");
|
|
577
|
-
this.#send({ type: "subscribe", stream: "tasks" });
|
|
578
|
-
await shown;
|
|
579
|
-
}),
|
|
580
|
-
switchConversation: (id) => this.#command(() => this.#switch(id)),
|
|
581
|
-
showSide: (id) => this.#command(() => this.#showSide(id)),
|
|
582
|
-
fork: (entryId, prompt, removeTools, options) => this.#command(async () => {
|
|
583
|
-
const { conversationId: forked } = await this.#call("fork", {
|
|
584
|
-
conversationId: conversationId(),
|
|
585
|
-
entryId,
|
|
586
|
-
...removeTools === undefined ? {} : { removeTools }
|
|
587
|
-
});
|
|
588
|
-
if (options?.show === "side")
|
|
589
|
-
await this.#showSide(forked);
|
|
590
|
-
else
|
|
591
|
-
await this.#switch(forked);
|
|
592
|
-
await this.#call("submit", { conversationId: forked, text: prompt, whenBusy: "followUp", requestId: newRequestId() });
|
|
593
|
-
}),
|
|
594
|
-
answer: (kind, requestId, answers, target) => this.#command(() => this.#call("answer", { conversationId: target ?? conversationId(), kind, requestId, answers })),
|
|
595
|
-
changes: () => this.#request(() => this.#call("changes", { conversationId: conversationId() })),
|
|
596
|
-
listFiles: (scope, query) => {
|
|
597
|
-
if (!hostSupportsCall(this.#hostMinor, "listFiles")) {
|
|
598
|
-
return Promise.reject(new Error("The host is too old for this (needs protocol 5.10); run `pinomad upgrade` on the host"));
|
|
599
|
-
}
|
|
600
|
-
return this.#call("listFiles", { scope, query });
|
|
601
|
-
},
|
|
602
|
-
beginAttachment: (name, size) => {
|
|
603
|
-
if (!hostSupportsCall(this.#hostMinor, "beginAttachment")) {
|
|
604
|
-
return Promise.reject(new Error("The host is too old for attachments (needs protocol 5.19); run `pinomad upgrade` on the host"));
|
|
605
|
-
}
|
|
606
|
-
return this.#call("beginAttachment", { name, size }).then(({ attachmentId }) => attachmentId);
|
|
607
|
-
},
|
|
608
|
-
appendAttachment: (attachmentId, offset, data) => {
|
|
609
|
-
if (!hostSupportsCall(this.#hostMinor, "appendAttachment")) {
|
|
610
|
-
return Promise.reject(new Error("The host is too old for attachments (needs protocol 5.19); run `pinomad upgrade` on the host"));
|
|
611
|
-
}
|
|
612
|
-
return this.#call("appendAttachment", { attachmentId, offset, data });
|
|
613
|
-
},
|
|
614
|
-
image: (ref) => {
|
|
615
|
-
if (!hostSupportsCall(this.#hostMinor, "image")) {
|
|
616
|
-
return Promise.reject(new Error("The host is too old for this (needs protocol 5.15); run `pinomad upgrade` on the host"));
|
|
617
|
-
}
|
|
618
|
-
return this.#call("image", { ref });
|
|
619
|
-
},
|
|
620
|
-
listSkills: (scope) => {
|
|
621
|
-
if (!hostSupportsCall(this.#hostMinor, "listSkills")) {
|
|
622
|
-
return Promise.reject(new Error("The host is too old for this (needs protocol 5.13); run `pinomad upgrade` on the host"));
|
|
623
|
-
}
|
|
624
|
-
return this.#call("listSkills", { scope });
|
|
625
|
-
},
|
|
626
|
-
listCommands: (id) => {
|
|
627
|
-
if (!hostSupportsCall(this.#hostMinor, "listCommands")) {
|
|
628
|
-
return Promise.reject(new Error("The host is too old for this (needs protocol 5.17); run `pinomad upgrade` on the host"));
|
|
629
|
-
}
|
|
630
|
-
return this.#call("listCommands", { conversationId: id });
|
|
631
|
-
},
|
|
632
|
-
createPairing: () => this.#request(() => this.#call("createPairing", {})),
|
|
633
|
-
revokeDevice: (publicKey) => this.#command(() => this.#call("revokeDevice", { publicKey })),
|
|
634
|
-
logout: (providerId) => this.#command(() => {
|
|
635
|
-
if (!hostSupportsCall(this.#hostMinor, "logout")) {
|
|
636
|
-
throw new Error("The host is too old for this (needs protocol 5.1); run `pinomad upgrade` on the host");
|
|
637
|
-
}
|
|
638
|
-
return this.#call("logout", { providerId });
|
|
639
|
-
}),
|
|
640
|
-
updateSettings: (patch) => this.#command(() => {
|
|
641
|
-
if (!hostSupportsCall(this.#hostMinor, "updateSettings")) {
|
|
642
|
-
throw new Error("The host is too old for this (needs protocol 5.2); run `pinomad upgrade` on the host");
|
|
643
|
-
}
|
|
644
|
-
if (patch.acceptAccountDevices !== undefined && !hostSupportsFeature(this.#hostMinor, "acceptAccountDevices")) {
|
|
645
|
-
throw new Error("The host is too old for this (needs protocol 5.8); run `pinomad upgrade` on the host");
|
|
646
|
-
}
|
|
647
|
-
if (patch.hostName !== undefined && !hostSupportsFeature(this.#hostMinor, "hostName")) {
|
|
648
|
-
throw new Error("The host is too old for this (needs protocol 5.12); run `pinomad upgrade` on the host");
|
|
649
|
-
}
|
|
650
|
-
return this.#call("updateSettings", { patch });
|
|
651
|
-
}),
|
|
652
|
-
createAgent: (role) => this.#request(async () => {
|
|
653
|
-
if (!hostSupportsCall(this.#hostMinor, "createAgent")) {
|
|
654
|
-
throw new Error("The host is too old for this (needs protocol 5.6); run `pinomad upgrade` on the host");
|
|
655
|
-
}
|
|
656
|
-
await this.#call("createAgent", { role });
|
|
657
|
-
}),
|
|
658
|
-
updateAgent: (role) => this.#request(async () => {
|
|
659
|
-
if (!hostSupportsCall(this.#hostMinor, "updateAgent")) {
|
|
660
|
-
throw new Error("The host is too old for this (needs protocol 5.6); run `pinomad upgrade` on the host");
|
|
661
|
-
}
|
|
662
|
-
await this.#call("updateAgent", { role });
|
|
663
|
-
}),
|
|
664
|
-
deleteAgent: (name) => this.#request(async () => {
|
|
665
|
-
if (!hostSupportsCall(this.#hostMinor, "deleteAgent")) {
|
|
666
|
-
throw new Error("The host is too old for this (needs protocol 5.6); run `pinomad upgrade` on the host");
|
|
667
|
-
}
|
|
668
|
-
await this.#call("deleteAgent", { name });
|
|
669
|
-
}),
|
|
670
|
-
setPushToken: (token) => this.#request(async () => {
|
|
671
|
-
if (!hostSupportsCall(this.#hostMinor, "setPushToken")) {
|
|
672
|
-
throw new Error("The host is too old for this (needs protocol 5.7); run `pinomad upgrade` on the host");
|
|
673
|
-
}
|
|
674
|
-
if (token !== null && typeof token !== "string" && !hostSupportsFeature(this.#hostMinor, "apnsPush")) {
|
|
675
|
-
throw new Error("The host is too old for this (needs protocol 5.9); run `pinomad upgrade` on the host");
|
|
676
|
-
}
|
|
677
|
-
await this.#call("setPushToken", { token });
|
|
678
|
-
})
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
async#switch(id) {
|
|
682
|
-
const previous = this.#current;
|
|
683
|
-
if (id === previous)
|
|
684
|
-
return;
|
|
685
|
-
await this.#acquire(id);
|
|
686
|
-
this.#current = id;
|
|
687
|
-
if (previous !== undefined)
|
|
688
|
-
this.#release(previous, this.#side);
|
|
689
|
-
this.#refresh();
|
|
690
|
-
}
|
|
691
|
-
#unshow() {
|
|
692
|
-
const previous = this.#current;
|
|
693
|
-
if (previous === undefined)
|
|
694
|
-
return;
|
|
695
|
-
this.#current = undefined;
|
|
696
|
-
this.#release(previous, this.#side);
|
|
697
|
-
this.#refresh();
|
|
698
|
-
}
|
|
699
|
-
async#showSide(id) {
|
|
700
|
-
const previous = this.#side;
|
|
701
|
-
if (id === previous)
|
|
702
|
-
return;
|
|
703
|
-
if (id !== undefined)
|
|
704
|
-
await this.#acquire(id);
|
|
705
|
-
this.#side = id;
|
|
706
|
-
if (previous !== undefined)
|
|
707
|
-
this.#release(previous, this.#current);
|
|
708
|
-
this.#refresh();
|
|
709
|
-
}
|
|
710
|
-
#close() {
|
|
711
|
-
if (this.#closed)
|
|
712
|
-
return;
|
|
713
|
-
this.#closed = true;
|
|
714
|
-
this.#update({ connection: "closed" });
|
|
715
|
-
this.#connection?.close();
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
export { connectRemoteDurable, ProtocolMismatchError };
|