From 7d722511fe00510faf4406b4fac89ff748aa3662 Mon Sep 17 00:00:00 2001 From: Jayko001 <86390011+Jayko001@users.noreply.github.com> Date: Tue, 2 Jun 2026 00:19:14 +0000 Subject: [PATCH 1/2] feat(browser-pools): typed acquire helper distinguishing got / timed-out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `acquireBrowserFromPool` returning a discriminated union — `{ status: 'acquired', browser }` on 200 or `{ status: 'timed_out' }` on 204 — so callers can disambiguate the long-poll timeout from a successful lease without inspecting the raw HTTP status. 404 continues to reject with the existing `NotFoundError`. Co-Authored-By: Claude Opus 4.7 --- src/index.ts | 1 + src/lib/browser-pools.ts | 31 +++++++++++++++++++++++++ tests/lib/browser-pools.test.ts | 41 +++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 src/lib/browser-pools.ts create mode 100644 tests/lib/browser-pools.test.ts diff --git a/src/index.ts b/src/index.ts index 81aa335..b02338a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ export { APIPromise } from './core/api-promise'; export { Kernel, type ClientOptions } from './client'; export { type BrowserFetchInit } from './lib/browser-fetch'; export { BrowserRouteCache, type BrowserRoute } from './lib/browser-routing'; +export { acquire as acquireBrowserFromPool, type AcquireOutcome } from './lib/browser-pools'; export { PagePromise } from './core/pagination'; export { KernelError, diff --git a/src/lib/browser-pools.ts b/src/lib/browser-pools.ts new file mode 100644 index 0000000..50811ac --- /dev/null +++ b/src/lib/browser-pools.ts @@ -0,0 +1,31 @@ +import type { Kernel } from '../client'; +import type { RequestOptions } from '../internal/request-options'; +import type { BrowserPoolAcquireParams, BrowserPoolAcquireResponse } from '../resources/browser-pools'; + +export type AcquireOutcome = + | { status: 'acquired'; browser: BrowserPoolAcquireResponse } + | { status: 'timed_out' }; + +/** + * Long-polling acquire that surfaces the HTTP outcome as a typed result. + * + * Resolves to one of: + * + * - `{ status: 'acquired', browser }` — a browser was leased from the pool. + * - `{ status: 'timed_out' }` — the long poll expired without a browser becoming + * available. Retry to keep waiting. + * + * Rejects with `NotFoundError` if the pool does not exist. + */ +export async function acquire( + client: Kernel, + idOrName: string, + body: BrowserPoolAcquireParams = {}, + options?: RequestOptions, +): Promise { + const { data, response } = await client.browserPools.acquire(idOrName, body, options).withResponse(); + if (response.status === 204) { + return { status: 'timed_out' }; + } + return { status: 'acquired', browser: data }; +} diff --git a/tests/lib/browser-pools.test.ts b/tests/lib/browser-pools.test.ts new file mode 100644 index 0000000..7ea8a5f --- /dev/null +++ b/tests/lib/browser-pools.test.ts @@ -0,0 +1,41 @@ +import Kernel, { NotFoundError } from '@onkernel/sdk'; + +import { acquire, type AcquireOutcome } from '../../src/lib/browser-pools'; + +const baseBrowser = { + session_id: 'sess-1', + base_url: 'http://browser-session.test/browser/kernel', + cdp_ws_url: 'wss://browser-session.test/browser/cdp?jwt=t', + webdriver_ws_url: 'wss://x', + created_at: '2020-01-01T00:00:00Z', + headless: true, + stealth: false, + timeout_seconds: 60, +}; + +const clientWith = (fetcher: typeof fetch) => + new Kernel({ apiKey: 'k', baseURL: 'https://api.example/', fetch: fetcher }); + +describe('browser pool typed acquire', () => { + test('resolves to acquired on 200', async () => { + const client = clientWith(async () => Response.json(baseBrowser)); + const result: AcquireOutcome = await acquire(client, 'my-pool'); + expect(result.status).toBe('acquired'); + if (result.status === 'acquired') { + expect(result.browser.session_id).toBe('sess-1'); + } + }); + + test('resolves to timed_out on 204', async () => { + const client = clientWith(async () => new Response(null, { status: 204 })); + const result = await acquire(client, 'my-pool'); + expect(result.status).toBe('timed_out'); + }); + + test('rejects with NotFoundError on 404', async () => { + const client = clientWith(async () => + Response.json({ code: 'not_found', message: 'pool not found' }, { status: 404 }), + ); + await expect(acquire(client, 'missing')).rejects.toBeInstanceOf(NotFoundError); + }); +}); From fb42e5875fbefa3d6a629080b59382df87edbff1 Mon Sep 17 00:00:00 2001 From: Jayko001 <86390011+Jayko001@users.noreply.github.com> Date: Tue, 2 Jun 2026 02:01:14 +0000 Subject: [PATCH 2/2] add not_found variant so callers don't need a try/catch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `{ status: 'not_found' }` to AcquireOutcome and wraps NotFoundError internally so all three documented outcomes — acquired, timed_out, not_found — are reachable via the same `result.status` switch. Co-Authored-By: Claude Opus 4.7 --- src/lib/browser-pools.ts | 22 ++++++++++++++++------ tests/lib/browser-pools.test.ts | 7 ++++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/lib/browser-pools.ts b/src/lib/browser-pools.ts index 50811ac..34d9d04 100644 --- a/src/lib/browser-pools.ts +++ b/src/lib/browser-pools.ts @@ -1,10 +1,12 @@ +import { NotFoundError } from '../core/error'; import type { Kernel } from '../client'; import type { RequestOptions } from '../internal/request-options'; import type { BrowserPoolAcquireParams, BrowserPoolAcquireResponse } from '../resources/browser-pools'; export type AcquireOutcome = | { status: 'acquired'; browser: BrowserPoolAcquireResponse } - | { status: 'timed_out' }; + | { status: 'timed_out' } + | { status: 'not_found' }; /** * Long-polling acquire that surfaces the HTTP outcome as a typed result. @@ -14,8 +16,9 @@ export type AcquireOutcome = * - `{ status: 'acquired', browser }` — a browser was leased from the pool. * - `{ status: 'timed_out' }` — the long poll expired without a browser becoming * available. Retry to keep waiting. + * - `{ status: 'not_found' }` — no pool exists with the given id or name. * - * Rejects with `NotFoundError` if the pool does not exist. + * Other API errors (auth, server errors, etc.) still reject. */ export async function acquire( client: Kernel, @@ -23,9 +26,16 @@ export async function acquire( body: BrowserPoolAcquireParams = {}, options?: RequestOptions, ): Promise { - const { data, response } = await client.browserPools.acquire(idOrName, body, options).withResponse(); - if (response.status === 204) { - return { status: 'timed_out' }; + try { + const { data, response } = await client.browserPools.acquire(idOrName, body, options).withResponse(); + if (response.status === 204) { + return { status: 'timed_out' }; + } + return { status: 'acquired', browser: data }; + } catch (err) { + if (err instanceof NotFoundError) { + return { status: 'not_found' }; + } + throw err; } - return { status: 'acquired', browser: data }; } diff --git a/tests/lib/browser-pools.test.ts b/tests/lib/browser-pools.test.ts index 7ea8a5f..b7ce258 100644 --- a/tests/lib/browser-pools.test.ts +++ b/tests/lib/browser-pools.test.ts @@ -1,4 +1,4 @@ -import Kernel, { NotFoundError } from '@onkernel/sdk'; +import Kernel from '@onkernel/sdk'; import { acquire, type AcquireOutcome } from '../../src/lib/browser-pools'; @@ -32,10 +32,11 @@ describe('browser pool typed acquire', () => { expect(result.status).toBe('timed_out'); }); - test('rejects with NotFoundError on 404', async () => { + test('resolves to not_found on 404', async () => { const client = clientWith(async () => Response.json({ code: 'not_found', message: 'pool not found' }, { status: 404 }), ); - await expect(acquire(client, 'missing')).rejects.toBeInstanceOf(NotFoundError); + const result = await acquire(client, 'missing'); + expect(result.status).toBe('not_found'); }); });