@coltonkirsten/a12s 0.3.0 → 0.4.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/dist/a12s.js +491 -19
- package/dist/src/client.d.ts +20 -1
- package/dist/src/client.js +231 -19
- package/dist/src/fleet.d.ts +160 -0
- package/dist/src/fleet.js +306 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +4 -1
- package/dist/src/node.d.ts +8 -0
- package/dist/src/node.js +15 -2
- package/dist/src/types.d.ts +176 -14
- package/package.json +1 -1
package/dist/a12s.js
CHANGED
|
@@ -52,6 +52,15 @@ var A12sError = class extends Error {
|
|
|
52
52
|
this.details = body.details;
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
|
+
var NotFound = class extends A12sError {
|
|
56
|
+
constructor(status, body) {
|
|
57
|
+
super(status, body);
|
|
58
|
+
this.name = "NotFound";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
function apiError(status, body) {
|
|
62
|
+
return status === 404 ? new NotFound(status, body) : new A12sError(status, body);
|
|
63
|
+
}
|
|
55
64
|
var A12sGapError = class extends Error {
|
|
56
65
|
after;
|
|
57
66
|
data;
|
|
@@ -73,6 +82,7 @@ var A12sClient = class {
|
|
|
73
82
|
url;
|
|
74
83
|
token;
|
|
75
84
|
fetchImpl;
|
|
85
|
+
shared;
|
|
76
86
|
constructor(opts = {}) {
|
|
77
87
|
this.url = (opts.url ?? "").replace(/\/+$/, "");
|
|
78
88
|
this.token = opts.token;
|
|
@@ -81,15 +91,16 @@ var A12sClient = class {
|
|
|
81
91
|
headers(extra = {}) {
|
|
82
92
|
return this.token ? { authorization: `Bearer ${this.token}`, ...extra } : extra;
|
|
83
93
|
}
|
|
84
|
-
|
|
94
|
+
/** @internal */
|
|
95
|
+
async request(method, path, body, opts = {}) {
|
|
85
96
|
const res = await this.fetchImpl(this.url + path, {
|
|
86
97
|
method,
|
|
87
|
-
headers: this.headers(body === void 0 ? {} : { "content-type": "application/json" }),
|
|
98
|
+
headers: this.headers({ ...body === void 0 ? {} : { "content-type": "application/json" }, ...opts.headers }),
|
|
88
99
|
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
89
|
-
signal
|
|
100
|
+
signal: opts.signal
|
|
90
101
|
});
|
|
91
102
|
const text = await res.text();
|
|
92
|
-
if (!res.ok) throw
|
|
103
|
+
if (!res.ok) throw apiError(res.status, parseError(res.status, text));
|
|
93
104
|
return text ? JSON.parse(text) : void 0;
|
|
94
105
|
}
|
|
95
106
|
/** Every Computer and Service: phase, session, active Turn, inbox, power. */
|
|
@@ -97,8 +108,8 @@ var A12sClient = class {
|
|
|
97
108
|
return this.request("GET", "/v1/status");
|
|
98
109
|
}
|
|
99
110
|
async computer(name) {
|
|
100
|
-
const c = (await this.status()).
|
|
101
|
-
if (!c) throw new
|
|
111
|
+
const c = (await this.status()).agentComputers.find((x) => x.name === name);
|
|
112
|
+
if (!c) throw new NotFound(404, { code: "not_found", message: `no Computer ${name}` });
|
|
102
113
|
return c;
|
|
103
114
|
}
|
|
104
115
|
/**
|
|
@@ -108,7 +119,7 @@ var A12sClient = class {
|
|
|
108
119
|
send(computer, msg) {
|
|
109
120
|
const m = typeof msg === "string" ? { body: msg } : { ...msg };
|
|
110
121
|
m.idempotencyKey ??= globalThis.crypto.randomUUID();
|
|
111
|
-
return this.request("POST", `/v1/computers/${enc(computer)}/inbox`, m);
|
|
122
|
+
return this.request("POST", `/v1/agent-computers/${enc(computer)}/inbox`, m);
|
|
112
123
|
}
|
|
113
124
|
/** Sends a Message, waits for the Turn that delivers it and resolves when that Turn ends: what `a12s prompt` does. */
|
|
114
125
|
async prompt(computer, body, opts = {}) {
|
|
@@ -135,8 +146,27 @@ var A12sClient = class {
|
|
|
135
146
|
}
|
|
136
147
|
return new EventStream((name, live, signal) => {
|
|
137
148
|
const q = opts.turn ? `?turn=${enc(opts.turn)}` : "";
|
|
138
|
-
return this.stream(name, `/v1/computers/${enc(name)}/events${q}`, opts.after, { ...opts, signal, live });
|
|
139
|
-
}, names ?? (async () => (await this.status()).
|
|
149
|
+
return this.stream(name, `/v1/agent-computers/${enc(name)}/events${q}`, opts.after, { ...opts, signal, live });
|
|
150
|
+
}, names ?? (async () => (await this.status()).agentComputers.map((c) => c.name)), opts.signal);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* @internal Events of the named agent computers (all if none), and the Controller's own about them, read from this
|
|
154
|
+
* client's one GET /v1/events connection.
|
|
155
|
+
*/
|
|
156
|
+
sharedEvents(names, opts = {}) {
|
|
157
|
+
this.shared ??= new SharedEvents(
|
|
158
|
+
(cursor, signal) => this.fetchImpl(this.url + "/v1/events", {
|
|
159
|
+
headers: this.headers({ accept: "text/event-stream", ...cursor !== void 0 ? { "last-event-id": cursor } : {} }),
|
|
160
|
+
signal
|
|
161
|
+
})
|
|
162
|
+
);
|
|
163
|
+
const shared = this.shared;
|
|
164
|
+
const wanted2 = names && new Set(names);
|
|
165
|
+
return new EventStream(
|
|
166
|
+
(_name, live, signal) => shared.subscribe({ wanted: wanted2, live, onGap: opts.onGap, reconnectMs: opts.reconnectMs }, signal),
|
|
167
|
+
[""],
|
|
168
|
+
opts.signal
|
|
169
|
+
);
|
|
140
170
|
}
|
|
141
171
|
/**
|
|
142
172
|
* Finished Turns of one Computer, several or all, as they end: who sent the work (`messages`), what the agent said
|
|
@@ -151,27 +181,27 @@ var A12sClient = class {
|
|
|
151
181
|
if (opts.bodies) q.set("bodies", "true");
|
|
152
182
|
if (opts.after !== void 0) q.set("after", String(opts.after));
|
|
153
183
|
const qs = q.toString();
|
|
154
|
-
return this.request("GET", `/v1/computers/${enc(computer)}/inbox${qs ? "?" + qs : ""}`);
|
|
184
|
+
return this.request("GET", `/v1/agent-computers/${enc(computer)}/inbox${qs ? "?" + qs : ""}`);
|
|
155
185
|
}
|
|
156
186
|
message(computer, id) {
|
|
157
|
-
return this.request("GET", `/v1/computers/${enc(computer)}/inbox/${enc(id)}`);
|
|
187
|
+
return this.request("GET", `/v1/agent-computers/${enc(computer)}/inbox/${enc(id)}`);
|
|
158
188
|
}
|
|
159
189
|
/** Withdraws a pending Message; a 409 A12sError names its state if it is too late. */
|
|
160
190
|
withdraw(computer, id) {
|
|
161
|
-
return this.request("DELETE", `/v1/computers/${enc(computer)}/inbox/${enc(id)}`);
|
|
191
|
+
return this.request("DELETE", `/v1/agent-computers/${enc(computer)}/inbox/${enc(id)}`);
|
|
162
192
|
}
|
|
163
193
|
/** Interrupts a Turn (default: the active one). The Turn ends `interrupted`; nothing is replayed. */
|
|
164
194
|
async stop(computer, turn) {
|
|
165
195
|
const id = turn ?? (await this.computer(computer)).activeTurn;
|
|
166
|
-
if (id) await this.request("POST", `/v1/computers/${enc(computer)}/turns/${enc(id)}/stop`, {});
|
|
196
|
+
if (id) await this.request("POST", `/v1/agent-computers/${enc(computer)}/turns/${enc(id)}/stop`, {});
|
|
167
197
|
}
|
|
168
198
|
/** Stops the Computer's processes and keeps its disks, session and inbox. */
|
|
169
199
|
async sleep(computer) {
|
|
170
|
-
await this.request("POST", `/v1/computers/${enc(computer)}/power`, { state: "asleep" });
|
|
200
|
+
await this.request("POST", `/v1/agent-computers/${enc(computer)}/power`, { state: "asleep" });
|
|
171
201
|
}
|
|
172
202
|
/** Starts a sleeping Computer; its session resumes on the next Turn. */
|
|
173
203
|
async wake(computer) {
|
|
174
|
-
await this.request("POST", `/v1/computers/${enc(computer)}/power`, { state: "awake" });
|
|
204
|
+
await this.request("POST", `/v1/agent-computers/${enc(computer)}/power`, { state: "awake" });
|
|
175
205
|
}
|
|
176
206
|
async services() {
|
|
177
207
|
return (await this.request("GET", "/v1/services")).services ?? [];
|
|
@@ -190,7 +220,7 @@ var A12sClient = class {
|
|
|
190
220
|
/** Waits until the Message is delivered and returns the Turn that carries it. */
|
|
191
221
|
async deliveredIn(computer, id, signal) {
|
|
192
222
|
while (!signal?.aborted) {
|
|
193
|
-
for await (const ev of this.stream(computer, `/v1/computers/${enc(computer)}/inbox/${enc(id)}/events`, void 0, {
|
|
223
|
+
for await (const ev of this.stream(computer, `/v1/agent-computers/${enc(computer)}/inbox/${enc(id)}/events`, void 0, {
|
|
194
224
|
signal,
|
|
195
225
|
once: true
|
|
196
226
|
})) {
|
|
@@ -234,7 +264,7 @@ var A12sClient = class {
|
|
|
234
264
|
continue;
|
|
235
265
|
}
|
|
236
266
|
if (!res.ok) {
|
|
237
|
-
const err =
|
|
267
|
+
const err = apiError(res.status, parseError(res.status, await res.text()));
|
|
238
268
|
if (res.status !== 503 && res.status !== 502) throw err;
|
|
239
269
|
opts.live?.();
|
|
240
270
|
await sleep(opts.reconnectMs ?? 1e3, opts.signal);
|
|
@@ -290,6 +320,157 @@ var A12sClient = class {
|
|
|
290
320
|
}
|
|
291
321
|
}
|
|
292
322
|
};
|
|
323
|
+
var SharedEvents = class {
|
|
324
|
+
constructor(open) {
|
|
325
|
+
this.open = open;
|
|
326
|
+
}
|
|
327
|
+
subs = /* @__PURE__ */ new Set();
|
|
328
|
+
conn;
|
|
329
|
+
live = false;
|
|
330
|
+
async *subscribe(sub, signal) {
|
|
331
|
+
const queue = [];
|
|
332
|
+
let failure;
|
|
333
|
+
let wake;
|
|
334
|
+
const notify = () => {
|
|
335
|
+
const w = wake;
|
|
336
|
+
wake = void 0;
|
|
337
|
+
w?.();
|
|
338
|
+
};
|
|
339
|
+
const s = {
|
|
340
|
+
...sub,
|
|
341
|
+
push: (ev) => {
|
|
342
|
+
queue.push(ev);
|
|
343
|
+
notify();
|
|
344
|
+
},
|
|
345
|
+
fail: (error) => {
|
|
346
|
+
failure ??= { error };
|
|
347
|
+
notify();
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
signal.addEventListener("abort", notify, { once: true });
|
|
351
|
+
this.add(s);
|
|
352
|
+
try {
|
|
353
|
+
for (; ; ) {
|
|
354
|
+
if (queue.length) {
|
|
355
|
+
yield queue.shift();
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (failure) throw failure.error;
|
|
359
|
+
if (signal.aborted) return;
|
|
360
|
+
await new Promise((r) => wake = r);
|
|
361
|
+
}
|
|
362
|
+
} finally {
|
|
363
|
+
signal.removeEventListener("abort", notify);
|
|
364
|
+
this.remove(s);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
add(s) {
|
|
368
|
+
this.subs.add(s);
|
|
369
|
+
if (!this.conn) {
|
|
370
|
+
const conn = new AbortController();
|
|
371
|
+
this.conn = conn;
|
|
372
|
+
this.live = false;
|
|
373
|
+
void this.run(conn);
|
|
374
|
+
} else if (this.live) {
|
|
375
|
+
s.live();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
remove(s) {
|
|
379
|
+
this.subs.delete(s);
|
|
380
|
+
if (this.subs.size === 0) this.stop();
|
|
381
|
+
}
|
|
382
|
+
stop() {
|
|
383
|
+
this.conn?.abort();
|
|
384
|
+
this.conn = void 0;
|
|
385
|
+
this.live = false;
|
|
386
|
+
}
|
|
387
|
+
async run(conn) {
|
|
388
|
+
const { signal } = conn;
|
|
389
|
+
let cursor;
|
|
390
|
+
try {
|
|
391
|
+
while (!signal.aborted) {
|
|
392
|
+
let res;
|
|
393
|
+
try {
|
|
394
|
+
res = await this.open(cursor, signal);
|
|
395
|
+
} catch (err) {
|
|
396
|
+
if (signal.aborted) return;
|
|
397
|
+
if (!isNetworkError(err)) throw err;
|
|
398
|
+
}
|
|
399
|
+
if (res && !res.ok) {
|
|
400
|
+
const err = apiError(res.status, parseError(res.status, await res.text()));
|
|
401
|
+
if (res.status !== 502 && res.status !== 503) throw err;
|
|
402
|
+
res = void 0;
|
|
403
|
+
}
|
|
404
|
+
if (res) {
|
|
405
|
+
if (!res.body) throw new A12sStreamError("GET /v1/events: response has no body");
|
|
406
|
+
const frames = readSSE(res.body);
|
|
407
|
+
try {
|
|
408
|
+
for (; ; ) {
|
|
409
|
+
let r;
|
|
410
|
+
try {
|
|
411
|
+
r = await frames.next();
|
|
412
|
+
} catch {
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
if (r.done) break;
|
|
416
|
+
cursor = this.frame(r.value, cursor);
|
|
417
|
+
}
|
|
418
|
+
} finally {
|
|
419
|
+
await frames.return(void 0);
|
|
420
|
+
res.body.cancel().catch(() => {
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
await sleep(this.reconnectMs(), signal);
|
|
425
|
+
}
|
|
426
|
+
} catch (err) {
|
|
427
|
+
if (signal.aborted) return;
|
|
428
|
+
const subs = [...this.subs];
|
|
429
|
+
if (this.conn === conn) this.stop();
|
|
430
|
+
for (const s of subs) s.fail(err);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/** Delivers one frame and returns the cursor to resume after. */
|
|
434
|
+
frame(f, cursor) {
|
|
435
|
+
if (f.event === "head") {
|
|
436
|
+
const id = parseJSON(f.data)?.id;
|
|
437
|
+
this.live = true;
|
|
438
|
+
for (const s of this.subs) s.live();
|
|
439
|
+
return typeof id === "string" && id ? id : cursor;
|
|
440
|
+
}
|
|
441
|
+
if (f.event === "gap") {
|
|
442
|
+
const data = cursor !== void 0 ? { lastEventId: cursor } : {};
|
|
443
|
+
const gap = { v: 1, computer: "", session: "", turn: "", seq: 0, ts: (/* @__PURE__ */ new Date()).toISOString(), type: "gap", data };
|
|
444
|
+
for (const s of this.subs) {
|
|
445
|
+
if (s.onGap === "throw") s.fail(new A12sGapError(data));
|
|
446
|
+
else s.push(gap);
|
|
447
|
+
}
|
|
448
|
+
return cursor;
|
|
449
|
+
}
|
|
450
|
+
if (f.event || !f.data) return cursor;
|
|
451
|
+
let ev;
|
|
452
|
+
try {
|
|
453
|
+
ev = JSON.parse(f.data);
|
|
454
|
+
} catch (err) {
|
|
455
|
+
throw new A12sStreamError(`malformed event after ${cursor ?? "(start)"}: ${f.data.slice(0, 120)}`, { cause: err });
|
|
456
|
+
}
|
|
457
|
+
for (const s of this.subs) if (wanted(s.wanted, ev)) s.push(ev);
|
|
458
|
+
return f.id ?? cursor;
|
|
459
|
+
}
|
|
460
|
+
reconnectMs() {
|
|
461
|
+
let ms = 1e3;
|
|
462
|
+
for (const s of this.subs) if (s.reconnectMs !== void 0) ms = Math.min(ms, s.reconnectMs);
|
|
463
|
+
return ms;
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
function agentComputerOf(ev) {
|
|
467
|
+
return ev.agentComputer || ev.computer;
|
|
468
|
+
}
|
|
469
|
+
function wanted(names, ev) {
|
|
470
|
+
if (!names) return true;
|
|
471
|
+
if (ev.type === "fleet.changed") return (ev.data?.names ?? []).some((n) => names.has(n));
|
|
472
|
+
return names.has(agentComputerOf(ev));
|
|
473
|
+
}
|
|
293
474
|
var EventStream = class {
|
|
294
475
|
constructor(source, names, signal) {
|
|
295
476
|
this.source = source;
|
|
@@ -429,7 +610,8 @@ var TurnBuilder = class {
|
|
|
429
610
|
open = /* @__PURE__ */ new Map();
|
|
430
611
|
add(ev) {
|
|
431
612
|
if (!ev.turn) return void 0;
|
|
432
|
-
const
|
|
613
|
+
const name = agentComputerOf(ev);
|
|
614
|
+
const key = `${name}\0${ev.turn}`;
|
|
433
615
|
let t = this.open.get(key);
|
|
434
616
|
if (!t) this.open.set(key, t = { text: "", afterTool: false, messages: [], tools: /* @__PURE__ */ new Map() });
|
|
435
617
|
switch (ev.type) {
|
|
@@ -454,7 +636,8 @@ var TurnBuilder = class {
|
|
|
454
636
|
case "turn.finished":
|
|
455
637
|
this.open.delete(key);
|
|
456
638
|
return {
|
|
457
|
-
|
|
639
|
+
agentComputer: name,
|
|
640
|
+
computer: name,
|
|
458
641
|
id: ev.turn,
|
|
459
642
|
session: ev.session,
|
|
460
643
|
status: ev.data.status,
|
|
@@ -463,6 +646,7 @@ var TurnBuilder = class {
|
|
|
463
646
|
messages: t.messages,
|
|
464
647
|
tools: [...t.tools.values()],
|
|
465
648
|
usage: ev.data.usage,
|
|
649
|
+
...ev.data.tokens ? { tokens: ev.data.tokens } : {},
|
|
466
650
|
...t.startedAt ? { startedAt: t.startedAt } : {},
|
|
467
651
|
finishedAt: ev.ts
|
|
468
652
|
};
|
|
@@ -502,6 +686,286 @@ function sleep(ms, signal) {
|
|
|
502
686
|
});
|
|
503
687
|
}
|
|
504
688
|
|
|
689
|
+
// src/fleet.ts
|
|
690
|
+
var enc2 = encodeURIComponent;
|
|
691
|
+
var maxReplaceTries = 5;
|
|
692
|
+
var Fleet = class _Fleet {
|
|
693
|
+
/** The lower-level client the handles use, for API calls that have no method here. */
|
|
694
|
+
client;
|
|
695
|
+
agentComputers;
|
|
696
|
+
services;
|
|
697
|
+
tokens;
|
|
698
|
+
credentials;
|
|
699
|
+
/** No discovery: `url` and `token` as given, by default the page's own origin. */
|
|
700
|
+
constructor(opts = {}) {
|
|
701
|
+
this.client = new A12sClient(opts);
|
|
702
|
+
this.agentComputers = new AgentComputers(this.client);
|
|
703
|
+
this.services = new Services(this.client);
|
|
704
|
+
this.tokens = new Tokens(this.client);
|
|
705
|
+
this.credentials = new Credentials(this.client);
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* In a browser: the Fleet behind the page's own origin, which is `a12s gateway` serving the page (no token
|
|
709
|
+
* needed). In Node: found like {@link connect} finds it. Pass `url` and `token` to reach anything else.
|
|
710
|
+
*/
|
|
711
|
+
static async connect(opts = {}) {
|
|
712
|
+
return new _Fleet(opts);
|
|
713
|
+
}
|
|
714
|
+
get url() {
|
|
715
|
+
return this.client.url;
|
|
716
|
+
}
|
|
717
|
+
async get() {
|
|
718
|
+
const r = await this.client.request("GET", "/v1/fleet");
|
|
719
|
+
return { version: r.version, spec: r.fleet ?? emptySpec(), drift: r.drift ?? [] };
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Reads the Fleet, lets `fn` change it (in place, or by returning a new spec) and writes it back only if nobody
|
|
723
|
+
* wrote in between; otherwise it reads again and retries, up to 5 times. `spec.defaults` is always present.
|
|
724
|
+
*/
|
|
725
|
+
async replace(fn) {
|
|
726
|
+
for (let attempt = 1; ; attempt++) {
|
|
727
|
+
const cur = await this.get();
|
|
728
|
+
cur.spec.defaults ??= {};
|
|
729
|
+
const next = await fn(cur.spec) ?? cur.spec;
|
|
730
|
+
try {
|
|
731
|
+
await this.client.request("PUT", "/v1/fleet", { fleet: next }, {
|
|
732
|
+
headers: { "if-match": `"${cur.version}"` }
|
|
733
|
+
});
|
|
734
|
+
} catch (err) {
|
|
735
|
+
if (attempt < maxReplaceTries && err instanceof A12sError && err.code === "version_mismatch") continue;
|
|
736
|
+
throw err;
|
|
737
|
+
}
|
|
738
|
+
return this.get();
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Live events of the named agent computers, or (no argument) all of them, and the Fleet's own (`fleet.changed`,
|
|
743
|
+
* `agent_computer.phase`). Every stream of this Fleet shares one connection, which reconnects without losing
|
|
744
|
+
* events. `await stream.ready()` before sending work you want to see from the start; `close()` (or `break`) ends it.
|
|
745
|
+
* `turn` or `after` (one agent computer) replays from its journal instead.
|
|
746
|
+
*/
|
|
747
|
+
events(agentComputers, opts = {}) {
|
|
748
|
+
if (opts.turn !== void 0 || opts.after !== void 0) return this.client.events(agentComputers, opts);
|
|
749
|
+
return this.client.sharedEvents(namesOf(agentComputers), opts);
|
|
750
|
+
}
|
|
751
|
+
/** Finished Turns of the named agent computers, or all of them, as they end. */
|
|
752
|
+
turns(agentComputers, opts = {}) {
|
|
753
|
+
return new TurnStream(this.client.sharedEvents(namesOf(agentComputers), opts));
|
|
754
|
+
}
|
|
755
|
+
/** Every agent computer and Service: phase, session, active Turn, inbox, power. */
|
|
756
|
+
status() {
|
|
757
|
+
return this.client.status();
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
function namesOf(names) {
|
|
761
|
+
return typeof names === "string" ? [names] : names;
|
|
762
|
+
}
|
|
763
|
+
function emptySpec() {
|
|
764
|
+
return { apiVersion: "a12s.ai/v1alpha1", kind: "Fleet", metadata: { name: "" }, agent_computers: {} };
|
|
765
|
+
}
|
|
766
|
+
function writeHeaders(opts) {
|
|
767
|
+
return opts.replace ? {} : { "if-none-match": "*" };
|
|
768
|
+
}
|
|
769
|
+
var AgentComputers = class {
|
|
770
|
+
constructor(client) {
|
|
771
|
+
this.client = client;
|
|
772
|
+
}
|
|
773
|
+
/** Adds an agent computer to the Fleet and starts it. Fails with code `exists` if the name is taken. */
|
|
774
|
+
async create(name, spec = {}) {
|
|
775
|
+
const { replace, ...body } = spec;
|
|
776
|
+
const r = await this.client.request("PUT", `/v1/agent-computers/${enc2(name)}`, body, {
|
|
777
|
+
headers: writeHeaders({ replace })
|
|
778
|
+
});
|
|
779
|
+
return r.agentComputer ? new AgentComputer(this.client, r.agentComputer) : this.get(name);
|
|
780
|
+
}
|
|
781
|
+
/** Throws {@link NotFound} if there is none. */
|
|
782
|
+
async get(name) {
|
|
783
|
+
return new AgentComputer(this.client, await this.client.request("GET", `/v1/agent-computers/${enc2(name)}`));
|
|
784
|
+
}
|
|
785
|
+
async list() {
|
|
786
|
+
const r = await this.client.request("GET", "/v1/agent-computers");
|
|
787
|
+
return (r.agentComputers ?? []).map((d) => new AgentComputer(this.client, d));
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
var AgentComputer = class {
|
|
791
|
+
constructor(client, data) {
|
|
792
|
+
this.client = client;
|
|
793
|
+
this.name = data.name;
|
|
794
|
+
this.set(data);
|
|
795
|
+
}
|
|
796
|
+
name;
|
|
797
|
+
spec;
|
|
798
|
+
/** The spec with the Fleet's defaults merged in. */
|
|
799
|
+
effective;
|
|
800
|
+
status;
|
|
801
|
+
set(d) {
|
|
802
|
+
this.spec = d.spec;
|
|
803
|
+
this.effective = d.effective;
|
|
804
|
+
this.status = d.status;
|
|
805
|
+
return this;
|
|
806
|
+
}
|
|
807
|
+
get path() {
|
|
808
|
+
return `/v1/agent-computers/${enc2(this.name)}`;
|
|
809
|
+
}
|
|
810
|
+
async reload() {
|
|
811
|
+
return this.set(await this.client.request("GET", this.path));
|
|
812
|
+
}
|
|
813
|
+
/** Changes the spec: a key set to null goes back to the Fleet's default. A busy one picks it up when idle. */
|
|
814
|
+
async update(changes) {
|
|
815
|
+
const r = await this.client.request("PATCH", this.path, changes);
|
|
816
|
+
return r.agentComputer ? this.set(r.agentComputer) : this.reload();
|
|
817
|
+
}
|
|
818
|
+
/** Removes it from the Fleet and stops it; its drives are kept. */
|
|
819
|
+
async remove() {
|
|
820
|
+
await this.client.request("DELETE", this.path);
|
|
821
|
+
}
|
|
822
|
+
/** Sends a Message, waits for the Turn that delivers it and resolves when that Turn ends. */
|
|
823
|
+
prompt(text, opts = {}) {
|
|
824
|
+
return this.client.prompt(this.name, text, opts);
|
|
825
|
+
}
|
|
826
|
+
/** Puts a Message in the inbox and returns at once; it is delivered at the next idle moment. */
|
|
827
|
+
send(text, opts = {}) {
|
|
828
|
+
return this.client.send(this.name, { ...opts, body: text });
|
|
829
|
+
}
|
|
830
|
+
/** Its events, live, from the Fleet's shared connection; `turn` replays one Turn from its start instead. */
|
|
831
|
+
events(opts = {}) {
|
|
832
|
+
if (opts.turn !== void 0 || opts.after !== void 0) return this.client.events(this.name, opts);
|
|
833
|
+
return this.client.sharedEvents([this.name], opts);
|
|
834
|
+
}
|
|
835
|
+
turns(opts = {}) {
|
|
836
|
+
const { limit, ...rest } = opts;
|
|
837
|
+
if (limit !== void 0) return this.pastTurns(limit);
|
|
838
|
+
return new TurnStream(this.client.sharedEvents([this.name], rest));
|
|
839
|
+
}
|
|
840
|
+
async pastTurns(limit) {
|
|
841
|
+
const r = await this.client.request("GET", `${this.path}/turns?limit=${limit}`);
|
|
842
|
+
return (r.turns ?? []).map((t) => ({
|
|
843
|
+
...t,
|
|
844
|
+
computer: t.agentComputer,
|
|
845
|
+
tools: t.tools.map((tc) => ({ ...tc, isError: tc.isError ?? false }))
|
|
846
|
+
}));
|
|
847
|
+
}
|
|
848
|
+
/** Stops its processes and keeps its disks, session and inbox. */
|
|
849
|
+
sleep() {
|
|
850
|
+
return this.client.sleep(this.name);
|
|
851
|
+
}
|
|
852
|
+
wake() {
|
|
853
|
+
return this.client.wake(this.name);
|
|
854
|
+
}
|
|
855
|
+
/** Interrupts a Turn (default: the active one). */
|
|
856
|
+
stop(turn) {
|
|
857
|
+
return this.client.stop(this.name, turn);
|
|
858
|
+
}
|
|
859
|
+
inbox(opts = {}) {
|
|
860
|
+
return this.client.inbox(this.name, opts);
|
|
861
|
+
}
|
|
862
|
+
message(id) {
|
|
863
|
+
return this.client.message(this.name, id);
|
|
864
|
+
}
|
|
865
|
+
/** Withdraws a pending Message; a 409 A12sError names its state if it is too late. */
|
|
866
|
+
withdraw(id) {
|
|
867
|
+
return this.client.withdraw(this.name, id);
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
var Services = class {
|
|
871
|
+
constructor(client) {
|
|
872
|
+
this.client = client;
|
|
873
|
+
}
|
|
874
|
+
/** Adds a Service to the Fleet. Fails with code `exists` if the name is taken. */
|
|
875
|
+
async create(name, spec = {}) {
|
|
876
|
+
const { replace, ...body } = spec;
|
|
877
|
+
const r = await this.client.request("PUT", `/v1/services/${enc2(name)}`, body, {
|
|
878
|
+
headers: writeHeaders({ replace })
|
|
879
|
+
});
|
|
880
|
+
return r.service ? new Service(this.client, r.service) : this.get(name);
|
|
881
|
+
}
|
|
882
|
+
/** Throws {@link NotFound} if there is none. */
|
|
883
|
+
async get(name) {
|
|
884
|
+
return new Service(this.client, await this.client.service(name));
|
|
885
|
+
}
|
|
886
|
+
async list() {
|
|
887
|
+
return (await this.client.services()).map((s) => new Service(this.client, s));
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
var Service = class {
|
|
891
|
+
constructor(client, status) {
|
|
892
|
+
this.client = client;
|
|
893
|
+
this.name = status.name;
|
|
894
|
+
this.status = status;
|
|
895
|
+
}
|
|
896
|
+
name;
|
|
897
|
+
status;
|
|
898
|
+
/** As written in the Fleet. */
|
|
899
|
+
get spec() {
|
|
900
|
+
return this.status.spec ?? {};
|
|
901
|
+
}
|
|
902
|
+
get path() {
|
|
903
|
+
return `/v1/services/${enc2(this.name)}`;
|
|
904
|
+
}
|
|
905
|
+
async reload() {
|
|
906
|
+
this.status = await this.client.service(this.name);
|
|
907
|
+
return this;
|
|
908
|
+
}
|
|
909
|
+
/** Changes the spec as a JSON merge patch: a key set to null is removed. */
|
|
910
|
+
async update(changes) {
|
|
911
|
+
const r = await this.client.request("PATCH", this.path, changes);
|
|
912
|
+
if (!r.service) return this.reload();
|
|
913
|
+
this.status = r.service;
|
|
914
|
+
return this;
|
|
915
|
+
}
|
|
916
|
+
/** Removes it from the Fleet and stops it; its state drive is kept. */
|
|
917
|
+
async remove() {
|
|
918
|
+
await this.client.request("DELETE", this.path);
|
|
919
|
+
}
|
|
920
|
+
sleep() {
|
|
921
|
+
return this.power("asleep");
|
|
922
|
+
}
|
|
923
|
+
wake() {
|
|
924
|
+
return this.power("awake");
|
|
925
|
+
}
|
|
926
|
+
async power(state) {
|
|
927
|
+
this.status = await this.client.request("POST", `${this.path}/power`, { state });
|
|
928
|
+
return this;
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
var Tokens = class {
|
|
932
|
+
constructor(client) {
|
|
933
|
+
this.client = client;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* A token for another program; owner only. The secret is in the result once. With `prefix` it may do everything
|
|
937
|
+
* the owner can for names that start with it, and nothing outside; otherwise `actions` on `agentComputers`.
|
|
938
|
+
*/
|
|
939
|
+
create(name, opts) {
|
|
940
|
+
if ("prefix" in opts) {
|
|
941
|
+
return this.client.createToken({ name, prefix: opts.prefix });
|
|
942
|
+
}
|
|
943
|
+
return this.client.createToken({ name, actions: opts.actions, computers: opts.agentComputers ?? ["*"] });
|
|
944
|
+
}
|
|
945
|
+
async list() {
|
|
946
|
+
return (await this.client.request("GET", "/v1/tokens")).tokens ?? [];
|
|
947
|
+
}
|
|
948
|
+
async remove(name) {
|
|
949
|
+
await this.client.request("DELETE", `/v1/tokens/${enc2(name)}`);
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
var Credentials = class {
|
|
953
|
+
constructor(client) {
|
|
954
|
+
this.client = client;
|
|
955
|
+
}
|
|
956
|
+
/** Stores the value on the Controller; agent computers that use it get the new value at once. */
|
|
957
|
+
set(name, value) {
|
|
958
|
+
return this.client.request("PUT", `/v1/credentials/${enc2(name)}`, { value });
|
|
959
|
+
}
|
|
960
|
+
async list() {
|
|
961
|
+
return (await this.client.request("GET", "/v1/credentials")).credentials ?? [];
|
|
962
|
+
}
|
|
963
|
+
/** Fails with A12sError code `in_use` while an agent computer or Service uses it. */
|
|
964
|
+
async remove(name) {
|
|
965
|
+
await this.client.request("DELETE", `/v1/credentials/${enc2(name)}`);
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
|
|
505
969
|
// src/index.ts
|
|
506
970
|
async function connect(opts = {}) {
|
|
507
971
|
return new A12sClient(opts);
|
|
@@ -511,7 +975,15 @@ export {
|
|
|
511
975
|
A12sError,
|
|
512
976
|
A12sGapError,
|
|
513
977
|
A12sStreamError,
|
|
978
|
+
AgentComputer,
|
|
979
|
+
AgentComputers,
|
|
980
|
+
Credentials,
|
|
514
981
|
EventStream,
|
|
982
|
+
Fleet,
|
|
983
|
+
NotFound,
|
|
984
|
+
Service,
|
|
985
|
+
Services,
|
|
986
|
+
Tokens,
|
|
515
987
|
TurnStream,
|
|
516
988
|
connect
|
|
517
989
|
};
|
package/dist/src/client.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export declare class A12sError extends Error {
|
|
|
18
18
|
readonly details?: Record<string, string>;
|
|
19
19
|
constructor(status: number, body: ApiErrorBody);
|
|
20
20
|
}
|
|
21
|
+
/** A 404: the agent computer, Service, Message or token does not exist. */
|
|
22
|
+
export declare class NotFound extends A12sError {
|
|
23
|
+
constructor(status: number, body: ApiErrorBody);
|
|
24
|
+
}
|
|
21
25
|
/** Thrown by an event stream with `onGap: "throw"` when events were lost. */
|
|
22
26
|
export declare class A12sGapError extends Error {
|
|
23
27
|
readonly after?: number;
|
|
@@ -44,6 +48,7 @@ export interface EventsOptions {
|
|
|
44
48
|
*/
|
|
45
49
|
onGap?: "yield" | "throw";
|
|
46
50
|
}
|
|
51
|
+
export type SharedEventsOptions = Omit<EventsOptions, "turn" | "after">;
|
|
47
52
|
export interface PromptOptions extends Omit<EnqueueRequest, "body"> {
|
|
48
53
|
signal?: AbortSignal;
|
|
49
54
|
/** Every event of the Turn, as it streams. */
|
|
@@ -53,13 +58,22 @@ export interface PromptOptions extends Omit<EnqueueRequest, "body"> {
|
|
|
53
58
|
export interface PromptResult extends Turn {
|
|
54
59
|
message: EnqueueResponse;
|
|
55
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* @deprecated Use {@link Fleet}: `fleet.agentComputers.get(name)` returns an {@link AgentComputer} with these
|
|
63
|
+
* methods. Kept for one release.
|
|
64
|
+
*/
|
|
56
65
|
export declare class A12sClient {
|
|
57
66
|
readonly url: string;
|
|
58
67
|
private readonly token?;
|
|
59
68
|
private readonly fetchImpl;
|
|
69
|
+
private shared?;
|
|
60
70
|
constructor(opts?: A12sClientOptions);
|
|
61
71
|
private headers;
|
|
62
|
-
|
|
72
|
+
/** @internal */
|
|
73
|
+
request<T>(method: string, path: string, body?: unknown, opts?: {
|
|
74
|
+
headers?: Record<string, string>;
|
|
75
|
+
signal?: AbortSignal;
|
|
76
|
+
}): Promise<T>;
|
|
63
77
|
/** Every Computer and Service: phase, session, active Turn, inbox, power. */
|
|
64
78
|
status(): Promise<StatusResponse>;
|
|
65
79
|
computer(name: string): Promise<ComputerStatus>;
|
|
@@ -76,6 +90,11 @@ export declare class A12sClient {
|
|
|
76
90
|
* before sending work you want to see from the start; `close()` (or `break`) ends it.
|
|
77
91
|
*/
|
|
78
92
|
events(computers?: string | string[], opts?: EventsOptions): EventStream;
|
|
93
|
+
/**
|
|
94
|
+
* @internal Events of the named agent computers (all if none), and the Controller's own about them, read from this
|
|
95
|
+
* client's one GET /v1/events connection.
|
|
96
|
+
*/
|
|
97
|
+
sharedEvents(names: string[] | undefined, opts?: SharedEventsOptions): EventStream;
|
|
79
98
|
/**
|
|
80
99
|
* Finished Turns of one Computer, several or all, as they end: who sent the work (`messages`), what the agent said
|
|
81
100
|
* (`text`) and how it ended (`status`). Same `ready()` and `close()` as {@link events}.
|