@giselles-ai/sandbox-agent 0.1.10 → 0.1.12
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/dist/index.d.ts +31 -1
- package/dist/index.js +180 -23
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { Sandbox } from '@vercel/sandbox';
|
|
3
3
|
|
|
4
|
+
type AgentType = "codex" | "gemini";
|
|
5
|
+
declare class Agent {
|
|
6
|
+
private _type;
|
|
7
|
+
private _snapshotId;
|
|
8
|
+
private _pendingOps;
|
|
9
|
+
private constructor();
|
|
10
|
+
static create(type: AgentType, options: {
|
|
11
|
+
snapshotId: string;
|
|
12
|
+
}): Agent;
|
|
13
|
+
get type(): AgentType;
|
|
14
|
+
get snapshotId(): string;
|
|
15
|
+
get dirty(): boolean;
|
|
16
|
+
addFiles(files: Array<{
|
|
17
|
+
path: string;
|
|
18
|
+
content: Buffer;
|
|
19
|
+
}>): this;
|
|
20
|
+
runCommands(commands: Array<{
|
|
21
|
+
cmd: string;
|
|
22
|
+
args?: string[];
|
|
23
|
+
}>): this;
|
|
24
|
+
prepare(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
4
27
|
type BaseChatRequest = {
|
|
5
28
|
message: string;
|
|
6
29
|
session_id?: string;
|
|
@@ -38,11 +61,18 @@ declare const codexRequestSchema: z.ZodObject<{
|
|
|
38
61
|
message: z.ZodString;
|
|
39
62
|
session_id: z.ZodOptional<z.ZodString>;
|
|
40
63
|
sandbox_id: z.ZodOptional<z.ZodString>;
|
|
64
|
+
relay_session_id: z.ZodOptional<z.ZodString>;
|
|
65
|
+
relay_token: z.ZodOptional<z.ZodString>;
|
|
41
66
|
}, z.core.$strip>;
|
|
42
67
|
type CodexAgentRequest = z.infer<typeof codexRequestSchema>;
|
|
43
68
|
type CodexAgentOptions = {
|
|
44
69
|
snapshotId?: string;
|
|
45
70
|
env?: Record<string, string>;
|
|
71
|
+
tools?: {
|
|
72
|
+
browser?: {
|
|
73
|
+
relayUrl?: string;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
46
76
|
};
|
|
47
77
|
declare function createCodexAgent(options?: CodexAgentOptions): ChatAgent<CodexAgentRequest>;
|
|
48
78
|
|
|
@@ -71,4 +101,4 @@ type GeminiAgentOptions = {
|
|
|
71
101
|
};
|
|
72
102
|
declare function createGeminiAgent(options?: GeminiAgentOptions): ChatAgent<GeminiAgentRequest>;
|
|
73
103
|
|
|
74
|
-
export { type BaseChatRequest, type ChatAgent, type ChatCommand, type RunChatInput, type StdoutMapper, createCodexAgent, createCodexStdoutMapper, createGeminiAgent, runChat };
|
|
104
|
+
export { Agent, type BaseChatRequest, type ChatAgent, type ChatCommand, type RunChatInput, type StdoutMapper, createCodexAgent, createCodexStdoutMapper, createGeminiAgent, runChat };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,92 @@
|
|
|
1
|
+
// src/agent.ts
|
|
2
|
+
import { Sandbox } from "@vercel/sandbox";
|
|
3
|
+
var Agent = class _Agent {
|
|
4
|
+
_type;
|
|
5
|
+
_snapshotId;
|
|
6
|
+
_pendingOps = [];
|
|
7
|
+
constructor(type, snapshotId) {
|
|
8
|
+
this._type = type;
|
|
9
|
+
this._snapshotId = snapshotId;
|
|
10
|
+
}
|
|
11
|
+
static create(type, options) {
|
|
12
|
+
const trimmed = options.snapshotId.trim();
|
|
13
|
+
if (!trimmed) {
|
|
14
|
+
throw new Error("snapshotId is required.");
|
|
15
|
+
}
|
|
16
|
+
return new _Agent(type, trimmed);
|
|
17
|
+
}
|
|
18
|
+
get type() {
|
|
19
|
+
return this._type;
|
|
20
|
+
}
|
|
21
|
+
get snapshotId() {
|
|
22
|
+
return this._snapshotId;
|
|
23
|
+
}
|
|
24
|
+
get dirty() {
|
|
25
|
+
return this._pendingOps.length > 0;
|
|
26
|
+
}
|
|
27
|
+
addFiles(files) {
|
|
28
|
+
if (files.length === 0) {
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
this._pendingOps.push({
|
|
32
|
+
kind: "writeFiles",
|
|
33
|
+
files
|
|
34
|
+
});
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
runCommands(commands) {
|
|
38
|
+
for (const command of commands) {
|
|
39
|
+
this._pendingOps.push({
|
|
40
|
+
kind: "runCommand",
|
|
41
|
+
cmd: command.cmd,
|
|
42
|
+
args: command.args ?? []
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
async prepare() {
|
|
48
|
+
if (!this.dirty) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const ops = this._pendingOps;
|
|
52
|
+
const sandbox = await Sandbox.create({
|
|
53
|
+
source: { type: "snapshot", snapshotId: this._snapshotId }
|
|
54
|
+
});
|
|
55
|
+
console.log(
|
|
56
|
+
`[sandbox] created sandbox=${sandbox.sandboxId} from snapshot=${this._snapshotId}`
|
|
57
|
+
);
|
|
58
|
+
for (const op of ops) {
|
|
59
|
+
switch (op.kind) {
|
|
60
|
+
case "writeFiles":
|
|
61
|
+
await sandbox.writeFiles(op.files);
|
|
62
|
+
break;
|
|
63
|
+
case "runCommand":
|
|
64
|
+
await sandbox.runCommand(op.cmd, op.args);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const snapshot = await sandbox.snapshot();
|
|
69
|
+
this._snapshotId = snapshot.snapshotId;
|
|
70
|
+
this._pendingOps = [];
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
1
74
|
// src/agents/codex-agent.ts
|
|
75
|
+
import * as TOML from "@iarna/toml";
|
|
2
76
|
import { z } from "zod";
|
|
3
77
|
|
|
4
78
|
// src/agents/codex-mapper.ts
|
|
5
79
|
function mapEvent(event) {
|
|
6
80
|
const type = event.type;
|
|
81
|
+
if (type === "thread.started") {
|
|
82
|
+
return {
|
|
83
|
+
type: "init",
|
|
84
|
+
session_id: event.thread_id ?? void 0
|
|
85
|
+
};
|
|
86
|
+
}
|
|
7
87
|
if (type === "session.created") {
|
|
8
88
|
return {
|
|
9
89
|
type: "init",
|
|
10
|
-
session_id: event.id ?? void 0,
|
|
11
90
|
modelId: event.model ?? void 0
|
|
12
91
|
};
|
|
13
92
|
}
|
|
@@ -33,6 +112,18 @@ function mapEvent(event) {
|
|
|
33
112
|
content: typeof event.message === "string" ? event.message : JSON.stringify(event)
|
|
34
113
|
};
|
|
35
114
|
}
|
|
115
|
+
if (type === "item.completed") {
|
|
116
|
+
const item = event.item;
|
|
117
|
+
if (item?.type === "agent_message" && typeof item.text === "string") {
|
|
118
|
+
return {
|
|
119
|
+
type: "message",
|
|
120
|
+
role: "assistant",
|
|
121
|
+
content: item.text,
|
|
122
|
+
delta: false
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
36
127
|
if (type === "response.completed") {
|
|
37
128
|
return null;
|
|
38
129
|
}
|
|
@@ -84,10 +175,13 @@ function createCodexStdoutMapper() {
|
|
|
84
175
|
}
|
|
85
176
|
|
|
86
177
|
// src/agents/codex-agent.ts
|
|
178
|
+
var CODEX_CONFIG_PATH = "/home/vercel-sandbox/.codex/config.toml";
|
|
87
179
|
var codexRequestSchema = z.object({
|
|
88
180
|
message: z.string().min(1),
|
|
89
181
|
session_id: z.string().min(1).optional(),
|
|
90
|
-
sandbox_id: z.string().min(1).optional()
|
|
182
|
+
sandbox_id: z.string().min(1).optional(),
|
|
183
|
+
relay_session_id: z.string().min(1).optional(),
|
|
184
|
+
relay_token: z.string().min(1).optional()
|
|
91
185
|
});
|
|
92
186
|
function requiredEnv(env, name) {
|
|
93
187
|
const value = env[name]?.trim();
|
|
@@ -96,34 +190,91 @@ function requiredEnv(env, name) {
|
|
|
96
190
|
}
|
|
97
191
|
return value;
|
|
98
192
|
}
|
|
193
|
+
async function patchCodexConfigTransportEnv(sandbox, bridgeTransportEnv) {
|
|
194
|
+
const buffer = await sandbox.readFileToBuffer({
|
|
195
|
+
path: CODEX_CONFIG_PATH
|
|
196
|
+
});
|
|
197
|
+
if (!buffer) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`Codex config not found in sandbox at ${CODEX_CONFIG_PATH}. Ensure the snapshot contains a pre-configured config.toml.`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const config = TOML.parse(
|
|
203
|
+
new TextDecoder().decode(buffer)
|
|
204
|
+
);
|
|
205
|
+
if (config.mcp_servers) {
|
|
206
|
+
for (const server of Object.values(config.mcp_servers)) {
|
|
207
|
+
server.env = { ...server.env, ...bridgeTransportEnv };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
await sandbox.writeFiles([
|
|
211
|
+
{
|
|
212
|
+
path: CODEX_CONFIG_PATH,
|
|
213
|
+
content: Buffer.from(TOML.stringify(config))
|
|
214
|
+
}
|
|
215
|
+
]);
|
|
216
|
+
}
|
|
217
|
+
function createCodexRequestSchema(browserEnabled) {
|
|
218
|
+
if (!browserEnabled) {
|
|
219
|
+
return codexRequestSchema;
|
|
220
|
+
}
|
|
221
|
+
return codexRequestSchema.superRefine((value, ctx) => {
|
|
222
|
+
if (!value.relay_session_id) {
|
|
223
|
+
ctx.addIssue({
|
|
224
|
+
code: "custom",
|
|
225
|
+
path: ["relay_session_id"],
|
|
226
|
+
message: "relay_session_id is required when tools.browser is enabled."
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
if (!value.relay_token) {
|
|
230
|
+
ctx.addIssue({
|
|
231
|
+
code: "custom",
|
|
232
|
+
path: ["relay_token"],
|
|
233
|
+
message: "relay_token is required when tools.browser is enabled."
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
function assertBrowserToolRelayCredentials(parsed) {
|
|
239
|
+
if (!parsed.relay_session_id || !parsed.relay_token) {
|
|
240
|
+
throw new Error("relay_session_id and relay_token are required.");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
99
243
|
function createCodexAgent(options = {}) {
|
|
100
244
|
const env = options.env ?? {};
|
|
101
245
|
const snapshotId = options.snapshotId?.trim() || requiredEnv(env, "SANDBOX_SNAPSHOT_ID");
|
|
102
|
-
const apiKey = env
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
);
|
|
246
|
+
const apiKey = requiredEnv(env, "CODEX_API_KEY");
|
|
247
|
+
const browserToolEnabled = options.tools?.browser !== void 0;
|
|
248
|
+
const browserToolRelayUrl = options.tools?.browser?.relayUrl?.trim();
|
|
249
|
+
if (browserToolEnabled) {
|
|
250
|
+
requiredEnv(env, "BROWSER_TOOL_RELAY_URL");
|
|
251
|
+
requiredEnv(env, "BROWSER_TOOL_RELAY_SESSION_ID");
|
|
252
|
+
requiredEnv(env, "BROWSER_TOOL_RELAY_TOKEN");
|
|
253
|
+
}
|
|
254
|
+
if (browserToolEnabled && !browserToolRelayUrl) {
|
|
255
|
+
throw new Error("tools.browser.relayUrl is empty.");
|
|
107
256
|
}
|
|
108
257
|
return {
|
|
109
|
-
requestSchema:
|
|
258
|
+
requestSchema: createCodexRequestSchema(browserToolEnabled),
|
|
110
259
|
snapshotId,
|
|
111
260
|
async prepareSandbox(_input) {
|
|
261
|
+
if (!browserToolEnabled) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
requiredEnv(env, "VERCEL_OIDC_TOKEN");
|
|
265
|
+
assertBrowserToolRelayCredentials(_input.input);
|
|
266
|
+
await patchCodexConfigTransportEnv(_input.sandbox, env);
|
|
112
267
|
},
|
|
113
268
|
createCommand({ input }) {
|
|
114
|
-
const args = [
|
|
115
|
-
|
|
116
|
-
"
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
input.message
|
|
120
|
-
];
|
|
269
|
+
const args = ["exec"];
|
|
270
|
+
if (input.session_id) {
|
|
271
|
+
args.push("resume", input.session_id);
|
|
272
|
+
}
|
|
273
|
+
args.push("--json", "--yolo", "--skip-git-repo-check", input.message);
|
|
121
274
|
return {
|
|
122
275
|
cmd: "codex",
|
|
123
276
|
args,
|
|
124
|
-
env: {
|
|
125
|
-
OPENAI_API_KEY: apiKey
|
|
126
|
-
}
|
|
277
|
+
env: { CODEX_API_KEY: apiKey }
|
|
127
278
|
};
|
|
128
279
|
},
|
|
129
280
|
createStdoutMapper() {
|
|
@@ -198,7 +349,7 @@ function createGeminiRequestSchema(browserEnabled) {
|
|
|
198
349
|
}
|
|
199
350
|
});
|
|
200
351
|
}
|
|
201
|
-
function
|
|
352
|
+
function assertBrowserToolRelayCredentials2(parsed) {
|
|
202
353
|
if (!parsed.relay_session_id || !parsed.relay_token) {
|
|
203
354
|
throw new Error("relay_session_id and relay_token are required.");
|
|
204
355
|
}
|
|
@@ -224,7 +375,7 @@ function createGeminiAgent(options = {}) {
|
|
|
224
375
|
return;
|
|
225
376
|
}
|
|
226
377
|
requiredEnv2(env, "VERCEL_OIDC_TOKEN");
|
|
227
|
-
|
|
378
|
+
assertBrowserToolRelayCredentials2(input);
|
|
228
379
|
await patchGeminiSettingsTransportEnv(sandbox, env);
|
|
229
380
|
},
|
|
230
381
|
createCommand({ input }) {
|
|
@@ -252,7 +403,7 @@ function createGeminiAgent(options = {}) {
|
|
|
252
403
|
|
|
253
404
|
// src/chat-run.ts
|
|
254
405
|
import { Writable } from "stream";
|
|
255
|
-
import { Sandbox } from "@vercel/sandbox";
|
|
406
|
+
import { Sandbox as Sandbox2 } from "@vercel/sandbox";
|
|
256
407
|
function emitText(controller, text, encoder) {
|
|
257
408
|
if (text.length === 0) {
|
|
258
409
|
return;
|
|
@@ -306,19 +457,23 @@ function runChat(input) {
|
|
|
306
457
|
const mapper = input.agent.createStdoutMapper?.();
|
|
307
458
|
void (async () => {
|
|
308
459
|
try {
|
|
309
|
-
const sandbox = parsed.sandbox_id ? await
|
|
460
|
+
const sandbox = parsed.sandbox_id ? await Sandbox2.get({ sandboxId: parsed.sandbox_id }) : await (async () => {
|
|
310
461
|
const snapshotId = input.agent.snapshotId?.trim();
|
|
311
462
|
if (!snapshotId) {
|
|
312
463
|
throw new Error(
|
|
313
464
|
"Agent must provide snapshotId when sandbox_id is not provided."
|
|
314
465
|
);
|
|
315
466
|
}
|
|
316
|
-
|
|
467
|
+
const created = await Sandbox2.create({
|
|
317
468
|
source: {
|
|
318
469
|
type: "snapshot",
|
|
319
470
|
snapshotId
|
|
320
471
|
}
|
|
321
472
|
});
|
|
473
|
+
console.log(
|
|
474
|
+
`[sandbox] created sandbox=${created.sandboxId} from snapshot=${snapshotId}`
|
|
475
|
+
);
|
|
476
|
+
return created;
|
|
322
477
|
})();
|
|
323
478
|
enqueueEvent({ type: "sandbox", sandbox_id: sandbox.sandboxId });
|
|
324
479
|
await input.agent.prepareSandbox({
|
|
@@ -335,6 +490,7 @@ function runChat(input) {
|
|
|
335
490
|
stdout: new Writable({
|
|
336
491
|
write(chunk, _encoding, callback) {
|
|
337
492
|
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
493
|
+
console.log("[sandbox-agent] raw stdout:", text);
|
|
338
494
|
if (mapper) {
|
|
339
495
|
for (const line of mapper.push(text)) {
|
|
340
496
|
enqueueStdout(line);
|
|
@@ -382,6 +538,7 @@ function runChat(input) {
|
|
|
382
538
|
);
|
|
383
539
|
}
|
|
384
540
|
export {
|
|
541
|
+
Agent,
|
|
385
542
|
createCodexAgent,
|
|
386
543
|
createCodexStdoutMapper,
|
|
387
544
|
createGeminiAgent,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@giselles-ai/sandbox-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@vercel/sandbox": "1.6.0",
|
|
31
|
+
"@iarna/toml": "3.0.0",
|
|
31
32
|
"zod": "4.3.6"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|