From 0ab607e1b3b7ffe8a126a4ce2f7beed2608059c2 Mon Sep 17 00:00:00 2001 From: phani Date: Tue, 14 Oct 2025 16:51:46 -0400 Subject: [PATCH 1/5] fix(uploads): serialize objects without files as JSON in multipart forms Objects without uploadable values are now JSON-serialized per OpenAPI contentType spec instead of being flattened into bracketed keys. Special handling for env_vars to maintain backward compatibility - it continues using flattened format (env_vars[KEY]) as expected by APIs. - Add hasUploadableValue check to determine serialization strategy - Add tests for source field (JSON) and env_vars (flattened) behavior - Fixes 400 errors where source[type] was sent instead of source JSON Resolves issue reported to Stainless support on Oct 8, 2025 --- src/internal/uploads.ts | 25 ++++++++++++++++++++--- tests/form.test.ts | 44 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/internal/uploads.ts b/src/internal/uploads.ts index a4e57ac..a6af246 100644 --- a/src/internal/uploads.ts +++ b/src/internal/uploads.ts @@ -176,9 +176,28 @@ const addFormValue = async (form: FormData, key: string, value: unknown): Promis } else if (Array.isArray(value)) { await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry))); } else if (typeof value === 'object') { - await Promise.all( - Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)), - ); + // Special case: env_vars should always be flattened for backward compatibility + // with APIs that expect env_vars[KEY] format + const shouldAlwaysFlatten = key === 'env_vars'; + + // If the object doesn't contain any uploadable values, + // serialize it as JSON instead of flattening it into bracketed keys. + // This handles fields with contentType: application/json in the OpenAPI spec. + if (!shouldAlwaysFlatten && !hasUploadableValue(value)) { + // Filter out undefined values to check if object has any actual content + const entries = Object.entries(value).filter(([_, v]) => v !== undefined); + if (entries.length > 0) { + form.append(key, JSON.stringify(value)); + } + // If all properties are undefined, don't add anything to the form + } else { + // Flatten objects that: + // - Contain uploadable values (files/blobs), or + // - Are explicitly marked to always flatten (like env_vars) + await Promise.all( + Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)), + ); + } } else { throw new TypeError( `Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`, diff --git a/tests/form.test.ts b/tests/form.test.ts index 56981ca..8abe689 100644 --- a/tests/form.test.ts +++ b/tests/form.test.ts @@ -62,7 +62,49 @@ describe('form data validation', () => { }, fetch, ); - expect(Array.from(form2.entries())).toEqual([['bar[foo]', 'string']]); + // Objects without uploadable values are now serialized as JSON + expect(Array.from(form2.entries())).toEqual([['bar', '{"foo":"string"}']]); + }); + + test('env_vars are always flattened for backward compatibility', async () => { + const form = await createForm( + { + env_vars: { + API_KEY: 'secret', + DEBUG: 'true', + }, + }, + fetch, + ); + // env_vars should be flattened, not JSON-serialized + expect(Array.from(form.entries())).toEqual([ + ['env_vars[API_KEY]', 'secret'], + ['env_vars[DEBUG]', 'true'], + ]); + }); + + test('source field is JSON-serialized', async () => { + const form = await createForm( + { + source: { + type: 'github', + url: 'https://github.com/user/repo', + ref: 'main', + entrypoint: 'app.py', + }, + }, + fetch, + ); + // source should be JSON-serialized per OpenAPI spec + const entries = Array.from(form.entries()); + expect(entries.length).toBe(1); + expect(entries[0]![0]).toBe('source'); + expect(JSON.parse(entries[0]![1] as string)).toEqual({ + type: 'github', + url: 'https://github.com/user/repo', + ref: 'main', + entrypoint: 'app.py', + }); }); test('nested undefined array item is stripped', async () => { From 07c599a8716c96b906458d1d47f807e847160da4 Mon Sep 17 00:00:00 2001 From: phani Date: Tue, 14 Oct 2025 17:03:16 -0400 Subject: [PATCH 2/5] Lint error fix --- src/internal/uploads.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/internal/uploads.ts b/src/internal/uploads.ts index a6af246..001b7d9 100644 --- a/src/internal/uploads.ts +++ b/src/internal/uploads.ts @@ -179,7 +179,6 @@ const addFormValue = async (form: FormData, key: string, value: unknown): Promis // Special case: env_vars should always be flattened for backward compatibility // with APIs that expect env_vars[KEY] format const shouldAlwaysFlatten = key === 'env_vars'; - // If the object doesn't contain any uploadable values, // serialize it as JSON instead of flattening it into bracketed keys. // This handles fields with contentType: application/json in the OpenAPI spec. From 1e971513c25cdfe84624c033ad89c5bfdc7fef20 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:31:22 +0000 Subject: [PATCH 3/5] feat: Phani/deploy with GitHub url --- .stats.yml | 6 +- README.md | 22 +--- api.md | 6 +- src/client.ts | 8 +- src/resources/deployments.ts | 74 +++++++++++-- src/resources/extensions.ts | 138 ++++++++++++------------ src/resources/index.ts | 4 +- tests/api-resources/deployments.test.ts | 21 +--- tests/api-resources/extensions.test.ts | 36 +++---- 9 files changed, 175 insertions(+), 140 deletions(-) diff --git a/.stats.yml b/.stats.yml index 6bb4af8..2bd40cc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 57 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-6c765f1c4ce1c4dd4ceb371f56bf047aa79af36031ba43cbd68fa16a5fdb9bb3.yml -openapi_spec_hash: e9086f69281360f4e0895c9274a59531 -config_hash: deadfc4d2b0a947673bcf559b5db6e1b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-6eaa6f5654abc94549962d7db1e8c7936af1f815bb3abe2f8249959394da1278.yml +openapi_spec_hash: 31ece7cd801e74228b80a8112a762e56 +config_hash: 3fc2057ce765bc5f27785a694ed0f553 diff --git a/README.md b/README.md index fa69c0c..0018d91 100644 --- a/README.md +++ b/README.md @@ -67,29 +67,17 @@ import Kernel, { toFile } from '@onkernel/sdk'; const client = new Kernel(); // If you have access to Node `fs` we recommend using `fs.createReadStream()`: -await client.deployments.create({ - entrypoint_rel_path: 'src/app.py', - file: fs.createReadStream('/path/to/file'), -}); +await client.deployments.create({ file: fs.createReadStream('/path/to/file') }); // Or if you have the web `File` API you can pass a `File` instance: -await client.deployments.create({ entrypoint_rel_path: 'src/app.py', file: new File(['my bytes'], 'file') }); +await client.deployments.create({ file: new File(['my bytes'], 'file') }); // You can also pass a `fetch` `Response`: -await client.deployments.create({ - entrypoint_rel_path: 'src/app.py', - file: await fetch('https://somesite/file'), -}); +await client.deployments.create({ file: await fetch('https://somesite/file') }); // Finally, if none of the above are convenient, you can use our `toFile` helper: -await client.deployments.create({ - entrypoint_rel_path: 'src/app.py', - file: await toFile(Buffer.from('my bytes'), 'file'), -}); -await client.deployments.create({ - entrypoint_rel_path: 'src/app.py', - file: await toFile(new Uint8Array([0, 1, 2]), 'file'), -}); +await client.deployments.create({ file: await toFile(Buffer.from('my bytes'), 'file') }); +await client.deployments.create({ file: await toFile(new Uint8Array([0, 1, 2]), 'file') }); ``` ## Handling errors diff --git a/api.md b/api.md index 92e5d8d..b442f34 100644 --- a/api.md +++ b/api.md @@ -183,13 +183,13 @@ Methods: Types: +- ExtensionCreateResponse - ExtensionListResponse -- ExtensionUploadResponse Methods: +- client.extensions.create({ ...params }) -> ExtensionCreateResponse +- client.extensions.retrieve(idOrName) -> Response - client.extensions.list() -> ExtensionListResponse - client.extensions.delete(idOrName) -> void -- client.extensions.download(idOrName) -> Response - client.extensions.downloadFromChromeStore({ ...params }) -> Response -- client.extensions.upload({ ...params }) -> ExtensionUploadResponse diff --git a/src/client.ts b/src/client.ts index 5497543..54af496 100644 --- a/src/client.ts +++ b/src/client.ts @@ -33,10 +33,10 @@ import { } from './resources/deployments'; import { KernelApp } from './core/app-framework'; import { + ExtensionCreateParams, + ExtensionCreateResponse, ExtensionDownloadFromChromeStoreParams, ExtensionListResponse, - ExtensionUploadParams, - ExtensionUploadResponse, Extensions, } from './resources/extensions'; import { @@ -918,10 +918,10 @@ export declare namespace Kernel { export { Extensions as Extensions, + type ExtensionCreateResponse as ExtensionCreateResponse, type ExtensionListResponse as ExtensionListResponse, - type ExtensionUploadResponse as ExtensionUploadResponse, + type ExtensionCreateParams as ExtensionCreateParams, type ExtensionDownloadFromChromeStoreParams as ExtensionDownloadFromChromeStoreParams, - type ExtensionUploadParams as ExtensionUploadParams, }; export type AppAction = API.AppAction; diff --git a/src/resources/deployments.ts b/src/resources/deployments.ts index b9dfe5a..c297548 100644 --- a/src/resources/deployments.ts +++ b/src/resources/deployments.ts @@ -19,7 +19,10 @@ export class Deployments extends APIResource { * ```ts * const deployment = await client.deployments.create({ * entrypoint_rel_path: 'src/app.py', + * env_vars: { FOO: 'bar' }, * file: fs.createReadStream('path/to/file'), + * region: 'aws.us-east-1a', + * version: '1.0.0', * }); * ``` */ @@ -349,12 +352,7 @@ export interface DeploymentCreateParams { /** * Relative path to the entrypoint of the application */ - entrypoint_rel_path: string; - - /** - * ZIP file containing the application source directory - */ - file: Uploadable; + entrypoint_rel_path?: string; /** * Map of environment variables to set for the deployed application. Each key-value @@ -362,6 +360,11 @@ export interface DeploymentCreateParams { */ env_vars?: { [key: string]: string }; + /** + * ZIP file containing the application source directory + */ + file?: Uploadable; + /** * Allow overwriting an existing app version */ @@ -372,12 +375,71 @@ export interface DeploymentCreateParams { */ region?: 'aws.us-east-1a'; + /** + * Source from which to fetch application code. + */ + source?: DeploymentCreateParams.Source; + /** * Version of the application. Can be any string. */ version?: string; } +export namespace DeploymentCreateParams { + /** + * Source from which to fetch application code. + */ + export interface Source { + /** + * Relative path to the application entrypoint within the selected path. + */ + entrypoint: string; + + /** + * Git ref (branch, tag, or commit SHA) to fetch. + */ + ref: string; + + /** + * Source type identifier. + */ + type: 'github'; + + /** + * Base repository URL (without blob/tree suffixes). + */ + url: string; + + /** + * Authentication for private repositories. + */ + auth?: Source.Auth; + + /** + * Path within the repo to deploy (omit to use repo root). + */ + path?: string; + } + + export namespace Source { + /** + * Authentication for private repositories. + */ + export interface Auth { + /** + * GitHub PAT or installation access token + */ + token: string; + + /** + * Auth method + */ + method: 'github_token'; + } + } +} + export interface DeploymentListParams extends OffsetPaginationParams { /** * Filter results by application name. diff --git a/src/resources/extensions.ts b/src/resources/extensions.ts index 5697a07..82f29ba 100644 --- a/src/resources/extensions.ts +++ b/src/resources/extensions.ts @@ -10,50 +10,65 @@ import { path } from '../internal/utils/path'; export class Extensions extends APIResource { /** - * List extensions owned by the caller's organization. + * Upload a zip file containing an unpacked browser extension. Optionally provide a + * unique name for later reference. * * @example * ```ts - * const extensions = await client.extensions.list(); + * const extension = await client.extensions.create({ + * file: fs.createReadStream('path/to/file'), + * }); * ``` */ - list(options?: RequestOptions): APIPromise { - return this._client.get('/extensions', options); + create(body: ExtensionCreateParams, options?: RequestOptions): APIPromise { + return this._client.post('/extensions', multipartFormRequestOptions({ body, ...options }, this._client)); } /** - * Delete an extension by its ID or by its name. + * Download the extension as a ZIP archive by ID or name. * * @example * ```ts - * await client.extensions.delete('id_or_name'); + * const extension = await client.extensions.retrieve( + * 'id_or_name', + * ); + * + * const content = await extension.blob(); + * console.log(content); * ``` */ - delete(idOrName: string, options?: RequestOptions): APIPromise { - return this._client.delete(path`/extensions/${idOrName}`, { + retrieve(idOrName: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/extensions/${idOrName}`, { ...options, - headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + headers: buildHeaders([{ Accept: 'application/octet-stream' }, options?.headers]), + __binaryResponse: true, }); } /** - * Download the extension as a ZIP archive by ID or name. + * List extensions owned by the caller's organization. * * @example * ```ts - * const response = await client.extensions.download( - * 'id_or_name', - * ); + * const extensions = await client.extensions.list(); + * ``` + */ + list(options?: RequestOptions): APIPromise { + return this._client.get('/extensions', options); + } + + /** + * Delete an extension by its ID or by its name. * - * const content = await response.blob(); - * console.log(content); + * @example + * ```ts + * await client.extensions.delete('id_or_name'); * ``` */ - download(idOrName: string, options?: RequestOptions): APIPromise { - return this._client.get(path`/extensions/${idOrName}`, { + delete(idOrName: string, options?: RequestOptions): APIPromise { + return this._client.delete(path`/extensions/${idOrName}`, { ...options, - headers: buildHeaders([{ Accept: 'application/octet-stream' }, options?.headers]), - __binaryResponse: true, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), }); } @@ -83,21 +98,37 @@ export class Extensions extends APIResource { __binaryResponse: true, }); } +} +/** + * A browser extension uploaded to Kernel. + */ +export interface ExtensionCreateResponse { /** - * Upload a zip file containing an unpacked browser extension. Optionally provide a - * unique name for later reference. - * - * @example - * ```ts - * const response = await client.extensions.upload({ - * file: fs.createReadStream('path/to/file'), - * }); - * ``` + * Unique identifier for the extension */ - upload(body: ExtensionUploadParams, options?: RequestOptions): APIPromise { - return this._client.post('/extensions', multipartFormRequestOptions({ body, ...options }, this._client)); - } + id: string; + + /** + * Timestamp when the extension was created + */ + created_at: string; + + /** + * Size of the extension archive in bytes + */ + size_bytes: number; + + /** + * Timestamp when the extension was last used + */ + last_used_at?: string | null; + + /** + * Optional, easier-to-reference name for the extension. Must be unique within the + * organization. + */ + name?: string | null; } export type ExtensionListResponse = Array; @@ -135,35 +166,16 @@ export namespace ExtensionListResponse { } } -/** - * A browser extension uploaded to Kernel. - */ -export interface ExtensionUploadResponse { - /** - * Unique identifier for the extension - */ - id: string; - +export interface ExtensionCreateParams { /** - * Timestamp when the extension was created - */ - created_at: string; - - /** - * Size of the extension archive in bytes - */ - size_bytes: number; - - /** - * Timestamp when the extension was last used + * ZIP file containing the browser extension. */ - last_used_at?: string | null; + file: Uploadable; /** - * Optional, easier-to-reference name for the extension. Must be unique within the - * organization. + * Optional unique name within the organization to reference this extension. */ - name?: string | null; + name?: string; } export interface ExtensionDownloadFromChromeStoreParams { @@ -178,23 +190,11 @@ export interface ExtensionDownloadFromChromeStoreParams { os?: 'win' | 'mac' | 'linux'; } -export interface ExtensionUploadParams { - /** - * ZIP file containing the browser extension. - */ - file: Uploadable; - - /** - * Optional unique name within the organization to reference this extension. - */ - name?: string; -} - export declare namespace Extensions { export { + type ExtensionCreateResponse as ExtensionCreateResponse, type ExtensionListResponse as ExtensionListResponse, - type ExtensionUploadResponse as ExtensionUploadResponse, + type ExtensionCreateParams as ExtensionCreateParams, type ExtensionDownloadFromChromeStoreParams as ExtensionDownloadFromChromeStoreParams, - type ExtensionUploadParams as ExtensionUploadParams, }; } diff --git a/src/resources/index.ts b/src/resources/index.ts index 83e4312..62dfb1a 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -27,10 +27,10 @@ export { } from './deployments'; export { Extensions, + type ExtensionCreateResponse, type ExtensionListResponse, - type ExtensionUploadResponse, + type ExtensionCreateParams, type ExtensionDownloadFromChromeStoreParams, - type ExtensionUploadParams, } from './extensions'; export { Invocations, diff --git a/tests/api-resources/deployments.test.ts b/tests/api-resources/deployments.test.ts index 80f430f..12d3bb7 100644 --- a/tests/api-resources/deployments.test.ts +++ b/tests/api-resources/deployments.test.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import Kernel, { toFile } from '@onkernel/sdk'; +import Kernel from '@onkernel/sdk'; const client = new Kernel({ apiKey: 'My API Key', @@ -9,11 +9,8 @@ const client = new Kernel({ describe('resource deployments', () => { // Prism tests are disabled - test.skip('create: only required params', async () => { - const responsePromise = client.deployments.create({ - entrypoint_rel_path: 'src/app.py', - file: await toFile(Buffer.from('# my file contents'), 'README.md'), - }); + test.skip('create', async () => { + const responsePromise = client.deployments.create({}); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -23,18 +20,6 @@ describe('resource deployments', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled - test.skip('create: required and optional params', async () => { - const response = await client.deployments.create({ - entrypoint_rel_path: 'src/app.py', - file: await toFile(Buffer.from('# my file contents'), 'README.md'), - env_vars: { foo: 'string' }, - force: false, - region: 'aws.us-east-1a', - version: '1.0.0', - }); - }); - // Prism tests are disabled test.skip('retrieve', async () => { const responsePromise = client.deployments.retrieve('id'); diff --git a/tests/api-resources/extensions.test.ts b/tests/api-resources/extensions.test.ts index 515415f..f401acf 100644 --- a/tests/api-resources/extensions.test.ts +++ b/tests/api-resources/extensions.test.ts @@ -9,8 +9,10 @@ const client = new Kernel({ describe('resource extensions', () => { // Prism tests are disabled - test.skip('list', async () => { - const responsePromise = client.extensions.list(); + test.skip('create: only required params', async () => { + const responsePromise = client.extensions.create({ + file: await toFile(Buffer.from('# my file contents'), 'README.md'), + }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -21,8 +23,16 @@ describe('resource extensions', () => { }); // Prism tests are disabled - test.skip('delete', async () => { - const responsePromise = client.extensions.delete('id_or_name'); + test.skip('create: required and optional params', async () => { + const response = await client.extensions.create({ + file: await toFile(Buffer.from('# my file contents'), 'README.md'), + name: 'name', + }); + }); + + // Prism tests are disabled + test.skip('list', async () => { + const responsePromise = client.extensions.list(); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -32,15 +42,9 @@ describe('resource extensions', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('downloadFromChromeStore: required and optional params', async () => { - const response = await client.extensions.downloadFromChromeStore({ url: 'url', os: 'win' }); - }); - // Prism tests are disabled - test.skip('upload: only required params', async () => { - const responsePromise = client.extensions.upload({ - file: await toFile(Buffer.from('# my file contents'), 'README.md'), - }); + test.skip('delete', async () => { + const responsePromise = client.extensions.delete('id_or_name'); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -50,11 +54,7 @@ describe('resource extensions', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled - test.skip('upload: required and optional params', async () => { - const response = await client.extensions.upload({ - file: await toFile(Buffer.from('# my file contents'), 'README.md'), - name: 'name', - }); + test('downloadFromChromeStore: required and optional params', async () => { + const response = await client.extensions.downloadFromChromeStore({ url: 'url', os: 'win' }); }); }); From 68e527cefd5659f579119a39ecd4c170193bbed5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:33:34 +0000 Subject: [PATCH 4/5] feat: click mouse, move mouse, screenshot --- .stats.yml | 8 +- api.md | 18 +- src/client.ts | 8 +- src/resources/browsers/browsers.ts | 24 ++ src/resources/browsers/computer.ts | 328 ++++++++++++++++++ src/resources/browsers/index.ts | 10 + src/resources/extensions.ts | 138 ++++---- src/resources/index.ts | 4 +- tests/api-resources/browsers/computer.test.ts | 155 +++++++++ tests/api-resources/extensions.test.ts | 36 +- 10 files changed, 629 insertions(+), 100 deletions(-) create mode 100644 src/resources/browsers/computer.ts create mode 100644 tests/api-resources/browsers/computer.test.ts diff --git a/.stats.yml b/.stats.yml index 2bd40cc..b4dc606 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 57 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-6eaa6f5654abc94549962d7db1e8c7936af1f815bb3abe2f8249959394da1278.yml -openapi_spec_hash: 31ece7cd801e74228b80a8112a762e56 -config_hash: 3fc2057ce765bc5f27785a694ed0f553 +configured_endpoints: 64 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-e21f0324774a1762bc2bba0da3a8a6b0d0e74720d7a1c83dec813f9e027fcf58.yml +openapi_spec_hash: f1b636abfd6cb8e7c2ba7ffb8e53b9ba +config_hash: 09a2df23048cb16689c9a390d9e5bc47 diff --git a/api.md b/api.md index b442f34..cd45552 100644 --- a/api.md +++ b/api.md @@ -150,6 +150,18 @@ Methods: - client.browsers.logs.stream(id, { ...params }) -> LogEvent +## Computer + +Methods: + +- client.browsers.computer.captureScreenshot(id, { ...params }) -> Response +- client.browsers.computer.clickMouse(id, { ...params }) -> void +- client.browsers.computer.dragMouse(id, { ...params }) -> void +- client.browsers.computer.moveMouse(id, { ...params }) -> void +- client.browsers.computer.pressKey(id, { ...params }) -> void +- client.browsers.computer.scroll(id, { ...params }) -> void +- client.browsers.computer.typeText(id, { ...params }) -> void + # Profiles Types: @@ -183,13 +195,13 @@ Methods: Types: -- ExtensionCreateResponse - ExtensionListResponse +- ExtensionUploadResponse Methods: -- client.extensions.create({ ...params }) -> ExtensionCreateResponse -- client.extensions.retrieve(idOrName) -> Response - client.extensions.list() -> ExtensionListResponse - client.extensions.delete(idOrName) -> void +- client.extensions.download(idOrName) -> Response - client.extensions.downloadFromChromeStore({ ...params }) -> Response +- client.extensions.upload({ ...params }) -> ExtensionUploadResponse diff --git a/src/client.ts b/src/client.ts index 54af496..5497543 100644 --- a/src/client.ts +++ b/src/client.ts @@ -33,10 +33,10 @@ import { } from './resources/deployments'; import { KernelApp } from './core/app-framework'; import { - ExtensionCreateParams, - ExtensionCreateResponse, ExtensionDownloadFromChromeStoreParams, ExtensionListResponse, + ExtensionUploadParams, + ExtensionUploadResponse, Extensions, } from './resources/extensions'; import { @@ -918,10 +918,10 @@ export declare namespace Kernel { export { Extensions as Extensions, - type ExtensionCreateResponse as ExtensionCreateResponse, type ExtensionListResponse as ExtensionListResponse, - type ExtensionCreateParams as ExtensionCreateParams, + type ExtensionUploadResponse as ExtensionUploadResponse, type ExtensionDownloadFromChromeStoreParams as ExtensionDownloadFromChromeStoreParams, + type ExtensionUploadParams as ExtensionUploadParams, }; export type AppAction = API.AppAction; diff --git a/src/resources/browsers/browsers.ts b/src/resources/browsers/browsers.ts index 97d0d77..df843b1 100644 --- a/src/resources/browsers/browsers.ts +++ b/src/resources/browsers/browsers.ts @@ -2,6 +2,17 @@ import { APIResource } from '../../core/resource'; import * as BrowsersAPI from './browsers'; +import * as ComputerAPI from './computer'; +import { + Computer, + ComputerCaptureScreenshotParams, + ComputerClickMouseParams, + ComputerDragMouseParams, + ComputerMoveMouseParams, + ComputerPressKeyParams, + ComputerScrollParams, + ComputerTypeTextParams, +} from './computer'; import * as LogsAPI from './logs'; import { LogStreamParams, Logs } from './logs'; import * as ProcessAPI from './process'; @@ -59,6 +70,7 @@ export class Browsers extends APIResource { fs: FsAPI.Fs = new FsAPI.Fs(this._client); process: ProcessAPI.Process = new ProcessAPI.Process(this._client); logs: LogsAPI.Logs = new LogsAPI.Logs(this._client); + computer: ComputerAPI.Computer = new ComputerAPI.Computer(this._client); /** * Create a new browser session from within an action. @@ -682,6 +694,7 @@ Browsers.Replays = Replays; Browsers.Fs = Fs; Browsers.Process = Process; Browsers.Logs = Logs; +Browsers.Computer = Computer; export declare namespace Browsers { export { @@ -739,4 +752,15 @@ export declare namespace Browsers { }; export { Logs as Logs, type LogStreamParams as LogStreamParams }; + + export { + Computer as Computer, + type ComputerCaptureScreenshotParams as ComputerCaptureScreenshotParams, + type ComputerClickMouseParams as ComputerClickMouseParams, + type ComputerDragMouseParams as ComputerDragMouseParams, + type ComputerMoveMouseParams as ComputerMoveMouseParams, + type ComputerPressKeyParams as ComputerPressKeyParams, + type ComputerScrollParams as ComputerScrollParams, + type ComputerTypeTextParams as ComputerTypeTextParams, + }; } diff --git a/src/resources/browsers/computer.ts b/src/resources/browsers/computer.ts new file mode 100644 index 0000000..49c2954 --- /dev/null +++ b/src/resources/browsers/computer.ts @@ -0,0 +1,328 @@ +// 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 Computer extends APIResource { + /** + * Capture a screenshot of the browser instance + * + * @example + * ```ts + * const response = + * await client.browsers.computer.captureScreenshot('id'); + * + * const content = await response.blob(); + * console.log(content); + * ``` + */ + captureScreenshot( + id: string, + body: ComputerCaptureScreenshotParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.post(path`/browsers/${id}/computer/screenshot`, { + body, + ...options, + headers: buildHeaders([{ Accept: 'image/png' }, options?.headers]), + __binaryResponse: true, + }); + } + + /** + * Simulate a mouse click action on the browser instance + * + * @example + * ```ts + * await client.browsers.computer.clickMouse('id', { + * x: 0, + * y: 0, + * }); + * ``` + */ + clickMouse(id: string, body: ComputerClickMouseParams, options?: RequestOptions): APIPromise { + return this._client.post(path`/browsers/${id}/computer/click_mouse`, { + body, + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } + + /** + * Drag the mouse along a path + * + * @example + * ```ts + * await client.browsers.computer.dragMouse('id', { + * path: [ + * [0, 0], + * [0, 0], + * ], + * }); + * ``` + */ + dragMouse(id: string, body: ComputerDragMouseParams, options?: RequestOptions): APIPromise { + return this._client.post(path`/browsers/${id}/computer/drag_mouse`, { + body, + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } + + /** + * Move the mouse cursor to the specified coordinates on the browser instance + * + * @example + * ```ts + * await client.browsers.computer.moveMouse('id', { + * x: 0, + * y: 0, + * }); + * ``` + */ + moveMouse(id: string, body: ComputerMoveMouseParams, options?: RequestOptions): APIPromise { + return this._client.post(path`/browsers/${id}/computer/move_mouse`, { + body, + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } + + /** + * Press one or more keys on the host computer + * + * @example + * ```ts + * await client.browsers.computer.pressKey('id', { + * keys: ['string'], + * }); + * ``` + */ + pressKey(id: string, body: ComputerPressKeyParams, options?: RequestOptions): APIPromise { + return this._client.post(path`/browsers/${id}/computer/press_key`, { + body, + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } + + /** + * Scroll the mouse wheel at a position on the host computer + * + * @example + * ```ts + * await client.browsers.computer.scroll('id', { x: 0, y: 0 }); + * ``` + */ + scroll(id: string, body: ComputerScrollParams, options?: RequestOptions): APIPromise { + return this._client.post(path`/browsers/${id}/computer/scroll`, { + body, + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } + + /** + * Type text on the browser instance + * + * @example + * ```ts + * await client.browsers.computer.typeText('id', { + * text: 'text', + * }); + * ``` + */ + typeText(id: string, body: ComputerTypeTextParams, options?: RequestOptions): APIPromise { + return this._client.post(path`/browsers/${id}/computer/type`, { + body, + ...options, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + }); + } +} + +export interface ComputerCaptureScreenshotParams { + region?: ComputerCaptureScreenshotParams.Region; +} + +export namespace ComputerCaptureScreenshotParams { + export interface Region { + /** + * Height of the region in pixels + */ + height: number; + + /** + * Width of the region in pixels + */ + width: number; + + /** + * X coordinate of the region's top-left corner + */ + x: number; + + /** + * Y coordinate of the region's top-left corner + */ + y: number; + } +} + +export interface ComputerClickMouseParams { + /** + * X coordinate of the click position + */ + x: number; + + /** + * Y coordinate of the click position + */ + y: number; + + /** + * Mouse button to interact with + */ + button?: 'left' | 'right' | 'middle' | 'back' | 'forward'; + + /** + * Type of click action + */ + click_type?: 'down' | 'up' | 'click'; + + /** + * Modifier keys to hold during the click + */ + hold_keys?: Array; + + /** + * Number of times to repeat the click + */ + num_clicks?: number; +} + +export interface ComputerDragMouseParams { + /** + * Ordered list of [x, y] coordinate pairs to move through while dragging. Must + * contain at least 2 points. + */ + path: Array>; + + /** + * Mouse button to drag with + */ + button?: 'left' | 'middle' | 'right'; + + /** + * Delay in milliseconds between button down and starting to move along the path. + */ + delay?: number; + + /** + * Modifier keys to hold during the drag + */ + hold_keys?: Array; + + /** + * Delay in milliseconds between relative steps while dragging (not the initial + * delay). + */ + step_delay_ms?: number; + + /** + * Number of relative move steps per segment in the path. Minimum 1. + */ + steps_per_segment?: number; +} + +export interface ComputerMoveMouseParams { + /** + * X coordinate to move the cursor to + */ + x: number; + + /** + * Y coordinate to move the cursor to + */ + y: number; + + /** + * Modifier keys to hold during the move + */ + hold_keys?: Array; +} + +export interface ComputerPressKeyParams { + /** + * List of key symbols to press. Each item should be a key symbol supported by + * xdotool (see X11 keysym definitions). Examples include "Return", "Shift", + * "Ctrl", "Alt", "F5". Items in this list could also be combinations, e.g. + * "Ctrl+t" or "Ctrl+Shift+Tab". + */ + keys: Array; + + /** + * Duration to hold the keys down in milliseconds. If omitted or 0, keys are + * tapped. + */ + duration?: number; + + /** + * Optional modifier keys to hold during the key press sequence. + */ + hold_keys?: Array; +} + +export interface ComputerScrollParams { + /** + * X coordinate at which to perform the scroll + */ + x: number; + + /** + * Y coordinate at which to perform the scroll + */ + y: number; + + /** + * Horizontal scroll amount. Positive scrolls right, negative scrolls left. + */ + delta_x?: number; + + /** + * Vertical scroll amount. Positive scrolls down, negative scrolls up. + */ + delta_y?: number; + + /** + * Modifier keys to hold during the scroll + */ + hold_keys?: Array; +} + +export interface ComputerTypeTextParams { + /** + * Text to type on the browser instance + */ + text: string; + + /** + * Delay in milliseconds between keystrokes + */ + delay?: number; +} + +export declare namespace Computer { + export { + type ComputerCaptureScreenshotParams as ComputerCaptureScreenshotParams, + type ComputerClickMouseParams as ComputerClickMouseParams, + type ComputerDragMouseParams as ComputerDragMouseParams, + type ComputerMoveMouseParams as ComputerMoveMouseParams, + type ComputerPressKeyParams as ComputerPressKeyParams, + type ComputerScrollParams as ComputerScrollParams, + type ComputerTypeTextParams as ComputerTypeTextParams, + }; +} diff --git a/src/resources/browsers/index.ts b/src/resources/browsers/index.ts index 53bb8a9..243f485 100644 --- a/src/resources/browsers/index.ts +++ b/src/resources/browsers/index.ts @@ -11,6 +11,16 @@ export { type BrowserDeleteParams, type BrowserLoadExtensionsParams, } from './browsers'; +export { + Computer, + type ComputerCaptureScreenshotParams, + type ComputerClickMouseParams, + type ComputerDragMouseParams, + type ComputerMoveMouseParams, + type ComputerPressKeyParams, + type ComputerScrollParams, + type ComputerTypeTextParams, +} from './computer'; export { Fs, type FFileInfoResponse, diff --git a/src/resources/extensions.ts b/src/resources/extensions.ts index 82f29ba..5697a07 100644 --- a/src/resources/extensions.ts +++ b/src/resources/extensions.ts @@ -10,65 +10,50 @@ import { path } from '../internal/utils/path'; export class Extensions extends APIResource { /** - * Upload a zip file containing an unpacked browser extension. Optionally provide a - * unique name for later reference. + * List extensions owned by the caller's organization. * * @example * ```ts - * const extension = await client.extensions.create({ - * file: fs.createReadStream('path/to/file'), - * }); + * const extensions = await client.extensions.list(); * ``` */ - create(body: ExtensionCreateParams, options?: RequestOptions): APIPromise { - return this._client.post('/extensions', multipartFormRequestOptions({ body, ...options }, this._client)); + list(options?: RequestOptions): APIPromise { + return this._client.get('/extensions', options); } /** - * Download the extension as a ZIP archive by ID or name. + * Delete an extension by its ID or by its name. * * @example * ```ts - * const extension = await client.extensions.retrieve( - * 'id_or_name', - * ); - * - * const content = await extension.blob(); - * console.log(content); + * await client.extensions.delete('id_or_name'); * ``` */ - retrieve(idOrName: string, options?: RequestOptions): APIPromise { - return this._client.get(path`/extensions/${idOrName}`, { + delete(idOrName: string, options?: RequestOptions): APIPromise { + return this._client.delete(path`/extensions/${idOrName}`, { ...options, - headers: buildHeaders([{ Accept: 'application/octet-stream' }, options?.headers]), - __binaryResponse: true, + headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), }); } /** - * List extensions owned by the caller's organization. + * Download the extension as a ZIP archive by ID or name. * * @example * ```ts - * const extensions = await client.extensions.list(); - * ``` - */ - list(options?: RequestOptions): APIPromise { - return this._client.get('/extensions', options); - } - - /** - * Delete an extension by its ID or by its name. + * const response = await client.extensions.download( + * 'id_or_name', + * ); * - * @example - * ```ts - * await client.extensions.delete('id_or_name'); + * const content = await response.blob(); + * console.log(content); * ``` */ - delete(idOrName: string, options?: RequestOptions): APIPromise { - return this._client.delete(path`/extensions/${idOrName}`, { + download(idOrName: string, options?: RequestOptions): APIPromise { + return this._client.get(path`/extensions/${idOrName}`, { ...options, - headers: buildHeaders([{ Accept: '*/*' }, options?.headers]), + headers: buildHeaders([{ Accept: 'application/octet-stream' }, options?.headers]), + __binaryResponse: true, }); } @@ -98,37 +83,21 @@ export class Extensions extends APIResource { __binaryResponse: true, }); } -} -/** - * A browser extension uploaded to Kernel. - */ -export interface ExtensionCreateResponse { /** - * Unique identifier for the extension - */ - id: string; - - /** - * Timestamp when the extension was created - */ - created_at: string; - - /** - * Size of the extension archive in bytes - */ - size_bytes: number; - - /** - * Timestamp when the extension was last used - */ - last_used_at?: string | null; - - /** - * Optional, easier-to-reference name for the extension. Must be unique within the - * organization. + * Upload a zip file containing an unpacked browser extension. Optionally provide a + * unique name for later reference. + * + * @example + * ```ts + * const response = await client.extensions.upload({ + * file: fs.createReadStream('path/to/file'), + * }); + * ``` */ - name?: string | null; + upload(body: ExtensionUploadParams, options?: RequestOptions): APIPromise { + return this._client.post('/extensions', multipartFormRequestOptions({ body, ...options }, this._client)); + } } export type ExtensionListResponse = Array; @@ -166,16 +135,35 @@ export namespace ExtensionListResponse { } } -export interface ExtensionCreateParams { +/** + * A browser extension uploaded to Kernel. + */ +export interface ExtensionUploadResponse { /** - * ZIP file containing the browser extension. + * Unique identifier for the extension */ - file: Uploadable; + id: string; /** - * Optional unique name within the organization to reference this extension. + * Timestamp when the extension was created */ - name?: string; + created_at: string; + + /** + * Size of the extension archive in bytes + */ + size_bytes: number; + + /** + * Timestamp when the extension was last used + */ + last_used_at?: string | null; + + /** + * Optional, easier-to-reference name for the extension. Must be unique within the + * organization. + */ + name?: string | null; } export interface ExtensionDownloadFromChromeStoreParams { @@ -190,11 +178,23 @@ export interface ExtensionDownloadFromChromeStoreParams { os?: 'win' | 'mac' | 'linux'; } +export interface ExtensionUploadParams { + /** + * ZIP file containing the browser extension. + */ + file: Uploadable; + + /** + * Optional unique name within the organization to reference this extension. + */ + name?: string; +} + export declare namespace Extensions { export { - type ExtensionCreateResponse as ExtensionCreateResponse, type ExtensionListResponse as ExtensionListResponse, - type ExtensionCreateParams as ExtensionCreateParams, + type ExtensionUploadResponse as ExtensionUploadResponse, type ExtensionDownloadFromChromeStoreParams as ExtensionDownloadFromChromeStoreParams, + type ExtensionUploadParams as ExtensionUploadParams, }; } diff --git a/src/resources/index.ts b/src/resources/index.ts index 62dfb1a..83e4312 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -27,10 +27,10 @@ export { } from './deployments'; export { Extensions, - type ExtensionCreateResponse, type ExtensionListResponse, - type ExtensionCreateParams, + type ExtensionUploadResponse, type ExtensionDownloadFromChromeStoreParams, + type ExtensionUploadParams, } from './extensions'; export { Invocations, diff --git a/tests/api-resources/browsers/computer.test.ts b/tests/api-resources/browsers/computer.test.ts new file mode 100644 index 0000000..2c2d7de --- /dev/null +++ b/tests/api-resources/browsers/computer.test.ts @@ -0,0 +1,155 @@ +// 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 computer', () => { + test('captureScreenshot: 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.computer.captureScreenshot( + 'id', + { region: { height: 0, width: 0, x: 0, y: 0 } }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(Kernel.NotFoundError); + }); + + // Prism tests are disabled + test.skip('clickMouse: only required params', async () => { + const responsePromise = client.browsers.computer.clickMouse('id', { x: 0, y: 0 }); + 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); + }); + + // Prism tests are disabled + test.skip('clickMouse: required and optional params', async () => { + const response = await client.browsers.computer.clickMouse('id', { + x: 0, + y: 0, + button: 'left', + click_type: 'down', + hold_keys: ['string'], + num_clicks: 0, + }); + }); + + // Prism tests are disabled + test.skip('dragMouse: only required params', async () => { + const responsePromise = client.browsers.computer.dragMouse('id', { + path: [ + [0, 0], + [0, 0], + ], + }); + 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); + }); + + // Prism tests are disabled + test.skip('dragMouse: required and optional params', async () => { + const response = await client.browsers.computer.dragMouse('id', { + path: [ + [0, 0], + [0, 0], + ], + button: 'left', + delay: 0, + hold_keys: ['string'], + step_delay_ms: 0, + steps_per_segment: 1, + }); + }); + + // Prism tests are disabled + test.skip('moveMouse: only required params', async () => { + const responsePromise = client.browsers.computer.moveMouse('id', { x: 0, y: 0 }); + 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); + }); + + // Prism tests are disabled + test.skip('moveMouse: required and optional params', async () => { + const response = await client.browsers.computer.moveMouse('id', { x: 0, y: 0, hold_keys: ['string'] }); + }); + + // Prism tests are disabled + test.skip('pressKey: only required params', async () => { + const responsePromise = client.browsers.computer.pressKey('id', { keys: ['string'] }); + 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); + }); + + // Prism tests are disabled + test.skip('pressKey: required and optional params', async () => { + const response = await client.browsers.computer.pressKey('id', { + keys: ['string'], + duration: 0, + hold_keys: ['string'], + }); + }); + + // Prism tests are disabled + test.skip('scroll: only required params', async () => { + const responsePromise = client.browsers.computer.scroll('id', { x: 0, y: 0 }); + 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); + }); + + // Prism tests are disabled + test.skip('scroll: required and optional params', async () => { + const response = await client.browsers.computer.scroll('id', { + x: 0, + y: 0, + delta_x: 0, + delta_y: 0, + hold_keys: ['string'], + }); + }); + + // Prism tests are disabled + test.skip('typeText: only required params', async () => { + const responsePromise = client.browsers.computer.typeText('id', { text: 'text' }); + 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); + }); + + // Prism tests are disabled + test.skip('typeText: required and optional params', async () => { + const response = await client.browsers.computer.typeText('id', { text: 'text', delay: 0 }); + }); +}); diff --git a/tests/api-resources/extensions.test.ts b/tests/api-resources/extensions.test.ts index f401acf..515415f 100644 --- a/tests/api-resources/extensions.test.ts +++ b/tests/api-resources/extensions.test.ts @@ -9,10 +9,8 @@ const client = new Kernel({ describe('resource extensions', () => { // Prism tests are disabled - test.skip('create: only required params', async () => { - const responsePromise = client.extensions.create({ - file: await toFile(Buffer.from('# my file contents'), 'README.md'), - }); + test.skip('list', async () => { + const responsePromise = client.extensions.list(); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -23,16 +21,8 @@ describe('resource extensions', () => { }); // Prism tests are disabled - test.skip('create: required and optional params', async () => { - const response = await client.extensions.create({ - file: await toFile(Buffer.from('# my file contents'), 'README.md'), - name: 'name', - }); - }); - - // Prism tests are disabled - test.skip('list', async () => { - const responsePromise = client.extensions.list(); + test.skip('delete', async () => { + const responsePromise = client.extensions.delete('id_or_name'); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -42,9 +32,15 @@ describe('resource extensions', () => { expect(dataAndResponse.response).toBe(rawResponse); }); + test('downloadFromChromeStore: required and optional params', async () => { + const response = await client.extensions.downloadFromChromeStore({ url: 'url', os: 'win' }); + }); + // Prism tests are disabled - test.skip('delete', async () => { - const responsePromise = client.extensions.delete('id_or_name'); + test.skip('upload: only required params', async () => { + const responsePromise = client.extensions.upload({ + file: await toFile(Buffer.from('# my file contents'), 'README.md'), + }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -54,7 +50,11 @@ describe('resource extensions', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - test('downloadFromChromeStore: required and optional params', async () => { - const response = await client.extensions.downloadFromChromeStore({ url: 'url', os: 'win' }); + // Prism tests are disabled + test.skip('upload: required and optional params', async () => { + const response = await client.extensions.upload({ + file: await toFile(Buffer.from('# my file contents'), 'README.md'), + name: 'name', + }); }); }); From 153049867641ab7ae6110e82e6894684ffada84b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:33:52 +0000 Subject: [PATCH 5/5] release: 0.15.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 9 +++++++++ package.json | 2 +- src/version.ts | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b19a3f4..f87262a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.14.2" + ".": "0.15.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 34be887..0725a2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.15.0 (2025-10-17) + +Full Changelog: [v0.14.2...v0.15.0](https://github.com/onkernel/kernel-node-sdk/compare/v0.14.2...v0.15.0) + +### Features + +* click mouse, move mouse, screenshot ([68e527c](https://github.com/onkernel/kernel-node-sdk/commit/68e527cefd5659f579119a39ecd4c170193bbed5)) +* Phani/deploy with GitHub url ([1e97151](https://github.com/onkernel/kernel-node-sdk/commit/1e971513c25cdfe84624c033ad89c5bfdc7fef20)) + ## 0.14.2 (2025-10-16) Full Changelog: [v0.14.1...v0.14.2](https://github.com/onkernel/kernel-node-sdk/compare/v0.14.1...v0.14.2) diff --git a/package.json b/package.json index 2d371a8..dfa3c73 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@onkernel/sdk", - "version": "0.14.2", + "version": "0.15.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 0af0c85..b67001e 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.14.2'; // x-release-please-version +export const VERSION = '0.15.0'; // x-release-please-version