@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/dist/client.d.ts CHANGED
@@ -1,9 +1,17 @@
1
1
  /**
2
2
  * AgentComposeClient — HTTP client for the agent-compose server API.
3
3
  *
4
- * register() accepts pre-built sources — use the CLI (agent-compose register)
5
- * or build sources yourself and pass them directly.
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.
6
12
  */
13
+ import type { RunEvent } from "./types/events.js";
14
+ import type { SandboxNetworkPolicy } from "./sandbox.js";
7
15
  export interface RegisterResult {
8
16
  id: string;
9
17
  name: string;
@@ -16,9 +24,54 @@ export interface RegisterResult {
16
24
  }
17
25
  export interface RunStatus {
18
26
  id: string;
19
- status: "running" | "success" | "failed" | "abandoned";
27
+ status: "running" | "success" | "failed" | "abandoned" | "canceled";
20
28
  output?: Record<string, unknown>;
21
29
  }
30
+ /** Row shape returned by `GET /api-keys`. */
31
+ export interface ApiKey {
32
+ object: "api_key";
33
+ id: string;
34
+ name: string | null;
35
+ last4: string | null;
36
+ scopes: string[];
37
+ teamId: string;
38
+ createdByUserId: string | null;
39
+ /** Non-null when the key is restricted to a single factory. */
40
+ factoryId: string | null;
41
+ createdAt: string;
42
+ expiresAt: string | null;
43
+ lastUsedAt: string | null;
44
+ revokedAt: string | null;
45
+ }
46
+ /** Response from `POST /api-keys`. The `key` field is the plaintext token —
47
+ * shown once at creation, never retrievable again. */
48
+ export interface ApiKeyCreated extends ApiKey {
49
+ key: string;
50
+ }
51
+ /** Single rollup row from `GET /api/v1/usage`. */
52
+ export interface UsageRollupRow {
53
+ eventType: string;
54
+ unit: string;
55
+ total: number;
56
+ tags: Record<string, unknown>;
57
+ }
58
+ /** Response from `GET /api/v1/usage`. */
59
+ export interface UsageResponse {
60
+ object: "list";
61
+ data: UsageRollupRow[];
62
+ has_more: boolean;
63
+ from: string | null;
64
+ to: string | null;
65
+ }
66
+ /** Response from `POST /api/v1/workflows/:id/cancel`. The endpoint is
67
+ * idempotent: cancelling a run that's already terminal returns its current
68
+ * outcome verbatim (rather than throwing or pretending it just canceled),
69
+ * so `status` widens to every terminal value the server might surface. */
70
+ export interface CancelRunResponse {
71
+ runId: string;
72
+ status: "canceled" | "success" | "failed" | "abandoned";
73
+ canceledAt: string;
74
+ }
22
75
  export interface SnapshotListEntry {
23
76
  runId: string;
24
77
  workflow: string | null;
@@ -28,8 +81,11 @@ export interface SnapshotListEntry {
28
81
  }
29
82
  export declare class AgentComposeClient {
30
83
  private readonly fetch;
84
+ private readonly baseUrl;
85
+ private readonly apiKey;
31
86
  constructor(baseUrl: string, apiKey: string);
32
- /** Register a workflow with pre-built sources. */
87
+ /** Register (or update) a workflow template inside a factory. Defaults to
88
+ * the team's `default` factory when `factorySlug` is omitted. */
33
89
  register(payload: {
34
90
  name: string;
35
91
  source: string;
@@ -41,19 +97,31 @@ export declare class AgentComposeClient {
41
97
  }>;
42
98
  networkPolicy?: unknown;
43
99
  placeholders?: Record<string, string>;
44
- /** Name (or `name@version`) of another workflow whose snapshot this
45
- * workflow's runner boots from. Must reference a workflow previously
46
- * registered with `--build`. */
47
- sandboxEnvironment?: string;
100
+ /** Reference to a snapshot the runner should boot from at run start.
101
+ * A run UUID, workflow name, or `name@version`. The referenced
102
+ * workflow must have been registered with `--build` (or any prior
103
+ * successful run with `saveSnapshot: true`). Per-invocation
104
+ * `invoke({ snapshot })` overrides this default. */
105
+ snapshot?: string;
48
106
  /** If true, runs default to capturing a long-lived sandbox snapshot on
49
- * success. Individual invocations can override via `invoke(..., { snapshot })`. */
50
- snapshot?: boolean;
107
+ * success. Individual invocations can override via
108
+ * `invoke(..., { saveSnapshot })`. */
109
+ saveSnapshot?: boolean;
110
+ /** Factory slug. Defaults to `"default"`. */
111
+ factorySlug?: string;
51
112
  }): Promise<RegisterResult>;
52
113
  /** Invoke a workflow. Returns run ID immediately — workflow runs asynchronously.
53
114
  *
54
- * `snapshot`: `true` overrides the workflow default; `false` opts out. When
55
- * the run captures a snapshot, its id is stamped on the row and can be
56
- * referenced as `sandboxEnvironment` by other workflows.
115
+ * `snapshot`: per-invocation override of the template-level `snapshot`
116
+ * field. Same forms — a run UUID, a workflow name, or `name@version`.
117
+ * Use this when one template needs to boot from many different
118
+ * snapshots (e.g. a benchmark workflow running against a different
119
+ * starting state per invocation). Does not change the template's
120
+ * registered default; only affects this run.
121
+ *
122
+ * `saveSnapshot`: `true` overrides the workflow default; `false` opts
123
+ * out. When the run captures a snapshot, its id is stamped on the row
124
+ * and can be referenced as the `snapshot` field on other workflows.
57
125
  *
58
126
  * `parentRunId`: links the new run to another of the same account's
59
127
  * currently-running runs. When omitted, the client auto-detects from
@@ -61,10 +129,16 @@ export declare class AgentComposeClient {
61
129
  * so workflows invoking other workflows get the parent/child tree for
62
130
  * free. Pass `null` to suppress auto-detection (e.g. invoking a top-level
63
131
  * sibling workflow from inside a runner for a reason unrelated to the
64
- * current run). Explicit non-null wins over auto-detection. */
132
+ * current run). Explicit non-null wins over auto-detection.
133
+ *
134
+ * `factorySlug`: defaults to `"default"`. */
65
135
  invoke(name: string, input?: Record<string, unknown>, opts?: {
66
- snapshot?: boolean;
136
+ snapshot?: string;
137
+ saveSnapshot?: boolean;
67
138
  parentRunId?: string | null;
139
+ factorySlug?: string;
140
+ networkPolicy?: SandboxNetworkPolicy;
141
+ placeholders?: Record<string, string>;
68
142
  }): Promise<{
69
143
  id: string;
70
144
  }>;
@@ -76,8 +150,12 @@ export declare class AgentComposeClient {
76
150
  * tests, tune up for long-running workflows. The parent-child auto-
77
151
  * detection from `invoke()` applies here too. */
78
152
  invokeAndWait(name: string, input?: Record<string, unknown>, opts?: {
79
- snapshot?: boolean;
153
+ snapshot?: string;
154
+ saveSnapshot?: boolean;
80
155
  parentRunId?: string | null;
156
+ factorySlug?: string;
157
+ networkPolicy?: SandboxNetworkPolicy;
158
+ placeholders?: Record<string, string>;
81
159
  timeoutMs?: number;
82
160
  pollIntervalMs?: number;
83
161
  }): Promise<RunStatus>;
@@ -90,21 +168,100 @@ export declare class AgentComposeClient {
90
168
  deleteSnapshot(runId: string): Promise<void>;
91
169
  /** Poll run status. */
92
170
  getStatus(runId: string): Promise<RunStatus>;
93
- /** List registered workflow templates. */
94
- listTemplates(): Promise<Array<{
171
+ /** List registered workflow templates. When `factorySlug` is supplied,
172
+ * scopes to that factory; otherwise returns every template the team can
173
+ * see across every factory in one round trip — each row carries its
174
+ * `factorySlug` so callers can route per-template actions to the right
175
+ * factory. */
176
+ listTemplates(opts?: {
177
+ factorySlug?: string;
178
+ }): Promise<Array<{
95
179
  name: string;
96
180
  version: string;
181
+ factorySlug: string;
97
182
  }>>;
183
+ /** List factories for the caller's team. */
184
+ listFactories(): Promise<FactoryRow[]>;
185
+ /** Create a factory. `slug` must be lowercase kebab-case and unique
186
+ * within the team. */
187
+ createFactory(payload: {
188
+ slug: string;
189
+ name: string;
190
+ description?: string;
191
+ }): Promise<FactoryRow>;
192
+ /** Get a factory by slug. */
193
+ getFactory(slug: string): Promise<FactoryRow>;
194
+ /** Rename or describe a factory. */
195
+ updateFactory(slug: string, updates: {
196
+ name?: string;
197
+ description?: string;
198
+ }): Promise<FactoryRow>;
199
+ /** Delete a factory. Refuses `default` and any factory still
200
+ * containing workflows. */
201
+ deleteFactory(slug: string): Promise<void>;
98
202
  /** Create or update a workflow secret. Value is stored in GCP Secret Manager. */
99
- setSecret(workflowName: string, key: string, value: string): Promise<{
203
+ setSecret(workflowName: string, key: string, value: string, opts?: {
204
+ factorySlug?: string;
205
+ }): Promise<{
100
206
  key: string;
101
207
  }>;
102
208
  /** List secret keys registered for a workflow (metadata only — values are never returned). */
103
- listSecrets(workflowName: string): Promise<Array<{
209
+ listSecrets(workflowName: string, opts?: {
210
+ factorySlug?: string;
211
+ }): Promise<Array<{
104
212
  key: string;
105
213
  createdAt: string;
106
214
  updatedAt: string;
107
215
  }>>;
108
216
  /** Delete a workflow secret. */
109
- deleteSecret(workflowName: string, key: string): Promise<void>;
217
+ deleteSecret(workflowName: string, key: string, opts?: {
218
+ factorySlug?: string;
219
+ }): Promise<void>;
220
+ /** Create a new API key on the caller's team. The plaintext `key` is
221
+ * returned once — it cannot be retrieved later.
222
+ *
223
+ * When `factorySlug` is set, the new key is restricted to that factory.
224
+ * Factory-scoped keys can only mint other keys bound to the same factory. */
225
+ createApiKey(input: {
226
+ name?: string;
227
+ scopes?: string[];
228
+ expiresAt?: string;
229
+ factorySlug?: string;
230
+ }): Promise<ApiKeyCreated>;
231
+ /** List API keys on the caller's team (metadata only — plaintext keys are
232
+ * never returned). */
233
+ listApiKeys(): Promise<ApiKey[]>;
234
+ /** Billable usage rollup for the caller's team over the [from, to) window. */
235
+ getUsage(from: Date, to: Date): Promise<UsageResponse>;
236
+ /** Cancel an in-progress run. Idempotent: cancelling an already-terminal
237
+ * run returns the current state without throwing. The server stamps the
238
+ * run as `canceled` and kills any live sandboxes. */
239
+ cancelRun(runId: string): Promise<CancelRunResponse>;
240
+ /** Stream lifecycle events for a run as an async iterable. Yields parsed
241
+ * `RunEvent` payloads in order; caller breaks on terminal events
242
+ * (`run_complete`, `run_failed`, `run_canceled`).
243
+ *
244
+ * `lastEventId` enables resume — pass the highest `seq` you've already
245
+ * processed to receive only events you missed.
246
+ *
247
+ * `signal` can be used to abort the stream from the caller side.
248
+ *
249
+ * Uses raw `fetch` (not ofetch) because SSE requires access to the
250
+ * response's `ReadableStream`, which ofetch consumes when parsing. Auth
251
+ * + base URL are still sourced from the same constructor inputs, and
252
+ * non-2xx responses throw the same `AgentComposeError`. */
253
+ streamRunLogs(runId: string, opts?: {
254
+ lastEventId?: number;
255
+ signal?: AbortSignal;
256
+ }): AsyncGenerator<RunEvent>;
257
+ }
258
+ /** A factory: a project-level grouping of workflows inside a team. */
259
+ export interface FactoryRow {
260
+ id: string;
261
+ teamId: string;
262
+ slug: string;
263
+ name: string;
264
+ description: string | null;
265
+ createdAt: string;
266
+ updatedAt: string;
110
267
  }
package/dist/index.d.ts CHANGED
@@ -20,7 +20,8 @@ export type { WorkflowFn, WorkflowCtx, WorkflowRun, AgentBudget, WorkflowHooks,
20
20
  export type { AgentMessage, AgentMessageInit, AgentMessageText, AgentMessageThinking, AgentMessageToolUse, AgentMessageToolResult, AgentMessageDone, AgentMessageError, AgentMessageUsage, AgentStatus, } from "./types/protocol.js";
21
21
  export type { SandboxProvider, DesktopSandboxProvider, } from "./types/sandbox.js";
22
22
  export { AgentComposeClient } from "./client.js";
23
- export type { RegisterResult, RunStatus } from "./client.js";
23
+ export type { RegisterResult, RunStatus, FactoryRow, SnapshotListEntry, ApiKey, ApiKeyCreated, UsageRollupRow, UsageResponse, CancelRunResponse, } from "./client.js";
24
+ export { parseSseStream } from "./sse.js";
24
25
  export { AgentComposeError } from "./errors.js";
25
26
  export { formatError } from "./utils/errors.js";
26
27
  export { discoverRuntimeName } from "./utils/discovery.js";
package/dist/index.js CHANGED
@@ -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
- sandboxEnvironment: def.sandboxEnvironment,
32
- snapshot: def.snapshot
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
- snapshot: env.snapshot ?? true,
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.replace(/\/$/, ""),
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
- return this.fetch("/api/v1/templates", { method: "POST", body: payload });
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
- return this.fetch(`/api/v1/templates/${name}/invoke`, {
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
- ...parentRunId ? { parentRunId } : {}
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?.parentRunId !== undefined ? { parentRunId: opts.parentRunId } : {}
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 body = await this.fetch("/api/v1/templates");
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
- setSecret(workflowName, key, value) {
440
- return this.fetch(`/api/v1/templates/${workflowName}/secrets`, { method: "POST", body: { key, value } });
505
+ async listFactories() {
506
+ const body = await this.fetch("/api/v1/factories");
507
+ return body.factories;
508
+ }
509
+ createFactory(payload) {
510
+ return this.fetch("/api/v1/factories", { method: "POST", body: payload });
441
511
  }
442
- async listSecrets(workflowName) {
443
- const body = await this.fetch(`/api/v1/templates/${workflowName}/secrets`);
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
- return this.fetch(`/api/v1/templates/${workflowName}/secrets/${key}`, { method: "DELETE" });
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
- sandboxEnvironment: wf.sandboxEnvironment,
503
- snapshot: wf.snapshot
626
+ snapshot: wf.snapshot,
627
+ saveSnapshot: wf.saveSnapshot
504
628
  } : undefined);
505
- const { networkPolicy, placeholders, sandboxEnvironment, snapshot } = wfBits ?? {};
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
- ...sandboxEnvironment ? { sandboxEnvironment } : {},
511
- ...snapshot !== undefined ? { snapshot } : {}
634
+ ...snapshot !== undefined ? { snapshot } : {},
635
+ ...saveSnapshot !== undefined ? { saveSnapshot } : {}
512
636
  };
513
637
  }
514
638
  // src/utils/schemas.ts
@@ -1077,6 +1201,7 @@ export {
1077
1201
  runWorkflow,
1078
1202
  runAgent,
1079
1203
  reconnectSandbox,
1204
+ parseSseStream,
1080
1205
  parseSseExecStream,
1081
1206
  parseNameVersion,
1082
1207
  parseAgentStatus,