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/.stats.yml b/.stats.yml
index 6bb4af8..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-6c765f1c4ce1c4dd4ceb371f56bf047aa79af36031ba43cbd68fa16a5fdb9bb3.yml
-openapi_spec_hash: e9086f69281360f4e0895c9274a59531
-config_hash: deadfc4d2b0a947673bcf559b5db6e1b
+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/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/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..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:
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/internal/uploads.ts b/src/internal/uploads.ts
index a4e57ac..001b7d9 100644
--- a/src/internal/uploads.ts
+++ b/src/internal/uploads.ts
@@ -176,9 +176,27 @@ 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/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/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/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
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/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/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 () => {