From 2c4d153399f48723d5eb4680379426029356e44c Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Wed, 3 Jun 2026 10:13:00 -0400 Subject: [PATCH 1/5] feat: route browser telemetry directly to the VM by default Add "telemetry" to the default KERNEL_BROWSER_ROUTING_SUBRESOURCES list so telemetry SSE streams are routed straight to the browser VM, and change the telemetry stream method path from /browsers/{id}/telemetry to /browsers/{id}/telemetry/stream so the direct-routing rewrite yields {base_url}/telemetry/stream on the VM (the VM's /telemetry is a different, non-streaming endpoint). DEPENDS ON the control-plane PR renaming the public endpoint /browsers/{id}/telemetry -> /browsers/{id}/telemetry/stream. Until that deploys, telemetry.stream() only works via direct routing. Verified with a live smoke test against prod: the telemetry stream request is rewritten to the VM proxy host (.../telemetry/stream?jwt=...), the Authorization header is stripped, and an api_call telemetry event arrives within ~1s of generating activity. Co-Authored-By: Claude Opus 4.8 (1M context) --- examples/smoke-browser-telemetry.ts | 141 ++++++++++++++++++++++++++++ scripts/smoke-browser-telemetry | 7 ++ src/lib/browser-routing.ts | 2 +- tests/lib/browser-routing.test.ts | 36 ++++++- 4 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 examples/smoke-browser-telemetry.ts create mode 100755 scripts/smoke-browser-telemetry diff --git a/examples/smoke-browser-telemetry.ts b/examples/smoke-browser-telemetry.ts new file mode 100644 index 00000000..27f4c50b --- /dev/null +++ b/examples/smoke-browser-telemetry.ts @@ -0,0 +1,141 @@ +import Kernel from '@onkernel/sdk'; + +function assert(condition: unknown, message: string): asserts condition { + if (!condition) { + throw new Error(message); + } +} + +function normalizeURL(input: unknown): string { + if (typeof input === 'string') { + return input; + } + if (input instanceof URL) { + return input.toString(); + } + return (input as Request).url; +} + +function authHeaderPresent(input: unknown, init?: RequestInit): boolean { + const headers = input instanceof Request ? new Headers(input.headers) : new Headers(init?.headers); + return headers.has('authorization'); +} + +async function main() { + // Telemetry is now a default routing subresource; set the env var explicitly to be safe. + process.env['KERNEL_BROWSER_ROUTING_SUBRESOURCES'] = 'curl,telemetry'; + + const records: Array<{ url: string; auth: boolean }> = []; + const realFetch: typeof fetch = fetch; + + const kernel = new Kernel({ + baseURL: process.env['KERNEL_BASE_URL'] || 'https://api.onkernel.com', + fetch: async (input, init) => { + records.push({ url: normalizeURL(input), auth: authHeaderPresent(input, init as RequestInit) }); + return realFetch(input as any, init as any); + }, + }); + + let sessionID: string | undefined; + + try { + console.log(`Using Kernel API ${kernel.baseURL}`); + const browser = await kernel.browsers.create({ + headless: true, + timeout_seconds: 120, + telemetry: { enabled: true }, + }); + sessionID = browser.session_id; + console.log(`Created browser ${sessionID}`); + + const route = kernel.browserRouteCache.get(sessionID); + assert(route, `expected a cached route for session ${sessionID}`); + const baseHost = new URL(route.baseURL).host; + console.log(`Cached VM base_url host: ${baseHost}`); + + const recordsBeforeStream = records.length; + const stream = await kernel.browsers.telemetry.stream(sessionID); + console.log(`Opened telemetry stream`); + + // The telemetry stream request should be the most recent recorded request. + const streamReq = records[records.length - 1]; + assert(streamReq, 'no recorded request for telemetry stream'); + assert(records.length > recordsBeforeStream, 'telemetry stream did not produce an outbound request'); + + const streamURL = new URL(streamReq.url); + console.log(`Telemetry stream outbound URL: ${streamReq.url} (auth=${streamReq.auth})`); + + assert( + streamURL.host === baseHost, + `telemetry stream host ${streamURL.host} did not match VM base_url host ${baseHost}`, + ); + assert(streamURL.host !== 'api.onkernel.com', 'telemetry stream was NOT routed (still api.onkernel.com)'); + assert( + streamURL.pathname.endsWith('/telemetry/stream'), + `telemetry stream path ${streamURL.pathname} did not end with /telemetry/stream`, + ); + assert(!!streamURL.searchParams.get('jwt'), 'telemetry stream URL missing jwt query param'); + assert(!streamReq.auth, 'Authorization header was NOT stripped on the routed telemetry stream request'); + console.log( + `Routing confirmed: stream -> ${streamURL.host}${streamURL.pathname} (jwt present, auth stripped)`, + ); + + // Generate activity so the "api" telemetry category emits an event. + const activity = (async () => { + try { + await kernel.browsers.curl(sessionID!, { + url: 'https://example.com/', + method: 'GET', + response_encoding: 'utf8', + timeout_ms: 10_000, + }); + console.log('Generated activity via browsers.curl'); + } catch (error) { + console.error('activity curl failed', error); + } + })(); + + let eventCount = 0; + const deadline = Date.now() + 25_000; + const reader = (async () => { + for await (const event of stream) { + eventCount += 1; + console.log( + `telemetry event #${eventCount}: seq=${(event as any)?.seq} type=${(event as any)?.event?.type}`, + ); + break; + } + })(); + + await activity; + await Promise.race([ + reader, + new Promise((resolve) => { + const timer = setInterval(() => { + if (eventCount > 0 || Date.now() > deadline) { + clearInterval(timer); + resolve(); + } + }, 250); + }), + ]); + + assert(eventCount >= 1, `expected at least one telemetry event within 25s, got ${eventCount}`); + console.log(`PASS telemetry stream received ${eventCount} event(s) over direct-routed VM connection`); + console.log(`SMOKE_RESULT eventsObserved=${eventCount} routedURL=${streamReq.url}`); + } finally { + if (sessionID) { + console.log(`Deleting browser ${sessionID}`); + try { + await kernel.browsers.deleteByID(sessionID); + } catch (error) { + console.error(`Failed to delete browser ${sessionID}`, error); + } + } + } +} + +void main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/scripts/smoke-browser-telemetry b/scripts/smoke-browser-telemetry new file mode 100755 index 00000000..3f374f84 --- /dev/null +++ b/scripts/smoke-browser-telemetry @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cd "$(dirname "$0")/.." + +./node_modules/.bin/ts-node -r tsconfig-paths/register examples/smoke-browser-telemetry.ts "$@" diff --git a/src/lib/browser-routing.ts b/src/lib/browser-routing.ts index 6580da6c..bbedec29 100644 --- a/src/lib/browser-routing.ts +++ b/src/lib/browser-routing.ts @@ -28,7 +28,7 @@ export class BrowserRouteCache { } const BROWSER_ROUTING_SUBRESOURCES_ENV = 'KERNEL_BROWSER_ROUTING_SUBRESOURCES'; -const DEFAULT_BROWSER_ROUTING_SUBRESOURCES = ['curl']; +const DEFAULT_BROWSER_ROUTING_SUBRESOURCES = ['curl', 'telemetry']; 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 1ca285cc..da87ad6e 100644 --- a/tests/lib/browser-routing.test.ts +++ b/tests/lib/browser-routing.test.ts @@ -381,9 +381,41 @@ describe('browser routing', () => { ).rejects.toThrow(/unsupported HTTP method/i); }); - test('defaults browser routing subresources to curl when env is unset', async () => { + test('defaults browser routing subresources to curl and telemetry when env is unset', async () => { await withBrowserRoutingEnv(undefined, async () => { - expect(browserRoutingSubresourcesFromEnv()).toEqual(['curl']); + expect(browserRoutingSubresourcesFromEnv()).toEqual(['curl', 'telemetry']); + }); + }); + + test('routes telemetry stream calls to the VM /telemetry/stream path 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', + }); + } + return new Response('id: 1\ndata: {"seq":1}\n\n', { + status: 200, + headers: { 'content-type': 'text/event-stream' }, + }); + }, + }); + + await kernel.browsers.create(); + await kernel.browsers.telemetry.stream('sess-1'); + + expect(calls[1]?.url).toBe('http://browser-session.test/browser/kernel/telemetry/stream?jwt=token-abc'); + expect(calls[1]?.headers.get('authorization')).toBeNull(); }); }); From 203054736d39a77e3e55895668130655a680b0c5 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Wed, 3 Jun 2026 10:40:42 -0400 Subject: [PATCH 2/5] refactor(examples): rename telemetry example to browser-telemetry Co-Authored-By: Claude Opus 4.8 (1M context) --- examples/browser-telemetry.ts | 30 ++++++ examples/smoke-browser-telemetry.ts | 141 ---------------------------- scripts/smoke-browser-telemetry | 7 -- 3 files changed, 30 insertions(+), 148 deletions(-) create mode 100644 examples/browser-telemetry.ts delete mode 100644 examples/smoke-browser-telemetry.ts delete mode 100755 scripts/smoke-browser-telemetry diff --git a/examples/browser-telemetry.ts b/examples/browser-telemetry.ts new file mode 100644 index 00000000..e2dbe438 --- /dev/null +++ b/examples/browser-telemetry.ts @@ -0,0 +1,30 @@ +import Kernel from '@onkernel/sdk'; + +async function main() { + const kernel = new Kernel(); + + // Create a browser with telemetry enabled so it emits events while it runs. + const browser = await kernel.browsers.create({ telemetry: { enabled: true } }); + + try { + // Telemetry is a default routing subresource, so the stream goes directly to the VM automatically. + const stream = await kernel.browsers.telemetry.stream(browser.session_id); + + // Make browser activity to generate telemetry. The "api" category emits an event per VM API call, + // so events arrive within ~1s. + for (let i = 0; i < 3; i++) { + await kernel.browsers.curl(browser.session_id, { url: 'https://example.com', method: 'GET' }); + } + + // Print a few events, then stop so the program terminates promptly. + let count = 0; + for await (const event of stream) { + console.log('telemetry event', event); + if (++count >= 3) break; + } + } finally { + await kernel.browsers.deleteByID(browser.session_id); + } +} + +void main(); diff --git a/examples/smoke-browser-telemetry.ts b/examples/smoke-browser-telemetry.ts deleted file mode 100644 index 27f4c50b..00000000 --- a/examples/smoke-browser-telemetry.ts +++ /dev/null @@ -1,141 +0,0 @@ -import Kernel from '@onkernel/sdk'; - -function assert(condition: unknown, message: string): asserts condition { - if (!condition) { - throw new Error(message); - } -} - -function normalizeURL(input: unknown): string { - if (typeof input === 'string') { - return input; - } - if (input instanceof URL) { - return input.toString(); - } - return (input as Request).url; -} - -function authHeaderPresent(input: unknown, init?: RequestInit): boolean { - const headers = input instanceof Request ? new Headers(input.headers) : new Headers(init?.headers); - return headers.has('authorization'); -} - -async function main() { - // Telemetry is now a default routing subresource; set the env var explicitly to be safe. - process.env['KERNEL_BROWSER_ROUTING_SUBRESOURCES'] = 'curl,telemetry'; - - const records: Array<{ url: string; auth: boolean }> = []; - const realFetch: typeof fetch = fetch; - - const kernel = new Kernel({ - baseURL: process.env['KERNEL_BASE_URL'] || 'https://api.onkernel.com', - fetch: async (input, init) => { - records.push({ url: normalizeURL(input), auth: authHeaderPresent(input, init as RequestInit) }); - return realFetch(input as any, init as any); - }, - }); - - let sessionID: string | undefined; - - try { - console.log(`Using Kernel API ${kernel.baseURL}`); - const browser = await kernel.browsers.create({ - headless: true, - timeout_seconds: 120, - telemetry: { enabled: true }, - }); - sessionID = browser.session_id; - console.log(`Created browser ${sessionID}`); - - const route = kernel.browserRouteCache.get(sessionID); - assert(route, `expected a cached route for session ${sessionID}`); - const baseHost = new URL(route.baseURL).host; - console.log(`Cached VM base_url host: ${baseHost}`); - - const recordsBeforeStream = records.length; - const stream = await kernel.browsers.telemetry.stream(sessionID); - console.log(`Opened telemetry stream`); - - // The telemetry stream request should be the most recent recorded request. - const streamReq = records[records.length - 1]; - assert(streamReq, 'no recorded request for telemetry stream'); - assert(records.length > recordsBeforeStream, 'telemetry stream did not produce an outbound request'); - - const streamURL = new URL(streamReq.url); - console.log(`Telemetry stream outbound URL: ${streamReq.url} (auth=${streamReq.auth})`); - - assert( - streamURL.host === baseHost, - `telemetry stream host ${streamURL.host} did not match VM base_url host ${baseHost}`, - ); - assert(streamURL.host !== 'api.onkernel.com', 'telemetry stream was NOT routed (still api.onkernel.com)'); - assert( - streamURL.pathname.endsWith('/telemetry/stream'), - `telemetry stream path ${streamURL.pathname} did not end with /telemetry/stream`, - ); - assert(!!streamURL.searchParams.get('jwt'), 'telemetry stream URL missing jwt query param'); - assert(!streamReq.auth, 'Authorization header was NOT stripped on the routed telemetry stream request'); - console.log( - `Routing confirmed: stream -> ${streamURL.host}${streamURL.pathname} (jwt present, auth stripped)`, - ); - - // Generate activity so the "api" telemetry category emits an event. - const activity = (async () => { - try { - await kernel.browsers.curl(sessionID!, { - url: 'https://example.com/', - method: 'GET', - response_encoding: 'utf8', - timeout_ms: 10_000, - }); - console.log('Generated activity via browsers.curl'); - } catch (error) { - console.error('activity curl failed', error); - } - })(); - - let eventCount = 0; - const deadline = Date.now() + 25_000; - const reader = (async () => { - for await (const event of stream) { - eventCount += 1; - console.log( - `telemetry event #${eventCount}: seq=${(event as any)?.seq} type=${(event as any)?.event?.type}`, - ); - break; - } - })(); - - await activity; - await Promise.race([ - reader, - new Promise((resolve) => { - const timer = setInterval(() => { - if (eventCount > 0 || Date.now() > deadline) { - clearInterval(timer); - resolve(); - } - }, 250); - }), - ]); - - assert(eventCount >= 1, `expected at least one telemetry event within 25s, got ${eventCount}`); - console.log(`PASS telemetry stream received ${eventCount} event(s) over direct-routed VM connection`); - console.log(`SMOKE_RESULT eventsObserved=${eventCount} routedURL=${streamReq.url}`); - } finally { - if (sessionID) { - console.log(`Deleting browser ${sessionID}`); - try { - await kernel.browsers.deleteByID(sessionID); - } catch (error) { - console.error(`Failed to delete browser ${sessionID}`, error); - } - } - } -} - -void main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/scripts/smoke-browser-telemetry b/scripts/smoke-browser-telemetry deleted file mode 100755 index 3f374f84..00000000 --- a/scripts/smoke-browser-telemetry +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -cd "$(dirname "$0")/.." - -./node_modules/.bin/ts-node -r tsconfig-paths/register examples/smoke-browser-telemetry.ts "$@" From a0c377d9a05c8f3f81c763be868b60d53663f772 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 17:45:30 +0000 Subject: [PATCH 3/5] feat: Fix browser pool update schema --- .stats.yml | 4 +-- src/resources/browser-pools.ts | 15 +++++------ tests/api-resources/browser-pools.test.ts | 32 ++--------------------- 3 files changed, 11 insertions(+), 40 deletions(-) diff --git a/.stats.yml b/.stats.yml index 5a5e5632..57627cd0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 117 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-1acd8f0b76ab00e36b53cc3ca90b72b2199f3388b3e307890adb464b87f9a2d8.yml -openapi_spec_hash: 82003125c1c2c5d82d19270bafb4a6ca +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-2d1337eec44e036b9c896b7db4691f0a12edfa79d3f28b611818bcedf62d44ee.yml +openapi_spec_hash: 30110dbbe733b16e40a6d0aa41d0c8c4 config_hash: ede72e4ae65cc5a6d6927938b3455c46 diff --git a/src/resources/browser-pools.ts b/src/resources/browser-pools.ts index dc1af440..c6e78492 100644 --- a/src/resources/browser-pools.ts +++ b/src/resources/browser-pools.ts @@ -48,7 +48,6 @@ export class BrowserPools extends APIResource { * ```ts * const browserPool = await client.browserPools.update( * 'id_or_name', - * { size: 10 }, * ); * ``` */ @@ -494,13 +493,6 @@ export interface BrowserPoolCreateParams { } export interface BrowserPoolUpdateParams { - /** - * Number of browsers to maintain in the pool. The maximum size is determined by - * your organization's pooled sessions limit (the sum of all pool sizes cannot - * exceed your limit). - */ - size: number; - /** * Custom Chrome enterprise policy overrides applied to all browsers in this pool. * Keys are Chrome enterprise policy names; values must match their expected types. @@ -554,6 +546,13 @@ export interface BrowserPoolUpdateParams { */ proxy_id?: string; + /** + * Number of browsers to maintain in the pool. The maximum size is determined by + * your organization's pooled sessions limit (the sum of all pool sizes cannot + * exceed your limit). + */ + size?: number; + /** * Optional URL to navigate to when a new browser is warmed into the pool. * Best-effort: failures to navigate do not fail pool fill. Only applied to diff --git a/tests/api-resources/browser-pools.test.ts b/tests/api-resources/browser-pools.test.ts index 7a473ba2..dff2eeda 100644 --- a/tests/api-resources/browser-pools.test.ts +++ b/tests/api-resources/browser-pools.test.ts @@ -60,8 +60,8 @@ describe('resource browserPools', () => { }); // Mock server tests are disabled - test.skip('update: only required params', async () => { - const responsePromise = client.browserPools.update('id_or_name', { size: 10 }); + test.skip('update', async () => { + const responsePromise = client.browserPools.update('id_or_name', {}); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -71,34 +71,6 @@ describe('resource browserPools', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Mock server tests are disabled - test.skip('update: required and optional params', async () => { - const response = await client.browserPools.update('id_or_name', { - size: 10, - chrome_policy: { foo: 'bar' }, - discard_all_idle: false, - extensions: [{ id: 'id', name: 'name' }], - fill_rate_per_minute: 0, - headless: false, - kiosk_mode: true, - name: 'my-pool', - profile: { - id: 'id', - name: 'name', - save_changes: true, - }, - proxy_id: 'proxy_id', - start_url: 'https://example.com', - stealth: true, - timeout_seconds: 60, - viewport: { - height: 800, - width: 1280, - refresh_rate: 60, - }, - }); - }); - // Mock server tests are disabled test.skip('list', async () => { const responsePromise = client.browserPools.list(); From 1e0f035be5c62191399afa4843eb5b15f32001e6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 19:40:36 +0000 Subject: [PATCH 4/5] feat: Add API-backed API key management endpoints --- .stats.yml | 4 ++-- src/resources/api-keys.ts | 18 +++++++++++++++++- tests/api-resources/api-keys.test.ts | 11 ++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index 57627cd0..9f173dc5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 117 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-2d1337eec44e036b9c896b7db4691f0a12edfa79d3f28b611818bcedf62d44ee.yml -openapi_spec_hash: 30110dbbe733b16e40a6d0aa41d0c8c4 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-3a1db7f11a92b28681929255ada59d2317ee4db98b5ff5aa6ce142a0664058b3.yml +openapi_spec_hash: b3064eaa589ae2a84993686ad1a3ee43 config_hash: ede72e4ae65cc5a6d6927938b3455c46 diff --git a/src/resources/api-keys.ts b/src/resources/api-keys.ts index cad02c32..a855c962 100644 --- a/src/resources/api-keys.ts +++ b/src/resources/api-keys.ts @@ -181,7 +181,23 @@ export interface APIKeyUpdateParams { name: string; } -export interface APIKeyListParams extends OffsetPaginationParams {} +export interface APIKeyListParams extends OffsetPaginationParams { + /** + * Case-insensitive substring match against API key name, creator, and project. API + * key identifiers and masked keys match by exact value or prefix. + */ + query?: string; + + /** + * Field to sort API keys by. + */ + sort_by?: 'created_at' | 'name' | 'expires_at'; + + /** + * Sort direction for API keys. + */ + sort_direction?: 'asc' | 'desc'; +} export declare namespace APIKeys { export { diff --git a/tests/api-resources/api-keys.test.ts b/tests/api-resources/api-keys.test.ts index c39c2228..14a45a0d 100644 --- a/tests/api-resources/api-keys.test.ts +++ b/tests/api-resources/api-keys.test.ts @@ -74,7 +74,16 @@ describe('resource apiKeys', () => { test.skip('list: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( - client.apiKeys.list({ limit: 100, offset: 0 }, { path: '/_stainless_unknown_path' }), + client.apiKeys.list( + { + limit: 100, + offset: 0, + query: 'query', + sort_by: 'created_at', + sort_direction: 'asc', + }, + { path: '/_stainless_unknown_path' }, + ), ).rejects.toThrow(Kernel.NotFoundError); }); From 0e8af794a4c06cc537b7eddb00cc2919c6e332d5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 19:41:06 +0000 Subject: [PATCH 5/5] release: 0.60.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 15 +++++++++++++++ package.json | 2 +- src/version.ts | 2 +- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index bfeb4872..85b1765b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.59.0" + ".": "0.60.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 37b5c5c5..ea88ea69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 0.60.0 (2026-06-03) + +Full Changelog: [v0.59.0...v0.60.0](https://github.com/kernel/kernel-node-sdk/compare/v0.59.0...v0.60.0) + +### Features + +* Add API-backed API key management endpoints ([1e0f035](https://github.com/kernel/kernel-node-sdk/commit/1e0f035be5c62191399afa4843eb5b15f32001e6)) +* Fix browser pool update schema ([a0c377d](https://github.com/kernel/kernel-node-sdk/commit/a0c377d9a05c8f3f81c763be868b60d53663f772)) +* route browser telemetry directly to the VM by default ([2c4d153](https://github.com/kernel/kernel-node-sdk/commit/2c4d153399f48723d5eb4680379426029356e44c)) + + +### Refactors + +* **examples:** rename telemetry example to browser-telemetry ([2030547](https://github.com/kernel/kernel-node-sdk/commit/203054736d39a77e3e55895668130655a680b0c5)) + ## 0.59.0 (2026-06-03) Full Changelog: [v0.58.0...v0.59.0](https://github.com/kernel/kernel-node-sdk/compare/v0.58.0...v0.59.0) diff --git a/package.json b/package.json index df4d823a..f9a6290a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@onkernel/sdk", - "version": "0.59.0", + "version": "0.60.0", "description": "The official TypeScript library for the Kernel API", "author": "Kernel <>", "types": "dist/index.d.ts", diff --git a/src/version.ts b/src/version.ts index 7fd65e71..3c00a63b 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.59.0'; // x-release-please-version +export const VERSION = '0.60.0'; // x-release-please-version