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 () => {