@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 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
- async request(method, path, body, signal) {
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 new A12sError(res.status, parseError(res.status, text));
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()).computers.find((x) => x.name === name);
101
- if (!c) throw new A12sError(404, { code: "not_found", message: `no 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 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()).computers.map((c) => c.name)), opts.signal);
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 = new A12sError(res.status, parseError(res.status, await res.text()));
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 key = `${ev.computer}\0${ev.turn}`;
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
- computer: ev.computer,
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
  };
@@ -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
- private request;
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}.