@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
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// Runs in Node, Deno, Bun and browsers: no node: imports here (`npm run check:browser` enforces it).
|
|
2
|
+
import { A12sClient, A12sError, TurnStream, } from "./client.js";
|
|
3
|
+
const enc = encodeURIComponent;
|
|
4
|
+
const maxReplaceTries = 5;
|
|
5
|
+
/**
|
|
6
|
+
* A running Fleet. Its agent computers, Services and tokens are in {@link agentComputers}, {@link services} and
|
|
7
|
+
* {@link tokens}.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const fleet = await Fleet.connect();
|
|
11
|
+
* const hello = await fleet.agentComputers.create("hello", { instructions: "Be helpful" });
|
|
12
|
+
* await hello.prompt("hi");
|
|
13
|
+
* await hello.remove();
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export class Fleet {
|
|
17
|
+
/** The lower-level client the handles use, for API calls that have no method here. */
|
|
18
|
+
client;
|
|
19
|
+
agentComputers;
|
|
20
|
+
services;
|
|
21
|
+
tokens;
|
|
22
|
+
credentials;
|
|
23
|
+
/** No discovery: `url` and `token` as given, by default the page's own origin. */
|
|
24
|
+
constructor(opts = {}) {
|
|
25
|
+
this.client = new A12sClient(opts);
|
|
26
|
+
this.agentComputers = new AgentComputers(this.client);
|
|
27
|
+
this.services = new Services(this.client);
|
|
28
|
+
this.tokens = new Tokens(this.client);
|
|
29
|
+
this.credentials = new Credentials(this.client);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* In a browser: the Fleet behind the page's own origin, which is `a12s gateway` serving the page (no token
|
|
33
|
+
* needed). In Node: found like {@link connect} finds it. Pass `url` and `token` to reach anything else.
|
|
34
|
+
*/
|
|
35
|
+
static async connect(opts = {}) {
|
|
36
|
+
return new Fleet(opts);
|
|
37
|
+
}
|
|
38
|
+
get url() {
|
|
39
|
+
return this.client.url;
|
|
40
|
+
}
|
|
41
|
+
async get() {
|
|
42
|
+
const r = await this.client.request("GET", "/v1/fleet");
|
|
43
|
+
return { version: r.version, spec: r.fleet ?? emptySpec(), drift: r.drift ?? [] };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Reads the Fleet, lets `fn` change it (in place, or by returning a new spec) and writes it back only if nobody
|
|
47
|
+
* wrote in between; otherwise it reads again and retries, up to 5 times. `spec.defaults` is always present.
|
|
48
|
+
*/
|
|
49
|
+
async replace(fn) {
|
|
50
|
+
for (let attempt = 1;; attempt++) {
|
|
51
|
+
const cur = await this.get();
|
|
52
|
+
cur.spec.defaults ??= {};
|
|
53
|
+
const next = (await fn(cur.spec)) ?? cur.spec;
|
|
54
|
+
try {
|
|
55
|
+
await this.client.request("PUT", "/v1/fleet", { fleet: next }, {
|
|
56
|
+
headers: { "if-match": `"${cur.version}"` },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
if (attempt < maxReplaceTries && err instanceof A12sError && err.code === "version_mismatch")
|
|
61
|
+
continue;
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
return this.get();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Live events of the named agent computers, or (no argument) all of them, and the Fleet's own (`fleet.changed`,
|
|
69
|
+
* `agent_computer.phase`). Every stream of this Fleet shares one connection, which reconnects without losing
|
|
70
|
+
* events. `await stream.ready()` before sending work you want to see from the start; `close()` (or `break`) ends it.
|
|
71
|
+
* `turn` or `after` (one agent computer) replays from its journal instead.
|
|
72
|
+
*/
|
|
73
|
+
events(agentComputers, opts = {}) {
|
|
74
|
+
if (opts.turn !== undefined || opts.after !== undefined)
|
|
75
|
+
return this.client.events(agentComputers, opts);
|
|
76
|
+
return this.client.sharedEvents(namesOf(agentComputers), opts);
|
|
77
|
+
}
|
|
78
|
+
/** Finished Turns of the named agent computers, or all of them, as they end. */
|
|
79
|
+
turns(agentComputers, opts = {}) {
|
|
80
|
+
return new TurnStream(this.client.sharedEvents(namesOf(agentComputers), opts));
|
|
81
|
+
}
|
|
82
|
+
/** Every agent computer and Service: phase, session, active Turn, inbox, power. */
|
|
83
|
+
status() {
|
|
84
|
+
return this.client.status();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function namesOf(names) {
|
|
88
|
+
return typeof names === "string" ? [names] : names;
|
|
89
|
+
}
|
|
90
|
+
function emptySpec() {
|
|
91
|
+
return { apiVersion: "a12s.ai/v1alpha1", kind: "Fleet", metadata: { name: "" }, agent_computers: {} };
|
|
92
|
+
}
|
|
93
|
+
function writeHeaders(opts) {
|
|
94
|
+
return opts.replace ? {} : { "if-none-match": "*" };
|
|
95
|
+
}
|
|
96
|
+
export class AgentComputers {
|
|
97
|
+
client;
|
|
98
|
+
constructor(client) {
|
|
99
|
+
this.client = client;
|
|
100
|
+
}
|
|
101
|
+
/** Adds an agent computer to the Fleet and starts it. Fails with code `exists` if the name is taken. */
|
|
102
|
+
async create(name, spec = {}) {
|
|
103
|
+
const { replace, ...body } = spec;
|
|
104
|
+
const r = await this.client.request("PUT", `/v1/agent-computers/${enc(name)}`, body, {
|
|
105
|
+
headers: writeHeaders({ replace }),
|
|
106
|
+
});
|
|
107
|
+
return r.agentComputer ? new AgentComputer(this.client, r.agentComputer) : this.get(name);
|
|
108
|
+
}
|
|
109
|
+
/** Throws {@link NotFound} if there is none. */
|
|
110
|
+
async get(name) {
|
|
111
|
+
return new AgentComputer(this.client, await this.client.request("GET", `/v1/agent-computers/${enc(name)}`));
|
|
112
|
+
}
|
|
113
|
+
async list() {
|
|
114
|
+
const r = await this.client.request("GET", "/v1/agent-computers");
|
|
115
|
+
return (r.agentComputers ?? []).map((d) => new AgentComputer(this.client, d));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** A handle on one agent computer. `spec`, `effective` and `status` are as of the last create, get, reload or update. */
|
|
119
|
+
export class AgentComputer {
|
|
120
|
+
client;
|
|
121
|
+
name;
|
|
122
|
+
spec;
|
|
123
|
+
/** The spec with the Fleet's defaults merged in. */
|
|
124
|
+
effective;
|
|
125
|
+
status;
|
|
126
|
+
constructor(client, data) {
|
|
127
|
+
this.client = client;
|
|
128
|
+
this.name = data.name;
|
|
129
|
+
this.set(data);
|
|
130
|
+
}
|
|
131
|
+
set(d) {
|
|
132
|
+
this.spec = d.spec;
|
|
133
|
+
this.effective = d.effective;
|
|
134
|
+
this.status = d.status;
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
get path() {
|
|
138
|
+
return `/v1/agent-computers/${enc(this.name)}`;
|
|
139
|
+
}
|
|
140
|
+
async reload() {
|
|
141
|
+
return this.set(await this.client.request("GET", this.path));
|
|
142
|
+
}
|
|
143
|
+
/** Changes the spec: a key set to null goes back to the Fleet's default. A busy one picks it up when idle. */
|
|
144
|
+
async update(changes) {
|
|
145
|
+
const r = await this.client.request("PATCH", this.path, changes);
|
|
146
|
+
return r.agentComputer ? this.set(r.agentComputer) : this.reload();
|
|
147
|
+
}
|
|
148
|
+
/** Removes it from the Fleet and stops it; its drives are kept. */
|
|
149
|
+
async remove() {
|
|
150
|
+
await this.client.request("DELETE", this.path);
|
|
151
|
+
}
|
|
152
|
+
/** Sends a Message, waits for the Turn that delivers it and resolves when that Turn ends. */
|
|
153
|
+
prompt(text, opts = {}) {
|
|
154
|
+
return this.client.prompt(this.name, text, opts);
|
|
155
|
+
}
|
|
156
|
+
/** Puts a Message in the inbox and returns at once; it is delivered at the next idle moment. */
|
|
157
|
+
send(text, opts = {}) {
|
|
158
|
+
return this.client.send(this.name, { ...opts, body: text });
|
|
159
|
+
}
|
|
160
|
+
/** Its events, live, from the Fleet's shared connection; `turn` replays one Turn from its start instead. */
|
|
161
|
+
events(opts = {}) {
|
|
162
|
+
if (opts.turn !== undefined || opts.after !== undefined)
|
|
163
|
+
return this.client.events(this.name, opts);
|
|
164
|
+
return this.client.sharedEvents([this.name], opts);
|
|
165
|
+
}
|
|
166
|
+
turns(opts = {}) {
|
|
167
|
+
const { limit, ...rest } = opts;
|
|
168
|
+
if (limit !== undefined)
|
|
169
|
+
return this.pastTurns(limit);
|
|
170
|
+
return new TurnStream(this.client.sharedEvents([this.name], rest));
|
|
171
|
+
}
|
|
172
|
+
async pastTurns(limit) {
|
|
173
|
+
const r = await this.client.request("GET", `${this.path}/turns?limit=${limit}`);
|
|
174
|
+
return (r.turns ?? []).map((t) => ({
|
|
175
|
+
...t,
|
|
176
|
+
computer: t.agentComputer,
|
|
177
|
+
tools: t.tools.map((tc) => ({ ...tc, isError: tc.isError ?? false })),
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
/** Stops its processes and keeps its disks, session and inbox. */
|
|
181
|
+
sleep() {
|
|
182
|
+
return this.client.sleep(this.name);
|
|
183
|
+
}
|
|
184
|
+
wake() {
|
|
185
|
+
return this.client.wake(this.name);
|
|
186
|
+
}
|
|
187
|
+
/** Interrupts a Turn (default: the active one). */
|
|
188
|
+
stop(turn) {
|
|
189
|
+
return this.client.stop(this.name, turn);
|
|
190
|
+
}
|
|
191
|
+
inbox(opts = {}) {
|
|
192
|
+
return this.client.inbox(this.name, opts);
|
|
193
|
+
}
|
|
194
|
+
message(id) {
|
|
195
|
+
return this.client.message(this.name, id);
|
|
196
|
+
}
|
|
197
|
+
/** Withdraws a pending Message; a 409 A12sError names its state if it is too late. */
|
|
198
|
+
withdraw(id) {
|
|
199
|
+
return this.client.withdraw(this.name, id);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
export class Services {
|
|
203
|
+
client;
|
|
204
|
+
constructor(client) {
|
|
205
|
+
this.client = client;
|
|
206
|
+
}
|
|
207
|
+
/** Adds a Service to the Fleet. Fails with code `exists` if the name is taken. */
|
|
208
|
+
async create(name, spec = {}) {
|
|
209
|
+
const { replace, ...body } = spec;
|
|
210
|
+
const r = await this.client.request("PUT", `/v1/services/${enc(name)}`, body, {
|
|
211
|
+
headers: writeHeaders({ replace }),
|
|
212
|
+
});
|
|
213
|
+
return r.service ? new Service(this.client, r.service) : this.get(name);
|
|
214
|
+
}
|
|
215
|
+
/** Throws {@link NotFound} if there is none. */
|
|
216
|
+
async get(name) {
|
|
217
|
+
return new Service(this.client, await this.client.service(name));
|
|
218
|
+
}
|
|
219
|
+
async list() {
|
|
220
|
+
return (await this.client.services()).map((s) => new Service(this.client, s));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/** A handle on one Service. `spec` and `status` are as of the last create, get, reload, update or power change. */
|
|
224
|
+
export class Service {
|
|
225
|
+
client;
|
|
226
|
+
name;
|
|
227
|
+
status;
|
|
228
|
+
constructor(client, status) {
|
|
229
|
+
this.client = client;
|
|
230
|
+
this.name = status.name;
|
|
231
|
+
this.status = status;
|
|
232
|
+
}
|
|
233
|
+
/** As written in the Fleet. */
|
|
234
|
+
get spec() {
|
|
235
|
+
return this.status.spec ?? {};
|
|
236
|
+
}
|
|
237
|
+
get path() {
|
|
238
|
+
return `/v1/services/${enc(this.name)}`;
|
|
239
|
+
}
|
|
240
|
+
async reload() {
|
|
241
|
+
this.status = await this.client.service(this.name);
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
/** Changes the spec as a JSON merge patch: a key set to null is removed. */
|
|
245
|
+
async update(changes) {
|
|
246
|
+
const r = await this.client.request("PATCH", this.path, changes);
|
|
247
|
+
if (!r.service)
|
|
248
|
+
return this.reload();
|
|
249
|
+
this.status = r.service;
|
|
250
|
+
return this;
|
|
251
|
+
}
|
|
252
|
+
/** Removes it from the Fleet and stops it; its state drive is kept. */
|
|
253
|
+
async remove() {
|
|
254
|
+
await this.client.request("DELETE", this.path);
|
|
255
|
+
}
|
|
256
|
+
sleep() {
|
|
257
|
+
return this.power("asleep");
|
|
258
|
+
}
|
|
259
|
+
wake() {
|
|
260
|
+
return this.power("awake");
|
|
261
|
+
}
|
|
262
|
+
async power(state) {
|
|
263
|
+
this.status = await this.client.request("POST", `${this.path}/power`, { state });
|
|
264
|
+
return this;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
export class Tokens {
|
|
268
|
+
client;
|
|
269
|
+
constructor(client) {
|
|
270
|
+
this.client = client;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* A token for another program; owner only. The secret is in the result once. With `prefix` it may do everything
|
|
274
|
+
* the owner can for names that start with it, and nothing outside; otherwise `actions` on `agentComputers`.
|
|
275
|
+
*/
|
|
276
|
+
create(name, opts) {
|
|
277
|
+
if ("prefix" in opts) {
|
|
278
|
+
return this.client.createToken({ name, prefix: opts.prefix });
|
|
279
|
+
}
|
|
280
|
+
return this.client.createToken({ name, actions: opts.actions, computers: opts.agentComputers ?? ["*"] });
|
|
281
|
+
}
|
|
282
|
+
async list() {
|
|
283
|
+
return (await this.client.request("GET", "/v1/tokens")).tokens ?? [];
|
|
284
|
+
}
|
|
285
|
+
async remove(name) {
|
|
286
|
+
await this.client.request("DELETE", `/v1/tokens/${enc(name)}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/** Credentials by name: set once, then name them in a spec's `credentials`. Values are never read back. */
|
|
290
|
+
export class Credentials {
|
|
291
|
+
client;
|
|
292
|
+
constructor(client) {
|
|
293
|
+
this.client = client;
|
|
294
|
+
}
|
|
295
|
+
/** Stores the value on the Controller; agent computers that use it get the new value at once. */
|
|
296
|
+
set(name, value) {
|
|
297
|
+
return this.client.request("PUT", `/v1/credentials/${enc(name)}`, { value });
|
|
298
|
+
}
|
|
299
|
+
async list() {
|
|
300
|
+
return (await this.client.request("GET", "/v1/credentials")).credentials ?? [];
|
|
301
|
+
}
|
|
302
|
+
/** Fails with A12sError code `in_use` while an agent computer or Service uses it. */
|
|
303
|
+
async remove(name) {
|
|
304
|
+
await this.client.request("DELETE", `/v1/credentials/${enc(name)}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { A12sClient, type A12sClientOptions } from "./client.js";
|
|
2
2
|
export * from "./client.js";
|
|
3
|
+
export * from "./fleet.js";
|
|
3
4
|
export * from "./types.js";
|
|
4
5
|
export type ConnectOptions = A12sClientOptions;
|
|
5
6
|
/**
|
|
7
|
+
* @deprecated Use `Fleet.connect()`. Kept for one release.
|
|
8
|
+
*
|
|
6
9
|
* In a browser: a client for the page's own origin, which is `a12s gateway` serving the page (no token needed).
|
|
7
10
|
* Pass `url` and `token` to reach anything else.
|
|
8
11
|
*/
|
package/dist/src/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// The default entry (browsers, Deno, workers): no node: imports. Node resolves "@coltonkirsten/a12s" to ./node.ts instead, through
|
|
2
|
-
// the "node" export condition, so `import {
|
|
2
|
+
// the "node" export condition, so `import { Fleet } from "@coltonkirsten/a12s"` works everywhere.
|
|
3
3
|
import { A12sClient } from "./client.js";
|
|
4
4
|
export * from "./client.js";
|
|
5
|
+
export * from "./fleet.js";
|
|
5
6
|
export * from "./types.js";
|
|
6
7
|
/**
|
|
8
|
+
* @deprecated Use `Fleet.connect()`. Kept for one release.
|
|
9
|
+
*
|
|
7
10
|
* In a browser: a client for the page's own origin, which is `a12s gateway` serving the page (no token needed).
|
|
8
11
|
* Pass `url` and `token` to reach anything else.
|
|
9
12
|
*/
|
package/dist/src/node.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { A12sClient, type A12sClientOptions } from "./client.js";
|
|
2
|
+
import { Fleet as PageFleet } from "./fleet.js";
|
|
2
3
|
export * from "./client.js";
|
|
4
|
+
export * from "./fleet.js";
|
|
3
5
|
export * from "./types.js";
|
|
6
|
+
export declare class Fleet extends PageFleet {
|
|
7
|
+
/** A running Fleet, as its owner, found like {@link connect} finds it. */
|
|
8
|
+
static connect(opts?: ConnectOptions): Promise<Fleet>;
|
|
9
|
+
}
|
|
4
10
|
export interface ConnectOptions extends A12sClientOptions {
|
|
5
11
|
/** The Fleet's name (`metadata.name`). Default: found as described on {@link connect}. */
|
|
6
12
|
fleet?: string;
|
|
@@ -10,6 +16,8 @@ export interface ConnectOptions extends A12sClientOptions {
|
|
|
10
16
|
cwd?: string;
|
|
11
17
|
}
|
|
12
18
|
/**
|
|
19
|
+
* @deprecated Use `Fleet.connect()`, which finds the Fleet the same way. Kept for one release.
|
|
20
|
+
*
|
|
13
21
|
* A client for a running Fleet, as its owner. It uses the first of:
|
|
14
22
|
* 1. `url` (and `token`), or `$A12S_URL` (and `$A12S_TOKEN`);
|
|
15
23
|
* 2. the Fleet named by `fleet`;
|
package/dist/src/node.js
CHANGED
|
@@ -3,9 +3,19 @@ import { readdir, readFile } from "node:fs/promises";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { A12sClient } from "./client.js";
|
|
6
|
+
import { Fleet as PageFleet } from "./fleet.js";
|
|
6
7
|
export * from "./client.js";
|
|
8
|
+
export * from "./fleet.js";
|
|
7
9
|
export * from "./types.js";
|
|
10
|
+
export class Fleet extends PageFleet {
|
|
11
|
+
/** A running Fleet, as its owner, found like {@link connect} finds it. */
|
|
12
|
+
static async connect(opts = {}) {
|
|
13
|
+
return new Fleet(await discover(opts));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
8
16
|
/**
|
|
17
|
+
* @deprecated Use `Fleet.connect()`, which finds the Fleet the same way. Kept for one release.
|
|
18
|
+
*
|
|
9
19
|
* A client for a running Fleet, as its owner. It uses the first of:
|
|
10
20
|
* 1. `url` (and `token`), or `$A12S_URL` (and `$A12S_TOKEN`);
|
|
11
21
|
* 2. the Fleet named by `fleet`;
|
|
@@ -14,9 +24,12 @@ export * from "./types.js";
|
|
|
14
24
|
* For 2–4 it reads `~/.a12s/<fleet>/controller.json`, which `a12s up` writes.
|
|
15
25
|
*/
|
|
16
26
|
export async function connect(opts = {}) {
|
|
27
|
+
return new A12sClient(await discover(opts));
|
|
28
|
+
}
|
|
29
|
+
async function discover(opts) {
|
|
17
30
|
const url = opts.url ?? process.env.A12S_URL;
|
|
18
31
|
if (url)
|
|
19
|
-
return
|
|
32
|
+
return { url, token: opts.token ?? process.env.A12S_TOKEN, fetch: opts.fetch };
|
|
20
33
|
const home = join(opts.home ?? homedir(), ".a12s");
|
|
21
34
|
const fleet = opts.fleet ?? (await fleetNameNearby(opts.cwd ?? process.cwd())) ?? (await onlyRunningFleet(home));
|
|
22
35
|
const path = join(home, fleet, "controller.json");
|
|
@@ -27,7 +40,7 @@ export async function connect(opts = {}) {
|
|
|
27
40
|
catch (err) {
|
|
28
41
|
throw new Error(`Fleet ${fleet} isn't running (no ${path}): run a12s up in its directory first`, { cause: err });
|
|
29
42
|
}
|
|
30
|
-
return
|
|
43
|
+
return { url: `http://127.0.0.1:${cf.port}`, token: cf.token, fetch: opts.fetch };
|
|
31
44
|
}
|
|
32
45
|
async function fleetNameNearby(cwd) {
|
|
33
46
|
const file = process.env.A12S_FLEET ? resolve(cwd, process.env.A12S_FLEET) : await findUp(cwd, "fleet.yaml");
|
package/dist/src/types.d.ts
CHANGED
|
@@ -8,12 +8,108 @@ export interface Harness {
|
|
|
8
8
|
version: string;
|
|
9
9
|
model?: string;
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
export interface AgentComputerSpec {
|
|
12
|
+
harness?: {
|
|
13
|
+
kind: "claude-code" | "codex" | (string & {});
|
|
14
|
+
version?: string;
|
|
15
|
+
model?: string;
|
|
16
|
+
};
|
|
17
|
+
image?: string;
|
|
18
|
+
instructions?: string;
|
|
19
|
+
/** File contents by path, written into the agent computer's home. */
|
|
20
|
+
files?: Record<string, string>;
|
|
21
|
+
resources?: {
|
|
22
|
+
cpu?: string | number;
|
|
23
|
+
memory?: string | number;
|
|
24
|
+
};
|
|
25
|
+
home?: {
|
|
26
|
+
size?: string | number;
|
|
27
|
+
preset?: string;
|
|
28
|
+
};
|
|
29
|
+
autonomy?: "full" | "restricted";
|
|
30
|
+
/** Credential names; their values come from `a12s up` or the credentials API. */
|
|
31
|
+
credentials?: string[];
|
|
32
|
+
drives?: Array<{
|
|
33
|
+
name: string;
|
|
34
|
+
at: string;
|
|
35
|
+
readOnly?: boolean;
|
|
36
|
+
}>;
|
|
37
|
+
egress?: {
|
|
38
|
+
allow?: string[];
|
|
39
|
+
};
|
|
40
|
+
/** `seccomp` is the profile JSON itself. */
|
|
41
|
+
security?: {
|
|
42
|
+
seccomp?: Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
inbox?: {
|
|
45
|
+
maxMessages?: number;
|
|
46
|
+
maxBytes?: string;
|
|
47
|
+
intro?: string;
|
|
48
|
+
};
|
|
49
|
+
power?: {
|
|
50
|
+
sleepAfterIdle?: string;
|
|
51
|
+
wakeOnMessage?: {
|
|
52
|
+
from: string[];
|
|
53
|
+
maxWakesPerHour?: number;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
uses?: Array<string | {
|
|
14
57
|
name: string;
|
|
58
|
+
mcp?: boolean;
|
|
59
|
+
}>;
|
|
60
|
+
grants?: {
|
|
61
|
+
send?: string[];
|
|
15
62
|
};
|
|
63
|
+
}
|
|
64
|
+
/** A JSON merge patch of T: a key set to null is deleted. */
|
|
65
|
+
export type MergePatch<T> = {
|
|
66
|
+
[K in keyof T]?: T[K] | null;
|
|
16
67
|
};
|
|
68
|
+
/** A Fleet as written: the defaults and each agent computer before the defaults are merged in. */
|
|
69
|
+
export interface FleetSpec {
|
|
70
|
+
apiVersion: string;
|
|
71
|
+
kind: string;
|
|
72
|
+
metadata: {
|
|
73
|
+
name: string;
|
|
74
|
+
};
|
|
75
|
+
egress?: {
|
|
76
|
+
enforcement?: string;
|
|
77
|
+
allowPrivate?: string[];
|
|
78
|
+
};
|
|
79
|
+
defaults?: AgentComputerSpec;
|
|
80
|
+
drives?: Record<string, {
|
|
81
|
+
size?: string | number;
|
|
82
|
+
access: string;
|
|
83
|
+
}>;
|
|
84
|
+
agent_computers: Record<string, AgentComputerSpec>;
|
|
85
|
+
services?: Record<string, Record<string, unknown>>;
|
|
86
|
+
}
|
|
87
|
+
export interface Change {
|
|
88
|
+
op: "create" | "update" | "remove" | (string & {});
|
|
89
|
+
kind: "agentComputer" | "service" | (string & {});
|
|
90
|
+
name: string;
|
|
91
|
+
/** "idle" for an update that waits for a busy agent computer's next idle boundary. */
|
|
92
|
+
when: "now" | "idle" | (string & {});
|
|
93
|
+
/** The top-level spec fields that differ, for an update. */
|
|
94
|
+
fields?: string[];
|
|
95
|
+
}
|
|
96
|
+
export interface FleetResponse {
|
|
97
|
+
version: number;
|
|
98
|
+
fleet: FleetSpec | null;
|
|
99
|
+
/** What changed through the API since the last whole-Fleet write: `a12s up` from the file undoes it. */
|
|
100
|
+
drift: Change[];
|
|
101
|
+
}
|
|
102
|
+
export interface AgentComputerData {
|
|
103
|
+
name: string;
|
|
104
|
+
/** As written. */
|
|
105
|
+
spec: AgentComputerSpec;
|
|
106
|
+
/** With the Fleet's defaults merged in. */
|
|
107
|
+
effective: AgentComputerSpec;
|
|
108
|
+
status: ComputerStatus;
|
|
109
|
+
}
|
|
110
|
+
export interface AgentComputersResponse {
|
|
111
|
+
agentComputers: AgentComputerData[];
|
|
112
|
+
}
|
|
17
113
|
export interface Lock {
|
|
18
114
|
a12s?: string;
|
|
19
115
|
images: Record<string, string>;
|
|
@@ -24,16 +120,12 @@ export interface Lock {
|
|
|
24
120
|
seccomp?: Record<string, string>;
|
|
25
121
|
}
|
|
26
122
|
export interface PutFleetRequest {
|
|
27
|
-
fleet:
|
|
123
|
+
fleet: FleetSpec;
|
|
28
124
|
lock?: Lock;
|
|
29
125
|
/** Credential values by name, base64-encoded (Go []byte). Kept in the Controller's memory only. */
|
|
30
126
|
credentials?: Record<string, string>;
|
|
31
127
|
asleep?: boolean;
|
|
32
128
|
refresh?: string[];
|
|
33
|
-
/** Home presets as base64 tar streams, keyed by their path in the Fleet. */
|
|
34
|
-
presets?: Record<string, string>;
|
|
35
|
-
/** security.seccomp profiles (JSON text), keyed by their path in the Fleet. */
|
|
36
|
-
seccomp?: Record<string, string>;
|
|
37
129
|
}
|
|
38
130
|
export interface ServiceChanges {
|
|
39
131
|
created: string[] | null;
|
|
@@ -41,6 +133,10 @@ export interface ServiceChanges {
|
|
|
41
133
|
removed: string[] | null;
|
|
42
134
|
}
|
|
43
135
|
export interface PutFleetResponse {
|
|
136
|
+
/** The Fleet's version after the write; unchanged by a dry run or a write that changed nothing. */
|
|
137
|
+
version: number;
|
|
138
|
+
dryRun?: boolean;
|
|
139
|
+
changes: Change[];
|
|
44
140
|
created: string[] | null;
|
|
45
141
|
updated: string[] | null;
|
|
46
142
|
staged: string[] | null;
|
|
@@ -49,6 +145,10 @@ export interface PutFleetResponse {
|
|
|
49
145
|
errors?: Record<string, string>;
|
|
50
146
|
warnings?: string[];
|
|
51
147
|
services: ServiceChanges;
|
|
148
|
+
/** The written agent computer, after a PUT or PATCH of one. */
|
|
149
|
+
agentComputer?: AgentComputerData;
|
|
150
|
+
/** The written Service, after a PUT or PATCH of one. */
|
|
151
|
+
service?: ServiceStatus;
|
|
52
152
|
}
|
|
53
153
|
export interface LimitValue {
|
|
54
154
|
value: number;
|
|
@@ -146,10 +246,14 @@ export interface ServiceStatus {
|
|
|
146
246
|
stateDrive?: string;
|
|
147
247
|
egressMode?: string;
|
|
148
248
|
warnings?: string[];
|
|
249
|
+
/** The Service as written in the Fleet. */
|
|
250
|
+
spec?: Record<string, unknown>;
|
|
149
251
|
}
|
|
150
252
|
export interface StatusResponse {
|
|
151
253
|
fleet: string;
|
|
152
254
|
egressEnforcement: string;
|
|
255
|
+
agentComputers: ComputerStatus[];
|
|
256
|
+
/** @deprecated `agentComputers` under its old name. */
|
|
153
257
|
computers: ComputerStatus[];
|
|
154
258
|
orphanedDrives?: DriveRow[];
|
|
155
259
|
credentialsLoaded: boolean;
|
|
@@ -213,10 +317,12 @@ export interface InboxListOptions {
|
|
|
213
317
|
bodies?: boolean;
|
|
214
318
|
after?: number;
|
|
215
319
|
}
|
|
216
|
-
/** The fields every
|
|
320
|
+
/** The fields every event has. `seq` is the agent computer's journal position (0 for the Controller's own events). */
|
|
217
321
|
export interface EventEnvelope {
|
|
218
322
|
v: number;
|
|
219
|
-
/** The
|
|
323
|
+
/** The agent computer it is about; absent on `fleet.changed` and in journals older than the field. */
|
|
324
|
+
agentComputer?: string;
|
|
325
|
+
/** `agentComputer` under its old name, for one release. */
|
|
220
326
|
computer: string;
|
|
221
327
|
session: string;
|
|
222
328
|
/** The Turn it belongs to; "" for events outside a Turn. */
|
|
@@ -264,8 +370,34 @@ export interface TurnFinishedData {
|
|
|
264
370
|
status: "completed" | "failed" | "interrupted";
|
|
265
371
|
/** The harness's own usage object (tokens, cost), or null. */
|
|
266
372
|
usage: unknown;
|
|
373
|
+
/** The same usage, normalized across harnesses, when the harness reported it. */
|
|
374
|
+
tokens?: TokenUsage;
|
|
267
375
|
error?: string;
|
|
268
376
|
}
|
|
377
|
+
/** A Turn's token usage, the same for every harness. `input` excludes cached input. */
|
|
378
|
+
export interface TokenUsage {
|
|
379
|
+
input: number;
|
|
380
|
+
output: number;
|
|
381
|
+
cacheRead: number;
|
|
382
|
+
cacheWrite: number;
|
|
383
|
+
}
|
|
384
|
+
/** The egress proxy refused a connection. */
|
|
385
|
+
export interface EgressDeniedData {
|
|
386
|
+
host: string;
|
|
387
|
+
port: number;
|
|
388
|
+
reason?: string;
|
|
389
|
+
}
|
|
390
|
+
/** The Fleet's version moved; `names` are the agent computers and Services the write changed. */
|
|
391
|
+
export interface FleetChangedData {
|
|
392
|
+
version: number;
|
|
393
|
+
/** Who wrote it. */
|
|
394
|
+
by: string;
|
|
395
|
+
names: string[];
|
|
396
|
+
}
|
|
397
|
+
export interface PhaseData {
|
|
398
|
+
phase: Phase;
|
|
399
|
+
error?: Failure;
|
|
400
|
+
}
|
|
269
401
|
export interface ToolStartedData {
|
|
270
402
|
id: string;
|
|
271
403
|
/** MCP tools are named `mcp__<server>__<tool>`, e.g. `mcp__a12s__send`. */
|
|
@@ -331,10 +463,15 @@ export type TurnRefusedEvent = Ev<"turn.refused", {
|
|
|
331
463
|
}>;
|
|
332
464
|
/** A Message that ended without delivery (cleared, withdrawn, expired, replaced), body included. */
|
|
333
465
|
export type InboxDiscardedEvent = Ev<"inbox.discarded", Record<string, unknown>>;
|
|
466
|
+
export type EgressDeniedEvent = Ev<"egress.denied", EgressDeniedData>;
|
|
467
|
+
/** Made by the Controller, on the fleet-wide stream only. */
|
|
468
|
+
export type FleetChangedEvent = Ev<"fleet.changed", FleetChangedData>;
|
|
469
|
+
/** Made by the Controller, on the fleet-wide stream only. */
|
|
470
|
+
export type PhaseEvent = Ev<"agent_computer.phase", PhaseData>;
|
|
334
471
|
/** Made by the SDK, never journaled: the stream could not resume from its cursor and events were lost. */
|
|
335
472
|
export type GapEvent = Ev<"gap", GapData>;
|
|
336
|
-
/** One event of
|
|
337
|
-
export type A12sEvent = TurnStartedEvent | TurnDispatchedEvent | TextDeltaEvent | ToolStartedEvent | ToolFinishedEvent | PermissionEvent | DiagnosticEvent | TurnFinishedEvent | SessionChangedEvent | ResumeFailedEvent | LifecycleEvent | HarnessRestartedEvent | ContinuationAbandonedEvent | TurnRefusedEvent | InboxDiscardedEvent | GapEvent;
|
|
473
|
+
/** One event of an agent computer's journal, or the Controller's own on the fleet-wide stream. Narrow on `type` to get typed `data`. */
|
|
474
|
+
export type A12sEvent = TurnStartedEvent | TurnDispatchedEvent | TextDeltaEvent | ToolStartedEvent | ToolFinishedEvent | PermissionEvent | DiagnosticEvent | TurnFinishedEvent | SessionChangedEvent | ResumeFailedEvent | LifecycleEvent | HarnessRestartedEvent | ContinuationAbandonedEvent | TurnRefusedEvent | InboxDiscardedEvent | EgressDeniedEvent | FleetChangedEvent | PhaseEvent | GapEvent;
|
|
338
475
|
export type EventType = A12sEvent["type"];
|
|
339
476
|
/** One tool call in a Turn. A `send` to another Computer is `mcp__a12s__send` with `input.body`. */
|
|
340
477
|
export interface ToolCall {
|
|
@@ -346,6 +483,8 @@ export interface ToolCall {
|
|
|
346
483
|
}
|
|
347
484
|
/** A finished Turn: what `turns()` yields and `prompt()` returns. */
|
|
348
485
|
export interface Turn {
|
|
486
|
+
agentComputer: string;
|
|
487
|
+
/** `agentComputer` under its old name, for one release. */
|
|
349
488
|
computer: string;
|
|
350
489
|
/** The Turn id. */
|
|
351
490
|
id: string;
|
|
@@ -359,24 +498,47 @@ export interface Turn {
|
|
|
359
498
|
/** The tools it called, in order, with their input and output: what it `send`s to other Computers is here. */
|
|
360
499
|
tools: ToolCall[];
|
|
361
500
|
usage: unknown;
|
|
501
|
+
tokens?: TokenUsage;
|
|
362
502
|
startedAt?: string;
|
|
363
503
|
finishedAt: string;
|
|
364
504
|
}
|
|
505
|
+
/** Past Turns, newest first, as GET /v1/agent-computers/{name}/turns sends them. */
|
|
506
|
+
export interface TurnsResponse {
|
|
507
|
+
turns: Array<Omit<Turn, "computer" | "tools"> & {
|
|
508
|
+
tools: Array<Omit<ToolCall, "isError"> & {
|
|
509
|
+
isError?: boolean;
|
|
510
|
+
}>;
|
|
511
|
+
}>;
|
|
512
|
+
}
|
|
365
513
|
export interface ApiErrorBody {
|
|
366
514
|
code: string;
|
|
367
515
|
message: string;
|
|
368
516
|
fix?: string;
|
|
369
517
|
details?: Record<string, string>;
|
|
370
518
|
}
|
|
519
|
+
export type TokenAction = "send" | "inbox" | "status" | "events" | "power" | "stop";
|
|
520
|
+
/** Either `prefix`, or `actions` on `computers` (agent computer names, or "*"). */
|
|
371
521
|
export interface TokenRequest {
|
|
372
522
|
name: string;
|
|
373
|
-
actions
|
|
374
|
-
computers
|
|
523
|
+
actions?: TokenAction[];
|
|
524
|
+
computers?: string[];
|
|
525
|
+
/** Every power over agent computers, Services and credentials whose names start with it; none outside. */
|
|
526
|
+
prefix?: string;
|
|
527
|
+
}
|
|
528
|
+
/** A credential's name, whether it has a value, and who uses it. */
|
|
529
|
+
export interface CredentialInfo {
|
|
530
|
+
name: string;
|
|
531
|
+
set: boolean;
|
|
532
|
+
usedBy?: string[];
|
|
533
|
+
}
|
|
534
|
+
export interface CredentialsResponse {
|
|
535
|
+
credentials: CredentialInfo[];
|
|
375
536
|
}
|
|
376
537
|
export interface Token {
|
|
377
538
|
name: string;
|
|
378
539
|
actions: string[];
|
|
379
540
|
computers: string[];
|
|
541
|
+
prefix?: string;
|
|
380
542
|
createdAt: string;
|
|
381
543
|
secret?: string;
|
|
382
544
|
}
|