@agent-compose/sdk 0.1.0 → 0.2.1
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 +276 -94
- package/dist/client.d.ts +178 -21
- package/dist/index.d.ts +2 -1
- package/dist/index.js +146 -21
- package/dist/runtimes/openai-desktop.js +145 -21
- 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
|
@@ -28,8 +28,8 @@ function defineWorkflow(def) {
|
|
|
28
28
|
return Object.assign(def.run, {
|
|
29
29
|
networkPolicy: def.networkPolicy,
|
|
30
30
|
placeholders: def.placeholders,
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
snapshot: def.snapshot,
|
|
32
|
+
saveSnapshot: def.saveSnapshot
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
// src/sandbox.ts
|
|
@@ -350,7 +350,7 @@ function defineSandboxEnvironment(env) {
|
|
|
350
350
|
throw new Error(`defineSandboxEnvironment(${env.name}): 'setup' must be a function`);
|
|
351
351
|
}
|
|
352
352
|
return defineWorkflow({
|
|
353
|
-
|
|
353
|
+
saveSnapshot: env.saveSnapshot ?? true,
|
|
354
354
|
run: () => env.setup(makeLocalSandboxProvider())
|
|
355
355
|
});
|
|
356
356
|
}
|
|
@@ -367,18 +367,74 @@ class AgentComposeError extends Error {
|
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
+
// src/sse.ts
|
|
371
|
+
async function* parseSseStream(body) {
|
|
372
|
+
const reader = body.getReader();
|
|
373
|
+
const decoder = new TextDecoder;
|
|
374
|
+
let buffer = "";
|
|
375
|
+
let event = "";
|
|
376
|
+
let seq = 0;
|
|
377
|
+
let dataLines = [];
|
|
378
|
+
try {
|
|
379
|
+
while (true) {
|
|
380
|
+
const { done, value } = await reader.read();
|
|
381
|
+
if (done)
|
|
382
|
+
break;
|
|
383
|
+
buffer += decoder.decode(value, { stream: true });
|
|
384
|
+
const lines = buffer.split(`
|
|
385
|
+
`);
|
|
386
|
+
buffer = lines.pop() ?? "";
|
|
387
|
+
for (const line of lines) {
|
|
388
|
+
if (line.startsWith("id:")) {
|
|
389
|
+
seq = Number(line.slice(3).trim());
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (line.startsWith("event:")) {
|
|
393
|
+
event = line.slice(6).trim();
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (line.startsWith("data:")) {
|
|
397
|
+
dataLines.push(line.slice(5).trim());
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (line === "" && dataLines.length > 0) {
|
|
401
|
+
try {
|
|
402
|
+
const data = JSON.parse(dataLines.join(`
|
|
403
|
+
`));
|
|
404
|
+
yield { id: seq, event, data };
|
|
405
|
+
} catch {}
|
|
406
|
+
event = "";
|
|
407
|
+
seq = 0;
|
|
408
|
+
dataLines = [];
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
} finally {
|
|
413
|
+
reader.releaseLock();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
370
417
|
// src/client.ts
|
|
371
418
|
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
419
|
+
var DEFAULT_FACTORY = "default";
|
|
372
420
|
function detectAmbientParentRunId() {
|
|
373
421
|
const envRunId = typeof process !== "undefined" ? process.env?.RUN_ID : undefined;
|
|
374
422
|
return envRunId && UUID_REGEX.test(envRunId) ? envRunId : null;
|
|
375
423
|
}
|
|
424
|
+
function templatePath(factorySlug, ...rest) {
|
|
425
|
+
const tail = rest.length > 0 ? "/" + rest.map(encodeURIComponent).join("/") : "";
|
|
426
|
+
return `/api/v1/factories/${encodeURIComponent(factorySlug)}/templates${tail}`;
|
|
427
|
+
}
|
|
376
428
|
|
|
377
429
|
class AgentComposeClient {
|
|
378
430
|
fetch;
|
|
431
|
+
baseUrl;
|
|
432
|
+
apiKey;
|
|
379
433
|
constructor(baseUrl, apiKey) {
|
|
434
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
435
|
+
this.apiKey = apiKey;
|
|
380
436
|
this.fetch = ofetch.create({
|
|
381
|
-
baseURL: baseUrl
|
|
437
|
+
baseURL: this.baseUrl,
|
|
382
438
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
383
439
|
async onResponseError({ response }) {
|
|
384
440
|
const body = response._data;
|
|
@@ -387,16 +443,21 @@ class AgentComposeClient {
|
|
|
387
443
|
});
|
|
388
444
|
}
|
|
389
445
|
register(payload) {
|
|
390
|
-
|
|
446
|
+
const { factorySlug = DEFAULT_FACTORY, ...body } = payload;
|
|
447
|
+
return this.fetch(templatePath(factorySlug), { method: "POST", body });
|
|
391
448
|
}
|
|
392
449
|
invoke(name, input, opts) {
|
|
393
450
|
const parentRunId = opts?.parentRunId === undefined ? detectAmbientParentRunId() : opts.parentRunId;
|
|
394
|
-
|
|
451
|
+
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
452
|
+
return this.fetch(templatePath(factorySlug, name, "invoke"), {
|
|
395
453
|
method: "POST",
|
|
396
454
|
body: {
|
|
397
455
|
input,
|
|
398
456
|
...opts?.snapshot !== undefined ? { snapshot: opts.snapshot } : {},
|
|
399
|
-
...
|
|
457
|
+
...opts?.saveSnapshot !== undefined ? { saveSnapshot: opts.saveSnapshot } : {},
|
|
458
|
+
...parentRunId ? { parentRunId } : {},
|
|
459
|
+
...opts?.networkPolicy !== undefined ? { networkPolicy: opts.networkPolicy } : {},
|
|
460
|
+
...opts?.placeholders !== undefined ? { placeholders: opts.placeholders } : {}
|
|
400
461
|
}
|
|
401
462
|
});
|
|
402
463
|
}
|
|
@@ -405,7 +466,11 @@ class AgentComposeClient {
|
|
|
405
466
|
const pollMs = opts?.pollIntervalMs ?? 1000;
|
|
406
467
|
const { id: runId } = await this.invoke(name, input, {
|
|
407
468
|
...opts?.snapshot !== undefined ? { snapshot: opts.snapshot } : {},
|
|
408
|
-
...opts?.
|
|
469
|
+
...opts?.saveSnapshot !== undefined ? { saveSnapshot: opts.saveSnapshot } : {},
|
|
470
|
+
...opts?.parentRunId !== undefined ? { parentRunId: opts.parentRunId } : {},
|
|
471
|
+
...opts?.factorySlug !== undefined ? { factorySlug: opts.factorySlug } : {},
|
|
472
|
+
...opts?.networkPolicy !== undefined ? { networkPolicy: opts.networkPolicy } : {},
|
|
473
|
+
...opts?.placeholders !== undefined ? { placeholders: opts.placeholders } : {}
|
|
409
474
|
});
|
|
410
475
|
const deadline = Date.now() + timeoutMs;
|
|
411
476
|
while (Date.now() < deadline) {
|
|
@@ -432,19 +497,78 @@ class AgentComposeClient {
|
|
|
432
497
|
getStatus(runId) {
|
|
433
498
|
return this.fetch(`/api/v1/workflows/${runId}/status`);
|
|
434
499
|
}
|
|
435
|
-
async listTemplates() {
|
|
436
|
-
const
|
|
500
|
+
async listTemplates(opts) {
|
|
501
|
+
const path = opts?.factorySlug ? templatePath(opts.factorySlug) : "/api/v1/templates";
|
|
502
|
+
const body = await this.fetch(path);
|
|
437
503
|
return body.templates;
|
|
438
504
|
}
|
|
439
|
-
|
|
440
|
-
|
|
505
|
+
async listFactories() {
|
|
506
|
+
const body = await this.fetch("/api/v1/factories");
|
|
507
|
+
return body.factories;
|
|
441
508
|
}
|
|
442
|
-
|
|
443
|
-
|
|
509
|
+
createFactory(payload) {
|
|
510
|
+
return this.fetch("/api/v1/factories", { method: "POST", body: payload });
|
|
511
|
+
}
|
|
512
|
+
getFactory(slug) {
|
|
513
|
+
return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`);
|
|
514
|
+
}
|
|
515
|
+
updateFactory(slug, updates) {
|
|
516
|
+
return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "PATCH", body: updates });
|
|
517
|
+
}
|
|
518
|
+
deleteFactory(slug) {
|
|
519
|
+
return this.fetch(`/api/v1/factories/${encodeURIComponent(slug)}`, { method: "DELETE" });
|
|
520
|
+
}
|
|
521
|
+
setSecret(workflowName, key, value, opts) {
|
|
522
|
+
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
523
|
+
return this.fetch(templatePath(factorySlug, workflowName, "secrets"), {
|
|
524
|
+
method: "POST",
|
|
525
|
+
body: { key, value }
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
async listSecrets(workflowName, opts) {
|
|
529
|
+
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
530
|
+
const body = await this.fetch(templatePath(factorySlug, workflowName, "secrets"));
|
|
444
531
|
return body.secrets.map((s) => ({ key: s.secretKey, createdAt: s.createdAt, updatedAt: s.updatedAt }));
|
|
445
532
|
}
|
|
446
|
-
deleteSecret(workflowName, key) {
|
|
447
|
-
|
|
533
|
+
deleteSecret(workflowName, key, opts) {
|
|
534
|
+
const factorySlug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
535
|
+
return this.fetch(templatePath(factorySlug, workflowName, "secrets", key), { method: "DELETE" });
|
|
536
|
+
}
|
|
537
|
+
createApiKey(input) {
|
|
538
|
+
return this.fetch("/api-keys", { method: "POST", body: input });
|
|
539
|
+
}
|
|
540
|
+
async listApiKeys() {
|
|
541
|
+
const body = await this.fetch("/api-keys");
|
|
542
|
+
return body.data;
|
|
543
|
+
}
|
|
544
|
+
getUsage(from, to) {
|
|
545
|
+
const qs = `?from=${encodeURIComponent(from.toISOString())}&to=${encodeURIComponent(to.toISOString())}`;
|
|
546
|
+
return this.fetch(`/api/v1/usage${qs}`);
|
|
547
|
+
}
|
|
548
|
+
cancelRun(runId) {
|
|
549
|
+
return this.fetch(`/api/v1/workflows/${encodeURIComponent(runId)}/cancel`, { method: "POST" });
|
|
550
|
+
}
|
|
551
|
+
async* streamRunLogs(runId, opts) {
|
|
552
|
+
const headers = { Authorization: `Bearer ${this.apiKey}` };
|
|
553
|
+
if (opts?.lastEventId && opts.lastEventId > 0) {
|
|
554
|
+
headers["Last-Event-ID"] = String(opts.lastEventId);
|
|
555
|
+
}
|
|
556
|
+
const res = await fetch(`${this.baseUrl}/api/v1/workflows/${encodeURIComponent(runId)}/stream`, {
|
|
557
|
+
headers,
|
|
558
|
+
...opts?.signal ? { signal: opts.signal } : {}
|
|
559
|
+
});
|
|
560
|
+
if (!res.ok || !res.body) {
|
|
561
|
+
let message = res.statusText;
|
|
562
|
+
try {
|
|
563
|
+
const body = await res.json();
|
|
564
|
+
if (body.error)
|
|
565
|
+
message = body.error;
|
|
566
|
+
} catch {}
|
|
567
|
+
throw new AgentComposeError(res.status, message);
|
|
568
|
+
}
|
|
569
|
+
for await (const ev of parseSseStream(res.body)) {
|
|
570
|
+
yield ev.data;
|
|
571
|
+
}
|
|
448
572
|
}
|
|
449
573
|
}
|
|
450
574
|
// src/utils/errors.ts
|
|
@@ -499,16 +623,16 @@ async function bundleWorkflow(workflowPath, overrides) {
|
|
|
499
623
|
const wfBits = await extractFromBundle(source, "workflow", (wf) => wf ? {
|
|
500
624
|
networkPolicy: wf.networkPolicy,
|
|
501
625
|
placeholders: wf.placeholders,
|
|
502
|
-
|
|
503
|
-
|
|
626
|
+
snapshot: wf.snapshot,
|
|
627
|
+
saveSnapshot: wf.saveSnapshot
|
|
504
628
|
} : undefined);
|
|
505
|
-
const { networkPolicy, placeholders,
|
|
629
|
+
const { networkPolicy, placeholders, snapshot, saveSnapshot } = wfBits ?? {};
|
|
506
630
|
return {
|
|
507
631
|
source,
|
|
508
632
|
networkPolicy: overrides?.networkPolicy ?? networkPolicy,
|
|
509
633
|
placeholders: overrides?.placeholders ?? placeholders,
|
|
510
|
-
...
|
|
511
|
-
...
|
|
634
|
+
...snapshot !== undefined ? { snapshot } : {},
|
|
635
|
+
...saveSnapshot !== undefined ? { saveSnapshot } : {}
|
|
512
636
|
};
|
|
513
637
|
}
|
|
514
638
|
// src/utils/schemas.ts
|
package/dist/sse.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Server-Sent Events (SSE) parser.
|
|
3
|
+
*
|
|
4
|
+
* Consumes a `ReadableStream<Uint8Array>` and yields parsed events one at a
|
|
5
|
+
* time. Each event carries:
|
|
6
|
+
* - `id`: numeric (from `id:` line; `0` if absent)
|
|
7
|
+
* - `event`: event name (from `event:` line; `""` if absent)
|
|
8
|
+
* - `data`: parsed JSON payload (the SDK's stream events are always JSON)
|
|
9
|
+
*
|
|
10
|
+
* Malformed payloads are silently skipped — same behaviour as the previous
|
|
11
|
+
* CLI-side parser. Caller drives the loop via `for await (...)` and is
|
|
12
|
+
* responsible for breaking on terminal events.
|
|
13
|
+
*
|
|
14
|
+
* Output type stays loose (`Record<string, unknown>`) on purpose: typed
|
|
15
|
+
* unions like `RunEvent` aren't structurally narrowable from
|
|
16
|
+
* `Record<string, unknown>`, so callers that want a discriminated union
|
|
17
|
+
* cast at the consumption site (see `streamRunLogs`). Keeping the parser
|
|
18
|
+
* payload-agnostic also means it's reusable for future SSE endpoints with
|
|
19
|
+
* different shapes.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseSseStream(body: ReadableStream<Uint8Array>): AsyncGenerator<{
|
|
22
|
+
id: number;
|
|
23
|
+
event: string;
|
|
24
|
+
data: Record<string, unknown>;
|
|
25
|
+
}>;
|
package/dist/types/events.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A sandbox environment is a workflow whose job is to leave its VM in a
|
|
3
3
|
* configured state, then snapshot it so other workflows can boot from that
|
|
4
|
-
* state.
|
|
5
|
-
* by default (opt out with `
|
|
4
|
+
* state. Capture is **opt-in**: the wrapper below sets `saveSnapshot: true`
|
|
5
|
+
* by default (opt out with `saveSnapshot: false` if you only want side
|
|
6
|
+
* effects).
|
|
6
7
|
*
|
|
7
|
-
* Once captured, reference the snapshot from another workflow's
|
|
8
|
-
*
|
|
8
|
+
* Once captured, reference the snapshot from another workflow's `snapshot`
|
|
9
|
+
* field by run UUID, workflow name, or `name@version`:
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* snapshot: "my-setup" // most recent successful snapshot
|
|
12
|
+
* snapshot: "my-setup@v1" // version-scoped recency
|
|
13
|
+
* snapshot: "<run-uuid>" // pinned to an exact run
|
|
13
14
|
*
|
|
14
15
|
* `defineSandboxEnvironment` is sugar over `defineWorkflow` — it makes the
|
|
15
16
|
* setup recipe read like an imperative script by supplying the local
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
* 1. Author a setup file with `defineSandboxEnvironment`.
|
|
20
21
|
* 2. `agentc register setup.ts --build` — registers and invokes once to
|
|
21
22
|
* capture the snapshot.
|
|
22
|
-
* 3. Other workflows declare `
|
|
23
|
+
* 3. Other workflows declare `snapshot: "name"` and boot from it.
|
|
23
24
|
* 4. `agentc snapshot list` / `delete` to manage the Vercel storage bill.
|
|
24
25
|
*
|
|
25
26
|
* @example
|
|
@@ -40,9 +41,9 @@ export interface SandboxEnvironmentDefinition {
|
|
|
40
41
|
name: string;
|
|
41
42
|
description?: string;
|
|
42
43
|
setup: (sb: SandboxProvider) => Promise<void>;
|
|
43
|
-
/** Override the sugar's `
|
|
44
|
-
* of snapshot capture (rarely useful — an env with no snapshot can't
|
|
45
|
-
* referenced as `
|
|
46
|
-
|
|
44
|
+
/** Override the sugar's `saveSnapshot: true` default. Set `false` to opt
|
|
45
|
+
* out of snapshot capture (rarely useful — an env with no snapshot can't
|
|
46
|
+
* be referenced as the `snapshot` field on another workflow). */
|
|
47
|
+
saveSnapshot?: boolean;
|
|
47
48
|
}
|
|
48
49
|
export declare function defineSandboxEnvironment(env: SandboxEnvironmentDefinition): WorkflowFn<void>;
|
package/dist/types/workflow.d.ts
CHANGED
|
@@ -52,19 +52,21 @@ export type WorkflowFn<T = unknown> = (ctx: WorkflowCtx, sandbox: SandboxProvide
|
|
|
52
52
|
export interface WorkflowDefinition<T = unknown> {
|
|
53
53
|
run: WorkflowFn<T>;
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
55
|
+
* Reference to a snapshot the runner should boot from at run start.
|
|
56
|
+
* Accepts a run UUID, a workflow name, or `name@version` — any workflow
|
|
57
|
+
* registered with `--build` produces a snapshot you can name here. The
|
|
58
|
+
* runner VM starts in that pre-configured state. Per-invocation
|
|
59
|
+
* `invoke({ snapshot })` overrides this default.
|
|
58
60
|
*/
|
|
59
|
-
|
|
61
|
+
snapshot?: string;
|
|
60
62
|
/**
|
|
61
|
-
* Capture a snapshot of the sandbox on successful /complete. Snapshots
|
|
62
|
-
* long-lived (never auto-expire) — customers list + delete them
|
|
63
|
-
* via `agentc snapshot list/delete`. Per-invocation
|
|
64
|
-
*
|
|
65
|
-
* true by default.
|
|
63
|
+
* Capture a snapshot of the sandbox on successful /complete. Snapshots
|
|
64
|
+
* are long-lived (never auto-expire) — customers list + delete them
|
|
65
|
+
* explicitly via `agentc snapshot list/delete`. Per-invocation
|
|
66
|
+
* `invoke({ saveSnapshot })` overrides this default.
|
|
67
|
+
* `defineSandboxEnvironment` sugar sets this to true by default.
|
|
66
68
|
*/
|
|
67
|
-
|
|
69
|
+
saveSnapshot?: boolean;
|
|
68
70
|
/**
|
|
69
71
|
* Outbound network policy for the runner sandbox.
|
|
70
72
|
* Use "*": [] to allow all traffic while still injecting headers for specific domains.
|
|
@@ -100,7 +102,7 @@ export interface WorkflowDefinition<T = unknown> {
|
|
|
100
102
|
* The returned function is a valid WorkflowFn with metadata fields attached
|
|
101
103
|
* for the bundler / registration layer to read.
|
|
102
104
|
*/
|
|
103
|
-
export declare function defineWorkflow<T = unknown>(def: WorkflowDefinition<T>): WorkflowFn<T> & Pick<WorkflowDefinition, "networkPolicy" | "placeholders" | "
|
|
105
|
+
export declare function defineWorkflow<T = unknown>(def: WorkflowDefinition<T>): WorkflowFn<T> & Pick<WorkflowDefinition, "networkPolicy" | "placeholders" | "snapshot" | "saveSnapshot">;
|
|
104
106
|
/** Observability-only lifecycle hooks — passed to the workflow engine, not workflow authors. */
|
|
105
107
|
export interface WorkflowHooks {
|
|
106
108
|
onStepStart?: (step: string) => void;
|
package/dist/utils/bundler.d.ts
CHANGED
|
@@ -13,11 +13,12 @@ export interface BundledWorkflow {
|
|
|
13
13
|
source: string;
|
|
14
14
|
networkPolicy?: SandboxNetworkPolicy;
|
|
15
15
|
placeholders?: Record<string, string>;
|
|
16
|
-
/**
|
|
17
|
-
* workflow boots from, if declared via `
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
/** Run UUID, workflow name, or `name@version` referencing the snapshot
|
|
17
|
+
* this workflow's runner boots from, if declared via the `snapshot`
|
|
18
|
+
* field on `defineWorkflow`. */
|
|
19
|
+
snapshot?: string;
|
|
20
|
+
/** Default capture-on-success flag from the workflow definition. */
|
|
21
|
+
saveSnapshot?: boolean;
|
|
21
22
|
}
|
|
22
23
|
/** Bundle a workflow from source. */
|
|
23
24
|
export declare function bundleWorkflow(workflowPath: string, overrides?: {
|
package/package.json
CHANGED