@giselles-ai/sandbox-agent-core 0.1.2 → 0.1.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +37 -37
  2. package/dist/index.js +366 -369
  3. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -1,54 +1,54 @@
1
- import { BridgeRequest, BridgeResponse, BridgeErrorCode } from '@giselles-ai/browser-tool';
2
- import Redis from 'ioredis';
3
1
  import { z } from 'zod';
2
+ import { RelayRequest, RelayResponse, RelayErrorCode } from '@giselles-ai/browser-tool';
3
+ import Redis from 'ioredis';
4
+
5
+ declare const requestSchema: z.ZodObject<{
6
+ message: z.ZodString;
7
+ session_id: z.ZodOptional<z.ZodString>;
8
+ sandbox_id: z.ZodOptional<z.ZodString>;
9
+ relay_session_id: z.ZodString;
10
+ relay_token: z.ZodString;
11
+ }, z.core.$strip>;
12
+ type GeminiChatRouteOptions = {
13
+ requestParser?: typeof requestSchema;
14
+ };
15
+ declare function createGeminiChatHandler(_options?: GeminiChatRouteOptions): (request: Request) => Promise<Response>;
16
+
17
+ declare function createRelayHandler(): {
18
+ GET: (request: Request) => Promise<Response>;
19
+ POST: (request: Request) => Promise<Response>;
20
+ };
4
21
 
5
- declare const BRIDGE_SSE_KEEPALIVE_INTERVAL_MS: number;
22
+ declare const RELAY_SSE_KEEPALIVE_INTERVAL_MS: number;
6
23
  declare global {
7
- var __geminiBrowserToolBridgeRedis: Redis | undefined;
24
+ var __browserToolRelayRedis: Redis | undefined;
8
25
  }
9
- declare class BridgeBrokerError extends Error {
10
- readonly code: BridgeErrorCode;
26
+ declare class RelayStoreError extends Error {
27
+ readonly code: RelayErrorCode;
11
28
  readonly status: number;
12
- constructor(code: BridgeErrorCode, message: string, status: number);
29
+ constructor(code: RelayErrorCode, message: string, status: number);
13
30
  }
14
- declare function createBridgeSubscriber(): Redis;
15
- declare function bridgeRequestChannel(sessionId: string): string;
16
- declare function createBridgeSession(): Promise<{
31
+ declare function createRelaySubscriber(): Redis;
32
+ declare function relayRequestChannel(sessionId: string): string;
33
+ declare function createRelaySession(): Promise<{
17
34
  sessionId: string;
18
35
  token: string;
19
36
  expiresAt: number;
20
37
  }>;
21
- declare function assertBridgeSession(sessionId: string, token: string): Promise<void>;
22
- declare function markBridgeBrowserConnected(sessionId: string, token: string): Promise<void>;
23
- declare function touchBridgeBrowserConnected(sessionId: string): Promise<void>;
24
- declare function dispatchBridgeRequest(input: {
38
+ declare function assertRelaySession(sessionId: string, token: string): Promise<void>;
39
+ declare function markBrowserConnected(sessionId: string, token: string): Promise<void>;
40
+ declare function touchBrowserConnected(sessionId: string): Promise<void>;
41
+ declare function dispatchRelayRequest(input: {
25
42
  sessionId: string;
26
43
  token: string;
27
- request: BridgeRequest;
44
+ request: RelayRequest;
28
45
  timeoutMs?: number;
29
- }): Promise<BridgeResponse>;
30
- declare function resolveBridgeResponse(input: {
46
+ }): Promise<RelayResponse>;
47
+ declare function resolveRelayResponse(input: {
31
48
  sessionId: string;
32
49
  token: string;
33
- response: BridgeResponse;
50
+ response: RelayResponse;
34
51
  }): Promise<void>;
35
- declare function toBridgeError(error: unknown): BridgeBrokerError;
36
-
37
- declare function createBridgeHandler(): {
38
- GET: (request: Request) => Promise<Response>;
39
- POST: (request: Request) => Promise<Response>;
40
- };
41
-
42
- declare const requestSchema: z.ZodObject<{
43
- message: z.ZodString;
44
- session_id: z.ZodOptional<z.ZodString>;
45
- sandbox_id: z.ZodOptional<z.ZodString>;
46
- bridge_session_id: z.ZodString;
47
- bridge_token: z.ZodString;
48
- }, z.core.$strip>;
49
- type GeminiChatRouteOptions = {
50
- requestParser?: typeof requestSchema;
51
- };
52
- declare function createGeminiChatHandler(_options?: GeminiChatRouteOptions): (request: Request) => Promise<Response>;
52
+ declare function toRelayError(error: unknown): RelayStoreError;
53
53
 
54
- export { BRIDGE_SSE_KEEPALIVE_INTERVAL_MS, assertBridgeSession, bridgeRequestChannel, createBridgeHandler, createBridgeSession, createBridgeSubscriber, createGeminiChatHandler, dispatchBridgeRequest, markBridgeBrowserConnected, resolveBridgeResponse, toBridgeError, touchBridgeBrowserConnected };
54
+ export { RELAY_SSE_KEEPALIVE_INTERVAL_MS, assertRelaySession, createGeminiChatHandler, createRelayHandler, createRelaySession, createRelaySubscriber, dispatchRelayRequest, markBrowserConnected, relayRequestChannel, resolveRelayResponse, toRelayError, touchBrowserConnected };
package/dist/index.js CHANGED
@@ -1,14 +1,247 @@
1
- // src/bridge-broker.ts
1
+ // src/chat-handler.ts
2
+ import { Writable } from "stream";
3
+ import { Sandbox } from "@vercel/sandbox";
4
+ import { z } from "zod";
5
+ var GEMINI_SETTINGS_PATH = "/home/vercel-sandbox/.gemini/settings.json";
6
+ var requestSchema = z.object({
7
+ message: z.string().min(1),
8
+ session_id: z.string().min(1).optional(),
9
+ sandbox_id: z.string().min(1).optional(),
10
+ relay_session_id: z.string().min(1),
11
+ relay_token: z.string().min(1)
12
+ });
13
+ function requiredEnv(name) {
14
+ const value = process.env[name]?.trim();
15
+ if (!value) {
16
+ throw new Error(`Missing required environment variable: ${name}`);
17
+ }
18
+ return value;
19
+ }
20
+ function extractTokenFromRequest(request) {
21
+ const oidcToken = request.headers.get("x-vercel-oidc-token")?.trim();
22
+ if (oidcToken) {
23
+ return oidcToken;
24
+ }
25
+ const authorization = request.headers.get("authorization")?.trim();
26
+ if (!authorization) {
27
+ return void 0;
28
+ }
29
+ if (/^bearer\s+/i.test(authorization)) {
30
+ return authorization.replace(/^bearer\s+/i, "").trim();
31
+ }
32
+ return authorization;
33
+ }
34
+ function buildMcpEnv(input) {
35
+ const env = {
36
+ BROWSER_TOOL_RELAY_URL: input.relayUrl,
37
+ BROWSER_TOOL_RELAY_SESSION_ID: input.relaySessionId,
38
+ BROWSER_TOOL_RELAY_TOKEN: input.relayToken
39
+ };
40
+ if (input.oidcToken) {
41
+ env.VERCEL_OIDC_TOKEN = input.oidcToken;
42
+ }
43
+ if (input.vercelProtectionBypass?.trim()) {
44
+ env.VERCEL_PROTECTION_BYPASS = input.vercelProtectionBypass.trim();
45
+ }
46
+ if (input.giselleProtectionBypass?.trim()) {
47
+ env.GISELLE_PROTECTION_BYPASS = input.giselleProtectionBypass.trim();
48
+ }
49
+ return env;
50
+ }
51
+ async function patchGeminiSettingsEnv(sandbox, mcpEnv) {
52
+ const buffer = await sandbox.readFileToBuffer({
53
+ path: GEMINI_SETTINGS_PATH
54
+ });
55
+ if (!buffer) {
56
+ throw new Error(
57
+ `Gemini settings not found in sandbox at ${GEMINI_SETTINGS_PATH}. Ensure the snapshot contains a pre-configured settings.json.`
58
+ );
59
+ }
60
+ const settings = JSON.parse(new TextDecoder().decode(buffer));
61
+ if (settings.mcpServers) {
62
+ settings.mcpServers = Object.fromEntries(
63
+ Object.entries(settings.mcpServers).map(([key, server]) => [
64
+ key,
65
+ { ...server, env: { ...server.env, ...mcpEnv } }
66
+ ])
67
+ );
68
+ }
69
+ await sandbox.writeFiles([
70
+ {
71
+ path: GEMINI_SETTINGS_PATH,
72
+ content: Buffer.from(JSON.stringify(settings, null, 2))
73
+ }
74
+ ]);
75
+ }
76
+ function emitText(controller, text, encoder) {
77
+ if (text.length === 0) {
78
+ return;
79
+ }
80
+ controller.enqueue(encoder.encode(text));
81
+ }
82
+ function emitEvent(controller, payload, encoder) {
83
+ emitText(controller, `${JSON.stringify(payload)}
84
+ `, encoder);
85
+ }
86
+ function createGeminiChatHandler(_options = {}) {
87
+ const requestParser = requestSchema;
88
+ return async function POST(request) {
89
+ const payload = await request.json().catch(() => null);
90
+ const parsed = requestParser.safeParse(payload);
91
+ if (!parsed.success) {
92
+ return Response.json(
93
+ {
94
+ error: "Invalid request payload.",
95
+ detail: parsed.error.flatten()
96
+ },
97
+ { status: 400 }
98
+ );
99
+ }
100
+ const stream = new ReadableStream({
101
+ start(controller) {
102
+ const encoder = new TextEncoder();
103
+ const abortController = new AbortController();
104
+ let closed = false;
105
+ const close = () => {
106
+ if (closed) {
107
+ return;
108
+ }
109
+ closed = true;
110
+ try {
111
+ controller.close();
112
+ } catch {
113
+ }
114
+ };
115
+ const onAbort = () => {
116
+ if (!abortController.signal.aborted) {
117
+ abortController.abort();
118
+ }
119
+ close();
120
+ };
121
+ if (request.signal.aborted) {
122
+ onAbort();
123
+ return;
124
+ }
125
+ request.signal.addEventListener("abort", onAbort);
126
+ const enqueueEvent = (payload2) => {
127
+ if (closed) {
128
+ return;
129
+ }
130
+ emitEvent(controller, payload2, encoder);
131
+ };
132
+ const enqueueStdout = (text) => {
133
+ if (closed) {
134
+ return;
135
+ }
136
+ emitText(controller, text, encoder);
137
+ };
138
+ (async () => {
139
+ const geminiApiKey = requiredEnv("GEMINI_API_KEY");
140
+ const sandboxSnapshotId = requiredEnv("SANDBOX_SNAPSHOT_ID");
141
+ const oidcToken = extractTokenFromRequest(request) ?? process.env.VERCEL_OIDC_TOKEN ?? "";
142
+ if (!oidcToken) {
143
+ throw new Error(
144
+ "Planner authentication is required: set OIDC token in x-vercel-oidc-token or VERCEL_OIDC_TOKEN."
145
+ );
146
+ }
147
+ const vercelProtectionBypass = process.env.VERCEL_PROTECTION_BYPASS?.trim() || void 0;
148
+ const giselleProtectionBypass = process.env.GISELLE_PROTECTION_PASSWORD?.trim() || void 0;
149
+ const relayUrl = process.env.BROWSER_TOOL_RELAY_URL?.trim() || new URL(request.url).origin;
150
+ const {
151
+ message,
152
+ session_id: sessionId,
153
+ sandbox_id: sandboxId,
154
+ relay_session_id: relaySessionId,
155
+ relay_token: relayToken
156
+ } = parsed.data;
157
+ const sandbox = sandboxId ? await Sandbox.get({ sandboxId }) : await Sandbox.create({
158
+ source: {
159
+ type: "snapshot",
160
+ snapshotId: sandboxSnapshotId
161
+ }
162
+ });
163
+ enqueueEvent({ type: "sandbox", sandbox_id: sandbox.sandboxId });
164
+ const mcpEnv = buildMcpEnv({
165
+ relayUrl,
166
+ relaySessionId,
167
+ relayToken,
168
+ oidcToken,
169
+ vercelProtectionBypass,
170
+ giselleProtectionBypass
171
+ });
172
+ await patchGeminiSettingsEnv(sandbox, mcpEnv);
173
+ const args = [
174
+ "--prompt",
175
+ message,
176
+ "--output-format",
177
+ "stream-json",
178
+ "--approval-mode",
179
+ "yolo"
180
+ ];
181
+ if (sessionId) {
182
+ args.push("--resume", sessionId);
183
+ }
184
+ await sandbox.runCommand({
185
+ cmd: "gemini",
186
+ args,
187
+ env: {
188
+ GEMINI_API_KEY: geminiApiKey
189
+ },
190
+ stdout: new Writable({
191
+ write(chunk, _encoding, callback) {
192
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
193
+ enqueueStdout(text);
194
+ callback();
195
+ }
196
+ }),
197
+ stderr: new Writable({
198
+ write(chunk, _encoding, callback) {
199
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
200
+ enqueueEvent({ type: "stderr", content: text });
201
+ callback();
202
+ }
203
+ }),
204
+ signal: abortController.signal
205
+ });
206
+ })().catch((error) => {
207
+ if (abortController.signal.aborted) {
208
+ return;
209
+ }
210
+ const message = error instanceof Error ? error.message : String(error);
211
+ enqueueEvent({ type: "stderr", content: `[error] ${message}` });
212
+ }).finally(() => {
213
+ request.signal.removeEventListener("abort", onAbort);
214
+ close();
215
+ });
216
+ }
217
+ });
218
+ return new Response(stream, {
219
+ headers: {
220
+ "Content-Type": "application/x-ndjson; charset=utf-8",
221
+ "Cache-Control": "no-cache, no-transform"
222
+ }
223
+ });
224
+ };
225
+ }
226
+
227
+ // src/relay-handler.ts
228
+ import {
229
+ relayRequestSchema,
230
+ relayResponseSchema as relayResponseSchema2
231
+ } from "@giselles-ai/browser-tool";
232
+ import { z as z2 } from "zod";
233
+
234
+ // src/relay-store.ts
2
235
  import { randomUUID } from "crypto";
3
236
  import {
4
- bridgeResponseSchema
237
+ relayResponseSchema
5
238
  } from "@giselles-ai/browser-tool";
6
239
  import Redis from "ioredis";
7
240
  var DEFAULT_SESSION_TTL_MS = 10 * 60 * 1e3;
8
241
  var DEFAULT_REQUEST_TTL_SEC = 60;
9
242
  var DEFAULT_DISPATCH_TIMEOUT_MS = 20 * 1e3;
10
243
  var BROWSER_PRESENCE_TTL_SEC = 90;
11
- var BRIDGE_SSE_KEEPALIVE_INTERVAL_MS = 20 * 1e3;
244
+ var RELAY_SSE_KEEPALIVE_INTERVAL_MS = 20 * 1e3;
12
245
  var REDIS_URL_ENV_CANDIDATES = [
13
246
  "REDIS_URL",
14
247
  "REDIS_TLS_URL",
@@ -16,13 +249,13 @@ var REDIS_URL_ENV_CANDIDATES = [
16
249
  "UPSTASH_REDIS_TLS_URL",
17
250
  "UPSTASH_REDIS_URL"
18
251
  ];
19
- var BRIDGE_SUBSCRIBER_REDIS_OPTIONS = {
252
+ var RELAY_SUBSCRIBER_REDIS_OPTIONS = {
20
253
  enableReadyCheck: false,
21
254
  autoResubscribe: false,
22
255
  autoResendUnfulfilledCommands: false,
23
256
  maxRetriesPerRequest: 2
24
257
  };
25
- var BridgeBrokerError = class extends Error {
258
+ var RelayStoreError = class extends Error {
26
259
  code;
27
260
  status;
28
261
  constructor(code, message, status) {
@@ -31,22 +264,22 @@ var BridgeBrokerError = class extends Error {
31
264
  this.status = status;
32
265
  }
33
266
  };
34
- function createBridgeError(code, message) {
267
+ function createRelayError(code, message) {
35
268
  switch (code) {
36
269
  case "UNAUTHORIZED":
37
- return new BridgeBrokerError(code, message, 401);
270
+ return new RelayStoreError(code, message, 401);
38
271
  case "NO_BROWSER":
39
- return new BridgeBrokerError(code, message, 409);
272
+ return new RelayStoreError(code, message, 409);
40
273
  case "TIMEOUT":
41
- return new BridgeBrokerError(code, message, 408);
274
+ return new RelayStoreError(code, message, 408);
42
275
  case "INVALID_RESPONSE":
43
- return new BridgeBrokerError(code, message, 422);
276
+ return new RelayStoreError(code, message, 422);
44
277
  case "NOT_FOUND":
45
- return new BridgeBrokerError(code, message, 404);
278
+ return new RelayStoreError(code, message, 404);
46
279
  case "INTERNAL":
47
- return new BridgeBrokerError(code, message, 500);
280
+ return new RelayStoreError(code, message, 500);
48
281
  default:
49
- return new BridgeBrokerError("INTERNAL", message, 500);
282
+ return new RelayStoreError("INTERNAL", message, 500);
50
283
  }
51
284
  }
52
285
  function resolveRedisUrl() {
@@ -56,39 +289,39 @@ function resolveRedisUrl() {
56
289
  return value;
57
290
  }
58
291
  }
59
- throw createBridgeError(
292
+ throw createRelayError(
60
293
  "INTERNAL",
61
294
  `Missing Redis URL. Set one of: ${REDIS_URL_ENV_CANDIDATES.join(", ")}`
62
295
  );
63
296
  }
64
297
  function getRedisClient() {
65
- if (!globalThis.__geminiBrowserToolBridgeRedis) {
66
- globalThis.__geminiBrowserToolBridgeRedis = new Redis(resolveRedisUrl(), {
298
+ if (!globalThis.__browserToolRelayRedis) {
299
+ globalThis.__browserToolRelayRedis = new Redis(resolveRedisUrl(), {
67
300
  maxRetriesPerRequest: 2
68
301
  });
69
302
  }
70
- return globalThis.__geminiBrowserToolBridgeRedis;
303
+ return globalThis.__browserToolRelayRedis;
71
304
  }
72
- function createBridgeSubscriber() {
73
- return getRedisClient().duplicate(BRIDGE_SUBSCRIBER_REDIS_OPTIONS);
305
+ function createRelaySubscriber() {
306
+ return getRedisClient().duplicate(RELAY_SUBSCRIBER_REDIS_OPTIONS);
74
307
  }
75
308
  function sessionKey(sessionId) {
76
- return `bridge:session:${sessionId}`;
309
+ return `relay:session:${sessionId}`;
77
310
  }
78
311
  function browserPresenceKey(sessionId) {
79
- return `bridge:browser:${sessionId}`;
312
+ return `relay:browser:${sessionId}`;
80
313
  }
81
314
  function requestTypeKey(sessionId, requestId) {
82
- return `bridge:req:${sessionId}:${requestId}:type`;
315
+ return `relay:req:${sessionId}:${requestId}:type`;
83
316
  }
84
317
  function responseKey(sessionId, requestId) {
85
- return `bridge:resp:${sessionId}:${requestId}`;
318
+ return `relay:resp:${sessionId}:${requestId}`;
86
319
  }
87
- function bridgeRequestChannel(sessionId) {
88
- return `bridge:${sessionId}:request`;
320
+ function relayRequestChannel(sessionId) {
321
+ return `relay:${sessionId}:request`;
89
322
  }
90
- function bridgeResponseChannel(sessionId, requestId) {
91
- return `bridge:${sessionId}:response:${requestId}`;
323
+ function relayResponseChannel(sessionId, requestId) {
324
+ return `relay:${sessionId}:response:${requestId}`;
92
325
  }
93
326
  function sessionExpiryTimestamp() {
94
327
  return Date.now() + DEFAULT_SESSION_TTL_MS;
@@ -98,15 +331,15 @@ function parseSessionRecord(raw) {
98
331
  try {
99
332
  parsed = JSON.parse(raw);
100
333
  } catch {
101
- throw createBridgeError(
334
+ throw createRelayError(
102
335
  "INTERNAL",
103
- "Bridge session payload in Redis is malformed."
336
+ "Relay session payload in Redis is malformed."
104
337
  );
105
338
  }
106
339
  if (!parsed || typeof parsed !== "object" || !("token" in parsed) || !("expiresAt" in parsed) || typeof parsed.token !== "string" || typeof parsed.expiresAt !== "number") {
107
- throw createBridgeError(
340
+ throw createRelayError(
108
341
  "INTERNAL",
109
- "Bridge session payload in Redis is invalid."
342
+ "Relay session payload in Redis is invalid."
110
343
  );
111
344
  }
112
345
  return {
@@ -118,26 +351,26 @@ function toRequestType(value) {
118
351
  if (value === "snapshot_request" || value === "execute_request") {
119
352
  return value;
120
353
  }
121
- throw createBridgeError(
354
+ throw createRelayError(
122
355
  "INTERNAL",
123
356
  `Stored request type is invalid: ${value}`
124
357
  );
125
358
  }
126
- function parseStoredBridgeResponse(raw) {
359
+ function parseStoredRelayResponse(raw) {
127
360
  let decoded = null;
128
361
  try {
129
362
  decoded = JSON.parse(raw);
130
363
  } catch {
131
- throw createBridgeError(
364
+ throw createRelayError(
132
365
  "INVALID_RESPONSE",
133
- "Bridge response payload in Redis is malformed."
366
+ "Relay response payload in Redis is malformed."
134
367
  );
135
368
  }
136
- const parsed = bridgeResponseSchema.safeParse(decoded);
369
+ const parsed = relayResponseSchema.safeParse(decoded);
137
370
  if (!parsed.success) {
138
- throw createBridgeError(
371
+ throw createRelayError(
139
372
  "INVALID_RESPONSE",
140
- "Bridge response payload in Redis is invalid."
373
+ "Relay response payload in Redis is invalid."
141
374
  );
142
375
  }
143
376
  return parsed.data;
@@ -157,16 +390,16 @@ async function getAuthorizedSession(sessionId, token) {
157
390
  const redis = getRedisClient();
158
391
  const raw = await redis.get(sessionKey(sessionId));
159
392
  if (!raw) {
160
- throw createBridgeError(
393
+ throw createRelayError(
161
394
  "UNAUTHORIZED",
162
- "Invalid bridge session credentials."
395
+ "Invalid relay session credentials."
163
396
  );
164
397
  }
165
398
  const session = parseSessionRecord(raw);
166
399
  if (session.token !== token) {
167
- throw createBridgeError(
400
+ throw createRelayError(
168
401
  "UNAUTHORIZED",
169
- "Invalid bridge session credentials."
402
+ "Invalid relay session credentials."
170
403
  );
171
404
  }
172
405
  const expiresAt = await touchSession(sessionId, token);
@@ -177,13 +410,13 @@ async function getAuthorizedSession(sessionId, token) {
177
410
  }
178
411
  function validateResponseType(requestType, responseType) {
179
412
  if (requestType === "snapshot_request" && responseType !== "snapshot_response") {
180
- throw createBridgeError(
413
+ throw createRelayError(
181
414
  "INVALID_RESPONSE",
182
415
  `Expected snapshot_response, but received ${responseType}.`
183
416
  );
184
417
  }
185
418
  if (requestType === "execute_request" && responseType !== "execute_response") {
186
- throw createBridgeError(
419
+ throw createRelayError(
187
420
  "INVALID_RESPONSE",
188
421
  `Expected execute_response, but received ${responseType}.`
189
422
  );
@@ -193,13 +426,13 @@ async function ensureBrowserConnected(sessionId) {
193
426
  const redis = getRedisClient();
194
427
  const isConnected = await redis.exists(browserPresenceKey(sessionId));
195
428
  if (isConnected === 0) {
196
- throw createBridgeError(
429
+ throw createRelayError(
197
430
  "NO_BROWSER",
198
- "No browser client is connected to this bridge session."
431
+ "No browser client is connected to this relay session."
199
432
  );
200
433
  }
201
434
  }
202
- async function waitForBridgeResponseSignal(input) {
435
+ async function waitForRelayResponseSignal(input) {
203
436
  await new Promise((resolve, reject) => {
204
437
  let settled = false;
205
438
  const onMessage = (channel) => {
@@ -218,7 +451,7 @@ async function waitForBridgeResponseSignal(input) {
218
451
  cleanup();
219
452
  const message = error instanceof Error ? error.message : "Unknown Redis subscriber error.";
220
453
  reject(
221
- createBridgeError(
454
+ createRelayError(
222
455
  "INTERNAL",
223
456
  `Redis subscriber failed while waiting for response. ${message}`
224
457
  )
@@ -231,9 +464,9 @@ async function waitForBridgeResponseSignal(input) {
231
464
  settled = true;
232
465
  cleanup();
233
466
  reject(
234
- createBridgeError(
467
+ createRelayError(
235
468
  "TIMEOUT",
236
- "Timed out waiting for browser bridge response."
469
+ "Timed out waiting for browser relay response."
237
470
  )
238
471
  );
239
472
  };
@@ -251,21 +484,21 @@ async function waitForBridgeResponseSignal(input) {
251
484
  }
252
485
  settled = true;
253
486
  cleanup();
254
- if (error instanceof BridgeBrokerError) {
487
+ if (error instanceof RelayStoreError) {
255
488
  reject(error);
256
489
  return;
257
490
  }
258
491
  const message = error instanceof Error ? error.message : "Unknown Redis publish error.";
259
492
  reject(
260
- createBridgeError(
493
+ createRelayError(
261
494
  "INTERNAL",
262
- `Failed to publish bridge request. ${message}`
495
+ `Failed to publish relay request. ${message}`
263
496
  )
264
497
  );
265
498
  });
266
499
  });
267
500
  }
268
- async function storeBridgeResponse(input) {
501
+ async function storeRelayResponse(input) {
269
502
  const redis = getRedisClient();
270
503
  await redis.multi().set(
271
504
  responseKey(input.sessionId, input.requestId),
@@ -273,11 +506,11 @@ async function storeBridgeResponse(input) {
273
506
  "EX",
274
507
  DEFAULT_REQUEST_TTL_SEC
275
508
  ).del(requestTypeKey(input.sessionId, input.requestId)).publish(
276
- bridgeResponseChannel(input.sessionId, input.requestId),
509
+ relayResponseChannel(input.sessionId, input.requestId),
277
510
  input.requestId
278
511
  ).exec();
279
512
  }
280
- async function createBridgeSession() {
513
+ async function createRelaySession() {
281
514
  const sessionId = randomUUID();
282
515
  const token = `${randomUUID()}-${randomUUID()}`;
283
516
  const expiresAt = sessionExpiryTimestamp();
@@ -297,10 +530,10 @@ async function createBridgeSession() {
297
530
  expiresAt
298
531
  };
299
532
  }
300
- async function assertBridgeSession(sessionId, token) {
533
+ async function assertRelaySession(sessionId, token) {
301
534
  await getAuthorizedSession(sessionId, token);
302
535
  }
303
- async function markBridgeBrowserConnected(sessionId, token) {
536
+ async function markBrowserConnected(sessionId, token) {
304
537
  await getAuthorizedSession(sessionId, token);
305
538
  const redis = getRedisClient();
306
539
  await redis.set(
@@ -310,7 +543,7 @@ async function markBridgeBrowserConnected(sessionId, token) {
310
543
  BROWSER_PRESENCE_TTL_SEC
311
544
  );
312
545
  }
313
- async function touchBridgeBrowserConnected(sessionId) {
546
+ async function touchBrowserConnected(sessionId) {
314
547
  const redis = getRedisClient();
315
548
  await redis.set(
316
549
  browserPresenceKey(sessionId),
@@ -319,7 +552,7 @@ async function touchBridgeBrowserConnected(sessionId) {
319
552
  BROWSER_PRESENCE_TTL_SEC
320
553
  );
321
554
  }
322
- async function dispatchBridgeRequest(input) {
555
+ async function dispatchRelayRequest(input) {
323
556
  await getAuthorizedSession(input.sessionId, input.token);
324
557
  await ensureBrowserConnected(input.sessionId);
325
558
  const redis = getRedisClient();
@@ -327,11 +560,8 @@ async function dispatchBridgeRequest(input) {
327
560
  const requestId = input.request.requestId;
328
561
  const requestTypeStateKey = requestTypeKey(input.sessionId, requestId);
329
562
  const storedResponseKey = responseKey(input.sessionId, requestId);
330
- const responseEventChannel = bridgeResponseChannel(
331
- input.sessionId,
332
- requestId
333
- );
334
- const subscriber = createBridgeSubscriber();
563
+ const responseEventChannel = relayResponseChannel(input.sessionId, requestId);
564
+ const subscriber = createRelaySubscriber();
335
565
  const setPending = await redis.set(
336
566
  requestTypeStateKey,
337
567
  input.request.type,
@@ -340,7 +570,7 @@ async function dispatchBridgeRequest(input) {
340
570
  "NX"
341
571
  );
342
572
  if (setPending !== "OK") {
343
- throw createBridgeError(
573
+ throw createRelayError(
344
574
  "INVALID_RESPONSE",
345
575
  `Request id is already pending: ${requestId}`
346
576
  );
@@ -348,27 +578,27 @@ async function dispatchBridgeRequest(input) {
348
578
  try {
349
579
  await redis.del(storedResponseKey);
350
580
  await subscriber.subscribe(responseEventChannel);
351
- await waitForBridgeResponseSignal({
581
+ await waitForRelayResponseSignal({
352
582
  subscriber,
353
583
  channel: responseEventChannel,
354
584
  timeoutMs,
355
585
  trigger: async () => {
356
586
  await redis.publish(
357
- bridgeRequestChannel(input.sessionId),
587
+ relayRequestChannel(input.sessionId),
358
588
  JSON.stringify(input.request)
359
589
  );
360
590
  }
361
591
  });
362
592
  const storedResponse = await redis.get(storedResponseKey);
363
593
  if (!storedResponse) {
364
- throw createBridgeError(
594
+ throw createRelayError(
365
595
  "TIMEOUT",
366
- "Bridge response notification was received without payload."
596
+ "Relay response notification was received without payload."
367
597
  );
368
598
  }
369
- const parsedResponse = parseStoredBridgeResponse(storedResponse);
599
+ const parsedResponse = parseStoredRelayResponse(storedResponse);
370
600
  if (parsedResponse.type === "error_response") {
371
- throw createBridgeError("INVALID_RESPONSE", parsedResponse.message);
601
+ throw createRelayError("INVALID_RESPONSE", parsedResponse.message);
372
602
  }
373
603
  validateResponseType(input.request.type, parsedResponse.type);
374
604
  return parsedResponse;
@@ -383,21 +613,21 @@ async function dispatchBridgeRequest(input) {
383
613
  });
384
614
  }
385
615
  }
386
- async function resolveBridgeResponse(input) {
616
+ async function resolveRelayResponse(input) {
387
617
  await getAuthorizedSession(input.sessionId, input.token);
388
618
  const redis = getRedisClient();
389
619
  const requestId = input.response.requestId;
390
620
  const requestTypeStateKey = requestTypeKey(input.sessionId, requestId);
391
621
  const expectedRequestTypeRaw = await redis.get(requestTypeStateKey);
392
622
  if (!expectedRequestTypeRaw) {
393
- throw createBridgeError(
623
+ throw createRelayError(
394
624
  "NOT_FOUND",
395
625
  `Pending request was not found: ${requestId}`
396
626
  );
397
627
  }
398
628
  const expectedRequestType = toRequestType(expectedRequestTypeRaw);
399
629
  if (input.response.type === "error_response") {
400
- await storeBridgeResponse({
630
+ await storeRelayResponse({
401
631
  sessionId: input.sessionId,
402
632
  requestId,
403
633
  response: input.response
@@ -407,63 +637,58 @@ async function resolveBridgeResponse(input) {
407
637
  try {
408
638
  validateResponseType(expectedRequestType, input.response.type);
409
639
  } catch (error) {
410
- const bridgeError = error instanceof BridgeBrokerError ? error : createBridgeError(
640
+ const relayError = error instanceof RelayStoreError ? error : createRelayError(
411
641
  "INVALID_RESPONSE",
412
- "Unexpected bridge response type."
642
+ "Unexpected relay response type."
413
643
  );
414
- await storeBridgeResponse({
644
+ await storeRelayResponse({
415
645
  sessionId: input.sessionId,
416
646
  requestId,
417
647
  response: {
418
648
  type: "error_response",
419
649
  requestId,
420
- message: bridgeError.message
650
+ message: relayError.message
421
651
  }
422
652
  });
423
- throw bridgeError;
653
+ throw relayError;
424
654
  }
425
- await storeBridgeResponse({
655
+ await storeRelayResponse({
426
656
  sessionId: input.sessionId,
427
657
  requestId,
428
658
  response: input.response
429
659
  });
430
660
  }
431
- function toBridgeError(error) {
432
- if (error instanceof BridgeBrokerError) {
661
+ function toRelayError(error) {
662
+ if (error instanceof RelayStoreError) {
433
663
  return error;
434
664
  }
435
- const message = error instanceof Error ? error.message : "Unexpected bridge failure.";
436
- return createBridgeError("INTERNAL", message);
665
+ const message = error instanceof Error ? error.message : "Unexpected relay failure.";
666
+ return createRelayError("INTERNAL", message);
437
667
  }
438
668
 
439
- // src/bridge-handler.ts
440
- import {
441
- bridgeRequestSchema,
442
- bridgeResponseSchema as bridgeResponseSchema2
443
- } from "@giselles-ai/browser-tool";
444
- import { z } from "zod";
445
- var LOG_PREFIX = "[bridge-handler]";
446
- var dispatchSchema = z.object({
447
- type: z.literal("bridge.dispatch"),
448
- sessionId: z.string().min(1),
449
- token: z.string().min(1),
450
- request: bridgeRequestSchema,
451
- timeoutMs: z.number().int().positive().max(55e3).optional()
669
+ // src/relay-handler.ts
670
+ var LOG_PREFIX = "[relay-handler]";
671
+ var dispatchSchema = z2.object({
672
+ type: z2.literal("relay.dispatch"),
673
+ sessionId: z2.string().min(1),
674
+ token: z2.string().min(1),
675
+ request: relayRequestSchema,
676
+ timeoutMs: z2.number().int().positive().max(55e3).optional()
452
677
  });
453
- var respondSchema = z.object({
454
- type: z.literal("bridge.respond"),
455
- sessionId: z.string().min(1),
456
- token: z.string().min(1),
457
- response: bridgeResponseSchema2
678
+ var respondSchema = z2.object({
679
+ type: z2.literal("relay.respond"),
680
+ sessionId: z2.string().min(1),
681
+ token: z2.string().min(1),
682
+ response: relayResponseSchema2
458
683
  });
459
- var postBodySchema = z.discriminatedUnion("type", [
684
+ var postBodySchema = z2.discriminatedUnion("type", [
460
685
  dispatchSchema,
461
686
  respondSchema
462
687
  ]);
463
688
  function createSafeError(code, message, status) {
464
689
  return Response.json({ ok: false, errorCode: code, message }, { status });
465
690
  }
466
- function createBridgeEventsRoute(request) {
691
+ function createRelayEventsRoute(request) {
467
692
  const url = new URL(request.url);
468
693
  const sessionId = url.searchParams.get("sessionId") ?? "";
469
694
  const token = url.searchParams.get("token") ?? "";
@@ -480,12 +705,12 @@ function createBridgeEventsRoute(request) {
480
705
  }
481
706
  let cleanup = null;
482
707
  const encoder = new TextEncoder();
483
- return assertBridgeSession(sessionId, token).then(() => {
708
+ return assertRelaySession(sessionId, token).then(() => {
484
709
  console.info(`${LOG_PREFIX} sse.connect`, { sessionId });
485
- const requestChannel = bridgeRequestChannel(sessionId);
710
+ const requestChannel = relayRequestChannel(sessionId);
486
711
  const stream = new ReadableStream({
487
712
  start(controller) {
488
- const subscriber = createBridgeSubscriber();
713
+ const subscriber = createRelaySubscriber();
489
714
  let keepaliveId = null;
490
715
  let closed = false;
491
716
  let nextEventId = 0;
@@ -567,20 +792,18 @@ data: ${rawJson}
567
792
  void (async () => {
568
793
  try {
569
794
  await subscriber.subscribe(requestChannel);
570
- await markBridgeBrowserConnected(sessionId, token);
795
+ await markBrowserConnected(sessionId, token);
571
796
  console.info(`${LOG_PREFIX} sse.ready`, { sessionId });
572
797
  sendSseData({ type: "ready", sessionId });
573
798
  keepaliveId = setInterval(() => {
574
- void touchBridgeBrowserConnected(sessionId).catch(
575
- () => void 0
576
- );
799
+ void touchBrowserConnected(sessionId).catch(() => void 0);
577
800
  try {
578
801
  sendSseComment("keepalive");
579
802
  } catch {
580
803
  void cleanup?.();
581
804
  closeController();
582
805
  }
583
- }, BRIDGE_SSE_KEEPALIVE_INTERVAL_MS);
806
+ }, RELAY_SSE_KEEPALIVE_INTERVAL_MS);
584
807
  } catch (error) {
585
808
  if (closed) {
586
809
  return;
@@ -608,25 +831,25 @@ data: ${rawJson}
608
831
  sessionId,
609
832
  error: error instanceof Error ? error.message : String(error)
610
833
  });
611
- const bridgeError = toBridgeError(error);
834
+ const relayError = toRelayError(error);
612
835
  return Response.json(
613
836
  {
614
- errorCode: bridgeError.code,
615
- message: bridgeError.message
837
+ errorCode: relayError.code,
838
+ message: relayError.message
616
839
  },
617
- { status: bridgeError.status }
840
+ { status: relayError.status }
618
841
  );
619
842
  });
620
843
  }
621
- async function createBridgePostRoute(request) {
844
+ async function createRelayPostRoute(request) {
622
845
  const payload = await request.json().catch(() => null);
623
846
  const parsed = postBodySchema.safeParse(payload);
624
847
  if (!parsed.success) {
625
848
  return createSafeError("INVALID_RESPONSE", "Invalid request payload.", 400);
626
849
  }
627
- if (parsed.data.type === "bridge.dispatch") {
850
+ if (parsed.data.type === "relay.dispatch") {
628
851
  try {
629
- const response = await dispatchBridgeRequest({
852
+ const response = await dispatchRelayRequest({
630
853
  sessionId: parsed.data.sessionId,
631
854
  token: parsed.data.token,
632
855
  request: parsed.data.request,
@@ -634,273 +857,47 @@ async function createBridgePostRoute(request) {
634
857
  });
635
858
  return Response.json({ ok: true, response });
636
859
  } catch (error) {
637
- const bridgeError = toBridgeError(error);
860
+ const relayError = toRelayError(error);
638
861
  return createSafeError(
639
- bridgeError.code,
640
- bridgeError.message,
641
- bridgeError.status
862
+ relayError.code,
863
+ relayError.message,
864
+ relayError.status
642
865
  );
643
866
  }
644
867
  }
645
868
  try {
646
- await resolveBridgeResponse({
869
+ await resolveRelayResponse({
647
870
  sessionId: parsed.data.sessionId,
648
871
  token: parsed.data.token,
649
872
  response: parsed.data.response
650
873
  });
651
874
  return Response.json({ ok: true });
652
875
  } catch (error) {
653
- const bridgeError = toBridgeError(error);
876
+ const relayError = toRelayError(error);
654
877
  return createSafeError(
655
- bridgeError.code,
656
- bridgeError.message,
657
- bridgeError.status
878
+ relayError.code,
879
+ relayError.message,
880
+ relayError.status
658
881
  );
659
882
  }
660
883
  }
661
- function createBridgeHandler() {
884
+ function createRelayHandler() {
662
885
  return {
663
- GET: async (request) => createBridgeEventsRoute(request),
664
- POST: createBridgePostRoute
665
- };
666
- }
667
-
668
- // src/chat-handler.ts
669
- import { Writable } from "stream";
670
- import { Sandbox } from "@vercel/sandbox";
671
- import { z as z2 } from "zod";
672
- var GEMINI_SETTINGS_PATH = "/home/vercel-sandbox/.gemini/settings.json";
673
- var requestSchema = z2.object({
674
- message: z2.string().min(1),
675
- session_id: z2.string().min(1).optional(),
676
- sandbox_id: z2.string().min(1).optional(),
677
- bridge_session_id: z2.string().min(1),
678
- bridge_token: z2.string().min(1)
679
- });
680
- function requiredEnv(name) {
681
- const value = process.env[name]?.trim();
682
- if (!value) {
683
- throw new Error(`Missing required environment variable: ${name}`);
684
- }
685
- return value;
686
- }
687
- function extractTokenFromRequest(request) {
688
- const oidcToken = request.headers.get("x-vercel-oidc-token")?.trim();
689
- if (oidcToken) {
690
- return oidcToken;
691
- }
692
- const authorization = request.headers.get("authorization")?.trim();
693
- if (!authorization) {
694
- return void 0;
695
- }
696
- if (/^bearer\s+/i.test(authorization)) {
697
- return authorization.replace(/^bearer\s+/i, "").trim();
698
- }
699
- return authorization;
700
- }
701
- function buildMcpEnv(input) {
702
- const env = {
703
- BROWSER_TOOL_BRIDGE_BASE_URL: input.bridgeBaseUrl,
704
- BROWSER_TOOL_BRIDGE_SESSION_ID: input.bridgeSessionId,
705
- BROWSER_TOOL_BRIDGE_TOKEN: input.bridgeToken
706
- };
707
- if (input.oidcToken) {
708
- env.VERCEL_OIDC_TOKEN = input.oidcToken;
709
- }
710
- if (input.vercelProtectionBypass?.trim()) {
711
- env.VERCEL_PROTECTION_BYPASS = input.vercelProtectionBypass.trim();
712
- }
713
- if (input.giselleProtectionBypass?.trim()) {
714
- env.GISELLE_PROTECTION_BYPASS = input.giselleProtectionBypass.trim();
715
- }
716
- return env;
717
- }
718
- async function patchGeminiSettingsEnv(sandbox, mcpEnv) {
719
- const buffer = await sandbox.readFileToBuffer({
720
- path: GEMINI_SETTINGS_PATH
721
- });
722
- if (!buffer) {
723
- throw new Error(
724
- `Gemini settings not found in sandbox at ${GEMINI_SETTINGS_PATH}. Ensure the snapshot contains a pre-configured settings.json.`
725
- );
726
- }
727
- const settings = JSON.parse(new TextDecoder().decode(buffer));
728
- if (settings.mcpServers) {
729
- settings.mcpServers = Object.fromEntries(
730
- Object.entries(settings.mcpServers).map(([key, server]) => [
731
- key,
732
- { ...server, env: { ...server.env, ...mcpEnv } }
733
- ])
734
- );
735
- }
736
- await sandbox.writeFiles([
737
- {
738
- path: GEMINI_SETTINGS_PATH,
739
- content: Buffer.from(JSON.stringify(settings, null, 2))
740
- }
741
- ]);
742
- }
743
- function emitText(controller, text, encoder) {
744
- if (text.length === 0) {
745
- return;
746
- }
747
- controller.enqueue(encoder.encode(text));
748
- }
749
- function emitEvent(controller, payload, encoder) {
750
- emitText(controller, `${JSON.stringify(payload)}
751
- `, encoder);
752
- }
753
- function createGeminiChatHandler(_options = {}) {
754
- const requestParser = requestSchema;
755
- return async function POST(request) {
756
- const payload = await request.json().catch(() => null);
757
- const parsed = requestParser.safeParse(payload);
758
- if (!parsed.success) {
759
- return Response.json(
760
- {
761
- error: "Invalid request payload.",
762
- detail: parsed.error.flatten()
763
- },
764
- { status: 400 }
765
- );
766
- }
767
- const stream = new ReadableStream({
768
- start(controller) {
769
- const encoder = new TextEncoder();
770
- const abortController = new AbortController();
771
- let closed = false;
772
- const close = () => {
773
- if (closed) {
774
- return;
775
- }
776
- closed = true;
777
- try {
778
- controller.close();
779
- } catch {
780
- }
781
- };
782
- const onAbort = () => {
783
- if (!abortController.signal.aborted) {
784
- abortController.abort();
785
- }
786
- close();
787
- };
788
- if (request.signal.aborted) {
789
- onAbort();
790
- return;
791
- }
792
- request.signal.addEventListener("abort", onAbort);
793
- const enqueueEvent = (payload2) => {
794
- if (closed) {
795
- return;
796
- }
797
- emitEvent(controller, payload2, encoder);
798
- };
799
- const enqueueStdout = (text) => {
800
- if (closed) {
801
- return;
802
- }
803
- emitText(controller, text, encoder);
804
- };
805
- (async () => {
806
- const geminiApiKey = requiredEnv("GEMINI_API_KEY");
807
- const sandboxSnapshotId = requiredEnv("SANDBOX_SNAPSHOT_ID");
808
- const oidcToken = extractTokenFromRequest(request) ?? process.env.VERCEL_OIDC_TOKEN ?? "";
809
- if (!oidcToken) {
810
- throw new Error(
811
- "Planner authentication is required: set OIDC token in x-vercel-oidc-token or VERCEL_OIDC_TOKEN."
812
- );
813
- }
814
- const vercelProtectionBypass = process.env.VERCEL_PROTECTION_BYPASS?.trim() || void 0;
815
- const giselleProtectionBypass = process.env.GISELLE_PROTECTION_PASSWORD?.trim() || void 0;
816
- const bridgeBaseUrl = process.env.BROWSER_TOOL_BRIDGE_BASE_URL?.trim() || new URL(request.url).origin;
817
- const {
818
- message,
819
- session_id: sessionId,
820
- sandbox_id: sandboxId,
821
- bridge_session_id: bridgeSessionId,
822
- bridge_token: bridgeToken
823
- } = parsed.data;
824
- const sandbox = sandboxId ? await Sandbox.get({ sandboxId }) : await Sandbox.create({
825
- source: {
826
- type: "snapshot",
827
- snapshotId: sandboxSnapshotId
828
- }
829
- });
830
- enqueueEvent({ type: "sandbox", sandbox_id: sandbox.sandboxId });
831
- const mcpEnv = buildMcpEnv({
832
- bridgeBaseUrl,
833
- bridgeSessionId,
834
- bridgeToken,
835
- oidcToken,
836
- vercelProtectionBypass,
837
- giselleProtectionBypass
838
- });
839
- await patchGeminiSettingsEnv(sandbox, mcpEnv);
840
- const args = [
841
- "--prompt",
842
- message,
843
- "--output-format",
844
- "stream-json",
845
- "--approval-mode",
846
- "yolo"
847
- ];
848
- if (sessionId) {
849
- args.push("--resume", sessionId);
850
- }
851
- await sandbox.runCommand({
852
- cmd: "gemini",
853
- args,
854
- env: {
855
- GEMINI_API_KEY: geminiApiKey
856
- },
857
- stdout: new Writable({
858
- write(chunk, _encoding, callback) {
859
- const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
860
- enqueueStdout(text);
861
- callback();
862
- }
863
- }),
864
- stderr: new Writable({
865
- write(chunk, _encoding, callback) {
866
- const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
867
- enqueueEvent({ type: "stderr", content: text });
868
- callback();
869
- }
870
- }),
871
- signal: abortController.signal
872
- });
873
- })().catch((error) => {
874
- if (abortController.signal.aborted) {
875
- return;
876
- }
877
- const message = error instanceof Error ? error.message : String(error);
878
- enqueueEvent({ type: "stderr", content: `[error] ${message}` });
879
- }).finally(() => {
880
- request.signal.removeEventListener("abort", onAbort);
881
- close();
882
- });
883
- }
884
- });
885
- return new Response(stream, {
886
- headers: {
887
- "Content-Type": "application/x-ndjson; charset=utf-8",
888
- "Cache-Control": "no-cache, no-transform"
889
- }
890
- });
886
+ GET: async (request) => createRelayEventsRoute(request),
887
+ POST: createRelayPostRoute
891
888
  };
892
889
  }
893
890
  export {
894
- BRIDGE_SSE_KEEPALIVE_INTERVAL_MS,
895
- assertBridgeSession,
896
- bridgeRequestChannel,
897
- createBridgeHandler,
898
- createBridgeSession,
899
- createBridgeSubscriber,
891
+ RELAY_SSE_KEEPALIVE_INTERVAL_MS,
892
+ assertRelaySession,
900
893
  createGeminiChatHandler,
901
- dispatchBridgeRequest,
902
- markBridgeBrowserConnected,
903
- resolveBridgeResponse,
904
- toBridgeError,
905
- touchBridgeBrowserConnected
894
+ createRelayHandler,
895
+ createRelaySession,
896
+ createRelaySubscriber,
897
+ dispatchRelayRequest,
898
+ markBrowserConnected,
899
+ relayRequestChannel,
900
+ resolveRelayResponse,
901
+ toRelayError,
902
+ touchBrowserConnected
906
903
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@giselles-ai/sandbox-agent-core",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "Apache-2.0",
@@ -26,7 +26,7 @@
26
26
  "format": "pnpm exec biome check --write ."
27
27
  },
28
28
  "dependencies": {
29
- "@giselles-ai/browser-tool": "0.1.2",
29
+ "@giselles-ai/browser-tool": "0.1.4",
30
30
  "@vercel/sandbox": "^1.0.0",
31
31
  "ioredis": "^5.9.2",
32
32
  "zod": "4.3.6"