@tangle-network/sandbox 0.10.2 → 0.10.4
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/LICENSE +11 -0
- package/dist/agent/index.d.ts +3 -2
- package/dist/auth/index.d.ts +1 -1
- package/dist/{client-8wOu9raV.d.ts → client-B8i6qrQr.d.ts} +2 -1
- package/dist/{client-B3oM6E-L.js → client-DTpHjPjc.js} +1 -1
- package/dist/collaboration/index.d.ts +1 -1
- package/dist/collaboration/index.js +1 -1
- package/dist/core.d.ts +4 -3
- package/dist/core.js +2 -2
- package/dist/{index-DS4SOkKG.d.ts → index-BFnMEze6.d.ts} +2 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.js +5 -4
- package/dist/runtime-api-CYA2DUQw.js +434 -0
- package/dist/runtime.d.ts +62 -0
- package/dist/runtime.js +81 -0
- package/dist/{sandbox-CeimsfC8.js → sandbox-Be9WAqSg.js} +124 -401
- package/dist/sandbox-BtNWbfd3.d.ts +1545 -0
- package/dist/tangle/index.d.ts +1 -1
- package/dist/tangle/index.js +1 -1
- package/dist/{tangle-DayyZe-i.js → tangle-D6UfK_B0.js} +1 -1
- package/dist/{sandbox-Bm2C9Phd.d.ts → types-BvSoEHz8.d.ts} +100 -1491
- package/package.json +44 -80
- /package/dist/{collaboration-D17lnOJX.js → collaboration-QZXrXdIb.js} +0 -0
- /package/dist/{errors-H9268M_g.d.ts → errors--P_nbLzM.d.ts} +0 -0
- /package/dist/{index-D-2pH_70.d.ts → index-CyHojkuA.d.ts} +0 -0
- /package/dist/{index-D7bwmNs8.d.ts → index-DKzapw-9.d.ts} +0 -0
|
@@ -1,159 +1,6 @@
|
|
|
1
|
+
import { n as normalizeSessionInfo, r as normalizeRuntimeBackendConfig, t as SandboxRuntimeApi } from "./runtime-api-CYA2DUQw.js";
|
|
1
2
|
import { c as SandboxError, d as TimeoutError, f as ValidationError, i as NetworkError, l as ServerError, n as CapabilityError, p as parseErrorResponse, s as QuotaError, t as AuthError, u as StateError } from "./errors-DSz87Rkk.js";
|
|
2
3
|
import { addTokenUsage, readTokenCostUsd, readTokenUsage } from "@tangle-network/agent-core";
|
|
3
|
-
//#region src/backend-config.ts
|
|
4
|
-
const LEGACY_QUESTION_ALIAS_KEY = ["question", "Channel"].join("");
|
|
5
|
-
function parseModelString(model) {
|
|
6
|
-
const parts = model.split("/");
|
|
7
|
-
if (parts.length >= 2) return {
|
|
8
|
-
provider: parts[0],
|
|
9
|
-
model: parts.slice(1).join("/")
|
|
10
|
-
};
|
|
11
|
-
return { model };
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Normalize runtime backend config for the wire format.
|
|
15
|
-
*
|
|
16
|
-
* Callers set `backend.profile` (named string or portable AgentProfile).
|
|
17
|
-
* This function generates the native `inlineProfile` shim automatically.
|
|
18
|
-
*/
|
|
19
|
-
function normalizeRuntimeBackendConfig(backend, options = {}) {
|
|
20
|
-
if (!backend && !options.model) return void 0;
|
|
21
|
-
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.`);
|
|
22
|
-
const portableProfile = backend?.profile && typeof backend.profile !== "string" ? backend.profile : void 0;
|
|
23
|
-
const inlineProfile = portableProfile ? toBackendProfile(portableProfile) : void 0;
|
|
24
|
-
const callerInlineProfile = backend?.inlineProfile;
|
|
25
|
-
if (callerInlineProfile && !inlineProfile) console.warn("[sandbox-sdk] backend.inlineProfile is deprecated. Use backend.profile (AgentPortableProfile) instead.");
|
|
26
|
-
if (inlineProfile && portableProfile) {
|
|
27
|
-
const reasoningEffort = portableProfile.model?.reasoningEffort ?? portableProfile.extensions?.[backend?.type ?? ""]?.reasoningEffort;
|
|
28
|
-
if (typeof reasoningEffort === "string" && reasoningEffort !== "auto" && reasoningEffort !== "none") inlineProfile.reasoningEffort = reasoningEffort;
|
|
29
|
-
}
|
|
30
|
-
return {
|
|
31
|
-
...backend?.type ? { type: backend.type } : {},
|
|
32
|
-
...typeof backend?.profile === "string" ? { profile: backend.profile } : {},
|
|
33
|
-
...inlineProfile ? { inlineProfile } : callerInlineProfile ? { inlineProfile: callerInlineProfile } : {},
|
|
34
|
-
...backend?.model ? { model: backend.model } : options.model ? { model: parseModelString(options.model) } : {},
|
|
35
|
-
...backend?.server ? { server: backend.server } : {},
|
|
36
|
-
...backend?.interactions !== void 0 ? { interactions: backend.interactions } : {},
|
|
37
|
-
...backend?.metadata !== void 0 ? { metadata: backend.metadata } : {}
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Permission keys the sidecar's strict `permission` schema accepts
|
|
42
|
-
* (apps/sidecar/src/schemas/agent-profile-schema.ts → permissionConfigSchema).
|
|
43
|
-
* Any other portable-permission key is a per-tool gate, not a permission, and
|
|
44
|
-
* is routed into `tools` instead of passed through — the sidecar's `.strict()`
|
|
45
|
-
* schema rejects unknown permission keys with `unrecognized_keys`.
|
|
46
|
-
*
|
|
47
|
-
* Name must not carry an UPPERCASE `SIDECAR_`/`ORCHESTRATOR_`-style prefix:
|
|
48
|
-
* obfuscate.sh runs `--rename-globals false`, so a module-global identifier
|
|
49
|
-
* ships verbatim in dist/, and verify-dist.sh bans those prefixes as
|
|
50
|
-
* closed-source leaks.
|
|
51
|
-
*/
|
|
52
|
-
const CANONICAL_PERMISSION_KEYS = new Set([
|
|
53
|
-
"edit",
|
|
54
|
-
"bash",
|
|
55
|
-
"webfetch",
|
|
56
|
-
"mcp",
|
|
57
|
-
"doom_loop",
|
|
58
|
-
"external_directory"
|
|
59
|
-
]);
|
|
60
|
-
function hasAnyProfileResource(resources) {
|
|
61
|
-
return !!(resources.files?.length || resources.tools?.length || resources.skills?.length || resources.agents?.length || resources.commands?.length || resources.instructions);
|
|
62
|
-
}
|
|
63
|
-
function validateProfileResources(profile) {
|
|
64
|
-
if (profile.resources !== void 0 && (profile.resources === null || typeof profile.resources !== "object" || Array.isArray(profile.resources))) throw new Error("backend.profile.resources must be an object when provided");
|
|
65
|
-
if (profile.resources?.plugins?.length) throw new Error("backend.profile.resources.plugins is not supported by the sandbox SDK profile contract; use backend-specific provider profiles for OpenCode plugin resources");
|
|
66
|
-
}
|
|
67
|
-
function toBackendProfile(profile) {
|
|
68
|
-
validateProfileResources(profile);
|
|
69
|
-
const permission = {};
|
|
70
|
-
const derivedTools = {};
|
|
71
|
-
for (const [rawKey, value] of Object.entries(profile.permissions ?? {})) {
|
|
72
|
-
const key = rawKey === "network" ? "webfetch" : rawKey;
|
|
73
|
-
if (CANONICAL_PERMISSION_KEYS.has(key)) permission[key] = value;
|
|
74
|
-
else if (typeof value === "string") derivedTools[key] = value !== "deny";
|
|
75
|
-
else console.warn(`[sandbox-sdk] dropping unsupported permission key "${rawKey}": not a canonical sidecar permission and not a string tool gate`);
|
|
76
|
-
}
|
|
77
|
-
const tools = {
|
|
78
|
-
...derivedTools,
|
|
79
|
-
...profile.tools ?? {}
|
|
80
|
-
};
|
|
81
|
-
const mcp = profile.mcp ? Object.fromEntries(Object.entries(profile.mcp).map(([name, config]) => {
|
|
82
|
-
const c = config;
|
|
83
|
-
const transport = c.transport;
|
|
84
|
-
const command = c.command;
|
|
85
|
-
const hasCommand = Array.isArray(command) ? command.length > 0 : typeof command === "string" && command.trim().length > 0;
|
|
86
|
-
const isRemote = transport === "http" || transport === "sse" || typeof c.url === "string" && !hasCommand;
|
|
87
|
-
const enabled = c.enabled;
|
|
88
|
-
const timeout = c.timeout;
|
|
89
|
-
if (isRemote) return [name, {
|
|
90
|
-
type: "remote",
|
|
91
|
-
url: c.url,
|
|
92
|
-
...c.headers ? { headers: c.headers } : {},
|
|
93
|
-
...enabled !== void 0 ? { enabled } : {},
|
|
94
|
-
...timeout !== void 0 ? { timeout } : {}
|
|
95
|
-
}];
|
|
96
|
-
return [name, {
|
|
97
|
-
type: "local",
|
|
98
|
-
command: [...Array.isArray(command) ? command : typeof command === "string" ? command.trim().split(/\s+/).filter(Boolean) : [], ...c.args ?? []],
|
|
99
|
-
...c.env ? { environment: c.env } : {},
|
|
100
|
-
...enabled !== void 0 ? { enabled } : {},
|
|
101
|
-
...timeout !== void 0 ? { timeout } : {}
|
|
102
|
-
}];
|
|
103
|
-
})) : void 0;
|
|
104
|
-
const openCodeExtension = profile.extensions?.opencode;
|
|
105
|
-
const plugins = openCodeExtension?.plugins;
|
|
106
|
-
if (plugins !== void 0 && (!Array.isArray(plugins) || plugins.some((plugin) => typeof plugin !== "string" || !plugin.trim()))) throw new Error("backend.profile.extensions.opencode.plugins must be an array of non-empty package names");
|
|
107
|
-
const commands = openCodeExtension?.commands;
|
|
108
|
-
if (commands !== void 0 && (commands === null || typeof commands !== "object" || Array.isArray(commands))) throw new Error("backend.profile.extensions.opencode.commands must be an object");
|
|
109
|
-
const profileExtends = openCodeExtension?.extends;
|
|
110
|
-
if (profileExtends !== void 0 && typeof profileExtends !== "string" && (!Array.isArray(profileExtends) || profileExtends.some((entry) => typeof entry !== "string" || !entry.trim()))) throw new Error("backend.profile.extensions.opencode.extends must be a profile name or array of profile names");
|
|
111
|
-
const agents = profile.subagents ? Object.fromEntries(Object.entries(profile.subagents).map(([name, subagent]) => [name, {
|
|
112
|
-
description: subagent.description,
|
|
113
|
-
prompt: subagent.prompt,
|
|
114
|
-
model: subagent.model,
|
|
115
|
-
tools: subagent.tools,
|
|
116
|
-
permission: subagent.permissions,
|
|
117
|
-
maxSteps: subagent.maxSteps,
|
|
118
|
-
...subagent.metadata ?? {}
|
|
119
|
-
}])) : void 0;
|
|
120
|
-
return {
|
|
121
|
-
name: profile.name,
|
|
122
|
-
description: profile.description,
|
|
123
|
-
version: profile.version,
|
|
124
|
-
tags: profile.tags,
|
|
125
|
-
...profileExtends ? { extends: profileExtends } : {},
|
|
126
|
-
model: profile.model?.default,
|
|
127
|
-
small_model: profile.model?.small,
|
|
128
|
-
...profile.prompt?.systemPrompt ? { systemPrompt: profile.prompt.systemPrompt } : {},
|
|
129
|
-
...Object.keys(tools).length > 0 ? { tools } : {},
|
|
130
|
-
...profile.prompt?.instructions?.length ? { instructions: profile.prompt.instructions } : {},
|
|
131
|
-
...Object.keys(permission).length > 0 ? { permission } : {},
|
|
132
|
-
...mcp ? { mcp } : {},
|
|
133
|
-
...profile.connections?.length ? { connections: profile.connections } : {},
|
|
134
|
-
...agents ? { agent: agents } : {},
|
|
135
|
-
...profile.resources && hasAnyProfileResource(profile.resources) ? { resources: profile.resources } : {},
|
|
136
|
-
...profile.hooks ? { hooks: profile.hooks } : {},
|
|
137
|
-
...profile.modes ? { modes: profile.modes } : {},
|
|
138
|
-
...commands ? { command: commands } : {},
|
|
139
|
-
...plugins?.length ? { plugin: plugins } : {},
|
|
140
|
-
...profile.extensions ? { extensions: profile.extensions } : {}
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Serialize a portable AgentProfile into the sidecar's canonical inline-profile
|
|
145
|
-
* wire shape (validated by inlineProfileSchema in
|
|
146
|
-
* apps/sidecar/src/schemas/agent-profile-schema.ts).
|
|
147
|
-
*
|
|
148
|
-
* Single choke point every product shares: streamPrompt →
|
|
149
|
-
* normalizeRuntimeBackendConfig → toBackendProfile. Exported so callers and the
|
|
150
|
-
* drift-guard test target one contract — the test parses this output with the
|
|
151
|
-
* sidecar's own schema, so any future schema tightening fails here first.
|
|
152
|
-
*/
|
|
153
|
-
function serializeForSidecar(profile) {
|
|
154
|
-
return toBackendProfile(profile);
|
|
155
|
-
}
|
|
156
|
-
//#endregion
|
|
157
4
|
//#region src/lib/sse-parser.ts
|
|
158
5
|
/**
|
|
159
6
|
* Parse a streaming SSE response body into an async iterable of
|
|
@@ -599,244 +446,6 @@ function collectAgentResponseText(events) {
|
|
|
599
446
|
return responseText && responseText.trim().length > 0 ? responseText : void 0;
|
|
600
447
|
}
|
|
601
448
|
//#endregion
|
|
602
|
-
//#region src/runtime-api.ts
|
|
603
|
-
/** Internal typed client for routes served by a sandbox runtime. */
|
|
604
|
-
var SandboxRuntimeApi = class {
|
|
605
|
-
terminals;
|
|
606
|
-
constructor(transport) {
|
|
607
|
-
this.transport = transport;
|
|
608
|
-
this.terminals = {
|
|
609
|
-
create: (options, request) => this.createTerminal(options, request),
|
|
610
|
-
input: (terminalId, data, request) => this.writeTerminal(terminalId, data, request),
|
|
611
|
-
list: () => this.listTerminals(),
|
|
612
|
-
get: (terminalId) => this.getTerminal(terminalId)
|
|
613
|
-
};
|
|
614
|
-
}
|
|
615
|
-
async health() {
|
|
616
|
-
await this.transport.ensureRunning();
|
|
617
|
-
return this.json("/health", { method: "GET" });
|
|
618
|
-
}
|
|
619
|
-
async ports() {
|
|
620
|
-
await this.transport.ensureRunning();
|
|
621
|
-
return (await this.json("/ports", { method: "GET" })).data?.ports ?? [];
|
|
622
|
-
}
|
|
623
|
-
async profiles() {
|
|
624
|
-
await this.transport.ensureRunning();
|
|
625
|
-
return this.json("/profiles", { method: "GET" });
|
|
626
|
-
}
|
|
627
|
-
async readFiles(paths, options = {}) {
|
|
628
|
-
if (paths.length === 0) return {
|
|
629
|
-
files: [],
|
|
630
|
-
errors: [],
|
|
631
|
-
stats: {
|
|
632
|
-
requested: 0,
|
|
633
|
-
succeeded: 0,
|
|
634
|
-
failed: 0
|
|
635
|
-
}
|
|
636
|
-
};
|
|
637
|
-
await this.transport.ensureRunning();
|
|
638
|
-
const files = [];
|
|
639
|
-
const errors = [];
|
|
640
|
-
let requested = 0;
|
|
641
|
-
let succeeded = 0;
|
|
642
|
-
let failed = 0;
|
|
643
|
-
for (let offset = 0; offset < paths.length; offset += 100) {
|
|
644
|
-
const chunk = paths.slice(offset, offset + 100);
|
|
645
|
-
const params = new URLSearchParams();
|
|
646
|
-
if (options.sessionId) params.set("sessionId", options.sessionId);
|
|
647
|
-
const query = params.toString();
|
|
648
|
-
const payload = await this.json(`/files/read-batch${query ? `?${query}` : ""}`, {
|
|
649
|
-
method: "POST",
|
|
650
|
-
body: JSON.stringify({
|
|
651
|
-
paths: chunk,
|
|
652
|
-
...options.encoding ? { encoding: options.encoding } : {}
|
|
653
|
-
})
|
|
654
|
-
});
|
|
655
|
-
for (const [path, result] of Object.entries(payload.data.files)) if (result.success) files.push({
|
|
656
|
-
path,
|
|
657
|
-
content: result.content,
|
|
658
|
-
encoding: result.encoding,
|
|
659
|
-
hash: result.hash,
|
|
660
|
-
size: result.size,
|
|
661
|
-
mtime: result.mtime
|
|
662
|
-
});
|
|
663
|
-
else errors.push({
|
|
664
|
-
path,
|
|
665
|
-
error: result.error,
|
|
666
|
-
code: result.code
|
|
667
|
-
});
|
|
668
|
-
const results = Object.values(payload.data.files);
|
|
669
|
-
requested += payload.data.stats?.requested ?? chunk.length;
|
|
670
|
-
succeeded += payload.data.stats?.succeeded ?? results.filter((entry) => entry.success).length;
|
|
671
|
-
failed += payload.data.stats?.failed ?? results.filter((entry) => !entry.success).length;
|
|
672
|
-
}
|
|
673
|
-
return {
|
|
674
|
-
files,
|
|
675
|
-
errors,
|
|
676
|
-
stats: {
|
|
677
|
-
requested,
|
|
678
|
-
succeeded,
|
|
679
|
-
failed
|
|
680
|
-
}
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
async fileTree(path, options = {}) {
|
|
684
|
-
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");
|
|
685
|
-
await this.transport.ensureRunning();
|
|
686
|
-
const params = new URLSearchParams({ path });
|
|
687
|
-
if (options.maxDepth !== void 0) params.set("maxDepth", String(options.maxDepth));
|
|
688
|
-
if (options.sessionId) params.set("sessionId", options.sessionId);
|
|
689
|
-
return (await this.json(`/files/tree?${params}`, { method: "GET" })).data;
|
|
690
|
-
}
|
|
691
|
-
async createSession(options) {
|
|
692
|
-
await this.transport.ensureRunning();
|
|
693
|
-
const info = normalizeSessionInfo(await this.json("/agents/sessions", {
|
|
694
|
-
method: "POST",
|
|
695
|
-
body: JSON.stringify({
|
|
696
|
-
...options.title !== void 0 ? { title: options.title } : {},
|
|
697
|
-
...options.parentId !== void 0 ? { parentID: options.parentId } : {},
|
|
698
|
-
backend: normalizeRuntimeBackendConfig(options.backend)
|
|
699
|
-
})
|
|
700
|
-
}));
|
|
701
|
-
if (!info) throw new ServerError("Session creation response missing session id", 502, {
|
|
702
|
-
endpoint: "/agents/sessions",
|
|
703
|
-
origin: "runtime"
|
|
704
|
-
});
|
|
705
|
-
return info;
|
|
706
|
-
}
|
|
707
|
-
async createTaskSession(options) {
|
|
708
|
-
await this.transport.ensureRunning();
|
|
709
|
-
const info = await this.json("/tasks", {
|
|
710
|
-
method: "POST",
|
|
711
|
-
body: JSON.stringify({
|
|
712
|
-
sessionId: options.sessionId,
|
|
713
|
-
backend: normalizeRuntimeBackendConfig(options.backend),
|
|
714
|
-
title: options.title,
|
|
715
|
-
...options.parentSessionId !== void 0 ? { parentSessionId: options.parentSessionId } : {},
|
|
716
|
-
...options.profile !== void 0 ? { profile: options.profile } : {},
|
|
717
|
-
...options.isolateFileWrites !== void 0 ? { isolateFileWrites: options.isolateFileWrites } : {}
|
|
718
|
-
})
|
|
719
|
-
});
|
|
720
|
-
if (!info.id) throw new ServerError("Task session creation response missing session id", 502, {
|
|
721
|
-
endpoint: "/tasks",
|
|
722
|
-
origin: "runtime"
|
|
723
|
-
});
|
|
724
|
-
return info;
|
|
725
|
-
}
|
|
726
|
-
async sendSessionMessage(id, request, options = {}) {
|
|
727
|
-
if (options.timeoutMs !== void 0 && options.timeoutMs <= 0) throw new ValidationError("timeoutMs must be greater than zero");
|
|
728
|
-
await this.transport.ensureRunning();
|
|
729
|
-
const headers = new Headers({ "x-no-retry": "true" });
|
|
730
|
-
if (options.credentials?.apiKey) headers.set("X-Backend-Api-Key", options.credentials.apiKey);
|
|
731
|
-
if (options.credentials?.baseUrl) headers.set("X-Backend-Base-Url", options.credentials.baseUrl);
|
|
732
|
-
const timeoutSignal = options.timeoutMs !== void 0 ? AbortSignal.timeout(options.timeoutMs) : void 0;
|
|
733
|
-
const signal = options.signal && timeoutSignal ? AbortSignal.any([options.signal, timeoutSignal]) : options.signal ?? timeoutSignal;
|
|
734
|
-
return this.json(`/agents/sessions/${encodeURIComponent(id)}/messages`, {
|
|
735
|
-
method: "POST",
|
|
736
|
-
headers,
|
|
737
|
-
signal,
|
|
738
|
-
body: JSON.stringify({
|
|
739
|
-
...request.messageId !== void 0 ? { messageID: request.messageId } : {},
|
|
740
|
-
parts: request.parts,
|
|
741
|
-
...request.system !== void 0 ? { system: request.system } : {},
|
|
742
|
-
...request.agent !== void 0 ? { agent: request.agent } : {},
|
|
743
|
-
...request.model !== void 0 ? { model: {
|
|
744
|
-
providerID: request.model.providerId,
|
|
745
|
-
modelID: request.model.modelId
|
|
746
|
-
} } : {},
|
|
747
|
-
...request.reasoningEffort !== void 0 ? { reasoningEffort: request.reasoningEffort } : {},
|
|
748
|
-
...request.turnId !== void 0 ? { turnId: request.turnId } : {},
|
|
749
|
-
...request.interactions !== void 0 ? { interactions: request.interactions } : {}
|
|
750
|
-
})
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
async deleteSession(id) {
|
|
754
|
-
await this.transport.ensureRunning();
|
|
755
|
-
await this.request(`/agents/sessions/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
756
|
-
}
|
|
757
|
-
async taskSessionChanges(id) {
|
|
758
|
-
await this.transport.ensureRunning();
|
|
759
|
-
return this.json(`/tasks/${encodeURIComponent(id)}/changes`, { method: "GET" });
|
|
760
|
-
}
|
|
761
|
-
async commitTaskSession(id, options = {}) {
|
|
762
|
-
await this.transport.ensureRunning();
|
|
763
|
-
return this.json(`/tasks/${encodeURIComponent(id)}/changes`, {
|
|
764
|
-
method: "POST",
|
|
765
|
-
body: JSON.stringify({
|
|
766
|
-
...options.message !== void 0 ? { message: options.message } : {},
|
|
767
|
-
...options.files !== void 0 ? { files: options.files } : {}
|
|
768
|
-
})
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
async createTerminal(options = {}, request = {}) {
|
|
772
|
-
await this.transport.ensureRunning();
|
|
773
|
-
const params = new URLSearchParams();
|
|
774
|
-
if (request.sessionId) params.set("sessionId", request.sessionId);
|
|
775
|
-
const query = params.toString();
|
|
776
|
-
return (await this.json(`/terminals${query ? `?${query}` : ""}`, {
|
|
777
|
-
method: "POST",
|
|
778
|
-
body: JSON.stringify(options)
|
|
779
|
-
})).data;
|
|
780
|
-
}
|
|
781
|
-
async writeTerminal(terminalId, data, request = {}) {
|
|
782
|
-
await this.transport.ensureRunning();
|
|
783
|
-
const params = new URLSearchParams();
|
|
784
|
-
if (request.sessionId) params.set("sessionId", request.sessionId);
|
|
785
|
-
const query = params.toString();
|
|
786
|
-
await this.request(`/terminals/${encodeURIComponent(terminalId)}/input${query ? `?${query}` : ""}`, {
|
|
787
|
-
method: "POST",
|
|
788
|
-
body: JSON.stringify({ data })
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
async listTerminals() {
|
|
792
|
-
await this.transport.ensureRunning();
|
|
793
|
-
return (await this.json("/terminals", { method: "GET" })).data ?? [];
|
|
794
|
-
}
|
|
795
|
-
async getTerminal(terminalId) {
|
|
796
|
-
await this.transport.ensureRunning();
|
|
797
|
-
const response = await this.transport.fetch(`/terminals/${encodeURIComponent(terminalId)}`, { method: "GET" });
|
|
798
|
-
if (response.status === 404) return null;
|
|
799
|
-
await this.assertOk(response);
|
|
800
|
-
return (await response.json()).data;
|
|
801
|
-
}
|
|
802
|
-
async json(path, init) {
|
|
803
|
-
return await (await this.request(path, init)).json();
|
|
804
|
-
}
|
|
805
|
-
async request(path, init) {
|
|
806
|
-
const response = await this.transport.fetch(path, init);
|
|
807
|
-
await this.assertOk(response);
|
|
808
|
-
return response;
|
|
809
|
-
}
|
|
810
|
-
async assertOk(response) {
|
|
811
|
-
if (response.ok) return;
|
|
812
|
-
const body = await response.text();
|
|
813
|
-
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
814
|
-
}
|
|
815
|
-
};
|
|
816
|
-
function normalizeSessionInfo(raw) {
|
|
817
|
-
const id = raw.id;
|
|
818
|
-
if (typeof id !== "string") return null;
|
|
819
|
-
const status = raw.status || "running";
|
|
820
|
-
return {
|
|
821
|
-
id,
|
|
822
|
-
parentId: typeof raw.parentID === "string" ? raw.parentID : typeof raw.parentId === "string" ? raw.parentId : void 0,
|
|
823
|
-
status: [
|
|
824
|
-
"queued",
|
|
825
|
-
"running",
|
|
826
|
-
"completed",
|
|
827
|
-
"failed",
|
|
828
|
-
"cancelled"
|
|
829
|
-
].includes(status) ? status : "running",
|
|
830
|
-
backend: typeof raw.backendType === "string" ? raw.backendType : typeof raw.backend === "string" ? raw.backend : void 0,
|
|
831
|
-
model: typeof raw.model === "string" ? raw.model : void 0,
|
|
832
|
-
promptCount: typeof raw.promptCount === "number" ? raw.promptCount : void 0,
|
|
833
|
-
createdAt: raw.createdAt ? new Date(raw.createdAt) : void 0,
|
|
834
|
-
startedAt: raw.startedAt ? new Date(raw.startedAt) : void 0,
|
|
835
|
-
endedAt: raw.endedAt ? new Date(raw.endedAt) : void 0,
|
|
836
|
-
raw
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
//#endregion
|
|
840
449
|
//#region src/interactive.ts
|
|
841
450
|
/**
|
|
842
451
|
* Handle for one session's interactive harness. Obtained via
|
|
@@ -1143,6 +752,13 @@ function isTransientSnapshotVisibilityError(error) {
|
|
|
1143
752
|
return message.includes("SNAPSHOT_NOT_FOUND") || message.includes("Failed to list snapshot") || message.includes("Storage agent error (404)");
|
|
1144
753
|
}
|
|
1145
754
|
/**
|
|
755
|
+
* The extra separator is intentional: absolute paths produce `//`, allowing
|
|
756
|
+
* the runtime route wildcard to retain the path's leading slash.
|
|
757
|
+
*/
|
|
758
|
+
function runtimeFilesystemPath(endpoint, path) {
|
|
759
|
+
return `${endpoint}/${path}`;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
1146
762
|
* Single source of truth for a run's outcome, shared by
|
|
1147
763
|
* `prompt`/`task`/`_sessionResult`/turn settling. A run succeeds only when it
|
|
1148
764
|
* reached a terminal event, raised no run-level error, hit no approval gate,
|
|
@@ -2757,7 +2373,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2757
2373
|
const fs = await import("node:fs/promises");
|
|
2758
2374
|
const path = await import("node:path");
|
|
2759
2375
|
await fs.mkdir(path.dirname(localPath), { recursive: true });
|
|
2760
|
-
const response = await this.runtimeFetch(
|
|
2376
|
+
const response = await this.runtimeFetch(runtimeFilesystemPath("/fs/download", remotePath), { method: "GET" });
|
|
2761
2377
|
if (!response.ok) {
|
|
2762
2378
|
const body = await response.text();
|
|
2763
2379
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
@@ -2807,7 +2423,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2807
2423
|
const fs = await import("node:fs/promises");
|
|
2808
2424
|
const path = await import("node:path");
|
|
2809
2425
|
const { execSync } = await import("node:child_process");
|
|
2810
|
-
const response = await this.runtimeFetch(
|
|
2426
|
+
const response = await this.runtimeFetch(runtimeFilesystemPath("/fs/download-dir", remoteDir), { method: "GET" });
|
|
2811
2427
|
if (!response.ok) {
|
|
2812
2428
|
const body = await response.text();
|
|
2813
2429
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
@@ -2830,7 +2446,8 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2830
2446
|
if (options?.long) params.set("long", "true");
|
|
2831
2447
|
if (options?.sessionId) params.set("sessionId", options.sessionId);
|
|
2832
2448
|
const query = params.toString();
|
|
2833
|
-
const
|
|
2449
|
+
const basePath = runtimeFilesystemPath("/fs/list", path);
|
|
2450
|
+
const url = query ? `${basePath}?${query}` : basePath;
|
|
2834
2451
|
const response = await this.runtimeFetch(url, { method: "GET" });
|
|
2835
2452
|
if (!response.ok) {
|
|
2836
2453
|
const body = await response.text();
|
|
@@ -2852,7 +2469,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2852
2469
|
}
|
|
2853
2470
|
async fsStat(path) {
|
|
2854
2471
|
await this.ensureRunning();
|
|
2855
|
-
const response = await this.runtimeFetch(
|
|
2472
|
+
const response = await this.runtimeFetch(runtimeFilesystemPath("/fs/stat", path), { method: "GET" });
|
|
2856
2473
|
if (!response.ok) {
|
|
2857
2474
|
const body = await response.text();
|
|
2858
2475
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
@@ -2890,7 +2507,8 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2890
2507
|
if (options?.recursive) params.set("recursive", "true");
|
|
2891
2508
|
if (options?.sessionId) params.set("sessionId", options.sessionId);
|
|
2892
2509
|
const query = params.toString();
|
|
2893
|
-
const
|
|
2510
|
+
const basePath = runtimeFilesystemPath("/fs", path);
|
|
2511
|
+
const url = query ? `${basePath}?${query}` : basePath;
|
|
2894
2512
|
const response = await this.runtimeFetch(url, { method: "DELETE" });
|
|
2895
2513
|
if (!response.ok) {
|
|
2896
2514
|
const body = await response.text();
|
|
@@ -2902,7 +2520,8 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2902
2520
|
const params = new URLSearchParams();
|
|
2903
2521
|
if (options?.recursive) params.set("recursive", "true");
|
|
2904
2522
|
const query = params.toString();
|
|
2905
|
-
const
|
|
2523
|
+
const basePath = runtimeFilesystemPath("/fs/mkdir", path);
|
|
2524
|
+
const url = query ? `${basePath}?${query}` : basePath;
|
|
2906
2525
|
const response = await this.runtimeFetch(url, { method: "POST" });
|
|
2907
2526
|
if (!response.ok) {
|
|
2908
2527
|
const body = await response.text();
|
|
@@ -2911,7 +2530,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
2911
2530
|
}
|
|
2912
2531
|
async fsExists(path) {
|
|
2913
2532
|
await this.ensureRunning();
|
|
2914
|
-
const response = await this.runtimeFetch(
|
|
2533
|
+
const response = await this.runtimeFetch(runtimeFilesystemPath("/fs/exists", path), { method: "GET" });
|
|
2915
2534
|
if (!response.ok) {
|
|
2916
2535
|
const body = await response.text();
|
|
2917
2536
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
@@ -4112,7 +3731,89 @@ var SandboxInstance = class SandboxInstance {
|
|
|
4112
3731
|
return ((await response.json()).children ?? []).map((child) => new SandboxInstance(this.client, this.parseInfo(child), this.defaultRuntimeBackend));
|
|
4113
3732
|
}
|
|
4114
3733
|
/**
|
|
3734
|
+
* Speculative rollouts: branch this RUNNING sandbox into `n` copy-on-write
|
|
3735
|
+
* children, run the SAME agent turn in every child in parallel, and
|
|
3736
|
+
* optionally score each child by running `scorer.command` inside it (exit 0
|
|
3737
|
+
* passes; stdout is parsed as the numeric score — highest wins).
|
|
3738
|
+
*
|
|
3739
|
+
* Async-op shape: resolves as soon as the children exist (their turns are
|
|
3740
|
+
* still running); poll {@link getRollout} or use {@link waitForRollout} for
|
|
3741
|
+
* results. Children are first-class sandboxes the caller may promote or
|
|
3742
|
+
* delete — each occupies a concurrent-session quota slot like a create.
|
|
3743
|
+
*
|
|
3744
|
+
* @example
|
|
3745
|
+
* ```typescript
|
|
3746
|
+
* const started = await box.rollout({
|
|
3747
|
+
* n: 4,
|
|
3748
|
+
* parts: [{ type: "text", text: "make the failing test pass" }],
|
|
3749
|
+
* scorer: { type: "command", command: "npm test --silent && echo 1" },
|
|
3750
|
+
* });
|
|
3751
|
+
* const settled = await box.waitForRollout(started.rolloutId);
|
|
3752
|
+
* console.log(settled.winner, settled.results);
|
|
3753
|
+
* ```
|
|
3754
|
+
*/
|
|
3755
|
+
async rollout(options) {
|
|
3756
|
+
if (!Number.isInteger(options.n) || options.n < 1 || options.n > 8) throw new ValidationError(`rollout n must be an integer in [1, 8] (got ${options.n})`);
|
|
3757
|
+
if (!Array.isArray(options.parts) || options.parts.length === 0) throw new ValidationError("rollout parts must be a non-empty array");
|
|
3758
|
+
if (options.scorer?.type === "command" && !options.scorer.command) throw new ValidationError("scorer.command is required when scorer.type is \"command\"");
|
|
3759
|
+
const response = await this.client.fetch(`/v1/sandboxes/${this.id}/rollouts`, {
|
|
3760
|
+
method: "POST",
|
|
3761
|
+
body: JSON.stringify({
|
|
3762
|
+
n: options.n,
|
|
3763
|
+
parts: options.parts,
|
|
3764
|
+
...options.scorer ? { scorer: options.scorer } : {}
|
|
3765
|
+
})
|
|
3766
|
+
});
|
|
3767
|
+
if (!response.ok) {
|
|
3768
|
+
const body = await response.text();
|
|
3769
|
+
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
3770
|
+
}
|
|
3771
|
+
const data = await response.json();
|
|
3772
|
+
if (typeof data.rolloutId !== "string" || data.rolloutId.length === 0) throw new ServerError(`Rollout create returned ${response.status} but body had no rolloutId: ${JSON.stringify(data).slice(0, 200)}`, response.status);
|
|
3773
|
+
return data;
|
|
3774
|
+
}
|
|
3775
|
+
/**
|
|
3776
|
+
* Poll a rollout's status and per-child results.
|
|
3777
|
+
*
|
|
3778
|
+
* Failed or timed-out children appear as result entries with `error` set —
|
|
3779
|
+
* partial results are never dropped. Rollout poll-state lives in
|
|
3780
|
+
* orchestrator RAM: after an orchestrator restart this returns 404 while
|
|
3781
|
+
* the children themselves survive as first-class sandboxes.
|
|
3782
|
+
*/
|
|
3783
|
+
async getRollout(rolloutId) {
|
|
3784
|
+
if (!rolloutId) throw new ValidationError("rolloutId is required");
|
|
3785
|
+
const response = await this.client.fetch(`/v1/sandboxes/${this.id}/rollouts/${encodeURIComponent(rolloutId)}`, { method: "GET" });
|
|
3786
|
+
if (!response.ok) {
|
|
3787
|
+
const body = await response.text();
|
|
3788
|
+
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
3789
|
+
}
|
|
3790
|
+
return await response.json();
|
|
3791
|
+
}
|
|
3792
|
+
/**
|
|
3793
|
+
* Poll {@link getRollout} until the rollout leaves `running`, then return
|
|
3794
|
+
* the terminal record — including `partial`/`failed` outcomes, whose
|
|
3795
|
+
* per-child `error` entries are results the caller inspects, not
|
|
3796
|
+
* exceptions. Throws {@link TimeoutError} on deadline or abort.
|
|
3797
|
+
*/
|
|
3798
|
+
async waitForRollout(rolloutId, options) {
|
|
3799
|
+
const timeoutMs = options?.timeoutMs ?? 36e4;
|
|
3800
|
+
const pollIntervalMs = options?.pollIntervalMs ?? 2e3;
|
|
3801
|
+
const startTime = Date.now();
|
|
3802
|
+
while (true) {
|
|
3803
|
+
if (options?.signal?.aborted) throw new TimeoutError(0, "Aborted");
|
|
3804
|
+
const record = await this.getRollout(rolloutId);
|
|
3805
|
+
if (record.status !== "running") return record;
|
|
3806
|
+
if (Date.now() - startTime > timeoutMs) throw new TimeoutError(timeoutMs, `Timed out waiting for rollout ${rolloutId} to settle`);
|
|
3807
|
+
await this.sleep(pollIntervalMs);
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
/**
|
|
4115
3811
|
* Stop the sandbox (keeps state for resume).
|
|
3812
|
+
*
|
|
3813
|
+
* The stop response carries the orchestrator's per-phase stop breakdown
|
|
3814
|
+
* (`stop_metering` / `container_kill` / `stop_complete` /
|
|
3815
|
+
* `stop_unaccounted`); after this resolves, {@link startupDiagnostics}
|
|
3816
|
+
* returns it (replacing the create breakdown).
|
|
4116
3817
|
*/
|
|
4117
3818
|
async stop() {
|
|
4118
3819
|
const response = await this.client.fetch(`/v1/sandboxes/${this.id}/stop`, { method: "POST" });
|
|
@@ -4120,7 +3821,9 @@ var SandboxInstance = class SandboxInstance {
|
|
|
4120
3821
|
const body = await response.text();
|
|
4121
3822
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
4122
3823
|
}
|
|
3824
|
+
const diagnostics = await this.readLifecycleDiagnostics(response);
|
|
4123
3825
|
await this.refresh();
|
|
3826
|
+
if (diagnostics) this.info.startupDiagnostics = diagnostics;
|
|
4124
3827
|
}
|
|
4125
3828
|
/**
|
|
4126
3829
|
* Resume a stopped sandbox.
|
|
@@ -4137,10 +3840,18 @@ var SandboxInstance = class SandboxInstance {
|
|
|
4137
3840
|
const body = await response.text();
|
|
4138
3841
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
4139
3842
|
}
|
|
3843
|
+
const diagnostics = await this.readLifecycleDiagnostics(response);
|
|
4140
3844
|
await this.refresh();
|
|
3845
|
+
if (diagnostics) this.info.startupDiagnostics = diagnostics;
|
|
4141
3846
|
}
|
|
4142
3847
|
/**
|
|
4143
3848
|
* Delete the sandbox permanently.
|
|
3849
|
+
*
|
|
3850
|
+
* The delete response carries the orchestrator's per-phase delete breakdown
|
|
3851
|
+
* (`container_remove` / `storage_binding_wait` / `cleanup_parallel` /
|
|
3852
|
+
* `delete_complete` / `delete_unaccounted`); after this resolves,
|
|
3853
|
+
* {@link startupDiagnostics} returns it for post-mortem reads on the
|
|
3854
|
+
* now-gone sandbox.
|
|
4144
3855
|
*/
|
|
4145
3856
|
async delete() {
|
|
4146
3857
|
const response = await this.client.fetch(`/v1/sandboxes/${this.id}`, { method: "DELETE" });
|
|
@@ -4148,6 +3859,17 @@ var SandboxInstance = class SandboxInstance {
|
|
|
4148
3859
|
const body = await response.text();
|
|
4149
3860
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
4150
3861
|
}
|
|
3862
|
+
const diagnostics = await this.readLifecycleDiagnostics(response);
|
|
3863
|
+
if (diagnostics) this.info.startupDiagnostics = diagnostics;
|
|
3864
|
+
}
|
|
3865
|
+
/**
|
|
3866
|
+
* Read the per-phase lifecycle breakdown off a stop/resume/delete response
|
|
3867
|
+
* body. Never throws: diagnostics are measurement data — a body that fails
|
|
3868
|
+
* to parse (older API, empty body) must not fail the lifecycle call.
|
|
3869
|
+
*/
|
|
3870
|
+
async readLifecycleDiagnostics(response) {
|
|
3871
|
+
const data = await response.json().catch(() => null);
|
|
3872
|
+
return data ? normalizeStartupDiagnostics(data.startupDiagnostics) : null;
|
|
4151
3873
|
}
|
|
4152
3874
|
/**
|
|
4153
3875
|
* keepAlive is intentionally unavailable until the API exposes timeout updates.
|
|
@@ -4732,6 +4454,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
4732
4454
|
* gates ProductTokenIssuer-issued JWTs — no new sidecar surface.
|
|
4733
4455
|
*/
|
|
4734
4456
|
async mintScopedToken(opts) {
|
|
4457
|
+
if ((opts.scope === "session" || opts.scope === "session-runtime") && !opts.sessionId) throw new ValidationError(`${opts.scope} scope requires a sessionId`);
|
|
4735
4458
|
const response = await this.client.fetch(`/v1/sandboxes/${encodeURIComponent(this.id)}/scoped-token`, {
|
|
4736
4459
|
method: "POST",
|
|
4737
4460
|
headers: { "Content-Type": "application/json" },
|
|
@@ -4772,7 +4495,7 @@ var SandboxInstance = class SandboxInstance {
|
|
|
4772
4495
|
const search = new URLSearchParams();
|
|
4773
4496
|
search.set("sessionId", id);
|
|
4774
4497
|
if (opts?.since) search.set("since", opts.since);
|
|
4775
|
-
const response = await this.runtimeFetch(`/agents/events
|
|
4498
|
+
const response = await this.runtimeFetch(`/agents/events?${search.toString()}`, { signal: opts?.signal });
|
|
4776
4499
|
if (!response.ok) {
|
|
4777
4500
|
const body = await response.text();
|
|
4778
4501
|
throw parseErrorResponse(response.status, body, void 0, response.headers);
|
|
@@ -5047,4 +4770,4 @@ function quoteForShell(value) {
|
|
|
5047
4770
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
5048
4771
|
}
|
|
5049
4772
|
//#endregion
|
|
5050
|
-
export {
|
|
4773
|
+
export { InteractiveSessionHandle as a, getSandboxEventText as c, exportTraceBundle as d, otelTraceIdForTangleTrace as f, parseSSEStream as g, encodeTextForWire as h, SandboxSession as i, normalizeConnection as l, encodePromptForWire as m, normalizeStartupDiagnostics as n, applySandboxEventText as o, toOtelJson as p, SandboxTaskSession as r, collectAgentResponseText as s, SandboxInstance as t, buildTraceExportPayload as u };
|