From 332e95807d2a6e70694393e75ba212e19d6a7935 Mon Sep 17 00:00:00 2001 From: tnsardesai <18272584+tnsardesai@users.noreply.github.com> Date: Tue, 18 Aug 2026 01:40:30 +0000 Subject: [PATCH 1/2] Route computer and playwright calls directly to the VM Add computer and playwright to the default browser routing allowlist so screenshot and execute traffic can skip the control plane once metro-api records activity on the kernel proxy. --- src/lib/browser-routing.ts | 2 +- tests/lib/browser-routing.test.ts | 98 ++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 4 deletions(-) diff --git a/src/lib/browser-routing.ts b/src/lib/browser-routing.ts index 7c5f0480..d0272a75 100644 --- a/src/lib/browser-routing.ts +++ b/src/lib/browser-routing.ts @@ -31,7 +31,7 @@ const BROWSER_ROUTING_SUBRESOURCES_ENV = 'KERNEL_BROWSER_ROUTING_SUBRESOURCES'; // Path prefixes eligible for direct-to-VM routing. "telemetry/stream" is the live // SSE endpoint (served by the VM); "telemetry/events" is a historical read served // by the control plane (S2) and must NOT be here. -const DEFAULT_BROWSER_ROUTING_SUBRESOURCES = ['curl', 'telemetry/stream']; +const DEFAULT_BROWSER_ROUTING_SUBRESOURCES = ['curl', 'telemetry/stream', 'computer', 'playwright']; const BROWSER_ROUTE_CACHEABLE_PATH = /^\/(?:v\d+\/)?browsers(?:\/[^/]+)?\/?$/; const BROWSER_POOL_ACQUIRE_PATH = /^\/(?:v\d+\/)?browser_pools\/[^/]+\/acquire\/?$/; const BROWSER_DELETE_BY_ID_PATH = /^\/(?:v\d+\/)?browsers\/([^/]+)\/?$/; diff --git a/tests/lib/browser-routing.test.ts b/tests/lib/browser-routing.test.ts index 79ce4c87..3d036f2a 100644 --- a/tests/lib/browser-routing.test.ts +++ b/tests/lib/browser-routing.test.ts @@ -444,20 +444,28 @@ describe('browser routing', () => { ).rejects.toThrow(/unsupported HTTP method/i); }); - test('defaults browser routing subresources to curl and telemetry/stream when env is unset', async () => { + test('defaults browser routing subresources to curl, telemetry/stream, computer, and playwright when env is unset', async () => { await withBrowserRoutingEnv(undefined, async () => { - expect(browserRoutingSubresourcesFromEnv()).toEqual(['curl', 'telemetry/stream']); + expect(browserRoutingSubresourcesFromEnv()).toEqual([ + 'curl', + 'telemetry/stream', + 'computer', + 'playwright', + ]); }); }); test('allowlist matching is segment-boundary aware (telemetry/events stays on the control plane)', () => { - const prefixes = ['curl', 'telemetry/stream']; + const prefixes = ['curl', 'telemetry/stream', 'computer', 'playwright']; expect(matchesDirectVMPrefix('telemetry/stream', prefixes)).toBe(true); expect(matchesDirectVMPrefix('telemetry/stream/x', prefixes)).toBe(true); expect(matchesDirectVMPrefix('telemetry/events', prefixes)).toBe(false); expect(matchesDirectVMPrefix('telemetry/streaming-config', prefixes)).toBe(false); expect(matchesDirectVMPrefix('telemetry', prefixes)).toBe(false); expect(matchesDirectVMPrefix('curl/raw', prefixes)).toBe(true); + expect(matchesDirectVMPrefix('computer/screenshot', prefixes)).toBe(true); + expect(matchesDirectVMPrefix('playwright/execute', prefixes)).toBe(true); + expect(matchesDirectVMPrefix('process/exec', prefixes)).toBe(false); expect(matchesDirectVMPrefix('fs/read', prefixes)).toBe(false); }); @@ -498,4 +506,88 @@ describe('browser routing', () => { expect(browserRoutingSubresourcesFromEnv()).toEqual([]); }); }); + + test('routes computer screenshot and playwright execute to the VM by default', async () => { + await withBrowserRoutingEnv(undefined, async () => { + const calls: Array<{ url: string; headers: Headers }> = []; + const kernel = new Kernel({ + apiKey: 'k', + baseURL: 'https://api.example/', + fetch: async (input, init?: RequestInit) => { + const url = normalizeURL(input); + const headers = input instanceof Request ? new Headers(input.headers) : new Headers(init?.headers); + calls.push({ url, headers }); + if (url === 'https://api.example/browsers') { + return Response.json({ + session_id: 'sess-1', + base_url: 'http://browser-session.test/browser/kernel', + cdp_ws_url: 'wss://browser-session.test/browser/cdp?jwt=token-abc', + }); + } + if (url.includes('/computer/screenshot')) { + return new Response(new Uint8Array([1, 2, 3]), { + status: 200, + headers: { 'content-type': 'image/png' }, + }); + } + return Response.json({ success: true }); + }, + }); + + await kernel.browsers.create(); + await kernel.browsers.computer.captureScreenshot('sess-1'); + await kernel.browsers.playwright.execute('sess-1', { code: 'return 1' }); + + expect(calls[1]?.url).toBe( + 'http://browser-session.test/browser/kernel/computer/screenshot?jwt=token-abc', + ); + expect(calls[1]?.headers.get('authorization')).toBeNull(); + expect(calls[2]?.url).toBe( + 'http://browser-session.test/browser/kernel/playwright/execute?jwt=token-abc', + ); + expect(calls[2]?.headers.get('authorization')).toBeNull(); + }); + }); + + test('keeps process, fs, and telemetry/events on the API origin by default', async () => { + await withBrowserRoutingEnv(undefined, async () => { + const calls: string[] = []; + const kernel = new Kernel({ + apiKey: 'k', + baseURL: 'https://api.example/', + fetch: async (input) => { + const url = normalizeURL(input); + calls.push(url); + if (url === 'https://api.example/browsers') { + return Response.json({ + session_id: 'sess-1', + base_url: 'http://browser-session.test/browser/kernel', + cdp_ws_url: 'wss://browser-session.test/browser/cdp?jwt=token-abc', + }); + } + if (url.includes('/telemetry/events')) { + return Response.json([]); + } + if (url.includes('/fs/read_file')) { + return new Response(new Uint8Array([1]), { + status: 200, + headers: { 'content-type': 'application/octet-stream' }, + }); + } + return Response.json({ exit_code: 0, stdout_b64: '', stderr_b64: '' }); + }, + }); + + await kernel.browsers.create(); + await kernel.browsers.process.exec('sess-1', { command: 'echo' }); + await kernel.browsers.fs.readFile('sess-1', { path: '/tmp/x' }); + await kernel.browsers.telemetry.events('sess-1'); + + expect(calls.slice(1)).toEqual([ + 'https://api.example/browsers/sess-1/process/exec', + 'https://api.example/browsers/sess-1/fs/read_file?path=%2Ftmp%2Fx', + 'https://api.example/browsers/sess-1/telemetry/events', + ]); + }); + }); }); From 8439fe578dbda6f2ec6b159a52ba0d207df4f80a Mon Sep 17 00:00:00 2001 From: tnsardesai <18272584+tnsardesai@users.noreply.github.com> Date: Tue, 18 Aug 2026 21:02:56 +0000 Subject: [PATCH 2/2] Shorten default-allowlist test name The asserted list can grow; the test name should not. --- tests/lib/browser-routing.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lib/browser-routing.test.ts b/tests/lib/browser-routing.test.ts index 3d036f2a..93320230 100644 --- a/tests/lib/browser-routing.test.ts +++ b/tests/lib/browser-routing.test.ts @@ -444,7 +444,7 @@ describe('browser routing', () => { ).rejects.toThrow(/unsupported HTTP method/i); }); - test('defaults browser routing subresources to curl, telemetry/stream, computer, and playwright when env is unset', async () => { + test('defaults browser routing subresources when env is unset', async () => { await withBrowserRoutingEnv(undefined, async () => { expect(browserRoutingSubresourcesFromEnv()).toEqual([ 'curl',