@tangle-network/sandbox 0.10.5 → 0.11.1-develop.20260717054349.4472dd9

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.
@@ -0,0 +1,678 @@
1
+ import { c as SandboxError, d as TimeoutError, f as ValidationError, i as NetworkError, l as ServerError, p as parseErrorResponse, s as QuotaError } from "./errors-Cbs78OlF.js";
2
+ //#region src/backend-config.ts
3
+ const LEGACY_QUESTION_ALIAS_KEY = ["question", "Channel"].join("");
4
+ function normalizeRuntimeModelOverride(model) {
5
+ const parts = model.split("/");
6
+ if (parts.length >= 2) return {
7
+ provider: parts[0],
8
+ model: parts.slice(1).join("/")
9
+ };
10
+ return { model };
11
+ }
12
+ /**
13
+ * Normalize runtime backend config for the wire format.
14
+ *
15
+ * Canonical profile objects are already accepted by the sidecar and are
16
+ * forwarded without translation.
17
+ */
18
+ function normalizeRuntimeBackendConfig(backend, options = {}) {
19
+ if (!backend && !options.model) return void 0;
20
+ if (backend && LEGACY_QUESTION_ALIAS_KEY in backend) throw new Error(`[sandbox-sdk] backend.${LEGACY_QUESTION_ALIAS_KEY} was removed. Use backend.interactions.question instead.`);
21
+ const callerInlineProfile = backend?.inlineProfile;
22
+ if (callerInlineProfile !== void 0) console.warn("[sandbox-sdk] backend.inlineProfile is deprecated. Use backend.profile (AgentProfile) instead.");
23
+ return {
24
+ ...backend?.type ? { type: backend.type } : {},
25
+ ...backend?.profile !== void 0 ? { profile: backend.profile } : {},
26
+ ...callerInlineProfile !== void 0 ? { inlineProfile: callerInlineProfile } : {},
27
+ ...backend?.model ? { model: backend.model } : options.model ? { model: normalizeRuntimeModelOverride(options.model) } : {},
28
+ ...backend?.server ? { server: backend.server } : {},
29
+ ...backend?.interactions !== void 0 ? { interactions: backend.interactions } : {},
30
+ ...backend?.metadata !== void 0 ? { metadata: backend.metadata } : {}
31
+ };
32
+ }
33
+ /**
34
+ * @deprecated Canonical profile objects can be sent directly as
35
+ * `backend.profile`; this compatibility export now preserves that object.
36
+ */
37
+ function serializeForSidecar(profile) {
38
+ return profile;
39
+ }
40
+ //#endregion
41
+ //#region src/lib/wire-encoding.ts
42
+ /**
43
+ * Encode a prompt text part for the wire. Server decodes at the route
44
+ * boundary; the LLM only ever sees the original UTF-8. Done
45
+ * unconditionally because readable bodies false-positive on ingress WAF
46
+ * rules that pattern-match shell-injection-shaped substrings (which
47
+ * legitimate prompts routinely contain — e.g. Step-0 dev-server
48
+ * bootstrap).
49
+ */
50
+ function encodeTextForWire(text) {
51
+ if (typeof Buffer !== "undefined") return Buffer.from(text, "utf8").toString("base64");
52
+ if (typeof btoa === "function") return btoa(encodeURIComponent(text).replace(/%([0-9A-F]{2})/g, (_, h) => String.fromCharCode(Number.parseInt(h, 16))));
53
+ throw new Error("encodeTextForWire: no base64 encoder available (Buffer and btoa both undefined)");
54
+ }
55
+ /**
56
+ * Convert a caller-supplied prompt (string or parts array) into the
57
+ * wire-format parts array. Every text part is base64-encoded; non-text
58
+ * parts pass through. A bare string is wrapped in a single text part.
59
+ */
60
+ function encodePromptForWire(message) {
61
+ return (typeof message === "string" ? [{
62
+ type: "text",
63
+ text: message
64
+ }] : message).map((part) => part.type === "text" ? {
65
+ type: "text",
66
+ text: encodeTextForWire(part.text)
67
+ } : part);
68
+ }
69
+ //#endregion
70
+ //#region src/lib/abort-signal.ts
71
+ function combineAbortSignals(signals) {
72
+ return AbortSignal.any(signals);
73
+ }
74
+ //#endregion
75
+ //#region ../../../packages/runtime-contracts/dist/file-limits.js
76
+ const MEBIBYTE$1 = 1024 * 1024;
77
+ 10 * MEBIBYTE$1;
78
+ const FILE_DECODED_WRITE_MAX_BYTES = 5 * MEBIBYTE$1;
79
+ 5 * MEBIBYTE$1;
80
+ 10 * MEBIBYTE$1;
81
+ 5 * MEBIBYTE$1;
82
+ //#endregion
83
+ //#region ../../../packages/runtime-contracts/dist/request-limits.js
84
+ /**
85
+ * Raw WIRE/TRANSPORT caps: request-byte limits checked while the body
86
+ * streams in (bodyLimit middleware, Content-Length, encoded proxy bodies),
87
+ * before anything is parsed or decoded. Rejections against these carry
88
+ * code `PAYLOAD_TOO_LARGE` (`payload-too-large.ts`) — the remedy is to
89
+ * split the delivery. Decoded/parsed-content caps (code `FILE_TOO_LARGE`)
90
+ * live in `file-io.ts`.
91
+ */
92
+ const MEBIBYTE = 1024 * 1024;
93
+ /**
94
+ * Gateway (CF Worker) proxy cap on any request body it forwards — both
95
+ * pass-through requests and route-constructed (`options.body`) proxy
96
+ * payloads. Workers buffer forwarded bodies (streaming upstream stalls),
97
+ * so this bounds Worker memory per request.
98
+ */
99
+ const SANDBOX_PROXY_REQUEST_MAX_BYTES = MEBIBYTE;
100
+ 32 * MEBIBYTE;
101
+ /**
102
+ * Max assembled size of one chunked-upload session — the ceiling of the
103
+ * sanctioned >1 MiB delivery path. Parts stage in sidecar memory until
104
+ * commit, so this bounds per-session memory, not the destination
105
+ * filesystem.
106
+ */
107
+ const FILE_UPLOAD_SESSION_MAX_BYTES = 64 * MEBIBYTE;
108
+ 128 * MEBIBYTE;
109
+ //#endregion
110
+ //#region ../../../packages/runtime-contracts/dist/size-limit-errors.js
111
+ /** Canonical error codes for wire-size and decoded-content rejections. */
112
+ const PAYLOAD_TOO_LARGE_CODE = "PAYLOAD_TOO_LARGE";
113
+ const FILE_TOO_LARGE_CODE = "FILE_TOO_LARGE";
114
+ function isRecord(value) {
115
+ return value !== null && typeof value === "object" && !Array.isArray(value);
116
+ }
117
+ function isPositiveInteger(value) {
118
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
119
+ }
120
+ function isSizeLimitErrorBodyFor(value, code) {
121
+ if (!isRecord(value) || value.success !== false || !isRecord(value.error)) return false;
122
+ const error = value.error;
123
+ return error.code === code && typeof error.message === "string" && typeof error.surface === "string" && isPositiveInteger(error.limitBytes) && (error.actualBytes === void 0 || isPositiveInteger(error.actualBytes));
124
+ }
125
+ function isPayloadTooLargeErrorBody(value) {
126
+ return isSizeLimitErrorBodyFor(value, PAYLOAD_TOO_LARGE_CODE);
127
+ }
128
+ function isFileTooLargeErrorBody(value) {
129
+ return isSizeLimitErrorBodyFor(value, FILE_TOO_LARGE_CODE);
130
+ }
131
+ //#endregion
132
+ //#region src/lib/chunked-upload.ts
133
+ /**
134
+ * Chunked upload — the sanctioned SDK path for delivering payloads larger
135
+ * than the sandbox API's single-request body caps. Splits the payload into
136
+ * server-sized parts against a `/fs/upload-session` session, PUTs each part
137
+ * with transient retry, then commits with a whole-payload SHA-256 so the
138
+ * runtime can verify nothing was dropped or reordered in transit.
139
+ *
140
+ * Browser/Worker-safe: no `node:` imports, no `Buffer`. Input normalizes to
141
+ * a `Uint8Array` and hashes via Web Crypto (`crypto.subtle`), available in
142
+ * browsers, Workers, and Node 18+ — the same environments this module must
143
+ * run in unmodified.
144
+ */
145
+ const DEFAULT_MAX_RETRIES = 4;
146
+ const RETRY_BASE_MS = 250;
147
+ const RETRY_MAX_MS = 2e3;
148
+ const delay = (ms, signal) => new Promise((resolve, reject) => {
149
+ if (signal?.aborted) {
150
+ reject(signal.reason ?? /* @__PURE__ */ new Error("aborted"));
151
+ return;
152
+ }
153
+ const timer = setTimeout(() => {
154
+ signal?.removeEventListener("abort", onAbort);
155
+ resolve();
156
+ }, ms);
157
+ function onAbort() {
158
+ clearTimeout(timer);
159
+ reject(signal?.reason ?? /* @__PURE__ */ new Error("aborted"));
160
+ }
161
+ signal?.addEventListener("abort", onAbort, { once: true });
162
+ });
163
+ /** Transient part-upload failures worth retrying: rate-limit/budget (429),
164
+ * 5xx, transport (network), and request timeouts — mirrors
165
+ * `isTransientWriteError` in `sandbox.ts`. A non-transient error (bad path,
166
+ * auth, checksum, containment) fails loud immediately. */
167
+ function isTransientUploadError(err) {
168
+ return err instanceof QuotaError || err instanceof ServerError || err instanceof NetworkError || err instanceof TimeoutError;
169
+ }
170
+ function isChecksumMismatch(err) {
171
+ return err instanceof SandboxError && err.code === "CHECKSUM_MISMATCH";
172
+ }
173
+ /** Cheap size probe used for the pre-flight cap check, read directly off
174
+ * the input shape (`Blob.size` / `ArrayBuffer.byteLength` / view
175
+ * `byteLength`) so an oversized `Blob` never has its bytes materialized
176
+ * just to be rejected. Strings have no cheap length in UTF-8, so they're
177
+ * encoded once here (and once more in {@link normalizeToBytes} — an
178
+ * acceptable cost given strings are never the multi-MB case this path
179
+ * exists for). */
180
+ function sizeOfInput(data) {
181
+ if (typeof data === "string") return new TextEncoder().encode(data).byteLength;
182
+ if (data instanceof ArrayBuffer) return data.byteLength;
183
+ if (ArrayBuffer.isView(data)) return data.byteLength;
184
+ return data.size;
185
+ }
186
+ /**
187
+ * Normalizes to a `Uint8Array` backed by a plain (never shared)
188
+ * `ArrayBuffer`. A caller-supplied view may be backed by a
189
+ * `SharedArrayBuffer`, which `crypto.subtle.digest` and `fetch`'s
190
+ * `BodyInit` both reject at the type level (and some engines reject at
191
+ * runtime) — copying here keeps every downstream call on one concrete,
192
+ * always-accepted buffer type.
193
+ */
194
+ async function normalizeToBytes(data) {
195
+ if (typeof data === "string") return new Uint8Array(new TextEncoder().encode(data));
196
+ if (data instanceof ArrayBuffer) return new Uint8Array(data.slice(0));
197
+ if (ArrayBuffer.isView(data)) {
198
+ const copy = new Uint8Array(data.byteLength);
199
+ copy.set(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
200
+ return copy;
201
+ }
202
+ return new Uint8Array(await data.arrayBuffer());
203
+ }
204
+ function webCryptoSubtle() {
205
+ const subtle = globalThis.crypto?.subtle;
206
+ if (!subtle) throw new Error("chunked upload requires Web Crypto (crypto.subtle) for SHA-256 hashing — available in browsers, Workers, and Node 18+");
207
+ return subtle;
208
+ }
209
+ async function sha256Hex(bytes) {
210
+ const digest = await webCryptoSubtle().digest("SHA-256", bytes);
211
+ return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
212
+ }
213
+ /**
214
+ * Canonical size-cap bodies (413 `PAYLOAD_TOO_LARGE` / `FILE_TOO_LARGE`)
215
+ * carry `surface`/`limitBytes`/`actualBytes` that the generic error parser
216
+ * drops; fold them into the message before delegating to
217
+ * `parseErrorResponse` for the rest (status→class mapping, retryAfterMs,
218
+ * origin).
219
+ */
220
+ async function parseUploadError(response) {
221
+ const bodyText = await response.text();
222
+ let parsedBody;
223
+ try {
224
+ parsedBody = JSON.parse(bodyText);
225
+ } catch {
226
+ parsedBody = void 0;
227
+ }
228
+ if (isPayloadTooLargeErrorBody(parsedBody) || isFileTooLargeErrorBody(parsedBody)) {
229
+ const { code, message, surface, limitBytes, actualBytes } = parsedBody.error;
230
+ const detail = typeof actualBytes === "number" ? `${message} (${surface}: ${actualBytes} of ${limitBytes} bytes)` : `${message} (${surface}: limit ${limitBytes} bytes)`;
231
+ return parseErrorResponse(response.status, JSON.stringify({
232
+ code,
233
+ message: detail
234
+ }), void 0, response.headers);
235
+ }
236
+ return parseErrorResponse(response.status, bodyText, void 0, response.headers);
237
+ }
238
+ async function initUploadSession(transport, remotePath, totalBytes, sha256, mode, signal) {
239
+ signal?.throwIfAborted();
240
+ const response = await transport.fetch("/fs/upload-session", {
241
+ method: "POST",
242
+ headers: { "Content-Type": "application/json" },
243
+ body: JSON.stringify({
244
+ path: remotePath,
245
+ mode,
246
+ totalBytes,
247
+ sha256
248
+ }),
249
+ signal
250
+ });
251
+ if (!response.ok) throw await parseUploadError(response);
252
+ return (await response.json()).data;
253
+ }
254
+ async function putPart(transport, uploadId, index, chunk, options) {
255
+ const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
256
+ for (let attempt = 0;; attempt++) {
257
+ options.signal?.throwIfAborted();
258
+ try {
259
+ const response = await transport.fetch(`/fs/upload-session/${encodeURIComponent(uploadId)}/parts/${index}`, {
260
+ method: "PUT",
261
+ headers: { "Content-Type": "application/octet-stream" },
262
+ body: chunk,
263
+ signal: options.signal
264
+ });
265
+ if (!response.ok) throw await parseUploadError(response);
266
+ return;
267
+ } catch (err) {
268
+ if (isTransientUploadError(err) && attempt < maxRetries) {
269
+ const retryAfterMs = err instanceof SandboxError ? err.retryAfterMs : void 0;
270
+ const backoff = Math.min(RETRY_BASE_MS * 2 ** attempt, RETRY_MAX_MS);
271
+ await delay(retryAfterMs ?? backoff, options.signal);
272
+ continue;
273
+ }
274
+ throw err;
275
+ }
276
+ }
277
+ }
278
+ function partCount(totalBytes, partSize) {
279
+ return Math.ceil(totalBytes / partSize);
280
+ }
281
+ async function uploadAllParts(transport, uploadId, bytes, partSize, totalParts, options) {
282
+ const totalBytes = bytes.byteLength;
283
+ const paceMs = options.paceMs ?? 0;
284
+ let uploadedBytes = 0;
285
+ let started = false;
286
+ for (let index = 0; index < totalParts; index++) {
287
+ if (started && paceMs > 0) await delay(paceMs, options.signal);
288
+ started = true;
289
+ const start = index * partSize;
290
+ const end = Math.min(start + partSize, totalBytes);
291
+ await putPart(transport, uploadId, index, bytes.subarray(start, end), options);
292
+ uploadedBytes += end - start;
293
+ options.onProgress?.({
294
+ bytesUploaded: uploadedBytes,
295
+ totalBytes,
296
+ percentage: totalBytes === 0 ? 100 : Math.round(uploadedBytes / totalBytes * 100)
297
+ });
298
+ }
299
+ }
300
+ async function commitSession(transport, uploadId, totalParts, sha256, signal) {
301
+ signal?.throwIfAborted();
302
+ const response = await transport.fetch(`/fs/upload-session/${encodeURIComponent(uploadId)}/commit`, {
303
+ method: "POST",
304
+ headers: { "Content-Type": "application/json" },
305
+ body: JSON.stringify({
306
+ totalParts,
307
+ sha256
308
+ }),
309
+ signal
310
+ });
311
+ if (!response.ok) throw await parseUploadError(response);
312
+ return (await response.json()).data;
313
+ }
314
+ /** Best-effort abort of an in-progress session — never masks the real
315
+ * failure that triggered it, and never itself throws. An orphaned session
316
+ * also self-expires server-side (`expiresAt`), so a failed DELETE here is
317
+ * not a leak, just deferred cleanup. */
318
+ async function abortSession(transport, uploadId) {
319
+ try {
320
+ await transport.fetch(`/fs/upload-session/${encodeURIComponent(uploadId)}`, { method: "DELETE" });
321
+ } catch {}
322
+ }
323
+ /**
324
+ * Upload arbitrary binary or text data to the sandbox via the chunked
325
+ * upload-session protocol, verifying integrity with a whole-payload
326
+ * SHA-256 at commit. Browser/Worker-safe and works identically over the
327
+ * gateway proxy or a direct sidecar connection — see
328
+ * {@link FileSystem.uploadData}.
329
+ *
330
+ * @param transport - Fetch-shaped adapter scoped to the sandbox runtime
331
+ * (paths are relative to it, e.g. `/fs/upload-session`).
332
+ * @param remotePath - Destination path in the sandbox.
333
+ * @param data - Payload to upload. Strings encode as UTF-8.
334
+ * @param options - See {@link ChunkedUploadOptions}.
335
+ * @throws {SandboxError} `PAYLOAD_TOO_LARGE` immediately (zero requests) if
336
+ * the payload exceeds {@link FILE_UPLOAD_SESSION_MAX_BYTES}; otherwise the
337
+ * typed error from whichever step (init/part/commit) failed after retries
338
+ * are exhausted.
339
+ */
340
+ async function uploadChunked(transport, remotePath, data, options = {}) {
341
+ const estimatedSize = sizeOfInput(data);
342
+ if (estimatedSize > 67108864) throw new SandboxError(`Payload for ${remotePath} is ${estimatedSize} bytes, over the ${FILE_UPLOAD_SESSION_MAX_BYTES} byte chunked-upload session cap`, "PAYLOAD_TOO_LARGE", 413);
343
+ options.signal?.throwIfAborted();
344
+ const bytes = await normalizeToBytes(data);
345
+ options.signal?.throwIfAborted();
346
+ const sha256 = await sha256Hex(bytes);
347
+ const init = await initUploadSession(transport, remotePath, bytes.byteLength, sha256, options.mode, options.signal);
348
+ const totalParts = partCount(bytes.byteLength, init.partSize);
349
+ try {
350
+ await uploadAllParts(transport, init.uploadId, bytes, init.partSize, totalParts, options);
351
+ try {
352
+ return await commitSession(transport, init.uploadId, totalParts, sha256, options.signal);
353
+ } catch (err) {
354
+ if (!isChecksumMismatch(err)) throw err;
355
+ await uploadAllParts(transport, init.uploadId, bytes, init.partSize, totalParts, options);
356
+ return await commitSession(transport, init.uploadId, totalParts, sha256, options.signal);
357
+ }
358
+ } catch (err) {
359
+ await abortSession(transport, init.uploadId);
360
+ throw err;
361
+ }
362
+ }
363
+ //#endregion
364
+ //#region src/runtime-api.ts
365
+ /** Internal typed client for routes served by a sandbox runtime. */
366
+ var SandboxRuntimeApi = class {
367
+ terminals;
368
+ constructor(transport) {
369
+ this.transport = transport;
370
+ this.terminals = {
371
+ create: (options, request) => this.createTerminal(options, request),
372
+ input: (terminalId, data, request) => this.writeTerminal(terminalId, data, request),
373
+ list: () => this.listTerminals(),
374
+ get: (terminalId) => this.getTerminal(terminalId)
375
+ };
376
+ }
377
+ async health() {
378
+ await this.transport.ensureRunning();
379
+ return this.json("/health", { method: "GET" });
380
+ }
381
+ async ports() {
382
+ await this.transport.ensureRunning();
383
+ return (await this.json("/ports", { method: "GET" })).data?.ports ?? [];
384
+ }
385
+ async profiles() {
386
+ await this.transport.ensureRunning();
387
+ return this.json("/profiles", { method: "GET" });
388
+ }
389
+ async readFiles(paths, options = {}) {
390
+ if (paths.length === 0) return {
391
+ files: [],
392
+ errors: [],
393
+ stats: {
394
+ requested: 0,
395
+ succeeded: 0,
396
+ failed: 0
397
+ }
398
+ };
399
+ await this.transport.ensureRunning();
400
+ const files = [];
401
+ const errors = [];
402
+ let requested = 0;
403
+ let succeeded = 0;
404
+ let failed = 0;
405
+ for (let offset = 0; offset < paths.length; offset += 100) {
406
+ const chunk = paths.slice(offset, offset + 100);
407
+ const params = new URLSearchParams();
408
+ if (options.sessionId) params.set("sessionId", options.sessionId);
409
+ const query = params.toString();
410
+ const payload = await this.json(`/files/read-batch${query ? `?${query}` : ""}`, {
411
+ method: "POST",
412
+ body: JSON.stringify({
413
+ paths: chunk,
414
+ encoding: options.encoding ?? "utf8"
415
+ })
416
+ });
417
+ for (const [path, result] of Object.entries(payload.data.files)) if (result.success) files.push({
418
+ path,
419
+ content: result.content,
420
+ encoding: result.encoding,
421
+ hash: result.hash,
422
+ size: result.size,
423
+ mtime: result.mtime
424
+ });
425
+ else errors.push({
426
+ path,
427
+ error: result.error,
428
+ code: result.code
429
+ });
430
+ const results = Object.values(payload.data.files);
431
+ requested += payload.data.stats?.requested ?? chunk.length;
432
+ succeeded += payload.data.stats?.succeeded ?? results.filter((entry) => entry.success).length;
433
+ failed += payload.data.stats?.failed ?? results.filter((entry) => !entry.success).length;
434
+ }
435
+ return {
436
+ files,
437
+ errors,
438
+ stats: {
439
+ requested,
440
+ succeeded,
441
+ failed
442
+ }
443
+ };
444
+ }
445
+ async writeFile(path, content, options = {}) {
446
+ await this.transport.ensureRunning();
447
+ const params = new URLSearchParams();
448
+ if (options.sessionId) params.set("sessionId", options.sessionId);
449
+ const query = params.toString();
450
+ return (await this.json(`/files/write${query ? `?${query}` : ""}`, {
451
+ method: "POST",
452
+ body: JSON.stringify({
453
+ path,
454
+ content,
455
+ ...options.encoding !== void 0 ? { encoding: options.encoding } : {},
456
+ ...options.mode !== void 0 ? { mode: options.mode } : {}
457
+ })
458
+ })).data;
459
+ }
460
+ /**
461
+ * Upload in-memory binary or text data to the sandbox over the chunked
462
+ * upload-session protocol — the browser/Worker-safe sanctioned path for
463
+ * payloads over ~1 MiB, sized to clear the gateway proxy's per-request cap
464
+ * (parts stay under {@link FILE_UPLOAD_PART_MAX_BYTES}). Binary-safe:
465
+ * accepts a `Blob`, `ArrayBuffer`, `ArrayBufferView`, or UTF-8 `string`.
466
+ * Delegates to the shared `uploadChunked` engine in `lib/chunked-upload.js`,
467
+ * wired to this client's `transport.fetch` so requests carry the same
468
+ * scoped-token `Authorization` header and base URL as every other method
469
+ * on this class (e.g. {@link SandboxRuntimeApi.writeFile}) — works
470
+ * identically over the gateway proxy or a direct sidecar connection.
471
+ *
472
+ * @throws {SandboxError} `PAYLOAD_TOO_LARGE` immediately, before any
473
+ * request, if the payload exceeds the chunked-upload session cap.
474
+ */
475
+ async uploadData(remotePath, data, options) {
476
+ await this.transport.ensureRunning();
477
+ return uploadChunked({ fetch: (path, init) => this.transport.fetch(path, init) }, remotePath, data, options);
478
+ }
479
+ async deleteFile(path, options = {}) {
480
+ await this.transport.ensureRunning();
481
+ const params = new URLSearchParams();
482
+ if (options.sessionId) params.set("sessionId", options.sessionId);
483
+ const query = params.toString();
484
+ await this.request(`/files/delete${query ? `?${query}` : ""}`, {
485
+ method: "POST",
486
+ body: JSON.stringify({ path })
487
+ });
488
+ }
489
+ async renameFile(sourcePath, destinationPath, options = {}) {
490
+ await this.transport.ensureRunning();
491
+ const params = new URLSearchParams();
492
+ if (options.sessionId) params.set("sessionId", options.sessionId);
493
+ const query = params.toString();
494
+ return (await this.json(`/files/rename${query ? `?${query}` : ""}`, {
495
+ method: "POST",
496
+ body: JSON.stringify({
497
+ sourcePath,
498
+ destinationPath
499
+ })
500
+ })).data;
501
+ }
502
+ async fileTree(path, options = {}) {
503
+ if (options.maxDepth !== void 0 && (!Number.isInteger(options.maxDepth) || options.maxDepth < 1 || options.maxDepth > 20)) throw new ValidationError("maxDepth must be an integer between 1 and 20");
504
+ await this.transport.ensureRunning();
505
+ const params = new URLSearchParams({ path });
506
+ if (options.maxDepth !== void 0) params.set("maxDepth", String(options.maxDepth));
507
+ if (options.sessionId) params.set("sessionId", options.sessionId);
508
+ return (await this.json(`/files/tree?${params}`, { method: "GET" })).data;
509
+ }
510
+ async createSession(options) {
511
+ await this.transport.ensureRunning();
512
+ const info = normalizeSessionInfo(await this.json("/agents/sessions", {
513
+ method: "POST",
514
+ body: JSON.stringify({
515
+ ...options.sessionId !== void 0 ? { sessionId: options.sessionId } : {},
516
+ ...options.title !== void 0 ? { title: options.title } : {},
517
+ ...options.parentId !== void 0 ? { parentID: options.parentId } : {},
518
+ backend: normalizeRuntimeBackendConfig(options.backend)
519
+ })
520
+ }));
521
+ if (!info) throw new ServerError("Session creation response missing session id", 502, {
522
+ endpoint: "/agents/sessions",
523
+ origin: "runtime"
524
+ });
525
+ return info;
526
+ }
527
+ async messages(id, options = {}) {
528
+ await this.transport.ensureRunning();
529
+ const params = new URLSearchParams({ sessionId: id });
530
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
531
+ if (options.offset !== void 0) params.set("offset", String(options.offset));
532
+ if (options.since !== void 0) params.set("since", String(options.since));
533
+ const query = params.toString();
534
+ return (await this.json(`/messages${query ? `?${query}` : ""}`, { method: "GET" })).map((message) => ({
535
+ id: message.info.id,
536
+ role: message.info.role,
537
+ timestamp: message.info.timestamp,
538
+ parts: message.parts,
539
+ metadata: message.info.metadata
540
+ }));
541
+ }
542
+ async createTaskSession(options) {
543
+ await this.transport.ensureRunning();
544
+ const info = await this.json("/tasks", {
545
+ method: "POST",
546
+ body: JSON.stringify({
547
+ sessionId: options.sessionId,
548
+ backend: normalizeRuntimeBackendConfig(options.backend),
549
+ title: options.title,
550
+ ...options.parentSessionId !== void 0 ? { parentSessionId: options.parentSessionId } : {},
551
+ ...options.profile !== void 0 ? { profile: options.profile } : {},
552
+ ...options.isolateFileWrites !== void 0 ? { isolateFileWrites: options.isolateFileWrites } : {}
553
+ })
554
+ });
555
+ if (!info.id) throw new ServerError("Task session creation response missing session id", 502, {
556
+ endpoint: "/tasks",
557
+ origin: "runtime"
558
+ });
559
+ return info;
560
+ }
561
+ async sendSessionMessage(id, request, options = {}) {
562
+ if (options.timeoutMs !== void 0 && options.timeoutMs <= 0) throw new ValidationError("timeoutMs must be greater than zero");
563
+ await this.transport.ensureRunning();
564
+ const headers = new Headers({ "x-no-retry": "true" });
565
+ if (options.credentials?.apiKey) headers.set("X-Backend-Api-Key", options.credentials.apiKey);
566
+ if (options.credentials?.baseUrl) headers.set("X-Backend-Base-Url", options.credentials.baseUrl);
567
+ const timeoutSignal = options.timeoutMs !== void 0 ? AbortSignal.timeout(options.timeoutMs) : void 0;
568
+ const signal = options.signal && timeoutSignal ? combineAbortSignals([options.signal, timeoutSignal]) : options.signal ?? timeoutSignal;
569
+ return this.json(`/agents/sessions/${encodeURIComponent(id)}/messages`, {
570
+ method: "POST",
571
+ headers,
572
+ signal,
573
+ body: JSON.stringify({
574
+ ...request.messageId !== void 0 ? { messageID: request.messageId } : {},
575
+ parts: request.parts.map((part) => part.type === "text" ? {
576
+ ...part,
577
+ text: encodeTextForWire(part.text)
578
+ } : part),
579
+ ...request.system !== void 0 ? { system: request.system } : {},
580
+ ...request.agent !== void 0 ? { agent: request.agent } : {},
581
+ ...request.model !== void 0 ? { model: {
582
+ providerID: request.model.providerId,
583
+ modelID: request.model.modelId
584
+ } } : {},
585
+ ...request.reasoningEffort !== void 0 ? { reasoningEffort: request.reasoningEffort } : {},
586
+ ...request.turnId !== void 0 ? { turnId: request.turnId } : {},
587
+ ...request.interactions !== void 0 ? { interactions: request.interactions } : {}
588
+ })
589
+ });
590
+ }
591
+ async deleteSession(id) {
592
+ await this.transport.ensureRunning();
593
+ await this.request(`/agents/sessions/${encodeURIComponent(id)}`, { method: "DELETE" });
594
+ }
595
+ async taskSessionChanges(id) {
596
+ await this.transport.ensureRunning();
597
+ return this.json(`/tasks/${encodeURIComponent(id)}/changes`, { method: "GET" });
598
+ }
599
+ async commitTaskSession(id, options = {}) {
600
+ await this.transport.ensureRunning();
601
+ return this.json(`/tasks/${encodeURIComponent(id)}/changes`, {
602
+ method: "POST",
603
+ body: JSON.stringify({
604
+ ...options.message !== void 0 ? { message: options.message } : {},
605
+ ...options.files !== void 0 ? { files: options.files } : {}
606
+ })
607
+ });
608
+ }
609
+ async createTerminal(options = {}, request = {}) {
610
+ await this.transport.ensureRunning();
611
+ const params = new URLSearchParams();
612
+ if (request.sessionId) params.set("sessionId", request.sessionId);
613
+ const query = params.toString();
614
+ return (await this.json(`/terminals${query ? `?${query}` : ""}`, {
615
+ method: "POST",
616
+ body: JSON.stringify(options)
617
+ })).data;
618
+ }
619
+ async writeTerminal(terminalId, data, request = {}) {
620
+ await this.transport.ensureRunning();
621
+ const params = new URLSearchParams();
622
+ if (request.sessionId) params.set("sessionId", request.sessionId);
623
+ const query = params.toString();
624
+ await this.request(`/terminals/${encodeURIComponent(terminalId)}/input${query ? `?${query}` : ""}`, {
625
+ method: "POST",
626
+ body: JSON.stringify({ data })
627
+ });
628
+ }
629
+ async listTerminals() {
630
+ await this.transport.ensureRunning();
631
+ return (await this.json("/terminals", { method: "GET" })).data ?? [];
632
+ }
633
+ async getTerminal(terminalId) {
634
+ await this.transport.ensureRunning();
635
+ const response = await this.transport.fetch(`/terminals/${encodeURIComponent(terminalId)}`, { method: "GET" });
636
+ if (response.status === 404) return null;
637
+ await this.assertOk(response);
638
+ return (await response.json()).data;
639
+ }
640
+ async json(path, init) {
641
+ return await (await this.request(path, init)).json();
642
+ }
643
+ async request(path, init) {
644
+ const response = await this.transport.fetch(path, init);
645
+ await this.assertOk(response);
646
+ return response;
647
+ }
648
+ async assertOk(response) {
649
+ if (response.ok) return;
650
+ const body = await response.text();
651
+ throw parseErrorResponse(response.status, body, void 0, response.headers);
652
+ }
653
+ };
654
+ function normalizeSessionInfo(raw) {
655
+ const id = raw.id;
656
+ if (typeof id !== "string") return null;
657
+ const status = raw.status || "running";
658
+ return {
659
+ id,
660
+ parentId: typeof raw.parentID === "string" ? raw.parentID : typeof raw.parentId === "string" ? raw.parentId : void 0,
661
+ status: [
662
+ "queued",
663
+ "running",
664
+ "completed",
665
+ "failed",
666
+ "cancelled"
667
+ ].includes(status) ? status : "running",
668
+ backend: typeof raw.backendType === "string" ? raw.backendType : typeof raw.backend === "string" ? raw.backend : void 0,
669
+ model: typeof raw.model === "string" ? raw.model : void 0,
670
+ promptCount: typeof raw.promptCount === "number" ? raw.promptCount : void 0,
671
+ createdAt: raw.createdAt ? new Date(raw.createdAt) : void 0,
672
+ startedAt: raw.startedAt ? new Date(raw.startedAt) : void 0,
673
+ endedAt: raw.endedAt ? new Date(raw.endedAt) : void 0,
674
+ raw
675
+ };
676
+ }
677
+ //#endregion
678
+ export { FILE_DECODED_WRITE_MAX_BYTES as a, encodeTextForWire as c, serializeForSidecar as d, SANDBOX_PROXY_REQUEST_MAX_BYTES as i, normalizeRuntimeBackendConfig as l, normalizeSessionInfo as n, combineAbortSignals as o, uploadChunked as r, encodePromptForWire as s, SandboxRuntimeApi as t, normalizeRuntimeModelOverride as u };