@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 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.CODEX_API_KEY?.trim() || env.OPENAI_API_KEY?.trim();
103
- if (!apiKey) {
104
- throw new Error(
105
- "Missing required environment variable: CODEX_API_KEY or OPENAI_API_KEY"
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: codexRequestSchema,
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
- "exec",
116
- "--json",
117
- "--yolo",
118
- "--skip-git-repo-check",
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 assertBrowserToolRelayCredentials(parsed) {
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
- assertBrowserToolRelayCredentials(input);
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 Sandbox.get({ sandboxId: parsed.sandbox_id }) : await (async () => {
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
- return Sandbox.create({
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.10",
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": {