@agent-compose/sdk 0.2.1 → 0.2.3

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/src/client.ts ADDED
@@ -0,0 +1,446 @@
1
+ /**
2
+ * AgentComposeClient — HTTP client for the agent-compose server API.
3
+ *
4
+ * Templates always live inside a factory (project). Methods that touch a
5
+ * specific template (`register`, `invoke`, `setSecret`, `listSecrets`,
6
+ * `deleteSecret`) take an optional `factorySlug`; when omitted, they target
7
+ * the team's auto-created `default` factory. There is no second URL space
8
+ * for "templates without a factory" — every workflow belongs to exactly one.
9
+ *
10
+ * `register()` accepts pre-built sources — use the CLI (`agent-compose
11
+ * register`) or build sources yourself and pass them directly.
12
+ */
13
+
14
+ import { ofetch } from "ofetch";
15
+ import { AgentComposeError } from "./errors.js";
16
+ import { parseSseStream } from "./sse.js";
17
+ import type { RunEvent } from "./types/events.js";
18
+ import type { SandboxNetworkPolicy } from "./sandbox.js";
19
+
20
+ /** UUID-v4-ish — matches the server-side predicate. Used to auto-detect
21
+ * that `process.env.RUN_ID` was injected by the runner sandbox (rather
22
+ * than being set by accident), so we only propagate it when it looks real. */
23
+ const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
24
+
25
+ /** Default factory slug — every team gets one auto-created at sign-up.
26
+ * Methods that don't take an explicit `factorySlug` resolve to this. */
27
+ const DEFAULT_FACTORY = "default";
28
+
29
+ /** Read-once cached parentRunId from `process.env.RUN_ID` at module load.
30
+ * `null` if unset or malformed. Exported so tests can override-and-reset. */
31
+ function detectAmbientParentRunId(): string | null {
32
+ const envRunId = (typeof process !== "undefined" ? process.env?.RUN_ID : undefined);
33
+ return envRunId && UUID_REGEX.test(envRunId) ? envRunId : null;
34
+ }
35
+
36
+ /** Build the `/api/v1/factories/:slug/templates/...` prefix. Slug + name are
37
+ * URL-encoded as defense-in-depth: today's server-side patterns
38
+ * (`^[a-z][a-z0-9-]*$`) can't produce reserved characters, but a future
39
+ * relaxation would otherwise quietly become an injection vector. */
40
+ function templatePath(factorySlug: string, ...rest: string[]): string {
41
+ const tail = rest.length > 0
42
+ ? "/" + rest.map(encodeURIComponent).join("/")
43
+ : "";
44
+ return `/api/v1/factories/${encodeURIComponent(factorySlug)}/templates${tail}`;
45
+ }
46
+
47
+ export interface RegisterResult {
48
+ id: string;
49
+ name: string;
50
+ version: string;
51
+ runtimes?: Array<{ name: string; version: string; id: string }>;
52
+ }
53
+
54
+ export interface RunStatus {
55
+ id: string;
56
+ status: "running" | "success" | "failed" | "abandoned" | "canceled";
57
+ output?: Record<string, unknown>;
58
+ }
59
+
60
+ /** Row shape returned by `GET /api-keys`. */
61
+ export interface ApiKey {
62
+ object: "api_key";
63
+ id: string;
64
+ name: string | null;
65
+ last4: string | null;
66
+ scopes: string[];
67
+ teamId: string;
68
+ createdByUserId: string | null;
69
+ /** Non-null when the key is restricted to a single factory. */
70
+ factoryId: string | null;
71
+ createdAt: string;
72
+ expiresAt: string | null;
73
+ lastUsedAt: string | null;
74
+ revokedAt: string | null;
75
+ }
76
+
77
+ /** Response from `POST /api-keys`. The `key` field is the plaintext token —
78
+ * shown once at creation, never retrievable again. */
79
+ export interface ApiKeyCreated extends ApiKey { key: string }
80
+
81
+ /** Single rollup row from `GET /api/v1/usage`. */
82
+ export interface UsageRollupRow {
83
+ eventType: string;
84
+ unit: string;
85
+ total: number;
86
+ tags: Record<string, unknown>;
87
+ }
88
+
89
+ /** Response from `GET /api/v1/usage`. */
90
+ export interface UsageResponse {
91
+ object: "list";
92
+ data: UsageRollupRow[];
93
+ has_more: boolean;
94
+ from: string | null;
95
+ to: string | null;
96
+ }
97
+
98
+ /** Response from `POST /api/v1/workflows/:id/cancel`. The endpoint is
99
+ * idempotent: cancelling a run that's already terminal returns its current
100
+ * outcome verbatim (rather than throwing or pretending it just canceled),
101
+ * so `status` widens to every terminal value the server might surface. */
102
+ export interface CancelRunResponse {
103
+ runId: string;
104
+ status: "canceled" | "success" | "failed" | "abandoned";
105
+ canceledAt: string;
106
+ }
107
+
108
+ export interface SnapshotListEntry {
109
+ runId: string;
110
+ workflow: string | null;
111
+ version: string | null;
112
+ vercelSnapshotId: string;
113
+ endedAt: string | null;
114
+ }
115
+
116
+ export class AgentComposeClient {
117
+ private readonly fetch: typeof ofetch;
118
+ private readonly baseUrl: string;
119
+ private readonly apiKey: string;
120
+
121
+ constructor(baseUrl: string, apiKey: string) {
122
+ this.baseUrl = baseUrl.replace(/\/$/, "");
123
+ this.apiKey = apiKey;
124
+ this.fetch = ofetch.create({
125
+ baseURL: this.baseUrl,
126
+ headers: { Authorization: `Bearer ${apiKey}` },
127
+ async onResponseError({ response }) {
128
+ const body = response._data as { error?: string } | undefined;
129
+ throw new AgentComposeError(response.status, body?.error ?? response.statusText);
130
+ },
131
+ });
132
+ }
133
+
134
+ /** Register (or update) a workflow template inside a factory. Defaults to
135
+ * the team's `default` factory when `factorySlug` is omitted. */
136
+ register(payload: {
137
+ name: string;
138
+ source: string;
139
+ version?: string;
140
+ schedule?: string;
141
+ runtimes?: Array<{ name: string; source: string }>;
142
+ networkPolicy?: unknown;
143
+ placeholders?: Record<string, string>;
144
+ /** Reference to a snapshot the runner should boot from at run start.
145
+ * A run UUID, workflow name, or `name@version`. The referenced
146
+ * workflow must have been registered with `--build` (or any prior
147
+ * successful run with `saveSnapshot: true`). Per-invocation
148
+ * `invoke({ snapshot })` overrides this default. */
149
+ snapshot?: string;
150
+ /** If true, runs default to capturing a long-lived sandbox snapshot on
151
+ * success. Individual invocations can override via
152
+ * `invoke(..., { saveSnapshot })`. */
153
+ saveSnapshot?: boolean;
154
+ /** Factory slug. Defaults to `"default"`. */
155
+ factorySlug?: string;
156
+ }): Promise<RegisterResult> {
157
+ const { factorySlug = DEFAULT_FACTORY, ...body } = payload;
158
+ return this.fetch(templatePath(factorySlug), { method: "POST", body });
159
+ }
160
+
161
+ /** Invoke a workflow. Returns run ID immediately — workflow runs asynchronously.
162
+ *
163
+ * `snapshot`: per-invocation override of the template-level `snapshot`
164
+ * field. Same forms — a run UUID, a workflow name, or `name@version`.
165
+ * Use this when one template needs to boot from many different
166
+ * snapshots (e.g. a benchmark workflow running against a different
167
+ * starting state per invocation). Does not change the template's
168
+ * registered default; only affects this run.
169
+ *
170
+ * `saveSnapshot`: `true` overrides the workflow default; `false` opts
171
+ * out. When the run captures a snapshot, its id is stamped on the row
172
+ * and can be referenced as the `snapshot` field on other workflows.
173
+ *
174
+ * `parentRunId`: links the new run to another of the same account's
175
+ * currently-running runs. When omitted, the client auto-detects from
176
+ * `process.env.RUN_ID` — set by the runner sandbox on every dispatch,
177
+ * so workflows invoking other workflows get the parent/child tree for
178
+ * free. Pass `null` to suppress auto-detection (e.g. invoking a top-level
179
+ * sibling workflow from inside a runner for a reason unrelated to the
180
+ * current run). Explicit non-null wins over auto-detection.
181
+ *
182
+ * `factorySlug`: defaults to `"default"`. */
183
+ invoke(
184
+ name: string,
185
+ input?: Record<string, unknown>,
186
+ opts?: {
187
+ snapshot?: string;
188
+ saveSnapshot?: boolean;
189
+ parentRunId?: string | null;
190
+ factorySlug?: string;
191
+ networkPolicy?: SandboxNetworkPolicy;
192
+ placeholders?: Record<string, string>;
193
+ },
194
+ ): Promise<{ id: string }> {
195
+ const parentRunId = opts?.parentRunId === undefined
196
+ ? detectAmbientParentRunId()
197
+ : opts.parentRunId;
198
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
199
+ return this.fetch(templatePath(factorySlug, name, "invoke"), {
200
+ method: "POST",
201
+ body: {
202
+ input,
203
+ ...(opts?.snapshot !== undefined ? { snapshot: opts.snapshot } : {}),
204
+ ...(opts?.saveSnapshot !== undefined ? { saveSnapshot: opts.saveSnapshot } : {}),
205
+ ...(parentRunId ? { parentRunId } : {}),
206
+ ...(opts?.networkPolicy !== undefined ? { networkPolicy: opts.networkPolicy } : {}),
207
+ ...(opts?.placeholders !== undefined ? { placeholders: opts.placeholders } : {}),
208
+ },
209
+ });
210
+ }
211
+
212
+ /** Invoke a workflow and wait for it to settle (success / failed / abandoned).
213
+ * Polls `getStatus` on a fixed interval. Rejects with `AgentComposeError`
214
+ * if the run settles non-success, or a plain `Error` on timeout.
215
+ *
216
+ * Defaults: `timeoutMs = 30min`, `pollIntervalMs = 1000ms`. Tune down for
217
+ * tests, tune up for long-running workflows. The parent-child auto-
218
+ * detection from `invoke()` applies here too. */
219
+ async invokeAndWait(
220
+ name: string,
221
+ input?: Record<string, unknown>,
222
+ opts?: {
223
+ snapshot?: string;
224
+ saveSnapshot?: boolean;
225
+ parentRunId?: string | null;
226
+ factorySlug?: string;
227
+ networkPolicy?: SandboxNetworkPolicy;
228
+ placeholders?: Record<string, string>;
229
+ timeoutMs?: number;
230
+ pollIntervalMs?: number;
231
+ },
232
+ ): Promise<RunStatus> {
233
+ const timeoutMs = opts?.timeoutMs ?? 30 * 60 * 1000;
234
+ const pollMs = opts?.pollIntervalMs ?? 1000;
235
+ const { id: runId } = await this.invoke(name, input, {
236
+ ...(opts?.snapshot !== undefined ? { snapshot: opts.snapshot } : {}),
237
+ ...(opts?.saveSnapshot !== undefined ? { saveSnapshot: opts.saveSnapshot } : {}),
238
+ ...(opts?.parentRunId !== undefined ? { parentRunId: opts.parentRunId } : {}),
239
+ ...(opts?.factorySlug !== undefined ? { factorySlug: opts.factorySlug } : {}),
240
+ ...(opts?.networkPolicy !== undefined ? { networkPolicy: opts.networkPolicy } : {}),
241
+ ...(opts?.placeholders !== undefined ? { placeholders: opts.placeholders } : {}),
242
+ });
243
+ const deadline = Date.now() + timeoutMs;
244
+ while (Date.now() < deadline) {
245
+ const status = await this.getStatus(runId);
246
+ if (status.status === "success" || status.status === "failed" || status.status === "abandoned") {
247
+ return status;
248
+ }
249
+ await new Promise((r) => setTimeout(r, pollMs));
250
+ }
251
+ // Use AgentComposeError (not plain Error) so catch-blocks handling SDK
252
+ // transport failures also handle timeouts uniformly. HTTP 504 is the
253
+ // closest idiomatic status for "upstream didn't answer in time."
254
+ throw new AgentComposeError(504, `invokeAndWait: run ${runId} did not settle within ${timeoutMs}ms`);
255
+ }
256
+
257
+ /** List runs this account has captured snapshots for. */
258
+ async listSnapshots(opts?: { workflow?: string; limit?: number }): Promise<SnapshotListEntry[]> {
259
+ const q = new URLSearchParams();
260
+ if (opts?.workflow) q.set("workflow", opts.workflow);
261
+ if (opts?.limit != null) q.set("limit", String(opts.limit));
262
+ const body = await this.fetch<{ object: "list"; data: SnapshotListEntry[]; has_more: boolean }>(
263
+ `/api/v1/snapshots${q.toString() ? `?${q}` : ""}`,
264
+ );
265
+ return body.data;
266
+ }
267
+
268
+ /** Delete the snapshot captured by a specific run. Frees Vercel storage. */
269
+ deleteSnapshot(runId: string): Promise<void> {
270
+ return this.fetch(`/api/v1/workflows/${runId}/snapshot`, { method: "DELETE" });
271
+ }
272
+
273
+ /** Poll run status. */
274
+ getStatus(runId: string): Promise<RunStatus> {
275
+ return this.fetch(`/api/v1/workflows/${runId}/status`);
276
+ }
277
+
278
+ /** List registered workflow templates. When `factorySlug` is supplied,
279
+ * scopes to that factory; otherwise returns every template the team can
280
+ * see across every factory in one round trip — each row carries its
281
+ * `factorySlug` so callers can route per-template actions to the right
282
+ * factory. */
283
+ async listTemplates(opts?: { factorySlug?: string }): Promise<Array<{ name: string; version: string; factorySlug: string }>> {
284
+ const path = opts?.factorySlug
285
+ ? templatePath(opts.factorySlug)
286
+ : "/api/v1/templates";
287
+ const body = await this.fetch<{ templates: Array<{ name: string; version: string; factorySlug: string }> }>(path);
288
+ return body.templates;
289
+ }
290
+
291
+ // ── Factories ──────────────────────────────────────────────────────────────
292
+ // Projects within a team. Workflows + secrets belong to exactly one factory.
293
+
294
+ /** List factories for the caller's team. */
295
+ async listFactories(): Promise<FactoryRow[]> {
296
+ const body = await this.fetch<{ factories: FactoryRow[] }>("/api/v1/factories");
297
+ return body.factories;
298
+ }
299
+
300
+ /** Create a factory. `slug` must be lowercase kebab-case and unique
301
+ * within the team. */
302
+ createFactory(payload: { slug: string; name: string; description?: string }): Promise<FactoryRow> {
303
+ return this.fetch<FactoryRow>("/api/v1/factories", { method: "POST", body: payload });
304
+ }
305
+
306
+ /** Get a factory by slug. */
307
+ getFactory(slug: string): Promise<FactoryRow> {
308
+ return this.fetch<FactoryRow>(`/api/v1/factories/${encodeURIComponent(slug)}`);
309
+ }
310
+
311
+ /** Rename or describe a factory. */
312
+ updateFactory(slug: string, updates: { name?: string; description?: string }): Promise<FactoryRow> {
313
+ return this.fetch<FactoryRow>(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "PATCH", body: updates });
314
+ }
315
+
316
+ /** Delete a factory. Refuses `default` and any factory still
317
+ * containing workflows. */
318
+ deleteFactory(slug: string): Promise<void> {
319
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "DELETE" });
320
+ }
321
+
322
+ // ── Secrets ────────────────────────────────────────────────────────────────
323
+ // Scoped to (factory, workflow, key). Stored in GCP Secret Manager;
324
+ // `factorySlug` defaults to `"default"`.
325
+
326
+ /** Create or update a workflow secret. Value is stored in GCP Secret Manager. */
327
+ setSecret(workflowName: string, key: string, value: string, opts?: { factorySlug?: string }): Promise<{ key: string }> {
328
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
329
+ return this.fetch(templatePath(factorySlug, workflowName, "secrets"), {
330
+ method: "POST",
331
+ body: { key, value },
332
+ });
333
+ }
334
+
335
+ /** List secret keys registered for a workflow (metadata only — values are never returned). */
336
+ async listSecrets(workflowName: string, opts?: { factorySlug?: string }): Promise<Array<{ key: string; createdAt: string; updatedAt: string }>> {
337
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
338
+ const body = await this.fetch<{ secrets: Array<{ secretKey: string; createdAt: string; updatedAt: string }> }>(
339
+ templatePath(factorySlug, workflowName, "secrets"),
340
+ );
341
+ return body.secrets.map(s => ({ key: s.secretKey, createdAt: s.createdAt, updatedAt: s.updatedAt }));
342
+ }
343
+
344
+ /** Delete a workflow secret. */
345
+ deleteSecret(workflowName: string, key: string, opts?: { factorySlug?: string }): Promise<void> {
346
+ const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
347
+ return this.fetch(templatePath(factorySlug, workflowName, "secrets", key), { method: "DELETE" });
348
+ }
349
+
350
+ // ── API keys ───────────────────────────────────────────────────────────────
351
+ // Both endpoints require an admin-scoped key as the bearer token.
352
+
353
+ /** Create a new API key on the caller's team. The plaintext `key` is
354
+ * returned once — it cannot be retrieved later.
355
+ *
356
+ * When `factorySlug` is set, the new key is restricted to that factory.
357
+ * Factory-scoped keys can only mint other keys bound to the same factory. */
358
+ createApiKey(input: {
359
+ name?: string;
360
+ scopes?: string[];
361
+ expiresAt?: string;
362
+ factorySlug?: string;
363
+ }): Promise<ApiKeyCreated> {
364
+ return this.fetch<ApiKeyCreated>("/api-keys", { method: "POST", body: input });
365
+ }
366
+
367
+ /** List API keys on the caller's team (metadata only — plaintext keys are
368
+ * never returned). */
369
+ async listApiKeys(): Promise<ApiKey[]> {
370
+ const body = await this.fetch<{ object: "list"; data: ApiKey[]; has_more: boolean }>("/api-keys");
371
+ return body.data;
372
+ }
373
+
374
+ // ── Usage ──────────────────────────────────────────────────────────────────
375
+
376
+ /** Billable usage rollup for the caller's team over the [from, to) window. */
377
+ getUsage(from: Date, to: Date): Promise<UsageResponse> {
378
+ const qs = `?from=${encodeURIComponent(from.toISOString())}&to=${encodeURIComponent(to.toISOString())}`;
379
+ return this.fetch<UsageResponse>(`/api/v1/usage${qs}`);
380
+ }
381
+
382
+ // ── Run control ────────────────────────────────────────────────────────────
383
+
384
+ /** Cancel an in-progress run. Idempotent: cancelling an already-terminal
385
+ * run returns the current state without throwing. The server stamps the
386
+ * run as `canceled` and kills any live sandboxes. */
387
+ cancelRun(runId: string): Promise<CancelRunResponse> {
388
+ return this.fetch<CancelRunResponse>(
389
+ `/api/v1/workflows/${encodeURIComponent(runId)}/cancel`,
390
+ { method: "POST" },
391
+ );
392
+ }
393
+
394
+ /** Stream lifecycle events for a run as an async iterable. Yields parsed
395
+ * `RunEvent` payloads in order; caller breaks on terminal events
396
+ * (`run_complete`, `run_failed`, `run_canceled`).
397
+ *
398
+ * `lastEventId` enables resume — pass the highest `seq` you've already
399
+ * processed to receive only events you missed.
400
+ *
401
+ * `signal` can be used to abort the stream from the caller side.
402
+ *
403
+ * Uses raw `fetch` (not ofetch) because SSE requires access to the
404
+ * response's `ReadableStream`, which ofetch consumes when parsing. Auth
405
+ * + base URL are still sourced from the same constructor inputs, and
406
+ * non-2xx responses throw the same `AgentComposeError`. */
407
+ async *streamRunLogs(
408
+ runId: string,
409
+ opts?: { lastEventId?: number; signal?: AbortSignal },
410
+ ): AsyncGenerator<RunEvent> {
411
+ const headers: Record<string, string> = { Authorization: `Bearer ${this.apiKey}` };
412
+ if (opts?.lastEventId && opts.lastEventId > 0) {
413
+ headers["Last-Event-ID"] = String(opts.lastEventId);
414
+ }
415
+ const res = await fetch(`${this.baseUrl}/api/v1/workflows/${encodeURIComponent(runId)}/stream`, {
416
+ headers,
417
+ ...(opts?.signal ? { signal: opts.signal } : {}),
418
+ });
419
+ if (!res.ok || !res.body) {
420
+ let message = res.statusText;
421
+ try {
422
+ const body = await res.json() as { error?: string };
423
+ if (body.error) message = body.error;
424
+ } catch { /* non-JSON error body — fall back to statusText */ }
425
+ throw new AgentComposeError(res.status, message);
426
+ }
427
+ for await (const ev of parseSseStream(res.body)) {
428
+ // The server's `data` payload already includes `event`, `runId`, `seq`,
429
+ // `at`, and the per-event payload — so `ev.data` IS the `RunEvent`.
430
+ // Cast directly; unknown future event names flow through untyped, and
431
+ // callers using the discriminated union see them via the default branch.
432
+ yield ev.data as unknown as RunEvent;
433
+ }
434
+ }
435
+ }
436
+
437
+ /** A factory: a project-level grouping of workflows inside a team. */
438
+ export interface FactoryRow {
439
+ id: string;
440
+ teamId: string;
441
+ slug: string;
442
+ name: string;
443
+ description: string | null;
444
+ createdAt: string;
445
+ updatedAt: string;
446
+ }
package/src/env.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ // Allow importing .md files as plain text (via Bun's static text import)
2
+ declare module "*.md" {
3
+ const content: string;
4
+ export default content;
5
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,10 @@
1
+ /** Thrown by AgentComposeClient when the server returns a non-2xx response. */
2
+ export class AgentComposeError extends Error {
3
+ constructor(
4
+ public readonly status: number,
5
+ message: string,
6
+ ) {
7
+ super(message);
8
+ this.name = "AgentComposeError";
9
+ }
10
+ }
package/src/index.ts ADDED
@@ -0,0 +1,111 @@
1
+ /// <reference path="./env.d.ts" />
2
+
3
+ /**
4
+ * @agent-compose/sdk
5
+ *
6
+ * Tools for defining runtimes and workflows, registering/invoking them
7
+ * against an agent-compose server, and running LLM agent loops inside a
8
+ * workflow via `runAgent(opts)`.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { defineWorkflow, runAgent, AgentComposeClient } from "@agent-compose/sdk";
13
+ * ```
14
+ */
15
+
16
+ // Factory functions
17
+ export { defineRuntime } from "./types/runtime.js";
18
+ export { defineWorkflow } from "./types/workflow.js";
19
+ export type { WorkflowDefinition } from "./types/workflow.js";
20
+ export { defineSandboxEnvironment } from "./types/sandbox-environment.js";
21
+ export type { SandboxEnvironmentDefinition } from "./types/sandbox-environment.js";
22
+
23
+ // Runtime types
24
+ export type {
25
+ AgentRuntime,
26
+ McpServerConfig,
27
+ ModelExecutionContract,
28
+ RuntimeOptions,
29
+ } from "./types/runtime.js";
30
+
31
+ // Workflow types and runtime utilities
32
+ export type {
33
+ WorkflowFn,
34
+ WorkflowCtx,
35
+ WorkflowRun,
36
+ AgentBudget,
37
+ WorkflowHooks,
38
+ } from "./types/workflow.js";
39
+
40
+ // Protocol types (agent-loop input/output shapes)
41
+ export type {
42
+ AgentMessage,
43
+ AgentMessageInit,
44
+ AgentMessageText,
45
+ AgentMessageThinking,
46
+ AgentMessageToolUse,
47
+ AgentMessageToolResult,
48
+ AgentMessageDone,
49
+ AgentMessageError,
50
+ AgentMessageUsage,
51
+ AgentStatus,
52
+ } from "./types/protocol.js";
53
+
54
+ // Sandbox types
55
+ export type {
56
+ SandboxProvider,
57
+ DesktopSandboxProvider,
58
+ } from "./types/sandbox.js";
59
+
60
+ // HTTP client
61
+ export { AgentComposeClient } from "./client.js";
62
+ export type {
63
+ RegisterResult, RunStatus, FactoryRow, SnapshotListEntry,
64
+ ApiKey, ApiKeyCreated,
65
+ UsageRollupRow, UsageResponse,
66
+ CancelRunResponse,
67
+ } from "./client.js";
68
+
69
+ // SSE parser — exposed so tests and downstream callers can reuse it.
70
+ export { parseSseStream } from "./sse.js";
71
+
72
+ // Errors and utilities
73
+ export { AgentComposeError } from "./errors.js";
74
+ export { formatError } from "./utils/errors.js";
75
+
76
+ // Source discovery + bundling utilities
77
+ export { discoverRuntimeName } from "./utils/discovery.js";
78
+ export { bundleWorkflow } from "./utils/bundler.js";
79
+ export type { BundledWorkflow } from "./utils/bundler.js";
80
+
81
+ // Zod schemas
82
+ export { AgentStatusSchema } from "./utils/schemas.js";
83
+
84
+ // Built-in runtimes
85
+ // Note: openAIDesktopRuntime is NOT exported here — it depends on sharp (native bindings)
86
+ // which can't be cross-compiled. Import directly: import openAIDesktopRuntime from "@agent-compose/sdk/runtimes/openai-desktop.js"
87
+ export { createClaudeRuntime, ClaudeRunner } from "./runtimes/claude.js";
88
+ export type { ClaudeRuntimeConfig } from "./runtimes/claude.js";
89
+ export { default as claudeRuntime } from "./runtimes/claude.js";
90
+
91
+ // Streaming event contract
92
+ export type { RunEvent } from "./types/events.js";
93
+
94
+ // Sandbox providers
95
+ export { createSandbox, reconnectSandbox, killAllSandboxes, killSandboxById,
96
+ getSandboxQuotas, listOwnedSandboxes, deleteSandboxSnapshot,
97
+ makeSandboxProvider, makeDesktopSandboxProvider,
98
+ parseSseExecStream, AGENT_COMPOSE_TAG } from "./sandbox.js";
99
+ export type { SandboxCreateOpts, SandboxNetworkPolicy, OwnedSandbox } from "./sandbox.js";
100
+
101
+ // Workflow engine
102
+ export { runWorkflow, WorkflowError, EngineError, classifyError, parseNameVersion } from "./workflows/engine.js";
103
+ export type { WorkflowResult, EngineSubsystem } from "./workflows/engine.js";
104
+
105
+ // Agent loop — for workflows that embed an LLM agent in their run() body.
106
+ export { agentLoop, parseAgentStatus, DEFAULT_CLAUDE_MODEL } from "./agent/agent-loop.js";
107
+ export type { AgentLoopResult } from "./agent/agent-loop.js";
108
+ export { runAgent } from "./agent/run-agent.js";
109
+ export type { RunAgentOpts } from "./agent/run-agent.js";
110
+ export { AgentMessageSchema, parseAgentResponse } from "./agent/protocol.js";
111
+ export { importSourceModule, TMP_DIR, LATEST_VERSION } from "./utils/source-loader.js";