@agent-relay/sandbox 0.1.2 → 0.1.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/README.md +226 -5
- package/dist/agent37/client.d.ts +115 -0
- package/dist/agent37/client.d.ts.map +1 -0
- package/dist/agent37/client.js +270 -0
- package/dist/agent37/client.js.map +1 -0
- package/dist/agent37/runtime.d.ts +415 -0
- package/dist/agent37/runtime.d.ts.map +1 -0
- package/dist/agent37/runtime.js +855 -0
- package/dist/agent37/runtime.js.map +1 -0
- package/dist/agentcore/bench.d.ts +93 -0
- package/dist/agentcore/bench.d.ts.map +1 -0
- package/dist/agentcore/bench.js +125 -0
- package/dist/agentcore/bench.js.map +1 -0
- package/dist/agentcore/capabilities.d.ts +169 -0
- package/dist/agentcore/capabilities.d.ts.map +1 -0
- package/dist/agentcore/capabilities.js +153 -0
- package/dist/agentcore/capabilities.js.map +1 -0
- package/dist/agentcore/config.d.ts +156 -0
- package/dist/agentcore/config.d.ts.map +1 -0
- package/dist/agentcore/config.js +12 -0
- package/dist/agentcore/config.js.map +1 -0
- package/dist/agentcore/internal/sdk.d.ts +181 -0
- package/dist/agentcore/internal/sdk.d.ts.map +1 -0
- package/dist/agentcore/internal/sdk.js +223 -0
- package/dist/agentcore/internal/sdk.js.map +1 -0
- package/dist/agentcore/runtime.d.ts +177 -0
- package/dist/agentcore/runtime.d.ts.map +1 -0
- package/dist/agentcore/runtime.js +706 -0
- package/dist/agentcore/runtime.js.map +1 -0
- package/dist/daytona/runtime.d.ts +23 -0
- package/dist/daytona/runtime.d.ts.map +1 -1
- package/dist/daytona/runtime.js +167 -5
- package/dist/daytona/runtime.js.map +1 -1
- package/dist/daytona/wire-supplement.d.ts +25 -0
- package/dist/daytona/wire-supplement.d.ts.map +1 -0
- package/dist/daytona/wire-supplement.js +16 -0
- package/dist/daytona/wire-supplement.js.map +1 -0
- package/dist/e2b/runtime.d.ts +107 -72
- package/dist/e2b/runtime.d.ts.map +1 -1
- package/dist/e2b/runtime.js +675 -190
- package/dist/e2b/runtime.js.map +1 -1
- package/dist/freestyle/capabilities.d.ts +28 -0
- package/dist/freestyle/capabilities.d.ts.map +1 -0
- package/dist/freestyle/capabilities.js +42 -0
- package/dist/freestyle/capabilities.js.map +1 -0
- package/dist/freestyle/config.d.ts +36 -0
- package/dist/freestyle/config.d.ts.map +1 -0
- package/dist/freestyle/config.js +2 -0
- package/dist/freestyle/config.js.map +1 -0
- package/dist/freestyle/internal/sdk.d.ts +69 -0
- package/dist/freestyle/internal/sdk.d.ts.map +1 -0
- package/dist/freestyle/internal/sdk.js +19 -0
- package/dist/freestyle/internal/sdk.js.map +1 -0
- package/dist/freestyle/runtime.d.ts +163 -0
- package/dist/freestyle/runtime.d.ts.map +1 -0
- package/dist/freestyle/runtime.js +798 -0
- package/dist/freestyle/runtime.js.map +1 -0
- package/dist/index.d.ts +35 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/microsandbox/runtime.d.ts +793 -0
- package/dist/microsandbox/runtime.d.ts.map +1 -0
- package/dist/microsandbox/runtime.js +2303 -0
- package/dist/microsandbox/runtime.js.map +1 -0
- package/dist/modal/bench.d.ts +199 -0
- package/dist/modal/bench.d.ts.map +1 -0
- package/dist/modal/bench.js +450 -0
- package/dist/modal/bench.js.map +1 -0
- package/dist/modal/capabilities.d.ts +180 -0
- package/dist/modal/capabilities.d.ts.map +1 -0
- package/dist/modal/capabilities.js +159 -0
- package/dist/modal/capabilities.js.map +1 -0
- package/dist/modal/config.d.ts +192 -0
- package/dist/modal/config.d.ts.map +1 -0
- package/dist/modal/config.js +169 -0
- package/dist/modal/config.js.map +1 -0
- package/dist/modal/internal/sdk.d.ts +141 -0
- package/dist/modal/internal/sdk.d.ts.map +1 -0
- package/dist/modal/internal/sdk.js +51 -0
- package/dist/modal/internal/sdk.js.map +1 -0
- package/dist/modal/runtime.d.ts +271 -0
- package/dist/modal/runtime.d.ts.map +1 -0
- package/dist/modal/runtime.js +871 -0
- package/dist/modal/runtime.js.map +1 -0
- package/dist/mount-script.d.ts +16 -1
- package/dist/mount-script.d.ts.map +1 -1
- package/dist/mount-script.js +76 -15
- package/dist/mount-script.js.map +1 -1
- package/dist/orchestrator.d.ts +15 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +13 -1
- package/dist/orchestrator.js.map +1 -1
- package/dist/port.d.ts +144 -6
- package/dist/port.d.ts.map +1 -1
- package/dist/port.js +27 -5
- package/dist/port.js.map +1 -1
- package/dist/types.d.ts +50 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/vercel/bench.d.ts +169 -0
- package/dist/vercel/bench.d.ts.map +1 -0
- package/dist/vercel/bench.js +337 -0
- package/dist/vercel/bench.js.map +1 -0
- package/dist/vercel/capabilities.d.ts +106 -0
- package/dist/vercel/capabilities.d.ts.map +1 -0
- package/dist/vercel/capabilities.js +128 -0
- package/dist/vercel/capabilities.js.map +1 -0
- package/dist/vercel/config.d.ts +119 -0
- package/dist/vercel/config.d.ts.map +1 -0
- package/dist/vercel/config.js +8 -0
- package/dist/vercel/config.js.map +1 -0
- package/dist/vercel/internal/sdk.d.ts +173 -0
- package/dist/vercel/internal/sdk.d.ts.map +1 -0
- package/dist/vercel/internal/sdk.js +117 -0
- package/dist/vercel/internal/sdk.js.map +1 -0
- package/dist/vercel/runtime.d.ts +305 -0
- package/dist/vercel/runtime.d.ts.map +1 -0
- package/dist/vercel/runtime.js +1169 -0
- package/dist/vercel/runtime.js.map +1 -0
- package/docs/freestyle.md +110 -0
- package/package.json +35 -4
package/README.md
CHANGED
|
@@ -10,10 +10,8 @@ run commands, stream results back, and tear it down.
|
|
|
10
10
|
The point of the port is that the orchestration layer holds no provider
|
|
11
11
|
knowledge. Swapping providers is a change of adapter, not a change of caller.
|
|
12
12
|
|
|
13
|
-
> **Status: pre-
|
|
14
|
-
>
|
|
15
|
-
> adapters and orchestrator land in subsequent changes, and the public API is
|
|
16
|
-
> not stable until a `1.0.0` release.
|
|
13
|
+
> **Status: pre-1.0.** The public API may still change before a `1.0.0`
|
|
14
|
+
> release.
|
|
17
15
|
|
|
18
16
|
## Install
|
|
19
17
|
|
|
@@ -23,6 +21,47 @@ npm install @agent-relay/sandbox
|
|
|
23
21
|
|
|
24
22
|
Provider SDKs are peer dependencies: install the one you intend to use. A
|
|
25
23
|
consumer that only runs local sandboxes does not need a remote provider SDK.
|
|
24
|
+
Adapters for providers that publish no JavaScript SDK speak their HTTP API
|
|
25
|
+
directly and add no dependency at all; they take an injectable `fetch` instead.
|
|
26
|
+
|
|
27
|
+
### Provider constraints
|
|
28
|
+
|
|
29
|
+
Each adapter inherits its provider SDK's requirements, and they are not all the
|
|
30
|
+
same as this package's:
|
|
31
|
+
|
|
32
|
+
| Adapter | Peer dependency | Requirements beyond this package's |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `DaytonaRuntime` | `@daytonaio/sdk` | — |
|
|
35
|
+
| `E2BSandboxRuntime` | `e2b` | — |
|
|
36
|
+
| `MicrosandboxRuntime` | `microsandbox` | **Node.js 22+**, a platform-specific native addon (macOS arm64, Linux x64/arm64, Windows x64/arm64), and — for its `local` backend — hardware virtualization: KVM on Linux, Apple Silicon on macOS, or WHP on Windows 10+ |
|
|
37
|
+
| `LocalSandboxRuntime` | — | A reachable local sandbox service |
|
|
38
|
+
|
|
39
|
+
The package itself keeps a Node 20 floor, because a consumer that never touches
|
|
40
|
+
the microsandbox adapter never loads that SDK: it is imported lazily, at first
|
|
41
|
+
use, and a load failure is reported with the constraint that most often
|
|
42
|
+
explains it.
|
|
43
|
+
|
|
44
|
+
### Microsandbox capabilities are backend-sensitive
|
|
45
|
+
|
|
46
|
+
`MicrosandboxRuntime.capabilities` is derived from the backend the instance is
|
|
47
|
+
bound to, not reported as a single process-wide constant:
|
|
48
|
+
|
|
49
|
+
| Capability | `local` | `cloud` | Why |
|
|
50
|
+
| --- | --- | --- | --- |
|
|
51
|
+
| `snapshots` | `true` | `false` | A snapshot is a host-local artifact: the installed SDK's typings describe `Snapshot` as an artifact on disk and resolve one under `~/.microsandbox/snapshots/<name>/`. This adapter consumes such an artifact from the calling host and never transfers it, so a create issued against a remote backend has nothing to resolve. Configuring `snapshot` with a cloud backend is refused in the constructor, before any SDK call. |
|
|
52
|
+
| `isolation` | `'strong'` | `'unknown'` | Locally the SDK boots a microVM with its own guest kernel on a virtualization-capable host, and the installed package states that requirement itself, so `'strong'` rests on something checkable here. This adapter observes and measures nothing about the cloud backend's isolation. |
|
|
53
|
+
|
|
54
|
+
Both values describe what this package has **established**, not what any
|
|
55
|
+
provider documents. `'unknown'` is not a synonym for weak and is not a claim
|
|
56
|
+
that the guarantee is missing — it means this package has not established one,
|
|
57
|
+
so a caller that requires a specific guarantee must decide for itself rather
|
|
58
|
+
than read an unverified `'strong'`.
|
|
59
|
+
|
|
60
|
+
Cloud region placement and resource enforcement are likewise not represented as
|
|
61
|
+
measured facts. Custom or published **ports are not supported**: the SDK builder
|
|
62
|
+
exposes `port()`/`portBind()`, but the ports this package targets have no
|
|
63
|
+
public-port surface, so the adapter never calls them and never implies a
|
|
64
|
+
reachable port.
|
|
26
65
|
|
|
27
66
|
## Design
|
|
28
67
|
|
|
@@ -40,6 +79,187 @@ is a required argument supplied by the caller. This keeps the package usable
|
|
|
40
79
|
outside the environment it was extracted from, and keeps credential handling in
|
|
41
80
|
the caller where it belongs.
|
|
42
81
|
|
|
82
|
+
Each adapter also declares what it genuinely supports rather than what its
|
|
83
|
+
method names imply. `resolveSandboxRuntimeCapabilities` reads that declaration,
|
|
84
|
+
so a caller learns up front whether a provider can reattach to a sandbox by id,
|
|
85
|
+
poll a background command, hand back a still-booting sandbox, or search by
|
|
86
|
+
label — instead of discovering the answer from a failure at run time.
|
|
87
|
+
|
|
88
|
+
### Daytona restart recovery
|
|
89
|
+
|
|
90
|
+
`DaytonaRuntime.start()` does not trust the provider state transition alone. It
|
|
91
|
+
rehydrates the SDK sandbox and runs a bounded `true` readiness probe after `start`,
|
|
92
|
+
because Daytona can report `STARTED` while its Toolbox exec daemon remains
|
|
93
|
+
unavailable. A healthy restart keeps the same sandbox ID. A failure during that
|
|
94
|
+
rehydration itself (auth, rate limit, or network) is not proof the exec daemon
|
|
95
|
+
is dead, so it is propagated as-is and never triggers a replacement.
|
|
96
|
+
|
|
97
|
+
When the post-start readiness probe fails, the runtime defaults to creating and proving
|
|
98
|
+
a replacement before deleting the unusable sandbox. The returned handle is
|
|
99
|
+
updated in place and can therefore have a new `id`; callers must persist that
|
|
100
|
+
returned ID. The replacement preserves the configured snapshot plus provider
|
|
101
|
+
labels, environment, lifecycle, volume, and network settings, but non-volume
|
|
102
|
+
filesystem changes in the old sandbox are not copied. Stateful callers that
|
|
103
|
+
prefer a hard failure to that trade-off can set `recreateOnFailedStart: false`.
|
|
104
|
+
|
|
105
|
+
### E2B runtime contract
|
|
106
|
+
|
|
107
|
+
`E2BSandboxRuntime` implements both the outer orchestration port and the live
|
|
108
|
+
`WorkflowRuntime` surface: metadata/state lookup, reattachment, synchronous and
|
|
109
|
+
durable asynchronous execution, upload/download, home-directory resolution,
|
|
110
|
+
pause/resume, and owned-resource teardown. It deliberately does not expose a
|
|
111
|
+
detached-launch method because E2B's public create call waits for a running
|
|
112
|
+
sandbox. Its live capability descriptor likewise reports PTY and streaming logs
|
|
113
|
+
as unsupported because this adapter exposes neither behavior, even though the
|
|
114
|
+
provider SDK has lower-level APIs for them.
|
|
115
|
+
|
|
116
|
+
The adapter applies an explicit sandbox lifetime on create, reconnect, and
|
|
117
|
+
synchronous use; `sandboxLifetimeMs` defaults to the configured asynchronous
|
|
118
|
+
run budget.
|
|
119
|
+
|
|
120
|
+
Synchronous runs always carry an explicit command lifetime cap, because E2B —
|
|
121
|
+
alone among the providers here — applies a 60-second default when the field is
|
|
122
|
+
omitted, while the orchestrator omits `timeoutMs` on most execs. A run without a
|
|
123
|
+
caller timeout therefore uses `syncRunBudgetMs`, which defaults to the
|
|
124
|
+
configured `runBudgetMs` (30 minutes unless set). An explicit caller timeout
|
|
125
|
+
always wins, and is the only thing that extends the sandbox past
|
|
126
|
+
`sandboxLifetimeMs`; the implicit budget never does.
|
|
127
|
+
|
|
128
|
+
Asynchronous session IDs are immutable idempotency keys. A retry
|
|
129
|
+
reconciles the durable admission record with E2B's process list and will not
|
|
130
|
+
erase the session directory or submit another copy. Status is pending only
|
|
131
|
+
while the matching provider process is present. If that process disappears
|
|
132
|
+
without publishing its exit sidecar, status becomes terminal with
|
|
133
|
+
`E2B_ASYNC_PROCESS_LOST_EXIT_CODE` (`255`).
|
|
134
|
+
|
|
135
|
+
### Daytona wire-supplement
|
|
136
|
+
|
|
137
|
+
Daytona's Sandbox wire response carries two fields — `sandboxClass` (the
|
|
138
|
+
sandbox's class/tier) and `warmPoolId` (set while a sandbox is an unclaimed
|
|
139
|
+
warm-pool member) — that exist on the low-level `@daytona/api-client` DTOs but
|
|
140
|
+
that the vendored `@daytonaio/sdk`'s `Sandbox` class does not copy onto
|
|
141
|
+
itself. `DaytonaRuntime.getWireSupplement(handle)` fetches both directly via
|
|
142
|
+
the SDK's low-level `sandboxApi`, the same reach pattern `runtime.ts` already
|
|
143
|
+
uses for detached create.
|
|
144
|
+
|
|
145
|
+
This is a narrow, deliberately temporary gap, not a fork of the SDK: the
|
|
146
|
+
other fields once suspected missing (`autoDestroyAt`, `autoPauseInterval`,
|
|
147
|
+
`spot`) already ship on the public `Sandbox` class as of `@daytonaio/sdk`
|
|
148
|
+
0.200.0–0.205.0 — a dependency bump alone covers those. Tracked upstream at
|
|
149
|
+
[daytona/clients#207](https://github.com/daytona/clients/issues/207)
|
|
150
|
+
(precedent: [#192](https://github.com/daytona/clients/pull/192), which added
|
|
151
|
+
`spot` the same way). Retire `src/daytona/wire-supplement.ts` once
|
|
152
|
+
`processSandboxDto()` copies `sandboxClass`/`warmPoolId` and a subsequent SDK
|
|
153
|
+
bump picks that up — `runtime.test.ts`'s `DaytonaRuntime smoke` suite has a
|
|
154
|
+
load-bearing regression test that fails once that happens.
|
|
155
|
+
### Freestyle runtime contract
|
|
156
|
+
|
|
157
|
+
`FreestyleRuntime` uses an explicit API key, home directory, ownership-name
|
|
158
|
+
prefix, and persistence policy. It never reads ambient credentials. Freestyle
|
|
159
|
+
does not expose labels on VM creation, so label lookup and warm leasing remain
|
|
160
|
+
unsupported; ownership and cleanup are scoped to collision-safe names under the
|
|
161
|
+
configured prefix. Deleted list rows are treated as gone only when the provider
|
|
162
|
+
sets `deleted: true`.
|
|
163
|
+
|
|
164
|
+
The adapter exposes buffered exec, file transfer, reattachment, owned-resource
|
|
165
|
+
listing, and verified deletion. Stop/start methods exist as a conservative
|
|
166
|
+
probe surface, but lifecycle remains undeclared because the live validation
|
|
167
|
+
account could not create a persistent VM. PTY, snapshots, streaming logs, fork,
|
|
168
|
+
and never-idle behavior are likewise not advertised without the required live
|
|
169
|
+
proof through this package's public port.
|
|
170
|
+
|
|
171
|
+
The official SDK is isolated under `src/freestyle/internal/`; public
|
|
172
|
+
configuration and capability metadata do not import vendor types. All create,
|
|
173
|
+
lookup, exec, lifecycle, and deletion operations have explicit deadlines. See
|
|
174
|
+
[the Freestyle adapter notes](./docs/freestyle.md) for dependency provenance,
|
|
175
|
+
provider constraints, and capability evidence.
|
|
176
|
+
|
|
177
|
+
### Vercel Sandbox runtime contract
|
|
178
|
+
|
|
179
|
+
`VercelSandboxRuntime` takes an explicit token, team id, project id, ownership
|
|
180
|
+
name prefix, and home directory, and passes credentials on every SDK call so the
|
|
181
|
+
vendor's own environment and on-disk OAuth fallbacks can never supply an
|
|
182
|
+
operator's identity.
|
|
183
|
+
|
|
184
|
+
A Vercel sandbox is addressed by its **name**, not an opaque id, so
|
|
185
|
+
`RuntimeHandle.id` carries the name and the configured prefix is a real
|
|
186
|
+
ownership boundary: stop, start, and delete refuse any name outside it.
|
|
187
|
+
`Sandbox.list` supports server-side `namePrefix` and `tags` filters, so label
|
|
188
|
+
lookup is a real search here — but every row is re-checked against the requested
|
|
189
|
+
tags in process, because a server filter that were ever ignored would hand back
|
|
190
|
+
someone else's sandbox as a warm lease. `getById` is implemented over `list`
|
|
191
|
+
rather than `Sandbox.get`, which resumes and bills the sandbox as a side effect.
|
|
192
|
+
|
|
193
|
+
Deletion is verified by absence, and a row that reappears under the same name
|
|
194
|
+
with a different `createdAt` counts as proof our sandbox is gone rather than as
|
|
195
|
+
a survivor. Failed verification retains the registration so cleanup stays
|
|
196
|
+
retryable. `acquire()` returns an `AsyncDisposable` for `await using` scopes.
|
|
197
|
+
|
|
198
|
+
Commands run as `sh -c` with `cwd` and `env` passed through provider fields, so
|
|
199
|
+
no caller value is spliced into a command string. Every operation carries its
|
|
200
|
+
own deadline, under an absolute `retryDeadlineMs` ceiling that spans the SDK's
|
|
201
|
+
internal retries.
|
|
202
|
+
|
|
203
|
+
Warm leasing, lifecycle, PTY, snapshots, streaming logs, fork, and verified
|
|
204
|
+
cleanup are all declared `false` pending live evidence; never-idle is settled
|
|
205
|
+
false, because every Vercel sandbox carries a termination deadline. The official
|
|
206
|
+
SDK is isolated under `src/vercel/internal/`. See
|
|
207
|
+
[the Vercel adapter notes](./docs/vercel.md) for dependency provenance,
|
|
208
|
+
capability evidence, and the active-CPU-versus-wall-clock pricing analysis.
|
|
209
|
+
|
|
210
|
+
Capabilities are also declared structurally. PTY and snapshots resolve to
|
|
211
|
+
`"not-exposed"` — real in the SDK, unreachable through this port, and so not
|
|
212
|
+
something a live probe may promote — while `lifetime` resolves to `"deadline"`,
|
|
213
|
+
the settled reason a Vercel sandbox has no never-idle tier. `filesystem` is
|
|
214
|
+
deliberately left `"unknown"`: durability is per-instance configuration
|
|
215
|
+
(`persistent`), and surviving a stop/resume is the same round trip `lifecycle`
|
|
216
|
+
is still awaiting live proof of.
|
|
217
|
+
|
|
218
|
+
### Modal runtime contract
|
|
219
|
+
|
|
220
|
+
`ModalRuntime` takes an explicit Modal **token pair** (`tokenId` and
|
|
221
|
+
`tokenSecret` — Modal does not use a single bearer key), an App name, an image
|
|
222
|
+
tag, a home directory, and an ownership-name prefix. It never reads ambient
|
|
223
|
+
credentials or a local Modal profile.
|
|
224
|
+
|
|
225
|
+
A Modal Sandbox is a child of an App, built from an Image, and it has a
|
|
226
|
+
**maximum lifetime after which the provider terminates it** — the SDK's own
|
|
227
|
+
default is five minutes. `maxLifetimeMs` is therefore required configuration and
|
|
228
|
+
is always sent explicitly. `createTimeoutSeconds` on `launch` is a deadline on
|
|
229
|
+
the create call and is deliberately not forwarded to that lifetime.
|
|
230
|
+
|
|
231
|
+
Modal exposes no stop/start for a Sandbox; `terminate` is the only lifecycle
|
|
232
|
+
transition and it is terminal. `start` and `stop` are absent rather than
|
|
233
|
+
no-ops, and `lifecycle` is declared false permanently. Async exec is likewise
|
|
234
|
+
not implemented: Modal cannot re-resolve a running exec by id, so the
|
|
235
|
+
`startScript`/`getScriptStatus`/`getScriptLogs` trio is omitted entirely instead
|
|
236
|
+
of being half-supported.
|
|
237
|
+
|
|
238
|
+
Ownership rides on Modal's native server-side tags rather than on a naming
|
|
239
|
+
convention. Every sandbox carries an ownership tag, every lookup filters on it
|
|
240
|
+
server-side, and reattachment and deletion both re-check it. Warm leasing is
|
|
241
|
+
implemented against that real tag filter but remains undeclared until a live
|
|
242
|
+
probe confirms it. Snapshots, volumes, PTY, and tunnels exist in the provider
|
|
243
|
+
and are documented, but are not advertised because this package's port exposes
|
|
244
|
+
no operation for them.
|
|
245
|
+
|
|
246
|
+
That last distinction is now stated structurally rather than in prose. The
|
|
247
|
+
adapter declares `declaredCapabilityModes`, so PTY and snapshots resolve to
|
|
248
|
+
`"not-exposed"` — a fact about this package's port, which `isPendingEvidence()`
|
|
249
|
+
reports as unmovable — rather than to a bare `false` a later canary might read
|
|
250
|
+
as merely unverified. `lifetime` resolves to `"deadline"`, which is the
|
|
251
|
+
structural reason a Modal sandbox can never be never-idle. Output is
|
|
252
|
+
`"buffered"`: Modal streams, the adapter drains. Warm leasing deliberately gets
|
|
253
|
+
no mode, because modes describe a capability's shape and not its verification
|
|
254
|
+
state.
|
|
255
|
+
|
|
256
|
+
The official SDK is isolated under `src/modal/internal/`, and because that SDK
|
|
257
|
+
speaks gRPC rather than HTTP there is no injectable transport seam; the boundary
|
|
258
|
+
is a structural mirror that is checked at build time instead. All create,
|
|
259
|
+
lookup, exec, upload, and deletion operations have explicit deadlines. See
|
|
260
|
+
[the Modal adapter notes](./docs/modal.md) for dependency provenance, provider
|
|
261
|
+
constraints, cost model, and capability evidence.
|
|
262
|
+
|
|
43
263
|
## Development
|
|
44
264
|
|
|
45
265
|
```bash
|
|
@@ -49,7 +269,8 @@ npm run typecheck
|
|
|
49
269
|
npm test # node:test
|
|
50
270
|
```
|
|
51
271
|
|
|
52
|
-
Requires Node.js 20 or newer.
|
|
272
|
+
Requires Node.js 20 or newer. The microsandbox adapter's own tests need Node 22+
|
|
273
|
+
to load the real SDK; without it, its SDK-contract checks skip rather than fail.
|
|
53
274
|
|
|
54
275
|
## Releasing
|
|
55
276
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal HTTP client for the Agent37 Cloud API.
|
|
3
|
+
*
|
|
4
|
+
* There is no official Agent37 JavaScript SDK to depend on: the only published
|
|
5
|
+
* npm package (`agent37`) is a command-line interface that exposes a `bin`, no
|
|
6
|
+
* library entry point, and no runtime dependencies. So this adapter speaks the
|
|
7
|
+
* documented HTTP contract directly over `fetch`, and takes an injectable
|
|
8
|
+
* `fetch` seam instead of a peer dependency. That is deliberate — an optional
|
|
9
|
+
* peer dependency pointing at a CLI would be a dependency on something that
|
|
10
|
+
* cannot be imported.
|
|
11
|
+
*
|
|
12
|
+
* Agent37 exposes two planes that share one credential but not one header:
|
|
13
|
+
*
|
|
14
|
+
* - the **hosting plane** (`{baseUrl}/v1/instances/...`), authenticated with
|
|
15
|
+
* `Authorization: Bearer <key>`, which creates, lists, execs, and tears down
|
|
16
|
+
* instances; and
|
|
17
|
+
* - the **instance plane** (the `url` an instance object reports),
|
|
18
|
+
* authenticated with `X-Agent37-Key: <key>`, which serves that one
|
|
19
|
+
* instance's filesystem.
|
|
20
|
+
*
|
|
21
|
+
* The instance plane's origin is never constructed here. It is read off the
|
|
22
|
+
* instance object, so no provider hostname is baked into this package.
|
|
23
|
+
*/
|
|
24
|
+
/** Response shape this client needs. Structurally satisfied by global `Response`. */
|
|
25
|
+
export type Agent37FetchResponse = {
|
|
26
|
+
readonly ok: boolean;
|
|
27
|
+
readonly status: number;
|
|
28
|
+
text(): Promise<string>;
|
|
29
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
30
|
+
};
|
|
31
|
+
/** Request shape this client sends. Structurally satisfied by global `RequestInit`. */
|
|
32
|
+
export type Agent37FetchInit = {
|
|
33
|
+
method?: string;
|
|
34
|
+
headers?: Record<string, string>;
|
|
35
|
+
body?: string | Uint8Array;
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
};
|
|
38
|
+
/** Injection seam for the transport. Global `fetch` is assignable to this. */
|
|
39
|
+
export type Agent37Fetch = (url: string, init?: Agent37FetchInit) => Promise<Agent37FetchResponse>;
|
|
40
|
+
/**
|
|
41
|
+
* A non-2xx response from either Agent37 plane.
|
|
42
|
+
*
|
|
43
|
+
* `code` is the machine-readable identifier the API documents callers should
|
|
44
|
+
* branch on — never the message text, and never the HTTP status alone, because
|
|
45
|
+
* one status carries several codes (409 is both `try_again` and
|
|
46
|
+
* `instance_limit_reached`).
|
|
47
|
+
*/
|
|
48
|
+
export declare class Agent37ApiError extends Error {
|
|
49
|
+
readonly status: number;
|
|
50
|
+
readonly code: string;
|
|
51
|
+
/** Method and path only. Never the query string, body, or any header. */
|
|
52
|
+
readonly request: string;
|
|
53
|
+
readonly retryable: boolean;
|
|
54
|
+
constructor(options: {
|
|
55
|
+
status: number;
|
|
56
|
+
code: string;
|
|
57
|
+
message: string;
|
|
58
|
+
request: string;
|
|
59
|
+
retryable: boolean;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
export type Agent37ClientOptions = {
|
|
63
|
+
/**
|
|
64
|
+
* An `sk_live_` workspace key. Required, and never read from the environment
|
|
65
|
+
* by this package: credential sourcing belongs to the caller.
|
|
66
|
+
*/
|
|
67
|
+
apiKey: string;
|
|
68
|
+
/**
|
|
69
|
+
* Origin of the Agent37 hosting API, without a trailing slash — for example
|
|
70
|
+
* the provider's public control plane, or a proxy in front of it. Required:
|
|
71
|
+
* this package ships no endpoint defaults.
|
|
72
|
+
*/
|
|
73
|
+
baseUrl: string;
|
|
74
|
+
/** Transport seam. Defaults to the global `fetch`. */
|
|
75
|
+
fetch?: Agent37Fetch;
|
|
76
|
+
/** Total attempts for a retryable failure, including the first. Default 3. */
|
|
77
|
+
maxAttempts?: number;
|
|
78
|
+
/** First backoff step; doubles per attempt. Default 250ms. */
|
|
79
|
+
retryBaseDelayMs?: number;
|
|
80
|
+
/** Sleep seam, so retry backoff is deterministic under test. */
|
|
81
|
+
sleep?: (ms: number) => Promise<void>;
|
|
82
|
+
};
|
|
83
|
+
export declare class Agent37Client {
|
|
84
|
+
private readonly apiKey;
|
|
85
|
+
private readonly baseUrl;
|
|
86
|
+
private readonly fetchImpl;
|
|
87
|
+
private readonly maxAttempts;
|
|
88
|
+
private readonly retryBaseDelayMs;
|
|
89
|
+
private readonly sleep;
|
|
90
|
+
constructor(options: Agent37ClientOptions);
|
|
91
|
+
/** JSON call against the hosting plane (`Authorization: Bearer`). */
|
|
92
|
+
hosting<T>(method: string, path: string, options?: {
|
|
93
|
+
body?: unknown;
|
|
94
|
+
query?: Record<string, string | undefined>;
|
|
95
|
+
timeoutMs?: number;
|
|
96
|
+
}): Promise<T>;
|
|
97
|
+
/** JSON call against one instance's own plane (`X-Agent37-Key`). */
|
|
98
|
+
instance<T>(instanceUrl: string, method: string, path: string, options?: {
|
|
99
|
+
body?: string | Uint8Array;
|
|
100
|
+
contentType?: string;
|
|
101
|
+
query?: Record<string, string | undefined>;
|
|
102
|
+
timeoutMs?: number;
|
|
103
|
+
headers?: Record<string, string>;
|
|
104
|
+
}): Promise<T>;
|
|
105
|
+
/** Byte-stream call against one instance's own plane, for file downloads. */
|
|
106
|
+
instanceBytes(instanceUrl: string, method: string, path: string, options?: {
|
|
107
|
+
query?: Record<string, string | undefined>;
|
|
108
|
+
timeoutMs?: number;
|
|
109
|
+
}): Promise<Uint8Array>;
|
|
110
|
+
private send;
|
|
111
|
+
private buildHeaders;
|
|
112
|
+
}
|
|
113
|
+
/** Exported for tests and for callers that want to mirror the retry policy. */
|
|
114
|
+
export declare function isRetryableAgent37Code(code: string): boolean;
|
|
115
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/agent37/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal HTTP client for the Agent37 Cloud API.
|
|
3
|
+
*
|
|
4
|
+
* There is no official Agent37 JavaScript SDK to depend on: the only published
|
|
5
|
+
* npm package (`agent37`) is a command-line interface that exposes a `bin`, no
|
|
6
|
+
* library entry point, and no runtime dependencies. So this adapter speaks the
|
|
7
|
+
* documented HTTP contract directly over `fetch`, and takes an injectable
|
|
8
|
+
* `fetch` seam instead of a peer dependency. That is deliberate — an optional
|
|
9
|
+
* peer dependency pointing at a CLI would be a dependency on something that
|
|
10
|
+
* cannot be imported.
|
|
11
|
+
*
|
|
12
|
+
* Agent37 exposes two planes that share one credential but not one header:
|
|
13
|
+
*
|
|
14
|
+
* - the **hosting plane** (`{baseUrl}/v1/instances/...`), authenticated with
|
|
15
|
+
* `Authorization: Bearer <key>`, which creates, lists, execs, and tears down
|
|
16
|
+
* instances; and
|
|
17
|
+
* - the **instance plane** (the `url` an instance object reports),
|
|
18
|
+
* authenticated with `X-Agent37-Key: <key>`, which serves that one
|
|
19
|
+
* instance's filesystem.
|
|
20
|
+
*
|
|
21
|
+
* The instance plane's origin is never constructed here. It is read off the
|
|
22
|
+
* instance object, so no provider hostname is baked into this package.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* A non-2xx response from either Agent37 plane.
|
|
26
|
+
*
|
|
27
|
+
* `code` is the machine-readable identifier the API documents callers should
|
|
28
|
+
* branch on — never the message text, and never the HTTP status alone, because
|
|
29
|
+
* one status carries several codes (409 is both `try_again` and
|
|
30
|
+
* `instance_limit_reached`).
|
|
31
|
+
*/
|
|
32
|
+
export class Agent37ApiError extends Error {
|
|
33
|
+
status;
|
|
34
|
+
code;
|
|
35
|
+
/** Method and path only. Never the query string, body, or any header. */
|
|
36
|
+
request;
|
|
37
|
+
retryable;
|
|
38
|
+
constructor(options) {
|
|
39
|
+
super(`Agent37 ${options.request} failed: ${options.status} ${options.code}: ${options.message}`);
|
|
40
|
+
this.name = "Agent37ApiError";
|
|
41
|
+
this.status = options.status;
|
|
42
|
+
this.code = options.code;
|
|
43
|
+
this.request = options.request;
|
|
44
|
+
this.retryable = options.retryable;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Codes the API documents as safe to retry with backoff.
|
|
49
|
+
*
|
|
50
|
+
* Everything absent from this set is retried never, not "retried cautiously".
|
|
51
|
+
* Two exclusions are load-bearing rather than conservative:
|
|
52
|
+
*
|
|
53
|
+
* - `provisioning_failed` (502) is also what a synchronous `exec` returns when
|
|
54
|
+
* it passes the provider's 280-second command cap. Retrying it would run the
|
|
55
|
+
* caller's command a second time, on an instance where the first copy is
|
|
56
|
+
* still running.
|
|
57
|
+
* - `container_unavailable` (502) means the instance is stopped. Retrying
|
|
58
|
+
* cannot change that; `start` can.
|
|
59
|
+
*/
|
|
60
|
+
const RETRYABLE_CODES = new Set([
|
|
61
|
+
// hosting plane
|
|
62
|
+
"no_capacity",
|
|
63
|
+
"try_again",
|
|
64
|
+
// transport plane
|
|
65
|
+
"container_unreachable",
|
|
66
|
+
"upstream_unreachable",
|
|
67
|
+
"host_mesh_not_ready",
|
|
68
|
+
"instance_saturated",
|
|
69
|
+
"wake_timeout",
|
|
70
|
+
"upstream_timeout",
|
|
71
|
+
// agent plane
|
|
72
|
+
"rate_limited",
|
|
73
|
+
]);
|
|
74
|
+
const DEFAULT_MAX_ATTEMPTS = 3;
|
|
75
|
+
const DEFAULT_RETRY_BASE_DELAY_MS = 250;
|
|
76
|
+
export class Agent37Client {
|
|
77
|
+
apiKey;
|
|
78
|
+
baseUrl;
|
|
79
|
+
fetchImpl;
|
|
80
|
+
maxAttempts;
|
|
81
|
+
retryBaseDelayMs;
|
|
82
|
+
sleep;
|
|
83
|
+
constructor(options) {
|
|
84
|
+
const apiKey = options.apiKey?.trim();
|
|
85
|
+
if (!apiKey) {
|
|
86
|
+
throw new Error("Agent37Client requires a non-empty apiKey");
|
|
87
|
+
}
|
|
88
|
+
const baseUrl = options.baseUrl?.trim();
|
|
89
|
+
if (!baseUrl) {
|
|
90
|
+
throw new Error("Agent37Client requires an explicit baseUrl: this package ships no endpoint defaults");
|
|
91
|
+
}
|
|
92
|
+
this.apiKey = apiKey;
|
|
93
|
+
this.baseUrl = stripTrailingSlash(baseUrl);
|
|
94
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
95
|
+
if (typeof this.fetchImpl !== "function") {
|
|
96
|
+
throw new Error("Agent37Client requires a fetch implementation (none found on globalThis)");
|
|
97
|
+
}
|
|
98
|
+
// Non-finite maxAttempts (NaN, ±Infinity) would poison the retry loop:
|
|
99
|
+
// `attempt >= NaN` is always false, so the throw guard never fires and the
|
|
100
|
+
// loop retries a retryable failure forever. Reject explicitly rather than
|
|
101
|
+
// silently coercing.
|
|
102
|
+
const requestedAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
103
|
+
if (!Number.isFinite(requestedAttempts)) {
|
|
104
|
+
throw new Error(`Agent37Client maxAttempts must be a finite number; got ${String(options.maxAttempts)}`);
|
|
105
|
+
}
|
|
106
|
+
this.maxAttempts = Math.max(1, Math.floor(requestedAttempts));
|
|
107
|
+
const requestedRetryBase = options.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;
|
|
108
|
+
if (!Number.isFinite(requestedRetryBase) || requestedRetryBase < 0) {
|
|
109
|
+
throw new Error(`Agent37Client retryBaseDelayMs must be a finite, non-negative number; got ${String(options.retryBaseDelayMs)}`);
|
|
110
|
+
}
|
|
111
|
+
this.retryBaseDelayMs = requestedRetryBase;
|
|
112
|
+
this.sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
113
|
+
}
|
|
114
|
+
/** JSON call against the hosting plane (`Authorization: Bearer`). */
|
|
115
|
+
async hosting(method, path, options = {}) {
|
|
116
|
+
const response = await this.send({
|
|
117
|
+
method,
|
|
118
|
+
origin: this.baseUrl,
|
|
119
|
+
path,
|
|
120
|
+
...(options.query ? { query: options.query } : {}),
|
|
121
|
+
headerStyle: "bearer",
|
|
122
|
+
...(options.body === undefined
|
|
123
|
+
? {}
|
|
124
|
+
: { body: JSON.stringify(options.body), contentType: "application/json" }),
|
|
125
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
126
|
+
});
|
|
127
|
+
return (await readJson(response));
|
|
128
|
+
}
|
|
129
|
+
/** JSON call against one instance's own plane (`X-Agent37-Key`). */
|
|
130
|
+
async instance(instanceUrl, method, path, options = {}) {
|
|
131
|
+
const response = await this.send({
|
|
132
|
+
method,
|
|
133
|
+
origin: stripTrailingSlash(instanceUrl),
|
|
134
|
+
path,
|
|
135
|
+
...(options.query ? { query: options.query } : {}),
|
|
136
|
+
headerStyle: "instance-key",
|
|
137
|
+
...(options.body === undefined ? {} : { body: options.body }),
|
|
138
|
+
...(options.contentType === undefined ? {} : { contentType: options.contentType }),
|
|
139
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
140
|
+
...(options.headers ? { headers: options.headers } : {}),
|
|
141
|
+
});
|
|
142
|
+
return (await readJson(response));
|
|
143
|
+
}
|
|
144
|
+
/** Byte-stream call against one instance's own plane, for file downloads. */
|
|
145
|
+
async instanceBytes(instanceUrl, method, path, options = {}) {
|
|
146
|
+
const response = await this.send({
|
|
147
|
+
method,
|
|
148
|
+
origin: stripTrailingSlash(instanceUrl),
|
|
149
|
+
path,
|
|
150
|
+
...(options.query ? { query: options.query } : {}),
|
|
151
|
+
headerStyle: "instance-key",
|
|
152
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
153
|
+
});
|
|
154
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
155
|
+
}
|
|
156
|
+
// --- internals ----------------------------------------------------------
|
|
157
|
+
async send(options) {
|
|
158
|
+
// `request` is what surfaces in errors: method and path, never the query
|
|
159
|
+
// string (it carries filesystem paths) and never a header (it carries the
|
|
160
|
+
// credential).
|
|
161
|
+
const request = `${options.method} ${options.path}`;
|
|
162
|
+
const url = buildUrl(options.origin, options.path, options.query);
|
|
163
|
+
let attempt = 0;
|
|
164
|
+
for (;;) {
|
|
165
|
+
attempt += 1;
|
|
166
|
+
// A thrown transport failure is NOT retried. The outcome of a POST that
|
|
167
|
+
// never returned is unknown, and re-sending it is how one submitted
|
|
168
|
+
// command becomes two. Only a *classified* response is retried, because
|
|
169
|
+
// only then is it known that the server rejected rather than accepted.
|
|
170
|
+
const response = await this.fetchImpl(url, {
|
|
171
|
+
method: options.method,
|
|
172
|
+
headers: this.buildHeaders(options),
|
|
173
|
+
...(options.body === undefined ? {} : { body: options.body }),
|
|
174
|
+
...(options.timeoutMs === undefined || options.timeoutMs <= 0
|
|
175
|
+
? {}
|
|
176
|
+
: { signal: AbortSignal.timeout(options.timeoutMs) }),
|
|
177
|
+
});
|
|
178
|
+
if (response.ok) {
|
|
179
|
+
return response;
|
|
180
|
+
}
|
|
181
|
+
const error = await toApiError(response, request);
|
|
182
|
+
if (!error.retryable || attempt >= this.maxAttempts) {
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
await this.sleep(this.retryBaseDelayMs * 2 ** (attempt - 1));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
buildHeaders(options) {
|
|
189
|
+
const headers = { ...(options.headers ?? {}) };
|
|
190
|
+
if (options.headerStyle === "bearer") {
|
|
191
|
+
headers.Authorization = `Bearer ${this.apiKey}`;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
headers["X-Agent37-Key"] = this.apiKey;
|
|
195
|
+
}
|
|
196
|
+
if (options.contentType) {
|
|
197
|
+
headers["Content-Type"] = options.contentType;
|
|
198
|
+
}
|
|
199
|
+
return headers;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// --- helpers --------------------------------------------------------------
|
|
203
|
+
function stripTrailingSlash(value) {
|
|
204
|
+
return value.replace(/\/+$/, "");
|
|
205
|
+
}
|
|
206
|
+
function buildUrl(origin, path, query) {
|
|
207
|
+
const params = new URLSearchParams();
|
|
208
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
209
|
+
if (value !== undefined) {
|
|
210
|
+
params.append(key, value);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const search = params.toString();
|
|
214
|
+
return `${origin}${path}${search ? `?${search}` : ""}`;
|
|
215
|
+
}
|
|
216
|
+
async function readJson(response) {
|
|
217
|
+
const text = await response.text();
|
|
218
|
+
if (!text) {
|
|
219
|
+
return {};
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
return JSON.parse(text);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return {};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Normalize the three documented error envelopes into one error.
|
|
230
|
+
*
|
|
231
|
+
* - hosting/agent plane: `{ "error": { "code", "message", ... } }`
|
|
232
|
+
* - transport plane: `{ "error": "flat_string_code" }`
|
|
233
|
+
* - neither (HTML from a proxy, empty body): synthesize `http_<status>`, which
|
|
234
|
+
* is never in the retryable set, so an unparseable failure fails fast.
|
|
235
|
+
*/
|
|
236
|
+
async function toApiError(response, request) {
|
|
237
|
+
let code = `http_${response.status}`;
|
|
238
|
+
let message = "";
|
|
239
|
+
try {
|
|
240
|
+
const parsed = JSON.parse(await response.text());
|
|
241
|
+
const raw = parsed.error;
|
|
242
|
+
if (typeof raw === "string" && raw) {
|
|
243
|
+
code = raw;
|
|
244
|
+
}
|
|
245
|
+
else if (raw && typeof raw === "object") {
|
|
246
|
+
const shaped = raw;
|
|
247
|
+
if (typeof shaped.code === "string" && shaped.code) {
|
|
248
|
+
code = shaped.code;
|
|
249
|
+
}
|
|
250
|
+
if (typeof shaped.message === "string") {
|
|
251
|
+
message = shaped.message;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
// Leave the synthesized code in place.
|
|
257
|
+
}
|
|
258
|
+
return new Agent37ApiError({
|
|
259
|
+
status: response.status,
|
|
260
|
+
code,
|
|
261
|
+
message,
|
|
262
|
+
request,
|
|
263
|
+
retryable: RETRYABLE_CODES.has(code),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/** Exported for tests and for callers that want to mirror the retry policy. */
|
|
267
|
+
export function isRetryableAgent37Code(code) {
|
|
268
|
+
return RETRYABLE_CODES.has(code);
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/agent37/client.ts"],"names":[],"mappings":"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"}
|