@coltonkirsten/a12s 0.4.3 → 0.6.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/src/client.d.ts +13 -8
- package/dist/src/client.js +13 -6
- package/dist/src/fleet.d.ts +34 -12
- package/dist/src/fleet.js +40 -13
- package/dist/src/types.d.ts +11 -15
- package/package.json +5 -7
- package/dist/a12s.js +0 -1036
package/dist/a12s.js
DELETED
|
@@ -1,1036 +0,0 @@
|
|
|
1
|
-
// src/sse.ts
|
|
2
|
-
async function* readSSE(body) {
|
|
3
|
-
const decoder = new TextDecoder();
|
|
4
|
-
let buf = "";
|
|
5
|
-
let frame = { data: [] };
|
|
6
|
-
const reader = body.getReader();
|
|
7
|
-
try {
|
|
8
|
-
for (; ; ) {
|
|
9
|
-
const { value, done } = await reader.read();
|
|
10
|
-
if (done) return;
|
|
11
|
-
buf += decoder.decode(value, { stream: true });
|
|
12
|
-
let nl;
|
|
13
|
-
while ((nl = buf.search(/\r\n|\r|\n/)) >= 0) {
|
|
14
|
-
if (buf[nl] === "\r" && nl === buf.length - 1) break;
|
|
15
|
-
const line = buf.slice(0, nl);
|
|
16
|
-
buf = buf.slice(nl + (buf.startsWith("\r\n", nl) ? 2 : 1));
|
|
17
|
-
if (line === "") {
|
|
18
|
-
if (frame.data.length > 0 || frame.event) {
|
|
19
|
-
yield { id: frame.id, event: frame.event, data: frame.data.join("\n") };
|
|
20
|
-
}
|
|
21
|
-
frame = { data: [] };
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
if (line.startsWith(":")) continue;
|
|
25
|
-
const colon = line.indexOf(":");
|
|
26
|
-
const field = colon < 0 ? line : line.slice(0, colon);
|
|
27
|
-
let val = colon < 0 ? "" : line.slice(colon + 1);
|
|
28
|
-
if (val.startsWith(" ")) val = val.slice(1);
|
|
29
|
-
if (field === "data") frame.data.push(val);
|
|
30
|
-
else if (field === "id") frame.id = val;
|
|
31
|
-
else if (field === "event") frame.event = val;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
} finally {
|
|
35
|
-
reader.releaseLock();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// src/client.ts
|
|
40
|
-
var A12sError = class extends Error {
|
|
41
|
-
status;
|
|
42
|
-
/** A stable code such as `busy`, `asleep`, `no_grant`, `inbox_full`, `not_found` (see docs/api.md#errors). */
|
|
43
|
-
code;
|
|
44
|
-
fix;
|
|
45
|
-
details;
|
|
46
|
-
constructor(status, body) {
|
|
47
|
-
super(body.fix ? `${body.code}: ${body.message} fix: ${body.fix}` : `${body.code}: ${body.message}`);
|
|
48
|
-
this.name = "A12sError";
|
|
49
|
-
this.status = status;
|
|
50
|
-
this.code = body.code;
|
|
51
|
-
this.fix = body.fix;
|
|
52
|
-
this.details = body.details;
|
|
53
|
-
}
|
|
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
|
-
}
|
|
64
|
-
var A12sGapError = class extends Error {
|
|
65
|
-
after;
|
|
66
|
-
data;
|
|
67
|
-
constructor(data) {
|
|
68
|
-
super(`journal gap after seq ${data.after ?? "(none)"}: events were lost; resync with status()`);
|
|
69
|
-
this.name = "A12sGapError";
|
|
70
|
-
this.after = data.after;
|
|
71
|
-
this.data = data;
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
var A12sStreamError = class extends Error {
|
|
75
|
-
constructor(message, options) {
|
|
76
|
-
super(message, options);
|
|
77
|
-
this.name = "A12sStreamError";
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
var enc = encodeURIComponent;
|
|
81
|
-
var A12sClient = class {
|
|
82
|
-
url;
|
|
83
|
-
token;
|
|
84
|
-
fetchImpl;
|
|
85
|
-
shared;
|
|
86
|
-
constructor(opts = {}) {
|
|
87
|
-
this.url = (opts.url ?? "").replace(/\/+$/, "");
|
|
88
|
-
this.token = opts.token;
|
|
89
|
-
this.fetchImpl = opts.fetch ?? ((...args) => fetch(...args));
|
|
90
|
-
}
|
|
91
|
-
headers(extra = {}) {
|
|
92
|
-
return this.token ? { authorization: `Bearer ${this.token}`, ...extra } : extra;
|
|
93
|
-
}
|
|
94
|
-
/** @internal */
|
|
95
|
-
async request(method, path, body, opts = {}) {
|
|
96
|
-
const res = await this.fetchImpl(this.url + path, {
|
|
97
|
-
method,
|
|
98
|
-
headers: this.headers({ ...body === void 0 ? {} : { "content-type": "application/json" }, ...opts.headers }),
|
|
99
|
-
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
100
|
-
signal: opts.signal
|
|
101
|
-
});
|
|
102
|
-
const text = await res.text();
|
|
103
|
-
if (!res.ok) throw apiError(res.status, parseError(res.status, text));
|
|
104
|
-
return text ? JSON.parse(text) : void 0;
|
|
105
|
-
}
|
|
106
|
-
/** Every Computer and Service: phase, session, active Turn, inbox, power. */
|
|
107
|
-
status() {
|
|
108
|
-
return this.request("GET", "/v1/status");
|
|
109
|
-
}
|
|
110
|
-
async computer(name) {
|
|
111
|
-
const c = (await this.status()).agentComputers.find((x) => x.name === name);
|
|
112
|
-
if (!c) throw new NotFound(404, { code: "not_found", message: `no agent computer ${name}` });
|
|
113
|
-
return c;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Puts a Message in a Computer's inbox and returns at once. It is delivered at the Computer's next idle moment,
|
|
117
|
-
* batched with anything else pending. Works while the Computer is busy or asleep.
|
|
118
|
-
*/
|
|
119
|
-
send(computer, msg) {
|
|
120
|
-
const m = typeof msg === "string" ? { body: msg } : { ...msg };
|
|
121
|
-
m.idempotencyKey ??= globalThis.crypto.randomUUID();
|
|
122
|
-
return this.request("POST", `/v1/agent-computers/${enc(computer)}/inbox`, m);
|
|
123
|
-
}
|
|
124
|
-
/** Sends a Message, waits for the Turn that delivers it and resolves when that Turn ends: what `a12s prompt` does. */
|
|
125
|
-
async prompt(computer, body, opts = {}) {
|
|
126
|
-
const { signal, onEvent, ...msg } = opts;
|
|
127
|
-
const message = await this.send(computer, { ...msg, body });
|
|
128
|
-
const turn = await this.deliveredIn(computer, message.id, signal);
|
|
129
|
-
const builder = new TurnBuilder();
|
|
130
|
-
for await (const ev of this.events(computer, { turn, signal, onGap: "throw" })) {
|
|
131
|
-
onEvent?.(ev);
|
|
132
|
-
const done = builder.add(ev);
|
|
133
|
-
if (done && done.id === turn) return { ...done, message };
|
|
134
|
-
}
|
|
135
|
-
throw new Error(`event stream for turn ${turn} ended early`);
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Live events of one Computer, several, or (no argument) every Computer in the Fleet, merged. Each event carries
|
|
139
|
-
* its `computer`. Reconnects after a network drop without losing or repeating events. Call `await stream.ready()`
|
|
140
|
-
* before sending work you want to see from the start; `close()` (or `break`) ends it.
|
|
141
|
-
*/
|
|
142
|
-
events(computers, opts = {}) {
|
|
143
|
-
const names = typeof computers === "string" ? [computers] : computers;
|
|
144
|
-
if ((opts.turn !== void 0 || opts.after !== void 0) && names?.length !== 1) {
|
|
145
|
-
throw new TypeError("events: turn and after need exactly one agent computer");
|
|
146
|
-
}
|
|
147
|
-
return new EventStream((name, live, signal) => {
|
|
148
|
-
const q = opts.turn ? `?turn=${enc(opts.turn)}` : "";
|
|
149
|
-
const events = this.stream(name, `/v1/agent-computers/${enc(name)}/events${q}`, opts.after, { ...opts, signal, live });
|
|
150
|
-
return opts.turn ? untilTurnFinished(events) : events;
|
|
151
|
-
}, names ?? (async () => (await this.status()).agentComputers.map((c) => c.name)), opts.signal);
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* @internal Events of the named agent computers (all if none), and the Controller's own about them, read from this
|
|
155
|
-
* client's one GET /v1/events connection.
|
|
156
|
-
*/
|
|
157
|
-
sharedEvents(names, opts = {}) {
|
|
158
|
-
this.shared ??= new SharedEvents(
|
|
159
|
-
(cursor, signal) => this.fetchImpl(this.url + "/v1/events", {
|
|
160
|
-
headers: this.headers({ accept: "text/event-stream", ...cursor !== void 0 ? { "last-event-id": cursor } : {} }),
|
|
161
|
-
signal
|
|
162
|
-
})
|
|
163
|
-
);
|
|
164
|
-
const shared = this.shared;
|
|
165
|
-
const wanted2 = names && new Set(names);
|
|
166
|
-
return new EventStream(
|
|
167
|
-
(_name, live, signal) => shared.subscribe({ wanted: wanted2, live, onGap: opts.onGap, reconnectMs: opts.reconnectMs }, signal),
|
|
168
|
-
[""],
|
|
169
|
-
opts.signal
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Finished Turns of one Computer, several or all, as they end: who sent the work (`messages`), what the agent said
|
|
174
|
-
* (`text`) and how it ended (`status`). Same `ready()` and `close()` as {@link events}.
|
|
175
|
-
*/
|
|
176
|
-
turns(computers, opts = {}) {
|
|
177
|
-
return new TurnStream(this.events(computers, opts));
|
|
178
|
-
}
|
|
179
|
-
inbox(computer, opts = {}) {
|
|
180
|
-
const q = new URLSearchParams();
|
|
181
|
-
if (opts.state) q.set("state", opts.state);
|
|
182
|
-
if (opts.bodies) q.set("bodies", "true");
|
|
183
|
-
if (opts.after !== void 0) q.set("after", String(opts.after));
|
|
184
|
-
const qs = q.toString();
|
|
185
|
-
return this.request("GET", `/v1/agent-computers/${enc(computer)}/inbox${qs ? "?" + qs : ""}`);
|
|
186
|
-
}
|
|
187
|
-
message(computer, id) {
|
|
188
|
-
return this.request("GET", `/v1/agent-computers/${enc(computer)}/inbox/${enc(id)}`);
|
|
189
|
-
}
|
|
190
|
-
/** Withdraws a pending Message; a 409 A12sError names its state if it is too late. */
|
|
191
|
-
withdraw(computer, id) {
|
|
192
|
-
return this.request("DELETE", `/v1/agent-computers/${enc(computer)}/inbox/${enc(id)}`);
|
|
193
|
-
}
|
|
194
|
-
/** Interrupts a Turn (default: the active one). The Turn ends `interrupted`; nothing is replayed. */
|
|
195
|
-
async stop(computer, turn) {
|
|
196
|
-
const id = turn ?? (await this.computer(computer)).activeTurn;
|
|
197
|
-
if (id) await this.request("POST", `/v1/agent-computers/${enc(computer)}/turns/${enc(id)}/stop`, {});
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Stops the agent computer's processes and keeps its disks, session and inbox. It stays asleep until wake(); with
|
|
201
|
-
* `allowWake` its power policy (wakeOnMessage) may wake it instead.
|
|
202
|
-
*/
|
|
203
|
-
async sleep(computer, opts = {}) {
|
|
204
|
-
await this.request("POST", `/v1/agent-computers/${enc(computer)}/power`, {
|
|
205
|
-
state: "asleep",
|
|
206
|
-
...opts.allowWake ? { allowWake: true } : {}
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
/** Starts a sleeping agent computer; its session resumes on the next Turn. `clearInbox` discards pending Messages. */
|
|
210
|
-
async wake(computer, opts = {}) {
|
|
211
|
-
await this.request("POST", `/v1/agent-computers/${enc(computer)}/power`, {
|
|
212
|
-
state: "awake",
|
|
213
|
-
...opts.clearInbox ? { clearInbox: true, requestId: crypto.randomUUID() } : {}
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
/** Holds the inbox: Messages still queue, none is delivered until resume(). */
|
|
217
|
-
hold(computer, opts = {}) {
|
|
218
|
-
return this.request("POST", `/v1/agent-computers/${enc(computer)}/inbox/hold`, opts.reason ? { reason: opts.reason } : {});
|
|
219
|
-
}
|
|
220
|
-
/** Releases a hold; pending Messages are delivered at the next idle moment. */
|
|
221
|
-
resume(computer) {
|
|
222
|
-
return this.request("POST", `/v1/agent-computers/${enc(computer)}/inbox/resume`, {});
|
|
223
|
-
}
|
|
224
|
-
async services() {
|
|
225
|
-
return (await this.request("GET", "/v1/services")).services ?? [];
|
|
226
|
-
}
|
|
227
|
-
service(name) {
|
|
228
|
-
return this.request("GET", `/v1/services/${enc(name)}`);
|
|
229
|
-
}
|
|
230
|
-
/** A scoped token for another program; owner only. The secret is in the response once. */
|
|
231
|
-
createToken(req) {
|
|
232
|
-
return this.request("POST", "/v1/tokens", req);
|
|
233
|
-
}
|
|
234
|
-
/** What `a12s up` sends: validate and apply a whole Fleet. Most apps leave this to the CLI. */
|
|
235
|
-
putFleet(req) {
|
|
236
|
-
return this.request("PUT", "/v1/fleet", req);
|
|
237
|
-
}
|
|
238
|
-
/** Waits until the Message is delivered and returns the Turn that carries it. */
|
|
239
|
-
async deliveredIn(computer, id, signal) {
|
|
240
|
-
while (!signal?.aborted) {
|
|
241
|
-
for await (const ev of this.stream(computer, `/v1/agent-computers/${enc(computer)}/inbox/${enc(id)}/events`, void 0, {
|
|
242
|
-
signal,
|
|
243
|
-
once: true
|
|
244
|
-
})) {
|
|
245
|
-
const d = ev.data ?? {};
|
|
246
|
-
const type = ev.type;
|
|
247
|
-
const state = type === "snapshot" ? d.message?.state : type;
|
|
248
|
-
const turn = type === "snapshot" ? d.message?.turn : d.turn;
|
|
249
|
-
if (state === "delivered" && turn) return turn;
|
|
250
|
-
if (["withdrawn", "discarded", "expired", "replaced"].includes(state)) {
|
|
251
|
-
throw new A12sError(409, { code: state, message: `message ${id} was ${state} before delivery` });
|
|
252
|
-
}
|
|
253
|
-
if (type === "snapshot" && d.held || type === "held") {
|
|
254
|
-
throw new A12sError(409, {
|
|
255
|
-
code: "held",
|
|
256
|
-
message: `${computer}'s inbox is held (${d.holdReason ?? d.reason}); ${id} waits in the inbox`,
|
|
257
|
-
fix: `a12s inbox resume ${computer}`
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
await sleep(1e3, signal);
|
|
262
|
-
}
|
|
263
|
-
throw signal.reason ?? new Error("aborted");
|
|
264
|
-
}
|
|
265
|
-
async *stream(computer, path, after, opts) {
|
|
266
|
-
let cursor = after;
|
|
267
|
-
for (; ; ) {
|
|
268
|
-
if (opts.signal?.aborted) return;
|
|
269
|
-
let res;
|
|
270
|
-
try {
|
|
271
|
-
res = await this.fetchImpl(this.url + path, {
|
|
272
|
-
headers: this.headers({
|
|
273
|
-
accept: "text/event-stream",
|
|
274
|
-
...cursor !== void 0 ? { "last-event-id": String(cursor) } : {}
|
|
275
|
-
}),
|
|
276
|
-
signal: opts.signal
|
|
277
|
-
});
|
|
278
|
-
} catch (err) {
|
|
279
|
-
if (opts.signal?.aborted) return;
|
|
280
|
-
if (!isNetworkError(err)) throw err;
|
|
281
|
-
await sleep(opts.reconnectMs ?? 1e3, opts.signal);
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
if (!res.ok) {
|
|
285
|
-
const err = apiError(res.status, parseError(res.status, await res.text()));
|
|
286
|
-
if (res.status !== 503 && res.status !== 502) throw err;
|
|
287
|
-
opts.live?.();
|
|
288
|
-
await sleep(opts.reconnectMs ?? 1e3, opts.signal);
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
if (!res.body) throw new A12sStreamError(`event stream ${path}: response has no body`);
|
|
292
|
-
const frames = readSSE(res.body);
|
|
293
|
-
try {
|
|
294
|
-
for (; ; ) {
|
|
295
|
-
let r;
|
|
296
|
-
try {
|
|
297
|
-
r = await frames.next();
|
|
298
|
-
} catch {
|
|
299
|
-
break;
|
|
300
|
-
}
|
|
301
|
-
if (r.done) break;
|
|
302
|
-
const f = r.value;
|
|
303
|
-
if (f.event === "head") {
|
|
304
|
-
const seq = parseJSON(f.data)?.seq;
|
|
305
|
-
if (typeof seq === "number") cursor = Math.max(cursor ?? 0, seq);
|
|
306
|
-
opts.live?.();
|
|
307
|
-
continue;
|
|
308
|
-
}
|
|
309
|
-
if (f.event === "gap") {
|
|
310
|
-
const server = parseJSON(f.data);
|
|
311
|
-
const data = { ...server && typeof server === "object" ? server : {}, after: cursor };
|
|
312
|
-
cursor = void 0;
|
|
313
|
-
if (opts.onGap === "throw") throw new A12sGapError(data);
|
|
314
|
-
const gap = { v: 1, computer, session: "", turn: "", seq: 0, ts: (/* @__PURE__ */ new Date()).toISOString(), type: "gap", data };
|
|
315
|
-
yield gap;
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
if (f.event || !f.data) continue;
|
|
319
|
-
let ev;
|
|
320
|
-
try {
|
|
321
|
-
ev = JSON.parse(f.data);
|
|
322
|
-
} catch (err) {
|
|
323
|
-
throw new A12sStreamError(`malformed event after seq ${cursor ?? "(start)"}: ${f.data.slice(0, 120)}`, {
|
|
324
|
-
cause: err
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
if (f.id !== void 0) cursor = Number(f.id);
|
|
328
|
-
yield ev;
|
|
329
|
-
}
|
|
330
|
-
} finally {
|
|
331
|
-
await frames.return(void 0);
|
|
332
|
-
res.body.cancel().catch(() => {
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
if (opts.signal?.aborted) return;
|
|
336
|
-
if (opts.once) return;
|
|
337
|
-
await sleep(opts.reconnectMs ?? 1e3, opts.signal);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
var SharedEvents = class {
|
|
342
|
-
constructor(open) {
|
|
343
|
-
this.open = open;
|
|
344
|
-
}
|
|
345
|
-
subs = /* @__PURE__ */ new Set();
|
|
346
|
-
conn;
|
|
347
|
-
live = false;
|
|
348
|
-
async *subscribe(sub, signal) {
|
|
349
|
-
const queue = [];
|
|
350
|
-
let failure;
|
|
351
|
-
let wake;
|
|
352
|
-
const notify = () => {
|
|
353
|
-
const w = wake;
|
|
354
|
-
wake = void 0;
|
|
355
|
-
w?.();
|
|
356
|
-
};
|
|
357
|
-
const s = {
|
|
358
|
-
...sub,
|
|
359
|
-
push: (ev) => {
|
|
360
|
-
queue.push(ev);
|
|
361
|
-
notify();
|
|
362
|
-
},
|
|
363
|
-
fail: (error) => {
|
|
364
|
-
failure ??= { error };
|
|
365
|
-
notify();
|
|
366
|
-
}
|
|
367
|
-
};
|
|
368
|
-
signal.addEventListener("abort", notify, { once: true });
|
|
369
|
-
this.add(s);
|
|
370
|
-
try {
|
|
371
|
-
for (; ; ) {
|
|
372
|
-
if (queue.length) {
|
|
373
|
-
yield queue.shift();
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
376
|
-
if (failure) throw failure.error;
|
|
377
|
-
if (signal.aborted) return;
|
|
378
|
-
await new Promise((r) => wake = r);
|
|
379
|
-
}
|
|
380
|
-
} finally {
|
|
381
|
-
signal.removeEventListener("abort", notify);
|
|
382
|
-
this.remove(s);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
add(s) {
|
|
386
|
-
this.subs.add(s);
|
|
387
|
-
if (!this.conn) {
|
|
388
|
-
const conn = new AbortController();
|
|
389
|
-
this.conn = conn;
|
|
390
|
-
this.live = false;
|
|
391
|
-
void this.run(conn);
|
|
392
|
-
} else if (this.live) {
|
|
393
|
-
s.live();
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
remove(s) {
|
|
397
|
-
this.subs.delete(s);
|
|
398
|
-
if (this.subs.size === 0) this.stop();
|
|
399
|
-
}
|
|
400
|
-
stop() {
|
|
401
|
-
this.conn?.abort();
|
|
402
|
-
this.conn = void 0;
|
|
403
|
-
this.live = false;
|
|
404
|
-
}
|
|
405
|
-
async run(conn) {
|
|
406
|
-
const { signal } = conn;
|
|
407
|
-
let cursor;
|
|
408
|
-
try {
|
|
409
|
-
while (!signal.aborted) {
|
|
410
|
-
let res;
|
|
411
|
-
try {
|
|
412
|
-
res = await this.open(cursor, signal);
|
|
413
|
-
} catch (err) {
|
|
414
|
-
if (signal.aborted) return;
|
|
415
|
-
if (!isNetworkError(err)) throw err;
|
|
416
|
-
}
|
|
417
|
-
if (res && !res.ok) {
|
|
418
|
-
const err = apiError(res.status, parseError(res.status, await res.text()));
|
|
419
|
-
if (res.status !== 502 && res.status !== 503) throw err;
|
|
420
|
-
res = void 0;
|
|
421
|
-
}
|
|
422
|
-
if (res) {
|
|
423
|
-
if (!res.body) throw new A12sStreamError("GET /v1/events: response has no body");
|
|
424
|
-
const frames = readSSE(res.body);
|
|
425
|
-
try {
|
|
426
|
-
for (; ; ) {
|
|
427
|
-
let r;
|
|
428
|
-
try {
|
|
429
|
-
r = await frames.next();
|
|
430
|
-
} catch {
|
|
431
|
-
break;
|
|
432
|
-
}
|
|
433
|
-
if (r.done) break;
|
|
434
|
-
cursor = this.frame(r.value, cursor);
|
|
435
|
-
}
|
|
436
|
-
} finally {
|
|
437
|
-
await frames.return(void 0);
|
|
438
|
-
res.body.cancel().catch(() => {
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
await sleep(this.reconnectMs(), signal);
|
|
443
|
-
}
|
|
444
|
-
} catch (err) {
|
|
445
|
-
if (signal.aborted) return;
|
|
446
|
-
const subs = [...this.subs];
|
|
447
|
-
if (this.conn === conn) this.stop();
|
|
448
|
-
for (const s of subs) s.fail(err);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
/** Delivers one frame and returns the cursor to resume after. */
|
|
452
|
-
frame(f, cursor) {
|
|
453
|
-
if (f.event === "head") {
|
|
454
|
-
const id = parseJSON(f.data)?.id;
|
|
455
|
-
this.live = true;
|
|
456
|
-
for (const s of this.subs) s.live();
|
|
457
|
-
return typeof id === "string" && id ? id : cursor;
|
|
458
|
-
}
|
|
459
|
-
if (f.event === "gap") {
|
|
460
|
-
const data = cursor !== void 0 ? { lastEventId: cursor } : {};
|
|
461
|
-
const gap = { v: 1, computer: "", session: "", turn: "", seq: 0, ts: (/* @__PURE__ */ new Date()).toISOString(), type: "gap", data };
|
|
462
|
-
for (const s of this.subs) {
|
|
463
|
-
if (s.onGap === "throw") s.fail(new A12sGapError(data));
|
|
464
|
-
else s.push(gap);
|
|
465
|
-
}
|
|
466
|
-
return cursor;
|
|
467
|
-
}
|
|
468
|
-
if (f.event || !f.data) return cursor;
|
|
469
|
-
let ev;
|
|
470
|
-
try {
|
|
471
|
-
ev = JSON.parse(f.data);
|
|
472
|
-
} catch (err) {
|
|
473
|
-
throw new A12sStreamError(`malformed event after ${cursor ?? "(start)"}: ${f.data.slice(0, 120)}`, { cause: err });
|
|
474
|
-
}
|
|
475
|
-
for (const s of this.subs) if (wanted(s.wanted, ev)) s.push(ev);
|
|
476
|
-
return f.id ?? cursor;
|
|
477
|
-
}
|
|
478
|
-
reconnectMs() {
|
|
479
|
-
let ms = 1e3;
|
|
480
|
-
for (const s of this.subs) if (s.reconnectMs !== void 0) ms = Math.min(ms, s.reconnectMs);
|
|
481
|
-
return ms;
|
|
482
|
-
}
|
|
483
|
-
};
|
|
484
|
-
function agentComputerOf(ev) {
|
|
485
|
-
return ev.agentComputer || ev.computer;
|
|
486
|
-
}
|
|
487
|
-
function wanted(names, ev) {
|
|
488
|
-
if (!names) return true;
|
|
489
|
-
if (ev.type === "fleet.changed") return (ev.data?.names ?? []).some((n) => names.has(n));
|
|
490
|
-
if (ev.type === "service.phase") return names.has(ev.data?.service);
|
|
491
|
-
return names.has(agentComputerOf(ev));
|
|
492
|
-
}
|
|
493
|
-
async function* untilTurnFinished(events) {
|
|
494
|
-
try {
|
|
495
|
-
for await (const ev of events) {
|
|
496
|
-
yield ev;
|
|
497
|
-
if (ev.type === "turn.finished") return;
|
|
498
|
-
}
|
|
499
|
-
} finally {
|
|
500
|
-
await events.return(void 0);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
var EventStream = class {
|
|
504
|
-
constructor(source, names, signal) {
|
|
505
|
-
this.source = source;
|
|
506
|
-
this.names = names;
|
|
507
|
-
this.readyP = new Promise((resolve, reject) => {
|
|
508
|
-
this.resolveReady = () => {
|
|
509
|
-
this.isReady = true;
|
|
510
|
-
resolve();
|
|
511
|
-
};
|
|
512
|
-
this.rejectReady = reject;
|
|
513
|
-
});
|
|
514
|
-
this.readyP.catch(() => {
|
|
515
|
-
});
|
|
516
|
-
if (signal?.aborted) this.ac.abort();
|
|
517
|
-
signal?.addEventListener("abort", () => this.close(), { once: true });
|
|
518
|
-
}
|
|
519
|
-
ac = new AbortController();
|
|
520
|
-
queue = [];
|
|
521
|
-
started;
|
|
522
|
-
remaining;
|
|
523
|
-
failure;
|
|
524
|
-
wake;
|
|
525
|
-
readyP;
|
|
526
|
-
resolveReady;
|
|
527
|
-
rejectReady;
|
|
528
|
-
isReady = false;
|
|
529
|
-
/**
|
|
530
|
-
* Resolves once every stream is connected and live (a sleeping Computer counts: its events start when it wakes).
|
|
531
|
-
* Events that happen after this are never missed.
|
|
532
|
-
*/
|
|
533
|
-
ready() {
|
|
534
|
-
this.start();
|
|
535
|
-
return this.readyP;
|
|
536
|
-
}
|
|
537
|
-
close() {
|
|
538
|
-
this.ac.abort();
|
|
539
|
-
this.resolveReady();
|
|
540
|
-
this.notify();
|
|
541
|
-
}
|
|
542
|
-
async next() {
|
|
543
|
-
this.start();
|
|
544
|
-
for (; ; ) {
|
|
545
|
-
if (this.queue.length) return { value: this.queue.shift(), done: false };
|
|
546
|
-
if (this.failure) {
|
|
547
|
-
const { error } = this.failure;
|
|
548
|
-
this.failure = void 0;
|
|
549
|
-
this.close();
|
|
550
|
-
throw error;
|
|
551
|
-
}
|
|
552
|
-
if (this.ac.signal.aborted || this.remaining === 0) return { value: void 0, done: true };
|
|
553
|
-
await new Promise((r) => this.wake = r);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
async return() {
|
|
557
|
-
this.close();
|
|
558
|
-
return { value: void 0, done: true };
|
|
559
|
-
}
|
|
560
|
-
async throw(err) {
|
|
561
|
-
this.close();
|
|
562
|
-
throw err;
|
|
563
|
-
}
|
|
564
|
-
[Symbol.asyncIterator]() {
|
|
565
|
-
return this;
|
|
566
|
-
}
|
|
567
|
-
start() {
|
|
568
|
-
this.started ??= (async () => {
|
|
569
|
-
const names = typeof this.names === "function" ? await this.names() : this.names;
|
|
570
|
-
this.remaining = names.length;
|
|
571
|
-
let live = 0;
|
|
572
|
-
if (names.length === 0) this.resolveReady();
|
|
573
|
-
for (const name of names) {
|
|
574
|
-
let counted = false;
|
|
575
|
-
const onLive = () => {
|
|
576
|
-
if (counted) return;
|
|
577
|
-
counted = true;
|
|
578
|
-
if (++live === names.length) this.resolveReady();
|
|
579
|
-
};
|
|
580
|
-
void this.pump(name, onLive);
|
|
581
|
-
}
|
|
582
|
-
this.notify();
|
|
583
|
-
})().catch((err) => this.fail(err));
|
|
584
|
-
}
|
|
585
|
-
async pump(name, onLive) {
|
|
586
|
-
try {
|
|
587
|
-
for await (const ev of this.source(name, onLive, this.ac.signal)) {
|
|
588
|
-
this.queue.push(ev);
|
|
589
|
-
this.notify();
|
|
590
|
-
}
|
|
591
|
-
} catch (err) {
|
|
592
|
-
if (!this.ac.signal.aborted) this.fail(err);
|
|
593
|
-
} finally {
|
|
594
|
-
onLive();
|
|
595
|
-
this.remaining = (this.remaining ?? 1) - 1;
|
|
596
|
-
this.notify();
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
fail(err) {
|
|
600
|
-
this.failure ??= { error: err };
|
|
601
|
-
if (!this.isReady) this.rejectReady(err);
|
|
602
|
-
this.notify();
|
|
603
|
-
}
|
|
604
|
-
notify() {
|
|
605
|
-
const w = this.wake;
|
|
606
|
-
this.wake = void 0;
|
|
607
|
-
w?.();
|
|
608
|
-
}
|
|
609
|
-
};
|
|
610
|
-
var TurnStream = class {
|
|
611
|
-
constructor(events) {
|
|
612
|
-
this.events = events;
|
|
613
|
-
}
|
|
614
|
-
builder = new TurnBuilder();
|
|
615
|
-
/** See {@link EventStream.ready}. */
|
|
616
|
-
ready() {
|
|
617
|
-
return this.events.ready();
|
|
618
|
-
}
|
|
619
|
-
close() {
|
|
620
|
-
this.events.close();
|
|
621
|
-
}
|
|
622
|
-
async next() {
|
|
623
|
-
for (; ; ) {
|
|
624
|
-
const r = await this.events.next();
|
|
625
|
-
if (r.done) return { value: void 0, done: true };
|
|
626
|
-
const turn = this.builder.add(r.value);
|
|
627
|
-
if (turn) return { value: turn, done: false };
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
async return() {
|
|
631
|
-
this.close();
|
|
632
|
-
return { value: void 0, done: true };
|
|
633
|
-
}
|
|
634
|
-
[Symbol.asyncIterator]() {
|
|
635
|
-
return this;
|
|
636
|
-
}
|
|
637
|
-
};
|
|
638
|
-
var TurnBuilder = class {
|
|
639
|
-
open = /* @__PURE__ */ new Map();
|
|
640
|
-
add(ev) {
|
|
641
|
-
if (!ev.turn) return void 0;
|
|
642
|
-
const name = agentComputerOf(ev);
|
|
643
|
-
const key = `${name}\0${ev.turn}`;
|
|
644
|
-
let t = this.open.get(key);
|
|
645
|
-
if (!t) this.open.set(key, t = { text: "", afterTool: false, messages: [], tools: /* @__PURE__ */ new Map() });
|
|
646
|
-
switch (ev.type) {
|
|
647
|
-
case "turn.started":
|
|
648
|
-
t.messages = ev.data.inbox?.messages ?? [];
|
|
649
|
-
t.startedAt = ev.ts;
|
|
650
|
-
return void 0;
|
|
651
|
-
case "tool.started":
|
|
652
|
-
t.afterTool = true;
|
|
653
|
-
t.tools.set(ev.data.id, { id: ev.data.id, name: ev.data.name, input: ev.data.input, isError: false });
|
|
654
|
-
return void 0;
|
|
655
|
-
case "tool.finished": {
|
|
656
|
-
const started = t.tools.get(ev.data.id);
|
|
657
|
-
t.tools.set(ev.data.id, { id: ev.data.id, name: ev.data.name, input: started?.input, output: ev.data.output, isError: ev.data.isError });
|
|
658
|
-
return void 0;
|
|
659
|
-
}
|
|
660
|
-
case "text.delta":
|
|
661
|
-
if (t.afterTool && t.text && !t.text.endsWith("\n")) t.text += "\n";
|
|
662
|
-
t.afterTool = false;
|
|
663
|
-
t.text += ev.data.text;
|
|
664
|
-
return void 0;
|
|
665
|
-
case "turn.finished":
|
|
666
|
-
this.open.delete(key);
|
|
667
|
-
return {
|
|
668
|
-
agentComputer: name,
|
|
669
|
-
computer: name,
|
|
670
|
-
id: ev.turn,
|
|
671
|
-
session: ev.session,
|
|
672
|
-
status: ev.data.status,
|
|
673
|
-
text: t.text,
|
|
674
|
-
...ev.data.error ? { error: ev.data.error } : {},
|
|
675
|
-
messages: t.messages,
|
|
676
|
-
tools: [...t.tools.values()],
|
|
677
|
-
usage: ev.data.usage,
|
|
678
|
-
...ev.data.tokens ? { tokens: ev.data.tokens } : {},
|
|
679
|
-
...t.startedAt ? { startedAt: t.startedAt } : {},
|
|
680
|
-
finishedAt: ev.ts
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
return void 0;
|
|
684
|
-
}
|
|
685
|
-
};
|
|
686
|
-
function isNetworkError(err) {
|
|
687
|
-
return err instanceof TypeError || err instanceof Error && err.name === "AbortError";
|
|
688
|
-
}
|
|
689
|
-
function parseJSON(text) {
|
|
690
|
-
try {
|
|
691
|
-
return text ? JSON.parse(text) : void 0;
|
|
692
|
-
} catch {
|
|
693
|
-
return text;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
function parseError(status, text) {
|
|
697
|
-
try {
|
|
698
|
-
const b = JSON.parse(text);
|
|
699
|
-
if (b && typeof b.code === "string") return b;
|
|
700
|
-
} catch {
|
|
701
|
-
}
|
|
702
|
-
return { code: `http_${status}`, message: text.trim() || `HTTP ${status}` };
|
|
703
|
-
}
|
|
704
|
-
function sleep(ms, signal) {
|
|
705
|
-
return new Promise((resolve) => {
|
|
706
|
-
const t = setTimeout(resolve, ms);
|
|
707
|
-
signal?.addEventListener(
|
|
708
|
-
"abort",
|
|
709
|
-
() => {
|
|
710
|
-
clearTimeout(t);
|
|
711
|
-
resolve();
|
|
712
|
-
},
|
|
713
|
-
{ once: true }
|
|
714
|
-
);
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
// src/fleet.ts
|
|
719
|
-
var enc2 = encodeURIComponent;
|
|
720
|
-
var maxReplaceTries = 5;
|
|
721
|
-
var Fleet = class _Fleet {
|
|
722
|
-
/** The lower-level client the handles use, for API calls that have no method here. */
|
|
723
|
-
client;
|
|
724
|
-
agentComputers;
|
|
725
|
-
services;
|
|
726
|
-
tokens;
|
|
727
|
-
credentials;
|
|
728
|
-
/** No discovery: `url` and `token` as given, by default the page's own origin. */
|
|
729
|
-
constructor(opts = {}) {
|
|
730
|
-
this.client = new A12sClient(opts);
|
|
731
|
-
this.agentComputers = new AgentComputers(this.client);
|
|
732
|
-
this.services = new Services(this.client);
|
|
733
|
-
this.tokens = new Tokens(this.client);
|
|
734
|
-
this.credentials = new Credentials(this.client);
|
|
735
|
-
}
|
|
736
|
-
/**
|
|
737
|
-
* In a browser: the Fleet behind the page's own origin, which is `a12s gateway` serving the page (no token
|
|
738
|
-
* needed). In Node: found like {@link connect} finds it. Pass `url` and `token` to reach anything else.
|
|
739
|
-
*/
|
|
740
|
-
static async connect(opts = {}) {
|
|
741
|
-
return new _Fleet(opts);
|
|
742
|
-
}
|
|
743
|
-
get url() {
|
|
744
|
-
return this.client.url;
|
|
745
|
-
}
|
|
746
|
-
async get() {
|
|
747
|
-
const r = await this.client.request("GET", "/v1/fleet");
|
|
748
|
-
return { version: r.version, spec: r.fleet ?? emptySpec(), drift: r.drift ?? [] };
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* Reads the Fleet, lets `fn` change it (in place, or by returning a new spec) and writes it back only if nobody
|
|
752
|
-
* wrote in between; otherwise it reads again and retries, up to 5 times. `spec.defaults` is always present.
|
|
753
|
-
*/
|
|
754
|
-
async replace(fn) {
|
|
755
|
-
for (let attempt = 1; ; attempt++) {
|
|
756
|
-
const cur = await this.get();
|
|
757
|
-
cur.spec.defaults ??= {};
|
|
758
|
-
const next = await fn(cur.spec) ?? cur.spec;
|
|
759
|
-
try {
|
|
760
|
-
warnWrites(
|
|
761
|
-
await this.client.request("PUT", "/v1/fleet", { fleet: next }, {
|
|
762
|
-
headers: { "if-match": `"${cur.version}"` }
|
|
763
|
-
})
|
|
764
|
-
);
|
|
765
|
-
} catch (err) {
|
|
766
|
-
if (attempt < maxReplaceTries && err instanceof A12sError && err.code === "version_mismatch") continue;
|
|
767
|
-
throw err;
|
|
768
|
-
}
|
|
769
|
-
return this.get();
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
/**
|
|
773
|
-
* Live events of the named agent computers, or (no argument) all of them, and the Fleet's own (`fleet.changed`,
|
|
774
|
-
* `agent_computer.phase`). Every stream of this Fleet shares one connection, which reconnects without losing
|
|
775
|
-
* events. `await stream.ready()` before sending work you want to see from the start; `close()` (or `break`) ends it.
|
|
776
|
-
* `turn` or `after` (one agent computer) replays from its journal instead.
|
|
777
|
-
*/
|
|
778
|
-
events(agentComputers, opts = {}) {
|
|
779
|
-
if (opts.turn !== void 0 || opts.after !== void 0) return this.client.events(agentComputers, opts);
|
|
780
|
-
return this.client.sharedEvents(namesOf(agentComputers), opts);
|
|
781
|
-
}
|
|
782
|
-
/** Finished Turns of the named agent computers, or all of them, as they end. */
|
|
783
|
-
turns(agentComputers, opts = {}) {
|
|
784
|
-
return new TurnStream(this.client.sharedEvents(namesOf(agentComputers), opts));
|
|
785
|
-
}
|
|
786
|
-
/** Every agent computer and Service: phase, session, active Turn, inbox, power. */
|
|
787
|
-
status() {
|
|
788
|
-
return this.client.status();
|
|
789
|
-
}
|
|
790
|
-
};
|
|
791
|
-
function namesOf(names) {
|
|
792
|
-
return typeof names === "string" ? [names] : names;
|
|
793
|
-
}
|
|
794
|
-
function emptySpec() {
|
|
795
|
-
return { apiVersion: "a12s.ai/v1alpha1", kind: "Fleet", metadata: { name: "" }, agent_computers: {} };
|
|
796
|
-
}
|
|
797
|
-
function writeHeaders(opts) {
|
|
798
|
-
return opts.replace ? {} : { "if-none-match": "*" };
|
|
799
|
-
}
|
|
800
|
-
var AgentComputers = class {
|
|
801
|
-
constructor(client) {
|
|
802
|
-
this.client = client;
|
|
803
|
-
}
|
|
804
|
-
/** Adds an agent computer to the Fleet and starts it. Fails with code `exists` if the name is taken. */
|
|
805
|
-
async create(name, spec = {}) {
|
|
806
|
-
const { replace, ...body } = spec;
|
|
807
|
-
const r = await this.client.request("PUT", `/v1/agent-computers/${enc2(name)}`, body, {
|
|
808
|
-
headers: writeHeaders({ replace })
|
|
809
|
-
});
|
|
810
|
-
warnWrites(r);
|
|
811
|
-
return r.agentComputer ? new AgentComputer(this.client, r.agentComputer) : this.get(name);
|
|
812
|
-
}
|
|
813
|
-
/** Throws {@link NotFound} if there is none. */
|
|
814
|
-
async get(name) {
|
|
815
|
-
return new AgentComputer(this.client, await this.client.request("GET", `/v1/agent-computers/${enc2(name)}`));
|
|
816
|
-
}
|
|
817
|
-
async list() {
|
|
818
|
-
const r = await this.client.request("GET", "/v1/agent-computers");
|
|
819
|
-
return (r.agentComputers ?? []).map((d) => new AgentComputer(this.client, d));
|
|
820
|
-
}
|
|
821
|
-
};
|
|
822
|
-
var AgentComputer = class {
|
|
823
|
-
constructor(client, data) {
|
|
824
|
-
this.client = client;
|
|
825
|
-
this.name = data.name;
|
|
826
|
-
this.set(data);
|
|
827
|
-
}
|
|
828
|
-
name;
|
|
829
|
-
spec;
|
|
830
|
-
/** The spec with the Fleet's defaults merged in. */
|
|
831
|
-
effective;
|
|
832
|
-
status;
|
|
833
|
-
set(d) {
|
|
834
|
-
this.spec = d.spec;
|
|
835
|
-
this.effective = d.effective;
|
|
836
|
-
this.status = d.status;
|
|
837
|
-
return this;
|
|
838
|
-
}
|
|
839
|
-
get path() {
|
|
840
|
-
return `/v1/agent-computers/${enc2(this.name)}`;
|
|
841
|
-
}
|
|
842
|
-
async reload() {
|
|
843
|
-
return this.set(await this.client.request("GET", this.path));
|
|
844
|
-
}
|
|
845
|
-
/** Changes the spec: a key set to null goes back to the Fleet's default. A busy one picks it up when idle. */
|
|
846
|
-
async update(changes) {
|
|
847
|
-
const r = await this.client.request("PATCH", this.path, changes);
|
|
848
|
-
warnWrites(r);
|
|
849
|
-
return r.agentComputer ? this.set(r.agentComputer) : this.reload();
|
|
850
|
-
}
|
|
851
|
-
/** Removes it from the Fleet and stops it; its drives are kept. */
|
|
852
|
-
async remove() {
|
|
853
|
-
await this.client.request("DELETE", this.path);
|
|
854
|
-
}
|
|
855
|
-
/** Sends a Message, waits for the Turn that delivers it and resolves when that Turn ends. */
|
|
856
|
-
prompt(text, opts = {}) {
|
|
857
|
-
return this.client.prompt(this.name, text, opts);
|
|
858
|
-
}
|
|
859
|
-
/** Puts a Message in the inbox and returns at once; it is delivered at the next idle moment. */
|
|
860
|
-
send(text, opts = {}) {
|
|
861
|
-
return this.client.send(this.name, { ...opts, body: text });
|
|
862
|
-
}
|
|
863
|
-
/** Its events, live, from the Fleet's shared connection; `turn` replays one Turn from its start instead. */
|
|
864
|
-
events(opts = {}) {
|
|
865
|
-
if (opts.turn !== void 0 || opts.after !== void 0) return this.client.events(this.name, opts);
|
|
866
|
-
return this.client.sharedEvents([this.name], opts);
|
|
867
|
-
}
|
|
868
|
-
turns(opts = {}) {
|
|
869
|
-
const { limit, ...rest } = opts;
|
|
870
|
-
if (limit !== void 0) return this.pastTurns(limit);
|
|
871
|
-
return new TurnStream(this.client.sharedEvents([this.name], rest));
|
|
872
|
-
}
|
|
873
|
-
async pastTurns(limit) {
|
|
874
|
-
const r = await this.client.request("GET", `${this.path}/turns?limit=${limit}`);
|
|
875
|
-
return (r.turns ?? []).map((t) => ({
|
|
876
|
-
...t,
|
|
877
|
-
computer: t.agentComputer,
|
|
878
|
-
tools: t.tools.map((tc) => ({ ...tc, isError: tc.isError ?? false }))
|
|
879
|
-
}));
|
|
880
|
-
}
|
|
881
|
-
/** Stops its processes and keeps its disks, session and inbox until wake(); `allowWake` lets wakeOnMessage wake it. */
|
|
882
|
-
sleep(opts = {}) {
|
|
883
|
-
return this.client.sleep(this.name, opts);
|
|
884
|
-
}
|
|
885
|
-
/** Wakes it; `clearInbox` discards the pending Messages first. */
|
|
886
|
-
wake(opts = {}) {
|
|
887
|
-
return this.client.wake(this.name, opts);
|
|
888
|
-
}
|
|
889
|
-
/** Holds its inbox: Messages still queue, none is delivered until resume(). */
|
|
890
|
-
hold(opts = {}) {
|
|
891
|
-
return this.client.hold(this.name, opts);
|
|
892
|
-
}
|
|
893
|
-
/** Releases a hold on its inbox. */
|
|
894
|
-
resume() {
|
|
895
|
-
return this.client.resume(this.name);
|
|
896
|
-
}
|
|
897
|
-
/** Interrupts a Turn (default: the active one). */
|
|
898
|
-
stop(turn) {
|
|
899
|
-
return this.client.stop(this.name, turn);
|
|
900
|
-
}
|
|
901
|
-
inbox(opts = {}) {
|
|
902
|
-
return this.client.inbox(this.name, opts);
|
|
903
|
-
}
|
|
904
|
-
message(id) {
|
|
905
|
-
return this.client.message(this.name, id);
|
|
906
|
-
}
|
|
907
|
-
/** Withdraws a pending Message; a 409 A12sError names its state if it is too late. */
|
|
908
|
-
withdraw(id) {
|
|
909
|
-
return this.client.withdraw(this.name, id);
|
|
910
|
-
}
|
|
911
|
-
};
|
|
912
|
-
var Services = class {
|
|
913
|
-
constructor(client) {
|
|
914
|
-
this.client = client;
|
|
915
|
-
}
|
|
916
|
-
/** Adds a Service to the Fleet. Fails with code `exists` if the name is taken. */
|
|
917
|
-
async create(name, spec = {}) {
|
|
918
|
-
const { replace, ...body } = spec;
|
|
919
|
-
const r = await this.client.request("PUT", `/v1/services/${enc2(name)}`, body, {
|
|
920
|
-
headers: writeHeaders({ replace })
|
|
921
|
-
});
|
|
922
|
-
warnWrites(r);
|
|
923
|
-
return r.service ? new Service(this.client, r.service) : this.get(name);
|
|
924
|
-
}
|
|
925
|
-
/** Throws {@link NotFound} if there is none. */
|
|
926
|
-
async get(name) {
|
|
927
|
-
return new Service(this.client, await this.client.service(name));
|
|
928
|
-
}
|
|
929
|
-
async list() {
|
|
930
|
-
return (await this.client.services()).map((s) => new Service(this.client, s));
|
|
931
|
-
}
|
|
932
|
-
};
|
|
933
|
-
var Service = class {
|
|
934
|
-
constructor(client, status) {
|
|
935
|
-
this.client = client;
|
|
936
|
-
this.name = status.name;
|
|
937
|
-
this.status = status;
|
|
938
|
-
}
|
|
939
|
-
name;
|
|
940
|
-
status;
|
|
941
|
-
/** As written in the Fleet. */
|
|
942
|
-
get spec() {
|
|
943
|
-
return this.status.spec ?? {};
|
|
944
|
-
}
|
|
945
|
-
get path() {
|
|
946
|
-
return `/v1/services/${enc2(this.name)}`;
|
|
947
|
-
}
|
|
948
|
-
async reload() {
|
|
949
|
-
this.status = await this.client.service(this.name);
|
|
950
|
-
return this;
|
|
951
|
-
}
|
|
952
|
-
/** Changes the spec as a JSON merge patch: a key set to null is removed. */
|
|
953
|
-
async update(changes) {
|
|
954
|
-
const r = await this.client.request("PATCH", this.path, changes);
|
|
955
|
-
warnWrites(r);
|
|
956
|
-
if (!r.service) return this.reload();
|
|
957
|
-
this.status = r.service;
|
|
958
|
-
return this;
|
|
959
|
-
}
|
|
960
|
-
/** Removes it from the Fleet and stops it; its state drive is kept. */
|
|
961
|
-
async remove() {
|
|
962
|
-
await this.client.request("DELETE", this.path);
|
|
963
|
-
}
|
|
964
|
-
sleep() {
|
|
965
|
-
return this.power("asleep");
|
|
966
|
-
}
|
|
967
|
-
wake() {
|
|
968
|
-
return this.power("awake");
|
|
969
|
-
}
|
|
970
|
-
async power(state) {
|
|
971
|
-
this.status = await this.client.request("POST", `${this.path}/power`, { state });
|
|
972
|
-
return this;
|
|
973
|
-
}
|
|
974
|
-
};
|
|
975
|
-
var Tokens = class {
|
|
976
|
-
constructor(client) {
|
|
977
|
-
this.client = client;
|
|
978
|
-
}
|
|
979
|
-
/**
|
|
980
|
-
* A token for another program; owner only. The secret is in the result once. With `prefix` it may do everything
|
|
981
|
-
* the owner can for names that start with it, and nothing outside; otherwise `actions` on `agentComputers`.
|
|
982
|
-
*/
|
|
983
|
-
create(name, opts) {
|
|
984
|
-
if ("prefix" in opts) {
|
|
985
|
-
return this.client.createToken({ name, prefix: opts.prefix });
|
|
986
|
-
}
|
|
987
|
-
return this.client.createToken({ name, actions: opts.actions, computers: opts.agentComputers ?? ["*"] });
|
|
988
|
-
}
|
|
989
|
-
async list() {
|
|
990
|
-
return (await this.client.request("GET", "/v1/tokens")).tokens ?? [];
|
|
991
|
-
}
|
|
992
|
-
async remove(name) {
|
|
993
|
-
await this.client.request("DELETE", `/v1/tokens/${enc2(name)}`);
|
|
994
|
-
}
|
|
995
|
-
};
|
|
996
|
-
var Credentials = class {
|
|
997
|
-
constructor(client) {
|
|
998
|
-
this.client = client;
|
|
999
|
-
}
|
|
1000
|
-
/** Stores the value on the Controller; agent computers that use it get the new value at once. */
|
|
1001
|
-
set(name, value) {
|
|
1002
|
-
return this.client.request("PUT", `/v1/credentials/${enc2(name)}`, { value });
|
|
1003
|
-
}
|
|
1004
|
-
async list() {
|
|
1005
|
-
return (await this.client.request("GET", "/v1/credentials")).credentials ?? [];
|
|
1006
|
-
}
|
|
1007
|
-
/** Fails with A12sError code `in_use` while an agent computer or Service uses it. */
|
|
1008
|
-
async remove(name) {
|
|
1009
|
-
await this.client.request("DELETE", `/v1/credentials/${enc2(name)}`);
|
|
1010
|
-
}
|
|
1011
|
-
};
|
|
1012
|
-
function warnWrites(r) {
|
|
1013
|
-
for (const w of r?.warnings ?? []) console.warn(`a12s: ${w}`);
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
// src/index.ts
|
|
1017
|
-
async function connect(opts = {}) {
|
|
1018
|
-
return new A12sClient(opts);
|
|
1019
|
-
}
|
|
1020
|
-
export {
|
|
1021
|
-
A12sClient,
|
|
1022
|
-
A12sError,
|
|
1023
|
-
A12sGapError,
|
|
1024
|
-
A12sStreamError,
|
|
1025
|
-
AgentComputer,
|
|
1026
|
-
AgentComputers,
|
|
1027
|
-
Credentials,
|
|
1028
|
-
EventStream,
|
|
1029
|
-
Fleet,
|
|
1030
|
-
NotFound,
|
|
1031
|
-
Service,
|
|
1032
|
-
Services,
|
|
1033
|
-
Tokens,
|
|
1034
|
-
TurnStream,
|
|
1035
|
-
connect
|
|
1036
|
-
};
|