@agent-compose/sdk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +385 -89
- package/dist/client.d.ts +173 -21
- package/dist/index.d.ts +2 -1
- package/dist/index.js +141 -20
- package/dist/runtimes/openai-desktop.js +140 -20
- package/dist/sse.d.ts +25 -0
- package/dist/types/events.d.ts +5 -0
- package/dist/types/sandbox-environment.d.ts +13 -12
- package/dist/types/workflow.d.ts +13 -11
- package/dist/utils/bundler.d.ts +6 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# @agent-compose/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK for [agent-compose](https://github.com/Layr-Labs/agent-compose). Use it to:
|
|
4
|
+
|
|
5
|
+
- **Author workflows** that run agentic LLM loops inside isolated sandboxes
|
|
6
|
+
- **Define runtimes** that wrap a coding-CLI tool (Claude Code, OpenAI Desktop, …) into a sandbox-portable agent loop
|
|
7
|
+
- **Register, invoke, observe, and cancel** workflows via the HTTP API (`AgentComposeClient`)
|
|
8
|
+
- **Manage factories, secrets, API keys, and snapshots** programmatically
|
|
9
|
+
|
|
10
|
+
The hierarchy: a **team** owns one or more **factories** (project containers); each factory owns workflow templates, secrets, and runs. Workflows are versioned per `(factory, name, version)`. New code that doesn't care about factories transparently lands in `default` — every team has one.
|
|
4
11
|
|
|
5
12
|
---
|
|
6
13
|
|
|
@@ -14,139 +21,428 @@ npm install zod
|
|
|
14
21
|
|
|
15
22
|
---
|
|
16
23
|
|
|
17
|
-
##
|
|
24
|
+
## Authoring a workflow
|
|
25
|
+
|
|
26
|
+
A workflow is `async (ctx, sandbox) => T`. Two positional args:
|
|
27
|
+
|
|
28
|
+
- **`ctx`** carries the run identity (`run.id`), the caller's `input`, plus
|
|
29
|
+
observability helpers (`setMetadata`, `step`).
|
|
30
|
+
- **`sandbox`** is a capability the engine constructs once for the run —
|
|
31
|
+
pass it to `runAgent({ sandbox, ... })` and to any helper that takes a
|
|
32
|
+
`SandboxProvider` (file writers, git utilities, command runners).
|
|
18
33
|
|
|
19
34
|
```typescript
|
|
20
|
-
// my-
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
// my-workflow.ts
|
|
36
|
+
import { defineWorkflow, runAgent, claudeRuntime } from "@agent-compose/sdk";
|
|
37
|
+
import PROMPT from "./prompt.md" with { type: "text" };
|
|
38
|
+
|
|
39
|
+
export default defineWorkflow({
|
|
40
|
+
async run(ctx, sandbox) {
|
|
41
|
+
const repo = (ctx.input?.repo as string | undefined) ?? "owner/repo";
|
|
42
|
+
|
|
43
|
+
const result = await runAgent({
|
|
44
|
+
sandbox,
|
|
45
|
+
runtime: claudeRuntime,
|
|
46
|
+
prompt: PROMPT,
|
|
47
|
+
promptVars: { REPO: repo },
|
|
48
|
+
tools: ["Bash", "Read", "Edit", "Write", "Grep", "Glob"],
|
|
49
|
+
budget: { turnsPerIteration: 40, maxIterations: 8 },
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await ctx.setMetadata({ summary: result.status?.summary });
|
|
53
|
+
return { ok: result.status?.completed ?? false };
|
|
54
|
+
},
|
|
55
|
+
// Optional: outbound network rules that the runner sandbox will enforce
|
|
56
|
+
// (Vercel only — E2B ignores). Use `$VAR` placeholders for secrets that
|
|
57
|
+
// get resolved from the per-workflow secret store at dispatch time.
|
|
58
|
+
networkPolicy: {
|
|
59
|
+
allow: {
|
|
60
|
+
"*": [],
|
|
61
|
+
"api.anthropic.com": [{ transform: [{ headers: { "x-api-key": "$ANTHROPIC_API_KEY" } }] }],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
28
64
|
});
|
|
29
65
|
```
|
|
30
66
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
67
|
+
`defineWorkflow` is a thin sugar — it returns the bare `run` function with
|
|
68
|
+
`networkPolicy` / `placeholders` / `snapshot` / `saveSnapshot` attached as
|
|
69
|
+
metadata that the bundler picks up at registration time. A plain
|
|
70
|
+
`export default async (ctx, sandbox) => {...}` is also valid; you just lose
|
|
71
|
+
the metadata channel.
|
|
72
|
+
|
|
73
|
+
### What the workflow can do with `ctx`
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
interface WorkflowCtx {
|
|
77
|
+
run: { id: string };
|
|
78
|
+
input?: Record<string, unknown>;
|
|
79
|
+
setMetadata: (data: Record<string, unknown>) => Promise<void>;
|
|
80
|
+
step<T>(name: string, fn: () => Promise<T>): Promise<T>;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
34
83
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
84
|
+
`step("phase-name", () => …)` wraps a phase for the run timeline — emits
|
|
85
|
+
`step_started` / `step_completed` / `step_failed` lifecycle events with
|
|
86
|
+
duration. Use it for setup, external API calls, or anything you want
|
|
87
|
+
visible on the dashboard's run detail page.
|
|
88
|
+
|
|
89
|
+
### The `runAgent` loop
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
runAgent({
|
|
93
|
+
sandbox, // the workflow's sandbox arg
|
|
94
|
+
runtime, // claudeRuntime, or your own via createClaudeRuntime / defineRuntime
|
|
95
|
+
prompt, // raw markdown — `--- frontmatter ---` is auto-stripped
|
|
96
|
+
promptVars?, // {{VAR}} substitutions; WORKING_DIR + DIFF_BASE auto-populate
|
|
97
|
+
tools?, // model tool allowlist (defaults inside agentLoop)
|
|
98
|
+
budget?, // { turnsPerIteration, maxIterations }
|
|
99
|
+
workingDir?, // every shell command runs here
|
|
100
|
+
responseSchema?, // zod — when set, the loop demands a `<response>` block on exit
|
|
101
|
+
onAgentEvent?, // per-message hook (e.g. wire to telemetry)
|
|
102
|
+
onIteration?, // per-iteration hook with parsed `<status>` block
|
|
103
|
+
})
|
|
104
|
+
// → AgentLoopResult { status?, response? (when responseSchema set), iterations, … }
|
|
45
105
|
```
|
|
46
106
|
|
|
107
|
+
The protocol is simple: the model emits XML-tagged blocks (`<status>` /
|
|
108
|
+
`<response>`) the loop parses. See `sdk/src/agent/protocol-suffix.md` for
|
|
109
|
+
the full instructions appended to every prompt.
|
|
110
|
+
|
|
47
111
|
---
|
|
48
112
|
|
|
49
|
-
##
|
|
113
|
+
## Defining a runtime
|
|
50
114
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
115
|
+
A "runtime" wraps an agent's underlying execution model — usually a coding
|
|
116
|
+
CLI like Claude Code or OpenAI Desktop — so `runAgent` can drive it. The SDK
|
|
117
|
+
ships built-ins; you only need a custom one for an exotic provider.
|
|
54
118
|
|
|
55
|
-
|
|
56
|
-
const planner = await ctx.spawnAgent("planner", {
|
|
57
|
-
data: { task: ctx.input.task },
|
|
58
|
-
});
|
|
119
|
+
### Built-in runtimes
|
|
59
120
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
121
|
+
```ts
|
|
122
|
+
import {
|
|
123
|
+
createClaudeRuntime, // factory, takes config
|
|
124
|
+
claudeRuntime, // pre-built default (DEFAULT_CLAUDE_MODEL)
|
|
125
|
+
ClaudeRunner, // class, if you need to override
|
|
126
|
+
} from "@agent-compose/sdk";
|
|
64
127
|
|
|
65
|
-
|
|
66
|
-
|
|
128
|
+
// openAIDesktopRuntime is NOT in the package root (it pulls in `sharp` for
|
|
129
|
+
// screenshot capture; the native binding can't be cross-compiled). Import
|
|
130
|
+
// directly when you actually want the desktop runtime:
|
|
131
|
+
import openAIDesktopRuntime from "@agent-compose/sdk/runtimes/openai-desktop.js";
|
|
132
|
+
```
|
|
67
133
|
|
|
68
|
-
|
|
134
|
+
### Custom runtime
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { defineRuntime, type AgentRuntime } from "@agent-compose/sdk";
|
|
138
|
+
|
|
139
|
+
const myRuntime: AgentRuntime = defineRuntime({
|
|
140
|
+
create: (sandbox, opts) => {
|
|
141
|
+
// Return a ModelExecutionContract — see sdk/src/types/runtime.ts
|
|
142
|
+
return {
|
|
143
|
+
sendMessage({ prompt, sessionId, signal }) {
|
|
144
|
+
// Async generator that yields AgentMessage chunks the loop parses.
|
|
145
|
+
return /* … */;
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
});
|
|
69
150
|
```
|
|
70
151
|
|
|
152
|
+
`AgentRuntime` is a tagged record with `create(sandbox, RuntimeOptions) →
|
|
153
|
+
ModelExecutionContract`. There is **no `provider` field** on it — the
|
|
154
|
+
runtime is bound to the workflow at author time (you pass it to `runAgent`),
|
|
155
|
+
not selected by the server.
|
|
156
|
+
|
|
71
157
|
---
|
|
72
158
|
|
|
73
|
-
##
|
|
159
|
+
## Registering a workflow
|
|
74
160
|
|
|
75
|
-
`
|
|
161
|
+
The `agentc` CLI handles the bundling-and-registration step for you:
|
|
76
162
|
|
|
77
|
-
```
|
|
78
|
-
|
|
163
|
+
```bash
|
|
164
|
+
agentc register my-workflow.ts -n my-workflow
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Under the hood that calls `bundleWorkflow(workflowPath)` (resolves imports,
|
|
168
|
+
inlines runtime sources via dynamic-require traversal) and `POST
|
|
169
|
+
/api/v1/factories/<slug>/templates` with the bundled source. If you need
|
|
170
|
+
to drive registration from your own build pipeline, you can do the same
|
|
171
|
+
thing via the SDK directly:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { AgentComposeClient, bundleWorkflow } from "@agent-compose/sdk";
|
|
79
175
|
|
|
80
176
|
const client = new AgentComposeClient(
|
|
81
|
-
"
|
|
82
|
-
process.env.
|
|
177
|
+
"https://your-server.example.com",
|
|
178
|
+
process.env.AGENT_COMPOSE_API_KEY!,
|
|
83
179
|
);
|
|
84
180
|
|
|
85
|
-
|
|
181
|
+
const bundled = await bundleWorkflow("./my-workflow.ts");
|
|
86
182
|
await client.register({
|
|
87
|
-
name:
|
|
88
|
-
|
|
89
|
-
//
|
|
183
|
+
name: "my-workflow",
|
|
184
|
+
source: bundled.source,
|
|
185
|
+
runtimes: bundled.runtimes, // [{ name, source }] — embedded so the runner has them locally
|
|
186
|
+
schedule: "*/30 * * * *", // optional cron
|
|
187
|
+
factorySlug: "default", // optional — defaults to "default"
|
|
188
|
+
// snapshot, saveSnapshot, networkPolicy, placeholders — all optional
|
|
90
189
|
});
|
|
91
190
|
```
|
|
92
191
|
|
|
93
|
-
|
|
94
|
-
-
|
|
95
|
-
- `./planner/index.ts`
|
|
96
|
-
- `./agents/planner.ts`
|
|
97
|
-
- `./agents/planner/index.ts`
|
|
98
|
-
|
|
99
|
-
For runtime `"claude"`, it looks for:
|
|
100
|
-
- `./claude.ts`
|
|
101
|
-
- `./runtimes/claude.ts`
|
|
192
|
+
`register()` requires the caller's API key to carry the `admin` scope
|
|
193
|
+
(or full team-access for legacy keys without scopes).
|
|
102
194
|
|
|
103
195
|
---
|
|
104
196
|
|
|
105
|
-
##
|
|
197
|
+
## Invoking a workflow
|
|
106
198
|
|
|
107
|
-
|
|
108
|
-
|
|
199
|
+
Two flavours:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
// Fire-and-forget — returns the run id immediately.
|
|
109
203
|
const { id } = await client.invoke("my-workflow", {
|
|
110
|
-
|
|
204
|
+
repo: "owner/repo",
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Block until the run settles (default 30min timeout, 1s poll).
|
|
208
|
+
const status = await client.invokeAndWait("my-workflow", { repo: "owner/repo" }, {
|
|
209
|
+
timeoutMs: 5 * 60_000,
|
|
210
|
+
pollIntervalMs: 2000,
|
|
111
211
|
});
|
|
212
|
+
console.log(status.status); // "success" | "failed" | "abandoned" | "canceled"
|
|
213
|
+
console.log(status.output); // workflow's return value
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`output` is the workflow's `run()` return value (whatever `defineWorkflow({
|
|
217
|
+
async run() { return … } })` resolves to). `setMetadata()` writes to a
|
|
218
|
+
separate `metadata` field — useful for "side-channel" facts (PR url, plan
|
|
219
|
+
url) without polluting the structured return.
|
|
220
|
+
|
|
221
|
+
`invoke` and `invokeAndWait` both accept `{ factorySlug, snapshot,
|
|
222
|
+
saveSnapshot, parentRunId }` as the third argument. `factorySlug` defaults
|
|
223
|
+
to `"default"`.
|
|
224
|
+
|
|
225
|
+
### Auto parent/child tracing
|
|
226
|
+
|
|
227
|
+
The SDK detects `process.env.RUN_ID` (set by the runner sandbox on every
|
|
228
|
+
dispatch) and automatically threads it as `parentRunId` on subsequent
|
|
229
|
+
`invoke()` calls. Workflows that fan out to other workflows get a
|
|
230
|
+
parent/child tree in the dashboard for free. Pass `parentRunId: null`
|
|
231
|
+
to opt out.
|
|
232
|
+
|
|
233
|
+
### Cancelling a run
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
await client.cancelRun(runId);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Idempotent — cancelling an already-terminal run returns the current state
|
|
240
|
+
without throwing. The server stamps the run as `canceled`, kills any live
|
|
241
|
+
sandboxes, and emits a `run_canceled` event on the stream.
|
|
242
|
+
|
|
243
|
+
### Streaming live logs
|
|
112
244
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
await new Promise(r => setTimeout(r, 5000));
|
|
117
|
-
status = await client.getStatus(id);
|
|
118
|
-
} while (status.status === "running");
|
|
245
|
+
`streamRunLogs` returns an async generator of `RunEvent`s in real time,
|
|
246
|
+
re-attaching via SSE under the hood. Pass `lastEventId` (the highest
|
|
247
|
+
`seq` you've already processed) to resume after a reconnect.
|
|
119
248
|
|
|
120
|
-
|
|
249
|
+
```ts
|
|
250
|
+
for await (const ev of client.streamRunLogs(runId, { lastEventId: 0 })) {
|
|
251
|
+
console.log(ev.event, ev.seq, ev.data);
|
|
252
|
+
if (ev.event === "run_complete" || ev.event === "run_failed" || ev.event === "run_canceled") {
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
121
256
|
```
|
|
122
257
|
|
|
258
|
+
`AbortSignal` works too — pass `{ signal }` and call `controller.abort()`
|
|
259
|
+
to tear the stream down from the caller side.
|
|
260
|
+
|
|
123
261
|
---
|
|
124
262
|
|
|
125
|
-
##
|
|
263
|
+
## Factories
|
|
264
|
+
|
|
265
|
+
Factories are project containers within a team. Each factory has its own
|
|
266
|
+
workflow templates, secrets, runs, and (optionally) scoped API keys. New
|
|
267
|
+
projects don't need to think about them — `default` is auto-created per
|
|
268
|
+
team and is what the SDK falls back to when `factorySlug` is omitted.
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
// CRUD on factories
|
|
272
|
+
await client.createFactory({ slug: "ci-bots", name: "CI Bots", description: "…" });
|
|
273
|
+
const factories = await client.listFactories();
|
|
274
|
+
const f = await client.getFactory("ci-bots");
|
|
275
|
+
await client.updateFactory("ci-bots", { name: "Continuous-Integration Bots" });
|
|
276
|
+
await client.deleteFactory("ci-bots");
|
|
277
|
+
|
|
278
|
+
// Templates list — flat across factories, or scoped to one
|
|
279
|
+
const all = await client.listTemplates();
|
|
280
|
+
const scoped = await client.listTemplates({ factorySlug: "ci-bots" });
|
|
281
|
+
|
|
282
|
+
// Register / invoke / secret operations all accept factorySlug
|
|
283
|
+
await client.register({ name: "scrape", source, factorySlug: "ci-bots", … });
|
|
284
|
+
await client.invoke("scrape", { url: "…" }, { factorySlug: "ci-bots" });
|
|
285
|
+
await client.setSecret("scrape", "GH_TOKEN", "ghp_…", { factorySlug: "ci-bots" });
|
|
286
|
+
```
|
|
126
287
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
288
|
+
CLI equivalents: `agentc factory list | create | get | update | delete`,
|
|
289
|
+
plus `--factory <slug>` on every other command.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Per-workflow secrets
|
|
294
|
+
|
|
295
|
+
Secrets live in GCP Secret Manager, one row per `(factory, workflow, key)`.
|
|
296
|
+
They're injected as env vars into the runner sandbox at dispatch time,
|
|
297
|
+
never persisted in the VM. Values are write-only — the API only returns
|
|
298
|
+
metadata (key, timestamps).
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
await client.setSecret("my-workflow", "ANTHROPIC_API_KEY", process.env.ANTHROPIC_API_KEY!);
|
|
302
|
+
const list = await client.listSecrets("my-workflow"); // [{ key, createdAt, updatedAt }]
|
|
303
|
+
await client.deleteSecret("my-workflow", "STALE_KEY");
|
|
131
304
|
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
305
|
+
// Scope to a non-default factory:
|
|
306
|
+
await client.setSecret("scrape", "GH_TOKEN", "ghp_…", { factorySlug: "ci-bots" });
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Mutations require `admin` scope.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## API keys
|
|
314
|
+
|
|
315
|
+
Mint and list scoped keys programmatically (requires an `admin`-scoped
|
|
316
|
+
caller key). New keys are returned **once**, in the same response as the
|
|
317
|
+
metadata — copy the `ac_…` value immediately.
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
const created = await client.createApiKey({
|
|
321
|
+
name: "ci-dispatcher",
|
|
322
|
+
scopes: ["read", "invoke"],
|
|
323
|
+
expiresAt: new Date(Date.now() + 30 * 86_400_000).toISOString(), // 30 days
|
|
324
|
+
// factorySlug: "ci-bots" // optional — scopes the key to a single factory
|
|
325
|
+
});
|
|
326
|
+
console.log(created.key); // "ac_…" — the only time you'll see this
|
|
327
|
+
|
|
328
|
+
const all = await client.listApiKeys();
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
CLI equivalent: `agentc keys create <name> --scopes read,invoke
|
|
332
|
+
--expires-in 30d`.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Usage
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
const usage = await client.getUsage(
|
|
340
|
+
new Date(Date.now() - 30 * 86_400_000),
|
|
341
|
+
new Date(),
|
|
342
|
+
);
|
|
343
|
+
// usage.rows: [{ day, runs, sandbox_seconds, … }]
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
CLI equivalent: `agentc usage`.
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Snapshots (replay-friendly sandboxes)
|
|
351
|
+
|
|
352
|
+
Long-running workflows can capture the runner sandbox as a Vercel snapshot
|
|
353
|
+
on success (`saveSnapshot: true`). Other workflows reference that snapshot
|
|
354
|
+
via the `snapshot` field to boot into the same prepared VM (deps installed,
|
|
355
|
+
repo cloned, etc.) instead of repeating setup.
|
|
135
356
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
interface AgentResult<T> { response: T; session: AgentSession }
|
|
357
|
+
```ts
|
|
358
|
+
// Capture per-invocation:
|
|
359
|
+
await client.invoke("my-workflow", input, { saveSnapshot: true });
|
|
140
360
|
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
interface ModelExecutionContract { sendMessage(opts): AsyncGenerator<AgentMessage> }
|
|
361
|
+
// Boot from a different snapshot per invocation (per-invocation override):
|
|
362
|
+
await client.invoke("my-workflow", input, { snapshot: "<run-id-or-name>" });
|
|
144
363
|
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
type AgentMessage = AgentMessageInit | AgentMessageText | AgentMessageToolUse | ...
|
|
364
|
+
// Or set a default at registration time:
|
|
365
|
+
defineWorkflow({ run, saveSnapshot: true });
|
|
148
366
|
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
367
|
+
// Browse / clean up:
|
|
368
|
+
const snaps = await client.listSnapshots({ workflow: "my-workflow", limit: 50 });
|
|
369
|
+
await client.deleteSnapshot(snaps[0].runId);
|
|
152
370
|
```
|
|
371
|
+
|
|
372
|
+
CLI equivalents: `agentc snapshot list` / `agentc snapshot delete <run-id>`.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Authentication
|
|
377
|
+
|
|
378
|
+
The SDK accepts a Bearer API key (`ac_…`). Mint one from the dashboard:
|
|
379
|
+
sign in at `<server-url>/login`, then **Settings → API Keys → Create key**.
|
|
380
|
+
|
|
381
|
+
Default scopes (`read + invoke`) are right for a CI / dispatch caller. Tick
|
|
382
|
+
`admin` only if this key needs to register templates, mint other keys, or
|
|
383
|
+
manage secrets.
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
const client = new AgentComposeClient(
|
|
387
|
+
process.env.AGENT_COMPOSE_URL!,
|
|
388
|
+
process.env.AGENT_COMPOSE_API_KEY!,
|
|
389
|
+
);
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
The dashboard itself uses the cookie-bound session path; the SDK is for
|
|
393
|
+
programmatic / server-to-server callers.
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Public exports — quick reference
|
|
398
|
+
|
|
399
|
+
| Export | What |
|
|
400
|
+
|---|---|
|
|
401
|
+
| `defineWorkflow` | Attach metadata to a workflow `run` function |
|
|
402
|
+
| `defineRuntime` | Wrap an agent execution provider as an `AgentRuntime` |
|
|
403
|
+
| `defineSandboxEnvironment` | Sugar for declaring a workflow whose primary purpose is to build a snapshot for others to boot from |
|
|
404
|
+
| `runAgent` / `agentLoop` | Embed an LLM loop inside a workflow |
|
|
405
|
+
| `runWorkflow` | Local engine for running a workflow in-process (test harness) |
|
|
406
|
+
| `bundleWorkflow` | Resolve + inline a workflow's runtime sources for registration |
|
|
407
|
+
| `claudeRuntime` / `createClaudeRuntime` / `ClaudeRunner` | Built-in Claude Code runtime + factory |
|
|
408
|
+
| `AgentComposeClient` | HTTP client — register, invoke, cancel, stream logs, factories, snapshots, secrets, API keys, usage |
|
|
409
|
+
| `AgentComposeError` | Thrown by every non-2xx HTTP response |
|
|
410
|
+
| `parseAgentStatus` / `parseAgentResponse` / `AgentStatusSchema` / `AgentMessageSchema` | Protocol parsers |
|
|
411
|
+
| `parseSseStream` | Generic SSE chunk decoder (used by `streamRunLogs`) |
|
|
412
|
+
| `createSandbox` / `reconnectSandbox` / `killAllSandboxes` / `killSandboxById` / `getSandboxQuotas` / `listOwnedSandboxes` / `deleteSandboxSnapshot` | Sandbox-provider helpers (Vercel + E2B) |
|
|
413
|
+
|
|
414
|
+
Type exports: `WorkflowFn`, `WorkflowCtx`, `WorkflowDefinition`,
|
|
415
|
+
`WorkflowHooks`, `AgentBudget`, `AgentRuntime`, `RuntimeOptions`,
|
|
416
|
+
`ModelExecutionContract`, `McpServerConfig`, `AgentMessage` (and its
|
|
417
|
+
variants), `AgentStatus`, `RunStatus`, `RegisterResult`, `RunEvent`,
|
|
418
|
+
`FactoryRow`, `SnapshotListEntry`, `ApiKey`, `ApiKeyCreated`,
|
|
419
|
+
`UsageRollupRow`, `UsageResponse`, `CancelRunResponse`, `AgentLoopResult`,
|
|
420
|
+
`RunAgentOpts`, `SandboxProvider`, `DesktopSandboxProvider`,
|
|
421
|
+
`SandboxNetworkPolicy`, `SandboxCreateOpts`, `OwnedSandbox`,
|
|
422
|
+
`BundledWorkflow`.
|
|
423
|
+
|
|
424
|
+
For the canonical signatures, follow your IDE's go-to-definition into
|
|
425
|
+
`@agent-compose/sdk` — `sdk/src/index.ts` is the public surface and the
|
|
426
|
+
files it re-exports from carry full inline docstrings.
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## Errors
|
|
431
|
+
|
|
432
|
+
All non-2xx HTTP responses throw `AgentComposeError(status, message)`. The
|
|
433
|
+
`message` is the server's `{ error: string }` body when present, falling
|
|
434
|
+
back to the HTTP status text:
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
import { AgentComposeError } from "@agent-compose/sdk";
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
await client.invoke("missing-workflow");
|
|
441
|
+
} catch (err) {
|
|
442
|
+
if (err instanceof AgentComposeError && err.status === 404) {
|
|
443
|
+
// template not registered
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
`invokeAndWait` throws `AgentComposeError(504, …)` on timeout for symmetry.
|