sandbox-agent 0.0.1 → 0.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/dist/index.d.ts +745 -0
- package/dist/index.js +254 -0
- package/dist/index.js.map +1 -0
- package/dist/spawn-BT4YX7BC.js +168 -0
- package/dist/spawn-BT4YX7BC.js.map +1 -0
- package/package.json +34 -4
- package/index.js +0 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var API_PREFIX = "/v1";
|
|
3
|
+
var SandboxAgentError = class extends Error {
|
|
4
|
+
status;
|
|
5
|
+
problem;
|
|
6
|
+
response;
|
|
7
|
+
constructor(status, problem, response) {
|
|
8
|
+
super(problem?.title ?? `Request failed with status ${status}`);
|
|
9
|
+
this.name = "SandboxAgentError";
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.problem = problem;
|
|
12
|
+
this.response = response;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var SandboxAgent = class _SandboxAgent {
|
|
16
|
+
baseUrl;
|
|
17
|
+
token;
|
|
18
|
+
fetcher;
|
|
19
|
+
defaultHeaders;
|
|
20
|
+
spawnHandle;
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
23
|
+
this.token = options.token;
|
|
24
|
+
this.fetcher = options.fetch ?? globalThis.fetch;
|
|
25
|
+
this.defaultHeaders = options.headers;
|
|
26
|
+
if (!this.fetcher) {
|
|
27
|
+
throw new Error("Fetch API is not available; provide a fetch implementation.");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
static async connect(options) {
|
|
31
|
+
return new _SandboxAgent(options);
|
|
32
|
+
}
|
|
33
|
+
static async start(options = {}) {
|
|
34
|
+
const spawnOptions = normalizeSpawnOptions(options.spawn, true);
|
|
35
|
+
if (!spawnOptions.enabled) {
|
|
36
|
+
throw new Error("SandboxAgent.start requires spawn to be enabled.");
|
|
37
|
+
}
|
|
38
|
+
const { spawnSandboxAgent } = await import("./spawn-BT4YX7BC.js");
|
|
39
|
+
const handle = await spawnSandboxAgent(spawnOptions, options.fetch ?? globalThis.fetch);
|
|
40
|
+
const client = new _SandboxAgent({
|
|
41
|
+
baseUrl: handle.baseUrl,
|
|
42
|
+
token: handle.token,
|
|
43
|
+
fetch: options.fetch,
|
|
44
|
+
headers: options.headers
|
|
45
|
+
});
|
|
46
|
+
client.spawnHandle = handle;
|
|
47
|
+
return client;
|
|
48
|
+
}
|
|
49
|
+
async listAgents() {
|
|
50
|
+
return this.requestJson("GET", `${API_PREFIX}/agents`);
|
|
51
|
+
}
|
|
52
|
+
async getHealth() {
|
|
53
|
+
return this.requestJson("GET", `${API_PREFIX}/health`);
|
|
54
|
+
}
|
|
55
|
+
async installAgent(agent, request = {}) {
|
|
56
|
+
await this.requestJson("POST", `${API_PREFIX}/agents/${encodeURIComponent(agent)}/install`, {
|
|
57
|
+
body: request
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async getAgentModes(agent) {
|
|
61
|
+
return this.requestJson("GET", `${API_PREFIX}/agents/${encodeURIComponent(agent)}/modes`);
|
|
62
|
+
}
|
|
63
|
+
async createSession(sessionId, request) {
|
|
64
|
+
return this.requestJson("POST", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}`, {
|
|
65
|
+
body: request
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async listSessions() {
|
|
69
|
+
return this.requestJson("GET", `${API_PREFIX}/sessions`);
|
|
70
|
+
}
|
|
71
|
+
async postMessage(sessionId, request) {
|
|
72
|
+
await this.requestJson("POST", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/messages`, {
|
|
73
|
+
body: request
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
async getEvents(sessionId, query) {
|
|
77
|
+
return this.requestJson("GET", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/events`, {
|
|
78
|
+
query
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async getEventsSse(sessionId, query, signal) {
|
|
82
|
+
return this.requestRaw("GET", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/events/sse`, {
|
|
83
|
+
query,
|
|
84
|
+
accept: "text/event-stream",
|
|
85
|
+
signal
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async postMessageStream(sessionId, request, query, signal) {
|
|
89
|
+
return this.requestRaw("POST", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/messages/stream`, {
|
|
90
|
+
query,
|
|
91
|
+
body: request,
|
|
92
|
+
accept: "text/event-stream",
|
|
93
|
+
signal
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async *streamEvents(sessionId, query, signal) {
|
|
97
|
+
const response = await this.getEventsSse(sessionId, query, signal);
|
|
98
|
+
yield* this.parseSseStream(response);
|
|
99
|
+
}
|
|
100
|
+
async *streamTurn(sessionId, request, query, signal) {
|
|
101
|
+
const response = await this.postMessageStream(sessionId, request, query, signal);
|
|
102
|
+
yield* this.parseSseStream(response);
|
|
103
|
+
}
|
|
104
|
+
async replyQuestion(sessionId, questionId, request) {
|
|
105
|
+
await this.requestJson(
|
|
106
|
+
"POST",
|
|
107
|
+
`${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/questions/${encodeURIComponent(questionId)}/reply`,
|
|
108
|
+
{ body: request }
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
async rejectQuestion(sessionId, questionId) {
|
|
112
|
+
await this.requestJson(
|
|
113
|
+
"POST",
|
|
114
|
+
`${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/questions/${encodeURIComponent(questionId)}/reject`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
async replyPermission(sessionId, permissionId, request) {
|
|
118
|
+
await this.requestJson(
|
|
119
|
+
"POST",
|
|
120
|
+
`${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/permissions/${encodeURIComponent(permissionId)}/reply`,
|
|
121
|
+
{ body: request }
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
async terminateSession(sessionId) {
|
|
125
|
+
await this.requestJson("POST", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/terminate`);
|
|
126
|
+
}
|
|
127
|
+
async dispose() {
|
|
128
|
+
if (this.spawnHandle) {
|
|
129
|
+
await this.spawnHandle.dispose();
|
|
130
|
+
this.spawnHandle = void 0;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async requestJson(method, path, options = {}) {
|
|
134
|
+
const response = await this.requestRaw(method, path, {
|
|
135
|
+
query: options.query,
|
|
136
|
+
body: options.body,
|
|
137
|
+
headers: options.headers,
|
|
138
|
+
accept: options.accept ?? "application/json"
|
|
139
|
+
});
|
|
140
|
+
if (response.status === 204) {
|
|
141
|
+
return void 0;
|
|
142
|
+
}
|
|
143
|
+
const text = await response.text();
|
|
144
|
+
if (!text) {
|
|
145
|
+
return void 0;
|
|
146
|
+
}
|
|
147
|
+
return JSON.parse(text);
|
|
148
|
+
}
|
|
149
|
+
async requestRaw(method, path, options = {}) {
|
|
150
|
+
const url = this.buildUrl(path, options.query);
|
|
151
|
+
const headers = new Headers(this.defaultHeaders ?? void 0);
|
|
152
|
+
if (this.token) {
|
|
153
|
+
headers.set("Authorization", `Bearer ${this.token}`);
|
|
154
|
+
}
|
|
155
|
+
if (options.accept) {
|
|
156
|
+
headers.set("Accept", options.accept);
|
|
157
|
+
}
|
|
158
|
+
const init = { method, headers, signal: options.signal };
|
|
159
|
+
if (options.body !== void 0) {
|
|
160
|
+
headers.set("Content-Type", "application/json");
|
|
161
|
+
init.body = JSON.stringify(options.body);
|
|
162
|
+
}
|
|
163
|
+
if (options.headers) {
|
|
164
|
+
const extra = new Headers(options.headers);
|
|
165
|
+
extra.forEach((value, key) => headers.set(key, value));
|
|
166
|
+
}
|
|
167
|
+
const response = await this.fetcher(url, init);
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
const problem = await this.readProblem(response);
|
|
170
|
+
throw new SandboxAgentError(response.status, problem, response);
|
|
171
|
+
}
|
|
172
|
+
return response;
|
|
173
|
+
}
|
|
174
|
+
buildUrl(path, query) {
|
|
175
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
176
|
+
if (query) {
|
|
177
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
178
|
+
if (value === void 0 || value === null) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
url.searchParams.set(key, String(value));
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return url.toString();
|
|
185
|
+
}
|
|
186
|
+
async readProblem(response) {
|
|
187
|
+
try {
|
|
188
|
+
const text = await response.clone().text();
|
|
189
|
+
if (!text) {
|
|
190
|
+
return void 0;
|
|
191
|
+
}
|
|
192
|
+
return JSON.parse(text);
|
|
193
|
+
} catch {
|
|
194
|
+
return void 0;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async *parseSseStream(response) {
|
|
198
|
+
if (!response.body) {
|
|
199
|
+
throw new Error("SSE stream is not readable in this environment.");
|
|
200
|
+
}
|
|
201
|
+
const reader = response.body.getReader();
|
|
202
|
+
const decoder = new TextDecoder();
|
|
203
|
+
let buffer = "";
|
|
204
|
+
while (true) {
|
|
205
|
+
const { done, value } = await reader.read();
|
|
206
|
+
if (done) {
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
|
|
210
|
+
let index = buffer.indexOf("\n\n");
|
|
211
|
+
while (index !== -1) {
|
|
212
|
+
const chunk = buffer.slice(0, index);
|
|
213
|
+
buffer = buffer.slice(index + 2);
|
|
214
|
+
const dataLines = chunk.split("\n").filter((line) => line.startsWith("data:"));
|
|
215
|
+
if (dataLines.length > 0) {
|
|
216
|
+
const payload = dataLines.map((line) => line.slice(5).trim()).join("\n");
|
|
217
|
+
if (payload) {
|
|
218
|
+
yield JSON.parse(payload);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
index = buffer.indexOf("\n\n");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
var normalizeSpawnOptions = (spawn, defaultEnabled) => {
|
|
227
|
+
if (typeof spawn === "boolean") {
|
|
228
|
+
return { enabled: spawn };
|
|
229
|
+
}
|
|
230
|
+
if (spawn) {
|
|
231
|
+
return { enabled: spawn.enabled ?? defaultEnabled, ...spawn };
|
|
232
|
+
}
|
|
233
|
+
return { enabled: defaultEnabled };
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/inspector.ts
|
|
237
|
+
var INSPECTOR_URL = "https://inspect.sandboxagent.dev";
|
|
238
|
+
function buildInspectorUrl(options) {
|
|
239
|
+
const normalized = options.baseUrl.replace(/\/+$/, "");
|
|
240
|
+
const params = new URLSearchParams({ url: normalized });
|
|
241
|
+
if (options.token) {
|
|
242
|
+
params.set("token", options.token);
|
|
243
|
+
}
|
|
244
|
+
if (options.headers && Object.keys(options.headers).length > 0) {
|
|
245
|
+
params.set("headers", JSON.stringify(options.headers));
|
|
246
|
+
}
|
|
247
|
+
return `${INSPECTOR_URL}?${params.toString()}`;
|
|
248
|
+
}
|
|
249
|
+
export {
|
|
250
|
+
SandboxAgent,
|
|
251
|
+
SandboxAgentError,
|
|
252
|
+
buildInspectorUrl
|
|
253
|
+
};
|
|
254
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/inspector.ts"],"sourcesContent":["import type { SandboxAgentSpawnHandle, SandboxAgentSpawnOptions } from \"./spawn.ts\";\nimport type {\n AgentInstallRequest,\n AgentListResponse,\n AgentModesResponse,\n CreateSessionRequest,\n CreateSessionResponse,\n EventsQuery,\n EventsResponse,\n HealthResponse,\n MessageRequest,\n PermissionReplyRequest,\n ProblemDetails,\n QuestionReplyRequest,\n SessionListResponse,\n TurnStreamQuery,\n UniversalEvent,\n} from \"./types.ts\";\n\nconst API_PREFIX = \"/v1\";\n\nexport interface SandboxAgentConnectOptions {\n baseUrl: string;\n token?: string;\n fetch?: typeof fetch;\n headers?: HeadersInit;\n}\n\nexport interface SandboxAgentStartOptions {\n spawn?: SandboxAgentSpawnOptions | boolean;\n fetch?: typeof fetch;\n headers?: HeadersInit;\n}\n\nexport class SandboxAgentError extends Error {\n readonly status: number;\n readonly problem?: ProblemDetails;\n readonly response: Response;\n\n constructor(status: number, problem: ProblemDetails | undefined, response: Response) {\n super(problem?.title ?? `Request failed with status ${status}`);\n this.name = \"SandboxAgentError\";\n this.status = status;\n this.problem = problem;\n this.response = response;\n }\n}\n\ntype QueryValue = string | number | boolean | null | undefined;\n\ntype RequestOptions = {\n query?: Record<string, QueryValue>;\n body?: unknown;\n headers?: HeadersInit;\n accept?: string;\n signal?: AbortSignal;\n};\n\nexport class SandboxAgent {\n private readonly baseUrl: string;\n private readonly token?: string;\n private readonly fetcher: typeof fetch;\n private readonly defaultHeaders?: HeadersInit;\n private spawnHandle?: SandboxAgentSpawnHandle;\n\n private constructor(options: SandboxAgentConnectOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, \"\");\n this.token = options.token;\n this.fetcher = options.fetch ?? globalThis.fetch;\n this.defaultHeaders = options.headers;\n\n if (!this.fetcher) {\n throw new Error(\"Fetch API is not available; provide a fetch implementation.\");\n }\n }\n\n static async connect(options: SandboxAgentConnectOptions): Promise<SandboxAgent> {\n return new SandboxAgent(options);\n }\n\n static async start(options: SandboxAgentStartOptions = {}): Promise<SandboxAgent> {\n const spawnOptions = normalizeSpawnOptions(options.spawn, true);\n if (!spawnOptions.enabled) {\n throw new Error(\"SandboxAgent.start requires spawn to be enabled.\");\n }\n const { spawnSandboxAgent } = await import(\"./spawn.js\");\n const handle = await spawnSandboxAgent(spawnOptions, options.fetch ?? globalThis.fetch);\n const client = new SandboxAgent({\n baseUrl: handle.baseUrl,\n token: handle.token,\n fetch: options.fetch,\n headers: options.headers,\n });\n client.spawnHandle = handle;\n return client;\n }\n\n async listAgents(): Promise<AgentListResponse> {\n return this.requestJson(\"GET\", `${API_PREFIX}/agents`);\n }\n\n async getHealth(): Promise<HealthResponse> {\n return this.requestJson(\"GET\", `${API_PREFIX}/health`);\n }\n\n async installAgent(agent: string, request: AgentInstallRequest = {}): Promise<void> {\n await this.requestJson(\"POST\", `${API_PREFIX}/agents/${encodeURIComponent(agent)}/install`, {\n body: request,\n });\n }\n\n async getAgentModes(agent: string): Promise<AgentModesResponse> {\n return this.requestJson(\"GET\", `${API_PREFIX}/agents/${encodeURIComponent(agent)}/modes`);\n }\n\n async createSession(sessionId: string, request: CreateSessionRequest): Promise<CreateSessionResponse> {\n return this.requestJson(\"POST\", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}`, {\n body: request,\n });\n }\n\n async listSessions(): Promise<SessionListResponse> {\n return this.requestJson(\"GET\", `${API_PREFIX}/sessions`);\n }\n\n async postMessage(sessionId: string, request: MessageRequest): Promise<void> {\n await this.requestJson(\"POST\", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/messages`, {\n body: request,\n });\n }\n\n async getEvents(sessionId: string, query?: EventsQuery): Promise<EventsResponse> {\n return this.requestJson(\"GET\", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/events`, {\n query,\n });\n }\n\n async getEventsSse(sessionId: string, query?: EventsQuery, signal?: AbortSignal): Promise<Response> {\n return this.requestRaw(\"GET\", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/events/sse`, {\n query,\n accept: \"text/event-stream\",\n signal,\n });\n }\n\n async postMessageStream(\n sessionId: string,\n request: MessageRequest,\n query?: TurnStreamQuery,\n signal?: AbortSignal,\n ): Promise<Response> {\n return this.requestRaw(\"POST\", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/messages/stream`, {\n query,\n body: request,\n accept: \"text/event-stream\",\n signal,\n });\n }\n\n async *streamEvents(\n sessionId: string,\n query?: EventsQuery,\n signal?: AbortSignal,\n ): AsyncGenerator<UniversalEvent, void, void> {\n const response = await this.getEventsSse(sessionId, query, signal);\n yield* this.parseSseStream(response);\n }\n\n async *streamTurn(\n sessionId: string,\n request: MessageRequest,\n query?: TurnStreamQuery,\n signal?: AbortSignal,\n ): AsyncGenerator<UniversalEvent, void, void> {\n const response = await this.postMessageStream(sessionId, request, query, signal);\n yield* this.parseSseStream(response);\n }\n\n async replyQuestion(\n sessionId: string,\n questionId: string,\n request: QuestionReplyRequest,\n ): Promise<void> {\n await this.requestJson(\n \"POST\",\n `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/questions/${encodeURIComponent(questionId)}/reply`,\n { body: request },\n );\n }\n\n async rejectQuestion(sessionId: string, questionId: string): Promise<void> {\n await this.requestJson(\n \"POST\",\n `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/questions/${encodeURIComponent(questionId)}/reject`,\n );\n }\n\n async replyPermission(\n sessionId: string,\n permissionId: string,\n request: PermissionReplyRequest,\n ): Promise<void> {\n await this.requestJson(\n \"POST\",\n `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/permissions/${encodeURIComponent(permissionId)}/reply`,\n { body: request },\n );\n }\n\n async terminateSession(sessionId: string): Promise<void> {\n await this.requestJson(\"POST\", `${API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/terminate`);\n }\n\n async dispose(): Promise<void> {\n if (this.spawnHandle) {\n await this.spawnHandle.dispose();\n this.spawnHandle = undefined;\n }\n }\n\n private async requestJson<T>(method: string, path: string, options: RequestOptions = {}): Promise<T> {\n const response = await this.requestRaw(method, path, {\n query: options.query,\n body: options.body,\n headers: options.headers,\n accept: options.accept ?? \"application/json\",\n });\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n const text = await response.text();\n if (!text) {\n return undefined as T;\n }\n\n return JSON.parse(text) as T;\n }\n\n private async requestRaw(method: string, path: string, options: RequestOptions = {}): Promise<Response> {\n const url = this.buildUrl(path, options.query);\n const headers = new Headers(this.defaultHeaders ?? undefined);\n\n if (this.token) {\n headers.set(\"Authorization\", `Bearer ${this.token}`);\n }\n\n if (options.accept) {\n headers.set(\"Accept\", options.accept);\n }\n\n const init: RequestInit = { method, headers, signal: options.signal };\n if (options.body !== undefined) {\n headers.set(\"Content-Type\", \"application/json\");\n init.body = JSON.stringify(options.body);\n }\n\n if (options.headers) {\n const extra = new Headers(options.headers);\n extra.forEach((value, key) => headers.set(key, value));\n }\n\n const response = await this.fetcher(url, init);\n if (!response.ok) {\n const problem = await this.readProblem(response);\n throw new SandboxAgentError(response.status, problem, response);\n }\n\n return response;\n }\n\n private buildUrl(path: string, query?: Record<string, QueryValue>): string {\n const url = new URL(`${this.baseUrl}${path}`);\n if (query) {\n Object.entries(query).forEach(([key, value]) => {\n if (value === undefined || value === null) {\n return;\n }\n url.searchParams.set(key, String(value));\n });\n }\n return url.toString();\n }\n\n private async readProblem(response: Response): Promise<ProblemDetails | undefined> {\n try {\n const text = await response.clone().text();\n if (!text) {\n return undefined;\n }\n return JSON.parse(text) as ProblemDetails;\n } catch {\n return undefined;\n }\n }\n\n private async *parseSseStream(response: Response): AsyncGenerator<UniversalEvent, void, void> {\n if (!response.body) {\n throw new Error(\"SSE stream is not readable in this environment.\");\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n // Normalize CRLF to LF for consistent parsing\n buffer += decoder.decode(value, { stream: true }).replace(/\\r\\n/g, \"\\n\");\n let index = buffer.indexOf(\"\\n\\n\");\n while (index !== -1) {\n const chunk = buffer.slice(0, index);\n buffer = buffer.slice(index + 2);\n const dataLines = chunk\n .split(\"\\n\")\n .filter((line) => line.startsWith(\"data:\"));\n if (dataLines.length > 0) {\n const payload = dataLines\n .map((line) => line.slice(5).trim())\n .join(\"\\n\");\n if (payload) {\n yield JSON.parse(payload) as UniversalEvent;\n }\n }\n index = buffer.indexOf(\"\\n\\n\");\n }\n }\n }\n}\n\nconst normalizeSpawnOptions = (\n spawn: SandboxAgentSpawnOptions | boolean | undefined,\n defaultEnabled: boolean,\n): SandboxAgentSpawnOptions => {\n if (typeof spawn === \"boolean\") {\n return { enabled: spawn };\n }\n if (spawn) {\n return { enabled: spawn.enabled ?? defaultEnabled, ...spawn };\n }\n return { enabled: defaultEnabled };\n};\n","const INSPECTOR_URL = \"https://inspect.sandboxagent.dev\";\n\nexport interface InspectorUrlOptions {\n /**\n * Base URL of the sandbox-agent server.\n */\n baseUrl: string;\n /**\n * Optional bearer token for authentication.\n */\n token?: string;\n /**\n * Optional extra headers to pass to the sandbox-agent server.\n * Will be JSON-encoded in the URL.\n */\n headers?: Record<string, string>;\n}\n\n/**\n * Builds a URL to the sandbox-agent inspector UI with the given connection parameters.\n */\nexport function buildInspectorUrl(options: InspectorUrlOptions): string {\n const normalized = options.baseUrl.replace(/\\/+$/, \"\");\n const params = new URLSearchParams({ url: normalized });\n if (options.token) {\n params.set(\"token\", options.token);\n }\n if (options.headers && Object.keys(options.headers).length > 0) {\n params.set(\"headers\", JSON.stringify(options.headers));\n }\n return `${INSPECTOR_URL}?${params.toString()}`;\n}\n"],"mappings":";AAmBA,IAAM,aAAa;AAeZ,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,QAAgB,SAAqC,UAAoB;AACnF,UAAM,SAAS,SAAS,8BAA8B,MAAM,EAAE;AAC9D,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,UAAU;AACf,SAAK,WAAW;AAAA,EAClB;AACF;AAYO,IAAM,eAAN,MAAM,cAAa;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA,EAEA,YAAY,SAAqC;AACvD,SAAK,UAAU,QAAQ,QAAQ,QAAQ,OAAO,EAAE;AAChD,SAAK,QAAQ,QAAQ;AACrB,SAAK,UAAU,QAAQ,SAAS,WAAW;AAC3C,SAAK,iBAAiB,QAAQ;AAE9B,QAAI,CAAC,KAAK,SAAS;AACjB,YAAM,IAAI,MAAM,6DAA6D;AAAA,IAC/E;AAAA,EACF;AAAA,EAEA,aAAa,QAAQ,SAA4D;AAC/E,WAAO,IAAI,cAAa,OAAO;AAAA,EACjC;AAAA,EAEA,aAAa,MAAM,UAAoC,CAAC,GAA0B;AAChF,UAAM,eAAe,sBAAsB,QAAQ,OAAO,IAAI;AAC9D,QAAI,CAAC,aAAa,SAAS;AACzB,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACpE;AACA,UAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,qBAAY;AACvD,UAAM,SAAS,MAAM,kBAAkB,cAAc,QAAQ,SAAS,WAAW,KAAK;AACtF,UAAM,SAAS,IAAI,cAAa;AAAA,MAC9B,SAAS,OAAO;AAAA,MAChB,OAAO,OAAO;AAAA,MACd,OAAO,QAAQ;AAAA,MACf,SAAS,QAAQ;AAAA,IACnB,CAAC;AACD,WAAO,cAAc;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,aAAyC;AAC7C,WAAO,KAAK,YAAY,OAAO,GAAG,UAAU,SAAS;AAAA,EACvD;AAAA,EAEA,MAAM,YAAqC;AACzC,WAAO,KAAK,YAAY,OAAO,GAAG,UAAU,SAAS;AAAA,EACvD;AAAA,EAEA,MAAM,aAAa,OAAe,UAA+B,CAAC,GAAkB;AAClF,UAAM,KAAK,YAAY,QAAQ,GAAG,UAAU,WAAW,mBAAmB,KAAK,CAAC,YAAY;AAAA,MAC1F,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,cAAc,OAA4C;AAC9D,WAAO,KAAK,YAAY,OAAO,GAAG,UAAU,WAAW,mBAAmB,KAAK,CAAC,QAAQ;AAAA,EAC1F;AAAA,EAEA,MAAM,cAAc,WAAmB,SAA+D;AACpG,WAAO,KAAK,YAAY,QAAQ,GAAG,UAAU,aAAa,mBAAmB,SAAS,CAAC,IAAI;AAAA,MACzF,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,eAA6C;AACjD,WAAO,KAAK,YAAY,OAAO,GAAG,UAAU,WAAW;AAAA,EACzD;AAAA,EAEA,MAAM,YAAY,WAAmB,SAAwC;AAC3E,UAAM,KAAK,YAAY,QAAQ,GAAG,UAAU,aAAa,mBAAmB,SAAS,CAAC,aAAa;AAAA,MACjG,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAU,WAAmB,OAA8C;AAC/E,WAAO,KAAK,YAAY,OAAO,GAAG,UAAU,aAAa,mBAAmB,SAAS,CAAC,WAAW;AAAA,MAC/F;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAAa,WAAmB,OAAqB,QAAyC;AAClG,WAAO,KAAK,WAAW,OAAO,GAAG,UAAU,aAAa,mBAAmB,SAAS,CAAC,eAAe;AAAA,MAClG;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,kBACJ,WACA,SACA,OACA,QACmB;AACnB,WAAO,KAAK,WAAW,QAAQ,GAAG,UAAU,aAAa,mBAAmB,SAAS,CAAC,oBAAoB;AAAA,MACxG;AAAA,MACA,MAAM;AAAA,MACN,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,OAAO,aACL,WACA,OACA,QAC4C;AAC5C,UAAM,WAAW,MAAM,KAAK,aAAa,WAAW,OAAO,MAAM;AACjE,WAAO,KAAK,eAAe,QAAQ;AAAA,EACrC;AAAA,EAEA,OAAO,WACL,WACA,SACA,OACA,QAC4C;AAC5C,UAAM,WAAW,MAAM,KAAK,kBAAkB,WAAW,SAAS,OAAO,MAAM;AAC/E,WAAO,KAAK,eAAe,QAAQ;AAAA,EACrC;AAAA,EAEA,MAAM,cACJ,WACA,YACA,SACe;AACf,UAAM,KAAK;AAAA,MACT;AAAA,MACA,GAAG,UAAU,aAAa,mBAAmB,SAAS,CAAC,cAAc,mBAAmB,UAAU,CAAC;AAAA,MACnG,EAAE,MAAM,QAAQ;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,WAAmB,YAAmC;AACzE,UAAM,KAAK;AAAA,MACT;AAAA,MACA,GAAG,UAAU,aAAa,mBAAmB,SAAS,CAAC,cAAc,mBAAmB,UAAU,CAAC;AAAA,IACrG;AAAA,EACF;AAAA,EAEA,MAAM,gBACJ,WACA,cACA,SACe;AACf,UAAM,KAAK;AAAA,MACT;AAAA,MACA,GAAG,UAAU,aAAa,mBAAmB,SAAS,CAAC,gBAAgB,mBAAmB,YAAY,CAAC;AAAA,MACvG,EAAE,MAAM,QAAQ;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,MAAM,iBAAiB,WAAkC;AACvD,UAAM,KAAK,YAAY,QAAQ,GAAG,UAAU,aAAa,mBAAmB,SAAS,CAAC,YAAY;AAAA,EACpG;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,aAAa;AACpB,YAAM,KAAK,YAAY,QAAQ;AAC/B,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,MAAc,YAAe,QAAgB,MAAc,UAA0B,CAAC,GAAe;AACnG,UAAM,WAAW,MAAM,KAAK,WAAW,QAAQ,MAAM;AAAA,MACnD,OAAO,QAAQ;AAAA,MACf,MAAM,QAAQ;AAAA,MACd,SAAS,QAAQ;AAAA,MACjB,QAAQ,QAAQ,UAAU;AAAA,IAC5B,CAAC;AAED,QAAI,SAAS,WAAW,KAAK;AAC3B,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,IACT;AAEA,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB;AAAA,EAEA,MAAc,WAAW,QAAgB,MAAc,UAA0B,CAAC,GAAsB;AACtG,UAAM,MAAM,KAAK,SAAS,MAAM,QAAQ,KAAK;AAC7C,UAAM,UAAU,IAAI,QAAQ,KAAK,kBAAkB,MAAS;AAE5D,QAAI,KAAK,OAAO;AACd,cAAQ,IAAI,iBAAiB,UAAU,KAAK,KAAK,EAAE;AAAA,IACrD;AAEA,QAAI,QAAQ,QAAQ;AAClB,cAAQ,IAAI,UAAU,QAAQ,MAAM;AAAA,IACtC;AAEA,UAAM,OAAoB,EAAE,QAAQ,SAAS,QAAQ,QAAQ,OAAO;AACpE,QAAI,QAAQ,SAAS,QAAW;AAC9B,cAAQ,IAAI,gBAAgB,kBAAkB;AAC9C,WAAK,OAAO,KAAK,UAAU,QAAQ,IAAI;AAAA,IACzC;AAEA,QAAI,QAAQ,SAAS;AACnB,YAAM,QAAQ,IAAI,QAAQ,QAAQ,OAAO;AACzC,YAAM,QAAQ,CAAC,OAAO,QAAQ,QAAQ,IAAI,KAAK,KAAK,CAAC;AAAA,IACvD;AAEA,UAAM,WAAW,MAAM,KAAK,QAAQ,KAAK,IAAI;AAC7C,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,UAAU,MAAM,KAAK,YAAY,QAAQ;AAC/C,YAAM,IAAI,kBAAkB,SAAS,QAAQ,SAAS,QAAQ;AAAA,IAChE;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,SAAS,MAAc,OAA4C;AACzE,UAAM,MAAM,IAAI,IAAI,GAAG,KAAK,OAAO,GAAG,IAAI,EAAE;AAC5C,QAAI,OAAO;AACT,aAAO,QAAQ,KAAK,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAC9C,YAAI,UAAU,UAAa,UAAU,MAAM;AACzC;AAAA,QACF;AACA,YAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,MACzC,CAAC;AAAA,IACH;AACA,WAAO,IAAI,SAAS;AAAA,EACtB;AAAA,EAEA,MAAc,YAAY,UAAyD;AACjF,QAAI;AACF,YAAM,OAAO,MAAM,SAAS,MAAM,EAAE,KAAK;AACzC,UAAI,CAAC,MAAM;AACT,eAAO;AAAA,MACT;AACA,aAAO,KAAK,MAAM,IAAI;AAAA,IACxB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,OAAe,eAAe,UAAgE;AAC5F,QAAI,CAAC,SAAS,MAAM;AAClB,YAAM,IAAI,MAAM,iDAAiD;AAAA,IACnE;AAEA,UAAM,SAAS,SAAS,KAAK,UAAU;AACvC,UAAM,UAAU,IAAI,YAAY;AAChC,QAAI,SAAS;AAEb,WAAO,MAAM;AACX,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,UAAI,MAAM;AACR;AAAA,MACF;AAEA,gBAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC,EAAE,QAAQ,SAAS,IAAI;AACvE,UAAI,QAAQ,OAAO,QAAQ,MAAM;AACjC,aAAO,UAAU,IAAI;AACnB,cAAM,QAAQ,OAAO,MAAM,GAAG,KAAK;AACnC,iBAAS,OAAO,MAAM,QAAQ,CAAC;AAC/B,cAAM,YAAY,MACf,MAAM,IAAI,EACV,OAAO,CAAC,SAAS,KAAK,WAAW,OAAO,CAAC;AAC5C,YAAI,UAAU,SAAS,GAAG;AACxB,gBAAM,UAAU,UACb,IAAI,CAAC,SAAS,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,EAClC,KAAK,IAAI;AACZ,cAAI,SAAS;AACX,kBAAM,KAAK,MAAM,OAAO;AAAA,UAC1B;AAAA,QACF;AACA,gBAAQ,OAAO,QAAQ,MAAM;AAAA,MAC/B;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAM,wBAAwB,CAC5B,OACA,mBAC6B;AAC7B,MAAI,OAAO,UAAU,WAAW;AAC9B,WAAO,EAAE,SAAS,MAAM;AAAA,EAC1B;AACA,MAAI,OAAO;AACT,WAAO,EAAE,SAAS,MAAM,WAAW,gBAAgB,GAAG,MAAM;AAAA,EAC9D;AACA,SAAO,EAAE,SAAS,eAAe;AACnC;;;ACzVA,IAAM,gBAAgB;AAqBf,SAAS,kBAAkB,SAAsC;AACtE,QAAM,aAAa,QAAQ,QAAQ,QAAQ,QAAQ,EAAE;AACrD,QAAM,SAAS,IAAI,gBAAgB,EAAE,KAAK,WAAW,CAAC;AACtD,MAAI,QAAQ,OAAO;AACjB,WAAO,IAAI,SAAS,QAAQ,KAAK;AAAA,EACnC;AACA,MAAI,QAAQ,WAAW,OAAO,KAAK,QAAQ,OAAO,EAAE,SAAS,GAAG;AAC9D,WAAO,IAAI,WAAW,KAAK,UAAU,QAAQ,OAAO,CAAC;AAAA,EACvD;AACA,SAAO,GAAG,aAAa,IAAI,OAAO,SAAS,CAAC;AAC9C;","names":[]}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// src/spawn.ts
|
|
2
|
+
var PLATFORM_PACKAGES = {
|
|
3
|
+
"darwin-arm64": "@sandbox-agent/cli-darwin-arm64",
|
|
4
|
+
"darwin-x64": "@sandbox-agent/cli-darwin-x64",
|
|
5
|
+
"linux-x64": "@sandbox-agent/cli-linux-x64",
|
|
6
|
+
"win32-x64": "@sandbox-agent/cli-win32-x64"
|
|
7
|
+
};
|
|
8
|
+
function isNodeRuntime() {
|
|
9
|
+
return typeof process !== "undefined" && !!process.versions?.node;
|
|
10
|
+
}
|
|
11
|
+
async function spawnSandboxAgent(options, fetcher) {
|
|
12
|
+
if (!isNodeRuntime()) {
|
|
13
|
+
throw new Error("Autospawn requires a Node.js runtime.");
|
|
14
|
+
}
|
|
15
|
+
const {
|
|
16
|
+
spawn
|
|
17
|
+
} = await import("child_process");
|
|
18
|
+
const crypto = await import("crypto");
|
|
19
|
+
const fs = await import("fs");
|
|
20
|
+
const path = await import("path");
|
|
21
|
+
const net = await import("net");
|
|
22
|
+
const { createRequire } = await import("module");
|
|
23
|
+
const bindHost = options.host ?? "127.0.0.1";
|
|
24
|
+
const port = options.port ?? await getFreePort(net, bindHost);
|
|
25
|
+
const connectHost = bindHost === "0.0.0.0" || bindHost === "::" ? "127.0.0.1" : bindHost;
|
|
26
|
+
const token = options.token ?? crypto.randomBytes(24).toString("hex");
|
|
27
|
+
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
28
|
+
const logMode = options.log ?? "inherit";
|
|
29
|
+
const binaryPath = options.binaryPath ?? resolveBinaryFromEnv(fs, path) ?? resolveBinaryFromCliPackage(createRequire(import.meta.url), path, fs) ?? resolveBinaryFromPath(fs, path);
|
|
30
|
+
if (!binaryPath) {
|
|
31
|
+
throw new Error("sandbox-agent binary not found. Install @sandbox-agent/cli or set SANDBOX_AGENT_BIN.");
|
|
32
|
+
}
|
|
33
|
+
const stdio = logMode === "inherit" ? "inherit" : logMode === "silent" ? "ignore" : "pipe";
|
|
34
|
+
const args = ["server", "--host", bindHost, "--port", String(port), "--token", token];
|
|
35
|
+
const child = spawn(binaryPath, args, {
|
|
36
|
+
stdio,
|
|
37
|
+
env: {
|
|
38
|
+
...process.env,
|
|
39
|
+
...options.env ?? {}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const cleanup = registerProcessCleanup(child);
|
|
43
|
+
const baseUrl = `http://${connectHost}:${port}`;
|
|
44
|
+
const ready = waitForHealth(baseUrl, fetcher ?? globalThis.fetch, timeoutMs, child, token);
|
|
45
|
+
await ready;
|
|
46
|
+
const dispose = async () => {
|
|
47
|
+
if (child.exitCode !== null) {
|
|
48
|
+
cleanup.dispose();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
child.kill("SIGTERM");
|
|
52
|
+
const exited = await waitForExit(child, 5e3);
|
|
53
|
+
if (!exited) {
|
|
54
|
+
child.kill("SIGKILL");
|
|
55
|
+
}
|
|
56
|
+
cleanup.dispose();
|
|
57
|
+
};
|
|
58
|
+
return { baseUrl, token, child, dispose };
|
|
59
|
+
}
|
|
60
|
+
function resolveBinaryFromEnv(fs, path) {
|
|
61
|
+
const value = process.env.SANDBOX_AGENT_BIN;
|
|
62
|
+
if (!value) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const resolved = path.resolve(value);
|
|
66
|
+
if (fs.existsSync(resolved)) {
|
|
67
|
+
return resolved;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
function resolveBinaryFromCliPackage(require2, path, fs) {
|
|
72
|
+
const key = `${process.platform}-${process.arch}`;
|
|
73
|
+
const pkg = PLATFORM_PACKAGES[key];
|
|
74
|
+
if (!pkg) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const pkgPath = require2.resolve(`${pkg}/package.json`);
|
|
79
|
+
const bin = process.platform === "win32" ? "sandbox-agent.exe" : "sandbox-agent";
|
|
80
|
+
const resolved = path.join(path.dirname(pkgPath), "bin", bin);
|
|
81
|
+
return fs.existsSync(resolved) ? resolved : null;
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function resolveBinaryFromPath(fs, path) {
|
|
87
|
+
const pathEnv = process.env.PATH ?? "";
|
|
88
|
+
const separator = process.platform === "win32" ? ";" : ":";
|
|
89
|
+
const candidates = pathEnv.split(separator).filter(Boolean);
|
|
90
|
+
const bin = process.platform === "win32" ? "sandbox-agent.exe" : "sandbox-agent";
|
|
91
|
+
for (const dir of candidates) {
|
|
92
|
+
const resolved = path.join(dir, bin);
|
|
93
|
+
if (fs.existsSync(resolved)) {
|
|
94
|
+
return resolved;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
async function getFreePort(net, host) {
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const server = net.createServer();
|
|
102
|
+
server.unref();
|
|
103
|
+
server.on("error", reject);
|
|
104
|
+
server.listen(0, host, () => {
|
|
105
|
+
const address = server.address();
|
|
106
|
+
server.close(() => resolve(address.port));
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async function waitForHealth(baseUrl, fetcher, timeoutMs, child, token) {
|
|
111
|
+
if (!fetcher) {
|
|
112
|
+
throw new Error("Fetch API is not available; provide a fetch implementation.");
|
|
113
|
+
}
|
|
114
|
+
const start = Date.now();
|
|
115
|
+
let lastError;
|
|
116
|
+
while (Date.now() - start < timeoutMs) {
|
|
117
|
+
if (child.exitCode !== null) {
|
|
118
|
+
throw new Error("sandbox-agent exited before becoming healthy.");
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const response = await fetcher(`${baseUrl}/v1/health`, {
|
|
122
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
123
|
+
});
|
|
124
|
+
if (response.ok) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
lastError = `status ${response.status}`;
|
|
128
|
+
} catch (err) {
|
|
129
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
130
|
+
}
|
|
131
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
132
|
+
}
|
|
133
|
+
throw new Error(`Timed out waiting for sandbox-agent health (${lastError ?? "unknown error"}).`);
|
|
134
|
+
}
|
|
135
|
+
async function waitForExit(child, timeoutMs) {
|
|
136
|
+
if (child.exitCode !== null) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
return new Promise((resolve) => {
|
|
140
|
+
const timer = setTimeout(() => resolve(false), timeoutMs);
|
|
141
|
+
child.once("exit", () => {
|
|
142
|
+
clearTimeout(timer);
|
|
143
|
+
resolve(true);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function registerProcessCleanup(child) {
|
|
148
|
+
const handler = () => {
|
|
149
|
+
if (child.exitCode === null) {
|
|
150
|
+
child.kill("SIGTERM");
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
process.once("exit", handler);
|
|
154
|
+
process.once("SIGINT", handler);
|
|
155
|
+
process.once("SIGTERM", handler);
|
|
156
|
+
return {
|
|
157
|
+
dispose: () => {
|
|
158
|
+
process.off("exit", handler);
|
|
159
|
+
process.off("SIGINT", handler);
|
|
160
|
+
process.off("SIGTERM", handler);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export {
|
|
165
|
+
isNodeRuntime,
|
|
166
|
+
spawnSandboxAgent
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=spawn-BT4YX7BC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/spawn.ts"],"sourcesContent":["import type { ChildProcess } from \"node:child_process\";\nimport type { AddressInfo } from \"node:net\";\n\nexport type SandboxAgentSpawnLogMode = \"inherit\" | \"pipe\" | \"silent\";\n\nexport type SandboxAgentSpawnOptions = {\n enabled?: boolean;\n host?: string;\n port?: number;\n token?: string;\n binaryPath?: string;\n timeoutMs?: number;\n log?: SandboxAgentSpawnLogMode;\n env?: Record<string, string>;\n};\n\nexport type SandboxAgentSpawnHandle = {\n baseUrl: string;\n token: string;\n child: ChildProcess;\n dispose: () => Promise<void>;\n};\n\nconst PLATFORM_PACKAGES: Record<string, string> = {\n \"darwin-arm64\": \"@sandbox-agent/cli-darwin-arm64\",\n \"darwin-x64\": \"@sandbox-agent/cli-darwin-x64\",\n \"linux-x64\": \"@sandbox-agent/cli-linux-x64\",\n \"win32-x64\": \"@sandbox-agent/cli-win32-x64\",\n};\n\nexport function isNodeRuntime(): boolean {\n return typeof process !== \"undefined\" && !!process.versions?.node;\n}\n\nexport async function spawnSandboxAgent(\n options: SandboxAgentSpawnOptions,\n fetcher?: typeof fetch,\n): Promise<SandboxAgentSpawnHandle> {\n if (!isNodeRuntime()) {\n throw new Error(\"Autospawn requires a Node.js runtime.\");\n }\n\n const {\n spawn,\n } = await import(\"node:child_process\");\n const crypto = await import(\"node:crypto\");\n const fs = await import(\"node:fs\");\n const path = await import(\"node:path\");\n const net = await import(\"node:net\");\n const { createRequire } = await import(\"node:module\");\n\n const bindHost = options.host ?? \"127.0.0.1\";\n const port = options.port ?? (await getFreePort(net, bindHost));\n const connectHost = bindHost === \"0.0.0.0\" || bindHost === \"::\" ? \"127.0.0.1\" : bindHost;\n const token = options.token ?? crypto.randomBytes(24).toString(\"hex\");\n const timeoutMs = options.timeoutMs ?? 15_000;\n const logMode: SandboxAgentSpawnLogMode = options.log ?? \"inherit\";\n\n const binaryPath =\n options.binaryPath ??\n resolveBinaryFromEnv(fs, path) ??\n resolveBinaryFromCliPackage(createRequire(import.meta.url), path, fs) ??\n resolveBinaryFromPath(fs, path);\n\n if (!binaryPath) {\n throw new Error(\"sandbox-agent binary not found. Install @sandbox-agent/cli or set SANDBOX_AGENT_BIN.\");\n }\n\n const stdio = logMode === \"inherit\" ? \"inherit\" : logMode === \"silent\" ? \"ignore\" : \"pipe\";\n const args = [\"server\", \"--host\", bindHost, \"--port\", String(port), \"--token\", token];\n const child = spawn(binaryPath, args, {\n stdio,\n env: {\n ...process.env,\n ...(options.env ?? {}),\n },\n });\n const cleanup = registerProcessCleanup(child);\n\n const baseUrl = `http://${connectHost}:${port}`;\n const ready = waitForHealth(baseUrl, fetcher ?? globalThis.fetch, timeoutMs, child, token);\n\n await ready;\n\n const dispose = async () => {\n if (child.exitCode !== null) {\n cleanup.dispose();\n return;\n }\n child.kill(\"SIGTERM\");\n const exited = await waitForExit(child, 5_000);\n if (!exited) {\n child.kill(\"SIGKILL\");\n }\n cleanup.dispose();\n };\n\n return { baseUrl, token, child, dispose };\n}\n\nfunction resolveBinaryFromEnv(fs: typeof import(\"node:fs\"), path: typeof import(\"node:path\")): string | null {\n const value = process.env.SANDBOX_AGENT_BIN;\n if (!value) {\n return null;\n }\n const resolved = path.resolve(value);\n if (fs.existsSync(resolved)) {\n return resolved;\n }\n return null;\n}\n\nfunction resolveBinaryFromCliPackage(\n require: ReturnType<typeof import(\"node:module\").createRequire>,\n path: typeof import(\"node:path\"),\n fs: typeof import(\"node:fs\"),\n): string | null {\n const key = `${process.platform}-${process.arch}`;\n const pkg = PLATFORM_PACKAGES[key];\n if (!pkg) {\n return null;\n }\n try {\n const pkgPath = require.resolve(`${pkg}/package.json`);\n const bin = process.platform === \"win32\" ? \"sandbox-agent.exe\" : \"sandbox-agent\";\n const resolved = path.join(path.dirname(pkgPath), \"bin\", bin);\n return fs.existsSync(resolved) ? resolved : null;\n } catch {\n return null;\n }\n}\n\nfunction resolveBinaryFromPath(fs: typeof import(\"node:fs\"), path: typeof import(\"node:path\")): string | null {\n const pathEnv = process.env.PATH ?? \"\";\n const separator = process.platform === \"win32\" ? \";\" : \":\";\n const candidates = pathEnv.split(separator).filter(Boolean);\n const bin = process.platform === \"win32\" ? \"sandbox-agent.exe\" : \"sandbox-agent\";\n for (const dir of candidates) {\n const resolved = path.join(dir, bin);\n if (fs.existsSync(resolved)) {\n return resolved;\n }\n }\n return null;\n}\n\nasync function getFreePort(net: typeof import(\"node:net\"), host: string): Promise<number> {\n return new Promise((resolve, reject) => {\n const server = net.createServer();\n server.unref();\n server.on(\"error\", reject);\n server.listen(0, host, () => {\n const address = server.address() as AddressInfo;\n server.close(() => resolve(address.port));\n });\n });\n}\n\nasync function waitForHealth(\n baseUrl: string,\n fetcher: typeof fetch | undefined,\n timeoutMs: number,\n child: ChildProcess,\n token: string,\n): Promise<void> {\n if (!fetcher) {\n throw new Error(\"Fetch API is not available; provide a fetch implementation.\");\n }\n const start = Date.now();\n let lastError: string | undefined;\n\n while (Date.now() - start < timeoutMs) {\n if (child.exitCode !== null) {\n throw new Error(\"sandbox-agent exited before becoming healthy.\");\n }\n try {\n const response = await fetcher(`${baseUrl}/v1/health`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (response.ok) {\n return;\n }\n lastError = `status ${response.status}`;\n } catch (err) {\n lastError = err instanceof Error ? err.message : String(err);\n }\n await new Promise((resolve) => setTimeout(resolve, 200));\n }\n\n throw new Error(`Timed out waiting for sandbox-agent health (${lastError ?? \"unknown error\"}).`);\n}\n\nasync function waitForExit(child: ChildProcess, timeoutMs: number): Promise<boolean> {\n if (child.exitCode !== null) {\n return true;\n }\n return new Promise((resolve) => {\n const timer = setTimeout(() => resolve(false), timeoutMs);\n child.once(\"exit\", () => {\n clearTimeout(timer);\n resolve(true);\n });\n });\n}\n\nfunction registerProcessCleanup(child: ChildProcess): { dispose: () => void } {\n const handler = () => {\n if (child.exitCode === null) {\n child.kill(\"SIGTERM\");\n }\n };\n\n process.once(\"exit\", handler);\n process.once(\"SIGINT\", handler);\n process.once(\"SIGTERM\", handler);\n\n return {\n dispose: () => {\n process.off(\"exit\", handler);\n process.off(\"SIGINT\", handler);\n process.off(\"SIGTERM\", handler);\n },\n };\n}\n"],"mappings":"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","names":["require"]}
|
package/package.json
CHANGED
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sandbox-agent",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"description": "Universal API for automatic coding agents in sandboxes.
|
|
3
|
+
"version": "0.1.0-rc.1",
|
|
4
|
+
"description": "Universal API for automatic coding agents in sandboxes. Supprots Claude Code, Codex, OpenCode, and Amp.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/rivet-dev/sandbox-agent"
|
|
9
9
|
},
|
|
10
|
-
"
|
|
11
|
-
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22.0.0",
|
|
24
|
+
"openapi-typescript": "^6.7.0",
|
|
25
|
+
"tsup": "^8.0.0",
|
|
26
|
+
"typescript": "^5.7.0",
|
|
27
|
+
"vitest": "^3.0.0"
|
|
28
|
+
},
|
|
29
|
+
"optionalDependencies": {
|
|
30
|
+
"@sandbox-agent/cli": "0.1.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"generate:openapi": "cargo check -p sandbox-agent-openapi-gen && cargo run -p sandbox-agent-openapi-gen -- --out ../../docs/openapi.json",
|
|
34
|
+
"generate:types": "openapi-typescript ../../docs/openapi.json -o src/generated/openapi.ts",
|
|
35
|
+
"generate": "pnpm run generate:openapi && pnpm run generate:types",
|
|
36
|
+
"build": "if [ -z \"$SKIP_OPENAPI_GEN\" ]; then pnpm run generate:openapi; fi && pnpm run generate:types && tsup",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:watch": "vitest"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = {};
|