From b5d654f72557c9f5949e2067f6e61a5dcd514b03 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 04:38:35 +0000 Subject: [PATCH 1/3] chore: make some internal functions async --- src/client.ts | 25 ++++++++++++++----------- tests/index.test.ts | 30 +++++++++++++++--------------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/client.ts b/src/client.ts index b8f3925..d6c7054 100644 --- a/src/client.ts +++ b/src/client.ts @@ -230,7 +230,7 @@ export class Kernel { * Create a new client instance re-using the same options given to the current client with optional overriding. */ withOptions(options: Partial): this { - return new (this.constructor as any as new (props: ClientOptions) => typeof this)({ + const client = new (this.constructor as any as new (props: ClientOptions) => typeof this)({ ...this._options, environment: options.environment ? options.environment : undefined, baseURL: options.environment ? undefined : this.baseURL, @@ -243,6 +243,7 @@ export class Kernel { apiKey: this.apiKey, ...options, }); + return client; } /** @@ -260,7 +261,7 @@ export class Kernel { return; } - protected authHeaders(opts: FinalRequestOptions): NullableHeaders | undefined { + protected async authHeaders(opts: FinalRequestOptions): Promise { return buildHeaders([{ Authorization: `Bearer ${this.apiKey}` }]); } @@ -392,7 +393,9 @@ export class Kernel { await this.prepareOptions(options); - const { req, url, timeout } = this.buildRequest(options, { retryCount: maxRetries - retriesRemaining }); + const { req, url, timeout } = await this.buildRequest(options, { + retryCount: maxRetries - retriesRemaining, + }); await this.prepareRequest(req, { url, options }); @@ -470,7 +473,7 @@ export class Kernel { } with status ${response.status} in ${headersTime - startTime}ms`; if (!response.ok) { - const shouldRetry = this.shouldRetry(response); + const shouldRetry = await this.shouldRetry(response); if (retriesRemaining && shouldRetry) { const retryMessage = `retrying, ${retriesRemaining} attempts remaining`; @@ -569,7 +572,7 @@ export class Kernel { } } - private shouldRetry(response: Response): boolean { + private async shouldRetry(response: Response): Promise { // Note this is not a standard header. const shouldRetryHeader = response.headers.get('x-should-retry'); @@ -646,10 +649,10 @@ export class Kernel { return sleepSeconds * jitter * 1000; } - buildRequest( + async buildRequest( inputOptions: FinalRequestOptions, { retryCount = 0 }: { retryCount?: number } = {}, - ): { req: FinalizedRequestInit; url: string; timeout: number } { + ): Promise<{ req: FinalizedRequestInit; url: string; timeout: number }> { const options = { ...inputOptions }; const { method, path, query, defaultBaseURL } = options; @@ -657,7 +660,7 @@ export class Kernel { if ('timeout' in options) validatePositiveInteger('timeout', options.timeout); options.timeout = options.timeout ?? this.timeout; const { bodyHeaders, body } = this.buildBody({ options }); - const reqHeaders = this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount }); + const reqHeaders = await this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount }); const req: FinalizedRequestInit = { method, @@ -673,7 +676,7 @@ export class Kernel { return { req, url, timeout: options.timeout }; } - private buildHeaders({ + private async buildHeaders({ options, method, bodyHeaders, @@ -683,7 +686,7 @@ export class Kernel { method: HTTPMethod; bodyHeaders: HeadersLike; retryCount: number; - }): Headers { + }): Promise { let idempotencyHeaders: HeadersLike = {}; if (this.idempotencyHeader && method !== 'get') { if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey(); @@ -699,7 +702,7 @@ export class Kernel { ...(options.timeout ? { 'X-Stainless-Timeout': String(Math.trunc(options.timeout / 1000)) } : {}), ...getPlatformHeaders(), }, - this.authHeaders(options), + await this.authHeaders(options), this._options.defaultHeaders, bodyHeaders, options.headers, diff --git a/tests/index.test.ts b/tests/index.test.ts index 5c12c9a..68b144c 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -26,13 +26,13 @@ describe('instantiate client', () => { apiKey: 'My API Key', }); - test('they are used in the request', () => { - const { req } = client.buildRequest({ path: '/foo', method: 'post' }); + test('they are used in the request', async () => { + const { req } = await client.buildRequest({ path: '/foo', method: 'post' }); expect(req.headers.get('x-my-default-header')).toEqual('2'); }); - test('can ignore `undefined` and leave the default', () => { - const { req } = client.buildRequest({ + test('can ignore `undefined` and leave the default', async () => { + const { req } = await client.buildRequest({ path: '/foo', method: 'post', headers: { 'X-My-Default-Header': undefined }, @@ -40,8 +40,8 @@ describe('instantiate client', () => { expect(req.headers.get('x-my-default-header')).toEqual('2'); }); - test('can be removed with `null`', () => { - const { req } = client.buildRequest({ + test('can be removed with `null`', async () => { + const { req } = await client.buildRequest({ path: '/foo', method: 'post', headers: { 'X-My-Default-Header': null }, @@ -357,7 +357,7 @@ describe('instantiate client', () => { }); describe('withOptions', () => { - test('creates a new client with overridden options', () => { + test('creates a new client with overridden options', async () => { const client = new Kernel({ baseURL: 'http://localhost:5000/', maxRetries: 3, apiKey: 'My API Key' }); const newClient = client.withOptions({ @@ -378,7 +378,7 @@ describe('instantiate client', () => { expect(newClient.constructor).toBe(client.constructor); }); - test('inherits options from the parent client', () => { + test('inherits options from the parent client', async () => { const client = new Kernel({ baseURL: 'http://localhost:5000/', defaultHeaders: { 'X-Test-Header': 'test-value' }, @@ -393,7 +393,7 @@ describe('instantiate client', () => { // Test inherited options remain the same expect(newClient.buildURL('/foo', null)).toEqual('http://localhost:5001/foo?test-param=test-value'); - const { req } = newClient.buildRequest({ path: '/foo', method: 'get' }); + const { req } = await newClient.buildRequest({ path: '/foo', method: 'get' }); expect(req.headers.get('x-test-header')).toEqual('test-value'); }); @@ -443,8 +443,8 @@ describe('request building', () => { const client = new Kernel({ apiKey: 'My API Key' }); describe('custom headers', () => { - test('handles undefined', () => { - const { req } = client.buildRequest({ + test('handles undefined', async () => { + const { req } = await client.buildRequest({ path: '/foo', method: 'post', body: { value: 'hello' }, @@ -479,8 +479,8 @@ describe('default encoder', () => { } } for (const jsonValue of [{}, [], { __proto__: null }, new Serializable(), new Collection(['item'])]) { - test(`serializes ${util.inspect(jsonValue)} as json`, () => { - const { req } = client.buildRequest({ + test(`serializes ${util.inspect(jsonValue)} as json`, async () => { + const { req } = await client.buildRequest({ path: '/foo', method: 'post', body: jsonValue, @@ -503,7 +503,7 @@ describe('default encoder', () => { asyncIterable, ]) { test(`converts ${util.inspect(streamValue)} to ReadableStream`, async () => { - const { req } = client.buildRequest({ + const { req } = await client.buildRequest({ path: '/foo', method: 'post', body: streamValue, @@ -516,7 +516,7 @@ describe('default encoder', () => { } test(`can set content-type for ReadableStream`, async () => { - const { req } = client.buildRequest({ + const { req } = await client.buildRequest({ path: '/foo', method: 'post', body: new Response('a\nb\nc\n').body, From d0734275dd0014d8710a08eaa3a732d4530abe4f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 16:24:43 +0000 Subject: [PATCH 2/3] feat(api): manual updates replays! --- .stats.yml | 8 +- api.md | 33 ++- src/client.ts | 18 +- src/resources/browsers.ts | 252 +----------------- src/resources/browsers/browsers.ts | 234 ++++++++++++++++ src/resources/browsers/index.ts | 19 ++ src/resources/browsers/replays.ts | 168 ++++++++++++ src/resources/index.ts | 2 +- .../{ => browsers}/browsers.test.ts | 1 - tests/api-resources/browsers/replays.test.ts | 68 +++++ 10 files changed, 527 insertions(+), 276 deletions(-) create mode 100644 src/resources/browsers/browsers.ts create mode 100644 src/resources/browsers/index.ts create mode 100644 src/resources/browsers/replays.ts rename tests/api-resources/{ => browsers}/browsers.test.ts (99%) create mode 100644 tests/api-resources/browsers/replays.test.ts diff --git a/.stats.yml b/.stats.yml index aa1aff7..a0553b1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 18 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-d173129101e26f450c200e84430d993479c034700cf826917425d513b88912e6.yml -openapi_spec_hash: 150b86da7588979d7619b1a894e4720c -config_hash: eaeed470b1070b34df69c49d68e67355 +configured_endpoints: 21 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-a5b1d2c806c42c1534eefc8d34516f7f6e4ab68cb6a836534ee549bdbe4653f3.yml +openapi_spec_hash: 0be350cc8ddbd1fc7e058ce6c3a44ee8 +config_hash: 307153ecd5b85f77ce8e0d87f6e5dfab diff --git a/api.md b/api.md index eeda41d..6d4c5ed 100644 --- a/api.md +++ b/api.md @@ -70,16 +70,29 @@ Methods: Types: -- BrowserPersistence -- BrowserCreateResponse -- BrowserRetrieveResponse -- BrowserListResponse +- BrowserPersistence +- BrowserCreateResponse +- BrowserRetrieveResponse +- BrowserListResponse Methods: -- client.browsers.create({ ...params }) -> BrowserCreateResponse -- client.browsers.retrieve(id) -> BrowserRetrieveResponse -- client.browsers.list() -> BrowserListResponse -- client.browsers.delete({ ...params }) -> void -- client.browsers.deleteByID(id) -> void -- client.browsers.retrieveReplay(id) -> Response +- client.browsers.create({ ...params }) -> BrowserCreateResponse +- client.browsers.retrieve(id) -> BrowserRetrieveResponse +- client.browsers.list() -> BrowserListResponse +- client.browsers.delete({ ...params }) -> void +- client.browsers.deleteByID(id) -> void + +## Replays + +Types: + +- ReplayListResponse +- ReplayStartResponse + +Methods: + +- client.browsers.replays.list(id) -> ReplayListResponse +- client.browsers.replays.download(replayID, { ...params }) -> Response +- client.browsers.replays.start(id, { ...params }) -> ReplayStartResponse +- client.browsers.replays.stop(replayID, { ...params }) -> void diff --git a/src/client.ts b/src/client.ts index d6c7054..76ae00d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -16,15 +16,6 @@ import * as Errors from './core/error'; import * as Uploads from './core/uploads'; import * as API from './resources/index'; import { APIPromise } from './core/api-promise'; -import { - BrowserCreateParams, - BrowserCreateResponse, - BrowserDeleteParams, - BrowserListResponse, - BrowserPersistence, - BrowserRetrieveResponse, - Browsers, -} from './resources/browsers'; import { DeploymentCreateParams, DeploymentCreateResponse, @@ -48,6 +39,15 @@ import { Invocations, } from './resources/invocations'; import { AppListParams, AppListResponse, Apps } from './resources/apps/apps'; +import { + BrowserCreateParams, + BrowserCreateResponse, + BrowserDeleteParams, + BrowserListResponse, + BrowserPersistence, + BrowserRetrieveResponse, + Browsers, +} from './resources/browsers/browsers'; import { type Fetch } from './internal/builtin-types'; import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers'; import { FinalRequestOptions, RequestOptions } from './internal/request-options'; diff --git a/src/resources/browsers.ts b/src/resources/browsers.ts index 14398d4..7d73ffd 100644 --- a/src/resources/browsers.ts +++ b/src/resources/browsers.ts @@ -1,253 +1,3 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../core/resource'; -import * as BrowsersAPI from './browsers'; -import { APIPromise } from '../core/api-promise'; -import { buildHeaders } from '../internal/headers'; -import { RequestOptions } from '../internal/request-options'; -import { path } from '../internal/utils/path'; - -export class Browsers extends APIResource { - /** - * Create a new browser session from within an action. - * - * @example - * ```ts - * const browser = await client.browsers.create(); - * ``` - */ - create( - body: BrowserCreateParams | null | undefined = {}, - options?: RequestOptions, - ): APIPromise { - return this._client.post('/browsers', { body, ...options }); - } - - /** - * Get information about a browser session. - * - * @example - * ```ts - * const browser = await client.browsers.retrieve( - * 'htzv5orfit78e1m2biiifpbv', - * ); - * ``` - */ - retrieve(id: string, options?: RequestOptions): APIPromise { - return this._client.get(path`/browsers/${id}`, options); - } - - /** - * List active browser sessions - * - * @example - * ```ts - * const browsers = await client.browsers.list(); - * ``` - */ - list(options?: RequestOptions): APIPromise { - return this._client.get('/browsers', options); - } - - /** - * Delete a persistent browser session by its persistent_id. - * - * @example - * ```ts - * await client.browsers.delete({ - * persistent_id: 'persistent_id', - * }); - * ``` - */ - delete(params: BrowserDeleteParams, options?: RequestOptions): APIPromise { - const { persistent_id } = params; - return this._client.delete('/browsers', { - query: { persistent_id }, - ...options, - headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), - }); - } - - /** - * Delete a browser session by ID - * - * @example - * ```ts - * await client.browsers.deleteByID( - * 'htzv5orfit78e1m2biiifpbv', - * ); - * ``` - */ - deleteByID(id: string, options?: RequestOptions): APIPromise { - return this._client.delete(path`/browsers/${id}`, { - ...options, - headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), - }); - } - - /** - * Get browser session replay. - * - * @example - * ```ts - * const response = await client.browsers.retrieveReplay( - * 'htzv5orfit78e1m2biiifpbv', - * ); - * - * const content = await response.blob(); - * console.log(content); - * ``` - */ - retrieveReplay(id: string, options?: RequestOptions): APIPromise { - return this._client.get(path`/browsers/${id}/replay`, { - ...options, - headers: buildHeaders([{ Accept: 'video/mp4' }, options?.headers]), - __binaryResponse: true, - }); - } -} - -/** - * Optional persistence configuration for the browser session. - */ -export interface BrowserPersistence { - /** - * Unique identifier for the persistent browser session. - */ - id: string; -} - -export interface BrowserCreateResponse { - /** - * Websocket URL for Chrome DevTools Protocol connections to the browser session - */ - cdp_ws_url: string; - - /** - * Unique identifier for the browser session - */ - session_id: string; - - /** - * Remote URL for live viewing the browser session. Only available for non-headless - * browsers. - */ - browser_live_view_url?: string; - - /** - * Optional persistence configuration for the browser session. - */ - persistence?: BrowserPersistence; - - /** - * Remote URL for viewing the browser session replay if enabled - */ - replay_view_url?: string; -} - -export interface BrowserRetrieveResponse { - /** - * Websocket URL for Chrome DevTools Protocol connections to the browser session - */ - cdp_ws_url: string; - - /** - * Unique identifier for the browser session - */ - session_id: string; - - /** - * Remote URL for live viewing the browser session. Only available for non-headless - * browsers. - */ - browser_live_view_url?: string; - - /** - * Optional persistence configuration for the browser session. - */ - persistence?: BrowserPersistence; - - /** - * Remote URL for viewing the browser session replay if enabled - */ - replay_view_url?: string; -} - -export type BrowserListResponse = Array; - -export namespace BrowserListResponse { - export interface BrowserListResponseItem { - /** - * Websocket URL for Chrome DevTools Protocol connections to the browser session - */ - cdp_ws_url: string; - - /** - * Unique identifier for the browser session - */ - session_id: string; - - /** - * Remote URL for live viewing the browser session. Only available for non-headless - * browsers. - */ - browser_live_view_url?: string; - - /** - * Optional persistence configuration for the browser session. - */ - persistence?: BrowsersAPI.BrowserPersistence; - - /** - * Remote URL for viewing the browser session replay if enabled - */ - replay_view_url?: string; - } -} - -export interface BrowserCreateParams { - /** - * If true, launches the browser using a headless image (no VNC/GUI). Defaults to - * false. - */ - headless?: boolean; - - /** - * action invocation ID - */ - invocation_id?: string; - - /** - * Optional persistence configuration for the browser session. - */ - persistence?: BrowserPersistence; - - /** - * If true, enables replay recording of the browser session. Defaults to false. - */ - replay?: boolean; - - /** - * If true, launches the browser in stealth mode to reduce detection by anti-bot - * mechanisms. - */ - stealth?: boolean; -} - -export interface BrowserDeleteParams { - /** - * Persistent browser identifier - */ - persistent_id: string; -} - -export declare namespace Browsers { - export { - type BrowserPersistence as BrowserPersistence, - type BrowserCreateResponse as BrowserCreateResponse, - type BrowserRetrieveResponse as BrowserRetrieveResponse, - type BrowserListResponse as BrowserListResponse, - type BrowserCreateParams as BrowserCreateParams, - type BrowserDeleteParams as BrowserDeleteParams, - }; -} +export * from './browsers/index'; diff --git a/src/resources/browsers/browsers.ts b/src/resources/browsers/browsers.ts new file mode 100644 index 0000000..1f8f204 --- /dev/null +++ b/src/resources/browsers/browsers.ts @@ -0,0 +1,234 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import * as BrowsersAPI from './browsers'; +import * as ReplaysAPI from './replays'; +import { + ReplayDownloadParams, + ReplayListResponse, + ReplayStartParams, + ReplayStartResponse, + ReplayStopParams, + Replays, +} from './replays'; +import { APIPromise } from '../../core/api-promise'; +import { buildHeaders } from '../../internal/headers'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; + +export class Browsers extends APIResource { + replays: ReplaysAPI.Replays = new ReplaysAPI.Replays(this._client); + + /** + * Create a new browser session from within an action. + * + * @example + * ```ts + * const browser = await client.browsers.create(); + * ``` + */ + create( + body: BrowserCreateParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.post('/browsers', { body, ...options }); + } + + /** + * Get information about a browser session. + * + * @example + * ```ts + * const browser = await client.browsers.retrieve( + * 'htzv5orfit78e1m2biiifpbv', + * ); + * ``` + */ + retrieve(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/browsers/${id}`, options); + } + + /** + * List active browser sessions + * + * @example + * ```ts + * const browsers = await client.browsers.list(); + * ``` + */ + list(options?: RequestOptions): APIPromise { + return this._client.get('/browsers', options); + } + + /** + * Delete a persistent browser session by its persistent_id. + * + * @example + * ```ts + * await client.browsers.delete({ + * persistent_id: 'persistent_id', + * }); + * ``` + */ + delete(params: BrowserDeleteParams, options?: RequestOptions): APIPromise { + const { persistent_id } = params; + return this._client.delete('/browsers', { + query: { persistent_id }, + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } + + /** + * Delete a browser session by ID + * + * @example + * ```ts + * await client.browsers.deleteByID( + * 'htzv5orfit78e1m2biiifpbv', + * ); + * ``` + */ + deleteByID(id: string, options?: RequestOptions): APIPromise { + return this._client.delete(path`/browsers/${id}`, { + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } +} + +/** + * Optional persistence configuration for the browser session. + */ +export interface BrowserPersistence { + /** + * Unique identifier for the persistent browser session. + */ + id: string; +} + +export interface BrowserCreateResponse { + /** + * Websocket URL for Chrome DevTools Protocol connections to the browser session + */ + cdp_ws_url: string; + + /** + * Unique identifier for the browser session + */ + session_id: string; + + /** + * Remote URL for live viewing the browser session. Only available for non-headless + * browsers. + */ + browser_live_view_url?: string; + + /** + * Optional persistence configuration for the browser session. + */ + persistence?: BrowserPersistence; +} + +export interface BrowserRetrieveResponse { + /** + * Websocket URL for Chrome DevTools Protocol connections to the browser session + */ + cdp_ws_url: string; + + /** + * Unique identifier for the browser session + */ + session_id: string; + + /** + * Remote URL for live viewing the browser session. Only available for non-headless + * browsers. + */ + browser_live_view_url?: string; + + /** + * Optional persistence configuration for the browser session. + */ + persistence?: BrowserPersistence; +} + +export type BrowserListResponse = Array; + +export namespace BrowserListResponse { + export interface BrowserListResponseItem { + /** + * Websocket URL for Chrome DevTools Protocol connections to the browser session + */ + cdp_ws_url: string; + + /** + * Unique identifier for the browser session + */ + session_id: string; + + /** + * Remote URL for live viewing the browser session. Only available for non-headless + * browsers. + */ + browser_live_view_url?: string; + + /** + * Optional persistence configuration for the browser session. + */ + persistence?: BrowsersAPI.BrowserPersistence; + } +} + +export interface BrowserCreateParams { + /** + * If true, launches the browser using a headless image (no VNC/GUI). Defaults to + * false. + */ + headless?: boolean; + + /** + * action invocation ID + */ + invocation_id?: string; + + /** + * Optional persistence configuration for the browser session. + */ + persistence?: BrowserPersistence; + + /** + * If true, launches the browser in stealth mode to reduce detection by anti-bot + * mechanisms. + */ + stealth?: boolean; +} + +export interface BrowserDeleteParams { + /** + * Persistent browser identifier + */ + persistent_id: string; +} + +Browsers.Replays = Replays; + +export declare namespace Browsers { + export { + type BrowserPersistence as BrowserPersistence, + type BrowserCreateResponse as BrowserCreateResponse, + type BrowserRetrieveResponse as BrowserRetrieveResponse, + type BrowserListResponse as BrowserListResponse, + type BrowserCreateParams as BrowserCreateParams, + type BrowserDeleteParams as BrowserDeleteParams, + }; + + export { + Replays as Replays, + type ReplayListResponse as ReplayListResponse, + type ReplayStartResponse as ReplayStartResponse, + type ReplayDownloadParams as ReplayDownloadParams, + type ReplayStartParams as ReplayStartParams, + type ReplayStopParams as ReplayStopParams, + }; +} diff --git a/src/resources/browsers/index.ts b/src/resources/browsers/index.ts new file mode 100644 index 0000000..deb5900 --- /dev/null +++ b/src/resources/browsers/index.ts @@ -0,0 +1,19 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export { + Browsers, + type BrowserPersistence, + type BrowserCreateResponse, + type BrowserRetrieveResponse, + type BrowserListResponse, + type BrowserCreateParams, + type BrowserDeleteParams, +} from './browsers'; +export { + Replays, + type ReplayListResponse, + type ReplayStartResponse, + type ReplayDownloadParams, + type ReplayStartParams, + type ReplayStopParams, +} from './replays'; diff --git a/src/resources/browsers/replays.ts b/src/resources/browsers/replays.ts new file mode 100644 index 0000000..208d8c3 --- /dev/null +++ b/src/resources/browsers/replays.ts @@ -0,0 +1,168 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../../core/resource'; +import { APIPromise } from '../../core/api-promise'; +import { buildHeaders } from '../../internal/headers'; +import { RequestOptions } from '../../internal/request-options'; +import { path } from '../../internal/utils/path'; + +export class Replays extends APIResource { + /** + * List all replays for the specified browser session. + * + * @example + * ```ts + * const replays = await client.browsers.replays.list('id'); + * ``` + */ + list(id: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/browsers/${id}/replays`, options); + } + + /** + * Download or stream the specified replay recording. + * + * @example + * ```ts + * const response = await client.browsers.replays.download( + * 'replay_id', + * { id: 'id' }, + * ); + * + * const content = await response.blob(); + * console.log(content); + * ``` + */ + download(replayID: string, params: ReplayDownloadParams, options?: RequestOptions): APIPromise { + const { id } = params; + return this._client.get(path`/browsers/${id}/replays/${replayID}`, { + ...options, + headers: buildHeaders([{ Accept: 'video/mp4' }, options?.headers]), + __binaryResponse: true, + }); + } + + /** + * Start recording the browser session and return a replay ID. + * + * @example + * ```ts + * const response = await client.browsers.replays.start('id'); + * ``` + */ + start( + id: string, + body: ReplayStartParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.post(path`/browsers/${id}/replays`, { body, ...options }); + } + + /** + * Stop the specified replay recording and persist the video. + * + * @example + * ```ts + * await client.browsers.replays.stop('replay_id', { + * id: 'id', + * }); + * ``` + */ + stop(replayID: string, params: ReplayStopParams, options?: RequestOptions): APIPromise { + const { id } = params; + return this._client.post(path`/browsers/${id}/replays/${replayID}/stop`, { + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } +} + +export type ReplayListResponse = Array; + +export namespace ReplayListResponse { + /** + * Information about a browser replay recording. + */ + export interface ReplayListResponseItem { + /** + * Unique identifier for the replay recording. + */ + replay_id: string; + + /** + * Timestamp when replay finished + */ + finished_at?: string | null; + + /** + * URL for viewing the replay recording. + */ + replay_view_url?: string; + + /** + * Timestamp when replay started + */ + started_at?: string | null; + } +} + +/** + * Information about a browser replay recording. + */ +export interface ReplayStartResponse { + /** + * Unique identifier for the replay recording. + */ + replay_id: string; + + /** + * Timestamp when replay finished + */ + finished_at?: string | null; + + /** + * URL for viewing the replay recording. + */ + replay_view_url?: string; + + /** + * Timestamp when replay started + */ + started_at?: string | null; +} + +export interface ReplayDownloadParams { + /** + * Browser session ID + */ + id: string; +} + +export interface ReplayStartParams { + /** + * Recording framerate in fps. + */ + framerate?: number; + + /** + * Maximum recording duration in seconds. + */ + max_duration_in_seconds?: number; +} + +export interface ReplayStopParams { + /** + * Browser session ID + */ + id: string; +} + +export declare namespace Replays { + export { + type ReplayListResponse as ReplayListResponse, + type ReplayStartResponse as ReplayStartResponse, + type ReplayDownloadParams as ReplayDownloadParams, + type ReplayStartParams as ReplayStartParams, + type ReplayStopParams as ReplayStopParams, + }; +} diff --git a/src/resources/index.ts b/src/resources/index.ts index b48f0f0..f51eb77 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -10,7 +10,7 @@ export { type BrowserListResponse, type BrowserCreateParams, type BrowserDeleteParams, -} from './browsers'; +} from './browsers/browsers'; export { Deployments, type DeploymentStateEvent, diff --git a/tests/api-resources/browsers.test.ts b/tests/api-resources/browsers/browsers.test.ts similarity index 99% rename from tests/api-resources/browsers.test.ts rename to tests/api-resources/browsers/browsers.test.ts index b066fa2..d28bd02 100644 --- a/tests/api-resources/browsers.test.ts +++ b/tests/api-resources/browsers/browsers.test.ts @@ -29,7 +29,6 @@ describe('resource browsers', () => { headless: false, invocation_id: 'rr33xuugxj9h0bkf1rdt2bet', persistence: { id: 'my-awesome-browser-for-user-1234' }, - replay: true, stealth: true, }, { path: '/_stainless_unknown_path' }, diff --git a/tests/api-resources/browsers/replays.test.ts b/tests/api-resources/browsers/replays.test.ts new file mode 100644 index 0000000..23a4cd9 --- /dev/null +++ b/tests/api-resources/browsers/replays.test.ts @@ -0,0 +1,68 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource replays', () => { + // skipped: tests are disabled for the time being + test.skip('list', async () => { + const responsePromise = client.browsers.replays.list('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // skipped: tests are disabled for the time being + test.skip('download: required and optional params', async () => { + const response = await client.browsers.replays.download('replay_id', { id: 'id' }); + }); + + // skipped: tests are disabled for the time being + test.skip('start', async () => { + const responsePromise = client.browsers.replays.start('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // skipped: tests are disabled for the time being + test.skip('start: 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.browsers.replays.start( + 'id', + { framerate: 1, max_duration_in_seconds: 1 }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Kernel.NotFoundError); + }); + + // skipped: tests are disabled for the time being + test.skip('stop: only required params', async () => { + const responsePromise = client.browsers.replays.stop('replay_id', { id: 'id' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + // skipped: tests are disabled for the time being + test.skip('stop: required and optional params', async () => { + const response = await client.browsers.replays.stop('replay_id', { id: 'id' }); + }); +}); From 270489165af0a876e2cba09edc53866264eb4c40 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 16:25:23 +0000 Subject: [PATCH 3/3] release: 0.8.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- src/version.ts | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 13708fa..64f3cdd 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.7.1" + ".": "0.8.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 71387ef..8a0ee17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 0.8.0 (2025-07-16) + +Full Changelog: [v0.7.1...v0.8.0](https://github.com/onkernel/kernel-node-sdk/compare/v0.7.1...v0.8.0) + +### Features + +* **api:** manual updates ([d073427](https://github.com/onkernel/kernel-node-sdk/commit/d0734275dd0014d8710a08eaa3a732d4530abe4f)) + + +### Chores + +* make some internal functions async ([b5d654f](https://github.com/onkernel/kernel-node-sdk/commit/b5d654f72557c9f5949e2067f6e61a5dcd514b03)) + ## 0.7.1 (2025-07-08) Full Changelog: [v0.7.0...v0.7.1](https://github.com/onkernel/kernel-node-sdk/compare/v0.7.0...v0.7.1) diff --git a/package.json b/package.json index 9abb848..c82d243 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@onkernel/sdk", - "version": "0.7.1", + "version": "0.8.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 5e85c4b..23f967c 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.7.1'; // x-release-please-version +export const VERSION = '0.8.0'; // x-release-please-version