From 8ea7bba5cd9cd9d047091386025965edeb316ab4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 00:59:13 +0000 Subject: [PATCH 01/18] chore: hide build context APIs --- .stats.yml | 6 +- api.md | 4 - src/index.ts | 2 - src/resources/blueprints.ts | 111 ------------------ src/resources/devboxes/devboxes.ts | 18 --- src/resources/index.ts | 1 - tests/api-resources/blueprints.test.ts | 4 - tests/api-resources/devboxes/devboxes.test.ts | 54 --------- 8 files changed, 3 insertions(+), 197 deletions(-) diff --git a/.stats.yml b/.stats.yml index bc705b998..1235f571e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 97 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-0dd27c6877ed117c50fe0af95cee4d54c646d2484368e131b8e3315eba3fffcc.yml -openapi_spec_hash: 68f663172747aef8e66f2b23289efc7b +configured_endpoints: 94 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-907baea7d51fd2660895c74603cf1ff765eb9f62eb10ce6e0c1d17ac1c16abf2.yml +openapi_spec_hash: f1280edb22cdd91238efc2b18e3d324c config_hash: 2363f563f42501d2b1587a4f64bdccaf diff --git a/api.md b/api.md index 54c2fa2f5..9b205061c 100644 --- a/api.md +++ b/api.md @@ -97,7 +97,6 @@ Types: - DevboxView - DevboxCreateSSHKeyResponse - DevboxDeleteDiskSnapshotResponse -- DevboxKeepAliveResponse - DevboxReadFileContentsResponse - DevboxRemoveTunnelResponse - DevboxUploadFileResponse @@ -115,15 +114,12 @@ Methods: - client.devboxes.execute(id, { ...params }) -> DevboxAsyncExecutionDetailView - client.devboxes.executeAsync(id, { ...params }) -> DevboxAsyncExecutionDetailView - client.devboxes.executeSync(id, { ...params }) -> DevboxExecutionDetailView -- client.devboxes.keepAlive(id) -> unknown - client.devboxes.listDiskSnapshots({ ...params }) -> DevboxSnapshotViewsDiskSnapshotsCursorIDPage - client.devboxes.readFileContents(id, { ...params }) -> string - client.devboxes.removeTunnel(id, { ...params }) -> unknown -- client.devboxes.resume(id) -> DevboxView - client.devboxes.shutdown(id) -> DevboxView - client.devboxes.snapshotDisk(id, { ...params }) -> DevboxSnapshotView - client.devboxes.snapshotDiskAsync(id, { ...params }) -> DevboxSnapshotView -- client.devboxes.suspend(id) -> DevboxView - client.devboxes.uploadFile(id, { ...params }) -> unknown - client.devboxes.waitForCommand(devboxId, executionId, { ...params }) -> DevboxAsyncExecutionDetailView - client.devboxes.writeFileContents(id, { ...params }) -> DevboxExecutionDetailView diff --git a/src/index.ts b/src/index.ts index 59536fcb2..1b2f733c7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -125,7 +125,6 @@ import { DevboxExecuteParams, DevboxExecuteSyncParams, DevboxExecutionDetailView, - DevboxKeepAliveResponse, DevboxKillExecutionRequest, DevboxListDiskSnapshotsParams, DevboxListParams, @@ -497,7 +496,6 @@ export declare namespace Runloop { type DevboxView as DevboxView, type DevboxCreateSSHKeyResponse as DevboxCreateSSHKeyResponse, type DevboxDeleteDiskSnapshotResponse as DevboxDeleteDiskSnapshotResponse, - type DevboxKeepAliveResponse as DevboxKeepAliveResponse, type DevboxReadFileContentsResponse as DevboxReadFileContentsResponse, type DevboxRemoveTunnelResponse as DevboxRemoveTunnelResponse, type DevboxUploadFileResponse as DevboxUploadFileResponse, diff --git a/src/resources/blueprints.ts b/src/resources/blueprints.ts index 993c3f0d0..d6418ea46 100644 --- a/src/resources/blueprints.ts +++ b/src/resources/blueprints.ts @@ -318,11 +318,6 @@ export interface BlueprintBuildParameters { */ build_args?: { [key: string]: string } | null; - /** - * A build context backed by an Object. - */ - build_context?: BlueprintBuildParameters.BuildContext | null; - /** * A list of code mounts to be included in the Blueprint. */ @@ -348,14 +343,6 @@ export interface BlueprintBuildParameters { */ metadata?: { [key: string]: string } | null; - /** - * (Optional) Map of named build contexts to attach to the Blueprint build, where - * the keys are the name used when referencing the contexts in a Dockerfile. See - * Docker buildx additional contexts for details: - * https://docs.docker.com/reference/cli/docker/buildx/build/#build-context - */ - named_build_contexts?: { [key: string]: BlueprintBuildParameters.NamedBuildContexts } | null; - /** * (Optional) Map of mount IDs/environment variable names to secret names. Secrets * will be available to commands during the build. Secrets are NOT stored in the @@ -378,30 +365,6 @@ export interface BlueprintBuildParameters { } export namespace BlueprintBuildParameters { - /** - * A build context backed by an Object. - */ - export interface BuildContext { - /** - * The ID of an object, whose contents are to be used as a build context. - */ - object_id: string; - - type: 'object'; - } - - /** - * A build context backed by an Object. - */ - export interface NamedBuildContexts { - /** - * The ID of an object, whose contents are to be used as a build context. - */ - object_id: string; - - type: 'object'; - } - export interface Service { /** * The image of the container service. @@ -640,11 +603,6 @@ export interface BlueprintCreateParams { */ build_args?: { [key: string]: string } | null; - /** - * A build context backed by an Object. - */ - build_context?: BlueprintCreateParams.BuildContext | null; - /** * A list of code mounts to be included in the Blueprint. */ @@ -670,14 +628,6 @@ export interface BlueprintCreateParams { */ metadata?: { [key: string]: string } | null; - /** - * (Optional) Map of named build contexts to attach to the Blueprint build, where - * the keys are the name used when referencing the contexts in a Dockerfile. See - * Docker buildx additional contexts for details: - * https://docs.docker.com/reference/cli/docker/buildx/build/#build-context - */ - named_build_contexts?: { [key: string]: BlueprintCreateParams.NamedBuildContexts } | null; - /** * (Optional) Map of mount IDs/environment variable names to secret names. Secrets * will be available to commands during the build. Secrets are NOT stored in the @@ -700,30 +650,6 @@ export interface BlueprintCreateParams { } export namespace BlueprintCreateParams { - /** - * A build context backed by an Object. - */ - export interface BuildContext { - /** - * The ID of an object, whose contents are to be used as a build context. - */ - object_id: string; - - type: 'object'; - } - - /** - * A build context backed by an Object. - */ - export interface NamedBuildContexts { - /** - * The ID of an object, whose contents are to be used as a build context. - */ - object_id: string; - - type: 'object'; - } - export interface Service { /** * The image of the container service. @@ -855,11 +781,6 @@ export interface BlueprintPreviewParams { */ build_args?: { [key: string]: string } | null; - /** - * A build context backed by an Object. - */ - build_context?: BlueprintPreviewParams.BuildContext | null; - /** * A list of code mounts to be included in the Blueprint. */ @@ -885,14 +806,6 @@ export interface BlueprintPreviewParams { */ metadata?: { [key: string]: string } | null; - /** - * (Optional) Map of named build contexts to attach to the Blueprint build, where - * the keys are the name used when referencing the contexts in a Dockerfile. See - * Docker buildx additional contexts for details: - * https://docs.docker.com/reference/cli/docker/buildx/build/#build-context - */ - named_build_contexts?: { [key: string]: BlueprintPreviewParams.NamedBuildContexts } | null; - /** * (Optional) Map of mount IDs/environment variable names to secret names. Secrets * will be available to commands during the build. Secrets are NOT stored in the @@ -915,30 +828,6 @@ export interface BlueprintPreviewParams { } export namespace BlueprintPreviewParams { - /** - * A build context backed by an Object. - */ - export interface BuildContext { - /** - * The ID of an object, whose contents are to be used as a build context. - */ - object_id: string; - - type: 'object'; - } - - /** - * A build context backed by an Object. - */ - export interface NamedBuildContexts { - /** - * The ID of an object, whose contents are to be used as a build context. - */ - object_id: string; - - type: 'object'; - } - export interface Service { /** * The image of the container service. diff --git a/src/resources/devboxes/devboxes.ts b/src/resources/devboxes/devboxes.ts index eb486e086..1fbc01ec8 100644 --- a/src/resources/devboxes/devboxes.ts +++ b/src/resources/devboxes/devboxes.ts @@ -395,15 +395,6 @@ export class Devboxes extends APIResource { return this._client.post(`/v1/devboxes/${id}/remove_tunnel`, { body, ...options }); } - /** - * Resume a suspended Devbox with the disk state captured as suspend time. Note - * that any previously running processes or daemons will need to be restarted using - * the Devbox shell tools. - */ - resume(id: string, options?: Core.RequestOptions): Core.APIPromise { - return this._client.post(`/v1/devboxes/${id}/resume`, options); - } - /** * Shutdown a running Devbox. This will permanently stop the Devbox. If you want to * save the state of the Devbox, you should take a snapshot before shutting down or @@ -460,15 +451,6 @@ export class Devboxes extends APIResource { return this._client.post(`/v1/devboxes/${id}/snapshot_disk_async`, { body, ...options }); } - /** - * Suspend a running Devbox and create a disk snapshot to enable resuming the - * Devbox later with the same disk. Note this will not snapshot memory state such - * as running processes. - */ - suspend(id: string, options?: Core.RequestOptions): Core.APIPromise { - return this._client.post(`/v1/devboxes/${id}/suspend`, options); - } - /** * Upload file contents of any type (binary, text, etc) to a Devbox. Note this API * is suitable for large files (larger than 100MB) and efficiently uploads files diff --git a/src/resources/index.ts b/src/resources/index.ts index b07a85e3e..456395ee1 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -61,7 +61,6 @@ export { type DevboxView, type DevboxCreateSSHKeyResponse, type DevboxDeleteDiskSnapshotResponse, - type DevboxKeepAliveResponse, type DevboxReadFileContentsResponse, type DevboxRemoveTunnelResponse, type DevboxUploadFileResponse, diff --git a/tests/api-resources/blueprints.test.ts b/tests/api-resources/blueprints.test.ts index 5f8049667..53fa329d4 100644 --- a/tests/api-resources/blueprints.test.ts +++ b/tests/api-resources/blueprints.test.ts @@ -26,7 +26,6 @@ describe('resource blueprints', () => { base_blueprint_id: 'base_blueprint_id', base_blueprint_name: 'base_blueprint_name', build_args: { foo: 'string' }, - build_context: { object_id: 'object_id', type: 'object' }, code_mounts: [ { repo_name: 'repo_name', @@ -52,7 +51,6 @@ describe('resource blueprints', () => { user_parameters: { uid: 0, username: 'username' }, }, metadata: { foo: 'string' }, - named_build_contexts: { foo: { object_id: 'object_id', type: 'object' } }, secrets: { foo: 'string' }, services: [ { @@ -257,7 +255,6 @@ describe('resource blueprints', () => { name: 'name', base_blueprint_name: 'base_blueprint_name', build_args: { foo: 'string' }, - build_context: { object_id: 'object_id', type: 'object' }, code_mounts: [ { repo_name: 'repo_name', @@ -283,7 +280,6 @@ describe('resource blueprints', () => { user_parameters: { uid: 0, username: 'username' }, }, metadata: { foo: 'string' }, - named_build_contexts: { foo: { object_id: 'object_id', type: 'object' } }, secrets: { foo: 'string' }, services: [ { diff --git a/tests/api-resources/devboxes/devboxes.test.ts b/tests/api-resources/devboxes/devboxes.test.ts index cba3193d9..6fa6e4243 100644 --- a/tests/api-resources/devboxes/devboxes.test.ts +++ b/tests/api-resources/devboxes/devboxes.test.ts @@ -262,24 +262,6 @@ describe('resource devboxes', () => { }); }); - test('keepAlive', async () => { - const responsePromise = client.devboxes.keepAlive('id'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - test('keepAlive: request options instead of 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.devboxes.keepAlive('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( - Runloop.NotFoundError, - ); - }); - test('listDiskSnapshots', async () => { const responsePromise = client.devboxes.listDiskSnapshots(); const rawResponse = await responsePromise.asResponse(); @@ -345,24 +327,6 @@ describe('resource devboxes', () => { const response = await client.devboxes.removeTunnel('id', { port: 0 }); }); - test('resume', async () => { - const responsePromise = client.devboxes.resume('id'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - test('resume: request options instead of 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.devboxes.resume('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( - Runloop.NotFoundError, - ); - }); - test('shutdown', async () => { const responsePromise = client.devboxes.shutdown('id'); const rawResponse = await responsePromise.asResponse(); @@ -439,24 +403,6 @@ describe('resource devboxes', () => { ).rejects.toThrow(Runloop.NotFoundError); }); - test('suspend', async () => { - const responsePromise = client.devboxes.suspend('id'); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - test('suspend: request options instead of 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.devboxes.suspend('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( - Runloop.NotFoundError, - ); - }); - test('uploadFile: only required params', async () => { const responsePromise = client.devboxes.uploadFile('id', { path: 'path' }); const rawResponse = await responsePromise.asResponse(); From 2ef3ede2bbc71b79f111ab9c0326573f44dfab4c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 23:40:48 +0000 Subject: [PATCH 02/18] fix(devbox): launch parameter typo --- .stats.yml | 4 ++-- src/resources/shared.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 1235f571e..d57d5f1ec 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 94 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-907baea7d51fd2660895c74603cf1ff765eb9f62eb10ce6e0c1d17ac1c16abf2.yml -openapi_spec_hash: f1280edb22cdd91238efc2b18e3d324c +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-ae41f8669a7ba1eec677634573b55bace4dedcf7027e0c1fee503d7c44472e3e.yml +openapi_spec_hash: 9a3c12559ec74a00adffe7b254757903 config_hash: 2363f563f42501d2b1587a4f64bdccaf diff --git a/src/resources/shared.ts b/src/resources/shared.ts index 3eee1e4a8..f352fa329 100644 --- a/src/resources/shared.ts +++ b/src/resources/shared.ts @@ -265,7 +265,7 @@ export namespace LaunchParameters { */ export interface UserParameters { /** - * User ID (UID) for the Linux user. Must be a positive integer. + * User ID (UID) for the Linux user. Must be a non-negative integer. */ uid: number; From e810958cdab4b067d91f7a2e3cec481b44a5a7d9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 02:37:39 +0000 Subject: [PATCH 03/18] fix(scorer): fixed RL_TEST_CONTEXT to RL_SCORER_CONTEXT --- .stats.yml | 4 ++-- src/resources/scenarios/scorers.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.stats.yml b/.stats.yml index d57d5f1ec..f02816b21 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 94 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-ae41f8669a7ba1eec677634573b55bace4dedcf7027e0c1fee503d7c44472e3e.yml -openapi_spec_hash: 9a3c12559ec74a00adffe7b254757903 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-c15740e10009ccfaf6cb6ae4463496618c05d033bdd779bf501f5afb0c05ffc9.yml +openapi_spec_hash: a08d1a45d83ed2956ab643d1c6c07a40 config_hash: 2363f563f42501d2b1587a4f64bdccaf diff --git a/src/resources/scenarios/scorers.ts b/src/resources/scenarios/scorers.ts index 7bfe05b32..5e5d0f892 100644 --- a/src/resources/scenarios/scorers.ts +++ b/src/resources/scenarios/scorers.ts @@ -79,7 +79,7 @@ export interface ScorerCreateResponse { id: string; /** - * Bash script that takes in $RL_TEST_CONTEXT as env variable and runs scoring. + * Bash script that takes in $RL_SCORER_CONTEXT as env variable and runs scoring. */ bash_script: string; @@ -99,7 +99,7 @@ export interface ScorerRetrieveResponse { id: string; /** - * Bash script that takes in $RL_TEST_CONTEXT as env variable and runs scoring. + * Bash script that takes in $RL_SCORER_CONTEXT as env variable and runs scoring. */ bash_script: string; @@ -119,7 +119,7 @@ export interface ScorerUpdateResponse { id: string; /** - * Bash script that takes in $RL_TEST_CONTEXT as env variable and runs scoring. + * Bash script that takes in $RL_SCORER_CONTEXT as env variable and runs scoring. */ bash_script: string; @@ -139,7 +139,7 @@ export interface ScorerListResponse { id: string; /** - * Bash script that takes in $RL_TEST_CONTEXT as env variable and runs scoring. + * Bash script that takes in $RL_SCORER_CONTEXT as env variable and runs scoring. */ bash_script: string; From a5143eff331d3404e1649ec57f01e67ea18b6613 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 01:13:48 +0000 Subject: [PATCH 04/18] fix(api): don't ignore devbox keep_alive, suspend and resume in api --- .stats.yml | 6 +-- api.md | 4 ++ src/index.ts | 2 + src/resources/devboxes/devboxes.ts | 18 +++++++ src/resources/index.ts | 1 + tests/api-resources/devboxes/devboxes.test.ts | 54 +++++++++++++++++++ 6 files changed, 82 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index f02816b21..21ba6b325 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 94 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-c15740e10009ccfaf6cb6ae4463496618c05d033bdd779bf501f5afb0c05ffc9.yml -openapi_spec_hash: a08d1a45d83ed2956ab643d1c6c07a40 +configured_endpoints: 97 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-d2463b4c27719ea7275c8f587fa2c90e333471fdede11e5f944faa92dfb417d1.yml +openapi_spec_hash: 132c1d9f04583e997df5d7698e155fff config_hash: 2363f563f42501d2b1587a4f64bdccaf diff --git a/api.md b/api.md index 9b205061c..54c2fa2f5 100644 --- a/api.md +++ b/api.md @@ -97,6 +97,7 @@ Types: - DevboxView - DevboxCreateSSHKeyResponse - DevboxDeleteDiskSnapshotResponse +- DevboxKeepAliveResponse - DevboxReadFileContentsResponse - DevboxRemoveTunnelResponse - DevboxUploadFileResponse @@ -114,12 +115,15 @@ Methods: - client.devboxes.execute(id, { ...params }) -> DevboxAsyncExecutionDetailView - client.devboxes.executeAsync(id, { ...params }) -> DevboxAsyncExecutionDetailView - client.devboxes.executeSync(id, { ...params }) -> DevboxExecutionDetailView +- client.devboxes.keepAlive(id) -> unknown - client.devboxes.listDiskSnapshots({ ...params }) -> DevboxSnapshotViewsDiskSnapshotsCursorIDPage - client.devboxes.readFileContents(id, { ...params }) -> string - client.devboxes.removeTunnel(id, { ...params }) -> unknown +- client.devboxes.resume(id) -> DevboxView - client.devboxes.shutdown(id) -> DevboxView - client.devboxes.snapshotDisk(id, { ...params }) -> DevboxSnapshotView - client.devboxes.snapshotDiskAsync(id, { ...params }) -> DevboxSnapshotView +- client.devboxes.suspend(id) -> DevboxView - client.devboxes.uploadFile(id, { ...params }) -> unknown - client.devboxes.waitForCommand(devboxId, executionId, { ...params }) -> DevboxAsyncExecutionDetailView - client.devboxes.writeFileContents(id, { ...params }) -> DevboxExecutionDetailView diff --git a/src/index.ts b/src/index.ts index 1b2f733c7..59536fcb2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -125,6 +125,7 @@ import { DevboxExecuteParams, DevboxExecuteSyncParams, DevboxExecutionDetailView, + DevboxKeepAliveResponse, DevboxKillExecutionRequest, DevboxListDiskSnapshotsParams, DevboxListParams, @@ -496,6 +497,7 @@ export declare namespace Runloop { type DevboxView as DevboxView, type DevboxCreateSSHKeyResponse as DevboxCreateSSHKeyResponse, type DevboxDeleteDiskSnapshotResponse as DevboxDeleteDiskSnapshotResponse, + type DevboxKeepAliveResponse as DevboxKeepAliveResponse, type DevboxReadFileContentsResponse as DevboxReadFileContentsResponse, type DevboxRemoveTunnelResponse as DevboxRemoveTunnelResponse, type DevboxUploadFileResponse as DevboxUploadFileResponse, diff --git a/src/resources/devboxes/devboxes.ts b/src/resources/devboxes/devboxes.ts index 1fbc01ec8..eb486e086 100644 --- a/src/resources/devboxes/devboxes.ts +++ b/src/resources/devboxes/devboxes.ts @@ -395,6 +395,15 @@ export class Devboxes extends APIResource { return this._client.post(`/v1/devboxes/${id}/remove_tunnel`, { body, ...options }); } + /** + * Resume a suspended Devbox with the disk state captured as suspend time. Note + * that any previously running processes or daemons will need to be restarted using + * the Devbox shell tools. + */ + resume(id: string, options?: Core.RequestOptions): Core.APIPromise { + return this._client.post(`/v1/devboxes/${id}/resume`, options); + } + /** * Shutdown a running Devbox. This will permanently stop the Devbox. If you want to * save the state of the Devbox, you should take a snapshot before shutting down or @@ -451,6 +460,15 @@ export class Devboxes extends APIResource { return this._client.post(`/v1/devboxes/${id}/snapshot_disk_async`, { body, ...options }); } + /** + * Suspend a running Devbox and create a disk snapshot to enable resuming the + * Devbox later with the same disk. Note this will not snapshot memory state such + * as running processes. + */ + suspend(id: string, options?: Core.RequestOptions): Core.APIPromise { + return this._client.post(`/v1/devboxes/${id}/suspend`, options); + } + /** * Upload file contents of any type (binary, text, etc) to a Devbox. Note this API * is suitable for large files (larger than 100MB) and efficiently uploads files diff --git a/src/resources/index.ts b/src/resources/index.ts index 456395ee1..b07a85e3e 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -61,6 +61,7 @@ export { type DevboxView, type DevboxCreateSSHKeyResponse, type DevboxDeleteDiskSnapshotResponse, + type DevboxKeepAliveResponse, type DevboxReadFileContentsResponse, type DevboxRemoveTunnelResponse, type DevboxUploadFileResponse, diff --git a/tests/api-resources/devboxes/devboxes.test.ts b/tests/api-resources/devboxes/devboxes.test.ts index 6fa6e4243..cba3193d9 100644 --- a/tests/api-resources/devboxes/devboxes.test.ts +++ b/tests/api-resources/devboxes/devboxes.test.ts @@ -262,6 +262,24 @@ describe('resource devboxes', () => { }); }); + test('keepAlive', async () => { + const responsePromise = client.devboxes.keepAlive('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('keepAlive: request options instead of 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.devboxes.keepAlive('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( + Runloop.NotFoundError, + ); + }); + test('listDiskSnapshots', async () => { const responsePromise = client.devboxes.listDiskSnapshots(); const rawResponse = await responsePromise.asResponse(); @@ -327,6 +345,24 @@ describe('resource devboxes', () => { const response = await client.devboxes.removeTunnel('id', { port: 0 }); }); + test('resume', async () => { + const responsePromise = client.devboxes.resume('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('resume: request options instead of 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.devboxes.resume('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( + Runloop.NotFoundError, + ); + }); + test('shutdown', async () => { const responsePromise = client.devboxes.shutdown('id'); const rawResponse = await responsePromise.asResponse(); @@ -403,6 +439,24 @@ describe('resource devboxes', () => { ).rejects.toThrow(Runloop.NotFoundError); }); + test('suspend', async () => { + const responsePromise = client.devboxes.suspend('id'); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('suspend: request options instead of 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.devboxes.suspend('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( + Runloop.NotFoundError, + ); + }); + test('uploadFile: only required params', async () => { const responsePromise = client.devboxes.uploadFile('id', { path: 'path' }); const rawResponse = await responsePromise.asResponse(); From e295c861c54be8320cf3a049023722159e1566d9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 22:36:20 +0000 Subject: [PATCH 05/18] feat(blueprints): Add build context to the OpenAPI spec (#6494) --- .stats.yml | 4 +- src/resources/blueprints.ts | 51 +++++++++++++++++++ src/resources/shared.ts | 15 ++++-- .../benchmarks/benchmarks.test.ts | 1 + tests/api-resources/blueprints.test.ts | 2 + .../api-resources/scenarios/scenarios.test.ts | 1 + 6 files changed, 69 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index 21ba6b325..a7dd140f8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 97 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-d2463b4c27719ea7275c8f587fa2c90e333471fdede11e5f944faa92dfb417d1.yml -openapi_spec_hash: 132c1d9f04583e997df5d7698e155fff +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-b1e4697ee11a301905abe34736d6a2e74a2200c2f9bade48b6f50ee2d65a814f.yml +openapi_spec_hash: 3ebe459b324ae2757ba3bee9d1484e90 config_hash: 2363f563f42501d2b1587a4f64bdccaf diff --git a/src/resources/blueprints.ts b/src/resources/blueprints.ts index d6418ea46..8d501c22c 100644 --- a/src/resources/blueprints.ts +++ b/src/resources/blueprints.ts @@ -318,6 +318,11 @@ export interface BlueprintBuildParameters { */ build_args?: { [key: string]: string } | null; + /** + * A build context backed by an Object. + */ + build_context?: BlueprintBuildParameters.BuildContext | null; + /** * A list of code mounts to be included in the Blueprint. */ @@ -365,6 +370,18 @@ export interface BlueprintBuildParameters { } export namespace BlueprintBuildParameters { + /** + * A build context backed by an Object. + */ + export interface BuildContext { + /** + * The ID of an object, whose contents are to be used as a build context. + */ + object_id: string; + + type: 'object'; + } + export interface Service { /** * The image of the container service. @@ -603,6 +620,11 @@ export interface BlueprintCreateParams { */ build_args?: { [key: string]: string } | null; + /** + * A build context backed by an Object. + */ + build_context?: BlueprintCreateParams.BuildContext | null; + /** * A list of code mounts to be included in the Blueprint. */ @@ -650,6 +672,18 @@ export interface BlueprintCreateParams { } export namespace BlueprintCreateParams { + /** + * A build context backed by an Object. + */ + export interface BuildContext { + /** + * The ID of an object, whose contents are to be used as a build context. + */ + object_id: string; + + type: 'object'; + } + export interface Service { /** * The image of the container service. @@ -781,6 +815,11 @@ export interface BlueprintPreviewParams { */ build_args?: { [key: string]: string } | null; + /** + * A build context backed by an Object. + */ + build_context?: BlueprintPreviewParams.BuildContext | null; + /** * A list of code mounts to be included in the Blueprint. */ @@ -828,6 +867,18 @@ export interface BlueprintPreviewParams { } export namespace BlueprintPreviewParams { + /** + * A build context backed by an Object. + */ + export interface BuildContext { + /** + * The ID of an object, whose contents are to be used as a build context. + */ + object_id: string; + + type: 'object'; + } + export interface Service { /** * The image of the container service. diff --git a/src/resources/shared.ts b/src/resources/shared.ts index f352fa329..8b95be6e5 100644 --- a/src/resources/shared.ts +++ b/src/resources/shared.ts @@ -285,10 +285,14 @@ export type Mount = export namespace Mount { export interface FileMountParameters { /** - * Map of file paths to file contents to be written before setup. Keys are absolute - * paths where files should be created, values are the file contents. + * Content of the file to mount. */ - files: { [key: string]: string }; + content: string; + + /** + * Target path where the file should be mounted. + */ + target: string; type: 'file_mount'; } @@ -322,6 +326,11 @@ export interface RunProfile { */ launchParameters?: LaunchParameters | null; + /** + * A list of mounts to be included in the scenario run. + */ + mounts?: Array | null; + /** * Purpose of the run. */ diff --git a/tests/api-resources/benchmarks/benchmarks.test.ts b/tests/api-resources/benchmarks/benchmarks.test.ts index ad125a1a0..d1850e6ba 100644 --- a/tests/api-resources/benchmarks/benchmarks.test.ts +++ b/tests/api-resources/benchmarks/benchmarks.test.ts @@ -189,6 +189,7 @@ describe('resource benchmarks', () => { resource_size_request: 'X_SMALL', user_parameters: { uid: 0, username: 'username' }, }, + mounts: [{ object_id: 'object_id', object_path: 'object_path', type: 'object_mount' }], purpose: 'purpose', secrets: { foo: 'string' }, }, diff --git a/tests/api-resources/blueprints.test.ts b/tests/api-resources/blueprints.test.ts index 53fa329d4..f781eb437 100644 --- a/tests/api-resources/blueprints.test.ts +++ b/tests/api-resources/blueprints.test.ts @@ -26,6 +26,7 @@ describe('resource blueprints', () => { base_blueprint_id: 'base_blueprint_id', base_blueprint_name: 'base_blueprint_name', build_args: { foo: 'string' }, + build_context: { object_id: 'object_id', type: 'object' }, code_mounts: [ { repo_name: 'repo_name', @@ -255,6 +256,7 @@ describe('resource blueprints', () => { name: 'name', base_blueprint_name: 'base_blueprint_name', build_args: { foo: 'string' }, + build_context: { object_id: 'object_id', type: 'object' }, code_mounts: [ { repo_name: 'repo_name', diff --git a/tests/api-resources/scenarios/scenarios.test.ts b/tests/api-resources/scenarios/scenarios.test.ts index b1dc55f80..d3f185a50 100644 --- a/tests/api-resources/scenarios/scenarios.test.ts +++ b/tests/api-resources/scenarios/scenarios.test.ts @@ -251,6 +251,7 @@ describe('resource scenarios', () => { resource_size_request: 'X_SMALL', user_parameters: { uid: 0, username: 'username' }, }, + mounts: [{ object_id: 'object_id', object_path: 'object_path', type: 'object_mount' }], purpose: 'purpose', secrets: { foo: 'string' }, }, From 1e6814677b20e12513da0faaa3cd4414a4d3df6e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 23:45:27 +0000 Subject: [PATCH 06/18] chore(mounts): Update documentation for deprecated fields to direct the user to the replacement API --- .stats.yml | 4 ++-- src/resources/devboxes/devboxes.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index a7dd140f8..0ab1f506b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 97 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-b1e4697ee11a301905abe34736d6a2e74a2200c2f9bade48b6f50ee2d65a814f.yml -openapi_spec_hash: 3ebe459b324ae2757ba3bee9d1484e90 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-cb2d725f71e87810cd872eacd70e867ca10f94980fdf9c78bb2844c02ee47bf3.yml +openapi_spec_hash: 16ce3e9184fc2afdee66db18a83a96e8 config_hash: 2363f563f42501d2b1587a4f64bdccaf diff --git a/src/resources/devboxes/devboxes.ts b/src/resources/devboxes/devboxes.ts index eb486e086..028d9a168 100644 --- a/src/resources/devboxes/devboxes.ts +++ b/src/resources/devboxes/devboxes.ts @@ -897,7 +897,7 @@ export interface DevboxCreateParams { blueprint_name?: string | null; /** - * A list of code mounts to be included in the Devbox. + * A list of code mounts to be included in the Devbox. Use mounts instead. */ code_mounts?: Array | null; @@ -914,7 +914,7 @@ export interface DevboxCreateParams { environment_variables?: { [key: string]: string } | null; /** - * (Optional) Map of paths and file contents to write before setup.. + * Map of paths and file contents to write before setup. Use mounts instead. */ file_mounts?: { [key: string]: string } | null; From 4ebe08c525a71cecb26e78a3a52602efb40fb968 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 6 Dec 2025 03:37:12 +0000 Subject: [PATCH 07/18] fix(mcp): return correct lines on typescript errors --- .devcontainer/Dockerfile | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .devcontainer/Dockerfile diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..8ea34be96 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,23 @@ +# syntax=docker/dockerfile:1 +FROM debian:bookworm-slim AS stainless + +RUN apt-get update && apt-get install -y \ + nodejs \ + npm \ + yarnpkg \ + && apt-get clean autoclean + +# Ensure UTF-8 encoding +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 + +# Yarn +RUN ln -sf /usr/bin/yarnpkg /usr/bin/yarn + +WORKDIR /workspace + +COPY package.json yarn.lock /workspace/ + +RUN yarn install + +COPY . /workspace From 83d2d6cb029c72383fdece53d3850af5416447a1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 6 Dec 2025 03:37:50 +0000 Subject: [PATCH 08/18] chore(internal): codegen related update --- .devcontainer/Dockerfile | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .devcontainer/Dockerfile diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 8ea34be96..000000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -# syntax=docker/dockerfile:1 -FROM debian:bookworm-slim AS stainless - -RUN apt-get update && apt-get install -y \ - nodejs \ - npm \ - yarnpkg \ - && apt-get clean autoclean - -# Ensure UTF-8 encoding -ENV LANG=C.UTF-8 -ENV LC_ALL=C.UTF-8 - -# Yarn -RUN ln -sf /usr/bin/yarnpkg /usr/bin/yarn - -WORKDIR /workspace - -COPY package.json yarn.lock /workspace/ - -RUN yarn install - -COPY . /workspace From 8932b2d7b4bc2a5c50487db572286448d388e6ea Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 6 Dec 2025 03:38:32 +0000 Subject: [PATCH 09/18] fix(mcp): correct code tool API endpoint --- .devcontainer/Dockerfile | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .devcontainer/Dockerfile diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..8ea34be96 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,23 @@ +# syntax=docker/dockerfile:1 +FROM debian:bookworm-slim AS stainless + +RUN apt-get update && apt-get install -y \ + nodejs \ + npm \ + yarnpkg \ + && apt-get clean autoclean + +# Ensure UTF-8 encoding +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 + +# Yarn +RUN ln -sf /usr/bin/yarnpkg /usr/bin/yarn + +WORKDIR /workspace + +COPY package.json yarn.lock /workspace/ + +RUN yarn install + +COPY . /workspace From 09eb01ddeb7496eb3badd068dba2fb9c1198fddf Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 6 Dec 2025 03:39:05 +0000 Subject: [PATCH 10/18] chore(internal): codegen related update --- .devcontainer/Dockerfile | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .devcontainer/Dockerfile diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 8ea34be96..000000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -# syntax=docker/dockerfile:1 -FROM debian:bookworm-slim AS stainless - -RUN apt-get update && apt-get install -y \ - nodejs \ - npm \ - yarnpkg \ - && apt-get clean autoclean - -# Ensure UTF-8 encoding -ENV LANG=C.UTF-8 -ENV LC_ALL=C.UTF-8 - -# Yarn -RUN ln -sf /usr/bin/yarnpkg /usr/bin/yarn - -WORKDIR /workspace - -COPY package.json yarn.lock /workspace/ - -RUN yarn install - -COPY . /workspace From 3e8406bb8b29a4e2cf87e89c22be7a8e5ecebf39 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:06:07 +0000 Subject: [PATCH 11/18] feat(devbox): return user for createSshKey --- .stats.yml | 4 +- api.md | 2 - src/index.ts | 2 - src/resources/devboxes/devboxes.ts | 5 + src/resources/shared.ts | 117 ++++++++++-------- tests/api-resources/blueprints.test.ts | 2 - tests/api-resources/devboxes/devboxes.test.ts | 1 - 7 files changed, 75 insertions(+), 58 deletions(-) diff --git a/.stats.yml b/.stats.yml index 0ab1f506b..0cb6f9948 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 97 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-cb2d725f71e87810cd872eacd70e867ca10f94980fdf9c78bb2844c02ee47bf3.yml -openapi_spec_hash: 16ce3e9184fc2afdee66db18a83a96e8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-b92a4ee1d2c5382b0d77ec6a16e2e03b79bfd0a08cd75e28ee219350d5b6c5c6.yml +openapi_spec_hash: 20d89d072b105d18e5bb8f73adb75063 config_hash: 2363f563f42501d2b1587a4f64bdccaf diff --git a/api.md b/api.md index 54c2fa2f5..847aff9e1 100644 --- a/api.md +++ b/api.md @@ -3,12 +3,10 @@ Types: - AfterIdle -- AgentMountParameters - AgentSource - CodeMountParameters - LaunchParameters - Mount -- ObjectMountParameters - RunProfile # Benchmarks diff --git a/src/index.ts b/src/index.ts index 59536fcb2..d38768874 100644 --- a/src/index.ts +++ b/src/index.ts @@ -590,12 +590,10 @@ export declare namespace Runloop { }; export type AfterIdle = API.AfterIdle; - export type AgentMountParameters = API.AgentMountParameters; export type AgentSource = API.AgentSource; export type CodeMountParameters = API.CodeMountParameters; export type LaunchParameters = API.LaunchParameters; export type Mount = API.Mount; - export type ObjectMountParameters = API.ObjectMountParameters; export type RunProfile = API.RunProfile; } diff --git a/src/resources/devboxes/devboxes.ts b/src/resources/devboxes/devboxes.ts index 028d9a168..a7b2baf41 100644 --- a/src/resources/devboxes/devboxes.ts +++ b/src/resources/devboxes/devboxes.ts @@ -865,6 +865,11 @@ export interface DevboxCreateSSHKeyResponse { */ ssh_private_key: string; + /** + * The Linux user to use for SSH connections to this Devbox. + */ + ssh_user: string; + /** * The host url of the Devbox that can be used for SSH. */ diff --git a/src/resources/shared.ts b/src/resources/shared.ts index 8b95be6e5..98752f3e9 100644 --- a/src/resources/shared.ts +++ b/src/resources/shared.ts @@ -12,32 +12,6 @@ export interface AfterIdle { on_idle: 'shutdown' | 'suspend'; } -export interface AgentMountParameters { - /** - * The ID of the agent to mount. Either agent_id or name must be set. - */ - agent_id: string | null; - - /** - * The name of the agent to mount. Returns the most recent agent with a matching - * name if no agent id string provided. Either agent id or name must be set - */ - agent_name: string | null; - - type: 'agent_mount'; - - /** - * Path to mount the agent on the Devbox. Required for git and object agents. Use - * absolute path (e.g., /home/user/agent) - */ - agent_path?: string | null; - - /** - * Optional auth token for private repositories. Only used for git agents. - */ - auth_token?: string | null; -} - /** * Agent source configuration. */ @@ -167,8 +141,6 @@ export interface CodeMountParameters { */ repo_owner: string; - type: 'code_mount'; - /** * The authentication token necessary to pull repo. */ @@ -276,14 +248,76 @@ export namespace LaunchParameters { } } -export type Mount = - | ObjectMountParameters - | AgentMountParameters - | CodeMountParameters - | Mount.FileMountParameters; +export type Mount = Mount.ObjectMount | Mount.AgentMount | Mount.CodeMount | Mount.FileMount; export namespace Mount { - export interface FileMountParameters { + export interface ObjectMount { + /** + * The ID of the object to write. + */ + object_id: string; + + /** + * The path to write the object on the Devbox. Use absolute path of object (ie + * /home/user/object.txt, or directory if archive /home/user/archive_dir) + */ + object_path: string; + + type: 'object_mount'; + } + + export interface AgentMount { + /** + * The ID of the agent to mount. Either agent_id or name must be set. + */ + agent_id: string | null; + + /** + * The name of the agent to mount. Returns the most recent agent with a matching + * name if no agent id string provided. Either agent id or name must be set + */ + agent_name: string | null; + + type: 'agent_mount'; + + /** + * Path to mount the agent on the Devbox. Required for git and object agents. Use + * absolute path (e.g., /home/user/agent) + */ + agent_path?: string | null; + + /** + * Optional auth token for private repositories. Only used for git agents. + */ + auth_token?: string | null; + } + + export interface CodeMount { + /** + * The name of the repo to mount. By default, code will be mounted at + * /home/user/{repo_name}s. + */ + repo_name: string; + + /** + * The owner of the repo. + */ + repo_owner: string; + + type: 'code_mount'; + + /** + * The authentication token necessary to pull repo. + */ + token?: string | null; + + /** + * Installation command to install and setup repository. + */ + install_command?: string | null; + } + + export interface FileMount { /** * Content of the file to mount. */ @@ -298,21 +332,6 @@ export namespace Mount { } } -export interface ObjectMountParameters { - /** - * The ID of the object to write. - */ - object_id: string; - - /** - * The path to write the object on the Devbox. Use absolute path of object (ie - * /home/user/object.txt, or directory if archive /home/user/archive_dir) - */ - object_path: string; - - type: 'object_mount'; -} - export interface RunProfile { /** * Mapping of Environment Variable to Value. May be shown in devbox logging. diff --git a/tests/api-resources/blueprints.test.ts b/tests/api-resources/blueprints.test.ts index f781eb437..ad2bd35d0 100644 --- a/tests/api-resources/blueprints.test.ts +++ b/tests/api-resources/blueprints.test.ts @@ -31,7 +31,6 @@ describe('resource blueprints', () => { { repo_name: 'repo_name', repo_owner: 'repo_owner', - type: 'code_mount', token: 'token', install_command: 'install_command', }, @@ -261,7 +260,6 @@ describe('resource blueprints', () => { { repo_name: 'repo_name', repo_owner: 'repo_owner', - type: 'code_mount', token: 'token', install_command: 'install_command', }, diff --git a/tests/api-resources/devboxes/devboxes.test.ts b/tests/api-resources/devboxes/devboxes.test.ts index cba3193d9..30eb681b6 100644 --- a/tests/api-resources/devboxes/devboxes.test.ts +++ b/tests/api-resources/devboxes/devboxes.test.ts @@ -39,7 +39,6 @@ describe('resource devboxes', () => { { repo_name: 'repo_name', repo_owner: 'repo_owner', - type: 'code_mount', token: 'token', install_command: 'install_command', }, From e040bc2bd4411dd566fbe6e339055403eb52bfca Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 20:23:37 +0000 Subject: [PATCH 12/18] feat(devbox): default to x86 arch --- .stats.yml | 4 ++-- src/resources/shared.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 0cb6f9948..ca5e82df9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 97 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-b92a4ee1d2c5382b0d77ec6a16e2e03b79bfd0a08cd75e28ee219350d5b6c5c6.yml -openapi_spec_hash: 20d89d072b105d18e5bb8f73adb75063 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-f2df3524e4b99c38b634c334d098aa2c7d543d5ea0f49c4dd8f4d92723b81b94.yml +openapi_spec_hash: c377abec5716d1d6c5b01a527a5bfdfb config_hash: 2363f563f42501d2b1587a4f64bdccaf diff --git a/src/resources/shared.ts b/src/resources/shared.ts index 98752f3e9..b66aeb78b 100644 --- a/src/resources/shared.ts +++ b/src/resources/shared.ts @@ -165,7 +165,7 @@ export interface LaunchParameters { after_idle?: AfterIdle | null; /** - * The target architecture for the Devbox. If unset, defaults to arm64. + * The target architecture for the Devbox. If unset, defaults to x86_64. */ architecture?: 'x86_64' | 'arm64' | null; From 2610c5d9207fab989f335633e9b8034d6905afd0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 13 Dec 2025 00:58:04 +0000 Subject: [PATCH 13/18] feat(devbox): added stdin streaming endpoint --- .stats.yml | 4 +-- src/resources/agents.ts | 20 ++++++++++++++ src/resources/benchmarks/benchmarks.ts | 9 +++++-- src/resources/benchmarks/runs.ts | 5 ++++ src/resources/blueprints.ts | 10 +++++++ src/resources/scenarios/runs.ts | 15 +++++++++++ src/resources/scenarios/scenarios.ts | 5 ++++ src/resources/secrets.ts | 2 +- src/resources/shared.ts | 10 ------- tests/api-resources/agents.test.ts | 26 +++++++++---------- .../benchmarks/benchmarks.test.ts | 2 +- tests/api-resources/benchmarks/runs.test.ts | 2 +- tests/api-resources/blueprints.test.ts | 4 +-- tests/api-resources/scenarios/runs.test.ts | 9 ++++++- .../api-resources/scenarios/scenarios.test.ts | 8 +++++- 15 files changed, 96 insertions(+), 35 deletions(-) diff --git a/.stats.yml b/.stats.yml index ca5e82df9..4cce660dc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 97 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-f2df3524e4b99c38b634c334d098aa2c7d543d5ea0f49c4dd8f4d92723b81b94.yml -openapi_spec_hash: c377abec5716d1d6c5b01a527a5bfdfb +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-370a5d676ff0ef41f3ca6ed669d29b6e3e6b7f0a914ac3dbed6bd45e75533c74.yml +openapi_spec_hash: 7bb5ea1117d754b7985aff0da5bca3a7 config_hash: 2363f563f42501d2b1587a4f64bdccaf diff --git a/src/resources/agents.ts b/src/resources/agents.ts index 4c090332b..c895ff513 100644 --- a/src/resources/agents.ts +++ b/src/resources/agents.ts @@ -52,6 +52,11 @@ export interface AgentCreateParameters { */ name: string; + /** + * The version of the Agent. Must be a semver string (e.g., '2.0.65') or a SHA. + */ + version: string; + /** * The source configuration for the Agent. */ @@ -107,6 +112,11 @@ export interface AgentView { */ name: string; + /** + * The version of the Agent. A semver string (e.g., '2.0.65') or a SHA. + */ + version: string; + /** * The source configuration for the Agent. */ @@ -119,6 +129,11 @@ export interface AgentCreateParams { */ name: string; + /** + * The version of the Agent. Must be a semver string (e.g., '2.0.65') or a SHA. + */ + version: string; + /** * The source configuration for the Agent. */ @@ -140,6 +155,11 @@ export interface AgentListParams extends AgentsCursorIDPageParams { * Search by agent ID or name. */ search?: string; + + /** + * Filter by version. Use 'latest' to get the most recently created agent. + */ + version?: string; } Agents.AgentViewsAgentsCursorIDPage = AgentViewsAgentsCursorIDPage; diff --git a/src/resources/benchmarks/benchmarks.ts b/src/resources/benchmarks/benchmarks.ts index 266e538ad..e00c617e7 100644 --- a/src/resources/benchmarks/benchmarks.ts +++ b/src/resources/benchmarks/benchmarks.ts @@ -405,11 +405,16 @@ export interface BenchmarkUpdateParams { scenario_ids?: Array | null; } -export interface BenchmarkListParams extends BenchmarksCursorIDPageParams {} +export interface BenchmarkListParams extends BenchmarksCursorIDPageParams { + /** + * Filter by name + */ + name?: string; +} export interface BenchmarkDefinitionsParams { /** - * The limit of items to return. Default is 20. + * The limit of items to return. Default is 20. Max is 5000. */ limit?: number; diff --git a/src/resources/benchmarks/runs.ts b/src/resources/benchmarks/runs.ts index f732a09a3..18ce250d0 100644 --- a/src/resources/benchmarks/runs.ts +++ b/src/resources/benchmarks/runs.ts @@ -87,6 +87,11 @@ export interface RunListParams extends BenchmarkRunsCursorIDPageParams { * The Benchmark ID to filter by. */ benchmark_id?: string; + + /** + * Filter by name + */ + name?: string; } export interface RunListScenarioRunsParams extends BenchmarkRunsCursorIDPageParams { diff --git a/src/resources/blueprints.ts b/src/resources/blueprints.ts index 8d501c22c..7d46dd8fa 100644 --- a/src/resources/blueprints.ts +++ b/src/resources/blueprints.ts @@ -740,6 +740,11 @@ export interface BlueprintListParams extends BlueprintsCursorIDPageParams { * Filter by name */ name?: string; + + /** + * Filter by build status (queued, provisioning, building, failed, build_complete) + */ + status?: string; } export interface BlueprintCreateFromInspectionParams { @@ -788,6 +793,11 @@ export interface BlueprintListPublicParams extends BlueprintsCursorIDPageParams * Filter by name */ name?: string; + + /** + * Filter by build status (queued, provisioning, building, failed, build_complete) + */ + status?: string; } export interface BlueprintPreviewParams { diff --git a/src/resources/scenarios/runs.ts b/src/resources/scenarios/runs.ts index 0146e3781..61ac0e939 100644 --- a/src/resources/scenarios/runs.ts +++ b/src/resources/scenarios/runs.ts @@ -131,10 +131,25 @@ export class Runs extends APIResource { } export interface RunListParams extends BenchmarkRunsCursorIDPageParams { + /** + * Filter by benchmark run ID + */ + benchmark_run_id?: string; + + /** + * Filter by name + */ + name?: string; + /** * Filter runs associated to Scenario given ID */ scenario_id?: string; + + /** + * Filter by state + */ + state?: string; } export declare namespace Runs { diff --git a/src/resources/scenarios/scenarios.ts b/src/resources/scenarios/scenarios.ts index 8407de1c2..740066699 100644 --- a/src/resources/scenarios/scenarios.ts +++ b/src/resources/scenarios/scenarios.ts @@ -799,6 +799,11 @@ export interface ScenarioListParams extends ScenariosCursorIDPageParams { * Query for Scenarios with a given name. */ name?: string; + + /** + * Filter by validation type + */ + validation_type?: string; } export interface ScenarioListPublicParams extends ScenariosCursorIDPageParams { diff --git a/src/resources/secrets.ts b/src/resources/secrets.ts index 86f78f59e..305b61dd3 100644 --- a/src/resources/secrets.ts +++ b/src/resources/secrets.ts @@ -154,7 +154,7 @@ export interface SecretUpdateParams { export interface SecretListParams { /** - * The limit of items to return. Default is 20. + * The limit of items to return. Default is 20. Max is 5000. */ limit?: number; } diff --git a/src/resources/shared.ts b/src/resources/shared.ts index b66aeb78b..cfb476bc1 100644 --- a/src/resources/shared.ts +++ b/src/resources/shared.ts @@ -77,11 +77,6 @@ export namespace AgentSource { */ agent_setup?: Array | null; - /** - * NPM version constraint - */ - npm_version?: string | null; - /** * NPM registry URL */ @@ -117,11 +112,6 @@ export namespace AgentSource { */ agent_setup?: Array | null; - /** - * Pip version constraint - */ - pip_version?: string | null; - /** * Pip registry URL */ diff --git a/tests/api-resources/agents.test.ts b/tests/api-resources/agents.test.ts index 6513f221d..3922a1ef6 100644 --- a/tests/api-resources/agents.test.ts +++ b/tests/api-resources/agents.test.ts @@ -10,7 +10,7 @@ const client = new Runloop({ describe('resource agents', () => { test('create: only required params', async () => { - const responsePromise = client.agents.create({ name: 'name' }); + const responsePromise = client.agents.create({ name: 'name', version: 'version' }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -23,22 +23,13 @@ describe('resource agents', () => { test('create: required and optional params', async () => { const response = await client.agents.create({ name: 'name', + version: 'version', source: { type: 'type', git: { repository: 'repository', agent_setup: ['string'], ref: 'ref' }, - npm: { - package_name: 'package_name', - agent_setup: ['string'], - npm_version: 'npm_version', - registry_url: 'registry_url', - }, + npm: { package_name: 'package_name', agent_setup: ['string'], registry_url: 'registry_url' }, object: { object_id: 'object_id', agent_setup: ['string'] }, - pip: { - package_name: 'package_name', - agent_setup: ['string'], - pip_version: 'pip_version', - registry_url: 'registry_url', - }, + pip: { package_name: 'package_name', agent_setup: ['string'], registry_url: 'registry_url' }, }, }); }); @@ -83,7 +74,14 @@ describe('resource agents', () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( client.agents.list( - { is_public: true, limit: 0, name: 'name', search: 'search', starting_after: 'starting_after' }, + { + is_public: true, + limit: 0, + name: 'name', + search: 'search', + starting_after: 'starting_after', + version: 'version', + }, { path: '/_stainless_unknown_path' }, ), ).rejects.toThrow(Runloop.NotFoundError); diff --git a/tests/api-resources/benchmarks/benchmarks.test.ts b/tests/api-resources/benchmarks/benchmarks.test.ts index d1850e6ba..6e9c1a4ee 100644 --- a/tests/api-resources/benchmarks/benchmarks.test.ts +++ b/tests/api-resources/benchmarks/benchmarks.test.ts @@ -95,7 +95,7 @@ describe('resource benchmarks', () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( client.benchmarks.list( - { limit: 0, starting_after: 'starting_after' }, + { limit: 0, name: 'name', starting_after: 'starting_after' }, { path: '/_stainless_unknown_path' }, ), ).rejects.toThrow(Runloop.NotFoundError); diff --git a/tests/api-resources/benchmarks/runs.test.ts b/tests/api-resources/benchmarks/runs.test.ts index 1afd23582..b4dae3887 100644 --- a/tests/api-resources/benchmarks/runs.test.ts +++ b/tests/api-resources/benchmarks/runs.test.ts @@ -49,7 +49,7 @@ describe('resource runs', () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( client.benchmarks.runs.list( - { benchmark_id: 'benchmark_id', limit: 0, starting_after: 'starting_after' }, + { benchmark_id: 'benchmark_id', limit: 0, name: 'name', starting_after: 'starting_after' }, { path: '/_stainless_unknown_path' }, ), ).rejects.toThrow(Runloop.NotFoundError); diff --git a/tests/api-resources/blueprints.test.ts b/tests/api-resources/blueprints.test.ts index ad2bd35d0..2aad9a94e 100644 --- a/tests/api-resources/blueprints.test.ts +++ b/tests/api-resources/blueprints.test.ts @@ -131,7 +131,7 @@ describe('resource blueprints', () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( client.blueprints.list( - { limit: 0, name: 'name', starting_after: 'starting_after' }, + { limit: 0, name: 'name', starting_after: 'starting_after', status: 'status' }, { path: '/_stainless_unknown_path' }, ), ).rejects.toThrow(Runloop.NotFoundError); @@ -215,7 +215,7 @@ describe('resource blueprints', () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( client.blueprints.listPublic( - { limit: 0, name: 'name', starting_after: 'starting_after' }, + { limit: 0, name: 'name', starting_after: 'starting_after', status: 'status' }, { path: '/_stainless_unknown_path' }, ), ).rejects.toThrow(Runloop.NotFoundError); diff --git a/tests/api-resources/scenarios/runs.test.ts b/tests/api-resources/scenarios/runs.test.ts index e18aac9bc..0997ff36f 100644 --- a/tests/api-resources/scenarios/runs.test.ts +++ b/tests/api-resources/scenarios/runs.test.ts @@ -49,7 +49,14 @@ describe('resource runs', () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( client.scenarios.runs.list( - { limit: 0, scenario_id: 'scenario_id', starting_after: 'starting_after' }, + { + benchmark_run_id: 'benchmark_run_id', + limit: 0, + name: 'name', + scenario_id: 'scenario_id', + starting_after: 'starting_after', + state: 'state', + }, { path: '/_stainless_unknown_path' }, ), ).rejects.toThrow(Runloop.NotFoundError); diff --git a/tests/api-resources/scenarios/scenarios.test.ts b/tests/api-resources/scenarios/scenarios.test.ts index d3f185a50..72930d937 100644 --- a/tests/api-resources/scenarios/scenarios.test.ts +++ b/tests/api-resources/scenarios/scenarios.test.ts @@ -185,7 +185,13 @@ describe('resource scenarios', () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( client.scenarios.list( - { benchmark_id: 'benchmark_id', limit: 0, name: 'name', starting_after: 'starting_after' }, + { + benchmark_id: 'benchmark_id', + limit: 0, + name: 'name', + starting_after: 'starting_after', + validation_type: 'validation_type', + }, { path: '/_stainless_unknown_path' }, ), ).rejects.toThrow(Runloop.NotFoundError); From 18414051998e62ae9a15f5bf10a90a53e9d46ec3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 13 Dec 2025 00:58:25 +0000 Subject: [PATCH 14/18] release: 1.1.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- src/version.ts | 2 +- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 37fcefaab..5fdd88304 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.0.0" + ".": "1.1.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 92b7eb321..78925691b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Changelog +## 1.1.0 (2025-12-13) + +Full Changelog: [v1.0.0...v1.1.0](https://github.com/runloopai/api-client-ts/compare/v1.0.0...v1.1.0) + +### Features + +* **blueprints:** Add build context to the OpenAPI spec ([#6494](https://github.com/runloopai/api-client-ts/issues/6494)) ([e295c86](https://github.com/runloopai/api-client-ts/commit/e295c861c54be8320cf3a049023722159e1566d9)) +* **devbox:** added stdin streaming endpoint ([2610c5d](https://github.com/runloopai/api-client-ts/commit/2610c5d9207fab989f335633e9b8034d6905afd0)) +* **devbox:** added suspend async and promoted suspend to automatically await the devbox running ([#670](https://github.com/runloopai/api-client-ts/issues/670)) ([e814e00](https://github.com/runloopai/api-client-ts/commit/e814e000496e923023e525b038efcb39e34b4cdd)) +* **devbox:** default to x86 arch ([e040bc2](https://github.com/runloopai/api-client-ts/commit/e040bc2bd4411dd566fbe6e339055403eb52bfca)) +* **devbox:** return user for createSshKey ([3e8406b](https://github.com/runloopai/api-client-ts/commit/3e8406bb8b29a4e2cf87e89c22be7a8e5ecebf39)) +* **ignore matcher:** support for .dockerignore for build context ([#673](https://github.com/runloopai/api-client-ts/issues/673)) ([a843d54](https://github.com/runloopai/api-client-ts/commit/a843d54b93a69ea4cf8bf22475fd0cf6b04ba98b)) + + +### Bug Fixes + +* **api:** don't ignore devbox keep_alive, suspend and resume in api ([a5143ef](https://github.com/runloopai/api-client-ts/commit/a5143eff331d3404e1649ec57f01e67ea18b6613)) +* **devbox:** launch parameter typo ([2ef3ede](https://github.com/runloopai/api-client-ts/commit/2ef3ede2bbc71b79f111ab9c0326573f44dfab4c)) +* **mcp:** correct code tool API endpoint ([8932b2d](https://github.com/runloopai/api-client-ts/commit/8932b2d7b4bc2a5c50487db572286448d388e6ea)) +* **mcp:** return correct lines on typescript errors ([4ebe08c](https://github.com/runloopai/api-client-ts/commit/4ebe08c525a71cecb26e78a3a52602efb40fb968)) +* **scorer:** fixed RL_TEST_CONTEXT to RL_SCORER_CONTEXT ([e810958](https://github.com/runloopai/api-client-ts/commit/e810958cdab4b067d91f7a2e3cec481b44a5a7d9)) + + +### Chores + +* hide build context APIs ([8ea7bba](https://github.com/runloopai/api-client-ts/commit/8ea7bba5cd9cd9d047091386025965edeb316ab4)) +* **internal:** codegen related update ([09eb01d](https://github.com/runloopai/api-client-ts/commit/09eb01ddeb7496eb3badd068dba2fb9c1198fddf)) +* **internal:** codegen related update ([83d2d6c](https://github.com/runloopai/api-client-ts/commit/83d2d6cb029c72383fdece53d3850af5416447a1)) +* **mounts:** Update documentation for deprecated fields to direct the user to the replacement API ([1e68146](https://github.com/runloopai/api-client-ts/commit/1e6814677b20e12513da0faaa3cd4414a4d3df6e)) + ## 1.0.0 (2025-12-01) Full Changelog: [v0.69.0...v1.0.0](https://github.com/runloopai/api-client-ts/compare/v0.69.0...v1.0.0) diff --git a/package-lock.json b/package-lock.json index 3afd4f0c5..4243f4ed0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@runloop/api-client", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@runloop/api-client", - "version": "1.0.0", + "version": "1.1.0", "license": "MIT", "dependencies": { "@types/node": "^18.11.18", diff --git a/package.json b/package.json index 2be2d1c00..8e1a406ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@runloop/api-client", - "version": "1.0.0", + "version": "1.1.0", "description": "The official TypeScript library for the Runloop API", "author": "Runloop ", "types": "dist/sdk.d.ts", diff --git a/src/version.ts b/src/version.ts index bea2896f0..c80f9757e 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '1.0.0'; // x-release-please-version +export const VERSION = '1.1.0'; // x-release-please-version From d09e73f69b635177ca0e2afb8162a0ae12169bea Mon Sep 17 00:00:00 2001 From: Albert Li Date: Fri, 12 Dec 2025 17:13:46 -0800 Subject: [PATCH 15/18] Fix types --- tests/objects/agent.test.ts | 20 +++++++++++++++---- tests/sdk/agent-ops.test.ts | 17 ++++++++++++++++ .../smoketests/object-oriented/agent.test.ts | 16 ++++++++++++--- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/tests/objects/agent.test.ts b/tests/objects/agent.test.ts index a8600c82b..fa8af04f3 100644 --- a/tests/objects/agent.test.ts +++ b/tests/objects/agent.test.ts @@ -23,6 +23,7 @@ describe('Agent (SDK)', () => { id: 'agent-123', create_time_ms: Date.now(), name: 'test-agent', + version: '1.0.0', is_public: false, source: { type: 'npm', @@ -39,6 +40,7 @@ describe('Agent (SDK)', () => { const agent = await Agent.create(mockClient, { name: 'test-agent', + version: '1.0.0', source: { type: 'npm', npm: { @@ -50,6 +52,7 @@ describe('Agent (SDK)', () => { expect(mockClient.agents.create).toHaveBeenCalledWith( { name: 'test-agent', + version: '1.0.0', source: { type: 'npm', npm: { @@ -78,6 +81,7 @@ describe('Agent (SDK)', () => { const agent = await Agent.create(mockClient, { name: 'git-agent', + version: '1.0.0', source: { type: 'git', git: { @@ -90,6 +94,7 @@ describe('Agent (SDK)', () => { expect(mockClient.agents.create).toHaveBeenCalledWith( { name: 'git-agent', + version: '1.0.0', source: { type: 'git', git: { @@ -117,6 +122,7 @@ describe('Agent (SDK)', () => { await Agent.create(mockClient, { name: 'pip-agent', + version: '1.0.0', source: { type: 'pip', pip: { @@ -128,6 +134,7 @@ describe('Agent (SDK)', () => { expect(mockClient.agents.create).toHaveBeenCalledWith( { name: 'pip-agent', + version: '1.0.0', source: { type: 'pip', pip: { @@ -152,9 +159,9 @@ describe('Agent (SDK)', () => { describe('list', () => { it('should list agents and return Agent instances', async () => { const mockAgents = [ - { id: 'agent-001', name: 'first-agent', create_time_ms: Date.now(), is_public: false }, - { id: 'agent-002', name: 'second-agent', create_time_ms: Date.now(), is_public: true }, - { id: 'agent-003', name: 'third-agent', create_time_ms: Date.now(), is_public: false }, + { id: 'agent-001', name: 'first-agent', version: '1.0.0', create_time_ms: Date.now(), is_public: false }, + { id: 'agent-002', name: 'second-agent', version: '1.0.0', create_time_ms: Date.now(), is_public: true }, + { id: 'agent-003', name: 'third-agent', version: '1.0.0', create_time_ms: Date.now(), is_public: false }, ]; // Mock async iterator @@ -181,7 +188,7 @@ describe('Agent (SDK)', () => { it('should pass filter parameters to list', async () => { const asyncIterator = { async *[Symbol.asyncIterator]() { - yield { id: 'agent-001', name: 'filtered-agent', create_time_ms: Date.now(), is_public: false }; + yield { id: 'agent-001', name: 'filtered-agent', version: '1.0.0', create_time_ms: Date.now(), is_public: false }; }, }; @@ -214,6 +221,7 @@ describe('Agent (SDK)', () => { mockClient.agents.create.mockResolvedValue(mockAgentData); agent = await Agent.create(mockClient, { name: 'test-agent', + version: '1.0.0', source: { type: 'npm', npm: { @@ -259,6 +267,7 @@ describe('Agent (SDK)', () => { await expect( Agent.create(mockClient, { name: 'failing-agent', + version: '1.0.0', source: { type: 'npm', npm: { @@ -293,12 +302,14 @@ describe('Agent (SDK)', () => { id: 'agent-minimal', create_time_ms: Date.now(), name: 'minimal', + version: '1.0.0', is_public: false, }; mockClient.agents.create.mockResolvedValue(minimalData); const agent = await Agent.create(mockClient, { name: 'minimal', + version: '1.0.0', source: { type: 'npm', npm: { @@ -325,6 +336,7 @@ describe('Agent (SDK)', () => { const agent = await Agent.create(mockClient, { name: 'object-agent', + version: '1.0.0', source: { type: 'object', object: { diff --git a/tests/sdk/agent-ops.test.ts b/tests/sdk/agent-ops.test.ts index c8bbc1653..d082158b7 100644 --- a/tests/sdk/agent-ops.test.ts +++ b/tests/sdk/agent-ops.test.ts @@ -28,6 +28,7 @@ describe('AgentOps', () => { id: 'agent-123', create_time_ms: Date.now(), name: 'test-agent', + version: '1.0.0', is_public: false, source: { type: 'npm', @@ -46,6 +47,7 @@ describe('AgentOps', () => { it('should create an agent from npm package', async () => { await agentOps.createFromNpm({ name: 'test-agent', + version: '1.0.0', package_name: '@runloop/example-agent', }); @@ -53,6 +55,7 @@ describe('AgentOps', () => { mockClient, { name: 'test-agent', + version: '1.0.0', source: { type: 'npm', npm: { @@ -67,6 +70,7 @@ describe('AgentOps', () => { it('should create an agent with all npm options', async () => { await agentOps.createFromNpm({ name: 'test-agent', + version: '1.0.0', package_name: '@runloop/example-agent', npm_version: '1.2.3', registry_url: 'https://registry.example.com', @@ -77,6 +81,7 @@ describe('AgentOps', () => { mockClient, { name: 'test-agent', + version: '1.0.0', source: { type: 'npm', npm: { @@ -96,6 +101,7 @@ describe('AgentOps', () => { it('should create an agent from pip package', async () => { await agentOps.createFromPip({ name: 'test-agent', + version: '1.0.0', package_name: 'runloop-example-agent', }); @@ -103,6 +109,7 @@ describe('AgentOps', () => { mockClient, { name: 'test-agent', + version: '1.0.0', source: { type: 'pip', pip: { @@ -117,6 +124,7 @@ describe('AgentOps', () => { it('should create an agent with all pip options', async () => { await agentOps.createFromPip({ name: 'test-agent', + version: '1.0.0', package_name: 'runloop-example-agent', pip_version: '1.2.3', registry_url: 'https://pypi.example.com', @@ -127,6 +135,7 @@ describe('AgentOps', () => { mockClient, { name: 'test-agent', + version: '1.0.0', source: { type: 'pip', pip: { @@ -146,6 +155,7 @@ describe('AgentOps', () => { it('should create an agent from git repository', async () => { await agentOps.createFromGit({ name: 'test-agent', + version: '1.0.0', repository: 'https://github.com/example/agent-repo', }); @@ -153,6 +163,7 @@ describe('AgentOps', () => { mockClient, { name: 'test-agent', + version: '1.0.0', source: { type: 'git', git: { @@ -167,6 +178,7 @@ describe('AgentOps', () => { it('should create an agent with all git options', async () => { await agentOps.createFromGit({ name: 'test-agent', + version: '1.0.0', repository: 'https://github.com/example/agent-repo', ref: 'develop', agent_setup: ['npm install', 'npm run build'], @@ -176,6 +188,7 @@ describe('AgentOps', () => { mockClient, { name: 'test-agent', + version: '1.0.0', source: { type: 'git', git: { @@ -194,6 +207,7 @@ describe('AgentOps', () => { it('should create an agent from object', async () => { await agentOps.createFromObject({ name: 'test-agent', + version: '1.0.0', object_id: 'obj_123', }); @@ -201,6 +215,7 @@ describe('AgentOps', () => { mockClient, { name: 'test-agent', + version: '1.0.0', source: { type: 'object', object: { @@ -215,6 +230,7 @@ describe('AgentOps', () => { it('should create an agent with agent_setup', async () => { await agentOps.createFromObject({ name: 'test-agent', + version: '1.0.0', object_id: 'obj_123', agent_setup: ['chmod +x setup.sh', './setup.sh'], }); @@ -223,6 +239,7 @@ describe('AgentOps', () => { mockClient, { name: 'test-agent', + version: '1.0.0', source: { type: 'object', object: { diff --git a/tests/smoketests/object-oriented/agent.test.ts b/tests/smoketests/object-oriented/agent.test.ts index 6ff8bd107..ed880a828 100644 --- a/tests/smoketests/object-oriented/agent.test.ts +++ b/tests/smoketests/object-oriented/agent.test.ts @@ -11,6 +11,7 @@ describe('smoketest: object-oriented agent', () => { const name = uniqueName('sdk-agent-test-basic'); const agent = await runloop.agent.create({ name: name, + version: '1.0.0', source: { type: 'npm', npm: { @@ -42,6 +43,7 @@ describe('smoketest: object-oriented agent', () => { const name = uniqueName('sdk-agent-test-info'); const agent = await runloop.agent.create({ name: name, + version: '1.0.0', source: { type: 'npm', npm: { @@ -84,6 +86,7 @@ describe('smoketest: object-oriented agent', () => { // Create an agent const created = await runloop.agent.create({ name: uniqueName('sdk-agent-test-retrieve'), + version: '1.0.0', source: { type: 'npm', npm: { @@ -118,9 +121,9 @@ describe('smoketest: object-oriented agent', () => { }; // Create multiple agents - const agent1 = await runloop.agent.create({ name: uniqueName('sdk-agent-test-list-1'), source: sourceConfig }); - const agent2 = await runloop.agent.create({ name: uniqueName('sdk-agent-test-list-2'), source: sourceConfig }); - const agent3 = await runloop.agent.create({ name: uniqueName('sdk-agent-test-list-3'), source: sourceConfig }); + const agent1 = await runloop.agent.create({ name: uniqueName('sdk-agent-test-list-1'), version: '1.0.0', source: sourceConfig }); + const agent2 = await runloop.agent.create({ name: uniqueName('sdk-agent-test-list-2'), version: '1.0.0', source: sourceConfig }); + const agent3 = await runloop.agent.create({ name: uniqueName('sdk-agent-test-list-3'), version: '1.0.0', source: sourceConfig }); try { // List agents @@ -151,6 +154,7 @@ describe('smoketest: object-oriented agent', () => { const agent = await runloop.agent.create({ name: name, + version: '1.0.0', source: { type: 'npm', npm: { @@ -179,6 +183,7 @@ describe('smoketest: object-oriented agent', () => { const agent = await runloop.agent.create({ name: name, + version: '1.0.0', source: { type: 'git', git: { @@ -209,6 +214,7 @@ describe('smoketest: object-oriented agent', () => { const name = uniqueName('sdk-agent-from-npm'); const agent = await runloop.agent.createFromNpm({ name: name, + version: '1.0.0', package_name: '@runloop/hello-world-agent', }); @@ -231,6 +237,7 @@ describe('smoketest: object-oriented agent', () => { const name = uniqueName('sdk-agent-from-pip'); const agent = await runloop.agent.createFromPip({ name: name, + version: '1.0.0', package_name: 'runloop-example-agent', }); @@ -253,6 +260,7 @@ describe('smoketest: object-oriented agent', () => { const name = uniqueName('sdk-agent-from-git'); const agent = await runloop.agent.createFromGit({ name: name, + version: '1.0.0', repository: 'https://github.com/runloop/example-agent', ref: 'main', }); @@ -291,6 +299,7 @@ describe('smoketest: object-oriented agent', () => { const agentName = uniqueName('sdk-agent-from-object'); agent = await runloop.agent.createFromObject({ name: agentName, + version: '1.0.0', object_id: storageObject.id, }); @@ -332,6 +341,7 @@ describe('smoketest: object-oriented agent', () => { // Create agent from storage object agent = await runloop.agent.createFromObject({ name: uniqueName('sdk-agent-for-mount'), + version: '1.0.0', object_id: storageObject.id, }); From 22a66677d722ea8ee9591c3b750451c00c2075f7 Mon Sep 17 00:00:00 2001 From: Albert Li Date: Fri, 12 Dec 2025 17:24:21 -0800 Subject: [PATCH 16/18] Fix smoke tests --- src/sdk/storage-object.ts | 11 +-- yarn.lock | 198 ++++++++++++++++++++------------------ 2 files changed, 106 insertions(+), 103 deletions(-) diff --git a/src/sdk/storage-object.ts b/src/sdk/storage-object.ts index c8ab0181d..bf0d6281c 100644 --- a/src/sdk/storage-object.ts +++ b/src/sdk/storage-object.ts @@ -6,6 +6,7 @@ import type { ObjectDownloadURLView, ObjectListParams, } from '../resources/objects'; +import { fetch } from '../_shims/index'; import * as fs from 'node:fs/promises'; import * as fsSync from 'node:fs'; import * as os from 'node:os'; @@ -509,17 +510,13 @@ export class StorageObject { throw new Error('No upload URL available. Object may already be completed or deleted.'); } - // Upload the file from disk + // Upload the file from disk (read into buffer like uploadFromFile does) try { - const fileStream = fsSync.createReadStream(tmpFilePath); - const stats = await fs.stat(tmpFilePath); + const fileBuffer = await fs.readFile(tmpFilePath); const response = await fetch(uploadUrl, { method: 'PUT', - body: fileStream as any, - headers: { - 'Content-Length': stats.size.toString(), - }, + body: fileBuffer, }); if (!response.ok) { diff --git a/yarn.lock b/yarn.lock index 8cbe1beba..b7d81ffc8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16,7 +16,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz" integrity sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA== -"@babel/core@^7.0.0", "@babel/core@^7.0.0 || ^8.0.0-0", "@babel/core@^7.0.0-0", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.8.0", "@babel/core@>=7.0.0-beta.0 <8": +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": version "7.28.5" resolved "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz" integrity sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw== @@ -500,13 +500,6 @@ strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" -"@jest/schemas@^29.6.3": - version "29.6.3" - resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz" - integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== - dependencies: - "@sinclair/typebox" "^0.27.8" - "@jest/schemas@30.0.5": version "30.0.5" resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz" @@ -514,6 +507,13 @@ dependencies: "@sinclair/typebox" "^0.34.0" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jest/source-map@^29.6.3": version "29.6.3" resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz" @@ -543,7 +543,7 @@ jest-haste-map "^29.7.0" slash "^3.0.0" -"@jest/transform@^29.0.0 || ^30.0.0", "@jest/transform@^29.7.0": +"@jest/transform@^29.7.0": version "29.7.0" resolved "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz" integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== @@ -564,7 +564,7 @@ slash "^3.0.0" write-file-atomic "^4.0.2" -"@jest/types@^29.0.0 || ^30.0.0", "@jest/types@30.2.0": +"@jest/types@30.2.0": version "30.2.0" resolved "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz" integrity sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg== @@ -615,14 +615,6 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": - version "0.3.31" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz" - integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" @@ -631,6 +623,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@material/material-color-utilities@^0.3.0": version "0.3.0" resolved "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.3.0.tgz" @@ -644,7 +644,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -684,7 +684,7 @@ dependencies: "@shikijs/types" "3.15.0" -"@shikijs/types@^3.15.0", "@shikijs/types@3.15.0": +"@shikijs/types@3.15.0", "@shikijs/types@^3.15.0": version "3.15.0" resolved "https://registry.npmjs.org/@shikijs/types/-/types-3.15.0.tgz" integrity sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw== @@ -734,7 +734,52 @@ resolved "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.2.tgz" integrity sha512-Ghyz4RJv4zyXzrUC1B2MLQBbppIB5c4jMZJybX2ebdEQAvryEKp3gq1kBksCNsatKGmEgXul88SETU19sMWcrw== -"@swc/core@*", "@swc/core@^1.3.102", "@swc/core@>=1.2.50": +"@swc/core-darwin-x64@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.2.tgz#f0c229fd21b0ef658cf50adb8b9a5f8c1919d2ec" + integrity sha512-7n/PGJOcL2QoptzL42L5xFFfXY5rFxLHnuz1foU+4ruUTG8x2IebGhtwVTpaDN8ShEv2UZObBlT1rrXTba15Zw== + +"@swc/core-linux-arm-gnueabihf@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.2.tgz#ab6d8535ab0294fb743044665c1c17330ccc2181" + integrity sha512-ZUQVCfRJ9wimuxkStRSlLwqX4TEDmv6/J+E6FicGkQ6ssLMWoKDy0cAo93HiWt/TWEee5vFhFaSQYzCuBEGO6A== + +"@swc/core-linux-arm64-gnu@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.2.tgz#c5db7bbba6af3bd93fd79b74850cf2fe04f1c074" + integrity sha512-GZh3pYBmfnpQ+JIg+TqLuz+pM+Mjsk5VOzi8nwKn/m+GvQBsxD5ectRtxuWUxMGNG8h0lMy4SnHRqdK3/iJl7A== + +"@swc/core-linux-arm64-musl@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.2.tgz#31cd61e6c83595248fda70a70e596f0a7f6cdf97" + integrity sha512-5av6VYZZeneiYIodwzGMlnyVakpuYZryGzFIbgu1XP8wVylZxduEzup4eP8atiMDFmIm+s4wn8GySJmYqeJC0A== + +"@swc/core-linux-x64-gnu@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.2.tgz#c857e95635fa2beaf6d3ecb9b1bd97ade5cda43a" + integrity sha512-1nO/UfdCLuT/uE/7oB3EZgTeZDCIa6nL72cFEpdegnqpJVNDI6Qb8U4g/4lfVPkmHq2lvxQ0L+n+JdgaZLhrRA== + +"@swc/core-linux-x64-musl@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.2.tgz#afa817865271f24502a0805cfab70ba553ade146" + integrity sha512-Ksfrb0Tx310kr+TLiUOvB/I80lyZ3lSOp6cM18zmNRT/92NB4mW8oX2Jo7K4eVEI2JWyaQUAFubDSha2Q+439A== + +"@swc/core-win32-arm64-msvc@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.2.tgz#5696added38837b6b3f442d63989ef4fcaa2d3de" + integrity sha512-IzUb5RlMUY0r1A9IuJrQ7Tbts1wWb73/zXVXT8VhewbHGoNlBKE0qUhKMED6Tv4wDF+pmbtUJmKXDthytAvLmg== + +"@swc/core-win32-ia32-msvc@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.2.tgz#3e42427998d49b54c55d49752b410309c7d02357" + integrity sha512-kCATEzuY2LP9AlbU2uScjcVhgnCAkRdu62vbce17Ro5kxEHxYWcugkveyBRS3AqZGtwAKYbMAuNloer9LS/hpw== + +"@swc/core-win32-x64-msvc@1.15.2": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.2.tgz#fa6505e20a3753b6884b35353d1614a03c54df7f" + integrity sha512-iJaHeYCF4jTn7OEKSa3KRiuVFIVYts8jYjNmCdyz1u5g8HRyTDISD76r8+ljEOgm36oviRQvcXaw6LFp1m0yyA== + +"@swc/core@^1.3.102": version "1.15.2" resolved "https://registry.npmjs.org/@swc/core/-/core-1.15.2.tgz" integrity sha512-OQm+yJdXxvSjqGeaWhP6Ia264ogifwAO7Q12uTDVYj/Ks4jBTI4JknlcjDRAXtRhqbWsfbZyK/5RtuIPyptk3w== @@ -900,7 +945,7 @@ "@types/tar@^6.1.13": version "6.1.13" - resolved "https://registry.npmjs.org/@types/tar/-/tar-6.1.13.tgz" + resolved "https://registry.yarnpkg.com/@types/tar/-/tar-6.1.13.tgz#9b5801c02175344101b4b91086ab2bbc8e93a9b6" integrity sha512-IznnlmU5f4WcGTh2ltRu/Ijpmk8wiWXfF0VA4s+HPjHZgvFggk1YaIkbo5krX/zUCzWF8N/l4+W/LNxnvAJ8nw== dependencies: "@types/node" "*" @@ -923,7 +968,7 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^6.7.0", "@typescript-eslint/eslint-plugin@6 - 7": +"@typescript-eslint/eslint-plugin@^6.7.0": version "6.21.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz" integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA== @@ -940,7 +985,7 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/parser@^6.0.0 || ^6.0.0-alpha", "@typescript-eslint/parser@^6.7.0": +"@typescript-eslint/parser@^6.7.0": version "6.21.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz" integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== @@ -1033,7 +1078,7 @@ acorn-walk@^8.1.1: dependencies: acorn "^8.11.0" -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: +acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: version "8.15.0" resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== @@ -1127,7 +1172,7 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -"babel-jest@^29.0.0 || ^30.0.0", babel-jest@^29.7.0: +babel-jest@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz" integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== @@ -1222,7 +1267,7 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -browserslist@^4.24.0, "browserslist@>= 4.21.0": +browserslist@^4.24.0: version "4.28.0" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz" integrity sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ== @@ -1580,7 +1625,7 @@ eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0", eslint@^8.49.0, eslint@>=8.0.0, eslint@8: +eslint@^8.49.0: version "8.57.1" resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz" integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== @@ -1719,7 +1764,7 @@ fast-glob@^3.2.12, fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.8" -fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0, fast-json-stable-stringify@2.x: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -1780,15 +1825,7 @@ find-cache-dir@^3.3.1: make-dir "^3.0.2" pkg-dir "^4.1.0" -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^4.1.0: +find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -2087,7 +2124,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@^2.0.3, inherits@2: +inherits@2, inherits@^2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2390,16 +2427,16 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz" integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== -jest-regex-util@^29.6.3: - version "29.6.3" - resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz" - integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== - jest-regex-util@30.0.1: version "30.0.1" resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz" integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + jest-resolve-dependencies@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz" @@ -2408,7 +2445,7 @@ jest-resolve-dependencies@^29.7.0: jest-regex-util "^29.6.3" jest-snapshot "^29.7.0" -jest-resolve@*, jest-resolve@^29.7.0: +jest-resolve@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz" integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== @@ -2504,7 +2541,7 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" -"jest-util@^29.0.0 || ^30.0.0", jest-util@^29.7.0: +jest-util@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz" integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== @@ -2552,7 +2589,7 @@ jest-worker@^29.7.0: merge-stream "^2.0.0" supports-color "^8.0.0" -"jest@^29.0.0 || ^30.0.0", jest@^29.4.0: +jest@^29.4.0: version "29.7.0" resolved "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz" integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== @@ -2782,28 +2819,14 @@ mimic-fn@^2.1.0: resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@^3.0.4: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.0.5: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== +minimatch@9.0.3: + version "9.0.3" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== dependencies: - brace-expansion "^1.1.7" + brace-expansion "^2.0.1" -minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -2817,13 +2840,6 @@ minimatch@^9.0.5: dependencies: brace-expansion "^2.0.1" -minimatch@9.0.3: - version "9.0.3" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz" - integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== - dependencies: - brace-expansion "^2.0.1" - minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" @@ -3044,7 +3060,7 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^3.0.0, prettier@>=3.0.0: +prettier@^3.0.0: version "3.6.2" resolved "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz" integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ== @@ -3165,17 +3181,7 @@ safe-buffer@~5.2.0: resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -semver@^6.0.0: - version "6.3.1" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^6.3.0: - version "6.3.1" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^6.3.1: +semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -3237,13 +3243,6 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - string-length@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" @@ -3268,6 +3267,13 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -3402,7 +3408,7 @@ ts-jest@^29.1.0: type-fest "^4.41.0" yargs-parser "^21.1.1" -ts-node@^10.5.0, ts-node@>=9.0.0: +ts-node@^10.5.0: version "10.9.2" resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== @@ -3527,7 +3533,7 @@ typedoc-plugin-pages@^1.1.0: compare-versions "^3.6.0" typedoc-default-themes "^0.10.1" -"typedoc@^0.25.13 || ^0.26.x || ^0.27.x || ^0.28.x", typedoc@^0.28.14, typedoc@^0.28.9, "typedoc@>=0.26.0 <0.29.0", typedoc@~0.28.0, "typedoc@0.26.x || 0.27.x || 0.28.x", typedoc@0.28.x: +typedoc@^0.28.14: version "0.28.14" resolved "https://registry.npmjs.org/typedoc/-/typedoc-0.28.14.tgz" integrity sha512-ftJYPvpVfQvFzpkoSfHLkJybdA/geDJ8BGQt/ZnkkhnBYoYW6lBgPQXu6vqLxO4X75dA55hX8Af847H5KXlEFA== @@ -3538,7 +3544,7 @@ typedoc-plugin-pages@^1.1.0: minimatch "^9.0.5" yaml "^2.8.1" -typescript@^5.9.0, typescript@>=2.7, typescript@>=4.2.0, "typescript@>=4.3 <6", typescript@>=4.3.0, "typescript@5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x": +typescript@^5.9.0: version "5.9.3" resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz" integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== From 7870b6a4d133833fbb331df4bfba568ed29cb1c3 Mon Sep 17 00:00:00 2001 From: Albert Li Date: Fri, 12 Dec 2025 17:28:20 -0800 Subject: [PATCH 17/18] Fix types --- src/sdk/storage-object.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sdk/storage-object.ts b/src/sdk/storage-object.ts index bf0d6281c..8809af4c3 100644 --- a/src/sdk/storage-object.ts +++ b/src/sdk/storage-object.ts @@ -321,7 +321,7 @@ export class StorageObject { try { const response = await fetch(uploadUrl, { method: 'PUT', - body: new Blob([text]), + body: Buffer.from(text, 'utf-8'), }); if (!response.ok) { @@ -396,7 +396,7 @@ export class StorageObject { try { const response = await fetch(uploadUrl, { method: 'PUT', - body: new Blob([buffer]), + body: buffer, }); if (!response.ok) { From 82f7a45392580aef1fb1f9f37f8b1cefe6153368 Mon Sep 17 00:00:00 2001 From: Albert Li Date: Fri, 12 Dec 2025 17:32:21 -0800 Subject: [PATCH 18/18] Fix tests --- tests/objects/storage-object.test.ts | 121 ++++++++++++++------------- 1 file changed, 63 insertions(+), 58 deletions(-) diff --git a/tests/objects/storage-object.test.ts b/tests/objects/storage-object.test.ts index 092bffc65..e3735a37d 100644 --- a/tests/objects/storage-object.test.ts +++ b/tests/objects/storage-object.test.ts @@ -4,8 +4,14 @@ import type { ObjectView, ObjectDownloadURLView } from '../../src/resources/obje // Mock the Runloop client jest.mock('../../src/index'); -// Mock fetch globally -(global as any).fetch = jest.fn(); +// Mock the shims module to use our mock fetch +jest.mock('../../src/_shims/index', () => ({ + fetch: jest.fn(), +})); + +// Get reference to the mocked fetch from shims +import { fetch as shimsFetch } from '../../src/_shims/index'; +const mockedShimsFetch = shimsFetch as unknown as jest.Mock; // Mock fs and path modules jest.mock('node:fs/promises', () => ({ @@ -110,7 +116,7 @@ describe('StorageObject (New API)', () => { }; // Reset fetch mock - ((global as any).fetch as jest.Mock).mockReset(); + mockedShimsFetch.mockReset(); }); describe('create', () => { @@ -245,17 +251,17 @@ describe('StorageObject (New API)', () => { // Mock getInfo to return object data with upload_url mockClient.objects.retrieve.mockResolvedValue(mockObjectData); - const mockFetchResponse = { + const mockedShimsFetchResponse = { ok: true, status: 200, statusText: 'OK', }; - ((global as any).fetch as jest.Mock).mockResolvedValue(mockFetchResponse); + mockedShimsFetch.mockResolvedValue(mockedShimsFetchResponse); await storageObject.uploadContent('Hello, World!'); - expect((global as any).fetch).toHaveBeenCalledWith(mockObjectData.upload_url, { + expect(mockedShimsFetch).toHaveBeenCalledWith(mockObjectData.upload_url, { method: 'PUT', body: Buffer.from('Hello, World!', 'utf-8'), }); @@ -265,18 +271,18 @@ describe('StorageObject (New API)', () => { // Mock getInfo to return object data with upload_url mockClient.objects.retrieve.mockResolvedValue(mockObjectData); - const mockFetchResponse = { + const mockedShimsFetchResponse = { ok: true, status: 200, statusText: 'OK', }; - ((global as any).fetch as jest.Mock).mockResolvedValue(mockFetchResponse); + mockedShimsFetch.mockResolvedValue(mockedShimsFetchResponse); const buffer = Buffer.from([0x89, 0x50, 0x4e, 0x47]); await storageObject.uploadContent(buffer); - expect((global as any).fetch).toHaveBeenCalledWith(mockObjectData.upload_url, { + expect(mockedShimsFetch).toHaveBeenCalledWith(mockObjectData.upload_url, { method: 'PUT', body: buffer, }); @@ -294,14 +300,14 @@ describe('StorageObject (New API)', () => { // Mock getInfo to return object data with upload_url mockClient.objects.retrieve.mockResolvedValue(mockObjectData); - const mockFetchResponse = { + const mockedShimsFetchResponse = { ok: false, status: 403, statusText: 'Forbidden', text: jest.fn().mockResolvedValue('Forbidden'), }; - ((global as any).fetch as jest.Mock).mockResolvedValue(mockFetchResponse); + mockedShimsFetch.mockResolvedValue(mockedShimsFetchResponse); await expect(storageObject.uploadContent('test')).rejects.toThrow('Upload failed: 403'); }); @@ -367,16 +373,16 @@ describe('StorageObject (New API)', () => { mockClient.objects.download.mockResolvedValue(mockDownloadUrl); - const mockFetchResponse = { + const mockedShimsFetchResponse = { ok: true, text: jest.fn().mockResolvedValue('File contents'), }; - ((global as any).fetch as jest.Mock).mockResolvedValue(mockFetchResponse); + mockedShimsFetch.mockResolvedValue(mockedShimsFetchResponse); const content = await storageObject.downloadAsText(); - expect((global as any).fetch).toHaveBeenCalledWith(mockDownloadUrl.download_url); + expect(mockedShimsFetch).toHaveBeenCalledWith(mockDownloadUrl.download_url); expect(content).toBe('File contents'); }); @@ -387,13 +393,13 @@ describe('StorageObject (New API)', () => { mockClient.objects.download.mockResolvedValue(mockDownloadUrl); - const mockFetchResponse = { + const mockedShimsFetchResponse = { ok: false, status: 404, statusText: 'Not Found', }; - ((global as any).fetch as jest.Mock).mockResolvedValue(mockFetchResponse); + mockedShimsFetch.mockResolvedValue(mockedShimsFetchResponse); await expect(storageObject.downloadAsText()).rejects.toThrow('Download failed: 404 Not Found'); }); @@ -408,16 +414,16 @@ describe('StorageObject (New API)', () => { mockClient.objects.download.mockResolvedValue(mockDownloadUrl); const mockArrayBuffer = new Uint8Array([0x89, 0x50, 0x4e, 0x47]).buffer; - const mockFetchResponse = { + const mockedShimsFetchResponse = { ok: true, arrayBuffer: jest.fn().mockResolvedValue(mockArrayBuffer), }; - ((global as any).fetch as jest.Mock).mockResolvedValue(mockFetchResponse); + mockedShimsFetch.mockResolvedValue(mockedShimsFetchResponse); const buffer = await storageObject.downloadAsBuffer(); - expect((global as any).fetch).toHaveBeenCalledWith(mockDownloadUrl.download_url); + expect(mockedShimsFetch).toHaveBeenCalledWith(mockDownloadUrl.download_url); expect(Buffer.isBuffer(buffer)).toBe(true); expect(buffer.length).toBe(4); }); @@ -452,7 +458,7 @@ describe('StorageObject (New API)', () => { // Upload - mock getInfo for uploadContent mockClient.objects.retrieve.mockResolvedValue(mockObjectData); - ((global as any).fetch as jest.Mock).mockResolvedValue({ ok: true }); + mockedShimsFetch.mockResolvedValue({ ok: true }); await obj.uploadContent('Test content'); // Complete @@ -465,7 +471,7 @@ describe('StorageObject (New API)', () => { download_url: 'https://s3.example.com/download/workflow-test.txt', }; mockClient.objects.download.mockResolvedValue(mockDownloadUrl); - ((global as any).fetch as jest.Mock).mockResolvedValue({ + mockedShimsFetch.mockResolvedValue({ ok: true, text: jest.fn().mockResolvedValue('Test content'), }); @@ -480,7 +486,7 @@ describe('StorageObject (New API)', () => { // Clear all mocks jest.clearAllMocks(); // Reset global fetch mock - ((global as any).fetch as jest.Mock).mockClear(); + mockedShimsFetch.mockClear(); }); it('should upload a text file with auto-detected content-type', async () => { @@ -496,7 +502,7 @@ describe('StorageObject (New API)', () => { mockClient.objects.retrieve.mockResolvedValue(mockObjectInfo); mockClient.objects.complete.mockResolvedValue(mockCompletedData); - ((global as any).fetch as jest.Mock).mockResolvedValue({ + mockedShimsFetch.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', @@ -526,7 +532,7 @@ describe('StorageObject (New API)', () => { mockClient.objects.retrieve.mockResolvedValue(mockObjectInfo); mockClient.objects.complete.mockResolvedValue(mockCompletedData); - ((global as any).fetch as jest.Mock).mockResolvedValue({ + mockedShimsFetch.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', @@ -576,7 +582,7 @@ describe('StorageObject (New API)', () => { mockClient.objects.create.mockResolvedValue(mockObjectData); mockClient.objects.retrieve.mockResolvedValue(mockObjectInfo); - ((global as any).fetch as jest.Mock).mockResolvedValue({ + mockedShimsFetch.mockResolvedValue({ ok: false, status: 500, statusText: 'Internal Server Error', @@ -600,7 +606,7 @@ describe('StorageObject (New API)', () => { mockClient.objects.retrieve.mockResolvedValue(mockObjectInfo); mockClient.objects.complete.mockResolvedValue(mockCompletedData); - ((global as any).fetch as jest.Mock).mockResolvedValue({ + mockedShimsFetch.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', @@ -627,7 +633,7 @@ describe('StorageObject (New API)', () => { // Clear all mocks jest.clearAllMocks(); // Reset global fetch mock - ((global as any).fetch as jest.Mock).mockClear(); + mockedShimsFetch.mockClear(); }); it('should upload text content with text content-type', async () => { @@ -640,7 +646,7 @@ describe('StorageObject (New API)', () => { mockClient.objects.retrieve.mockResolvedValue(mockObjectInfo); mockClient.objects.complete.mockResolvedValue(mockCompletedData); - ((global as any).fetch as jest.Mock).mockResolvedValue({ + mockedShimsFetch.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', @@ -652,11 +658,11 @@ describe('StorageObject (New API)', () => { { name: 'hello.txt', content_type: 'text', metadata: null }, undefined, ); - // uploadFromText uses Blob for fetch body - const fetchCalls = ((global as any).fetch as jest.Mock).mock.calls; + // uploadFromText uses Buffer for fetch body + const fetchCalls = mockedShimsFetch.mock.calls; expect(fetchCalls[0][0]).toBe('https://upload.example.com/text'); expect(fetchCalls[0][1].method).toBe('PUT'); - expect(fetchCalls[0][1].body).toBeInstanceOf(Blob); + expect(Buffer.isBuffer(fetchCalls[0][1].body)).toBe(true); expect(result).toBeInstanceOf(StorageObject); expect(result.id).toBe('text-123'); }); @@ -671,7 +677,7 @@ describe('StorageObject (New API)', () => { mockClient.objects.retrieve.mockResolvedValue(mockObjectInfo); mockClient.objects.complete.mockResolvedValue(mockCompletedData); - ((global as any).fetch as jest.Mock).mockResolvedValue({ + mockedShimsFetch.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', @@ -696,7 +702,7 @@ describe('StorageObject (New API)', () => { mockClient.objects.create.mockResolvedValue(mockObjectData); mockClient.objects.retrieve.mockResolvedValue(mockObjectInfo); - ((global as any).fetch as jest.Mock).mockResolvedValue({ + mockedShimsFetch.mockResolvedValue({ ok: false, status: 403, statusText: 'Forbidden', @@ -717,7 +723,7 @@ describe('StorageObject (New API)', () => { mockClient.objects.retrieve.mockResolvedValue(mockObjectInfo); mockClient.objects.complete.mockResolvedValue(mockCompletedData); - ((global as any).fetch as jest.Mock).mockResolvedValue({ + mockedShimsFetch.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', @@ -727,7 +733,7 @@ describe('StorageObject (New API)', () => { // Verify all three steps were called expect(mockClient.objects.create).toHaveBeenCalledTimes(1); - expect((global as any).fetch).toHaveBeenCalledTimes(1); + expect(mockedShimsFetch).toHaveBeenCalledTimes(1); expect(mockClient.objects.complete).toHaveBeenCalledTimes(1); expect(result).toBeInstanceOf(StorageObject); }); @@ -738,7 +744,7 @@ describe('StorageObject (New API)', () => { // Clear all mocks jest.clearAllMocks(); // Reset global fetch mock - ((global as any).fetch as jest.Mock).mockClear(); + mockedShimsFetch.mockClear(); }); it('should upload buffer with specified content-type and name', async () => { @@ -751,7 +757,7 @@ describe('StorageObject (New API)', () => { mockClient.objects.retrieve.mockResolvedValue(mockObjectInfo); mockClient.objects.complete.mockResolvedValue(mockCompletedData); - ((global as any).fetch as jest.Mock).mockResolvedValue({ + mockedShimsFetch.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', @@ -765,11 +771,11 @@ describe('StorageObject (New API)', () => { { name: 'buffer.txt', content_type: 'text', metadata: { source: 'buffer' } }, { metadata: { source: 'buffer' } }, ); - // uploadFromBuffer uses Blob for fetch body - const fetchCalls = ((global as any).fetch as jest.Mock).mock.calls; + // uploadFromBuffer uses Buffer for fetch body + const fetchCalls = mockedShimsFetch.mock.calls; expect(fetchCalls[0][0]).toBe('https://upload.example.com/buffer'); expect(fetchCalls[0][1].method).toBe('PUT'); - expect(fetchCalls[0][1].body).toBeInstanceOf(Blob); + expect(Buffer.isBuffer(fetchCalls[0][1].body)).toBe(true); expect(result).toBeInstanceOf(StorageObject); expect(result.id).toBe('buffer-123'); }); @@ -796,7 +802,7 @@ describe('StorageObject (New API)', () => { mockClient.objects.create.mockResolvedValue(mockObjectData); mockClient.objects.retrieve.mockResolvedValue(mockObjectInfo); - ((global as any).fetch as jest.Mock).mockResolvedValue({ + mockedShimsFetch.mockResolvedValue({ ok: false, status: 403, statusText: 'Forbidden', @@ -817,7 +823,7 @@ describe('StorageObject (New API)', () => { mockClient.objects.retrieve.mockResolvedValue(mockObjectInfo); mockClient.objects.complete.mockResolvedValue(mockCompletedData); - ((global as any).fetch as jest.Mock).mockResolvedValue({ + mockedShimsFetch.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', @@ -827,7 +833,7 @@ describe('StorageObject (New API)', () => { // Verify all three steps were called expect(mockClient.objects.create).toHaveBeenCalledTimes(1); - expect((global as any).fetch).toHaveBeenCalledTimes(1); + expect(mockedShimsFetch).toHaveBeenCalledTimes(1); expect(mockClient.objects.complete).toHaveBeenCalledTimes(1); expect(result).toBeInstanceOf(StorageObject); }); @@ -840,7 +846,7 @@ describe('StorageObject (New API)', () => { // Clear all mocks jest.clearAllMocks(); // Reset global fetch mock - ((global as any).fetch as jest.Mock).mockClear(); + mockedShimsFetch.mockClear(); // Get tar mock mockTar = require('tar'); // Reset ignore matcher mock @@ -852,6 +858,8 @@ describe('StorageObject (New API)', () => { // Mock directory exists mockFs.stat.mockResolvedValue({ isDirectory: () => true, size: 100 }); mockFs.mkdtemp.mockResolvedValue('/tmp/runloop-upload-123'); + // Mock reading the tarball file + mockFs.readFile.mockResolvedValue(Buffer.from('mock tarball content')); // Mock write stream const mockWriteStream = { @@ -859,12 +867,6 @@ describe('StorageObject (New API)', () => { }; mockFsSync.createWriteStream.mockReturnValue(mockWriteStream); - // Mock read stream - const mockReadStream = { - pipe: jest.fn(), - }; - mockFsSync.createReadStream.mockReturnValue(mockReadStream); - // Mock tar stream const mockTarStream = { pipe: jest.fn((dest) => { @@ -885,7 +887,7 @@ describe('StorageObject (New API)', () => { mockClient.objects.retrieve.mockResolvedValue(mockObjectInfo); mockClient.objects.complete.mockResolvedValue(mockCompletedData); - ((global as any).fetch as jest.Mock).mockResolvedValue({ + mockedShimsFetch.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', @@ -902,7 +904,7 @@ describe('StorageObject (New API)', () => { expect(mockTar.create).toHaveBeenCalled(); expect(mockFs.mkdtemp).toHaveBeenCalled(); expect(mockFsSync.createWriteStream).toHaveBeenCalled(); - expect(mockFsSync.createReadStream).toHaveBeenCalled(); + expect(mockFs.readFile).toHaveBeenCalled(); expect(mockFs.rm).toHaveBeenCalledWith('/tmp/runloop-upload-123', { recursive: true, force: true }); expect(result).toBeInstanceOf(StorageObject); expect(result.id).toBe('dir-123'); @@ -912,13 +914,14 @@ describe('StorageObject (New API)', () => { // Mock directory exists mockFs.stat.mockResolvedValue({ isDirectory: () => true, size: 100 }); mockFs.mkdtemp.mockResolvedValue('/tmp/runloop-upload-123'); + // Mock reading the tarball file + mockFs.readFile.mockResolvedValue(Buffer.from('mock tarball content')); // Mocks for streams const mockWriteStream = { on: jest.fn().mockReturnThis(), }; mockFsSync.createWriteStream.mockReturnValue(mockWriteStream); - mockFsSync.createReadStream.mockReturnValue({}); // Provide a fake matcher const matcher = { matches: jest.fn() }; @@ -939,7 +942,7 @@ describe('StorageObject (New API)', () => { const mockObjectData = { id: 'dir-ignore', upload_url: 'https://upload.example.com/dir' }; mockClient.objects.create.mockResolvedValue(mockObjectData); mockClient.objects.complete.mockResolvedValue({ ...mockObjectData, state: 'READ_ONLY' }); - ((global as any).fetch as jest.Mock).mockResolvedValue({ ok: true }); + mockedShimsFetch.mockResolvedValue({ ok: true }); await StorageObject.uploadFromDir(mockClient, './my-project', { name: 'project.tar.gz', @@ -966,13 +969,14 @@ describe('StorageObject (New API)', () => { // Mock directory exists mockFs.stat.mockResolvedValue({ isDirectory: () => true, size: 100 }); mockFs.mkdtemp.mockResolvedValue('/tmp/runloop-upload-123'); + // Mock reading the tarball file + mockFs.readFile.mockResolvedValue(Buffer.from('mock tarball content')); // Mocks for streams const mockWriteStream = { on: jest.fn().mockReturnThis(), }; mockFsSync.createWriteStream.mockReturnValue(mockWriteStream); - mockFsSync.createReadStream.mockReturnValue({}); // Mock tar stream const mockTarStream = { @@ -991,7 +995,7 @@ describe('StorageObject (New API)', () => { mockClient.objects.create.mockResolvedValue(mockObjectData); mockClient.objects.complete.mockResolvedValue(mockCompletedData); - ((global as any).fetch as jest.Mock).mockResolvedValue({ + mockedShimsFetch.mockResolvedValue({ ok: true, status: 200, statusText: 'OK', @@ -1040,12 +1044,13 @@ describe('StorageObject (New API)', () => { it('should handle upload failures gracefully', async () => { mockFs.stat.mockResolvedValue({ isDirectory: () => true, size: 100 }); mockFs.mkdtemp.mockResolvedValue('/tmp/runloop-upload-123'); + // Mock reading the tarball file + mockFs.readFile.mockResolvedValue(Buffer.from('mock tarball content')); const mockWriteStream = { on: jest.fn().mockReturnThis(), }; mockFsSync.createWriteStream.mockReturnValue(mockWriteStream); - mockFsSync.createReadStream.mockReturnValue({}); const mockTarStream = { pipe: jest.fn((dest) => { @@ -1060,7 +1065,7 @@ describe('StorageObject (New API)', () => { const mockObjectData = { id: 'dir-999', upload_url: 'https://upload.example.com/dir' }; mockClient.objects.create.mockResolvedValue(mockObjectData); - ((global as any).fetch as jest.Mock).mockResolvedValue({ + mockedShimsFetch.mockResolvedValue({ ok: false, status: 500, statusText: 'Internal Server Error',