@agent-compose/sdk 0.5.6 → 0.5.8
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 +4 -4
- package/dist/agent/__tests__/run-agent-liveness.test.d.ts +17 -0
- package/dist/agent/agent-context.d.ts +67 -0
- package/dist/agent/agent-loop-contract.test.d.ts +1 -0
- package/dist/agent/agent-loop.d.ts +2 -1
- package/dist/client.d.ts +129 -24
- package/dist/index.d.ts +9 -4
- package/dist/index.js +553 -89
- package/dist/pause/wrappers.d.ts +7 -11
- package/dist/runtimes/claude.d.ts +9 -1
- package/dist/runtimes/openai-desktop.js +548 -89
- package/dist/sandbox-errors.d.ts +49 -0
- package/dist/sandbox.d.ts +92 -13
- package/dist/step-invocation/protocol.d.ts +6 -0
- package/dist/step-invocation/types.d.ts +1 -1
- package/dist/types/execution-context.d.ts +1 -3
- package/dist/types/sandbox-environment.d.ts +1 -10
- package/dist/types/sandbox.d.ts +27 -3
- package/dist/types/workflow-metadata.d.ts +81 -13
- package/dist/types/workflow.d.ts +45 -10
- package/dist/utils/bundler.d.ts +40 -9
- package/dist/workflow-steps/workflow.d.ts +4 -3
- package/package.json +2 -2
- package/src/agent/agent-context.ts +212 -0
- package/src/agent/agent-loop.ts +78 -10
- package/src/agent/run-agent.ts +37 -1
- package/src/client.ts +232 -26
- package/src/index.ts +9 -4
- package/src/pause/wrappers.ts +7 -21
- package/src/runtimes/claude.ts +66 -8
- package/src/sandbox-errors.ts +53 -0
- package/src/sandbox.ts +438 -61
- package/src/step-invocation/invoker.ts +66 -8
- package/src/step-invocation/protocol.ts +9 -0
- package/src/step-invocation/server.ts +27 -4
- package/src/step-invocation/types.ts +1 -1
- package/src/types/execution-context.ts +1 -3
- package/src/types/sandbox-environment.ts +1 -11
- package/src/types/sandbox.ts +28 -3
- package/src/types/workflow-metadata.ts +91 -16
- package/src/types/workflow.ts +45 -12
- package/src/utils/bundler.ts +46 -13
- package/src/workflow-steps/workflow.ts +4 -3
- package/src/workflows/invoke-child.ts +7 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox-infrastructure error.
|
|
3
|
+
*
|
|
4
|
+
* Distinct from `StepExecutionError` (the user/runner step-failure wrapper):
|
|
5
|
+
* that's the workflow author's problem. A `SandboxUnavailableError` means the
|
|
6
|
+
* sandbox itself couldn't carry the step — the provider refused a command, the
|
|
7
|
+
* sandbox was reclaimed (idle/lifetime timeout, eviction), an API blip, or the
|
|
8
|
+
* connection dropped mid-stream. None of these are a fault in the customer's
|
|
9
|
+
* code, so they're surfaced as "infrastructure issue, retry" rather than
|
|
10
|
+
* blaming the user.
|
|
11
|
+
*
|
|
12
|
+
* ## Retryability is about *when*, not *which error*
|
|
13
|
+
*
|
|
14
|
+
* The Vercel provider (`sandbox.ts`) decides `retryable` purely from *where* it
|
|
15
|
+
* caught the error — not by inspecting status codes or error vocabularies:
|
|
16
|
+
* - `retryable: true` — caught before the runner launched the step (file
|
|
17
|
+
* write / command launch / reconnect refused). No user code ran, so
|
|
18
|
+
* re-provisioning a fresh sandbox and re-running the step is safe. ANY
|
|
19
|
+
* error here qualifies (sandbox reclaimed, 429, 5xx, network blip).
|
|
20
|
+
* - `retryable: false` — caught while/after the runner streamed, so user code
|
|
21
|
+
* may already have executed side effects. Surfaced honestly as an infra
|
|
22
|
+
* failure, but NOT auto-retried.
|
|
23
|
+
*
|
|
24
|
+
* ## Cross-process contract
|
|
25
|
+
*
|
|
26
|
+
* The thrown error crosses the Temporal activity→workflow serialisation
|
|
27
|
+
* boundary, which preserves the error **message** but discards custom instance
|
|
28
|
+
* fields. So the retryability signal is encoded IN the message via a stable
|
|
29
|
+
* prefix — `[sandbox-unavailable:retryable]` or `[sandbox-unavailable:terminal]`
|
|
30
|
+
* — following the same `[kind] …` convention `StepExecutionError` uses. The
|
|
31
|
+
* server (`utils/transient-errors.ts`) matches this prefix with a pure regex to
|
|
32
|
+
* classify the failure and decide whether the workflow re-provisions and
|
|
33
|
+
* retries. A unit test pins the SDK-produced string against the server matcher
|
|
34
|
+
* so the two can't drift.
|
|
35
|
+
*/
|
|
36
|
+
/** Stable message prefix. Both variants share this leading token so a single
|
|
37
|
+
* server-side regex recognises the class; the `:retryable` / `:terminal`
|
|
38
|
+
* suffix carries the recovery decision. */
|
|
39
|
+
export declare const SANDBOX_UNAVAILABLE_PREFIX = "[sandbox-unavailable";
|
|
40
|
+
export declare class SandboxUnavailableError extends Error {
|
|
41
|
+
/** True when caught before any user code ran (safe to re-provision + retry);
|
|
42
|
+
* false when the sandbox died mid/after execution. */
|
|
43
|
+
readonly retryable: boolean;
|
|
44
|
+
readonly sandboxId: string | undefined;
|
|
45
|
+
constructor(detail: string, opts: {
|
|
46
|
+
retryable: boolean;
|
|
47
|
+
sandboxId?: string;
|
|
48
|
+
});
|
|
49
|
+
}
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -7,20 +7,29 @@
|
|
|
7
7
|
import { Sandbox } from "e2b";
|
|
8
8
|
import { Sandbox as Desktop } from "@e2b/desktop";
|
|
9
9
|
import type { SandboxProvider, DesktopSandboxProvider, SandboxCommandResult } from "./types/sandbox.js";
|
|
10
|
+
import type { ConnectorRequestRules } from "./types/workflow-metadata.js";
|
|
11
|
+
import type { NetworkPolicy as VercelNetworkPolicy } from "@vercel/sandbox";
|
|
10
12
|
export type { SandboxProvider, DesktopSandboxProvider, SandboxCommandRunOptions, SandboxCommandResult } from "./types/sandbox.js";
|
|
11
13
|
export type SandboxProviderName = "vercel" | "e2b" | "e2b-desktop";
|
|
12
14
|
export declare const AGENT_COMPOSE_TAG: string;
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
15
|
-
* When a sandbox makes a request matching
|
|
16
|
-
*
|
|
17
|
-
* exist inside the VM.
|
|
16
|
+
* Network policy for outbound HTTPS requests — ONE shape for every provider;
|
|
17
|
+
* only the enforcement point differs. When a sandbox makes a request matching
|
|
18
|
+
* a domain in `allow`, the egress layer injects the specified headers before
|
|
19
|
+
* forwarding — credentials never exist inside the VM. Vercel's firewall
|
|
20
|
+
* enforces this natively (`requestRules` translate to native `match` rules);
|
|
21
|
+
* E2B enforces it via the embedded iron-proxy, which pulls the identical
|
|
22
|
+
* resolved policy from the server.
|
|
18
23
|
*/
|
|
19
24
|
export interface SandboxNetworkHeaderTransform {
|
|
20
25
|
headers?: Record<string, string>;
|
|
21
26
|
}
|
|
22
27
|
export interface SandboxNetworkAllowRule {
|
|
23
28
|
transform?: SandboxNetworkHeaderTransform[];
|
|
29
|
+
/** Tier-2 request gate (method/path) — the transform (and, on iron-proxy,
|
|
30
|
+
* the request itself) only applies when the request matches. Present on
|
|
31
|
+
* connector-auth rules; see `ConnectorRequestRules`. */
|
|
32
|
+
requestRules?: ConnectorRequestRules;
|
|
24
33
|
}
|
|
25
34
|
export interface SandboxNetworkSubnetPolicy {
|
|
26
35
|
allow?: string[];
|
|
@@ -30,19 +39,50 @@ export type SandboxNetworkPolicy = "allow-all" | "deny-all" | {
|
|
|
30
39
|
allow?: string[] | Record<string, SandboxNetworkAllowRule[]>;
|
|
31
40
|
subnets?: SandboxNetworkSubnetPolicy;
|
|
32
41
|
};
|
|
42
|
+
/** Sandbox machine size. A coarse small/medium/large knob that maps to
|
|
43
|
+
* provider machine specs at create time. Vercel honours it natively via
|
|
44
|
+
* `resources.vcpus` (2048 MB RAM per vCPU). E2B sizing is baked into the
|
|
45
|
+
* template, so E2B ignores this field. Default: "small". */
|
|
46
|
+
/** Sandbox hardware SKU. Named for the actual machine spec (vCPU + RAM) rather
|
|
47
|
+
* than abstract t-shirt sizes. Memory is always 2048 MB per vCPU:
|
|
48
|
+
* 2vcpu-4gb = 2 vCPU / 4 GiB (Vercel's own default machine)
|
|
49
|
+
* 4vcpu-8gb = 4 vCPU / 8 GiB
|
|
50
|
+
* 8vcpu-16gb = 8 vCPU / 16 GiB (per-sandbox ceiling on STANDARD accounts —
|
|
51
|
+
* probed live: 16 & 32 vCPU 400 on dev)
|
|
52
|
+
* 32vcpu-64gb = 32 vCPU / 64 GiB (ENTERPRISE ONLY — standard accounts reject >8 vCPU) */
|
|
53
|
+
export type SandboxSize = "2vcpu-4gb" | "4vcpu-8gb" | "8vcpu-16gb" | "32vcpu-64gb";
|
|
54
|
+
/** SKU → Vercel vCPU count (RAM follows at 2048 MB/vCPU). */
|
|
55
|
+
export declare const SANDBOX_VCPUS: Record<SandboxSize, number>;
|
|
56
|
+
/** SDK fallback size when neither the caller nor the deployment specifies one.
|
|
57
|
+
* Deliberately conservative — the OPERATIONAL default is chosen per-environment
|
|
58
|
+
* by the server via the `SANDBOX_DEFAULT_SIZE` env var (prod = Enterprise →
|
|
59
|
+
* `32vcpu-64gb`; dev = `8vcpu-16gb`, since the dev account caps at 8 vCPU).
|
|
60
|
+
* TODO(sandbox-size): the prod default is temporarily `32vcpu-64gb` for a
|
|
61
|
+
* memory-hungry workload — lower it back when no longer needed. */
|
|
62
|
+
export declare const DEFAULT_SANDBOX_SIZE: SandboxSize;
|
|
33
63
|
export interface SandboxCreateOpts {
|
|
34
64
|
envs: Record<string, string>;
|
|
35
65
|
metadata: Record<string, string>;
|
|
36
66
|
timeoutMs: number;
|
|
37
|
-
/** Provider-specific template/snapshot identifier. E2B: template
|
|
67
|
+
/** Provider-specific template/snapshot identifier. E2B: template id or
|
|
68
|
+
* snapshot id (omit → E2B's default base). Vercel: snapshot id (omit → node24). */
|
|
38
69
|
template?: string;
|
|
39
|
-
/**
|
|
70
|
+
/** Machine size. Vercel maps it to `resources.vcpus`; E2B ignores it
|
|
71
|
+
* (size is template-defined). Omit → `DEFAULT_SANDBOX_SIZE`. */
|
|
72
|
+
size?: SandboxSize;
|
|
73
|
+
/** Outbound request policy with header transforms — ONE shape for every
|
|
74
|
+
* provider; only the enforcement point differs. Vercel's firewall
|
|
75
|
+
* enforces + injects natively from this value. E2B enforces it via an
|
|
76
|
+
* EMBEDDED iron-proxy inside the VM (loopback DNS + iptables, started
|
|
77
|
+
* at boot), which fetches the identical resolved policy from the
|
|
78
|
+
* server's per-run egress-policy endpoint — so this field is not passed
|
|
79
|
+
* to E2B's API. See server/src/sandbox/iron-proxy.ts. */
|
|
40
80
|
networkPolicy?: SandboxNetworkPolicy;
|
|
41
81
|
}
|
|
42
82
|
/**
|
|
43
83
|
* What the provider reports as "currently alive" — the single input to orphan
|
|
44
84
|
* reconciliation. `metadata` is best-effort: E2B populates it from sandbox
|
|
45
|
-
* labels, Vercel
|
|
85
|
+
* labels, Vercel from sandbox tags (max 5, stamped at create). Callers
|
|
46
86
|
* that need runId correlation cross-reference `sandboxId` against their own
|
|
47
87
|
* state (server: `workflow_runs.sandbox_id` + `run_agent_sandboxes.provider_sandbox_id`).
|
|
48
88
|
*/
|
|
@@ -60,18 +100,17 @@ interface SandboxProviderDef {
|
|
|
60
100
|
* Returns the number of sandboxes currently running against this provider.
|
|
61
101
|
* Used for quota observability — account-wide, not per-instance.
|
|
62
102
|
*
|
|
63
|
-
*
|
|
64
|
-
* its own sandboxes; DD aggregates
|
|
65
|
-
*
|
|
66
|
-
* the configured project (assumed dedicated to agent-compose).
|
|
103
|
+
* Both providers scope by our AGENT_COMPOSE_TAG (E2B metadata labels,
|
|
104
|
+
* Vercel tags) so each fleet only reports its own sandboxes; DD aggregates
|
|
105
|
+
* across instances with `sum by {provider}`.
|
|
67
106
|
*/
|
|
68
107
|
getActiveCount?: (env: Record<string, string>) => Promise<number>;
|
|
69
108
|
/**
|
|
70
109
|
* Provider's view of what's currently alive for our account/fleet.
|
|
71
110
|
* The reconciliation SoT — a sandbox missing from this list IS dead,
|
|
72
111
|
* regardless of what our DB says. Implemented by listing the provider's
|
|
73
|
-
* running sandboxes; E2B filters by metadata tag, Vercel
|
|
74
|
-
*
|
|
112
|
+
* running sandboxes; E2B filters by metadata tag, Vercel by the `executor`
|
|
113
|
+
* sandbox tag.
|
|
75
114
|
*/
|
|
76
115
|
listOwned?: (env: Record<string, string>) => Promise<OwnedSandbox[]>;
|
|
77
116
|
/**
|
|
@@ -93,6 +132,46 @@ export interface ParseSseExecStreamOptions {
|
|
|
93
132
|
* Used by the agent sandbox broker in runner.ts.
|
|
94
133
|
*/
|
|
95
134
|
export declare function parseSseExecStream(body: ReadableStream<Uint8Array>, opts?: ParseSseExecStreamOptions): Promise<SandboxCommandResult>;
|
|
135
|
+
/** Matches a path containing a dot-dot segment — literal (`/../`) or
|
|
136
|
+
* percent-encoded (`%2e%2e`, `%2f` boundaries). `DOT_SEGMENT_PATH_RE2` is
|
|
137
|
+
* the RE2 form handed to Vercel's matcher: wrapped in `.*` so it behaves
|
|
138
|
+
* identically under search and full-match semantics (RE2 has no lookaround,
|
|
139
|
+
* so the guard is a positive match on the traversal pattern, used as a
|
|
140
|
+
* credential-stripping first rule). `DOT_SEGMENT_RE` is the local JS twin
|
|
141
|
+
* used to validate DECLARED prefixes. */
|
|
142
|
+
export declare const DOT_SEGMENT_PATH_RE2 = ".*(?:^|/|%2[fF])(?:\\.|%2[eE]){2}(?:/|%2[fF]|$).*";
|
|
143
|
+
/**
|
|
144
|
+
* Translate our policy shape into Vercel's native `NetworkPolicy`.
|
|
145
|
+
*
|
|
146
|
+
* The shapes are identical except for Tier-2: our `requestRules`
|
|
147
|
+
* (`{ methods, pathPrefixes }`) become Vercel `match` rules
|
|
148
|
+
* (`{ method, path: { startsWith } }`) — one rule per path prefix, since a
|
|
149
|
+
* Vercel matcher carries a single path.
|
|
150
|
+
*
|
|
151
|
+
* Dot-segment defense: prefix matching is raw — `/repos/acme/../../user`
|
|
152
|
+
* starts with `/repos/acme/` but the origin normalizes it to `/user`, riding
|
|
153
|
+
* the credential outside its declared scope. We don't get to assume the
|
|
154
|
+
* enforcer RFC-3986-normalizes before matching, so every domain with a path
|
|
155
|
+
* gate gets a transform-free FIRST rule matching dot-segment paths
|
|
156
|
+
* (first-match-wins ⇒ such requests go out credential-free), and declared
|
|
157
|
+
* prefixes themselves are validated (must start with "/", no dot-segments).
|
|
158
|
+
*
|
|
159
|
+
* Accepted divergence from iron-proxy (E2B): off-gate requests to an allowed
|
|
160
|
+
* domain pass WITHOUT the transform (Vercel terminates TLS only for
|
|
161
|
+
* transform-bearing domains and applies first-match-wins) — iron-proxy
|
|
162
|
+
* REFUSES them instead. Reachability differs; the property that matters is
|
|
163
|
+
* preserved on both substrates: the brokered credential rides only declared
|
|
164
|
+
* method/path combinations. Pinned by vercel-network-policy.test.ts.
|
|
165
|
+
*/
|
|
166
|
+
export declare function toVercelNetworkPolicy(policy: SandboxNetworkPolicy): VercelNetworkPolicy;
|
|
167
|
+
/** Vercel caps sandbox tags at 5. Build the tag set with the fleet `executor`
|
|
168
|
+
* tag always present and always winning over caller metadata — `listOwned` /
|
|
169
|
+
* orphan reconciliation scope on it, so a clobbered or dropped executor tag
|
|
170
|
+
* makes the sandbox invisible to cleanup. When metadata overflows the cap,
|
|
171
|
+
* keep executor + the lexicographically-first 4 metadata keys, so what gets
|
|
172
|
+
* dropped is deterministic rather than dependent on object insertion order. */
|
|
173
|
+
export declare const VERCEL_MAX_TAGS = 5;
|
|
174
|
+
export declare function buildVercelTags(metadata: Record<string, string>): Record<string, string>;
|
|
96
175
|
/** Minimal SandboxProvider that targets the current process's host VM.
|
|
97
176
|
* `commands.run` → `child_process.spawn`; `files.write` → `fs.writeFile`;
|
|
98
177
|
* `kill` is a no-op because the caller IS the VM. Used by
|
|
@@ -54,3 +54,9 @@ export declare function stepInputPath(stepIndex: number): string;
|
|
|
54
54
|
* context. The serveStep reads from this exact path; both halves use
|
|
55
55
|
* this helper. */
|
|
56
56
|
export declare function requestContextPath(stepIndex: number): string;
|
|
57
|
+
/** Sandbox-side path where the runner persists the sentinel line as a
|
|
58
|
+
* FILE, keyed by the per-invocation token (unique even across resumes
|
|
59
|
+
* of the same step index). stdout is the fast path, but providers can
|
|
60
|
+
* drop the tail of a heavy stdout stream — the invoker falls back to
|
|
61
|
+
* reading this file when no sentinel is found on stdout. */
|
|
62
|
+
export declare function stepResultFilePath(token: string): string;
|
|
@@ -48,7 +48,7 @@ export type StepResult<TOutput = unknown> = {
|
|
|
48
48
|
* it so the runtime type and the parsed shape can't drift.
|
|
49
49
|
*
|
|
50
50
|
* Loose by design (the inner zod shape stops at orchestration fields
|
|
51
|
-
* the engine needs): the SDK wrapper layer (`
|
|
51
|
+
* the engine needs): the SDK wrapper layer (`sleep` /
|
|
52
52
|
* `sleep` / `waitForEvent`) owns its own payload contract, and the
|
|
53
53
|
* engine treats `payload` as opaque. */
|
|
54
54
|
export declare const StepPauseRequestSchema: z.ZodObject<{
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { InvokeAndWaitOptions, RunStatus } from "../client.js";
|
|
3
3
|
import type { RequestContext } from "../request-context/request-context.js";
|
|
4
4
|
import type { PauseRequest } from "../pause/pause-core.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type { WaitForEventRequest } from "../pause/wrappers.js";
|
|
6
6
|
import type { SandboxProvider } from "./sandbox.js";
|
|
7
7
|
/** The identity of this workflow run. */
|
|
8
8
|
export interface WorkflowRun {
|
|
@@ -38,8 +38,6 @@ export interface BaseExecutionContext {
|
|
|
38
38
|
* PauseExpiredError / PauseSchemaError. See ADR-0006 / ADR-0011.
|
|
39
39
|
*/
|
|
40
40
|
pause<T = unknown>(req: PauseRequest<T>): Promise<T>;
|
|
41
|
-
/** Pause for a typed decision (a `schema` is required). Wrapper over `pause`. */
|
|
42
|
-
requestDecision<T>(req: RequestDecisionRequest<T>): Promise<T>;
|
|
43
41
|
/** Lightweight timed pause — resolves after `durationMs`, no snapshot. */
|
|
44
42
|
sleep(durationMs: number): Promise<void>;
|
|
45
43
|
/** Pause until an event resumes by `correlationKey`. Wrapper over `pause`. */
|
|
@@ -45,17 +45,8 @@ export interface SandboxEnvironmentDefinition {
|
|
|
45
45
|
* useful — an env with no snapshot can't be referenced as a
|
|
46
46
|
* `bootFrom` on another workflow). */
|
|
47
47
|
snapshots?: SnapshotConfig;
|
|
48
|
-
/** Override the sugar's `memory: false` default. Setup workflows
|
|
49
|
-
* don't typically benefit from memory extraction; opt in explicitly
|
|
50
|
-
* when they do. */
|
|
51
|
-
memory?: boolean;
|
|
52
48
|
}
|
|
53
49
|
/** Sugar over `defineWorkflow` for setup-only workflows that exist to
|
|
54
50
|
* capture a snapshot. The workflow takes no meaningful input and returns
|
|
55
|
-
* nothing — its value is the side effect on the sandbox VM.
|
|
56
|
-
*
|
|
57
|
-
* Defaults `memory: false` because sandbox environments emit setup
|
|
58
|
-
* output (npm installs, command exit codes) rather than agent traces
|
|
59
|
-
* worth memorising. Authors can opt in explicitly via `env.memory:
|
|
60
|
-
* true` if their environment somehow does want extraction. */
|
|
51
|
+
* nothing — its value is the side effect on the sandbox VM. */
|
|
61
52
|
export declare function defineSandboxEnvironment(env: SandboxEnvironmentDefinition): Workflow<Record<string, unknown>, void>;
|
package/dist/types/sandbox.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Sandbox abstraction — the compute environment an agent runs inside.
|
|
3
3
|
* Provider-agnostic: E2B, Vercel, Docker, or any other backend implements this.
|
|
4
4
|
*/
|
|
5
|
+
import type { SandboxNetworkPolicy } from "../sandbox.js";
|
|
5
6
|
/** Base compute interface — pure I/O, no filesystem path or git concerns. */
|
|
6
7
|
export interface SandboxCommandRunOptions {
|
|
7
8
|
cwd?: string;
|
|
@@ -20,6 +21,22 @@ export interface SandboxCommandResult {
|
|
|
20
21
|
stdout: string;
|
|
21
22
|
stderr: string;
|
|
22
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* A sandbox provider implements the RAW provider operations only. It does NOT
|
|
26
|
+
* implement transient-failure retry/backoff: reconnecting and snapshotting both
|
|
27
|
+
* pause/resume/transition the sandbox and race with it being reclaimed/stopped
|
|
28
|
+
* under load (E2B "Paused sandbox not found", Vercel stopping-sandbox calls,
|
|
29
|
+
* 5xx/429, dropped connections). That resilience is supplied centrally —
|
|
30
|
+
* `reconnectSandbox` runs every provider's `reconnect` through
|
|
31
|
+
* `withSandboxRetry` (generic p-retry backoff, gated by
|
|
32
|
+
* `isTransientSandboxError`), and `createSandbox`/`reconnectSandbox` wrap the
|
|
33
|
+
* returned provider's `snapshot` the same way (via `withSnapshotRetry`).
|
|
34
|
+
* `create` is deliberately NOT retried: a create whose response is lost would
|
|
35
|
+
* mint a second billed sandbox and orphan the first; provision failures are
|
|
36
|
+
* re-attempted at the workflow layer instead. So: a NEW provider only writes
|
|
37
|
+
* the happy-path call; the abstraction guarantees the robust pattern, and no
|
|
38
|
+
* provider re-implements it.
|
|
39
|
+
*/
|
|
23
40
|
export interface SandboxProvider {
|
|
24
41
|
sandboxId: string;
|
|
25
42
|
/** Working directory for the agent process. Set by onStart after environment setup. */
|
|
@@ -31,9 +48,9 @@ export interface SandboxProvider {
|
|
|
31
48
|
write(path: string, content: string): Promise<void>;
|
|
32
49
|
};
|
|
33
50
|
kill(): Promise<void>;
|
|
34
|
-
/** Capture the running sandbox's state as a reusable snapshot. Vercel
|
|
35
|
-
*
|
|
36
|
-
*
|
|
51
|
+
/** Capture the running sandbox's state as a reusable snapshot. Vercel and E2B
|
|
52
|
+
* both support it natively (E2B via `createSnapshot()`); providers without a
|
|
53
|
+
* live-snapshot primitive return `undefined`. Used by the server's
|
|
37
54
|
* `--build` flow to stamp the snapshot id on the workflow row.
|
|
38
55
|
*
|
|
39
56
|
* `sizeBytes` is the on-disk footprint reported by the provider. May
|
|
@@ -43,6 +60,13 @@ export interface SandboxProvider {
|
|
|
43
60
|
snapshotId: string;
|
|
44
61
|
sizeBytes?: number;
|
|
45
62
|
}>;
|
|
63
|
+
/** Replace the live sandbox's egress policy in place. Vercel implements
|
|
64
|
+
* it via `sandbox.update({ networkPolicy })` (2.x) so the server can
|
|
65
|
+
* push a freshly resolved policy — with re-minted connector access
|
|
66
|
+
* tokens — before each step instead of relying on the policy baked at
|
|
67
|
+
* create. Providers whose enforcement lives inside the VM (E2B
|
|
68
|
+
* iron-proxy) leave it undefined. */
|
|
69
|
+
updateNetworkPolicy?(policy: SandboxNetworkPolicy): Promise<void>;
|
|
46
70
|
}
|
|
47
71
|
/** Stateless provider-level snapshot deletion — no live sandbox needed,
|
|
48
72
|
* called from customer-initiated `DELETE /workflows/:runId/snapshot`. */
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Keep this module free of value imports from `types/workflow.ts` or
|
|
9
9
|
* `workflow-steps/workflow.ts` — it is the cycle-break point.
|
|
10
10
|
*/
|
|
11
|
-
import type { SandboxNetworkPolicy } from "../sandbox.js";
|
|
11
|
+
import type { SandboxNetworkPolicy, SandboxSize } from "../sandbox.js";
|
|
12
12
|
import type { Processor } from "../processors/processor.js";
|
|
13
13
|
/**
|
|
14
14
|
* Workflow-level metadata read by the server at registration. Lives on
|
|
@@ -18,10 +18,6 @@ import type { Processor } from "../processors/processor.js";
|
|
|
18
18
|
* The bundler reads these from the default export at registration time
|
|
19
19
|
* and forwards them to the server's POST /api/v1/templates payload.
|
|
20
20
|
*/
|
|
21
|
-
/** Whether the built-in Workflow Memory extractor should run after this
|
|
22
|
-
* workflow completes. Boolean toggle — custom post-run workflows live
|
|
23
|
-
* in the separate `postRunHooks` array on `WorkflowMetadata`. */
|
|
24
|
-
export type WorkflowMemoryConfig = boolean;
|
|
25
21
|
/** Boot from a specific captured snapshot, addressed by its id. Operators
|
|
26
22
|
* pick one from the dashboard snapshot list (or `agentc snapshot list`) and
|
|
27
23
|
* paste the id here. */
|
|
@@ -79,6 +75,74 @@ export interface IOSchema {
|
|
|
79
75
|
/** Alias kept for backwards source-compatibility with the original
|
|
80
76
|
* output-only release. New code should prefer `IOSchema`. */
|
|
81
77
|
export type OutputSchema = IOSchema;
|
|
78
|
+
/** Per-connector HTTP request matcher (Tier-2 capability narrowing). The
|
|
79
|
+
* iron-proxy / firewall consults these when deciding whether to attach the
|
|
80
|
+
* brokered Authorization header to an outbound request. A request to the
|
|
81
|
+
* connector host whose method is not in `methods`, or whose path matches no
|
|
82
|
+
* entry in `pathPrefixes`, is refused (403) and the token is WITHHELD. */
|
|
83
|
+
export interface ConnectorRequestRules {
|
|
84
|
+
/** Allowed HTTP methods (upper-case). Omit = any method (subject to
|
|
85
|
+
* `access`). */
|
|
86
|
+
methods?: string[];
|
|
87
|
+
/** Allowed path prefixes (matched against the request path, case-
|
|
88
|
+
* sensitive). Omit = any path. */
|
|
89
|
+
pathPrefixes?: string[];
|
|
90
|
+
}
|
|
91
|
+
/** One provider entry in a workflow's `connectors` declaration (ADR-0007).
|
|
92
|
+
* Scopes are the provider-side OAuth scopes the workflow's API calls
|
|
93
|
+
* need; registration warns when no installed grant covers them. */
|
|
94
|
+
export interface ConnectorRequirement {
|
|
95
|
+
scopes?: string[];
|
|
96
|
+
/** Coarse capability bound on the brokered token. `"read"` defaults the
|
|
97
|
+
* allowed methods to GET/HEAD/OPTIONS (unless `request.methods` overrides)
|
|
98
|
+
* and, for the GitHub App provider, narrows the minted installation token
|
|
99
|
+
* to `contents: read`. `"write"` permits all methods. Omit = `"write"`
|
|
100
|
+
* (unchanged broad behaviour). */
|
|
101
|
+
access?: "read" | "write";
|
|
102
|
+
/** Fine-grained per-request matcher enforced at the egress proxy. */
|
|
103
|
+
request?: ConnectorRequestRules;
|
|
104
|
+
}
|
|
105
|
+
/** Map of provider key (`github`, `slack`, …) → requirement. Declaring a
|
|
106
|
+
* provider here makes the server resolve an authorized grant at dispatch
|
|
107
|
+
* and inject a fresh access token at the network layer — workflow code
|
|
108
|
+
* calls the provider API with plain fetch/SDKs and never sees the token. */
|
|
109
|
+
export type ConnectorRequirements = Record<string, ConnectorRequirement>;
|
|
110
|
+
/** Marks this workflow as a catalogue OPERATION of a connector — e.g. the
|
|
111
|
+
* `create-issue` operation of the `github` connector. Operations are
|
|
112
|
+
* ordinary workflows (deterministic or agent-driven) that declare the
|
|
113
|
+
* matching `connectors` requirement; the tag is what groups them under
|
|
114
|
+
* the connector in the dashboard catalogue and the agent-facing
|
|
115
|
+
* operation listing. Together with `description` + `input`/`output`
|
|
116
|
+
* schemas this gives every operation MCP-tool-like self-description:
|
|
117
|
+
* what it does, what it takes, what it produces. */
|
|
118
|
+
export interface ConnectorOperationTag {
|
|
119
|
+
provider: string;
|
|
120
|
+
operation: string;
|
|
121
|
+
}
|
|
122
|
+
/** Tier-1 invoke ACL. When a workflow brokers a connector credential AND
|
|
123
|
+
* declares an `invokePolicy`, dispatch evaluates the calling principal
|
|
124
|
+
* against it BEFORE binding any grant. A caller matching ANY provided
|
|
125
|
+
* clause passes; matching none → HTTP 403. Omit = whole team (unchanged).
|
|
126
|
+
*
|
|
127
|
+
* Each clause is OR-combined:
|
|
128
|
+
* - `users`: `"owner"` (only the registering user) or an array of user
|
|
129
|
+
* ids / emails the caller must be one of.
|
|
130
|
+
* - `apiKeys`: api-key ids permitted to invoke.
|
|
131
|
+
* - `workflows`: parent workflow names permitted to child-invoke this one. */
|
|
132
|
+
export interface InvokePolicy {
|
|
133
|
+
users?: "owner" | string[];
|
|
134
|
+
apiKeys?: string[];
|
|
135
|
+
workflows?: string[];
|
|
136
|
+
}
|
|
137
|
+
/** Sandbox machine resources for a workflow's runs. A coarse size knob
|
|
138
|
+
* today; kept as its own object so finer controls (disk, gpu, …) can be
|
|
139
|
+
* added later without reshaping `WorkflowMetadata`. */
|
|
140
|
+
export interface SandboxResources {
|
|
141
|
+
/** Machine size — `small | medium | large`. Maps to provider specs at
|
|
142
|
+
* create time (Vercel: 2 / 4 / 8 vCPU, 2048 MB RAM per vCPU). Omit →
|
|
143
|
+
* `"small"`. E2B sizing is template-defined and ignores this. */
|
|
144
|
+
size?: SandboxSize;
|
|
145
|
+
}
|
|
82
146
|
export interface WorkflowMetadata {
|
|
83
147
|
/** One-line, human-readable description of what the workflow does.
|
|
84
148
|
* Surfaced on the dashboard template tile + run page header. Authors
|
|
@@ -99,15 +163,19 @@ export interface WorkflowMetadata {
|
|
|
99
163
|
outputSchema?: IOSchema;
|
|
100
164
|
/** All snapshot config — boot source + capture mode. */
|
|
101
165
|
snapshots?: SnapshotConfig;
|
|
166
|
+
/** Sandbox machine resources (size). Optional; omit → small. */
|
|
167
|
+
resources?: SandboxResources;
|
|
102
168
|
processors?: readonly Processor[];
|
|
103
|
-
/**
|
|
104
|
-
*
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
*
|
|
109
|
-
|
|
110
|
-
|
|
169
|
+
/** Connector requirements — providers whose APIs this workflow calls.
|
|
170
|
+
* Dispatch resolves an authorized grant per provider and injects a
|
|
171
|
+
* fresh access token at the network layer (ADR-0007). */
|
|
172
|
+
connectors?: ConnectorRequirements;
|
|
173
|
+
/** Catalogue tag marking this workflow as an operation of a connector.
|
|
174
|
+
* See `ConnectorOperationTag`. */
|
|
175
|
+
connectorOperation?: ConnectorOperationTag;
|
|
176
|
+
/** Tier-1 invoke ACL — who may dispatch this connector-brokering workflow.
|
|
177
|
+
* See `InvokePolicy`. */
|
|
178
|
+
invokePolicy?: InvokePolicy;
|
|
111
179
|
}
|
|
112
180
|
/**
|
|
113
181
|
* Pull the server-readable declarations off a source object (run-form
|
package/dist/types/workflow.d.ts
CHANGED
|
@@ -21,13 +21,14 @@ import type { Processor } from "../processors/processor.js";
|
|
|
21
21
|
import type { StepWorkflowDefinition } from "../workflow-steps/workflow.js";
|
|
22
22
|
import type { Workflow } from "../workflow-steps/types.js";
|
|
23
23
|
import type { BaseExecutionContext } from "./execution-context.js";
|
|
24
|
+
import { type ConnectorRequirements, type ConnectorOperationTag, type InvokePolicy } from "./workflow-metadata.js";
|
|
24
25
|
export type { WorkflowRun, InvokeChild, BaseExecutionContext } from "./execution-context.js";
|
|
25
26
|
export type { WorkflowMetadata } from "./workflow-metadata.js";
|
|
26
27
|
export interface AgentEventSink {
|
|
27
28
|
emit(event: AgentLifecycleEvent): void | Promise<void>;
|
|
28
29
|
}
|
|
29
|
-
import type {
|
|
30
|
-
export type {
|
|
30
|
+
import type { SnapshotConfig, BootSnapshot, ReuseSnapshot, IOSchema, OutputSchema, SandboxResources } from "./workflow-metadata.js";
|
|
31
|
+
export type { SnapshotConfig, BootSnapshot, ReuseSnapshot, IOSchema, OutputSchema, SandboxResources };
|
|
31
32
|
/** Turn/iteration budget for `agent(opts)`. Re-exported here so authors
|
|
32
33
|
* can type per-invoke budget overrides they pass as workflow input. */
|
|
33
34
|
export interface AgentBudget {
|
|
@@ -99,6 +100,13 @@ export interface WorkflowDefinition<TOutput = unknown, TInput extends Record<str
|
|
|
99
100
|
* `invoke({ snapshots })` overrides this default.
|
|
100
101
|
*/
|
|
101
102
|
snapshots?: SnapshotConfig;
|
|
103
|
+
/**
|
|
104
|
+
* Sandbox machine size — `small` (default) | `medium` | `large`. Maps to
|
|
105
|
+
* provider machine specs at create (Vercel: 2 / 4 / 8 vCPU, 2048 MB RAM
|
|
106
|
+
* per vCPU). Optional; omit for `small`. E2B sizing is template-defined
|
|
107
|
+
* and ignores this. Per-invocation `invoke({ size })` overrides it.
|
|
108
|
+
*/
|
|
109
|
+
resources?: SandboxResources;
|
|
102
110
|
/**
|
|
103
111
|
* Outbound network policy for the runner sandbox.
|
|
104
112
|
* Use "*": [] to allow all traffic while still injecting headers for specific domains.
|
|
@@ -135,14 +143,41 @@ export interface WorkflowDefinition<TOutput = unknown, TInput extends Record<str
|
|
|
135
143
|
* }
|
|
136
144
|
*/
|
|
137
145
|
placeholders?: Record<string, string>;
|
|
138
|
-
/**
|
|
139
|
-
*
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Connector requirements (ADR-0007). Declaring a provider makes the
|
|
148
|
+
* server resolve an authorized OAuth grant for the calling principal at
|
|
149
|
+
* dispatch time and inject a fresh access token at the network layer —
|
|
150
|
+
* workflow code talks to the provider API with plain fetch/SDKs and
|
|
151
|
+
* never holds the credential.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* connectors: { github: { scopes: ["repo"] } }
|
|
155
|
+
*/
|
|
156
|
+
connectors?: ConnectorRequirements;
|
|
157
|
+
/**
|
|
158
|
+
* Marks this workflow as a catalogue OPERATION of a connector — e.g.
|
|
159
|
+
* the `create-issue` operation of the `github` connector. Pair with
|
|
160
|
+
* `description` + `input`/`output` schemas so the operation is fully
|
|
161
|
+
* self-describing (MCP-tool-like) to humans and agents browsing the
|
|
162
|
+
* connector catalogue.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* connectorOperation: { provider: "github", operation: "create-issue" }
|
|
166
|
+
*/
|
|
167
|
+
connectorOperation?: ConnectorOperationTag;
|
|
168
|
+
/**
|
|
169
|
+
* Tier-1 invoke ACL (connector credential boundary). When this workflow
|
|
170
|
+
* declares `connectors` (it brokers a credential) AND an `invokePolicy`,
|
|
171
|
+
* the server evaluates the calling principal against the policy BEFORE
|
|
172
|
+
* binding any grant — a caller matching no clause is refused with HTTP
|
|
173
|
+
* 403. Has no effect on workflows that declare no connectors. Omit to
|
|
174
|
+
* leave the workflow invokable by the whole team.
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* connectors: { github: { access: "read" } },
|
|
178
|
+
* invokePolicy: { users: "owner", workflows: ["nightly-orchestrator"] }
|
|
179
|
+
*/
|
|
180
|
+
invokePolicy?: InvokePolicy;
|
|
146
181
|
}
|
|
147
182
|
/**
|
|
148
183
|
* Declare a workflow. Two forms; both return a `Workflow` whose
|
package/dist/utils/bundler.d.ts
CHANGED
|
@@ -19,14 +19,40 @@
|
|
|
19
19
|
* this function returns alongside the bundled bytes, and cross-checks the
|
|
20
20
|
* manifest's `sourceHash` against the source it received.
|
|
21
21
|
*/
|
|
22
|
-
import type {
|
|
22
|
+
import type { SnapshotConfig } from "../types/workflow.js";
|
|
23
|
+
import type { IOSchema, ConnectorRequirements, ConnectorOperationTag, InvokePolicy, SandboxResources } from "../types/workflow-metadata.js";
|
|
23
24
|
import type { SandboxNetworkPolicy } from "../sandbox.js";
|
|
24
25
|
import { type WorkflowPlan } from "../types/workflow-plan.js";
|
|
25
26
|
/** Bumped when the manifest contract changes in a way the server should
|
|
26
27
|
* notice. The server pins its manifest schema to this exact value (no
|
|
27
28
|
* `>=` accepted) so a manifest forged with a future version is refused
|
|
28
|
-
* by zod before any other validation runs.
|
|
29
|
-
|
|
29
|
+
* by zod before any other validation runs. NOTE: this gates NEW
|
|
30
|
+
* registrations only — nothing re-validates stored `workflows.metadata`
|
|
31
|
+
* rows, so a non-additive metadata change ALSO needs a server-side
|
|
32
|
+
* warning for already-registered workflows (see `warnRetiredMetadataKeys`
|
|
33
|
+
* in server/src/engine/dispatch.ts).
|
|
34
|
+
*
|
|
35
|
+
* v2: `memory` / `postRunHooks` registration options removed — bundles
|
|
36
|
+
* from CLIs that still emit them are refused at register.
|
|
37
|
+
*
|
|
38
|
+
* TODO(registration-versioning): turn this constant into a real migration
|
|
39
|
+
* policy so breaking changes start with a number, not an investigation:
|
|
40
|
+
* 1. Server keeps a MIN_SUPPORTED_BUNDLER_VERSION alongside the exact
|
|
41
|
+
* pin — versions in [min, current] keep dispatching; below min gets
|
|
42
|
+
* an ACTIONABLE 409 naming the agentc version to re-register with
|
|
43
|
+
* (today, pre-enforcement templates with no manifest at all 409 with
|
|
44
|
+
* no migration path — see dispatch.ts).
|
|
45
|
+
* 2. Stamp the SDK version into the manifest at registration, so
|
|
46
|
+
* runtime-compat breaks (not just bundler-format breaks) are also
|
|
47
|
+
* queryable per stored workflow.
|
|
48
|
+
* 3. Operator audit = one query: workflows whose
|
|
49
|
+
* metadata.manifest.bundlerVersion < current (or missing), grouped
|
|
50
|
+
* by team/owner — the definitive "who needs migration" list before
|
|
51
|
+
* any breaking release.
|
|
52
|
+
* 4. Dashboard badge on stale templates: "registered with an older
|
|
53
|
+
* toolchain — re-register to update" (pairs with the template
|
|
54
|
+
* connector/metadata card work). */
|
|
55
|
+
export declare const BUNDLER_VERSION = 2;
|
|
30
56
|
/**
|
|
31
57
|
* Manifest emitted alongside the bundled source. Fully serialisable JSON;
|
|
32
58
|
* the server validates its shape with zod and never inspects the source
|
|
@@ -58,17 +84,22 @@ export interface BundledWorkflow {
|
|
|
58
84
|
/** Snapshot config from the workflow definition — `bootFrom` (where to
|
|
59
85
|
* restore at run start), `save`, `retain`. */
|
|
60
86
|
snapshots?: SnapshotConfig;
|
|
87
|
+
/** Sandbox machine size declared via `defineWorkflow({ resources: { size } })`. */
|
|
88
|
+
resources?: SandboxResources;
|
|
61
89
|
workflowPlan: WorkflowPlan;
|
|
62
|
-
/** Workflow Memory extractor config. */
|
|
63
|
-
memory?: WorkflowMemoryConfig;
|
|
64
|
-
/** Ordered post-hook workflow names declared on the workflow. */
|
|
65
|
-
postRunHooks?: readonly string[];
|
|
66
90
|
/** Compact JSON-Schema-shaped description of the workflow's input
|
|
67
91
|
* type. Extracted from the workflow's declared `input` zod schema
|
|
68
92
|
* at bundle time; undefined when the schema is `z.unknown()`. */
|
|
69
|
-
inputSchema?:
|
|
93
|
+
inputSchema?: IOSchema;
|
|
70
94
|
/** Same for the workflow's output zod schema. */
|
|
71
|
-
outputSchema?:
|
|
95
|
+
outputSchema?: IOSchema;
|
|
96
|
+
/** Connector requirements declared on the workflow (ADR-0007). */
|
|
97
|
+
connectors?: ConnectorRequirements;
|
|
98
|
+
/** Connector-catalogue operation tag, when this workflow is one. */
|
|
99
|
+
connectorOperation?: ConnectorOperationTag;
|
|
100
|
+
/** Tier-1 invoke ACL — who may dispatch this connector-brokering
|
|
101
|
+
* workflow (`defineWorkflow({ invokePolicy })`). */
|
|
102
|
+
invokePolicy?: InvokePolicy;
|
|
72
103
|
}
|
|
73
104
|
/**
|
|
74
105
|
* Parse the bundled source and assert the default export is a CallExpression
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import type { z } from "zod";
|
|
22
22
|
import type { Step, Workflow } from "./types.js";
|
|
23
|
-
import type { SnapshotConfig,
|
|
23
|
+
import type { SnapshotConfig, SandboxResources } from "../types/workflow-metadata.js";
|
|
24
24
|
import type { SandboxNetworkPolicy } from "../sandbox.js";
|
|
25
25
|
import type { Processor } from "../processors/processor.js";
|
|
26
26
|
export interface WorkflowBuilder<TInput, TCurrent> {
|
|
@@ -46,8 +46,9 @@ export interface StepWorkflowDefinition<TInput, TOutput> {
|
|
|
46
46
|
networkPolicy?: SandboxNetworkPolicy;
|
|
47
47
|
placeholders?: Record<string, string>;
|
|
48
48
|
snapshots?: SnapshotConfig;
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
/** Sandbox machine size — small (default) | medium | large. Vercel maps
|
|
50
|
+
* it to vCPUs; E2B sizing is template-defined. Omit → small. */
|
|
51
|
+
resources?: SandboxResources;
|
|
51
52
|
processors?: readonly Processor[];
|
|
52
53
|
}
|
|
53
54
|
export declare function createStepWorkflow<TInput, TOutput>(opts: StepWorkflowDefinition<TInput, TOutput>): WorkflowBuilder<TInput, TInput>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-compose/sdk",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.8",
|
|
4
4
|
"description": "Client library for agent-compose — define agents, runtimes, and workflows, and invoke them against an agent-compose server.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"@babel/parser": "^7.29.3",
|
|
65
65
|
"@babel/types": "^7.29.0",
|
|
66
66
|
"@e2b/desktop": "^1.1.2",
|
|
67
|
-
"@vercel/sandbox": "^
|
|
67
|
+
"@vercel/sandbox": "^2.2.0",
|
|
68
68
|
"ai": "^6.0.175",
|
|
69
69
|
"e2b": "^2.3.0",
|
|
70
70
|
"ofetch": "^1.5.1",
|