From ec93be59073e0dfa3f41407eafc3a37659ceaeea Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Nov 2026 12:59:37 +0000 Subject: [PATCH 01/60] Add Scope.workspace for Blaxel client configuration. Expose an optional nonblank workspace field on the shared Scope contract, regenerate Go and TypeScript protobuf packages, and mirror the path constant in both public SDKs. Co-authored-by: Nabhag Motivaras --- proto/kit/sandbox/v1/client.proto | 6 ++++++ .../contracts/kit/sandbox/v1/client.pb.go | 21 +++++++++++++++---- sdks/go/sandbox/client_contracts.go | 1 + sdks/go/sandbox/config_paths.go | 3 +++ .../contracts/src/kit/sandbox/v1/client_pb.ts | 9 +++++++- .../sandbox/src/contracts/config_paths.ts | 3 +++ specs/client.yaml | 1 + 7 files changed, 39 insertions(+), 5 deletions(-) diff --git a/proto/kit/sandbox/v1/client.proto b/proto/kit/sandbox/v1/client.proto index a5bf540d..14cb8bf2 100644 --- a/proto/kit/sandbox/v1/client.proto +++ b/proto/kit/sandbox/v1/client.proto @@ -147,4 +147,10 @@ message Scope { message: "must not be blank" expression: "this.trim() != ''" }]; + // The Blaxel workspace name. + optional string workspace = 5 [(buf.validate.field).cel = { + id: "nonblank" + message: "must not be blank" + expression: "this.trim() != ''" + }]; } diff --git a/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go b/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go index 0aaf667e..f675adb2 100644 --- a/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go +++ b/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go @@ -457,7 +457,9 @@ type Scope struct { // The Daytona organization. Bearer auth requires it when the SDK cannot resolve one. OrganizationId *string `protobuf:"bytes,3,opt,name=organization_id,json=organizationId,proto3,oneof" json:"organization_id,omitempty"` // Reserved for providers that scope work to a project. Current providers reject it when supplied. - ProjectId *string `protobuf:"bytes,4,opt,name=project_id,json=projectId,proto3,oneof" json:"project_id,omitempty"` + ProjectId *string `protobuf:"bytes,4,opt,name=project_id,json=projectId,proto3,oneof" json:"project_id,omitempty"` + // The Blaxel workspace name. + Workspace *string `protobuf:"bytes,5,opt,name=workspace,proto3,oneof" json:"workspace,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -520,6 +522,13 @@ func (x *Scope) GetProjectId() string { return "" } +func (x *Scope) GetWorkspace() string { + if x != nil && x.Workspace != nil { + return *x.Workspace + } + return "" +} + var File_kit_sandbox_v1_client_proto protoreflect.FileDescriptor const file_kit_sandbox_v1_client_proto_rawDesc = "" + @@ -567,7 +576,7 @@ const file_kit_sandbox_v1_client_proto_rawDesc = "" + "\vexactly_one\x12.requires exactly one of client_secret, jwt_key\x1a,has(this.client_secret) != has(this.jwt_key)B\x10\n" + "\x0e_client_secretB\n" + "\n" + - "\b_jwt_key\"\xc0\x03\n" + + "\b_jwt_key\"\xa9\x04\n" + "\x05Scope\x12V\n" + "\bapp_name\x18\x01 \x01(\tB6\xbaH3\xba\x010\n" + "\bnonblank\x12\x11must not be blank\x1a\x11this.trim() != ''H\x00R\aappName\x88\x01\x01\x12]\n" + @@ -577,11 +586,15 @@ const file_kit_sandbox_v1_client_proto_rawDesc = "" + "\bnonblank\x12\x11must not be blank\x1a\x11this.trim() != ''H\x02R\x0eorganizationId\x88\x01\x01\x12Z\n" + "\n" + "project_id\x18\x04 \x01(\tB6\xbaH3\xba\x010\n" + - "\bnonblank\x12\x11must not be blank\x1a\x11this.trim() != ''H\x03R\tprojectId\x88\x01\x01B\v\n" + + "\bnonblank\x12\x11must not be blank\x1a\x11this.trim() != ''H\x03R\tprojectId\x88\x01\x01\x12Y\n" + + "\tworkspace\x18\x05 \x01(\tB6\xbaH3\xba\x010\n" + + "\bnonblank\x12\x11must not be blank\x1a\x11this.trim() != ''H\x04R\tworkspace\x88\x01\x01B\v\n" + "\t_app_nameB\x0e\n" + "\f_environmentB\x12\n" + "\x10_organization_idB\r\n" + - "\v_project_idBPZNgithub.com/sandbox-kit/kit/sdks/go/internal/contracts/kit/sandbox/v1;sandboxv1b\x06proto3" + "\v_project_idB\f\n" + + "\n" + + "_workspaceBPZNgithub.com/sandbox-kit/kit/sdks/go/internal/contracts/kit/sandbox/v1;sandboxv1b\x06proto3" var ( file_kit_sandbox_v1_client_proto_rawDescOnce sync.Once diff --git a/sdks/go/sandbox/client_contracts.go b/sdks/go/sandbox/client_contracts.go index 2d846086..3a5fb75b 100644 --- a/sdks/go/sandbox/client_contracts.go +++ b/sdks/go/sandbox/client_contracts.go @@ -47,6 +47,7 @@ type ( // Scope selects the account boundary for the client. // Modal requires AppName. Environment is often "main". // Daytona accepts OrganizationId. + // Workspace is the Blaxel workspace name. Scope = sandboxv1.Scope ) diff --git a/sdks/go/sandbox/config_paths.go b/sdks/go/sandbox/config_paths.go index bfaf4e04..b3b13b07 100644 --- a/sdks/go/sandbox/config_paths.go +++ b/sdks/go/sandbox/config_paths.go @@ -68,6 +68,9 @@ const ConfigFieldScopeOrganizationId = "scope.organization_id" // ConfigFieldScopeProjectId is the canonical path "scope.project_id". const ConfigFieldScopeProjectId = "scope.project_id" +// ConfigFieldScopeWorkspace is the canonical path "scope.workspace". +const ConfigFieldScopeWorkspace = "scope.workspace" + // ConfigFieldTimeout is the canonical path "timeout". const ConfigFieldTimeout = "timeout" diff --git a/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts b/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts index bf93979d..9a2d6094 100644 --- a/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts +++ b/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts @@ -13,7 +13,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file kit/sandbox/v1/client.proto. */ export const file_kit_sandbox_v1_client: GenFile = /*@__PURE__*/ - fileDesc("ChtraXQvc2FuZGJveC92MS9jbGllbnQucHJvdG8SDmtpdC5zYW5kYm94LnYxIssCCgZDb25maWcSKAoEYXV0aBgBIAEoCzIaLmtpdC5zYW5kYm94LnYxLkF1dGhDb25maWcSTQoIZW5kcG9pbnQYAiABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gAiAEBEksKBnJlZ2lvbhgDIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSAGIAQESLQoFc2NvcGUYBCABKAsyFS5raXQuc2FuZGJveC52MS5TY29wZVIHY29udGV4dBI0Cgd0aW1lb3V0GAUgASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uQgi6SAWqAQIyAEILCglfZW5kcG9pbnRCCQoHX3JlZ2lvbiLCAwoKQXV0aENvbmZpZxI0CgdhcGlfa2V5GAEgASgLMiEua2l0LnNhbmRib3gudjEuQVBJS2V5Q3JlZGVudGlhbHNIABI6Cgp0b2tlbl9wYWlyGAIgASgLMiQua2l0LnNhbmRib3gudjEuVG9rZW5QYWlyQ3JlZGVudGlhbHNIABI+CgxiZWFyZXJfdG9rZW4YAyABKAsyJi5raXQuc2FuZGJveC52MS5CZWFyZXJUb2tlbkNyZWRlbnRpYWxzSAASOQoNb2F1dGhfcmVmcmVzaBgEIAEoCzIgLmtpdC5zYW5kYm94LnYxLk9BdXRoQ3JlZGVudGlhbHNIADq3AbpIswEasAEKGWF1dGhfY3JlZGVudGlhbHNfcmVxdWlyZWQSM2F1dGggY3JlZGVudGlhbHMgYXJlIHJlcXVpcmVkIHdoZW4gYXV0aCBpcyBzdXBwbGllZBpeaGFzKHRoaXMuYXBpX2tleSkgfHwgaGFzKHRoaXMudG9rZW5fcGFpcikgfHwgaGFzKHRoaXMuYmVhcmVyX3Rva2VuKSB8fCBoYXModGhpcy5vYXV0aF9yZWZyZXNoKUINCgtjcmVkZW50aWFscyJYChFBUElLZXlDcmVkZW50aWFscxJDCgNrZXkYASABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJyKiAQoUVG9rZW5QYWlyQ3JlZGVudGlhbHMSQgoCaWQYASABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJxJGCgZzZWNyZXQYAiABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJyJfChZCZWFyZXJUb2tlbkNyZWRlbnRpYWxzEkUKBXRva2VuGAEgASgJQja6SDO6ATAKCG5vbmJsYW5rEhFtdXN0IG5vdCBiZSBibGFuaxoRdGhpcy50cmltKCkgIT0gJyci3gMKEE9BdXRoQ3JlZGVudGlhbHMSTQoNcmVmcmVzaF90b2tlbhgBIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnEkkKCWNsaWVudF9pZBgCIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnElIKDWNsaWVudF9zZWNyZXQYAyABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gAiAEBEkwKB2p3dF9rZXkYBCABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gBiAEBOnC6SG0aawoLZXhhY3RseV9vbmUSLnJlcXVpcmVzIGV4YWN0bHkgb25lIG9mIGNsaWVudF9zZWNyZXQsIGp3dF9rZXkaLGhhcyh0aGlzLmNsaWVudF9zZWNyZXQpICE9IGhhcyh0aGlzLmp3dF9rZXkpQhAKDl9jbGllbnRfc2VjcmV0QgoKCF9qd3Rfa2V5Io8DCgVTY29wZRJNCghhcHBfbmFtZRgBIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSACIAQESUAoLZW52aXJvbm1lbnQYAiABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gBiAEBElQKD29yZ2FuaXphdGlvbl9pZBgDIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSAKIAQESTwoKcHJvamVjdF9pZBgEIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSAOIAQFCCwoJX2FwcF9uYW1lQg4KDF9lbnZpcm9ubWVudEISChBfb3JnYW5pemF0aW9uX2lkQg0KC19wcm9qZWN0X2lkQlBaTmdpdGh1Yi5jb20vc2FuZGJveC1raXQva2l0L3Nka3MvZ28vaW50ZXJuYWwvY29udHJhY3RzL2tpdC9zYW5kYm94L3YxO3NhbmRib3h2MWIGcHJvdG8z", [file_buf_validate_validate, file_google_protobuf_duration]); + fileDesc("ChtraXQvc2FuZGJveC92MS9jbGllbnQucHJvdG8SDmtpdC5zYW5kYm94LnYxIssCCgZDb25maWcSKAoEYXV0aBgBIAEoCzIaLmtpdC5zYW5kYm94LnYxLkF1dGhDb25maWcSTQoIZW5kcG9pbnQYAiABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gAiAEBEksKBnJlZ2lvbhgDIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSAGIAQESLQoFc2NvcGUYBCABKAsyFS5raXQuc2FuZGJveC52MS5TY29wZVIHY29udGV4dBI0Cgd0aW1lb3V0GAUgASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uQgi6SAWqAQIyAEILCglfZW5kcG9pbnRCCQoHX3JlZ2lvbiLCAwoKQXV0aENvbmZpZxI0CgdhcGlfa2V5GAEgASgLMiEua2l0LnNhbmRib3gudjEuQVBJS2V5Q3JlZGVudGlhbHNIABI6Cgp0b2tlbl9wYWlyGAIgASgLMiQua2l0LnNhbmRib3gudjEuVG9rZW5QYWlyQ3JlZGVudGlhbHNIABI+CgxiZWFyZXJfdG9rZW4YAyABKAsyJi5raXQuc2FuZGJveC52MS5CZWFyZXJUb2tlbkNyZWRlbnRpYWxzSAASOQoNb2F1dGhfcmVmcmVzaBgEIAEoCzIgLmtpdC5zYW5kYm94LnYxLk9BdXRoQ3JlZGVudGlhbHNIADq3AbpIswEasAEKGWF1dGhfY3JlZGVudGlhbHNfcmVxdWlyZWQSM2F1dGggY3JlZGVudGlhbHMgYXJlIHJlcXVpcmVkIHdoZW4gYXV0aCBpcyBzdXBwbGllZBpeaGFzKHRoaXMuYXBpX2tleSkgfHwgaGFzKHRoaXMudG9rZW5fcGFpcikgfHwgaGFzKHRoaXMuYmVhcmVyX3Rva2VuKSB8fCBoYXModGhpcy5vYXV0aF9yZWZyZXNoKUINCgtjcmVkZW50aWFscyJYChFBUElLZXlDcmVkZW50aWFscxJDCgNrZXkYASABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJyKiAQoUVG9rZW5QYWlyQ3JlZGVudGlhbHMSQgoCaWQYASABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJxJGCgZzZWNyZXQYAiABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJyJfChZCZWFyZXJUb2tlbkNyZWRlbnRpYWxzEkUKBXRva2VuGAEgASgJQja6SDO6ATAKCG5vbmJsYW5rEhFtdXN0IG5vdCBiZSBibGFuaxoRdGhpcy50cmltKCkgIT0gJyci3gMKEE9BdXRoQ3JlZGVudGlhbHMSTQoNcmVmcmVzaF90b2tlbhgBIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnEkkKCWNsaWVudF9pZBgCIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnElIKDWNsaWVudF9zZWNyZXQYAyABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gAiAEBEkwKB2p3dF9rZXkYBCABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gBiAEBOnC6SG0aawoLZXhhY3RseV9vbmUSLnJlcXVpcmVzIGV4YWN0bHkgb25lIG9mIGNsaWVudF9zZWNyZXQsIGp3dF9rZXkaLGhhcyh0aGlzLmNsaWVudF9zZWNyZXQpICE9IGhhcyh0aGlzLmp3dF9rZXkpQhAKDl9jbGllbnRfc2VjcmV0QgoKCF9qd3Rfa2V5Iu0DCgVTY29wZRJNCghhcHBfbmFtZRgBIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSACIAQESUAoLZW52aXJvbm1lbnQYAiABKAlCNrpIM7oBMAoIbm9uYmxhbmsSEW11c3Qgbm90IGJlIGJsYW5rGhF0aGlzLnRyaW0oKSAhPSAnJ0gBiAEBElQKD29yZ2FuaXphdGlvbl9pZBgDIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSAKIAQESTwoKcHJvamVjdF9pZBgEIAEoCUI2ukgzugEwCghub25ibGFuaxIRbXVzdCBub3QgYmUgYmxhbmsaEXRoaXMudHJpbSgpICE9ICcnSAOIAQESTgoJd29ya3NwYWNlGAUgASgJQja6SDO6ATAKCG5vbmJsYW5rEhFtdXN0IG5vdCBiZSBibGFuaxoRdGhpcy50cmltKCkgIT0gJydIBIgBAUILCglfYXBwX25hbWVCDgoMX2Vudmlyb25tZW50QhIKEF9vcmdhbml6YXRpb25faWRCDQoLX3Byb2plY3RfaWRCDAoKX3dvcmtzcGFjZUJQWk5naXRodWIuY29tL3NhbmRib3gta2l0L2tpdC9zZGtzL2dvL2ludGVybmFsL2NvbnRyYWN0cy9raXQvc2FuZGJveC92MTtzYW5kYm94djFiBnByb3RvMw", [file_buf_validate_validate, file_google_protobuf_duration]); /** * Shared client configuration. Provider selection is a language SDK concern and @@ -259,6 +259,13 @@ export type Scope = Message<"kit.sandbox.v1.Scope"> & { * @generated from field: optional string project_id = 4; */ projectId?: string | undefined; + + /** + * The Blaxel workspace name. + * + * @generated from field: optional string workspace = 5; + */ + workspace?: string | undefined; }; /** diff --git a/sdks/typescript/sandbox/src/contracts/config_paths.ts b/sdks/typescript/sandbox/src/contracts/config_paths.ts index 6372fd0c..73f98263 100644 --- a/sdks/typescript/sandbox/src/contracts/config_paths.ts +++ b/sdks/typescript/sandbox/src/contracts/config_paths.ts @@ -65,6 +65,9 @@ export const ConfigFieldScopeOrganizationId = "scope.organization_id" as const; /** Canonical path "scope.project_id". */ export const ConfigFieldScopeProjectId = "scope.project_id" as const; +/** Canonical path "scope.workspace". */ +export const ConfigFieldScopeWorkspace = "scope.workspace" as const; + /** Canonical path "timeout". */ export const ConfigFieldTimeout = "timeout" as const; diff --git a/specs/client.yaml b/specs/client.yaml index c2837f41..411428be 100644 --- a/specs/client.yaml +++ b/specs/client.yaml @@ -56,3 +56,4 @@ validation: environment: { nonblank: true } organization_id: { nonblank: true } project_id: { nonblank: true } + workspace: { nonblank: true } From 7cc229266d32c016c5c46f2e51dca906cd63d222 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Nov 2026 13:11:16 +0000 Subject: [PATCH 02/60] Add @sandbox-kit/blaxel TypeScript provider. Mirror the Modal provider layout with apiKey + workspace auth, required name/image create via SandboxInstance.create, get/refresh and delete by name, filesystem and blocking run, and Vitest rejection/mapping coverage. Co-authored-by: Nabhag Motivaras --- sdks/typescript/package-lock.json | 6080 +++++++++++------ sdks/typescript/package.json | 1 + sdks/typescript/providers/blaxel/.gitignore | 2 + sdks/typescript/providers/blaxel/.nvmrc | 1 + sdks/typescript/providers/blaxel/LICENSE | 21 + sdks/typescript/providers/blaxel/README.md | 87 + sdks/typescript/providers/blaxel/package.json | 77 + .../providers/blaxel/src/blaxel.test.ts | 499 ++ .../typescript/providers/blaxel/src/blaxel.ts | 445 ++ .../blaxel/src/checks/provider_checks.ts | 32 + .../blaxel/src/config/provider_config.ts | 85 + .../typescript/providers/blaxel/src/errors.ts | 342 + sdks/typescript/providers/blaxel/src/index.ts | 2 + .../providers/blaxel/src/mappings/mappings.ts | 412 ++ .../blaxel/src/mappings/provider_mappings.ts | 145 + .../providers/blaxel/src/provider.ts | 29 + .../providers/blaxel/src/rejection.test.ts | 184 + .../providers/blaxel/tsconfig.build.json | 7 + .../typescript/providers/blaxel/tsconfig.json | 23 + .../providers/blaxel/tsconfig.test.json | 3 + .../providers/blaxel/vitest.config.ts | 24 + 21 files changed, 6444 insertions(+), 2057 deletions(-) create mode 100644 sdks/typescript/providers/blaxel/.gitignore create mode 100644 sdks/typescript/providers/blaxel/.nvmrc create mode 100644 sdks/typescript/providers/blaxel/LICENSE create mode 100644 sdks/typescript/providers/blaxel/README.md create mode 100644 sdks/typescript/providers/blaxel/package.json create mode 100644 sdks/typescript/providers/blaxel/src/blaxel.test.ts create mode 100644 sdks/typescript/providers/blaxel/src/blaxel.ts create mode 100644 sdks/typescript/providers/blaxel/src/checks/provider_checks.ts create mode 100644 sdks/typescript/providers/blaxel/src/config/provider_config.ts create mode 100644 sdks/typescript/providers/blaxel/src/errors.ts create mode 100644 sdks/typescript/providers/blaxel/src/index.ts create mode 100644 sdks/typescript/providers/blaxel/src/mappings/mappings.ts create mode 100644 sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts create mode 100644 sdks/typescript/providers/blaxel/src/provider.ts create mode 100644 sdks/typescript/providers/blaxel/src/rejection.test.ts create mode 100644 sdks/typescript/providers/blaxel/tsconfig.build.json create mode 100644 sdks/typescript/providers/blaxel/tsconfig.json create mode 100644 sdks/typescript/providers/blaxel/tsconfig.test.json create mode 100644 sdks/typescript/providers/blaxel/vitest.config.ts diff --git a/sdks/typescript/package-lock.json b/sdks/typescript/package-lock.json index bd729346..b5dfae37 100644 --- a/sdks/typescript/package-lock.json +++ b/sdks/typescript/package-lock.json @@ -8,6 +8,7 @@ "workspaces": [ "contracts", "sandbox", + "providers/blaxel", "providers/daytona", "providers/modal" ] @@ -42,10 +43,6 @@ "@bufbuild/protobuf": "^2.6.2" } }, - "contracts/node_modules/@bufbuild/protobuf": { - "version": "2.16.0", - "license": "(Apache-2.0 AND BSD-3-Clause)" - }, "contracts/node_modules/@bufbuild/protovalidate": { "version": "1.3.0", "license": "Apache-2.0", @@ -61,18 +58,6 @@ "version": "0.6.1", "license": "MIT" }, - "contracts/node_modules/typescript": { - "version": "5.9.3", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/@aws-sdk/checksums": { "version": "3.1001.4", "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1001.4.tgz", @@ -401,6 +386,36 @@ "node": ">=18.0.0" } }, + "node_modules/@blaxel/core": { + "version": "0.3.35", + "resolved": "https://registry.npmjs.org/@blaxel/core/-/core-0.3.35.tgz", + "integrity": "sha512-R71Qus/sQ5tuF3Em+0p2h44cPrCGg58pwXlMbz/35CIEDPr9HaNmUQAzlVklCdBACOWUTzIwsqp2nB0jFEqZug==", + "license": "MIT", + "dependencies": { + "@hey-api/client-fetch": "^0.10.0", + "@modelcontextprotocol/sdk": "^1.20.0", + "archiver": "^7.0.1", + "axios": "^1.9.0", + "dockerfile-ast": "^0.7.1", + "dotenv": "^17.4.2", + "form-data": "^4.0.2", + "jwt-decode": "^4.0.0", + "smol-toml": "^1.8.0", + "uuid": "^11.1.0", + "ws": "^8.18.2", + "yaml": "^2.7.1", + "zod": "^4.6.6" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.16.0.tgz", + "integrity": "sha512-FWa0sPlqYGJgpTs6OxBRcL/AW4JT0OIO+W1ajerluoTVh8CV0B7XM1qsNCRgnkSCkRCgOICKHRQlY17jrsm0+Q==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, "node_modules/@cbor-extract/cbor-extract-darwin-arm64": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.2.tgz", @@ -571,12 +586,244 @@ "node": ">=6" } }, + "node_modules/@hey-api/client-fetch": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@hey-api/client-fetch/-/client-fetch-0.10.2.tgz", + "integrity": "sha512-AGiFYDx+y8VT1wlQ3EbzzZtfU8EfV+hLLRTtr8Y/tjYZaxIECwJagVZf24YzNbtEBXONFV50bwcU1wLVGXe1ow==", + "deprecated": "Starting with v0.73.0, this package is bundled directly inside @hey-api/openapi-ts.", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/hey-api" + }, + "peerDependencies": { + "@hey-api/openapi-ts": "< 2" + } + }, + "node_modules/@hey-api/codegen-core": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@hey-api/codegen-core/-/codegen-core-0.9.1.tgz", + "integrity": "sha512-s97jL1dgTMuiMHv2BZ1X4Tgd99Mf9GOvGdNqNcGwIMmnR+PgYNoraj4Zvp134MKsNCap/m7k0r0vKKnl56pj4w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@hey-api/types": "0.1.4", + "ansi-colors": "4.1.3", + "c12": "3.3.4", + "color-support": "1.1.3" + }, + "engines": { + "node": ">=22.18.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/json-schema-ref-parser": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.4.4.tgz", + "integrity": "sha512-otmd+zCxbYVBIp/mlMTnGkvlNYLkVKgs3VOIq0kSnenhB1+fRwLPQIeSwyWM6E51oXhUedkYjVsVpkVexeuJOA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jsdevtools/ono": "7.1.3", + "@types/json-schema": "7.0.15", + "js-yaml": "4.2.0" + }, + "engines": { + "node": ">=22.18.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/openapi-ts": { + "version": "0.99.0", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.99.0.tgz", + "integrity": "sha512-SePU/5oEWWkvUBYmvzdYRctseoLuskyhs4ET0RvLIcmzc8yLQoA2R+KtBIQ8bPsoSUB0m4E5SmBnl6aGSA0szQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@hey-api/codegen-core": "0.9.1", + "@hey-api/json-schema-ref-parser": "1.4.4", + "@hey-api/shared": "0.5.0", + "@hey-api/spec-types": "0.2.0", + "@hey-api/types": "0.1.4", + "@lukeed/ms": "2.0.2", + "ansi-colors": "4.1.3", + "color-support": "1.1.3", + "commander": "15.0.0", + "get-tsconfig": "4.14.0" + }, + "bin": { + "openapi-ts": "bin/run.js" + }, + "engines": { + "node": ">=22.18.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + }, + "peerDependencies": { + "typescript": ">=5.5.3 || >=6.0.0 || 6.0.1-rc" + } + }, + "node_modules/@hey-api/shared": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@hey-api/shared/-/shared-0.5.0.tgz", + "integrity": "sha512-JN/j4Ebh4cJGYIQ5cwWuqe7GeSUyQoz7oC51WqyhKOcrejK6DKZMDkshc5d1eKTRuRL+rjozuRcoUaZZn2DGPw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@hey-api/codegen-core": "0.9.1", + "@hey-api/json-schema-ref-parser": "1.4.4", + "@hey-api/spec-types": "0.2.0", + "@hey-api/types": "0.1.4", + "ansi-colors": "4.1.3", + "cross-spawn": "7.0.6", + "open": "11.0.0", + "semver": "7.8.4" + }, + "engines": { + "node": ">=22.18.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/spec-types": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@hey-api/spec-types/-/spec-types-0.2.0.tgz", + "integrity": "sha512-ibQ8Is7evMavzr8GNyJCcTg975d8DpaMUyLmOrQ85UBdy1l6t1KuRAwgChAbesJsIlNV6gjmlXruWyegDX18Fg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@hey-api/types": "0.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + } + }, + "node_modules/@hey-api/types": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@hey-api/types/-/types-0.1.4.tgz", + "integrity": "sha512-thWfawrDIP7wSI9ioT13I5soaaqB5vAPIiZmgD8PbeEVKNrkonc0N/Sjj97ezl7oQgusZmaNphGdMKipPO6IBg==", + "license": "MIT", + "peer": true + }, + "node_modules/@hono/node-server": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.6.tgz", + "integrity": "sha512-p/KH9MnfNcsm3sR6x4lNgm5j+QN4Et8srKredl9DZfXiyZ1UQ3aQ2YVpFLbqktYN5JI+DB+VZFKM2PqE5hLKjQ==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "hono": "^4 || ^5.0.0-0" + } + }, "node_modules/@iarna/toml": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", "license": "ISC" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.6.0.tgz", + "integrity": "sha512-XZ5apbzreCbXSKwPjJstu8R0zOi247w3XYwWxtwBXgpZrCyc1Ka7p9/xY0A9/diMewmHbEKz+FIraWaEWjlN5g==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -589,6 +836,13 @@ "node": ">=18.0.0" } }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@js-sdsl/ordered-map": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", @@ -599,6 +853,63 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT", + "peer": true + }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.32.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.2.tgz", + "integrity": "sha512-SWsVtH/0vULSQStoZ6xxH2K09Li8fXYTMC64ZeogCdpZ3XSi1eCPOByV1OpuJxuI3OcmHhosDWYyii07tpPsLw==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1246,6 +1557,26 @@ "node": ">=14" } }, + "node_modules/@oxc-project/types": { + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.156.0.tgz", + "integrity": "sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1303,69 +1634,353 @@ "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", "license": "BSD-3-Clause" }, - "node_modules/@sandbox-kit/contracts": { - "resolved": "contracts", - "link": true - }, - "node_modules/@sandbox-kit/daytona": { - "resolved": "providers/daytona", - "link": true - }, - "node_modules/@sandbox-kit/modal": { - "resolved": "providers/modal", - "link": true - }, - "node_modules/@sandbox-kit/sandbox": { - "resolved": "sandbox", - "link": true - }, - "node_modules/@smithy/core": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.36.0.tgz", - "integrity": "sha512-3hJiwG1F5t1SoXqp/yOCmYXziyx7ZVqF63S5W6YU201e/qwVaHPex+E/3vvyY9a3ORq/b+BydeNZpP2saQ9X2g==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.19.0", - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.18.tgz", + "integrity": "sha512-ubTG+4KlidvioxjOFdsdEa2F6JVGZb8m2ByJUqQ7fYzUeT1cEn194POcsQ10PKH4p//q0YQn4SB3YpSuzcA1Yg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=18.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", - "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.33.2", - "@smithy/types": "^4.17.2", - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.18.tgz", + "integrity": "sha512-2Xa1t/JeFGTZn5TbzgPppcCftF8sCVnTNvrIvoSdcZhoFzAuVy1L3xxQUQ4sfWDtwVzg1u6Yi1R3SlJugRNV2Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=18.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", - "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.18.0", - "tslib": "^2.6.2" - }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.18.tgz", + "integrity": "sha512-iUzw9HshVvrWci9gVXjxWS7/M/IcLvNo67vu2c/w9zKBz4hIvPRixdUpIW/iCfhldLaq9ER2hecsI9UY5z0/yA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=18.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@smithy/node-http-handler": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", - "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", - "license": "Apache-2.0", - "dependencies": { + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.18.tgz", + "integrity": "sha512-XuDHFzbFEtZl0QGJi92+fmG67tfnp1iZ9VOeDkwasGyTq2VG3/5PnJnvAr+MTzFh/rNScJ3VhN+XpZyyGsN7Jw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.18.tgz", + "integrity": "sha512-MM8opLfsLK15oEXDz6iBc0t3ahu+ew9jsAmha+ovZ33hSwYgqo7YmpRITqFTfW7xA71BLEQ3jJdTsaCmhB1c9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.18.tgz", + "integrity": "sha512-J6EoCWiy5RnFQzHrKyHXXevZ9lukQfGpnchX/zG/k/WC2nc9O9XyS1JgA8kI9VxMFggMbCPjpv7LcikriXfehw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.18.tgz", + "integrity": "sha512-HIkFuLF7CdDeki+1aCZLLbLq0hk/PHntb+z4/YCTo47JbksTD6jJEELIz92tYROmDmvMrNqUMqBi71BkJLDR+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.18.tgz", + "integrity": "sha512-4oqWItLLYt7IceOnElCct8T2ek5p7PmD7LN04TRJRXGo5f3HtKTgGH6PhYRFK4C2ICkq/LMUU0BVPvvlAKhm8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.18.tgz", + "integrity": "sha512-+AkRly5SrCEgnPhGRfub7pucASYEC2fTsxZg7VCcnozpA2+Wn/gCYy1t7LuJS67/xyJu/7noYBJncDUkbwgw2A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.18.tgz", + "integrity": "sha512-Ttg1r9vVykMnQrE/GwYO9icKtXiuiMfRe/w74xJkeoVuvhRG+WtT6LEMOf58Yl63yd77lhHzO8Y55R8CC3XALA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.18.tgz", + "integrity": "sha512-xX1Tt4o5jUMma0vJsXp5eRZA9nOuyigb2dKJMMzZRmzMxLYGE9PaKKSqHPzPvSQgM8ZOEacSTQwDQcwWfh0xmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.18.tgz", + "integrity": "sha512-4VVlGI5K2kociZlW3lSN8VjG3TW5yQIlAcfCdBXMgGjJxZll15Fa4ocRHXHvIVVnwlibCw/NQajJrNDiqqpiiw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.18.tgz", + "integrity": "sha512-PApTsgH1jLYuyy3tSCz9De82Gq5qXwi7vImVZhEzTgX1C4150Cp5M2ZfL3pLvbCvbEHL0oJeC6pd6podtv8Y5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.18.tgz", + "integrity": "sha512-J2XjWjx/SP+4x0H4r6dz3Vzb5WVkg7re40Do9K7HJ3sK2/xcmZrWwakXlSwaMvOkvruLJCD4CL/3wqsIR/thgg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.18.tgz", + "integrity": "sha512-XwRJxRQvkuEtS0hIIt29QrqPJ+JzMJSNYHJw4ae/mc6uEarfRG1ceyg4HmmWiBdL/YCb6g7N5BLbSvmRJRYZHw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.1.0.tgz", + "integrity": "sha512-K+NU2HDCEem4sClgHH4ZZXwdJeNFkJo+hRk479DIwdIviAPN2hFJH1KHHwg8CxHQXvi0M5XY4deTXINfIiSG6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/contracts": { + "resolved": "contracts", + "link": true + }, + "node_modules/@sandbox-kit/daytona": { + "resolved": "providers/daytona", + "link": true + }, + "node_modules/@sandbox-kit/modal": { + "resolved": "providers/modal", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "sandbox", + "link": true + }, + "node_modules/@smithy/core": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.36.0.tgz", + "integrity": "sha512-3hJiwG1F5t1SoXqp/yOCmYXziyx7ZVqF63S5W6YU201e/qwVaHPex+E/3vvyY9a3ORq/b+BydeNZpP2saQ9X2g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.19.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.5.2.tgz", + "integrity": "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.2", + "@smithy/types": "^4.17.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.8.0.tgz", + "integrity": "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.33.3", + "@smithy/types": "^4.18.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", + "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", + "license": "Apache-2.0", + "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.18.0", "tslib": "^2.6.2" @@ -1406,6 +2021,45 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.10.tgz", + "integrity": "sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT", + "peer": true + }, "node_modules/@types/node": { "version": "26.6.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.5.tgz", @@ -1415,14 +2069,177 @@ "undici-types": "~8.9.0" } }, - "node_modules/abort-controller-x": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.5.0.tgz", - "integrity": "sha512-yTt9CI0x+nRfX6BFMenEGP8ooPvErGH6AbFz20C2IeOLIlDsrw/VHpgne3GsCEuTA410IiFiaLVFKmgM4bKEPQ==", - "license": "MIT" - }, - "node_modules/agent-base": { - "version": "6.0.2", + "node_modules/@vitest/expect": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.11", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.11", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.11", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abort-controller-x": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.5.0.tgz", + "integrity": "sha512-yTt9CI0x+nRfX6BFMenEGP8ooPvErGH6AbFz20C2IeOLIlDsrw/VHpgne3GsCEuTA410IiFiaLVFKmgM4bKEPQ==", + "license": "MIT" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", @@ -1433,6 +2250,49 @@ "node": ">= 6.0.0" } }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1457,28 +2317,46 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } }, - "node_modules/axios": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", - "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.6", - "https-proxy-agent": "^5.0.1", - "proxy-from-env": "^2.1.0" + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "node_modules/archiver-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "funding": [ { "type": "github", @@ -1493,74 +2371,430 @@ "url": "https://feross.org/support" } ], - "license": "MIT" - }, - "node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "license": "MIT" + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "node_modules/archiver/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "license": "MIT", "dependencies": { - "streamsearch": "^1.1.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">=10.16.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0", + "peer": true + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, "engines": { - "node": ">= 0.4" + "node": ">=12" } }, - "node_modules/cbor-extract": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.2.tgz", - "integrity": "sha512-hlSxxI9XO2yQfe9g6msd3g4xCfDqK5T5P0fRMLuaLHhxn4ViPrm+a+MUfhrvH2W962RGxcBwEGzLQyjbDG1gng==", - "hasInstallScript": true, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", "license": "MIT", - "optional": true, "dependencies": { - "node-gyp-build-optional-packages": "5.1.1" - }, - "bin": { - "download-cbor-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/b4a": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.9.0.tgz", + "integrity": "sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/bare-buffer": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/bare-buffer/-/bare-buffer-3.7.1.tgz", + "integrity": "sha512-cIjZnSO+y89ykVX9x96OVnA6tGORBn7vGJ/e3PCbBHMEyfMNG/ixzOS0lLa6saMlWcs69bSKjxFyDwjK152OMg==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.20.0" + } + }, + "node_modules/bare-events": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.2.tgz", + "integrity": "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.4.tgz", + "integrity": "sha512-MPC2eyrpbk3A873sK4y6rk+Iv4o2enxTjHZ8zH3HO0mjrpm5XjnguY2XmfidIsDV6vnjpbMBKUAJpV0ISD2SsQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.28.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-path": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.2.tgz", + "integrity": "sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==", + "license": "Apache-2.0" + }, + "node_modules/bare-stream": { + "version": "2.13.4", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.4.tgz", + "integrity": "sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.6.tgz", + "integrity": "sha512-iqkXHkW65De+BT14nHLiBFbNuJ/DdMrZaVNJ1IXPctpK6b1olUJ5Qmy8QBvwROajTiGztso2n82kFEvCsNG7Rg==", + "license": "Apache-2.0", + "dependencies": { + "bare-buffer": "^3.0.0", + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz", + "integrity": "sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.1.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.3", + "on-finished": "^2.4.1", + "qs": "^6.16.1", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bundle-name": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz", + "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", + "license": "MIT", + "peer": true, + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/c12": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", + "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", + "license": "MIT", + "peer": true, + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "defu": "^6.1.6", + "dotenv": "^17.3.1", + "exsolve": "^1.0.8", + "giget": "^3.2.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.1.0", + "pkg-types": "^2.3.0", + "rc9": "^3.0.1" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/cbor-extract": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.2.tgz", + "integrity": "sha512-hlSxxI9XO2yQfe9g6msd3g4xCfDqK5T5P0fRMLuaLHhxn4ViPrm+a+MUfhrvH2W962RGxcBwEGzLQyjbDG1gng==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.1.1" + }, + "bin": { + "download-cbor-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { "@cbor-extract/cbor-extract-darwin-arm64": "2.2.2", "@cbor-extract/cbor-extract-darwin-x64": "2.2.2", "@cbor-extract/cbor-extract-linux-arm": "2.2.2", @@ -1578,6 +2812,32 @@ "cbor-extract": "^2.2.2" } }, + "node_modules/chai": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "peer": true, + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -1625,6 +2885,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "peer": true, + "bin": { + "color-support": "bin.js" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1637,32 +2907,320 @@ "node": ">= 0.8" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, + "peer": true, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=22.12.0" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/compress-commons/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "license": "MIT", + "peer": true + }, + "node_modules/content-disposition": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-2.0.1.tgz", + "integrity": "sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/crc32-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "license": "MIT", + "peer": true, + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT", + "peer": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", "engines": { "node": ">=0.4.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT", + "peer": true + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -1673,6 +3231,31 @@ "node": ">=8" } }, + "node_modules/dockerfile-ast": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/dockerfile-ast/-/dockerfile-ast-0.7.1.tgz", + "integrity": "sha512-oX/A4I0EhSkGqrFv0YuvPkBUSYp1XiY8O8zAKc8Djglx8ocz+JfOr8gP0ryRMC2myqvDLagmnZaU9ot1vG2ijw==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1687,12 +3270,37 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.1.tgz", + "integrity": "sha512-Ev/hIyePLZtpL98x562lvPtenrUfGr0lfaf44HFZ+hDEwso7ccV9QAZrEVLg0hxNAzygaAFxy2dYaAKKM0G06A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/engine.io-client": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.7.tgz", @@ -1796,6 +3404,40 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -1805,6 +3447,36 @@ "node": ">=0.8.x" } }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -1817,26 +3489,171 @@ "node": ">=0.10.0" } }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=8.6.0" + "node": ">=12.0.0" } }, - "node_modules/fastq": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", - "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "node_modules/express": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.3.0.tgz", + "integrity": "sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.3.0", + "content-disposition": "^2.0.1", + "content-type": "^2.0.0", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.8", + "qs": "^6.16.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.1.0", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.7.3", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.3.tgz", + "integrity": "sha512-zPLqDI2f0gV2WmGnacLf9XSSqn4VldRyP5LCzOwx0BKH40mO6+2PCFkoSsRO2jePx87/Kf6NUF6P2QOTKXHbQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/exsolve": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.2.0.tgz", + "integrity": "sha512-h1pQPR9XoeSMdntRNCjyTznaume4UpfXrULBnlkZ2CUM/G5eSa+pBByGldO5uR8dxbPC+OuJXsPPNf+JZ81ADg==", + "license": "MIT", + "peer": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -1854,6 +3671,27 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/follow-redirects": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz", @@ -1874,6 +3712,22 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", @@ -1890,12 +3744,45 @@ "node": ">= 6" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/forwarded-parse": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", "license": "MIT" }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1951,6 +3838,50 @@ "node": ">= 0.4" } }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "license": "MIT", + "peer": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/giget": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/giget/-/giget-3.3.1.tgz", + "integrity": "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==", + "license": "MIT", + "peer": true, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -1975,6 +3906,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -2026,6 +3963,35 @@ "node": ">=0.10.0" } }, + "node_modules/hono": { + "version": "4.13.17", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.17.tgz", + "integrity": "sha512-vZIcbkvjOR5hHRWERbGkr8fGfF0GGM1ooYkuH448iO3b2AdVEr3AXZSw4Z3Kn+1L5z0CVoVeyV29VGYmFh05kQ==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -2039,6 +4005,22 @@ "node": ">= 6" } }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -2079,6 +4061,40 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "10.7.6", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.6.tgz", + "integrity": "sha512-00LYkM6byILa9eQIx6qoaXCM40xTLd56AZ4vyaPW3YTyb49VZYPo5xsiAq4tuQ54GH4R7gVdYScJ1B7B77PuTg==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "peer": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2109,6 +4125,38 @@ "node": ">=0.10.0" } }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2118,6 +4166,52 @@ "node": ">=0.12.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, "node_modules/isomorphic-ws": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", @@ -2127,1741 +4221,1709 @@ "ws": "*" } }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT" + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "node_modules/jose": { + "version": "6.2.16", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.16.tgz", + "integrity": "sha512-4LSJpbuBuPdGrONCdZUy0xnxtLf8eEz+EY0SMXdHQEBU7bgUHDVblU/JAjryv3wID9/LK7/YuwFwq+AQNfMscQ==", "license": "MIT", - "engines": { - "node": ">= 0.4" + "funding": { + "url": "https://github.com/sponsors/panva" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", + "peer": true, "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "argparse": "^2.0.1" }, - "engines": { - "node": ">=8.6" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=18" } }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "license": "MIT", "dependencies": { - "minipass": "^7.1.2" + "readable-stream": "^2.0.5" }, "engines": { - "node": ">= 18" + "node": ">= 0.6.3" } }, - "node_modules/modal": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/modal/-/modal-0.11.1.tgz", - "integrity": "sha512-RVb84wPZGG4zd3q2Fuww/ku5kZbp9sDkSUVqIc2VdKa+2QQyuqM/puVDbeVze8XN9tdUp9IJZRbdECJmz56TeQ==", - "license": "Apache-2.0", + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", "dependencies": { - "cbor-x": "^1.6.0", - "long": "^5.3.1", - "nice-grpc": "^2.1.12", - "protobufjs": "^7.5.0", - "smol-toml": "^1.9.0", - "uuid": "^11.1.0" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/module-details-from-path": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", - "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, - "node_modules/nice-grpc": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/nice-grpc/-/nice-grpc-2.1.17.tgz", - "integrity": "sha512-pu9xYPlWSeqoYQOCqb2ftQqZi/P2DaI70PSuwnxvoyIRiilaOVtktyPe6LmuXegWB4Ic1sPuDGCnCCASbwzK0w==", + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "license": "MIT", "dependencies": { - "@grpc/grpc-js": "^1.14.0", - "abort-controller-x": "^0.5.0", - "nice-grpc-common": "^2.0.4" + "safe-buffer": "~5.1.0" } }, - "node_modules/nice-grpc-common": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/nice-grpc-common/-/nice-grpc-common-2.0.4.tgz", - "integrity": "sha512-gOEXlD6ShXZMZ8k+49wm/bA2j1+3IKbEFV9WYREJcvZV4kM5L1KkILYTX9VYBzZF2OuYCe1wp8c82bQkvR0fHw==", - "license": "MIT", + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", "dependencies": { - "ts-error": "^1.0.6" + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", - "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", - "license": "MIT", + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", "optional": true, - "dependencies": { - "detect-libc": "^2.0.1" + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "license": "MIT", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" - }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "license": "MIT", + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8.6" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/protobufjs": { - "version": "7.6.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", - "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.3.2" - }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=12.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", - "license": "MIT", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" ], - "license": "MIT" - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, "engines": { - "node": ">= 6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/require-in-the-middle": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", - "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "module-details-from-path": "^1.0.3" - }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=9.3.0 || >=8.10.0 <9.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "license": "MIT", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, - "node_modules/shell-quote": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz", - "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/smol-toml": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.1.tgz", - "integrity": "sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==", - "license": "BSD-3-Clause", + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", "engines": { - "node": ">= 18" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/cyyynthia" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/socket.io-client": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.4.tgz", - "integrity": "sha512-7wjKAvVXYOJsbmUgkWbnhPD4bFpkqrUR0++XOjpGFmqDqsqV1CI93VTraeExLxpjxEqrWgFoSRZu1ihjLWfkAA==", + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1", - "engine.io-client": "~6.6.1", - "socket.io-parser": "~4.2.4" - }, "engines": { - "node": ">=10.0.0" + "node": ">= 8" } }, - "node_modules/socket.io-parser": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz", - "integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==", + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "license": "MIT", "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=10.0.0" + "node": ">=8.6" } }, - "node_modules/stream-browserify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", - "dependencies": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string_decoder": { - "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==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" + "node": ">= 0.6" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "mime-db": "1.52.0" }, "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", "dependencies": { - "ansi-regex": "^5.0.1" + "brace-expansion": "^2.0.2" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/tar": { - "version": "7.5.22", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", - "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, "engines": { - "node": ">=18" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "license": "MIT", "dependencies": { - "is-number": "^7.0.0" + "minipass": "^7.1.2" }, "engines": { - "node": ">=8.0" + "node": ">= 18" } }, - "node_modules/ts-error": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/ts-error/-/ts-error-1.0.6.tgz", - "integrity": "sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==", - "license": "MIT" - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "node_modules/modal": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/modal/-/modal-0.11.1.tgz", + "integrity": "sha512-RVb84wPZGG4zd3q2Fuww/ku5kZbp9sDkSUVqIc2VdKa+2QQyuqM/puVDbeVze8XN9tdUp9IJZRbdECJmz56TeQ==", + "license": "Apache-2.0", + "dependencies": { + "cbor-x": "^1.6.0", + "long": "^5.3.1", + "nice-grpc": "^2.1.12", + "protobufjs": "^7.5.0", + "smol-toml": "^1.9.0", + "uuid": "^11.1.0" + } }, - "node_modules/undici-types": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", - "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", "license": "MIT" }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/uuid": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", - "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "dev": true, "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } ], "license": "MIT", "bin": { - "uuid": "dist/esm/bin/uuid" + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "content-type": "^2.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/ws": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", - "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=18" }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/nice-grpc": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/nice-grpc/-/nice-grpc-2.1.17.tgz", + "integrity": "sha512-pu9xYPlWSeqoYQOCqb2ftQqZi/P2DaI70PSuwnxvoyIRiilaOVtktyPe6LmuXegWB4Ic1sPuDGCnCCASbwzK0w==", + "license": "MIT", + "dependencies": { + "@grpc/grpc-js": "^1.14.0", + "abort-controller-x": "^0.5.0", + "nice-grpc-common": "^2.0.4" + } + }, + "node_modules/nice-grpc-common": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/nice-grpc-common/-/nice-grpc-common-2.0.4.tgz", + "integrity": "sha512-gOEXlD6ShXZMZ8k+49wm/bA2j1+3IKbEFV9WYREJcvZV4kM5L1KkILYTX9VYBzZF2OuYCe1wp8c82bQkvR0fHw==", + "license": "MIT", + "dependencies": { + "ts-error": "^1.0.6" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", + "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" } }, - "node_modules/xmlhttprequest-ssl": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", - "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", "engines": { - "node": ">=0.4.0" + "node": ">=0.10.0" } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/yaml": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", - "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "license": "MIT", + "peer": true + }, + "node_modules/on-finished": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.2.tgz", + "integrity": "sha512-ATWUv387i+nLD49EiDoOft5scf32Vsdz3ktrQJG9bi7jRFmaXTssvyvj1EmeKVVgvesbqUtvQQvaS2TpTukAZQ==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" }, "engines": { - "node": ">= 14.6" + "node": ">= 0.8" }, "funding": { - "url": "https://github.com/sponsors/eemeli" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/yargs": { - "version": "17.7.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", - "engines": { - "node": ">=12" + "dependencies": { + "wrappy": "1" } }, - "providers/daytona": { - "name": "@sandbox-kit/daytona", - "version": "0.1.7", + "node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", "license": "MIT", + "peer": true, "dependencies": { - "@bufbuild/protobuf": "2.16.0", - "@daytona/sdk": "0.225.0", - "@sandbox-kit/sandbox": "0.1.7" - }, - "devDependencies": { - "@types/node": "24.10.1", - "typescript": "5.9.3", - "vitest": "4.1.11" + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" }, "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.15.0" - } - }, - "providers/daytona/node_modules/@bufbuild/protobuf": { - "version": "2.16.0", - "license": "(Apache-2.0 AND BSD-3-Clause)" - }, - "providers/daytona/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.6.0", - "dev": true, - "license": "MIT" - }, - "providers/daytona/node_modules/@oxc-project/types": { - "version": "0.153.0", - "dev": true, - "license": "MIT", + "node": ">=20" + }, "funding": { - "url": "https://github.com/sponsors/oxc-project" + "url": "https://github.com/sponsors/sindresorhus" } }, - "providers/daytona/node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.13", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=0.10.0" } }, - "providers/daytona/node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "providers/daytona/node_modules/@standard-schema/spec": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, - "providers/daytona/node_modules/@types/chai": { - "version": "5.2.3", - "dev": true, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "engines": { + "node": ">= 0.8" } }, - "providers/daytona/node_modules/@types/deep-eql": { - "version": "4.0.2", - "dev": true, - "license": "MIT" - }, - "providers/daytona/node_modules/@types/estree": { - "version": "1.0.9", - "dev": true, - "license": "MIT" - }, - "providers/daytona/node_modules/@types/node": { - "version": "24.10.1", - "dev": true, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" + "engines": { + "node": ">=8" } }, - "providers/daytona/node_modules/@vitest/expect": { - "version": "4.1.11", - "dev": true, - "license": "MIT", + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/isaacs" } }, - "providers/daytona/node_modules/@vitest/mocker": { - "version": "4.1.11", - "dev": true, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.11", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "providers/daytona/node_modules/@vitest/pretty-format": { - "version": "4.1.11", + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "license": "MIT", + "peer": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" + "engines": { + "node": ">=8.6" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "providers/daytona/node_modules/@vitest/runner": { - "version": "4.1.11", - "dev": true, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.11", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=16.20.0" } }, - "providers/daytona/node_modules/@vitest/snapshot": { - "version": "4.1.11", - "dev": true, + "node_modules/pkg-types": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.4.0.tgz", + "integrity": "sha512-07Fys7iVx0X6TcviMOgogM1Lk33ZyqOezMnU79S+kffWqwolG/5W9V8EgQSeYCIfFTYVgssRbnH630Oa2+soBA==", "license": "MIT", + "peer": true, "dependencies": { - "@vitest/pretty-format": "4.1.11", - "@vitest/utils": "4.1.11", - "magic-string": "^0.30.21", + "confbox": "^0.3.1", + "exsolve": "^1.1.3", "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" } }, - "providers/daytona/node_modules/@vitest/spy": { - "version": "4.1.11", - "dev": true, + "node_modules/pkg-types/node_modules/confbox": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.2.tgz", + "integrity": "sha512-ZUAKhKlkCOrIGKc8cj7zek9s9z1mveHif/25nUshI2BzCRzF7KH7KHa+Lnh2ubq+e1tIlaFuP763mzW4qVoehg==", "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } + "peer": true }, - "providers/daytona/node_modules/@vitest/utils": { - "version": "4.1.11", + "node_modules/postcss": { + "version": "8.5.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", + "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.11", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" + "nanoid": "^3.3.20", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": "^10 || ^12 || >=14" } }, - "providers/daytona/node_modules/assertion-error": { - "version": "2.0.1", - "dev": true, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", "license": "MIT", + "peer": true, "engines": { - "node": ">=12" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "providers/daytona/node_modules/chai": { - "version": "6.3.0", - "dev": true, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 0.6.0" } }, - "providers/daytona/node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, - "providers/daytona/node_modules/estree-walker": { - "version": "3.0.3", - "dev": true, - "license": "MIT", + "node_modules/protobufjs": { + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", + "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "providers/daytona/node_modules/expect-type": { - "version": "1.4.0", - "dev": true, - "license": "Apache-2.0", + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, "engines": { "node": ">=12.0.0" } }, - "providers/daytona/node_modules/fsevents": { - "version": "2.3.3", - "dev": true, + "node_modules/proxy-addr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "providers/daytona/node_modules/lightningcss": { - "version": "1.33.0", - "dev": true, - "license": "MPL-2.0", "dependencies": { - "detect-libc": "^2.0.3" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 0.10" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" + "url": "https://opencollective.com/express" } }, - "providers/daytona/node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "node": ">=10" } }, - "providers/daytona/node_modules/magic-string": { - "version": "0.30.21", - "dev": true, - "license": "MIT", + "node_modules/qs": { + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.17.1.tgz", + "integrity": "sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA==", + "license": "BSD-3-Clause", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "providers/daytona/node_modules/nanoid": { - "version": "3.3.20", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "providers/daytona/node_modules/obug": { - "version": "2.2.1", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT", - "engines": { - "node": ">=12.20.0" + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "providers/daytona/node_modules/picocolors": { - "version": "1.1.1", - "dev": true, - "license": "ISC" - }, - "providers/daytona/node_modules/postcss": { - "version": "8.5.30", - "dev": true, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "type": "github", + "url": "https://github.com/sponsors/feross" }, { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" + "type": "patreon", + "url": "https://www.patreon.com/feross" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "consulting", + "url": "https://feross.org/support" } ], + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "license": "MIT", - "dependencies": { - "nanoid": "^3.3.20", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.2" - }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "providers/daytona/node_modules/rolldown": { - "version": "1.2.13", - "dev": true, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.153.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, - "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.13", - "@rolldown/binding-android-arm64": "1.2.13", - "@rolldown/binding-darwin-arm64": "1.2.13", - "@rolldown/binding-darwin-x64": "1.2.13", - "@rolldown/binding-freebsd-x64": "1.2.13", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", - "@rolldown/binding-linux-arm64-gnu": "1.2.13", - "@rolldown/binding-linux-arm64-musl": "1.2.13", - "@rolldown/binding-linux-ppc64-gnu": "1.2.13", - "@rolldown/binding-linux-s390x-gnu": "1.2.13", - "@rolldown/binding-linux-x64-gnu": "1.2.13", - "@rolldown/binding-linux-x64-musl": "1.2.13", - "@rolldown/binding-openharmony-arm64": "1.2.13", - "@rolldown/binding-win32-arm64-msvc": "1.2.13", - "@rolldown/binding-win32-x64-msvc": "1.2.13" - } - }, - "providers/daytona/node_modules/siginfo": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "providers/daytona/node_modules/source-map-js": { - "version": "1.2.2", - "dev": true, - "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" + "node": ">= 0.10" } }, - "providers/daytona/node_modules/stackback": { - "version": "0.0.2", - "dev": true, - "license": "MIT" - }, - "providers/daytona/node_modules/std-env": { - "version": "4.3.0", - "dev": true, - "license": "MIT" - }, - "providers/daytona/node_modules/tinybench": { - "version": "2.9.0", - "dev": true, - "license": "MIT" - }, - "providers/daytona/node_modules/tinyexec": { - "version": "1.3.1", - "dev": true, + "node_modules/rc9": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.1.0.tgz", + "integrity": "sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==", "license": "MIT", - "engines": { - "node": ">=18" + "peer": true, + "dependencies": { + "defu": "^6.1.7", + "destr": "^2.0.5" } }, - "providers/daytona/node_modules/tinyglobby": { - "version": "0.2.17", - "dev": true, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=12.0.0" + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "engines": { + "node": ">=10" } }, - "providers/daytona/node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "dev": true, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", "license": "MIT", + "peer": true, "engines": { - "node": ">=12.0.0" + "node": ">= 20.19.0" }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "providers/daytona/node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.7", - "dev": true, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=0.10.0" } }, - "providers/daytona/node_modules/tinyrainbow": { - "version": "3.2.0", - "dev": true, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=0.10.0" } }, - "providers/daytona/node_modules/typescript": { - "version": "5.9.3", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" }, "engines": { - "node": ">=14.17" + "node": ">=9.3.0 || >=8.10.0 <9.0.0" } }, - "providers/daytona/node_modules/undici-types": { - "version": "7.16.0", - "dev": true, - "license": "MIT" + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } }, - "providers/daytona/node_modules/vite": { - "version": "8.3.4", + "node_modules/rolldown": { + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.18.tgz", + "integrity": "sha512-C1uuT3UR60SShuCaPQFAAZyefJCWZlAKB02BV3BuPE2Rs4fevRQltEKoFO2tV5W9/eJhU5RieV6vJj3euulfNQ==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.33.0", - "picomatch": "^4.0.7", - "postcss": "^8.5.29", - "rolldown": "~1.2.12", - "tinyglobby": "^0.2.17" + "@oxc-project/types": "=0.156.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "vite": "bin/vite.js" + "rolldown": "bin/cli.mjs" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, "optionalDependencies": { - "fsevents": "~2.3.3" + "@rolldown/binding-android-arm-eabi": "1.2.18", + "@rolldown/binding-android-arm64": "1.2.18", + "@rolldown/binding-darwin-arm64": "1.2.18", + "@rolldown/binding-darwin-x64": "1.2.18", + "@rolldown/binding-freebsd-x64": "1.2.18", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.18", + "@rolldown/binding-linux-arm64-gnu": "1.2.18", + "@rolldown/binding-linux-arm64-musl": "1.2.18", + "@rolldown/binding-linux-ppc64-gnu": "1.2.18", + "@rolldown/binding-linux-s390x-gnu": "1.2.18", + "@rolldown/binding-linux-x64-gnu": "1.2.18", + "@rolldown/binding-linux-x64-musl": "1.2.18", + "@rolldown/binding-openharmony-arm64": "1.2.18", + "@rolldown/binding-win32-arm64-msvc": "1.2.18", + "@rolldown/binding-win32-x64-msvc": "1.2.18" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.7.1", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" + "engines": { + "node": ">= 18" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "sugarss": { - "optional": true + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "terser": { - "optional": true + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "tsx": { - "optional": true + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "yaml": { - "optional": true + { + "type": "consulting", + "url": "https://feross.org/support" } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "providers/daytona/node_modules/vite/node_modules/picomatch": { - "version": "4.0.7", - "dev": true, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, "engines": { - "node": ">=12" + "node": ">= 18" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "providers/daytona/node_modules/vitest": { - "version": "4.1.11", - "dev": true, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.11", - "@vitest/mocker": "4.1.11", - "@vitest/pretty-format": "4.1.11", - "@vitest/runner": "4.1.11", - "@vitest/snapshot": "4.1.11", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" + "mime-db": "^1.54.0" }, "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" }, "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.11", - "@vitest/browser-preview": "4.1.11", - "@vitest/browser-webdriverio": "4.1.11", - "@vitest/coverage-istanbul": "4.1.11", - "@vitest/coverage-v8": "4.1.11", - "@vitest/ui": "4.1.11", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "providers/daytona/node_modules/vitest/node_modules/es-module-lexer": { - "version": "2.3.2", - "dev": true, - "license": "MIT" - }, - "providers/daytona/node_modules/vitest/node_modules/picomatch": { - "version": "4.0.7", - "dev": true, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, "engines": { - "node": ">=12" + "node": ">= 18" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "providers/daytona/node_modules/why-is-node-running": { - "version": "2.3.0", - "dev": true, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" + "shebang-regex": "^3.0.0" }, "engines": { "node": ">=8" } }, - "providers/modal": { - "name": "@sandbox-kit/modal", - "version": "0.1.7", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", - "dependencies": { - "@bufbuild/protobuf": "2.16.0", - "@sandbox-kit/sandbox": "0.1.7", - "modal": "0.11.1" - }, - "devDependencies": { - "@types/node": "24.10.1", - "typescript": "5.9.3", - "vitest": "4.1.11" - }, "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + "node": ">=8" } }, - "providers/modal/node_modules/@bufbuild/protobuf": { - "version": "2.16.0", - "license": "(Apache-2.0 AND BSD-3-Clause)" - }, - "providers/modal/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.6.0", - "dev": true, - "license": "MIT" - }, - "providers/modal/node_modules/@oxc-project/types": { - "version": "0.153.0", - "dev": true, + "node_modules/shell-quote": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz", + "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { - "url": "https://github.com/sponsors/oxc-project" + "url": "https://github.com/sponsors/ljharb" } }, - "providers/modal/node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.13", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "providers/modal/node_modules/@rolldown/pluginutils": { + "node_modules/side-channel-list": { "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "providers/modal/node_modules/@standard-schema/spec": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, - "providers/modal/node_modules/@types/chai": { - "version": "5.2.3", - "dev": true, + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "providers/modal/node_modules/@types/deep-eql": { - "version": "4.0.2", - "dev": true, - "license": "MIT" - }, - "providers/modal/node_modules/@types/estree": { - "version": "1.0.9", - "dev": true, - "license": "MIT" - }, - "providers/modal/node_modules/@types/node": { - "version": "24.10.1", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "providers/modal/node_modules/@vitest/expect": { - "version": "4.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/ljharb" } }, - "providers/modal/node_modules/@vitest/mocker": { - "version": "4.1.11", - "dev": true, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.11", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "engines": { + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "providers/modal/node_modules/@vitest/pretty-format": { - "version": "4.1.11", - "dev": true, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { - "tinyrainbow": "^3.1.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/ljharb" } }, - "providers/modal/node_modules/@vitest/runner": { - "version": "4.1.11", + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.11", - "pathe": "^2.0.3" + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/isaacs" } }, - "providers/modal/node_modules/@vitest/snapshot": { - "version": "4.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.11", - "@vitest/utils": "4.1.11", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" + "node_modules/smol-toml": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.1.tgz", + "integrity": "sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/cyyynthia" } }, - "providers/modal/node_modules/@vitest/spy": { - "version": "4.1.11", - "dev": true, + "node_modules/socket.io-client": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.4.tgz", + "integrity": "sha512-7wjKAvVXYOJsbmUgkWbnhPD4bFpkqrUR0++XOjpGFmqDqsqV1CI93VTraeExLxpjxEqrWgFoSRZu1ihjLWfkAA==", "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" } }, - "providers/modal/node_modules/@vitest/utils": { - "version": "4.1.11", - "dev": true, + "node_modules/socket.io-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz", + "integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==", "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.11", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=10.0.0" } }, - "providers/modal/node_modules/assertion-error": { - "version": "2.0.1", + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "providers/modal/node_modules/chai": { - "version": "6.3.0", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 0.8" } }, - "providers/modal/node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "providers/modal/node_modules/es-module-lexer": { - "version": "2.3.2", + "node_modules/std-env": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", "dev": true, "license": "MIT" }, - "providers/modal/node_modules/estree-walker": { - "version": "3.0.3", - "dev": true, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0" + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" } }, - "providers/modal/node_modules/expect-type": { - "version": "1.4.0", - "dev": true, - "license": "Apache-2.0", + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", "engines": { - "node": ">=12.0.0" + "node": ">=10.0.0" } }, - "providers/modal/node_modules/fdir": { - "version": "6.5.0", - "dev": true, + "node_modules/streamx": { + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.1.tgz", + "integrity": "sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==", "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" } }, - "providers/modal/node_modules/fsevents": { - "version": "2.3.3", - "dev": true, + "node_modules/string_decoder": { + "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==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "dependencies": { + "safe-buffer": "~5.2.0" } }, - "providers/modal/node_modules/lightningcss": { - "version": "1.33.0", - "dev": true, - "license": "MPL-2.0", + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { - "detect-libc": "^2.0.3" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" + "node": ">=8" } }, - "providers/modal/node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "engines": { + "node": ">=8" } }, - "providers/modal/node_modules/magic-string": { - "version": "0.30.21", - "dev": true, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "providers/modal/node_modules/nanoid": { - "version": "3.3.20", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "ansi-regex": "^5.0.1" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=8" } }, - "providers/modal/node_modules/obug": { - "version": "2.2.1", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">=12.20.0" + "node": ">=8" } }, - "providers/modal/node_modules/picocolors": { - "version": "1.1.1", - "dev": true, - "license": "ISC" - }, - "providers/modal/node_modules/picomatch": { - "version": "4.0.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "engines": { + "node": ">=18" } }, - "providers/modal/node_modules/postcss": { - "version": "8.5.30", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/tar-stream": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.2.tgz", + "integrity": "sha512-+8NeqHRjQWH9nYlwo2gamAMImZCVzI4UoEgDpWorBt9OEfppiZn+uSkskzQKPWIZyji/C8fpWO7u69G0DX0tbg==", "license": "MIT", "dependencies": { - "nanoid": "^3.3.20", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" + "b4a": "^1.9.0", + "bare-fs": "^4.8.2", + "fast-fifo": "^1.3.2", + "streamx": "^2.28.1" } }, - "providers/modal/node_modules/rolldown": { - "version": "1.2.13", - "dev": true, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.153.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.13", - "@rolldown/binding-android-arm64": "1.2.13", - "@rolldown/binding-darwin-arm64": "1.2.13", - "@rolldown/binding-darwin-x64": "1.2.13", - "@rolldown/binding-freebsd-x64": "1.2.13", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", - "@rolldown/binding-linux-arm64-gnu": "1.2.13", - "@rolldown/binding-linux-arm64-musl": "1.2.13", - "@rolldown/binding-linux-ppc64-gnu": "1.2.13", - "@rolldown/binding-linux-s390x-gnu": "1.2.13", - "@rolldown/binding-linux-x64-gnu": "1.2.13", - "@rolldown/binding-linux-x64-musl": "1.2.13", - "@rolldown/binding-openharmony-arm64": "1.2.13", - "@rolldown/binding-win32-arm64-msvc": "1.2.13", - "@rolldown/binding-win32-x64-msvc": "1.2.13" - } - }, - "providers/modal/node_modules/siginfo": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "providers/modal/node_modules/source-map-js": { - "version": "1.2.2", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "streamx": "^2.12.5" } }, - "providers/modal/node_modules/stackback": { - "version": "0.0.2", - "dev": true, - "license": "MIT" - }, - "providers/modal/node_modules/std-env": { - "version": "4.3.0", - "dev": true, - "license": "MIT" + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } }, - "providers/modal/node_modules/tinybench": { + "node_modules/tinybench": { "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, - "providers/modal/node_modules/tinyexec": { + "node_modules/tinyexec": { "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "dev": true, "license": "MIT", "engines": { "node": ">=18" } }, - "providers/modal/node_modules/tinyglobby": { + "node_modules/tinyglobby": { "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -3875,17 +5937,140 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "providers/modal/node_modules/tinyrainbow": { + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.2.0.tgz", + "integrity": "sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==", "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" } }, - "providers/modal/node_modules/typescript": { + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-error": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ts-error/-/ts-error-1.0.6.tgz", + "integrity": "sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typescript": { "version": "5.9.3", - "dev": true, + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -3895,20 +6080,60 @@ "node": ">=14.17" } }, - "providers/modal/node_modules/undici-types": { - "version": "7.16.0", - "dev": true, + "node_modules/undici-types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "providers/modal/node_modules/vite": { - "version": "8.3.4", + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "8.3.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.7.tgz", + "integrity": "sha512-oxh0dMxrayBr33ScJvvBJ2HBQ2jc1TBrXKR2NyL/MJ9WPuX19mN2DXWUmywKdyw5RFVQBo8hmtb2FMC0LHrnng==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", - "postcss": "^8.5.29", - "rolldown": "~1.2.12", + "postcss": "^8.5.32", + "rolldown": "~1.2.18", "tinyglobby": "^0.2.17" }, "bin": { @@ -3925,7 +6150,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.7.1", + "@vitejs/devtools": "^0.7.1 || ^0.8.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -3976,8 +6201,23 @@ } } }, - "providers/modal/node_modules/vitest": { + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "dev": true, "license": "MIT", "dependencies": { @@ -4064,727 +6304,453 @@ } } }, - "providers/modal/node_modules/why-is-node-running": { - "version": "2.3.0", + "node_modules/vitest/node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, "engines": { - "node": ">=8" - } - }, - "sandbox": { - "name": "@sandbox-kit/sandbox", - "version": "0.1.7", - "license": "MIT", - "dependencies": { - "@bufbuild/protobuf": "2.16.0", - "@bufbuild/protovalidate": "1.3.0", - "@sandbox-kit/contracts": "0.1.7" - }, - "devDependencies": { - "@types/node": "24.10.1", - "typescript": "5.9.3", - "vitest": "4.1.11" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.15.0" - } - }, - "sandbox/node_modules/@bufbuild/cel": { - "version": "0.6.1", - "license": "Apache-2.0", - "dependencies": { - "@bufbuild/cel-spec": "0.6.1", - "@bufbuild/re2": "0.6.1" - }, - "peerDependencies": { - "@bufbuild/protobuf": "^2.6.2" - } - }, - "sandbox/node_modules/@bufbuild/cel-spec": { - "version": "0.6.1", - "license": "Apache-2.0", - "peerDependencies": { - "@bufbuild/protobuf": "^2.6.2" - } - }, - "sandbox/node_modules/@bufbuild/protobuf": { - "version": "2.16.0", - "license": "(Apache-2.0 AND BSD-3-Clause)" - }, - "sandbox/node_modules/@bufbuild/protovalidate": { - "version": "1.3.0", - "license": "Apache-2.0", - "dependencies": { - "@bufbuild/cel": "0.6.1", - "@bufbuild/re2": "0.6.1" + "node": ">=12" }, - "peerDependencies": { - "@bufbuild/protobuf": "^2.8.0" - } - }, - "sandbox/node_modules/@bufbuild/re2": { - "version": "0.6.1", - "license": "MIT" - }, - "sandbox/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.6.0", - "dev": true, - "license": "MIT" - }, - "sandbox/node_modules/@oxc-project/types": { - "version": "0.153.0", - "dev": true, - "license": "MIT", "funding": { - "url": "https://github.com/sponsors/oxc-project" - } - }, - "sandbox/node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.13", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "sandbox/node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "dev": true, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.16.tgz", + "integrity": "sha512-qeNEuOi9a3qkqRf7Gfez2WL2VwlBTLCNekHBGO9kxPVUp9XuY15yfTJ9jwvOwKI0I2MhUUfZq95NSp+LK1CLQg==", "license": "MIT" }, - "sandbox/node_modules/@standard-schema/spec": { - "version": "1.1.0", - "dev": true, + "node_modules/vscode-languageserver-types": { + "version": "3.18.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.18.5.tgz", + "integrity": "sha512-kPd6+JQNz3HMJvjKlqUxERSlZHAntl/aGpsk7ImwIh0ewDKDcjgAEyVSYciPiOJD3ftbK9IrfiqUeTZLSbCizw==", "license": "MIT" }, - "sandbox/node_modules/@types/chai": { - "version": "5.2.3", - "dev": true, - "license": "MIT", + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "sandbox/node_modules/@types/deep-eql": { - "version": "4.0.2", - "dev": true, - "license": "MIT" - }, - "sandbox/node_modules/@types/estree": { - "version": "1.0.9", - "dev": true, - "license": "MIT" - }, - "sandbox/node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" } }, - "sandbox/node_modules/@vitest/expect": { - "version": "4.1.11", - "dev": true, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "sandbox/node_modules/@vitest/mocker": { - "version": "4.1.11", - "dev": true, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.11", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" }, "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { - "msw": { + "bufferutil": { "optional": true }, - "vite": { + "utf-8-validate": { "optional": true } } }, - "sandbox/node_modules/@vitest/pretty-format": { - "version": "4.1.11", - "dev": true, + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", "license": "MIT", + "peer": true, "dependencies": { - "tinyrainbow": "^3.1.0" + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/sindresorhus" } }, - "sandbox/node_modules/@vitest/runner": { - "version": "4.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.11", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" } }, - "sandbox/node_modules/@vitest/snapshot": { - "version": "4.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.11", - "@vitest/utils": "4.1.11", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" } }, - "sandbox/node_modules/@vitest/spy": { - "version": "4.1.11", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, - "sandbox/node_modules/@vitest/utils": { - "version": "4.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.11", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/eemeli" } }, - "sandbox/node_modules/assertion-error": { - "version": "2.0.1", - "dev": true, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, "engines": { "node": ">=12" } }, - "sandbox/node_modules/chai": { - "version": "6.3.0", - "dev": true, - "license": "MIT", + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", "engines": { - "node": ">=18" + "node": ">=12" } }, - "sandbox/node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "sandbox/node_modules/es-module-lexer": { - "version": "2.3.2", - "dev": true, - "license": "MIT" - }, - "sandbox/node_modules/estree-walker": { - "version": "3.0.3", - "dev": true, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "sandbox/node_modules/expect-type": { - "version": "1.4.0", - "dev": true, - "license": "Apache-2.0", + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, "engines": { - "node": ">=12.0.0" + "node": ">= 14" } }, - "sandbox/node_modules/fdir": { - "version": "6.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true + "node_modules/zip-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } - } - }, - "sandbox/node_modules/fsevents": { - "version": "2.3.3", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "sandbox/node_modules/lightningcss": { - "version": "1.33.0", - "dev": true, - "license": "MPL-2.0", + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "license": "MIT", "dependencies": { - "detect-libc": "^2.0.3" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "sandbox/node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, + "node_modules/zod": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", + "license": "MIT", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/colinhacks" } }, - "sandbox/node_modules/magic-string": { - "version": "0.30.21", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" } }, - "sandbox/node_modules/nanoid": { - "version": "3.3.20", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" } }, - "sandbox/node_modules/obug": { - "version": "2.2.1", + "providers/blaxel/node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], "license": "MIT", - "engines": { - "node": ">=12.20.0" + "dependencies": { + "undici-types": "~7.16.0" } }, - "sandbox/node_modules/picocolors": { - "version": "1.1.1", - "dev": true, - "license": "ISC" - }, - "sandbox/node_modules/picomatch": { - "version": "4.0.7", + "providers/blaxel/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } + "license": "MIT" }, - "sandbox/node_modules/postcss": { - "version": "8.5.30", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "providers/daytona": { + "name": "@sandbox-kit/daytona", + "version": "0.1.7", "license": "MIT", "dependencies": { - "nanoid": "^3.3.20", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.2" + "@bufbuild/protobuf": "2.16.0", + "@daytona/sdk": "0.225.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" } }, - "sandbox/node_modules/rolldown": { - "version": "1.2.13", + "providers/daytona/node_modules/@types/node": { + "version": "24.10.1", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.153.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.13", - "@rolldown/binding-android-arm64": "1.2.13", - "@rolldown/binding-darwin-arm64": "1.2.13", - "@rolldown/binding-darwin-x64": "1.2.13", - "@rolldown/binding-freebsd-x64": "1.2.13", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", - "@rolldown/binding-linux-arm64-gnu": "1.2.13", - "@rolldown/binding-linux-arm64-musl": "1.2.13", - "@rolldown/binding-linux-ppc64-gnu": "1.2.13", - "@rolldown/binding-linux-s390x-gnu": "1.2.13", - "@rolldown/binding-linux-x64-gnu": "1.2.13", - "@rolldown/binding-linux-x64-musl": "1.2.13", - "@rolldown/binding-openharmony-arm64": "1.2.13", - "@rolldown/binding-win32-arm64-msvc": "1.2.13", - "@rolldown/binding-win32-x64-msvc": "1.2.13" - } - }, - "sandbox/node_modules/siginfo": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "sandbox/node_modules/source-map-js": { - "version": "1.2.2", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "undici-types": "~7.16.0" } }, - "sandbox/node_modules/stackback": { - "version": "0.0.2", - "dev": true, - "license": "MIT" - }, - "sandbox/node_modules/std-env": { - "version": "4.3.0", - "dev": true, - "license": "MIT" - }, - "sandbox/node_modules/tinybench": { - "version": "2.9.0", + "providers/daytona/node_modules/undici-types": { + "version": "7.16.0", "dev": true, "license": "MIT" }, - "sandbox/node_modules/tinyexec": { - "version": "1.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "sandbox/node_modules/tinyglobby": { - "version": "0.2.17", - "dev": true, + "providers/modal": { + "name": "@sandbox-kit/modal", + "version": "0.1.7", "license": "MIT", "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7", + "modal": "0.11.1" }, - "engines": { - "node": ">=12.0.0" + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "sandbox/node_modules/tinyrainbow": { - "version": "3.2.0", - "dev": true, - "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" } }, - "sandbox/node_modules/typescript": { - "version": "5.9.3", + "providers/modal/node_modules/@types/node": { + "version": "24.10.1", "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" } }, - "sandbox/node_modules/undici-types": { + "providers/modal/node_modules/undici-types": { "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, - "sandbox/node_modules/vite": { - "version": "8.3.4", - "dev": true, + "sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", "license": "MIT", "dependencies": { - "lightningcss": "^1.33.0", - "picomatch": "^4.0.7", - "postcss": "^8.5.29", - "rolldown": "~1.2.12", - "tinyglobby": "^0.2.17" + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" }, - "bin": { - "vite": "bin/vite.js" + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" }, "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "sandbox/node_modules/@bufbuild/cel": { + "version": "0.6.1", + "license": "Apache-2.0", + "dependencies": { + "@bufbuild/cel-spec": "0.6.1", + "@bufbuild/re2": "0.6.1" }, "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.7.1", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } + "@bufbuild/protobuf": "^2.6.2" } }, - "sandbox/node_modules/vitest": { - "version": "4.1.11", - "dev": true, - "license": "MIT", + "sandbox/node_modules/@bufbuild/cel-spec": { + "version": "0.6.1", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^2.6.2" + } + }, + "sandbox/node_modules/@bufbuild/protovalidate": { + "version": "1.3.0", + "license": "Apache-2.0", "dependencies": { - "@vitest/expect": "4.1.11", - "@vitest/mocker": "4.1.11", - "@vitest/pretty-format": "4.1.11", - "@vitest/runner": "4.1.11", - "@vitest/snapshot": "4.1.11", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "@bufbuild/cel": "0.6.1", + "@bufbuild/re2": "0.6.1" }, "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.11", - "@vitest/browser-preview": "4.1.11", - "@vitest/browser-webdriverio": "4.1.11", - "@vitest/coverage-istanbul": "4.1.11", - "@vitest/coverage-v8": "4.1.11", - "@vitest/ui": "4.1.11", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } + "@bufbuild/protobuf": "^2.8.0" } }, - "sandbox/node_modules/why-is-node-running": { - "version": "2.3.0", + "sandbox/node_modules/@bufbuild/re2": { + "version": "0.6.1", + "license": "MIT" + }, + "sandbox/node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" + "undici-types": "~7.16.0" } + }, + "sandbox/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" } } } diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json index 4395d56e..ccd67e28 100644 --- a/sdks/typescript/package.json +++ b/sdks/typescript/package.json @@ -5,6 +5,7 @@ "workspaces": [ "contracts", "sandbox", + "providers/blaxel", "providers/daytona", "providers/modal" ] diff --git a/sdks/typescript/providers/blaxel/.gitignore b/sdks/typescript/providers/blaxel/.gitignore new file mode 100644 index 00000000..1eae0cf6 --- /dev/null +++ b/sdks/typescript/providers/blaxel/.gitignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ diff --git a/sdks/typescript/providers/blaxel/.nvmrc b/sdks/typescript/providers/blaxel/.nvmrc new file mode 100644 index 00000000..df6ae337 --- /dev/null +++ b/sdks/typescript/providers/blaxel/.nvmrc @@ -0,0 +1 @@ +24.21.0 diff --git a/sdks/typescript/providers/blaxel/LICENSE b/sdks/typescript/providers/blaxel/LICENSE new file mode 100644 index 00000000..d843376c --- /dev/null +++ b/sdks/typescript/providers/blaxel/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 sandbox-kit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md new file mode 100644 index 00000000..d6a33cb9 --- /dev/null +++ b/sdks/typescript/providers/blaxel/README.md @@ -0,0 +1,87 @@ +# `@sandbox-kit/blaxel` + +Blaxel provider for [`@sandbox-kit/sandbox`](https://www.npmjs.com/package/@sandbox-kit/sandbox). +Use the shared client interface; this package supplies the `blaxel()` provider. + +## Install + +```sh +npm install @sandbox-kit/sandbox @sandbox-kit/blaxel +``` + +Requires Node.js 20, 22, or 24.15+, a Blaxel API key, and a workspace name. + +## Quick start + +```ts +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region: process.env.BL_REGION ?? "us-pdx-1", + timeout: 120_000, +}); + +const sandbox = await client.create({ + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 4096n }, + lifetime: { maximumLifetime: 24 * 60 * 60_000 }, +}); +console.log(sandbox.id); +``` + +Closing the client releases local SDK resources only. Reclaim sandboxes with +TTL / idle policies or `delete`. + +## Configuration + +| Setting | Notes | +| -------------- | ------------------------------------------------------------- | +| API key | `auth: { apiKey: { key } }` | +| Omitted `auth` | Blaxel SDK environment / CLI credentials | +| Workspace | `scope.workspace` — Blaxel workspace name | +| Region | `region` — default create placement | +| Endpoint | Not supported | + +Token-pair, bearer, and OAuth client modes are rejected. Scope fields other +than `workspace` are rejected. + +## What you can do + +**Create** requires a nonblank `name` and an image source. Optional memory +(MiB; Blaxel CPU follows memory), labels, environment variables, ports, outbound +domains, max-age TTL, idle-delete policy, and a create timeout (1–50 whole +seconds). Creation calls native `SandboxInstance.create`, not +`createIfNotExists`. + +**Lifecycle** on the handle: `get` / `refresh` (lookup by name), `delete`, and +`close`. `close` does not delete the sandbox. Start, stop, pause, archive, +terminate, and attach are unsupported. + +**Files** through `sandbox.filesystem`: `readFile`, `writeFile`, +`copyFromLocal`, `copyToLocal`, `makeDirectory`, `listDirectory`, `stat`, and +`remove`. Paths in the sandbox are absolute. `parents` defaults to true; +`parents: false` is rejected. A missing file passed to `copyFromLocal` is not +found before Blaxel is called, and the error field is `local_path`. A local +directory is an invalid argument with the same field. `copyToLocal` creates a +missing local parent directory. + +**Commands** through `sandbox.process.run`. Set exactly one of `argv` or +`shell`. `argv` is sent as a POSIX-quoted shell string. Runs are blocking +(`waitForCompletion: true`). A non-zero exit code is not an SDK error—check +`exitCode`. Blaxel returns stdout and stderr. Omit or zero `timeout` for no +command limit. Command timeouts must be whole seconds (`2000` is two seconds; +`1500` is rejected). Aborting the signal rejects the `await`; the remote +command may keep running. + +When you set memory, Blaxel requires at least 256 MiB. Omitted memory keeps +the Blaxel default. CPU, CPU limit, memory limit, and disk overrides are +rejected. + +## License + +MIT diff --git a/sdks/typescript/providers/blaxel/package.json b/sdks/typescript/providers/blaxel/package.json new file mode 100644 index 00000000..21197e73 --- /dev/null +++ b/sdks/typescript/providers/blaxel/package.json @@ -0,0 +1,77 @@ +{ + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "description": "blaxel provider for sandbox kit", + "keywords": [ + "sandbox-kit", + "sandbox", + "blaxel", + "sdk", + "provider" + ], + "license": "MIT", + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/sandbox-kit/kit.git", + "directory": "sdks/typescript/providers/blaxel" + }, + "homepage": "https://github.com/sandbox-kit/kit/tree/main/sdks/typescript/providers/blaxel", + "bugs": { + "url": "https://github.com/sandbox-kit/kit/issues" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "imports": { + "#mappings/*.js": { + "@sandbox-kit/source": "./src/mappings/*.ts", + "default": "./dist/mappings/*.js" + }, + "#checks/*.js": { + "@sandbox-kit/source": "./src/checks/*.ts", + "default": "./dist/checks/*.js" + }, + "#config/*.js": { + "@sandbox-kit/source": "./src/config/*.ts", + "default": "./dist/config/*.js" + }, + "#errors.js": { + "@sandbox-kit/source": "./src/errors.ts", + "default": "./dist/errors.js" + } + }, + "scripts": { + "clean": "rm -rf dist", + "build": "tsc -p tsconfig.build.json", + "prepublishOnly": "npm run clean && npm run build", + "test": "tsc -p tsconfig.test.json && vitest run" + }, + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + } +} diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts new file mode 100644 index 00000000..d2a3e1a4 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -0,0 +1,499 @@ +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { + Client, + ErrorKind, + PolicyMode, + PortTransport, + WaitCondition, + ConfigFieldAuthApiKeyKey, + ConfigFieldEndpoint, + CreateFieldEnvironment, + CreateFieldLifetimeMaximumLifetime, + CreateFieldName, + CreateFieldResourcesMemoryMib, + FileFieldLocalPath, +} from "@sandbox-kit/sandbox"; +import { mapClientConfig, mapCreateRequest } from "#mappings/mappings.js"; +import { BlaxelProvider } from "./blaxel.js"; +import { mapResources } from "#mappings/provider_mappings.js"; +import { captureProviderState } from "#config/provider_config.js"; +import { mapBlaxelError } from "#errors.js"; +import { ResponseError } from "@blaxel/core"; + +describe("Blaxel mappings", () => { + it("maps API key authentication and workspace scope", () => { + expect( + mapClientConfig({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "my-workspace" }, + }), + ).toEqual({ + apiKey: "bl_key", + workspace: "my-workspace", + }); + }); + + it("rejects unsupported client endpoint before SDK construction", () => { + expect(() => + mapClientConfig({ endpoint: "https://unsupported.example" }), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.Unsupported, + field: ConfigFieldEndpoint, + operation: "initialize", + }), + ); + }); + + it("requires name and image on create", () => { + const config = { scope: { workspace: "ws" } }; + expect(() => + mapCreateRequest( + { source: { image: { reference: "blaxel/base-image:latest" } } }, + config, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldName, + }), + ); + }); + + it("rejects blank auth credentials in Client construction", () => { + expect( + () => + new Client({ + provider: new BlaxelProvider(() => { + throw new Error("native SDK must not construct"); + }), + auth: { apiKey: { key: "" } }, + scope: { workspace: "ws" }, + }), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: ConfigFieldAuthApiKeyKey, + operation: "initialize", + }), + ); + }); + + it("captures retained configuration without aliasing caller state", () => { + const config = { + scope: { workspace: "original" }, + region: "us-pdx-1", + }; + const state = captureProviderState(config); + config.scope.workspace = "changed"; + + expect(state).toMatchObject({ + scope: { workspace: "original" }, + region: "us-pdx-1", + }); + }); + + it("maps the creation surface into the native Blaxel plan", () => { + const plan = mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 4096n }, + network: { + outboundDomains: ["example.com"], + ports: [{ port: 3000, transport: PortTransport.Http2Tls }], + }, + lifetime: { + maximumLifetime: 24 * 60 * 60_000, + idleTerminate: { + mode: PolicyMode.After, + after: 30 * 60_000, + }, + }, + provisioning: { + timeout: 30_000, + waitFor: WaitCondition.Ready, + }, + labels: { purpose: "test" }, + environment: { NODE_ENV: "test" }, + }, + { scope: { workspace: "ws" }, region: "us-pdx-1" }, + ); + expect(plan).toMatchObject({ + waitUntilReady: true, + readyTimeoutMs: 30_000, + options: { timeout: 30 }, + params: { + name: "example", + image: "blaxel/base-image:latest", + memory: 4096, + region: "us-pdx-1", + ttl: "1d", + labels: { purpose: "test" }, + envs: [{ name: "NODE_ENV", value: "test" }], + ports: [{ target: 3000, protocol: "HTTP" }], + network: { allowedDomains: ["example.com"] }, + lifecycle: { + expirationPolicies: [ + { type: "ttl-idle", action: "delete", value: "30m" }, + ], + }, + }, + }); + }); + + it("rejects memory below the Blaxel floor", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 128n }, + }, + { scope: { workspace: "ws" } }, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldResourcesMemoryMib, + }), + ); + expect(mapResources({ memoryMib: 4096n })).toEqual({ memory: 4096 }); + }); + + it("rejects lifetime durations that are not whole seconds", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + lifetime: { maximumLifetime: 1_500 }, + }, + { scope: { workspace: "ws" } }, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldLifetimeMaximumLifetime, + }), + ); + }); + + it("classifies native ResponseError status codes", () => { + const native = new ResponseError( + new Response(null, { status: 404, statusText: "Not Found" }), + { error: { code: "WORKLOAD_NOT_FOUND", message: "missing" } }, + new Error("missing"), + ); + expect(mapBlaxelError(native, "refresh")).toMatchObject({ + kind: ErrorKind.NotFound, + provider: "blaxel", + operation: "refresh", + cause: native, + }); + }); + + it("applies environment checks before mapping", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + environment: { "NOT VALID": "value" }, + }, + { scope: { workspace: "ws" } }, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldEnvironment, + }), + ); + }); +}); + +describe("Blaxel provider orchestration", () => { + it("creates with SandboxInstance.create and never createIfNotExists", async () => { + let created: + | { params: unknown; options: unknown } + | undefined; + let configured: unknown; + const nativeSandbox = { + metadata: { name: "example" }, + status: "RUNNING", + spec: { region: "us-pdx-1" }, + fs: {} as never, + process: {} as never, + async wait() { + return this; + }, + async delete() {}, + }; + const client = new Client({ + provider: new BlaxelProvider(() => ({ + configure(config) { + configured = config; + }, + async create(params, options) { + created = { params, options }; + return nativeSandbox; + }, + async get() { + throw new Error("unexpected get"); + }, + async delete() { + throw new Error("unexpected delete"); + }, + })), + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + region: "us-pdx-1", + }); + + const sandbox = await client.create({ + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 2048n }, + }); + await client.close(); + + expect(configured).toEqual({ apiKey: "bl_key", workspace: "ws" }); + expect(created?.params).toMatchObject({ + name: "example", + image: "blaxel/base-image:latest", + memory: 2048, + region: "us-pdx-1", + }); + expect(created?.options).toBeUndefined(); + expect(sandbox.id).toBe("example"); + expect(sandbox.info().name).toBe("example"); + }); + + it("looks up sandboxes by name for get/refresh", async () => { + let lookedUp: string | undefined; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get(name) { + lookedUp = name; + return { + metadata: { name }, + status: "RUNNING", + fs: {} as never, + process: {} as never, + async wait() { + return this; + }, + async delete() {}, + }; + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + if (backend.refresh === undefined) throw new Error("refresh is missing"); + const info = await backend.refresh("existing", {}); + expect(lookedUp).toBe("existing"); + expect(info).toMatchObject({ id: "existing", provider: "blaxel" }); + }); + + it("deletes by name after a provider-verified lookup", async () => { + const deleted: string[] = []; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get(name) { + return { + metadata: { name }, + status: "RUNNING", + fs: {} as never, + process: {} as never, + async wait() { + return this; + }, + async delete() { + deleted.push(name); + }, + }; + }, + async delete(name) { + deleted.push(`static:${name}`); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + if (backend.delete === undefined) throw new Error("delete is missing"); + await backend.delete("doomed", {}, {}); + expect(deleted).toEqual(["doomed"]); + }); + + it("does not create a sandbox after the signal aborts during mapping", async () => { + const controller = new AbortController(); + let created = false; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + created = true; + throw new Error("native create"); + }, + async get() { + throw new Error("unexpected get"); + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + controller.abort(); + if (backend.create === undefined) throw new Error("create is missing"); + await expect( + backend.create( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + }, + { signal: controller.signal }, + ), + ).rejects.toMatchObject({ + kind: ErrorKind.Canceled, + operation: "create", + }); + expect(created).toBe(false); + }); + + it("rejects a missing or directory local file before Blaxel is called", async () => { + let lookedUp = false; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + lookedUp = true; + throw new Error("native get must not run"); + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + const copyFromLocal = backend.copyFromLocal; + const copyToLocal = backend.copyToLocal; + if (!copyFromLocal || !copyToLocal) + throw new Error("copy methods are missing"); + await expect( + copyFromLocal.call( + backend, + "sandbox-1", + join(tmpdir(), "kit-missing-source.txt"), + "/tmp/note.txt", + {}, + {}, + ), + ).rejects.toMatchObject({ + kind: ErrorKind.NotFound, + field: FileFieldLocalPath, + operation: "copy_from_local", + }); + await expect( + copyFromLocal.call( + backend, + "sandbox-1", + tmpdir(), + "/tmp/note.txt", + {}, + {}, + ), + ).rejects.toMatchObject({ + kind: ErrorKind.InvalidArgument, + field: FileFieldLocalPath, + operation: "copy_from_local", + }); + await expect( + copyToLocal.call(backend, "sandbox-1", "/tmp/note.txt", tmpdir(), {}, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.InvalidArgument, + field: FileFieldLocalPath, + operation: "copy_to_local", + }); + expect(lookedUp).toBe(false); + }); + + it("runs commands with waitForCompletion and second-based timeouts", async () => { + const seen: Array<{ + command: string; + waitForCompletion?: boolean; + timeout?: number; + }> = []; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + return { + metadata: { name: "sandbox-1" }, + fs: {} as never, + process: { + async exec(request) { + const entry: (typeof seen)[number] = { + command: request.command, + }; + if (request.waitForCompletion !== undefined) + entry.waitForCompletion = request.waitForCompletion; + if (request.timeout !== undefined) entry.timeout = request.timeout; + seen.push(entry); + return { exitCode: 0, stdout: "ok", stderr: "" }; + }, + }, + async wait() { + return this; + }, + async delete() {}, + }; + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + if (backend.run === undefined) throw new Error("run is missing"); + + await backend.run("sandbox-1", { argv: ["echo", "hi"] }, {}); + await backend.run("sandbox-1", { argv: ["echo", "hi"], timeout: 0 }, {}); + await backend.run( + "sandbox-1", + { argv: ["echo", "hi"], timeout: 5_000 }, + {}, + ); + await expect( + backend.run("sandbox-1", { argv: ["echo", "hi"], timeout: 1_500 }, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.InvalidArgument, + field: "timeout", + operation: "run", + }); + + expect(seen).toEqual([ + { command: "'echo' 'hi'", waitForCompletion: true, timeout: 0 }, + { command: "'echo' 'hi'", waitForCompletion: true, timeout: 0 }, + { command: "'echo' 'hi'", waitForCompletion: true, timeout: 5 }, + ]); + }); +}); diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts new file mode 100644 index 00000000..bb878a03 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -0,0 +1,445 @@ +import { basename, dirname } from "node:path"; +import { + SandboxInstance, + settings, + type Config as NativeClientConfig, + type Directory, + type File as BlaxelFile, + type SandboxCreateConfiguration, + type SandboxCreateOptions, +} from "@blaxel/core"; + +type BlaxelSubdirectory = Directory["subdirectories"][number]; +import { + ErrorKind, + ExecFieldTimeout, + FileFieldParents, + FileType, + type FileInfo, + type OperationOptions, + type Provider, + type SandboxInfo, +} from "@sandbox-kit/sandbox"; +import type { + Backend, + Config, + CreateOptions, + CreateResult, + DeleteOptions, + ExecOptions, + ExecResult, + FileCopyOptions, + FileReadOptions, + FileWriteOptions, + ListDirectoryOptions, + MakeDirectoryOptions, + RemoveOptions, + StatOptions, +} from "@sandbox-kit/sandbox"; +import { + mapClientConfig, + mapCreateRequest, + mapSandboxInfo, +} from "#mappings/mappings.js"; +import { + ensureLocalParent, + mapBlaxelError, + providerError, + readLocalFile, + requireLocalDestination, +} from "#errors.js"; +import { BackendBase, ProviderBase } from "./provider.js"; +import { + captureProviderState, + type ProviderState, +} from "#config/provider_config.js"; + +interface NativeSandboxHandle { + readonly metadata?: { + readonly name?: string; + readonly labels?: Readonly>; + }; + readonly status?: unknown; + readonly spec?: { readonly region?: string }; + readonly fs: { + readBinary(path: string): Promise; + writeBinary(path: string, content: Uint8Array): Promise; + mkdir(path: string, permissions?: string): Promise; + ls(path: string): Promise; + rm(path: string, recursive?: boolean): Promise; + download(src: string, destinationPath: string): Promise; + }; + readonly process: { + exec(request: { + command: string; + waitForCompletion?: boolean; + timeout?: number; + workingDir?: string; + env?: Record; + }): Promise<{ + exitCode?: number; + stdout?: string; + stderr?: string; + }>; + }; + wait(options?: { + maxWait?: number; + interval?: number; + }): Promise; + delete(): Promise; +} + +interface NativeBlaxel { + configure(config: NativeClientConfig): void; + create( + params: SandboxCreateConfiguration, + options?: SandboxCreateOptions, + ): Promise; + get(name: string): Promise; + delete(name: string): Promise; +} + +type NativeFactory = (config: NativeClientConfig) => NativeBlaxel; + +/** @internal Exported for adapter boundary tests, but not from the package root. */ +export class BlaxelProvider extends ProviderBase { + constructor( + private readonly nativeFactory: NativeFactory = (config) => { + settings.setConfig(config); + return { + configure(next: NativeClientConfig): void { + settings.setConfig(next); + }, + create(params, options) { + return SandboxInstance.create(params, options); + }, + get(name) { + return SandboxInstance.get(name); + }, + delete(name) { + return SandboxInstance.delete(name); + }, + }; + }, + ) { + super(); + } + + initialize(config: Config): Backend { + try { + const nativeConfig = mapClientConfig(config); + const native = this.nativeFactory(nativeConfig); + native.configure(nativeConfig); + return new BlaxelBackend(native, captureProviderState(config)); + } catch (error) { + throw mapBlaxelError(error, "initialize"); + } + } +} + +class BlaxelBackend extends BackendBase implements Backend { + constructor( + private readonly native: NativeBlaxel, + private readonly config: ProviderState, + ) { + super(); + } + + async create( + request: CreateOptions, + options: OperationOptions, + ): Promise { + try { + throwIfAborted(options.signal); + const plan = mapCreateRequest(request, this.config); + throwIfAborted(options.signal); + const sandbox = await this.native.create(plan.params, plan.options); + if (plan.waitUntilReady) { + throwIfAborted(options.signal); + await sandbox.wait({ + ...(plan.readyTimeoutMs === undefined + ? {} + : { maxWait: plan.readyTimeoutMs }), + }); + } + return { sandbox: mapSandboxInfo(sandbox, request) }; + } catch (error) { + throw mapBlaxelError(error, "create"); + } + } + + async refresh(id: string, options: OperationOptions): Promise { + try { + throwIfAborted(options.signal); + const sandbox = await this.native.get(id); + throwIfAborted(options.signal); + return mapSandboxInfo(sandbox); + } catch (error) { + throw mapBlaxelError(error, "refresh"); + } + } + + async delete( + id: string, + _request: DeleteOptions, + options: OperationOptions, + ): Promise { + try { + throwIfAborted(options.signal); + const sandbox = await this.native.get(id); + throwIfAborted(options.signal); + const info = mapSandboxInfo(sandbox); + await sandbox.delete(); + return info; + } catch (error) { + throw mapBlaxelError(error, "delete"); + } + } + + async readFile( + id: string, + path: string, + _request: FileReadOptions, + options: OperationOptions, + ): Promise { + return this.#withSandbox("read_file", id, options, async (sandbox) => { + const blob = await sandbox.fs.readBinary(path); + return new Uint8Array(await blob.arrayBuffer()); + }); + } + + async writeFile( + id: string, + path: string, + data: Uint8Array, + _request: FileWriteOptions, + options: OperationOptions, + ): Promise { + await this.#withSandbox("write_file", id, options, async (sandbox) => { + await sandbox.fs.writeBinary(path, data); + }); + } + + async copyFromLocal( + id: string, + localPath: string, + remotePath: string, + _request: FileCopyOptions, + options: OperationOptions, + ): Promise { + const data = await readLocalFile(localPath, "copy_from_local"); + await this.#withSandbox("copy_from_local", id, options, async (sandbox) => { + await sandbox.fs.writeBinary(remotePath, data); + }); + } + + async copyToLocal( + id: string, + remotePath: string, + localPath: string, + _request: FileCopyOptions, + options: OperationOptions, + ): Promise { + await requireLocalDestination(localPath, "copy_to_local"); + await ensureLocalParent(localPath, "copy_to_local"); + await this.#withSandbox("copy_to_local", id, options, async (sandbox) => { + await sandbox.fs.download(remotePath, localPath); + }); + } + + async makeDirectory( + id: string, + path: string, + request: MakeDirectoryOptions, + options: OperationOptions, + ): Promise { + if (request.parents === false) { + providerError({ + kind: ErrorKind.Unsupported, + operation: "make_directory", + field: FileFieldParents, + message: "parents false is not available", + }); + } + await this.#withSandbox("make_directory", id, options, async (sandbox) => { + await sandbox.fs.mkdir(path); + }); + } + + async listDirectory( + id: string, + path: string, + _request: ListDirectoryOptions, + options: OperationOptions, + ): Promise { + return this.#withSandbox("list_directory", id, options, async (sandbox) => { + const listing = await sandbox.fs.ls(path); + return [ + ...listing.subdirectories.map((entry) => directoryInfo(entry)), + ...listing.files.map((entry) => fileInfo(entry)), + ]; + }); + } + + async stat( + id: string, + path: string, + _request: StatOptions, + options: OperationOptions, + ): Promise { + return this.#withSandbox("stat", id, options, async (sandbox) => { + const parent = dirname(path); + const name = basename(path); + if (name === "" || name === "/" || path === "/") { + const listing = await sandbox.fs.ls(path); + return { + name: listing.name || "/", + path: listing.path || path, + type: FileType.Directory, + size: 0n, + }; + } + try { + const listing = await sandbox.fs.ls(parent === "." ? "/" : parent); + const file = listing.files.find((entry) => entry.name === name); + if (file) return fileInfo(file); + const directory = listing.subdirectories.find( + (entry) => entry.name === name, + ); + if (directory) return directoryInfo(directory); + } catch { + // Fall through to treating the path as a directory. + } + const self = await sandbox.fs.ls(path); + return { + name: self.name || name, + path: self.path || path, + type: FileType.Directory, + size: 0n, + }; + }); + } + + async remove( + id: string, + path: string, + request: RemoveOptions, + options: OperationOptions, + ): Promise { + await this.#withSandbox("remove", id, options, async (sandbox) => { + await sandbox.fs.rm(path, request.recursive === true); + }); + } + + async run( + id: string, + request: ExecOptions, + options: OperationOptions, + ): Promise { + return this.#withSandbox("run", id, options, async (sandbox) => { + const timeout = execTimeoutSeconds(request.timeout); + const result = await sandbox.process.exec({ + command: blaxelCommand(request), + waitForCompletion: true, + // Blaxel: omit/0 means no auto-kill. Kit omit/0 map to 0. + timeout: timeout ?? 0, + ...(request.workingDirectory === undefined + ? {} + : { workingDir: request.workingDirectory }), + ...(request.environment === undefined + ? {} + : { env: { ...request.environment } }), + }); + return { + exitCode: result.exitCode ?? 0, + stdout: new TextEncoder().encode(result.stdout ?? ""), + stderr: new TextEncoder().encode(result.stderr ?? ""), + }; + }); + } + + async #withSandbox( + operation: string, + id: string, + options: OperationOptions, + body: (sandbox: NativeSandboxHandle) => Promise, + ): Promise { + try { + throwIfAborted(options.signal); + const sandbox = await this.native.get(id); + throwIfAborted(options.signal); + return await body(sandbox); + } catch (error) { + throw mapBlaxelError(error, operation); + } + } + + async closeSandbox(_id: string, _options: OperationOptions): Promise {} + + async close(options: OperationOptions): Promise { + throwIfAborted(options.signal); + } +} + +function fileInfo(info: BlaxelFile): FileInfo { + const modified = Date.parse(info.lastModified); + return { + name: info.name, + path: info.path, + type: FileType.File, + size: BigInt(Math.trunc(info.size)), + ...(info.permissions ? { permissions: info.permissions } : {}), + ...(info.owner ? { owner: info.owner } : {}), + ...(info.group ? { group: info.group } : {}), + ...(Number.isNaN(modified) ? {} : { modifiedAt: new Date(modified) }), + }; +} + +function directoryInfo(info: BlaxelSubdirectory): FileInfo { + return { + name: info.name, + path: info.path, + type: FileType.Directory, + size: 0n, + }; +} + +function throwIfAborted(signal: AbortSignal | undefined): void { + signal?.throwIfAborted(); +} + +function blaxelCommand(request: ExecOptions): string { + if (request.shell !== undefined) return request.shell; + return posixShellJoin(request.argv ?? []); +} + +function posixShellJoin(argv: readonly string[]): string { + return argv.map(posixQuote).join(" "); +} + +function posixQuote(value: string): string { + if (value === "") return "''"; + return "'" + value.replaceAll("'", `'"'"'`) + "'"; +} + +/** + * Maps Kit ExecOptions.timeout (ms) to Blaxel's process timeout (seconds). + * Omitted and explicit zero become undefined so the caller sends 0 (no limit). + */ +function execTimeoutSeconds(timeout: number | undefined): number | undefined { + if (timeout === undefined || timeout === 0) return undefined; + if (!Number.isFinite(timeout) || timeout < 0 || timeout % 1_000 !== 0) { + providerError({ + kind: ErrorKind.InvalidArgument, + operation: "run", + field: ExecFieldTimeout, + message: "timeout must be a whole number of seconds", + }); + } + return timeout / 1_000; +} + +/** Selects Blaxel as the provider for the shared Sandbox Kit client. */ +export function blaxel(): Provider { + return new BlaxelProvider(); +} diff --git a/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts b/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts new file mode 100644 index 00000000..de2c3f86 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts @@ -0,0 +1,32 @@ +import { + ErrorKind, + CreateFieldEnvironment, + CreateFieldResourcesMemoryMib, + validEnvName, +} from "@sandbox-kit/sandbox"; +import type { CreateOptions } from "@sandbox-kit/sandbox"; +import { providerError } from "#errors.js"; + +// validateProviderCreate rejects creation settings this provider cannot honor. +// Rules are inventoried in specs/providers/blaxel.yaml checks when present. +export function validateProviderCreate(request: CreateOptions): void { + const memoryMib = request.resources?.memoryMib; + if (memoryMib !== undefined && memoryMib < BigInt("256")) { + providerError({ + kind: ErrorKind.InvalidArgument, + operation: "create", + field: CreateFieldResourcesMemoryMib, + message: "resources.memory_mib is outside supported values", + }); + } + for (const value of Object.keys(request.environment ?? {})) { + if (!validEnvName(value)) { + providerError({ + kind: ErrorKind.InvalidArgument, + operation: "create", + field: CreateFieldEnvironment, + message: "environment is outside supported values", + }); + } + } +} diff --git a/sdks/typescript/providers/blaxel/src/config/provider_config.ts b/sdks/typescript/providers/blaxel/src/config/provider_config.ts new file mode 100644 index 00000000..31557b73 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/config/provider_config.ts @@ -0,0 +1,85 @@ +// Client configuration composition for the native Blaxel settings object. +// Kept in sync with specs/providers/blaxel.yaml client: rules when present. + +import type { Config as NativeClientConfig } from "@blaxel/core"; +import { + ErrorKind, + type Config, + type Scope, + ConfigFieldAuth, + ConfigFieldEndpoint, + ConfigFieldScopeAppName, + ConfigFieldScopeEnvironment, + ConfigFieldScopeOrganizationId, + ConfigFieldScopeProjectId, +} from "@sandbox-kit/sandbox"; +import { + mapAPIKeyCredentials, + mapClientScope, +} from "#mappings/provider_mappings.js"; +import { providerError } from "#errors.js"; + +export function mapGeneratedClientConfig(config: Config): NativeClientConfig { + const source = config; + const target: NativeClientConfig = {}; + if (source.endpoint !== undefined) + unsupported( + ConfigFieldEndpoint, + "Endpoint cannot be configured through this SDK's public client constructor", + ); + if (source.scope !== undefined) { + Object.assign(target, mapClientScope(source.scope, "initialize")); + if (source.scope.appName !== undefined) + unsupported( + ConfigFieldScopeAppName, + "unsupported client configuration field", + ); + if (source.scope.environment !== undefined) + unsupported( + ConfigFieldScopeEnvironment, + "unsupported client configuration field", + ); + if (source.scope.organizationId !== undefined) + unsupported( + ConfigFieldScopeOrganizationId, + "unsupported client configuration field", + ); + if (source.scope.projectId !== undefined) + unsupported( + ConfigFieldScopeProjectId, + "unsupported client configuration field", + ); + } + if (source.auth?.apiKey !== undefined) { + Object.assign( + target, + mapAPIKeyCredentials(source.auth.apiKey, "initialize"), + ); + } else if (source.auth !== undefined) { + unsupported(ConfigFieldAuth, "unsupported authentication mode"); + } + return target; +} + +export interface ProviderState { + readonly scope?: Scope; + readonly region?: string; +} + +export function captureProviderState(config: Config): ProviderState { + return { + ...(config.scope === undefined + ? {} + : { scope: structuredClone(config.scope) }), + ...(config.region === undefined ? {} : { region: config.region }), + }; +} + +function unsupported(field: string, message: string): never { + providerError({ + kind: ErrorKind.Unsupported, + operation: "initialize", + field, + message, + }); +} diff --git a/sdks/typescript/providers/blaxel/src/errors.ts b/sdks/typescript/providers/blaxel/src/errors.ts new file mode 100644 index 00000000..5fb98b6e --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/errors.ts @@ -0,0 +1,342 @@ +// Native Blaxel error translation is handwritten. +// Classification order: existing SandboxError, abort/timeout DOMException, +// then Blaxel helpers / ResponseError status, then unknown. +// Cause stays on SandboxError.cause. + +import { mkdir, readFile, stat, writeFile } from "node:fs/promises"; +import { dirname } from "node:path"; + +import { + CredentialsError, + getBlaxelErrorCode, + getBlaxelErrorStatus, + isCreationTimeoutError, + isGatewayError, + ResponseError, + SandboxCreationTimeoutError, + SandboxGatewayError, +} from "@blaxel/core"; +import { + ErrorKind, + FileFieldLocalPath, + SandboxError, + sandboxError, + type ErrorInfo, +} from "@sandbox-kit/sandbox"; + +/** Inputs for translating a native/provider failure into {@link SandboxError}. */ +interface MappedErrorInput { + kind: ErrorInfo["kind"]; + error: unknown; + operation: string; + statusCode?: number; + providerCode?: string; +} + +/** Inputs for throwing a Blaxel-scoped validation or mapping error. */ +export interface ProviderErrorInput { + kind: ErrorInfo["kind"]; + operation: string; + field: string; + message: string; +} + +export function mapBlaxelError(error: unknown, operation: string): SandboxError { + if (error instanceof SandboxError) return error; + if (error instanceof DOMException && error.name === "AbortError") + return create({ kind: ErrorKind.Canceled, error, operation }); + if (error instanceof DOMException && error.name === "TimeoutError") + return create({ kind: ErrorKind.Timeout, error, operation }); + + let kind: ErrorInfo["kind"] = ErrorKind.Unknown; + const statusCode = getBlaxelErrorStatus(error); + const providerCode = stringifyCode(getBlaxelErrorCode(error)); + + if (error instanceof CredentialsError) kind = ErrorKind.Authentication; + if ( + kind === ErrorKind.Unknown && + (error instanceof SandboxCreationTimeoutError || isCreationTimeoutError(error)) + ) + kind = ErrorKind.Timeout; + if ( + kind === ErrorKind.Unknown && + (error instanceof SandboxGatewayError || isGatewayError(error)) + ) + kind = ErrorKind.Unavailable; + + if (kind === ErrorKind.Unknown && providerCode !== undefined) + kind = kindFromCode(providerCode); + + if (kind === ErrorKind.Unknown && statusCode !== undefined) + kind = kindFromStatus(statusCode); + + if ( + kind === ErrorKind.Unknown && + error instanceof ResponseError && + error.status !== undefined + ) + kind = kindFromStatus(error.status); + + return create({ + kind, + error, + operation, + ...(statusCode === undefined ? {} : { statusCode }), + ...(providerCode === undefined ? {} : { providerCode }), + }); +} + +function kindFromCode(code: string): ErrorInfo["kind"] { + switch (code) { + case "AUTHENTICATION_REQUIRED": + case "AUTHENTICATION_FAILED": + case "TOKEN_REVOKED": + return ErrorKind.Authentication; + case "FORBIDDEN": + case "UNAUTHORIZED": + case "UNAUTHORIZED_WORKSPACE": + return ErrorKind.PermissionDenied; + case "ROUTE_NOT_FOUND": + case "WORKLOAD_NOT_FOUND": + case "WORKSPACE_NOT_FOUND": + case "IMAGE_NOT_FOUND": + case "VOLUME_NOT_FOUND": + case "ACCOUNT_NOT_FOUND": + case "SANDBOX_DELETED": + case "VOLUME_DELETED": + return ErrorKind.NotFound; + case "SANDBOX_ALREADY_EXISTS": + case "VOLUME_ALREADY_ATTACHED": + return ErrorKind.AlreadyExists; + case "BAD_REQUEST": + case "INVALID_INPUT": + case "INVALID_IMAGE": + case "INVALID_PORTS": + case "INVALID_REGION": + case "INVALID_VOLUME": + case "INVALID_VOLUME_COUNT": + case "INVALID_VOLUME_REGION": + case "VALIDATION_FAILED": + case "VALIDATION_ERROR": + case "WORKSPACE_REQUIRED": + return ErrorKind.InvalidArgument; + case "CREATION_TIMEOUT": + case "UPSTREAM_CONNECT_TIMEOUT": + case "UPSTREAM_TIMEOUT": + return ErrorKind.Timeout; + case "QUOTA_EXCEEDED": + case "USAGE_LIMIT_EXCEEDED": + return ErrorKind.ResourceExhausted; + case "RATE_LIMIT_EXCEEDED": + case "WORKSPACE_RATE_LIMIT_EXCEEDED": + return ErrorKind.RateLimited; + case "SANDBOX_DELETION_IN_PROGRESS": + return ErrorKind.Conflict; + case "WORKLOAD_UNAVAILABLE": + case "ROUTING_UNAVAILABLE": + case "SERVICE_UNAVAILABLE": + case "AUTHENTICATION_UNAVAILABLE": + case "UPSTREAM_CONNECT_FAILED": + case "UPSTREAM_ERROR": + case "GATEWAY_INTERNAL_ERROR": + return ErrorKind.Unavailable; + case "WORKLOAD_FAILED": + case "INTERNAL_ERROR": + case "HANDLER_ERROR": + case "DATABASE_ERROR": + return ErrorKind.Internal; + default: + return ErrorKind.Unknown; + } +} + +function kindFromStatus(status: number): ErrorInfo["kind"] { + switch (status) { + case 400: + case 422: + return ErrorKind.InvalidArgument; + case 401: + return ErrorKind.Authentication; + case 402: + return ErrorKind.ResourceExhausted; + case 403: + return ErrorKind.PermissionDenied; + case 404: + case 410: + return ErrorKind.NotFound; + case 408: + return ErrorKind.Timeout; + case 409: + return ErrorKind.Conflict; + case 412: + return ErrorKind.FailedPrecondition; + case 429: + return ErrorKind.RateLimited; + case 500: + return ErrorKind.Internal; + case 502: + case 503: + return ErrorKind.Unavailable; + case 504: + return ErrorKind.Timeout; + default: + return ErrorKind.Unknown; + } +} + +function stringifyCode(code: unknown): string | undefined { + if (typeof code === "string" && code !== "") return code; + if (typeof code === "number") return String(code); + return undefined; +} + +function create(input: MappedErrorInput): SandboxError { + const { kind, error, operation, statusCode, providerCode } = input; + return sandboxError( + { + kind, + provider: "blaxel", + operation, + field: "", + message: + error instanceof Error + ? error.message + : "sandbox-kit blaxel: provider operation failed", + ...(statusCode === undefined ? {} : { statusCode }), + ...(providerCode === undefined ? {} : { providerCode }), + }, + error, + ); +} + +/** Rejects a missing local file or a directory before Blaxel reads it. */ +export async function requireLocalFile( + localPath: string, + operation: string, +): Promise { + let info; + try { + info = await stat(localPath); + } catch (error) { + if (nodeCode(error)) throw localFileError(error, operation); + throw error; + } + if (info.isDirectory()) localDirectory(operation); +} + +/** Rejects a local destination that is already a directory. */ +export async function requireLocalDestination( + localPath: string, + operation: string, +): Promise { + try { + const info = await stat(localPath); + if (info.isDirectory()) localDirectory(operation); + } catch (error) { + if (nodeCode(error) === "ENOENT") return; + if (nodeCode(error)) throw localFileError(error, operation); + throw error; + } +} + +/** Creates the parent directory for a local destination path. */ +export async function ensureLocalParent( + localPath: string, + operation: string, +): Promise { + try { + await mkdir(dirname(localPath), { recursive: true }); + } catch (error) { + throw localFileError(error, operation); + } +} + +/** Reads a local file for upload into the sandbox. */ +export async function readLocalFile( + localPath: string, + operation: string, +): Promise { + await requireLocalFile(localPath, operation); + try { + return await readFile(localPath); + } catch (error) { + throw localFileError(error, operation); + } +} + +/** Writes downloaded sandbox bytes to a local path. */ +export async function writeLocalFile( + localPath: string, + data: Uint8Array, + operation: string, +): Promise { + await requireLocalDestination(localPath, operation); + await ensureLocalParent(localPath, operation); + try { + await writeFile(localPath, data); + } catch (error) { + throw localFileError(error, operation); + } +} + +/** Classifies a local filesystem failure. Other errors stay with provider mapping. */ +export function translateLocalCopyError( + error: unknown, + operation: string, +): unknown { + if (error instanceof SandboxError) return error; + const code = nodeCode(error); + if (!code || !localCodes.has(code)) return error; + return localFileError(error, operation); +} + +function localDirectory(operation: string): never { + providerError({ + kind: ErrorKind.InvalidArgument, + operation, + field: FileFieldLocalPath, + message: "local path is a directory", + }); +} + +function localFileError(error: unknown, operation: string): SandboxError { + return sandboxError( + { + kind: + nodeCode(error) === "ENOENT" + ? ErrorKind.NotFound + : ErrorKind.InvalidArgument, + provider: "blaxel", + operation, + field: FileFieldLocalPath, + message: error instanceof Error ? error.message : "local path failed", + }, + error, + ); +} + +function nodeCode(error: unknown): string | undefined { + if (typeof error !== "object" || !error || !("code" in error)) + return undefined; + return typeof error.code === "string" ? error.code : undefined; +} + +const localCodes = new Set([ + "ENOENT", + "EISDIR", + "ENOTDIR", + "EACCES", + "EPERM", + "ENOSPC", +]); + +/** Local throw helper for mappings, checks, and config. */ +export function providerError(input: ProviderErrorInput): never { + throw sandboxError({ + kind: input.kind, + provider: "blaxel", + operation: input.operation, + field: input.field, + message: "sandbox-kit blaxel: " + input.message, + }); +} diff --git a/sdks/typescript/providers/blaxel/src/index.ts b/sdks/typescript/providers/blaxel/src/index.ts new file mode 100644 index 00000000..4d8b5e92 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/index.ts @@ -0,0 +1,2 @@ +export { providerName } from "./provider.js"; +export { blaxel } from "./blaxel.js"; diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts new file mode 100644 index 00000000..7b3e4db4 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -0,0 +1,412 @@ +// Provider-specific orchestration around generated portable field mappings. +import type { + Config as NativeClientConfig, + Port, + SandboxCreateConfiguration, + SandboxCreateOptions, + SandboxLifecycle, +} from "@blaxel/core"; +import { + ErrorKind, + PolicyMode, + PortTransport, + SandboxState, + WaitCondition, + type Config, + type CreateOptions, + type SandboxInfo, + ConfigFieldRegion, + CreateFieldIsolation, + CreateFieldLifetime, + CreateFieldLifetimeIdleTerminateAfter, + CreateFieldLifetimeIdleTerminateMode, + CreateFieldLifetimeMaximumLifetime, + CreateFieldName, + CreateFieldNetwork, + CreateFieldNetworkPortsPort, + CreateFieldNetworkPortsTransport, + CreateFieldObservability, + CreateFieldPlacement, + CreateFieldProvisioningQueueTimeout, + CreateFieldProvisioningTimeout, + CreateFieldProvisioningWaitFor, + CreateFieldReadiness, + CreateFieldResources, + CreateFieldRuntime, + CreateFieldSecurity, + CreateFieldSource, + CreateFieldSourceImageReference, +} from "@sandbox-kit/sandbox"; +import { + mapCommonCreateFields as generatedMapCommonCreateFields, + mapResources as generatedMapResources, + mapSandboxIdentity as generatedMapSandboxIdentity, + type BlaxelSandboxIdentity, +} from "./provider_mappings.js"; +import { validateProviderCreate } from "#checks/provider_checks.js"; +import { mapGeneratedClientConfig } from "#config/provider_config.js"; +import type { ProviderState } from "#config/provider_config.js"; +import { providerError } from "#errors.js"; + +export interface BlaxelCreatePlan { + readonly params: SandboxCreateConfiguration; + readonly options?: SandboxCreateOptions; + readonly waitUntilReady: boolean; + readonly readyTimeoutMs?: number; +} + +export function mapClientConfig(config: Config): NativeClientConfig { + return mapGeneratedClientConfig(config); +} + +export function mapCreateRequest( + request: CreateOptions, + config: ProviderState, +): BlaxelCreatePlan { + validateProviderCreate(request); + if (request.name === undefined) + invalid(CreateFieldName, "Blaxel requires an explicit sandbox name"); + if (request.source?.image === undefined) + unsupported(CreateFieldSource, "Blaxel requires an explicit image source"); + + const params: SandboxCreateConfiguration = generatedMapCommonCreateFields( + request, + ); + params.name = nonblank(request.name, CreateFieldName); + params.image = nonblank( + request.source.image.reference, + CreateFieldSourceImageReference, + ); + + if (config.region !== undefined) + params.region = nonblankConfig(config.region, ConfigFieldRegion); + + mapRuntime(request); + mapIsolation(request); + mapResources(request, params); + mapPlacement(request); + mapNetwork(request, params); + mapSecurity(request); + mapLifetime(request, params); + mapReadiness(request); + mapObservability(request); + const wait = mapProvisioning(request); + + return { + params, + ...(wait.options === undefined ? {} : { options: wait.options }), + waitUntilReady: wait.waitUntilReady, + ...(wait.readyTimeoutMs === undefined + ? {} + : { readyTimeoutMs: wait.readyTimeoutMs }), + }; +} + +function mapRuntime(r: CreateOptions): void { + if (r.runtime !== undefined) + unsupported( + CreateFieldRuntime, + "runtime settings must come from the Blaxel image", + ); +} + +function mapIsolation(r: CreateOptions): void { + if (r.isolation !== undefined) + unsupported(CreateFieldIsolation, "Blaxel isolation is not configurable"); +} + +function mapResources( + r: CreateOptions, + p: SandboxCreateConfiguration, +): void { + const v = r.resources; + if (!v) return; + if ( + v.cpuCores !== undefined || + v.cpuLimitCores !== undefined || + v.memoryLimitMib !== undefined || + v.diskMib !== undefined + ) + unsupported( + CreateFieldResources, + "Blaxel only maps resources.memory_mib; CPU follows memory", + ); + Object.assign(p, generatedMapResources(v)); +} + +function mapPlacement(r: CreateOptions): void { + if (r.placement !== undefined) + unsupported( + CreateFieldPlacement, + "set client region instead of create placement", + ); +} + +function mapNetwork(r: CreateOptions, p: SandboxCreateConfiguration): void { + const v = r.network; + if (!v) return; + if ( + v.egress !== undefined || + v.outboundCidrs !== undefined || + v.inboundCidrs !== undefined || + v.privateNetwork !== undefined || + v.linkedSandbox !== undefined || + v.proxyReference !== undefined || + v.outboundProxyUrl !== undefined || + v.customDomain !== undefined + ) + unsupported( + CreateFieldNetwork, + "requested network settings are unsupported by Blaxel", + ); + + if (v.outboundDomains !== undefined) { + p.network = { + ...(p.network ?? {}), + allowedDomains: [...v.outboundDomains], + }; + } + + if (v.ports) { + const ports: Port[] = []; + for (const x of v.ports) { + const port = x.port; + if (port === undefined) + invalid(CreateFieldNetworkPortsPort, "port is required"); + ports.push({ + target: port, + protocol: blaxelPortProtocol( + x.transport ?? PortTransport.Default, + ), + }); + } + p.ports = ports; + } +} + +function blaxelPortProtocol( + transport: PortTransport, +): NonNullable { + if ( + transport === PortTransport.Default || + transport === PortTransport.Plain + ) + return "TCP"; + if (transport === PortTransport.Tls) return "TLS"; + if (transport === PortTransport.Http2Tls) return "HTTP"; + invalid( + CreateFieldNetworkPortsTransport, + "requested port transport is unsupported", + ); +} + +function mapSecurity(r: CreateOptions): void { + if (r.security !== undefined) + unsupported( + CreateFieldSecurity, + "Blaxel security settings are not mapped", + ); +} + +function mapLifetime(r: CreateOptions, p: SandboxCreateConfiguration): void { + const v = r.lifetime; + if (!v) return; + if ( + v.ephemeral !== undefined || + v.idleStop !== undefined || + v.idlePause !== undefined || + v.stoppedArchive !== undefined || + v.stoppedDelete !== undefined + ) + unsupported( + CreateFieldLifetime, + "requested lifecycle policy is unsupported by Blaxel", + ); + + if (v.maximumLifetime !== undefined) { + p.ttl = durationString( + v.maximumLifetime, + CreateFieldLifetimeMaximumLifetime, + 5 * 60_000, + ); + } + + if (v.idleTerminate) { + const mode = v.idleTerminate.mode ?? PolicyMode.Default; + if (mode === PolicyMode.Disabled) { + // Explicit disable: omit idle policy. + } else if (mode === PolicyMode.After) { + if (v.idleTerminate.after === undefined) + invalid(CreateFieldLifetimeIdleTerminateAfter, "duration is required"); + const value = durationString( + v.idleTerminate.after, + CreateFieldLifetimeIdleTerminateAfter, + 60_000, + ); + const lifecycle: SandboxLifecycle = { + expirationPolicies: [{ type: "ttl-idle", action: "delete", value }], + }; + p.lifecycle = lifecycle; + } else if (mode !== PolicyMode.Default) + invalid(CreateFieldLifetimeIdleTerminateMode, "unknown policy mode"); + } +} + +function mapReadiness(r: CreateOptions): void { + if (r.readiness !== undefined) + unsupported(CreateFieldReadiness, "Blaxel readiness probes are not mapped"); +} + +function mapObservability(r: CreateOptions): void { + if (r.observability !== undefined) + unsupported( + CreateFieldObservability, + "Blaxel observability settings are not mapped", + ); +} + +function mapProvisioning(r: CreateOptions): { + waitUntilReady: boolean; + readyTimeoutMs?: number; + options?: SandboxCreateOptions; +} { + const v = r.provisioning; + if (!v) return { waitUntilReady: false }; + if (v.queueTimeout !== undefined) + unsupported( + CreateFieldProvisioningQueueTimeout, + "Blaxel queue timeout is unsupported", + ); + + let options: SandboxCreateOptions | undefined; + if (v.timeout !== undefined) { + const seconds = wholeSeconds( + v.timeout, + "create", + CreateFieldProvisioningTimeout, + ); + if (seconds === undefined || seconds < 1 || seconds > 50) + invalid( + CreateFieldProvisioningTimeout, + "create timeout must be a whole number of seconds between 1 and 50", + ); + options = { timeout: seconds }; + } + + const waitFor = v.waitFor ?? WaitCondition.Default; + if (waitFor === WaitCondition.Default || waitFor === WaitCondition.Scheduled) + return { + waitUntilReady: false, + ...(options === undefined ? {} : { options }), + }; + if (waitFor === WaitCondition.Ready) { + if (v.timeout === undefined || v.timeout <= 0) + invalid( + CreateFieldProvisioningTimeout, + "ready wait requires a positive timeout", + ); + return { + waitUntilReady: true, + readyTimeoutMs: v.timeout, + ...(options === undefined ? {} : { options }), + }; + } + unsupported( + CreateFieldProvisioningWaitFor, + "requested wait condition is unsupported", + ); +} + +export function mapSandboxInfo( + value: BlaxelSandboxIdentity, + request?: CreateOptions, +): SandboxInfo { + const mapped = generatedMapSandboxIdentity(value); + const id = mapped.id ?? request?.name ?? value.metadata?.name ?? ""; + const result: SandboxInfo = { + id, + provider: "blaxel", + state: mapped.state ?? SandboxState.Running, + labels: mapped.labels ?? {}, + }; + if (mapped.name !== undefined) mutable(result).name = mapped.name; + else if (request?.name !== undefined) mutable(result).name = request.name; + if (request?.labels !== undefined && mapped.labels === undefined) + mutable(result).labels = { ...request.labels }; + if (mapped.region !== undefined) mutable(result).region = mapped.region; + if (mapped.providerState !== undefined) + mutable(result).providerState = mapped.providerState; + return result; +} + +function mutable(v: SandboxInfo): { + -readonly [K in keyof SandboxInfo]: SandboxInfo[K]; +} { + return v; +} + +function nonblank(v: string, f: string): string { + if (!v.trim()) invalid(f, "must not be blank"); + return v; +} + +function nonblankConfig(v: string, f: string): string { + if (!v.trim()) configInvalid(f, "must not be blank"); + return v; +} + +/** Converts Kit millisecond durations into Blaxel duration strings (s/m/h/d). */ +function durationString(ms: number, field: string, minimumMs: number): string { + if (!Number.isFinite(ms) || ms <= 0 || ms % 1_000 !== 0) + invalid(field, "must be a whole number of seconds"); + if (ms < minimumMs) + invalid(field, "is below the Blaxel minimum duration"); + const seconds = ms / 1_000; + if (seconds % 86_400 === 0) return `${seconds / 86_400}d`; + if (seconds % 3_600 === 0) return `${seconds / 3_600}h`; + if (seconds % 60 === 0) return `${seconds / 60}m`; + return `${seconds}s`; +} + +function wholeSeconds( + value: number | undefined, + operation: string, + field: string, +): number | undefined { + if (value === undefined) return undefined; + if (!Number.isFinite(value) || value < 0 || value % 1_000 !== 0) + providerError({ + kind: ErrorKind.InvalidArgument, + operation, + field, + message: "timeout must be a whole number of seconds", + }); + return value / 1_000; +} + +function invalid(field: string, message: string): never { + providerError({ + kind: ErrorKind.InvalidArgument, + operation: "create", + field, + message, + }); +} + +function unsupported(field: string, message: string): never { + providerError({ + kind: ErrorKind.Unsupported, + operation: "create", + field, + message, + }); +} + +function configInvalid(field: string, message: string): never { + providerError({ + kind: ErrorKind.InvalidArgument, + operation: "initialize", + field, + message, + }); +} diff --git a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts new file mode 100644 index 00000000..ef514130 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts @@ -0,0 +1,145 @@ +// Provider field mappings between shared CreateOptions/Config and the native SDK. +// Inventoried in specs/providers/blaxel.yaml groups when present; kept in sync by hand. + +import { + ErrorKind, + SandboxState, + type SandboxInfo, + type Scope, + CreateFieldResourcesMemoryMib, +} from "@sandbox-kit/sandbox"; +import type { + APIKeyCredentials, + CreateOptions, + Resources, +} from "@sandbox-kit/sandbox"; +import type { + Config as NativeClientConfig, + SandboxCreateConfiguration, +} from "@blaxel/core"; +import { providerError, type ProviderErrorInput } from "#errors.js"; + +type Mutable = { -readonly [K in keyof T]: T[K] }; + +/** Minimal native sandbox shape used for identity mapping. */ +export interface BlaxelSandboxIdentity { + readonly metadata?: { + readonly name?: string; + readonly labels?: Readonly>; + }; + readonly status?: unknown; + readonly spec?: { readonly region?: string }; +} + +export function mapSandboxIdentity( + source: BlaxelSandboxIdentity | undefined, + _operation: string = "create", +): Partial { + const target: Mutable> = {}; + if (source === undefined) return target; + const name = source.metadata?.name; + if (name !== undefined && name !== "") { + target.id = name; + target.name = name; + } + if (source.status !== undefined) { + target.providerState = String(source.status); + const state = mapSandboxState(String(source.status)); + if (state !== undefined) target.state = state; + } + const region = source.spec?.region; + if (region !== undefined && region !== "") target.region = region; + const labels = source.metadata?.labels; + if (labels !== undefined) target.labels = { ...labels }; + target.provider = "blaxel"; + return target; +} + +export function mapClientScope( + source: Scope | undefined, + _operation: string = "initialize", +): NativeClientConfig { + const target: Mutable = {}; + if (source === undefined) return target; + if (source.workspace !== undefined) target.workspace = source.workspace; + return target; +} + +export function mapCommonCreateFields( + source: CreateOptions | undefined, + _operation: string = "create", +): SandboxCreateConfiguration { + const target: Mutable = {}; + if (source === undefined) return target; + if (source.name !== undefined) target.name = source.name; + if (source.environment !== undefined) { + target.envs = Object.entries(source.environment).map(([name, value]) => ({ + name, + value: String(value), + })); + } + if (source.labels !== undefined) target.labels = { ...source.labels }; + return target; +} + +export function mapResources( + source: Resources | undefined, + operation: string = "create", +): Pick { + const target: Mutable> = {}; + if (source === undefined) return target; + if (source.memoryMib !== undefined) { + const value = source.memoryMib; + if (value > BigInt(Number.MAX_SAFE_INTEGER)) + mappingError({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldResourcesMemoryMib, + message: "memory_mib cannot be represented", + operation, + }); + target.memory = Number(value); + } + return target; +} + +export function mapAPIKeyCredentials( + source: APIKeyCredentials | undefined, + _operation: string = "initialize", +): NativeClientConfig { + const target: Mutable = {}; + if (source === undefined) return target; + target.apiKey = source.key; + return target; +} + +function mapSandboxState(status: string): SandboxState | undefined { + const normalized = status.toUpperCase(); + switch (normalized) { + case "RUNNING": + case "READY": + case "DEPLOYED": + return SandboxState.Running; + case "PENDING": + case "CREATING": + case "DEPLOYING": + case "STARTING": + return SandboxState.Starting; + case "STANDBY": + case "SUSPENDED": + case "SLEEPING": + return SandboxState.Paused; + case "ARCHIVED": + return SandboxState.Archived; + case "FAILED": + return SandboxState.Error; + case "TERMINATED": + case "DELETED": + return SandboxState.Terminated; + default: + return undefined; + } +} + +function mappingError(input: ProviderErrorInput): never { + providerError(input); +} diff --git a/sdks/typescript/providers/blaxel/src/provider.ts b/sdks/typescript/providers/blaxel/src/provider.ts new file mode 100644 index 00000000..4d66e586 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/provider.ts @@ -0,0 +1,29 @@ +// Blaxel provider construction and native SDK orchestration are handwritten. + +import type { + Backend, + Config, + CreateOptions, + CreateResult, + OperationOptions, + Provider, +} from "@sandbox-kit/sandbox"; + +export const providerName = "blaxel" as const; + +/** @internal Shared provider identity wiring. */ +export abstract class ProviderBase implements Provider { + readonly name = providerName; + abstract initialize(config: Config): Backend; +} + +/** @internal Shared backend identity and required create/close surface. */ +export abstract class BackendBase implements Backend { + readonly name = providerName; + abstract create( + request: CreateOptions, + options: OperationOptions, + ): Promise; + abstract close(options: OperationOptions): Promise; + abstract closeSandbox(id: string, options: OperationOptions): Promise; +} diff --git a/sdks/typescript/providers/blaxel/src/rejection.test.ts b/sdks/typescript/providers/blaxel/src/rejection.test.ts new file mode 100644 index 00000000..b19e22ed --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/rejection.test.ts @@ -0,0 +1,184 @@ +import { + ConfigFieldAuth, + ConfigFieldEndpoint, + ConfigFieldScopeAppName, + ConfigFieldScopeOrganizationId, + CreateFieldIsolation, + CreateFieldLifetime, + CreateFieldName, + CreateFieldNetwork, + CreateFieldObservability, + CreateFieldPlacement, + CreateFieldProvisioningQueueTimeout, + CreateFieldReadiness, + CreateFieldResources, + CreateFieldRuntime, + CreateFieldSecurity, + CreateFieldSource, + ErrorKind, + IsolationKind, + type Config, + type CreateOptions, +} from "@sandbox-kit/sandbox"; +import { describe, expect, it } from "vitest"; + +import { mapClientConfig, mapCreateRequest } from "#mappings/mappings.js"; + +const workspace = { scope: { workspace: "ws" } }; + +describe("rejected Blaxel create settings", () => { + const cases: { + name: string; + request: CreateOptions; + kind: (typeof ErrorKind)[keyof typeof ErrorKind]; + field: string; + }[] = [ + { + name: "cpu resources", + request: { resources: { cpuCores: 1 } }, + kind: ErrorKind.Unsupported, + field: CreateFieldResources, + }, + { + name: "disk", + request: { resources: { diskMib: 1024n } }, + kind: ErrorKind.Unsupported, + field: CreateFieldResources, + }, + { + name: "placement", + request: { placement: { regions: ["us-pdx-1"] } }, + kind: ErrorKind.Unsupported, + field: CreateFieldPlacement, + }, + { + name: "queue timeout", + request: { provisioning: { queueTimeout: 60_000 } }, + kind: ErrorKind.Unsupported, + field: CreateFieldProvisioningQueueTimeout, + }, + { + name: "readiness", + request: { readiness: { tcpPort: 8080 } }, + kind: ErrorKind.Unsupported, + field: CreateFieldReadiness, + }, + { + name: "observability", + request: { + observability: { telemetryEndpoint: "https://otel.example" }, + }, + kind: ErrorKind.Unsupported, + field: CreateFieldObservability, + }, + { + name: "security", + request: { security: { publicAccess: true } }, + kind: ErrorKind.Unsupported, + field: CreateFieldSecurity, + }, + { + name: "private network", + request: { network: { privateNetwork: true } }, + kind: ErrorKind.Unsupported, + field: CreateFieldNetwork, + }, + { + name: "ephemeral lifetime", + request: { lifetime: { ephemeral: true } }, + kind: ErrorKind.Unsupported, + field: CreateFieldLifetime, + }, + { + name: "runtime", + request: { runtime: { language: "python" } }, + kind: ErrorKind.Unsupported, + field: CreateFieldRuntime, + }, + { + name: "isolation", + request: { isolation: { kind: IsolationKind.Container } }, + kind: ErrorKind.Unsupported, + field: CreateFieldIsolation, + }, + ]; + + it("missing name", () => { + expect(() => + mapCreateRequest( + { source: { image: { reference: "blaxel/base-image:latest" } } }, + workspace, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldName, + }), + ); + }); + + it("missing image", () => { + expect(() => mapCreateRequest({ name: "box" }, workspace)).toThrowError( + expect.objectContaining({ + kind: ErrorKind.Unsupported, + field: CreateFieldSource, + }), + ); + }); + + for (const item of cases) { + it(item.name, () => { + expect(() => + mapCreateRequest(namedImage(item.request), workspace), + ).toThrowError( + expect.objectContaining({ kind: item.kind, field: item.field }), + ); + }); + } +}); + +describe("rejected Blaxel client settings", () => { + const cases: { name: string; config: Config; field: string }[] = [ + { + name: "endpoint", + config: { endpoint: "https://unsupported.example" }, + field: ConfigFieldEndpoint, + }, + { + name: "organization", + config: { scope: { organizationId: "org" } }, + field: ConfigFieldScopeOrganizationId, + }, + { + name: "app name", + config: { scope: { appName: "app" } }, + field: ConfigFieldScopeAppName, + }, + { + name: "token pair auth", + config: { + auth: { tokenPair: { id: "id", secret: "secret" } }, + }, + field: ConfigFieldAuth, + }, + ]; + + for (const item of cases) { + it(item.name, () => { + expect(() => mapClientConfig(item.config)).toThrowError( + expect.objectContaining({ + kind: ErrorKind.Unsupported, + field: item.field, + }), + ); + }); + } +}); + +function namedImage(request: CreateOptions): CreateOptions { + return { + name: "sandbox", + ...request, + source: { image: { reference: "blaxel/base-image:latest" } }, + }; +} diff --git a/sdks/typescript/providers/blaxel/tsconfig.build.json b/sdks/typescript/providers/blaxel/tsconfig.build.json new file mode 100644 index 00000000..cf0eba47 --- /dev/null +++ b/sdks/typescript/providers/blaxel/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false + }, + "exclude": ["src/**/*.test.ts"] +} diff --git a/sdks/typescript/providers/blaxel/tsconfig.json b/sdks/typescript/providers/blaxel/tsconfig.json new file mode 100644 index 00000000..35b5033f --- /dev/null +++ b/sdks/typescript/providers/blaxel/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "declaration": true, + "declarationMap": false, + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "noImplicitOverride": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true, + "customConditions": ["@sandbox-kit/source"], + "noEmit": true + }, + "include": ["src/**/*.ts"] +} diff --git a/sdks/typescript/providers/blaxel/tsconfig.test.json b/sdks/typescript/providers/blaxel/tsconfig.test.json new file mode 100644 index 00000000..fc8520e7 --- /dev/null +++ b/sdks/typescript/providers/blaxel/tsconfig.test.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.json" +} diff --git a/sdks/typescript/providers/blaxel/vitest.config.ts b/sdks/typescript/providers/blaxel/vitest.config.ts new file mode 100644 index 00000000..da559838 --- /dev/null +++ b/sdks/typescript/providers/blaxel/vitest.config.ts @@ -0,0 +1,24 @@ +/// +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { defineConfig } from "vitest/config"; + +const root = path.dirname(fileURLToPath(import.meta.url)); + +export default defineConfig({ + resolve: { + alias: [ + { + find: /^#(mappings|checks|config)\/(.+)\.js$/, + replacement: path.join(root, "src/$1/$2.ts"), + }, + { + find: "#errors.js", + replacement: path.join(root, "src/errors.ts"), + }, + ], + }, + test: { + environment: "node", + }, +}); From ed217f37c0781c13a98a029efaf14ea9964aaacc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Nov 2026 13:13:52 +0000 Subject: [PATCH 03/60] Add Blaxel Go provider with create, FS, run, and delete. Pin github.com/blaxel-ai/sdk-go@v0.31.0, inventory the provider in specs/providers/blaxel.yaml, and mirror Modal/Daytona file roles for API-key plus workspace client config, name/image create, get/refresh by name, the shared filesystem surface, blocking Run, and delete. Co-authored-by: Nabhag Motivaras --- docs/naming.md | 1 + go.work | 1 + sdks/go/providers/blaxel/LICENSE | 21 ++ sdks/go/providers/blaxel/README.md | 90 +++++ sdks/go/providers/blaxel/client.go | 117 +++++++ sdks/go/providers/blaxel/client_test.go | 102 ++++++ sdks/go/providers/blaxel/clone_message.go | 56 +++ .../go/providers/blaxel/clone_message_test.go | 25 ++ sdks/go/providers/blaxel/creation.go | 233 +++++++++++++ sdks/go/providers/blaxel/doc.go | 8 + sdks/go/providers/blaxel/errors.go | 97 +++++ sdks/go/providers/blaxel/errors_test.go | 37 ++ sdks/go/providers/blaxel/filesystem.go | 330 ++++++++++++++++++ sdks/go/providers/blaxel/filesystem_test.go | 46 +++ sdks/go/providers/blaxel/go.mod | 36 ++ sdks/go/providers/blaxel/go.sum | 49 +++ sdks/go/providers/blaxel/lifecycle.go | 39 +++ sdks/go/providers/blaxel/metadata.go | 110 ++++++ sdks/go/providers/blaxel/process.go | 125 +++++++ sdks/go/providers/blaxel/process_test.go | 32 ++ sdks/go/providers/blaxel/provider.go | 102 ++++++ sdks/go/providers/blaxel/provider_checks.go | 53 +++ sdks/go/providers/blaxel/provider_mappings.go | 120 +++++++ sdks/go/providers/blaxel/rejection_test.go | 173 +++++++++ sdks/go/providers/blaxel/sandbox.go | 66 ++++ sdks/go/providers/blaxel/sandbox_test.go | 112 ++++++ specs/providers/blaxel.yaml | 232 ++++++++++++ 27 files changed, 2413 insertions(+) create mode 100644 sdks/go/providers/blaxel/LICENSE create mode 100644 sdks/go/providers/blaxel/README.md create mode 100644 sdks/go/providers/blaxel/client.go create mode 100644 sdks/go/providers/blaxel/client_test.go create mode 100644 sdks/go/providers/blaxel/clone_message.go create mode 100644 sdks/go/providers/blaxel/clone_message_test.go create mode 100644 sdks/go/providers/blaxel/creation.go create mode 100644 sdks/go/providers/blaxel/doc.go create mode 100644 sdks/go/providers/blaxel/errors.go create mode 100644 sdks/go/providers/blaxel/errors_test.go create mode 100644 sdks/go/providers/blaxel/filesystem.go create mode 100644 sdks/go/providers/blaxel/filesystem_test.go create mode 100644 sdks/go/providers/blaxel/go.mod create mode 100644 sdks/go/providers/blaxel/go.sum create mode 100644 sdks/go/providers/blaxel/lifecycle.go create mode 100644 sdks/go/providers/blaxel/metadata.go create mode 100644 sdks/go/providers/blaxel/process.go create mode 100644 sdks/go/providers/blaxel/process_test.go create mode 100644 sdks/go/providers/blaxel/provider.go create mode 100644 sdks/go/providers/blaxel/provider_checks.go create mode 100644 sdks/go/providers/blaxel/provider_mappings.go create mode 100644 sdks/go/providers/blaxel/rejection_test.go create mode 100644 sdks/go/providers/blaxel/sandbox.go create mode 100644 sdks/go/providers/blaxel/sandbox_test.go create mode 100644 specs/providers/blaxel.yaml diff --git a/docs/naming.md b/docs/naming.md index 28688488..7e762341 100644 --- a/docs/naming.md +++ b/docs/naming.md @@ -39,6 +39,7 @@ SDK client remain private. Construction uses sdks/go/sandbox/ Public SDK module sdks/go/providers/modal/ Optional Modal module sdks/go/providers/daytona/ Optional Daytona module +sdks/go/providers/blaxel/ Optional Blaxel module sdks/go/internal/contracts/ Generated Go wire types; only packages under sdks/go can import them sdks/go/internal/sdk/ Shared SDK error type and provider helpers proto/kit/sandbox/v1/ Shared contracts diff --git a/go.work b/go.work index 23e1fbab..98592419 100644 --- a/go.work +++ b/go.work @@ -78,6 +78,7 @@ use ( ./examples/go/modal/process/run-modal-with-timeout ./sdks/go/internal/contracts ./sdks/go/internal/sdk + ./sdks/go/providers/blaxel ./sdks/go/providers/daytona ./sdks/go/providers/modal ./sdks/go/sandbox diff --git a/sdks/go/providers/blaxel/LICENSE b/sdks/go/providers/blaxel/LICENSE new file mode 100644 index 00000000..d843376c --- /dev/null +++ b/sdks/go/providers/blaxel/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 sandbox-kit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md new file mode 100644 index 00000000..43c9f6aa --- /dev/null +++ b/sdks/go/providers/blaxel/README.md @@ -0,0 +1,90 @@ +# Blaxel + +Create and manage Blaxel sandboxes with the shared sandbox client. + +Requires Go 1.26.0 or later and a Blaxel API key. Set `Auth` to an API key and +`Scope.Workspace` to the workspace name. If either is omitted, the Blaxel SDK +uses `BL_API_KEY` and `BL_WORKSPACE` from the environment when present. +`Config.Region` is required for create. + +## Install + +```sh +go get github.com/sandbox-kit/kit/sdks/go/sandbox@latest +go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest +``` + +## Create a sandbox + +```go +client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), +}) +if err != nil { + log.Fatal(err) +} +defer client.Close(context.Background()) + +instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("my-sandbox"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, +}) +if err != nil { + log.Fatal(err) +} +fmt.Println(instance.ID()) +``` + +Create requires a name and an image. Closing the client does not delete the +sandbox. Call `Delete`, or set a lifetime policy. + +## Settings + +| Setting | How to set it | +| --------- | -------------------------------------- | +| API key | `Auth` with `APIKey` | +| Workspace | `Scope.Workspace` | +| Region | `Config.Region` (required for create) | + +`Config.Endpoint` is not supported. Token pair, OAuth, and bearer auth are +rejected. `Scope` fields other than `Workspace` are rejected. + +## Operations + +Create from a registry image with an explicit name. You can set labels, +environment variables, memory, maximum lifetime, and idle terminate. + +On the handle: `Get`, `Refresh`, `Delete`, and `Close`. `Get` and `Refresh` look +up the sandbox by name. `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and +`Terminate` are not supported. `Close` does not contact Blaxel and does not +change the sandbox. + +`Filesystem` supports the shared file calls: `ReadFile`, `WriteFile`, +`CopyFromLocal`, `CopyToLocal`, `MakeDirectory`, `ListDirectory`, `Stat`, and +`Remove`. Paths in the sandbox are absolute. A file timeout is the context +deadline. `Parents` defaults to true. `Parents` set to false is rejected. A +missing file passed to `CopyFromLocal` is not found, and the error field is +`local_path`. A local directory is an invalid argument with the same field. +`CopyToLocal` creates a missing local parent directory. + +`Process().Run` runs one command and waits for the result. Set exactly one of +`Argv` or `Shell`. `Argv` is sent as a POSIX-quoted shell string. A non-zero +exit code is success for the SDK—read `GetExitCode`. Blaxel returns stdout and +stderr. Omit or zero `Timeout` for no Blaxel command limit. Command timeouts +must be whole seconds. Cancel the context to stop waiting. + +Memory is allocated in MiB. Blaxel derives CPU from memory, so `cpu_cores` and +disk MiB overrides are rejected. + +## License + +MIT diff --git a/sdks/go/providers/blaxel/client.go b/sdks/go/providers/blaxel/client.go new file mode 100644 index 00000000..433a3a7e --- /dev/null +++ b/sdks/go/providers/blaxel/client.go @@ -0,0 +1,117 @@ +package blaxel + +import ( + "github.com/blaxel-ai/sdk-go/option" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +// mapClientConfig maps shared client configuration into native SDK options. +// Unsupported supplied settings fail before the SDK is constructed. +// Shared Config validation runs in sandbox.NewClient; this maps only. +type clientSettings struct { + source *sandbox.Config + opts []option.RequestOption +} + +func mapClientConfig(config *sandbox.Config) ([]option.RequestOption, error) { + if config == nil { + config = &sandbox.Config{} + } + settings := &clientSettings{source: config} + if err := settings.applyEndpoint(); err != nil { + return nil, err + } + if err := settings.applyScope(); err != nil { + return nil, err + } + if err := settings.applyAuth(); err != nil { + return nil, err + } + return settings.opts, nil +} + +func (s *clientSettings) applyEndpoint() error { + if s.source.Endpoint == nil { + return nil + } + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindUnsupported, + Provider: "blaxel", + Operation: "initialize", + Field: sandbox.ConfigFieldEndpoint, + Message: "sandbox-kit blaxel: Endpoint cannot be configured through this SDK's public client constructor", + }, nil) +} + +func (s *clientSettings) applyScope() error { + if s.source.Scope == nil { + return nil + } + if s.source.Scope.AppName != nil { + return unsupportedClientField(sandbox.ConfigFieldScopeAppName, "unsupported client configuration field") + } + if s.source.Scope.Environment != nil { + return unsupportedClientField(sandbox.ConfigFieldScopeEnvironment, "unsupported client configuration field") + } + if s.source.Scope.OrganizationId != nil { + return unsupportedClientField(sandbox.ConfigFieldScopeOrganizationId, "unsupported client configuration field") + } + if s.source.Scope.ProjectId != nil { + return unsupportedClientField(sandbox.ConfigFieldScopeProjectId, "unsupported client configuration field") + } + if s.source.Scope.Workspace == nil { + return nil + } + workspace := s.source.Scope.GetWorkspace() + if workspace == "" { + return invalidClientField(sandbox.ConfigFieldScopeWorkspace, "workspace requires a non-blank value") + } + s.opts = append(s.opts, option.WithWorkspace(workspace)) + return nil +} + +func (s *clientSettings) applyAuth() error { + auth := s.source.GetAuth() + if auth == nil { + return nil + } + switch credentials := auth.Credentials.(type) { + case *sandbox.APIKey: + if credentials.ApiKey == nil { + return invalidClientField(sandbox.ConfigFieldAuthApiKey, "api_key requires key") + } + if credentials.ApiKey.Key == "" { + return invalidClientField(sandbox.ConfigFieldAuthApiKeyKey, "api_key requires key") + } + s.opts = append(s.opts, option.WithAPIKey(credentials.ApiKey.Key)) + default: + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindUnsupported, + Provider: "blaxel", + Operation: "initialize", + Field: sandbox.ConfigFieldAuth, + Message: "sandbox-kit blaxel: unsupported authentication mode", + }, nil) + } + return nil +} + +func invalidClientField(field, message string) error { + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindInvalidArgument, + Provider: "blaxel", + Operation: "initialize", + Field: field, + Message: "sandbox-kit blaxel: " + message, + }, nil) +} + +func unsupportedClientField(field, message string) error { + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindUnsupported, + Provider: "blaxel", + Operation: "initialize", + Field: field, + Message: "sandbox-kit blaxel: " + message, + }, nil) +} diff --git a/sdks/go/providers/blaxel/client_test.go b/sdks/go/providers/blaxel/client_test.go new file mode 100644 index 00000000..57f45fca --- /dev/null +++ b/sdks/go/providers/blaxel/client_test.go @@ -0,0 +1,102 @@ +package blaxel + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestCommonInitializationMappings(t *testing.T) { + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: "api-key"}, + }}, + Scope: &sandbox.Scope{Workspace: sandbox.Value("workspace")}, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(time.Second), + } + opts, err := mapClientConfig(config) + if err != nil { + t.Fatal(err) + } + if len(opts) != 2 { + t.Fatalf("expected api key and workspace options, got %d", len(opts)) + } +} + +func TestUnsupportedClientSettingsRejectedBeforeSDKInitialization(t *testing.T) { + for _, config := range []*sandbox.Config{ + {Auth: &sandbox.AuthConfig{Credentials: &sandbox.TokenPair{TokenPair: &sandbox.TokenPairCredentials{Id: "id", Secret: "secret"}}}}, + {Scope: &sandbox.Scope{AppName: sandbox.Value("app")}}, + {Endpoint: sandbox.Value("https://example.invalid")}, + } { + if provider, err := New().NewClient(config); err == nil || provider != nil { + t.Fatal("unsupported client setting initialized SDK") + } + } +} + +func TestUnsupportedScopeFieldsUseCanonicalPaths(t *testing.T) { + for _, tc := range []struct { + name string + scope *sandbox.Scope + field string + }{ + {"app", &sandbox.Scope{AppName: sandbox.Value("app")}, sandbox.ConfigFieldScopeAppName}, + {"environment", &sandbox.Scope{Environment: sandbox.Value("env")}, sandbox.ConfigFieldScopeEnvironment}, + {"organization", &sandbox.Scope{OrganizationId: sandbox.Value("org")}, sandbox.ConfigFieldScopeOrganizationId}, + {"project", &sandbox.Scope{ProjectId: sandbox.Value("project")}, sandbox.ConfigFieldScopeProjectId}, + } { + t.Run(tc.name, func(t *testing.T) { + _, err := mapClientConfig(&sandbox.Config{Scope: tc.scope}) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindUnsupported || detail.Field != tc.field { + t.Fatalf("error = %v detail = %#v", err, detail) + } + }) + } +} + +func TestProviderInitializesAndOwnsSDKWithoutRequests(t *testing.T) { + t.Setenv("BL_API_KEY", "") + t.Setenv("BL_WORKSPACE", "") + client, err := sandbox.NewClient(New(), &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: "test-only"}, + }}, + Scope: &sandbox.Scope{Workspace: sandbox.Value("test-workspace")}, + Region: sandbox.Value("us-pdx-1"), + }) + if err != nil { + t.Fatal(err) + } + if client.ProviderName() != "blaxel" { + t.Fatal("wrong provider") + } + if err := client.Close(context.Background()); err != nil { + t.Fatal(err) + } +} + +func TestAuthReportsTheMissingCredential(t *testing.T) { + cases := []struct { + name string + config *sandbox.Config + field string + }{ + {"missing key", &sandbox.Config{Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{}}}, sandbox.ConfigFieldAuthApiKey}, + {"blank key", &sandbox.Config{Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ApiKey: &sandbox.APIKeyCredentials{}}}}, sandbox.ConfigFieldAuthApiKeyKey}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := mapClientConfig(tc.config) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidArgument || detail.Field != tc.field || detail.Provider != "blaxel" { + t.Fatalf("error = %v detail = %#v", err, detail) + } + }) + } +} diff --git a/sdks/go/providers/blaxel/clone_message.go b/sdks/go/providers/blaxel/clone_message.go new file mode 100644 index 00000000..5277c0c8 --- /dev/null +++ b/sdks/go/providers/blaxel/clone_message.go @@ -0,0 +1,56 @@ +package blaxel + +import ( + "reflect" + + "google.golang.org/protobuf/proto" +) + +// cloneMessage copies a protobuf message without copying its embedded mutex. +// proto.Clone drops empty slices and maps; those are restored so an explicit +// empty collection still counts as set. +func cloneMessage[T proto.Message](src T) T { + value := reflect.ValueOf(src) + if !value.IsValid() || (value.Kind() == reflect.Pointer && value.IsNil()) { + var zero T + return zero + } + dst := proto.Clone(src).(T) + preserveEmpty(value, reflect.ValueOf(dst)) + return dst +} + +func preserveEmpty(src, dst reflect.Value) { + if src.Kind() == reflect.Pointer { + if src.IsNil() || dst.Kind() != reflect.Pointer || dst.IsNil() { + return + } + src, dst = src.Elem(), dst.Elem() + } + if src.Kind() != reflect.Struct || dst.Kind() != reflect.Struct { + return + } + for i := 0; i < src.NumField(); i++ { + if src.Type().Field(i).PkgPath != "" { + continue + } + sv, dv := src.Field(i), dst.Field(i) + switch sv.Kind() { + case reflect.Slice: + if !sv.IsNil() && dv.IsNil() { + dv.Set(reflect.MakeSlice(sv.Type(), 0, 0)) + } + case reflect.Map: + if !sv.IsNil() && dv.IsNil() { + dv.Set(reflect.MakeMap(sv.Type())) + } + case reflect.Pointer: + if sv.IsNil() || dv.IsNil() || sv.Elem().Kind() != reflect.Struct { + continue + } + preserveEmpty(sv, dv) + case reflect.Struct: + preserveEmpty(sv, dv) + } + } +} diff --git a/sdks/go/providers/blaxel/clone_message_test.go b/sdks/go/providers/blaxel/clone_message_test.go new file mode 100644 index 00000000..04564e82 --- /dev/null +++ b/sdks/go/providers/blaxel/clone_message_test.go @@ -0,0 +1,25 @@ +package blaxel + +import ( + "testing" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestCloneMessageKeepsExplicitEmptyCollections(t *testing.T) { + src := &sandbox.CreateOptions{ + Environment: map[string]string{}, + Runtime: &sandbox.RuntimeConfig{Entrypoint: []string{}}, + } + dst := cloneMessage(src) + if dst == nil || dst == src || dst.Environment == nil || dst.Runtime == nil || dst.Runtime.Entrypoint == nil { + t.Fatalf("empty collections dropped: %#v", dst) + } + dst.Environment["MODE"] = "changed" + if len(src.Environment) != 0 { + t.Fatal("clone shares the source map") + } + if cloneMessage((*sandbox.CreateOptions)(nil)) != nil { + t.Fatal("nil source returned a message") + } +} diff --git a/sdks/go/providers/blaxel/creation.go b/sdks/go/providers/blaxel/creation.go new file mode 100644 index 00000000..6c240f32 --- /dev/null +++ b/sdks/go/providers/blaxel/creation.go @@ -0,0 +1,233 @@ +package blaxel + +import ( + "time" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/internal/sdk" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +// createPlan is the Blaxel SDK argument for one creation request. +type createPlan struct { + params blaxel.SandboxNewParams +} + +// creation translates one CreateOptions value into native Blaxel arguments. +type creation struct { + request *sandbox.CreateOptions + rest *sandbox.CreateOptions + params blaxel.SandboxNewParams + region string +} + +// planCreate checks the request and builds SDK arguments without contacting Blaxel. +func planCreate(request *sandbox.CreateOptions, region string) (createPlan, error) { + if request == nil { + request = &sandbox.CreateOptions{} + } + if err := validateProviderCreate(request); err != nil { + return createPlan{}, err + } + if region == "" { + return createPlan{}, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.ConfigFieldRegion, "Config.Region is required") + } + params, rest, err := mapCommonCreateFields(request) + if err != nil { + return createPlan{}, err + } + planned := &creation{request: request, rest: rest, params: params, region: region} + planned.params.Sandbox.Spec.Region = blaxel.String(region) + for _, apply := range []func() error{ + planned.source, + planned.resources, + planned.lifetime, + planned.runtime, + planned.isolation, + planned.security, + planned.network, + planned.provisioning, + planned.observability, + } { + if err := apply(); err != nil { + return createPlan{}, err + } + } + if err := sdk.RejectUnmapped("blaxel", "", planned.rest); err != nil { + return createPlan{}, err + } + return createPlan{params: planned.params}, nil +} + +func (c *creation) source() error { + image := c.request.GetSource().GetImage() + c.params.Sandbox.Spec.Runtime.Image = blaxel.String(image.Reference) + c.rest.Source = nil + return nil +} + +func (c *creation) resources() error { + if c.request.Resources == nil { + return nil + } + mapped, remaining, err := mapResources(c.request.Resources) + if err != nil { + return err + } + if remaining.CpuCores != nil || remaining.CpuLimitCores != nil || remaining.MemoryLimitMib != nil || remaining.DiskMib != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResources, "one or more Blaxel resource fields are not mapped") + } + if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldResources, remaining); err != nil { + return err + } + if mapped.Memory.Valid() { + c.params.Sandbox.Spec.Runtime.Memory = mapped.Memory + } + c.rest.Resources = nil + return nil +} + +func (c *creation) lifetime() error { + if c.request.Lifetime == nil { + return nil + } + config := cloneMessage(c.request.Lifetime) + if config.MaximumLifetime != nil { + formatted, err := formatBlaxelDuration(config.MaximumLifetime.AsDuration()) + if err != nil { + return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeMaximumLifetime, err.Error()) + } + c.params.Sandbox.Spec.Runtime.Ttl = blaxel.String(formatted) + } + config.MaximumLifetime = nil + if config.IdleTerminate != nil { + policy, err := mapIdleTerminate(config.IdleTerminate) + if err != nil { + return err + } + if policy != nil { + c.params.Sandbox.Spec.Lifecycle.ExpirationPolicies = append( + c.params.Sandbox.Spec.Lifecycle.ExpirationPolicies, + *policy, + ) + } + } + config.IdleTerminate = nil + if config.Ephemeral != nil || config.IdleStop != nil || config.IdlePause != nil || config.StoppedArchive != nil || config.StoppedDelete != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldLifetime, "one or more Blaxel lifetime fields are not mapped") + } + if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldLifetime, config); err != nil { + return err + } + c.rest.Lifetime = nil + return nil +} + +func mapIdleTerminate(action *sandbox.AutomaticAction) (*blaxel.ExpirationPolicyParam, error) { + if action == nil { + return nil, nil + } + switch action.Mode { + case sandbox.PolicyModeDefault: + return nil, nil + case sandbox.PolicyModeDisabled: + return nil, providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldLifetimeIdleTerminate, "disabling idle terminate is not representable") + case sandbox.PolicyModeAfter: + if action.After == nil { + return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminate, "duration is required") + } + formatted, err := formatBlaxelDuration(action.After.AsDuration()) + if err != nil { + return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminate, err.Error()) + } + return &blaxel.ExpirationPolicyParam{ + Action: blaxel.ExpirationPolicyActionDelete, + Type: blaxel.ExpirationPolicyTypeTtlIdle, + Value: blaxel.String(formatted), + }, nil + default: + return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminate, "unknown policy mode") + } +} + +func (c *creation) runtime() error { + if c.request.Runtime == nil { + return nil + } + config := cloneMessage(c.request.Runtime) + if config.WorkingDirectory != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldRuntimeWorkingDirectory, "Blaxel create-time working directory is not mapped") + } + if config.Entrypoint != nil || config.Pty != nil || config.User != nil || config.Language != nil || config.Version != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldRuntime, "runtime user, language, version, entrypoint, and PTY are not mapped by Blaxel") + } + if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldRuntime, config); err != nil { + return err + } + c.rest.Runtime = nil + return nil +} + +func (c *creation) isolation() error { + if c.request.Isolation == nil { + return nil + } + config := cloneMessage(c.request.Isolation) + if config.Kind != sandbox.IsolationKindDefault || config.ProviderKind != nil || config.NestedVirtualization != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldIsolation, "Blaxel isolation class is not mapped") + } + if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldIsolation, config); err != nil { + return err + } + c.rest.Isolation = nil + return nil +} + +func (c *creation) security() error { + if c.request.Security == nil { + return nil + } + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldSecurity, "Blaxel security fields are not mapped") +} + +func (c *creation) network() error { + if c.request.Network == nil { + return nil + } + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldNetwork, "Blaxel network fields are not mapped") +} + +func (c *creation) provisioning() error { + if c.request.Provisioning == nil { + return nil + } + config := cloneMessage(c.request.Provisioning) + if config.Timeout != nil { + timeout := config.Timeout.AsDuration() + if timeout < 0 || timeout%time.Second != 0 { + return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldProvisioningTimeout, "timeout must be a whole number of seconds") + } + // Blaxel create does not accept a create-wait timeout on NewInstance. + if timeout > 0 { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldProvisioningTimeout, "Blaxel create timeout is not mapped") + } + } + config.Timeout = nil + if config.QueueTimeout != nil || config.WaitFor != sandbox.WaitConditionDefault { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldProvisioning, "Blaxel provisioning wait settings are not mapped") + } + config.QueueTimeout = nil + config.WaitFor = 0 + if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldProvisioning, config); err != nil { + return err + } + c.rest.Provisioning = nil + return nil +} + +func (c *creation) observability() error { + if c.request.Observability == nil { + return nil + } + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldObservability, "Blaxel observability fields are not mapped") +} diff --git a/sdks/go/providers/blaxel/doc.go b/sdks/go/providers/blaxel/doc.go new file mode 100644 index 00000000..02e887d8 --- /dev/null +++ b/sdks/go/providers/blaxel/doc.go @@ -0,0 +1,8 @@ +// Package blaxel connects the sandbox client to Blaxel. +// +// Pass [New] to sandbox.NewClient. Create requires a sandbox name and an image +// source. The backend implements shared filesystem operations through +// [sandbox.SandboxFiles] and blocking command execution through +// [sandbox.SandboxProcess]. Closing the client does not delete the Blaxel +// sandbox. +package blaxel diff --git a/sdks/go/providers/blaxel/errors.go b/sdks/go/providers/blaxel/errors.go new file mode 100644 index 00000000..272e7ca4 --- /dev/null +++ b/sdks/go/providers/blaxel/errors.go @@ -0,0 +1,97 @@ +package blaxel + +import ( + "context" + "errors" + + sdkblaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/internal/sdk" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func mapProviderError(err error, operation string) error { + if err == nil { + return nil + } + var local *sandbox.Error + if errors.As(err, &local) { + return sdk.WithErrorContext(err, "blaxel", operation) + } + kind := sandbox.ErrorKindUnknown + var statusCode *uint32 + var providerCode, providerSource *string + if errors.Is(err, context.Canceled) { + kind = sandbox.ErrorKindCanceled + } else if errors.Is(err, context.DeadlineExceeded) { + kind = sandbox.ErrorKindTimeout + } + var native *sdkblaxel.Error + if errors.As(err, &native) { + if native.StatusCode > 0 { + statusCode = sandbox.Value(uint32(native.StatusCode)) + } + if native.ErrorCode != "" { + providerCode = sandbox.Value(native.ErrorCode) + } + if native.BlaxelSource != "" { + providerSource = sandbox.Value(native.BlaxelSource) + } else if native.Origin != "" { + providerSource = sandbox.Value(native.Origin) + } + if kind == sandbox.ErrorKindUnknown { + switch native.ErrorCode { + case sdkblaxel.ErrAuthenticationRequired, sdkblaxel.ErrAuthenticationFailed: + kind = sandbox.ErrorKindAuthentication + case sdkblaxel.ErrForbidden: + kind = sandbox.ErrorKindPermissionDenied + case sdkblaxel.ErrRouteNotFound, sdkblaxel.ErrWorkloadNotFound, sdkblaxel.ErrWorkspaceNotFound: + kind = sandbox.ErrorKindNotFound + case sdkblaxel.ErrWorkloadUnavailable: + kind = sandbox.ErrorKindUnavailable + case sdkblaxel.ErrUsageLimitExceeded: + kind = sandbox.ErrorKindResourceExhausted + case sdkblaxel.ErrBadRequest, sdkblaxel.ErrPolicyViolation: + kind = sandbox.ErrorKindInvalidArgument + } + } + if kind == sandbox.ErrorKindUnknown { + switch native.StatusCode { + case 400: + kind = sandbox.ErrorKindInvalidArgument + case 401: + kind = sandbox.ErrorKindAuthentication + case 403: + kind = sandbox.ErrorKindPermissionDenied + case 404: + kind = sandbox.ErrorKindNotFound + case 408: + kind = sandbox.ErrorKindTimeout + case 409: + kind = sandbox.ErrorKindConflict + case 410: + kind = sandbox.ErrorKindNotFound + case 412: + kind = sandbox.ErrorKindFailedPrecondition + case 422: + kind = sandbox.ErrorKindInvalidArgument + case 429: + kind = sandbox.ErrorKindRateLimited + case 500: + kind = sandbox.ErrorKindInternal + case 502, 503: + kind = sandbox.ErrorKindUnavailable + case 504: + kind = sandbox.ErrorKindTimeout + } + } + } + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: kind, + Provider: "blaxel", + Operation: operation, + Message: err.Error(), + StatusCode: statusCode, + ProviderCode: providerCode, + ProviderSource: providerSource, + }, err) +} diff --git a/sdks/go/providers/blaxel/errors_test.go b/sdks/go/providers/blaxel/errors_test.go new file mode 100644 index 00000000..5fc414f8 --- /dev/null +++ b/sdks/go/providers/blaxel/errors_test.go @@ -0,0 +1,37 @@ +package blaxel + +import ( + "context" + "errors" + "net/http" + "net/url" + "testing" + + sdkblaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestMapProviderErrorClassifiesAPIErrors(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes/demo") + if err != nil { + t.Fatal(err) + } + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusNotFound, + ErrorCode: sdkblaxel.ErrWorkloadNotFound, + Request: &http.Request{Method: http.MethodGet, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusNotFound}, + }, "refresh") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindNotFound || detail.Provider != "blaxel" || detail.Operation != "refresh" { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } +} + +func TestMapProviderErrorPreservesContext(t *testing.T) { + err := mapProviderError(context.Canceled, "run") + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindCanceled { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} diff --git a/sdks/go/providers/blaxel/filesystem.go b/sdks/go/providers/blaxel/filesystem.go new file mode 100644 index 00000000..76dbd7b9 --- /dev/null +++ b/sdks/go/providers/blaxel/filesystem.go @@ -0,0 +1,330 @@ +package blaxel + +import ( + "context" + "errors" + "os" + "path/filepath" + "time" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/blaxel-ai/sdk-go/option" + "github.com/sandbox-kit/kit/sdks/go/sandbox" + "google.golang.org/protobuf/types/known/timestamppb" +) + +var _ sandbox.SandboxFiles = (*backend)(nil) + +func (b *backend) ReadFile(ctx context.Context, id, path string, _ *sandbox.FileReadOptions) ([]byte, error) { + instance, err := b.fileSandbox(ctx, id, "read_file") + if err != nil { + return nil, err + } + data, err := instance.FS.ReadBinary(ctx, path) + if err != nil { + return nil, mapProviderError(err, "read_file") + } + return data, nil +} + +func (b *backend) WriteFile(ctx context.Context, id, path string, data []byte, _ *sandbox.FileWriteOptions) error { + instance, err := b.fileSandbox(ctx, id, "write_file") + if err != nil { + return err + } + if data == nil { + data = []byte{} + } + if _, err := instance.FS.WriteBinary(ctx, path, data, ""); err != nil { + return mapProviderError(err, "write_file") + } + return nil +} + +func (b *backend) CopyFromLocal(ctx context.Context, id, localPath, remotePath string, _ *sandbox.FileCopyOptions) error { + if err := localSourceFile(localPath, "copy_from_local"); err != nil { + return err + } + data, err := os.ReadFile(localPath) + if err != nil { + return localPathError(err, "copy_from_local") + } + instance, err := b.fileSandbox(ctx, id, "copy_from_local") + if err != nil { + return err + } + if _, err := instance.FS.WriteBinary(ctx, remotePath, data, ""); err != nil { + return mapProviderError(err, "copy_from_local") + } + return nil +} + +func (b *backend) CopyToLocal(ctx context.Context, id, remotePath, localPath string, _ *sandbox.FileCopyOptions) error { + if err := localDestination(localPath, "copy_to_local"); err != nil { + return err + } + if err := ensureLocalParent(localPath, "copy_to_local"); err != nil { + return err + } + instance, err := b.fileSandbox(ctx, id, "copy_to_local") + if err != nil { + return err + } + if err := instance.FS.Download(ctx, remotePath, localPath); err != nil { + return mapCallerPathError(err, "copy_to_local", localPath) + } + return nil +} + +func (b *backend) MakeDirectory(ctx context.Context, id, path string, options *sandbox.MakeDirectoryOptions) error { + if err := rejectParents(options); err != nil { + return err + } + instance, err := b.fileSandbox(ctx, id, "make_directory") + if err != nil { + return err + } + if _, err := instance.FS.Mkdir(ctx, path, ""); err != nil { + return mapProviderError(err, "make_directory") + } + return nil +} + +func (b *backend) ListDirectory(ctx context.Context, id, path string, _ *sandbox.ListDirectoryOptions) ([]*sandbox.FileInfo, error) { + instance, err := b.fileSandbox(ctx, id, "list_directory") + if err != nil { + return nil, err + } + listing, err := instance.FS.LS(ctx, path) + if err != nil { + return nil, mapProviderError(err, "list_directory") + } + result := make([]*sandbox.FileInfo, 0, len(listing.Files)+len(listing.Subdirectories)) + for _, entry := range listing.Files { + info, err := blaxelFileInfo(entry, sandbox.FileTypeFile, "list_directory") + if err != nil { + return nil, err + } + result = append(result, info) + } + for _, entry := range listing.Subdirectories { + result = append(result, &sandbox.FileInfo{ + Name: entry.Name, + Path: entry.Path, + Type: sandbox.FileTypeDirectory, + }) + } + return result, nil +} + +func (b *backend) Stat(ctx context.Context, id, path string, _ *sandbox.StatOptions) (*sandbox.FileInfo, error) { + instance, err := b.fileSandbox(ctx, id, "stat") + if err != nil { + return nil, err + } + fs := b.filesystemService(instance) + res, err := fs.Get(ctx, path, blaxel.SandboxFilesystemGetParams{}) + if err != nil { + return nil, mapProviderError(err, "stat") + } + if res == nil { + return nil, sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindInvalidResponse, + Provider: "blaxel", + Operation: "stat", + Field: sandbox.FileFieldPath, + Message: "sandbox-kit blaxel: SDK returned no file metadata", + }, nil) + } + if res.JSON.Files.Valid() || res.JSON.Subdirectories.Valid() { + dir := res.AsDirectory() + return &sandbox.FileInfo{ + Name: dir.Name, + Path: dir.Path, + Type: sandbox.FileTypeDirectory, + }, nil + } + file := res.AsFilesystemReadWithContent() + return blaxelFileInfo(blaxel.FilesystemRead{ + Name: file.Name, + Path: file.Path, + Size: file.Size, + Permissions: file.Permissions, + Owner: file.Owner, + Group: file.Group, + LastModified: file.LastModified, + }, sandbox.FileTypeFile, "stat") +} + +func (b *backend) Remove(ctx context.Context, id, path string, options *sandbox.RemoveOptions) error { + instance, err := b.fileSandbox(ctx, id, "remove") + if err != nil { + return err + } + recursive := options != nil && options.GetRecursive() + if _, err := instance.FS.RM(ctx, path, recursive); err != nil { + return mapProviderError(err, "remove") + } + return nil +} + +func (b *backend) fileSandbox(ctx context.Context, id, operation string) (*blaxel.SandboxInstance, error) { + instance, err := b.client.Sandboxes.GetInstance(ctx, id) + if err != nil { + return nil, mapProviderError(err, operation) + } + if instance == nil || instance.FS == nil { + return nil, sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindInvalidResponse, + Provider: "blaxel", + Operation: operation, + Message: "sandbox-kit blaxel: SDK returned no filesystem", + }, nil) + } + return instance, nil +} + +func (b *backend) filesystemService(instance *blaxel.SandboxInstance) blaxel.SandboxFilesystemService { + opts := append([]option.RequestOption{}, b.client.Options...) + if instance != nil && instance.Metadata.URL != "" { + opts = append(opts, option.WithBaseURL(instance.Metadata.URL)) + } + return blaxel.NewSandboxFilesystemService(opts...) +} + +func rejectParents(options *sandbox.MakeDirectoryOptions) error { + if options == nil || options.Parents == nil || *options.Parents { + return nil + } + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindUnsupported, + Provider: "blaxel", + Operation: "make_directory", + Field: sandbox.FileFieldParents, + Message: "sandbox-kit blaxel: parents false is not available", + }, nil) +} + +func localSourceFile(path, operation string) error { + info, err := os.Stat(path) + if err != nil { + return localPathError(err, operation) + } + if info.IsDir() { + return localDirectoryError(operation) + } + return nil +} + +func localDestination(path, operation string) error { + info, err := os.Stat(path) + if errors.Is(err, os.ErrNotExist) { + return nil + } + if err != nil { + return localPathError(err, operation) + } + if info.IsDir() { + return localDirectoryError(operation) + } + return nil +} + +func ensureLocalParent(path, operation string) error { + parent := filepath.Dir(path) + if parent == "." || parent == string(filepath.Separator) { + return nil + } + if err := os.MkdirAll(parent, 0o755); err != nil { + return localPathError(err, operation) + } + return nil +} + +func localDirectoryError(operation string) error { + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindInvalidArgument, + Provider: "blaxel", + Operation: operation, + Field: sandbox.FileFieldLocalPath, + Message: "sandbox-kit blaxel: local path is a directory", + }, nil) +} + +func localPathError(err error, operation string) error { + kind := sandbox.ErrorKindInvalidArgument + if errors.Is(err, os.ErrNotExist) { + kind = sandbox.ErrorKindNotFound + } + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: kind, + Provider: "blaxel", + Operation: operation, + Field: sandbox.FileFieldLocalPath, + Message: err.Error(), + }, err) +} + +func mapCallerPathError(err error, operation, localPath string) error { + var pathError *os.PathError + if errors.As(err, &pathError) && filepath.Clean(pathError.Path) == filepath.Clean(localPath) { + return localPathError(pathError, operation) + } + return mapProviderError(err, operation) +} + +func blaxelFileInfo(entry blaxel.FilesystemRead, kind sandbox.FileType, operation string) (*sandbox.FileInfo, error) { + if entry.Path == "" && entry.Name == "" { + return nil, sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindInvalidResponse, + Provider: "blaxel", + Operation: operation, + Field: sandbox.FileFieldPath, + Message: "sandbox-kit blaxel: SDK returned no file metadata", + }, nil) + } + result := &sandbox.FileInfo{ + Name: entry.Name, + Path: entry.Path, + Type: kind, + Size: entry.Size, + } + if entry.Permissions != "" { + result.Permissions = sandbox.Value(entry.Permissions) + } + if entry.Owner != "" { + result.Owner = sandbox.Value(entry.Owner) + } + if entry.Group != "" { + result.Group = sandbox.Value(entry.Group) + } + if modified := parseOptionalTime(entry.LastModified); modified != nil { + result.ModifiedAt = modified + } + return result, nil +} + +func parseOptionalTime(raw string) *timestamppb.Timestamp { + if raw == "" { + return nil + } + parsed, err := parseBlaxelTime(raw) + if err != nil { + return nil + } + return timestamppb.New(parsed) +} + +func parseBlaxelTime(raw string) (time.Time, error) { + for _, layout := range []string{ + time.RFC3339Nano, + time.RFC3339, + time.RFC1123, + time.RFC1123Z, + } { + if parsed, err := time.Parse(layout, raw); err == nil { + return parsed, nil + } + } + return time.Time{}, errors.New("unrecognized timestamp") +} diff --git a/sdks/go/providers/blaxel/filesystem_test.go b/sdks/go/providers/blaxel/filesystem_test.go new file mode 100644 index 00000000..e07c2286 --- /dev/null +++ b/sdks/go/providers/blaxel/filesystem_test.go @@ -0,0 +1,46 @@ +package blaxel + +import ( + "errors" + "os" + "path/filepath" + "testing" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestLocalSourceFileValidation(t *testing.T) { + dir := t.TempDir() + missing := filepath.Join(dir, "missing.txt") + if err := localSourceFile(missing, "copy_from_local"); err == nil { + t.Fatal("expected missing file error") + } else { + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindNotFound || detail.Field != sandbox.FileFieldLocalPath { + t.Fatalf("error = %v detail = %#v", err, detail) + } + } + if err := localSourceFile(dir, "copy_from_local"); err == nil { + t.Fatal("expected directory error") + } else { + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidArgument || detail.Field != sandbox.FileFieldLocalPath { + t.Fatalf("error = %v detail = %#v", err, detail) + } + } + file := filepath.Join(dir, "note.txt") + if err := os.WriteFile(file, []byte("hi"), 0o644); err != nil { + t.Fatal(err) + } + if err := localSourceFile(file, "copy_from_local"); err != nil { + t.Fatal(err) + } +} + +func TestRejectParentsFalse(t *testing.T) { + err := rejectParents(&sandbox.MakeDirectoryOptions{Parents: sandbox.Value(false)}) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindUnsupported || detail.Field != sandbox.FileFieldParents { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} diff --git a/sdks/go/providers/blaxel/go.mod b/sdks/go/providers/blaxel/go.mod new file mode 100644 index 00000000..c6a2cdbb --- /dev/null +++ b/sdks/go/providers/blaxel/go.mod @@ -0,0 +1,36 @@ +module github.com/sandbox-kit/kit/sdks/go/providers/blaxel + +go 1.26.0 + +require ( + github.com/blaxel-ai/sdk-go v0.31.0 + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 + google.golang.org/protobuf v1.36.12 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../sandbox + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../internal/sdk diff --git a/sdks/go/providers/blaxel/go.sum b/sdks/go/providers/blaxel/go.sum new file mode 100644 index 00000000..c177a211 --- /dev/null +++ b/sdks/go/providers/blaxel/go.sum @@ -0,0 +1,49 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/sdks/go/providers/blaxel/lifecycle.go b/sdks/go/providers/blaxel/lifecycle.go new file mode 100644 index 00000000..408be25e --- /dev/null +++ b/sdks/go/providers/blaxel/lifecycle.go @@ -0,0 +1,39 @@ +package blaxel + +import ( + "context" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +var _ sandbox.SandboxDeleter = (*backend)(nil) + +// DeleteSandbox permanently deletes a Blaxel sandbox by name. +// Start, stop, pause, archive, and terminate are not implemented. +func (b *backend) DeleteSandbox(ctx context.Context, id string, _ *sandbox.DeleteOptions) (*sandbox.SandboxInfo, error) { + instance, err := b.client.Sandboxes.GetInstance(ctx, id) + if err != nil { + return nil, mapProviderError(err, "delete") + } + if instance == nil || instance.Sandbox == nil { + return nil, sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindInvalidResponse, + Provider: "blaxel", + Operation: "delete", + Field: sandbox.InfoFieldId, + Message: "sandbox-kit blaxel: SDK returned no sandbox", + }, nil) + } + info, err := sandboxInfo(instance.Sandbox) + if err != nil { + return nil, err + } + if err := instance.Delete(ctx); err != nil { + return nil, mapProviderError(err, "delete") + } + info.State = sandbox.SandboxStateDeleted + info.Terminal = sandbox.Value(true) + info.ProviderState = sandbox.Value(string(blaxel.StatusTerminated)) + return info, nil +} diff --git a/sdks/go/providers/blaxel/metadata.go b/sdks/go/providers/blaxel/metadata.go new file mode 100644 index 00000000..80011120 --- /dev/null +++ b/sdks/go/providers/blaxel/metadata.go @@ -0,0 +1,110 @@ +package blaxel + +import ( + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/sandbox" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type observedSandbox struct { + native *blaxel.Sandbox + info *sandbox.SandboxInfo +} + +func sandboxInfo(native *blaxel.Sandbox) (*sandbox.SandboxInfo, error) { + info, err := mapSandboxInfo(native) + if err != nil { + return info, err + } + observed := &observedSandbox{native: native, info: info} + if err := observed.resources(); err != nil { + return info, err + } + observed.state() + observed.metadata() + return info, nil +} + +func (s *observedSandbox) resources() error { + resources, err := mapAllocatedResources(s.native) + if err != nil { + return err + } + if resources.MemoryMib != nil { + s.info.Resources = resources + } + return nil +} + +func (s *observedSandbox) state() { + switch s.native.Status { + case blaxel.StatusUploading, blaxel.StatusBuilding, blaxel.StatusDeploying, blaxel.StatusBuilt: + s.info.State = sandbox.SandboxStateQueued + case blaxel.StatusDeployed: + switch s.native.State { + case blaxel.SandboxStateStandby: + s.info.State = sandbox.SandboxStatePaused + default: + s.info.State = sandbox.SandboxStateRunning + } + case blaxel.StatusDeleting, blaxel.StatusDeactivating: + s.info.State = sandbox.SandboxStateDeleting + case blaxel.StatusTerminated, blaxel.StatusDeactivated: + s.info.State = sandbox.SandboxStateDeleted + s.info.Terminal = sandbox.Value(true) + case blaxel.StatusArchiving: + s.info.State = sandbox.SandboxStateArchiving + case blaxel.StatusArchived: + s.info.State = sandbox.SandboxStateArchived + case blaxel.StatusUnarchiving: + s.info.State = sandbox.SandboxStateStarting + case blaxel.StatusFailed: + s.info.State = sandbox.SandboxStateError + default: + switch s.native.State { + case blaxel.SandboxStateRunning: + s.info.State = sandbox.SandboxStateRunning + case blaxel.SandboxStateStandby: + s.info.State = sandbox.SandboxStatePaused + } + } +} + +func (s *observedSandbox) metadata() { + native := s.native + values := make(map[string]*sandbox.MetadataValue, 12) + putText(values, "display_name", native.Metadata.DisplayName) + putText(values, "created_at", native.Metadata.CreatedAt) + putText(values, "updated_at", native.Metadata.UpdatedAt) + putText(values, "last_used_at", native.LastUsedAt) + putText(values, "node_generation", native.NodeGeneration) + if native.ExpiresIn > 0 { + values["expires_in_seconds"] = metadataInt(native.ExpiresIn) + } + if native.State != "" { + values["runtime_state"] = metadataText(string(native.State)) + } + if len(values) > 0 { + s.info.ProviderMetadata = &sandbox.MetadataObject{Values: values} + } + if native.Metadata.CreatedAt != "" { + if parsed, err := parseBlaxelTime(native.Metadata.CreatedAt); err == nil { + s.info.CreatedAt = timestamppb.New(parsed) + } + } +} + +func putText(values map[string]*sandbox.MetadataValue, key, value string) { + if value == "" { + return + } + values[key] = metadataText(value) +} + +func metadataInt(value int64) *sandbox.MetadataValue { + return &sandbox.MetadataValue{Value: &sandbox.MetadataSignedInteger{SignedInteger: value}} +} + +func metadataText(value string) *sandbox.MetadataValue { + return &sandbox.MetadataValue{Value: &sandbox.MetadataText{Text: value}} +} diff --git a/sdks/go/providers/blaxel/process.go b/sdks/go/providers/blaxel/process.go new file mode 100644 index 00000000..223b8715 --- /dev/null +++ b/sdks/go/providers/blaxel/process.go @@ -0,0 +1,125 @@ +package blaxel + +import ( + "context" + "strings" + "time" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/internal/sdk" + "github.com/sandbox-kit/kit/sdks/go/sandbox" + "google.golang.org/protobuf/types/known/durationpb" +) + +var _ sandbox.SandboxProcess = (*backend)(nil) + +func (b *backend) RunCommand(ctx context.Context, id string, request *sandbox.ExecOptions) (*sandbox.ExecResult, error) { + instance, err := b.processSandbox(ctx, id, "run") + if err != nil { + return nil, err + } + command, err := blaxelCommand(request) + if err != nil { + return nil, err + } + timeout, err := commandTimeout("run", request.GetTimeout()) + if err != nil { + return nil, err + } + body := blaxel.ProcessRequestParam{ + Command: command, + WaitForCompletion: blaxel.Bool(true), + } + if request.WorkingDirectory != nil { + body.WorkingDir = blaxel.String(*request.WorkingDirectory) + } + if request.Environment != nil { + body.Env = request.Environment + } + if timeout > 0 { + body.Timeout = blaxel.Int(int64(timeout / time.Second)) + } else { + // Explicit zero disables Blaxel's keep-alive default kill window. + body.Timeout = blaxel.Int(0) + } + response, err := instance.Process.New(ctx, body) + if err != nil { + return nil, mapProviderError(err, "run") + } + if response == nil { + return nil, sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindInvalidResponse, + Provider: "blaxel", + Operation: "run", + Message: "sandbox-kit blaxel: SDK returned no exec result", + }, nil) + } + return &sandbox.ExecResult{ + ExitCode: int32(response.ExitCode), + Stdout: []byte(response.Stdout), + Stderr: []byte(response.Stderr), + }, nil +} + +func (b *backend) processSandbox(ctx context.Context, id, operation string) (*blaxel.SandboxInstance, error) { + instance, err := b.client.Sandboxes.GetInstance(ctx, id) + if err != nil { + return nil, mapProviderError(err, operation) + } + if instance == nil || instance.Process == nil { + return nil, sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindInvalidResponse, + Provider: "blaxel", + Operation: operation, + Message: "sandbox-kit blaxel: SDK returned no process API", + }, nil) + } + return instance, nil +} + +func blaxelCommand(request *sandbox.ExecOptions) (string, error) { + if request.Shell != nil { + return *request.Shell, nil + } + if len(request.Argv) == 0 { + return "", sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindInvalidArgument, + Provider: "blaxel", + Operation: "run", + Field: sandbox.ExecFieldArgv, + Message: "sandbox-kit: set argv or shell", + }, nil) + } + return posixShellJoin(request.Argv), nil +} + +// commandTimeout maps Kit ExecOptions.timeout to Blaxel's whole-second timeout. +// Nil and explicit zero become 0 so Blaxel adds no command limit. +func commandTimeout(operation string, value *durationpb.Duration) (time.Duration, error) { + timeout, ok := sdk.WholeSecondDuration(value) + if !ok { + return 0, sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindInvalidArgument, + Provider: "blaxel", + Operation: operation, + Field: sandbox.ExecFieldTimeout, + Message: "sandbox-kit blaxel: timeout must be a whole number of seconds", + }, nil) + } + return timeout, nil +} + +func posixShellJoin(argv []string) string { + parts := make([]string, len(argv)) + for i, arg := range argv { + parts[i] = posixQuote(arg) + } + return strings.Join(parts, " ") +} + +func posixQuote(value string) string { + if value == "" { + return "''" + } + return "'" + strings.ReplaceAll(value, "'", `'"'"'`) + "'" +} diff --git a/sdks/go/providers/blaxel/process_test.go b/sdks/go/providers/blaxel/process_test.go new file mode 100644 index 00000000..94494a4e --- /dev/null +++ b/sdks/go/providers/blaxel/process_test.go @@ -0,0 +1,32 @@ +package blaxel + +import ( + "errors" + "testing" + "time" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestBlaxelCommandArgvAndShell(t *testing.T) { + command, err := blaxelCommand(&sandbox.ExecOptions{Argv: []string{"echo", "hello world"}}) + if err != nil || command != "'echo' 'hello world'" { + t.Fatalf("argv command = %q err=%v", command, err) + } + command, err = blaxelCommand(&sandbox.ExecOptions{Shell: sandbox.Value("echo hi")}) + if err != nil || command != "echo hi" { + t.Fatalf("shell command = %q err=%v", command, err) + } +} + +func TestCommandTimeoutWholeSeconds(t *testing.T) { + timeout, err := commandTimeout("run", sandbox.Duration(2*time.Second)) + if err != nil || timeout != 2*time.Second { + t.Fatalf("timeout = %v err=%v", timeout, err) + } + _, err = commandTimeout("run", sandbox.Duration(1500*time.Millisecond)) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Field != sandbox.ExecFieldTimeout { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} diff --git a/sdks/go/providers/blaxel/provider.go b/sdks/go/providers/blaxel/provider.go new file mode 100644 index 00000000..c7149aad --- /dev/null +++ b/sdks/go/providers/blaxel/provider.go @@ -0,0 +1,102 @@ +package blaxel + +import ( + "context" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +// Provider is the Blaxel provider. Pass New to sandbox.NewClient. +type Provider struct { +} + +// backend owns the initialized native SDK and captured configuration. +type backend struct { + // client is the initialized native SDK client. It is not part of the public + // sandbox API. + client *blaxel.Client + // region is the captured default region. Nil means the caller omitted it; + // create requires a region. + region *string +} + +// New returns the Blaxel provider. Pass it to sandbox.NewClient. +// +// Example: +// +// client, err := sandbox.NewClient(New(), &sandbox.Config{}) +func New() *Provider { + return &Provider{} +} + +// Name returns "blaxel". +func (*Provider) Name() string { + return "blaxel" +} + +// Name returns this integration's provider identifier. +func (*backend) Name() string { + return "blaxel" +} + +// NewClient opens the Blaxel SDK from config. +// Pass New to sandbox.NewClient. Calling NewClient directly skips the shared client. +func (*Provider) NewClient(config *sandbox.Config) (sandbox.Backend, error) { + backend, err := newBackend(config) + return backend, mapProviderError(err, "initialize") +} + +// newBackend maps config, constructs the native SDK, and captures provider +// state before returning the backend. +func newBackend(config *sandbox.Config) (sandbox.Backend, error) { + if config == nil { + config = &sandbox.Config{} + } + opts, err := mapClientConfig(config) + if err != nil { + return nil, err + } + native := blaxel.NewClient(opts...) + result := &backend{client: &native} + result.capture(config) + return result, nil +} + +// capture copies retained configuration onto the backend. Later changes to the +// caller config do not affect the copy. +func (b *backend) capture(config *sandbox.Config) { + if config == nil { + return + } + if config.Region != nil { + value := *config.Region + b.region = &value + } +} + +// Create creates one sandbox through the native SDK and returns shared +// metadata. +func (b *backend) Create(ctx context.Context, request *sandbox.CreateOptions) (*sandbox.CreateResult, error) { + response, err := b.create(ctx, request) + return response, mapProviderError(err, "create") +} + +// Close releases local provider state. It does not stop or delete the cloud +// sandbox. The Blaxel SDK client has no Close method; context cancellation is +// honored before return. +func (b *backend) Close(ctx context.Context) error { + if err := ctx.Err(); err != nil { + return mapProviderError(err, "close") + } + return nil +} + +// CloseSandbox does not contact Blaxel and does not change the sandbox. +func (*backend) CloseSandbox(context.Context, string) error { + return nil +} + +var _ sandbox.Provider = (*Provider)(nil) +var _ sandbox.Backend = (*backend)(nil) +var _ sandbox.SandboxCreator = (*backend)(nil) diff --git a/sdks/go/providers/blaxel/provider_checks.go b/sdks/go/providers/blaxel/provider_checks.go new file mode 100644 index 00000000..5c52e0ac --- /dev/null +++ b/sdks/go/providers/blaxel/provider_checks.go @@ -0,0 +1,53 @@ +package blaxel + +import ( + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +// validateProviderCreate rejects creation settings this provider cannot honor. +// It runs before the native create call. +// +// Rules are inventoried in specs/providers/blaxel.yaml checks and kept in sync +// here by hand (not emitted). +func validateProviderCreate(request *sandbox.CreateOptions) error { + if request == nil { + return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldName, "name is required") + } + if request.GetName() == "" { + return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldName, "name is required") + } + if request.GetSource().GetImage() == nil || request.GetSource().GetImage().Reference == "" { + return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldSourceImage, "an explicit image source is required") + } + if request.GetSource().GetSnapshot() != nil || request.GetSource().GetWarmPool() != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldSource, "only an image source is supported") + } + if request.Placement != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldPlacement, "per-request Blaxel placement is unsupported; use Config.region") + } + if resources := request.Resources; resources != nil { + if resources.CpuCores != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResourcesCpuCores, "Blaxel derives CPU from memory; cpu_cores is not mapped") + } + if resources.CpuLimitCores != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResourcesCpuLimitCores, "Blaxel resource limits are not mapped") + } + if resources.MemoryLimitMib != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResourcesMemoryLimitMib, "Blaxel resource limits are not mapped") + } + if resources.DiskMib != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResourcesDiskMib, "Blaxel disk size is not mapped as MiB") + } + } + return nil +} + +func providerCheckError(kind sandbox.ErrorKind, field, message string) error { + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: kind, + Provider: "blaxel", + Operation: "create", + Field: field, + Message: "sandbox-kit blaxel: " + message, + }, nil) +} diff --git a/sdks/go/providers/blaxel/provider_mappings.go b/sdks/go/providers/blaxel/provider_mappings.go new file mode 100644 index 00000000..c22d0095 --- /dev/null +++ b/sdks/go/providers/blaxel/provider_mappings.go @@ -0,0 +1,120 @@ +package blaxel + +import ( + "fmt" + "math" + "time" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/blaxel-ai/sdk-go/shared" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +// mapCommonCreateFields maps name, environment, and labels into the native +// create request and returns the fields that were not mapped. +func mapCommonCreateFields(source *sandbox.CreateOptions) (blaxel.SandboxNewParams, *sandbox.CreateOptions, error) { + target := blaxel.SandboxNewParams{} + if source == nil { + return target, &sandbox.CreateOptions{}, nil + } + remaining := cloneMessage(source) + if source.Name != nil { + target.Sandbox.Metadata.Name = source.GetName() + } + remaining.Name = nil + if len(source.Environment) > 0 { + envs := make([]shared.EnvParam, 0, len(source.Environment)) + for name, value := range source.Environment { + envs = append(envs, shared.EnvParam{ + Name: blaxel.String(name), + Value: blaxel.String(value), + }) + } + target.Sandbox.Spec.Runtime.Envs = envs + } + remaining.Environment = nil + if source.Labels != nil { + target.Sandbox.Metadata.Labels = source.Labels + } + remaining.Labels = nil + return target, remaining, nil +} + +// mapResources maps supplied memory into the native request and returns the +// fields that were not mapped. +func mapResources(source *sandbox.Resources) (blaxel.SandboxRuntimeParam, *sandbox.Resources, error) { + target := blaxel.SandboxRuntimeParam{} + if source == nil { + return target, &sandbox.Resources{}, nil + } + remaining := cloneMessage(source) + if source.MemoryMib != nil { + value := source.GetMemoryMib() + if value == 0 || value > math.MaxInt64 { + return target, remaining, sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindInvalidArgument, + Provider: "blaxel", + Operation: "create", + Field: sandbox.CreateFieldResourcesMemoryMib, + Message: "sandbox-kit blaxel: memory_mib cannot be represented", + }, nil) + } + target.Memory = blaxel.Int(int64(value)) + } + remaining.MemoryMib = nil + return target, remaining, nil +} + +// mapSandboxInfo maps native fields into shared metadata. It does not invent +// values the SDK omitted. +func mapSandboxInfo(source *blaxel.Sandbox) (*sandbox.SandboxInfo, error) { + target := &sandbox.SandboxInfo{Provider: "blaxel"} + if source == nil { + return target, nil + } + name := source.Metadata.Name + target.Id = name + if name != "" { + target.Name = sandbox.Value(name) + } + if source.Status != "" { + target.ProviderState = sandbox.Value(string(source.Status)) + } else if source.State != "" { + target.ProviderState = sandbox.Value(string(source.State)) + } + if source.Spec.Region != "" { + target.Region = sandbox.Value(source.Spec.Region) + } + if source.Metadata.Labels != nil { + target.Labels = source.Metadata.Labels + } + return target, nil +} + +// mapAllocatedResources maps native memory into shared metadata. +func mapAllocatedResources(source *blaxel.Sandbox) (*sandbox.Resources, error) { + target := &sandbox.Resources{} + if source == nil || source.Spec.Runtime.Memory <= 0 { + return target, nil + } + target.MemoryMib = sandbox.Value(uint64(source.Spec.Runtime.Memory)) + return target, nil +} + +// formatBlaxelDuration formats a whole-second duration as a Blaxel TTL string. +func formatBlaxelDuration(value time.Duration) (string, error) { + if value <= 0 || value%time.Second != 0 { + return "", fmt.Errorf("duration must be a positive whole number of seconds") + } + seconds := int64(value / time.Second) + switch { + case seconds%86400 == 0: + return fmt.Sprintf("%dd", seconds/86400), nil + case seconds%3600 == 0: + return fmt.Sprintf("%dh", seconds/3600), nil + case seconds%60 == 0: + return fmt.Sprintf("%dm", seconds/60), nil + default: + return fmt.Sprintf("%ds", seconds), nil + } +} diff --git a/sdks/go/providers/blaxel/rejection_test.go b/sdks/go/providers/blaxel/rejection_test.go new file mode 100644 index 00000000..33d9708c --- /dev/null +++ b/sdks/go/providers/blaxel/rejection_test.go @@ -0,0 +1,173 @@ +package blaxel + +import ( + "errors" + "testing" + "time" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestRejectedCreateSettings(t *testing.T) { + for _, tc := range []struct { + name string + request *sandbox.CreateOptions + kind sandbox.ErrorKind + field string + }{ + { + name: "missing name", + request: imaged(&sandbox.CreateOptions{}), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldName, + }, + { + name: "missing image", + request: &sandbox.CreateOptions{Name: sandbox.Value("sandbox")}, + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldSourceImage, + }, + { + name: "cpu cores", + request: namedImage(&sandbox.CreateOptions{ + Resources: &sandbox.Resources{CpuCores: sandbox.Value(2.0)}, + }), + kind: sandbox.ErrorKindUnsupported, + field: sandbox.CreateFieldResourcesCpuCores, + }, + { + name: "disk", + request: namedImage(&sandbox.CreateOptions{ + Resources: &sandbox.Resources{DiskMib: sandbox.Value(uint64(1024))}, + }), + kind: sandbox.ErrorKindUnsupported, + field: sandbox.CreateFieldResourcesDiskMib, + }, + { + name: "placement", + request: namedImage(&sandbox.CreateOptions{ + Placement: &sandbox.Placement{Cloud: sandbox.Value("aws")}, + }), + kind: sandbox.ErrorKindUnsupported, + field: sandbox.CreateFieldPlacement, + }, + { + name: "security", + request: namedImage(&sandbox.CreateOptions{ + Security: &sandbox.SecurityConfig{PublicAccess: sandbox.Value(true)}, + }), + kind: sandbox.ErrorKindUnsupported, + field: sandbox.CreateFieldSecurity, + }, + { + name: "network", + request: namedImage(&sandbox.CreateOptions{ + Network: &sandbox.NetworkConfig{PrivateNetwork: sandbox.Value(true)}, + }), + kind: sandbox.ErrorKindUnsupported, + field: sandbox.CreateFieldNetwork, + }, + { + name: "ephemeral", + request: namedImage(&sandbox.CreateOptions{ + Lifetime: &sandbox.LifetimePolicy{Ephemeral: sandbox.Value(true)}, + }), + kind: sandbox.ErrorKindUnsupported, + field: sandbox.CreateFieldLifetime, + }, + { + name: "fractional maximum lifetime", + request: namedImage(&sandbox.CreateOptions{ + Lifetime: &sandbox.LifetimePolicy{MaximumLifetime: sandbox.Duration(1500 * time.Millisecond)}, + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldLifetimeMaximumLifetime, + }, + } { + t.Run(tc.name, func(t *testing.T) { + _, err := planCreate(tc.request, "us-pdx-1") + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != tc.kind || detail.Field != tc.field { + t.Fatalf("error = %v detail = %#v", err, detail) + } + }) + } +} + +func TestRejectedClientSettings(t *testing.T) { + for _, tc := range []struct { + name string + config *sandbox.Config + field string + kind sandbox.ErrorKind + }{ + { + name: "endpoint", + config: &sandbox.Config{Endpoint: sandbox.Value("https://example.invalid")}, + field: sandbox.ConfigFieldEndpoint, + kind: sandbox.ErrorKindUnsupported, + }, + { + name: "app", + config: &sandbox.Config{Scope: &sandbox.Scope{AppName: sandbox.Value("app")}}, + field: sandbox.ConfigFieldScopeAppName, + kind: sandbox.ErrorKindUnsupported, + }, + { + name: "environment", + config: &sandbox.Config{Scope: &sandbox.Scope{Environment: sandbox.Value("env")}}, + field: sandbox.ConfigFieldScopeEnvironment, + kind: sandbox.ErrorKindUnsupported, + }, + { + name: "organization", + config: &sandbox.Config{Scope: &sandbox.Scope{OrganizationId: sandbox.Value("org")}}, + field: sandbox.ConfigFieldScopeOrganizationId, + kind: sandbox.ErrorKindUnsupported, + }, + { + name: "project", + config: &sandbox.Config{Scope: &sandbox.Scope{ProjectId: sandbox.Value("project")}}, + field: sandbox.ConfigFieldScopeProjectId, + kind: sandbox.ErrorKindUnsupported, + }, + { + name: "token pair", + config: &sandbox.Config{Auth: &sandbox.AuthConfig{Credentials: &sandbox.TokenPair{TokenPair: &sandbox.TokenPairCredentials{Id: "id", Secret: "secret"}}}}, + field: sandbox.ConfigFieldAuth, + kind: sandbox.ErrorKindUnsupported, + }, + { + name: "bearer", + config: &sandbox.Config{Auth: &sandbox.AuthConfig{Credentials: &sandbox.BearerToken{BearerToken: &sandbox.BearerTokenCredentials{Token: "jwt"}}}}, + field: sandbox.ConfigFieldAuth, + kind: sandbox.ErrorKindUnsupported, + }, + } { + t.Run(tc.name, func(t *testing.T) { + _, err := mapClientConfig(tc.config) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != tc.kind || detail.Field != tc.field { + t.Fatalf("error = %v detail = %#v", err, detail) + } + }) + } +} + +func TestMissingRegionRejected(t *testing.T) { + _, err := planCreate(namedImage(&sandbox.CreateOptions{}), "") + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidArgument || detail.Field != sandbox.ConfigFieldRegion { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} + +func imaged(request *sandbox.CreateOptions) *sandbox.CreateOptions { + request.Source = &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}} + return request +} + +func namedImage(request *sandbox.CreateOptions) *sandbox.CreateOptions { + request.Name = sandbox.Value("sandbox") + return imaged(request) +} diff --git a/sdks/go/providers/blaxel/sandbox.go b/sdks/go/providers/blaxel/sandbox.go new file mode 100644 index 00000000..589acdcd --- /dev/null +++ b/sdks/go/providers/blaxel/sandbox.go @@ -0,0 +1,66 @@ +package blaxel + +import ( + "context" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +// create calls the Blaxel SDK and returns shared metadata. +// Create requires a name and an image source. Native create is used; create-if- +// not-exists is not. +func (b *backend) create(ctx context.Context, request *sandbox.CreateOptions) (*sandbox.CreateResult, error) { + if request == nil { + request = &sandbox.CreateOptions{} + } + region := "" + if b.region != nil { + region = *b.region + } + plan, err := planCreate(request, region) + if err != nil { + return nil, err + } + instance, err := b.client.Sandboxes.NewInstance(ctx, plan.params) + if err != nil { + return nil, err + } + if instance == nil || instance.Sandbox == nil { + return nil, sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindInvalidResponse, + Provider: "blaxel", + Operation: "create", + Field: sandbox.InfoFieldId, + Message: "sandbox-kit blaxel: SDK returned no sandbox", + }, nil) + } + info, err := sandboxInfo(instance.Sandbox) + if err != nil { + return nil, err + } + return &sandbox.CreateResult{Sandbox: info}, nil +} + +// RefreshSandbox retrieves current metadata for an existing Blaxel sandbox by name. +func (b *backend) RefreshSandbox(ctx context.Context, id string) (*sandbox.SandboxInfo, error) { + instance, err := b.client.Sandboxes.GetInstance(ctx, id) + if err != nil { + return nil, mapProviderError(err, "refresh") + } + if instance == nil || instance.Sandbox == nil { + return nil, sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindInvalidResponse, + Provider: "blaxel", + Operation: "refresh", + Field: sandbox.InfoFieldId, + Message: "sandbox-kit blaxel: SDK returned no sandbox", + }, nil) + } + info, err := sandboxInfo(instance.Sandbox) + if err != nil { + return nil, err + } + return info, nil +} + +var _ sandbox.SandboxRefresher = (*backend)(nil) diff --git a/sdks/go/providers/blaxel/sandbox_test.go b/sdks/go/providers/blaxel/sandbox_test.go new file mode 100644 index 00000000..a381c6a9 --- /dev/null +++ b/sdks/go/providers/blaxel/sandbox_test.go @@ -0,0 +1,112 @@ +package blaxel + +import ( + "testing" + "time" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestPlanCreateMapsRequiredFields(t *testing.T) { + plan, err := planCreate(&sandbox.CreateOptions{ + Name: sandbox.Value("demo"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Labels: map[string]string{"team": "kit"}, + Environment: map[string]string{"FOO": "bar"}, + Resources: &sandbox.Resources{MemoryMib: sandbox.Value(uint64(4096))}, + Lifetime: &sandbox.LifetimePolicy{ + MaximumLifetime: sandbox.Duration(30 * time.Minute), + IdleTerminate: &sandbox.AutomaticAction{ + Mode: sandbox.PolicyModeAfter, + After: sandbox.Duration(10 * time.Minute), + }, + }, + }, "us-pdx-1") + if err != nil { + t.Fatal(err) + } + if plan.params.Sandbox.Metadata.Name != "demo" { + t.Fatalf("name = %q", plan.params.Sandbox.Metadata.Name) + } + if !plan.params.Sandbox.Spec.Runtime.Image.Valid() || plan.params.Sandbox.Spec.Runtime.Image.Value != "blaxel/base-image:latest" { + t.Fatalf("image = %#v", plan.params.Sandbox.Spec.Runtime.Image) + } + if !plan.params.Sandbox.Spec.Region.Valid() || plan.params.Sandbox.Spec.Region.Value != "us-pdx-1" { + t.Fatalf("region = %#v", plan.params.Sandbox.Spec.Region) + } + if !plan.params.Sandbox.Spec.Runtime.Memory.Valid() || plan.params.Sandbox.Spec.Runtime.Memory.Value != 4096 { + t.Fatalf("memory = %#v", plan.params.Sandbox.Spec.Runtime.Memory) + } + if !plan.params.Sandbox.Spec.Runtime.Ttl.Valid() || plan.params.Sandbox.Spec.Runtime.Ttl.Value != "30m" { + t.Fatalf("ttl = %#v", plan.params.Sandbox.Spec.Runtime.Ttl) + } + if len(plan.params.Sandbox.Spec.Lifecycle.ExpirationPolicies) != 1 { + t.Fatalf("policies = %#v", plan.params.Sandbox.Spec.Lifecycle.ExpirationPolicies) + } + policy := plan.params.Sandbox.Spec.Lifecycle.ExpirationPolicies[0] + if policy.Type != blaxel.ExpirationPolicyTypeTtlIdle || !policy.Value.Valid() || policy.Value.Value != "10m" { + t.Fatalf("idle policy = %#v", policy) + } + if plan.params.CreateIfNotExist.Valid() { + t.Fatal("createIfNotExist must stay unset for native create") + } +} + +func TestMapSandboxInfoUsesNameAsID(t *testing.T) { + info, err := mapSandboxInfo(&blaxel.Sandbox{ + Metadata: blaxel.Metadata{ + Name: "demo", + Labels: map[string]string{"team": "kit"}, + }, + Spec: blaxel.SandboxSpec{Region: "us-pdx-1", Runtime: blaxel.SandboxRuntime{Memory: 2048}}, + Status: blaxel.StatusDeployed, + State: blaxel.SandboxStateRunning, + }) + if err != nil { + t.Fatal(err) + } + if info.Id != "demo" || info.GetName() != "demo" || info.GetRegion() != "us-pdx-1" || info.GetProviderState() != "DEPLOYED" { + t.Fatalf("info = %#v", info) + } + observed, err := sandboxInfo(&blaxel.Sandbox{ + Metadata: blaxel.Metadata{Name: "demo"}, + Spec: blaxel.SandboxSpec{Runtime: blaxel.SandboxRuntime{Memory: 2048}}, + Status: blaxel.StatusDeployed, + State: blaxel.SandboxStateRunning, + }) + if err != nil { + t.Fatal(err) + } + if observed.State != sandbox.SandboxStateRunning || observed.Resources.GetMemoryMib() != 2048 { + t.Fatalf("observed = %#v", observed) + } +} + +func TestFormatBlaxelDuration(t *testing.T) { + for _, tc := range []struct { + in time.Duration + want string + ok bool + }{ + {30 * time.Second, "30s", true}, + {5 * time.Minute, "5m", true}, + {2 * time.Hour, "2h", true}, + {48 * time.Hour, "2d", true}, + {1500 * time.Millisecond, "", false}, + {0, "", false}, + } { + got, err := formatBlaxelDuration(tc.in) + if tc.ok { + if err != nil || got != tc.want { + t.Fatalf("%v: got %q err %v", tc.in, got, err) + } + continue + } + if err == nil { + t.Fatalf("%v: expected error", tc.in) + } + } +} diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml new file mode 100644 index 00000000..99276338 --- /dev/null +++ b/specs/providers/blaxel.yaml @@ -0,0 +1,232 @@ +version: 1 +provider: blaxel +# Provider create checks are handwritten in sdks/*/providers/blaxel (provider_checks). +# Keep this list in sync with those implementations; it is not emitted. +checks: + - { path: name, required: true } + - { path: source.image, required: true } + - { path: placement, unsupported: true } + - { path: resources.cpu_cores, unsupported: true } + - { path: resources.cpu_limit_cores, unsupported: true } + - { path: resources.memory_limit_mib, unsupported: true } + - { path: resources.disk_mib, unsupported: true } +client: + target: + name: ClientOptions + bindings: + go: + { + import: github.com/blaxel-ai/sdk-go/option, + name: RequestOption, + } + typescript: { import: "@blaxel/core", name: ClientOptions } + managed: [provider, timeout] + retained: [region] + rejected: + { + endpoint: "Endpoint cannot be configured through this SDK's public client constructor", + } + scope: { field: scope, group: mapClientScope, retained: [workspace] } + auth: + - { field: api_key, group: mapAPIKeyCredentials } +runtime: + go: + constructor: { function: NewClient } + state: + - { + name: region, + type: string, + optional: true, + capture: region, + doc: "is the captured default region. Nil means the caller omitted it; create requires a region.", + } + cleanup: { method: Close, accepts_context: true, returns_error: false } + typescript: + constructor: { function: BlaxelApiClient } + state: + - { + name: region, + type: string, + optional: true, + capture: region, + doc: "is the captured default region. Omission leaves create without a region unless the environment supplies one.", + } + cleanup: + { method: close, accepts_context: false, returns_error: false } +# Mapping groups are handwritten in sdks/*/providers/blaxel (provider_mappings). +# Keep this inventory in sync with those implementations; it is not emitted. +groups: + - name: mapClientScope + direction: request + source: { name: Scope } + target: + name: ClientOptions + bindings: + go: + import: github.com/blaxel-ai/sdk-go/option + name: RequestOption + fields: { workspace: WithWorkspace } + typescript: + import: "@blaxel/core" + name: ClientOptions + fields: { workspace: workspace } + fields: + - { from: workspace, to: workspace } + - name: mapAPIKeyCredentials + direction: request + source: { name: APIKeyCredentials } + target: + name: ClientOptions + bindings: + go: + import: github.com/blaxel-ai/sdk-go/option + name: RequestOption + fields: { key: WithAPIKey } + typescript: + import: "@blaxel/core" + name: ClientOptions + fields: { key: apiKey } + fields: + - { from: key, to: key } + - name: mapCommonCreateFields + direction: request + source: { name: CreateOptions } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: + { + name: Sandbox.Metadata.Name, + environment: Sandbox.Spec.Runtime.Envs, + labels: Sandbox.Metadata.Labels, + } + typescript: + import: "@blaxel/core" + name: SandboxCreateRequest + fields: + { + name: metadata.name, + environment: spec.runtime.envs, + labels: metadata.labels, + } + fields: + - { from: name, to: name } + - { from: environment, to: environment } + - { from: labels, to: labels } + - name: mapResources + direction: request + source: { name: Resources } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: { memory: Sandbox.Spec.Runtime.Memory } + typescript: + import: "@blaxel/core" + name: SandboxCreateRequest + fields: { memory: spec.runtime.memory } + fields: + - { + from: memory_mib, + to: memory, + maximum: 2147483647, + cast: integer, + } + - name: mapLifetimeDuration + direction: request + source: { name: LifetimePolicy } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: { ttl: Sandbox.Spec.Runtime.Ttl } + typescript: + import: "@blaxel/core" + name: SandboxCreateRequest + fields: { ttl: spec.runtime.ttl } + fields: + - { + from: maximum_lifetime, + to: ttl, + transform: duration_blaxel, + } + - name: mapIdleDuration + error_path: lifetime.idle_terminate + direction: request + source: { name: AutomaticAction } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: { lifecycle: Sandbox.Spec.Lifecycle.ExpirationPolicies } + typescript: + import: "@blaxel/core" + name: SandboxCreateRequest + fields: { lifecycle: spec.lifecycle.expirationPolicies } + fields: + - { + from: after, + to: lifecycle, + transform: duration_blaxel_idle, + } + - name: mapSandboxInfo + direction: response + source: + name: Sandbox + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: Sandbox + fields: + { + id: Metadata.Name, + name: Metadata.Name, + state: Status, + region: Spec.Region, + labels: Metadata.Labels, + } + typescript: + import: "@blaxel/core" + name: Sandbox + fields: + { + id: metadata.name, + name: metadata.name, + state: status, + region: spec.region, + labels: metadata.labels, + } + target: { name: SandboxInfo } + fields: + - { from: id, to: id } + - { from: name, to: name } + - { from: state, to: provider_state, cast: string } + - { from: region, to: region } + - { from: labels, to: labels } + - name: mapAllocatedResources + direction: response + when: + - { field: memory, greater_than: 0 } + source: + name: Sandbox + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: Sandbox + fields: { memory: Spec.Runtime.Memory } + typescript: + import: "@blaxel/core" + name: Sandbox + fields: { memory: spec.runtime.memory } + target: { name: Resources } + fields: + - { from: memory, to: memory_mib, cast: number } From 08950b1e0b58a7ba5dcdfe7b3a69105cfffb1d6e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Nov 2026 13:27:38 +0000 Subject: [PATCH 04/60] Add Blaxel examples, docs, and site coverage. Land full Go and TypeScript example catalogs for Blaxel, align Go network mapping with ports and outbound domains, and update provider docs plus the website to describe Blaxel as a first-class provider. Co-authored-by: Nabhag Motivaras --- README.md | 14 +- docs/configuration.md | 35 ++--- docs/filesystem.md | 2 +- docs/naming.md | 2 +- docs/process.md | 4 +- docs/provider-verification.md | 2 +- docs/providers.md | 50 ++++++-- docs/sandbox-creation.md | 10 +- docs/sandbox-lifecycle.md | 4 +- examples/README.md | 35 ++++- examples/go/README.md | 34 ++++- examples/go/blaxel/.env.example | 4 + .../create-blaxel-client-timeout/.env.example | 4 + .../create-blaxel-client-timeout/README.md | 43 +++++++ .../create-blaxel-client-timeout/go.mod | 40 ++++++ .../create-blaxel-client-timeout/go.sum | 51 ++++++++ .../create-blaxel-client-timeout/main.go | 50 ++++++++ .../create-blaxel-idle/.env.example | 4 + .../create-blaxel-idle/README.md | 43 +++++++ .../configuration/create-blaxel-idle/go.mod | 40 ++++++ .../configuration/create-blaxel-idle/go.sum | 51 ++++++++ .../configuration/create-blaxel-idle/main.go | 56 ++++++++ .../create-blaxel-sandbox/.env.example | 4 + .../create-blaxel-sandbox/README.md | 44 +++++++ .../create-blaxel-sandbox/go.mod | 40 ++++++ .../create-blaxel-sandbox/go.sum | 51 ++++++++ .../create-blaxel-sandbox/main.go | 50 ++++++++ .../.env.example | 4 + .../create-blaxel-with-allowlists/README.md | 43 +++++++ .../create-blaxel-with-allowlists/go.mod | 40 ++++++ .../create-blaxel-with-allowlists/go.sum | 51 ++++++++ .../create-blaxel-with-allowlists/main.go | 53 ++++++++ .../create-blaxel-with-labels/.env.example | 4 + .../create-blaxel-with-labels/README.md | 42 ++++++ .../create-blaxel-with-labels/go.mod | 40 ++++++ .../create-blaxel-with-labels/go.sum | 51 ++++++++ .../create-blaxel-with-labels/main.go | 57 +++++++++ .../create-blaxel-with-ports/.env.example | 4 + .../create-blaxel-with-ports/README.md | 43 +++++++ .../create-blaxel-with-ports/go.mod | 40 ++++++ .../create-blaxel-with-ports/go.sum | 51 ++++++++ .../create-blaxel-with-ports/main.go | 56 ++++++++ .../create-blaxel-with-resources/.env.example | 4 + .../create-blaxel-with-resources/README.md | 43 +++++++ .../create-blaxel-with-resources/go.mod | 40 ++++++ .../create-blaxel-with-resources/go.sum | 51 ++++++++ .../create-blaxel-with-resources/main.go | 51 ++++++++ .../create-blaxel-with-ttl/.env.example | 4 + .../create-blaxel-with-ttl/README.md | 43 +++++++ .../create-blaxel-with-ttl/go.mod | 40 ++++++ .../create-blaxel-with-ttl/go.sum | 51 ++++++++ .../create-blaxel-with-ttl/main.go | 53 ++++++++ .../errors/handle-blaxel-errors/.env.example | 4 + .../errors/handle-blaxel-errors/README.md | 43 +++++++ .../blaxel/errors/handle-blaxel-errors/go.mod | 40 ++++++ .../blaxel/errors/handle-blaxel-errors/go.sum | 51 ++++++++ .../errors/handle-blaxel-errors/main.go | 65 ++++++++++ .../copy-file-from-blaxel/.env.example | 10 ++ .../copy-file-from-blaxel/README.md | 48 +++++++ .../filesystem/copy-file-from-blaxel/go.mod | 40 ++++++ .../filesystem/copy-file-from-blaxel/go.sum | 51 ++++++++ .../filesystem/copy-file-from-blaxel/main.go | 53 ++++++++ .../copy-file-into-blaxel/.env.example | 10 ++ .../copy-file-into-blaxel/README.md | 48 +++++++ .../filesystem/copy-file-into-blaxel/go.mod | 40 ++++++ .../filesystem/copy-file-into-blaxel/go.sum | 51 ++++++++ .../filesystem/copy-file-into-blaxel/main.go | 53 ++++++++ .../list-blaxel-directory/.env.example | 8 ++ .../list-blaxel-directory/README.md | 46 +++++++ .../filesystem/list-blaxel-directory/go.mod | 40 ++++++ .../filesystem/list-blaxel-directory/go.sum | 51 ++++++++ .../filesystem/list-blaxel-directory/main.go | 56 ++++++++ .../make-blaxel-directory/.env.example | 8 ++ .../make-blaxel-directory/README.md | 46 +++++++ .../filesystem/make-blaxel-directory/go.mod | 40 ++++++ .../filesystem/make-blaxel-directory/go.sum | 51 ++++++++ .../filesystem/make-blaxel-directory/main.go | 52 ++++++++ .../filesystem/read-blaxel-file/.env.example | 8 ++ .../filesystem/read-blaxel-file/README.md | 46 +++++++ .../blaxel/filesystem/read-blaxel-file/go.mod | 40 ++++++ .../blaxel/filesystem/read-blaxel-file/go.sum | 51 ++++++++ .../filesystem/read-blaxel-file/main.go | 53 ++++++++ .../remove-blaxel-file/.env.example | 10 ++ .../filesystem/remove-blaxel-file/README.md | 48 +++++++ .../filesystem/remove-blaxel-file/go.mod | 40 ++++++ .../filesystem/remove-blaxel-file/go.sum | 51 ++++++++ .../filesystem/remove-blaxel-file/main.go | 56 ++++++++ .../filesystem/stat-blaxel-file/.env.example | 8 ++ .../filesystem/stat-blaxel-file/README.md | 46 +++++++ .../blaxel/filesystem/stat-blaxel-file/go.mod | 40 ++++++ .../blaxel/filesystem/stat-blaxel-file/go.sum | 51 ++++++++ .../filesystem/stat-blaxel-file/main.go | 53 ++++++++ .../filesystem/write-blaxel-file/.env.example | 8 ++ .../filesystem/write-blaxel-file/README.md | 46 +++++++ .../filesystem/write-blaxel-file/go.mod | 40 ++++++ .../filesystem/write-blaxel-file/go.sum | 51 ++++++++ .../filesystem/write-blaxel-file/main.go | 53 ++++++++ .../close-blaxel-handle/.env.example | 6 + .../lifecycle/close-blaxel-handle/README.md | 45 +++++++ .../lifecycle/close-blaxel-handle/go.mod | 40 ++++++ .../lifecycle/close-blaxel-handle/go.sum | 51 ++++++++ .../lifecycle/close-blaxel-handle/main.go | 50 ++++++++ .../delete-blaxel-sandbox/.env.example | 6 + .../lifecycle/delete-blaxel-sandbox/README.md | 45 +++++++ .../lifecycle/delete-blaxel-sandbox/go.mod | 40 ++++++ .../lifecycle/delete-blaxel-sandbox/go.sum | 51 ++++++++ .../lifecycle/delete-blaxel-sandbox/main.go | 53 ++++++++ .../lifecycle/get-blaxel-sandbox/.env.example | 6 + .../lifecycle/get-blaxel-sandbox/README.md | 45 +++++++ .../lifecycle/get-blaxel-sandbox/go.mod | 40 ++++++ .../lifecycle/get-blaxel-sandbox/go.sum | 51 ++++++++ .../lifecycle/get-blaxel-sandbox/main.go | 48 +++++++ .../refresh-blaxel-sandbox/.env.example | 6 + .../refresh-blaxel-sandbox/README.md | 44 +++++++ .../lifecycle/refresh-blaxel-sandbox/go.mod | 40 ++++++ .../lifecycle/refresh-blaxel-sandbox/go.sum | 51 ++++++++ .../lifecycle/refresh-blaxel-sandbox/main.go | 53 ++++++++ .../run-blaxel-check-exit/.env.example | 6 + .../process/run-blaxel-check-exit/README.md | 44 +++++++ .../process/run-blaxel-check-exit/go.mod | 40 ++++++ .../process/run-blaxel-check-exit/go.sum | 51 ++++++++ .../process/run-blaxel-check-exit/main.go | 56 ++++++++ .../process/run-blaxel-command/.env.example | 6 + .../process/run-blaxel-command/README.md | 45 +++++++ .../blaxel/process/run-blaxel-command/go.mod | 40 ++++++ .../blaxel/process/run-blaxel-command/go.sum | 51 ++++++++ .../blaxel/process/run-blaxel-command/main.go | 53 ++++++++ .../run-blaxel-shell-command/.env.example | 6 + .../run-blaxel-shell-command/README.md | 44 +++++++ .../process/run-blaxel-shell-command/go.mod | 40 ++++++ .../process/run-blaxel-shell-command/go.sum | 51 ++++++++ .../process/run-blaxel-shell-command/main.go | 53 ++++++++ .../process/run-blaxel-with-cwd/.env.example | 8 ++ .../process/run-blaxel-with-cwd/README.md | 46 +++++++ .../blaxel/process/run-blaxel-with-cwd/go.mod | 40 ++++++ .../blaxel/process/run-blaxel-with-cwd/go.sum | 51 ++++++++ .../process/run-blaxel-with-cwd/main.go | 58 +++++++++ .../process/run-blaxel-with-env/.env.example | 6 + .../process/run-blaxel-with-env/README.md | 44 +++++++ .../blaxel/process/run-blaxel-with-env/go.mod | 40 ++++++ .../blaxel/process/run-blaxel-with-env/go.sum | 51 ++++++++ .../process/run-blaxel-with-env/main.go | 54 ++++++++ .../run-blaxel-with-timeout/.env.example | 6 + .../process/run-blaxel-with-timeout/README.md | 45 +++++++ .../process/run-blaxel-with-timeout/go.mod | 40 ++++++ .../process/run-blaxel-with-timeout/go.sum | 51 ++++++++ .../process/run-blaxel-with-timeout/main.go | 54 ++++++++ examples/typescript/README.md | 34 ++++- examples/typescript/blaxel/.env.example | 4 + .../create-blaxel-client-timeout/.env.example | 4 + .../create-blaxel-client-timeout/.gitignore | 3 + .../create-blaxel-client-timeout/README.md | 45 +++++++ .../package-lock.json | 98 ++++++++++++++ .../create-blaxel-client-timeout/package.json | 22 ++++ .../create-blaxel-client-timeout/src/main.ts | 18 +++ .../tsconfig.json | 18 +++ .../create-blaxel-idle/.env.example | 4 + .../create-blaxel-idle/.gitignore | 3 + .../create-blaxel-idle/README.md | 45 +++++++ .../create-blaxel-idle/package-lock.json | 98 ++++++++++++++ .../create-blaxel-idle/package.json | 22 ++++ .../create-blaxel-idle/src/main.ts | 19 +++ .../create-blaxel-idle/tsconfig.json | 18 +++ .../create-blaxel-sandbox/.env.example | 4 + .../create-blaxel-sandbox/.gitignore | 3 + .../create-blaxel-sandbox/README.md | 46 +++++++ .../create-blaxel-sandbox/package-lock.json | 98 ++++++++++++++ .../create-blaxel-sandbox/package.json | 22 ++++ .../create-blaxel-sandbox/src/main.ts | 18 +++ .../create-blaxel-sandbox/tsconfig.json | 18 +++ .../.env.example | 4 + .../create-blaxel-with-allowlists/.gitignore | 3 + .../create-blaxel-with-allowlists/README.md | 45 +++++++ .../package-lock.json | 98 ++++++++++++++ .../package.json | 22 ++++ .../create-blaxel-with-allowlists/src/main.ts | 19 +++ .../tsconfig.json | 18 +++ .../create-blaxel-with-labels/.env.example | 4 + .../create-blaxel-with-labels/.gitignore | 3 + .../create-blaxel-with-labels/README.md | 44 +++++++ .../package-lock.json | 98 ++++++++++++++ .../create-blaxel-with-labels/package.json | 22 ++++ .../create-blaxel-with-labels/src/main.ts | 20 +++ .../create-blaxel-with-labels/tsconfig.json | 18 +++ .../create-blaxel-with-ports/.env.example | 4 + .../create-blaxel-with-ports/.gitignore | 3 + .../create-blaxel-with-ports/README.md | 45 +++++++ .../package-lock.json | 98 ++++++++++++++ .../create-blaxel-with-ports/package.json | 22 ++++ .../create-blaxel-with-ports/src/main.ts | 24 ++++ .../create-blaxel-with-ports/tsconfig.json | 18 +++ .../create-blaxel-with-resources/.env.example | 4 + .../create-blaxel-with-resources/.gitignore | 3 + .../create-blaxel-with-resources/README.md | 45 +++++++ .../package-lock.json | 98 ++++++++++++++ .../create-blaxel-with-resources/package.json | 22 ++++ .../create-blaxel-with-resources/src/main.ts | 19 +++ .../tsconfig.json | 18 +++ .../create-blaxel-with-ttl/.env.example | 4 + .../create-blaxel-with-ttl/.gitignore | 3 + .../create-blaxel-with-ttl/README.md | 45 +++++++ .../create-blaxel-with-ttl/package-lock.json | 98 ++++++++++++++ .../create-blaxel-with-ttl/package.json | 22 ++++ .../create-blaxel-with-ttl/src/main.ts | 19 +++ .../create-blaxel-with-ttl/tsconfig.json | 18 +++ .../errors/handle-blaxel-errors/.env.example | 4 + .../errors/handle-blaxel-errors/.gitignore | 3 + .../errors/handle-blaxel-errors/README.md | 45 +++++++ .../handle-blaxel-errors/package-lock.json | 98 ++++++++++++++ .../errors/handle-blaxel-errors/package.json | 22 ++++ .../errors/handle-blaxel-errors/src/main.ts | 29 +++++ .../errors/handle-blaxel-errors/tsconfig.json | 18 +++ .../copy-file-from-blaxel/.env.example | 10 ++ .../copy-file-from-blaxel/.gitignore | 3 + .../copy-file-from-blaxel/README.md | 50 ++++++++ .../copy-file-from-blaxel/package-lock.json | 98 ++++++++++++++ .../copy-file-from-blaxel/package.json | 22 ++++ .../copy-file-from-blaxel/src/main.ts | 18 +++ .../copy-file-from-blaxel/tsconfig.json | 18 +++ .../copy-file-into-blaxel/.env.example | 10 ++ .../copy-file-into-blaxel/.gitignore | 3 + .../copy-file-into-blaxel/README.md | 50 ++++++++ .../copy-file-into-blaxel/package-lock.json | 98 ++++++++++++++ .../copy-file-into-blaxel/package.json | 22 ++++ .../copy-file-into-blaxel/src/main.ts | 18 +++ .../copy-file-into-blaxel/tsconfig.json | 18 +++ .../list-blaxel-directory/.env.example | 8 ++ .../list-blaxel-directory/.gitignore | 3 + .../list-blaxel-directory/README.md | 48 +++++++ .../list-blaxel-directory/package-lock.json | 98 ++++++++++++++ .../list-blaxel-directory/package.json | 22 ++++ .../list-blaxel-directory/src/main.ts | 20 +++ .../list-blaxel-directory/tsconfig.json | 18 +++ .../make-blaxel-directory/.env.example | 8 ++ .../make-blaxel-directory/.gitignore | 3 + .../make-blaxel-directory/README.md | 48 +++++++ .../make-blaxel-directory/package-lock.json | 98 ++++++++++++++ .../make-blaxel-directory/package.json | 22 ++++ .../make-blaxel-directory/src/main.ts | 17 +++ .../make-blaxel-directory/tsconfig.json | 18 +++ .../filesystem/read-blaxel-file/.env.example | 8 ++ .../filesystem/read-blaxel-file/.gitignore | 3 + .../filesystem/read-blaxel-file/README.md | 48 +++++++ .../read-blaxel-file/package-lock.json | 98 ++++++++++++++ .../filesystem/read-blaxel-file/package.json | 22 ++++ .../filesystem/read-blaxel-file/src/main.ts | 17 +++ .../filesystem/read-blaxel-file/tsconfig.json | 18 +++ .../remove-blaxel-file/.env.example | 10 ++ .../filesystem/remove-blaxel-file/.gitignore | 3 + .../filesystem/remove-blaxel-file/README.md | 50 ++++++++ .../remove-blaxel-file/package-lock.json | 98 ++++++++++++++ .../remove-blaxel-file/package.json | 22 ++++ .../filesystem/remove-blaxel-file/src/main.ts | 18 +++ .../remove-blaxel-file/tsconfig.json | 18 +++ .../filesystem/stat-blaxel-file/.env.example | 8 ++ .../filesystem/stat-blaxel-file/.gitignore | 3 + .../filesystem/stat-blaxel-file/README.md | 48 +++++++ .../stat-blaxel-file/package-lock.json | 98 ++++++++++++++ .../filesystem/stat-blaxel-file/package.json | 22 ++++ .../filesystem/stat-blaxel-file/src/main.ts | 17 +++ .../filesystem/stat-blaxel-file/tsconfig.json | 18 +++ .../filesystem/write-blaxel-file/.env.example | 8 ++ .../filesystem/write-blaxel-file/.gitignore | 3 + .../filesystem/write-blaxel-file/README.md | 48 +++++++ .../write-blaxel-file/package-lock.json | 98 ++++++++++++++ .../filesystem/write-blaxel-file/package.json | 22 ++++ .../filesystem/write-blaxel-file/src/main.ts | 18 +++ .../write-blaxel-file/tsconfig.json | 18 +++ .../close-blaxel-handle/.env.example | 6 + .../lifecycle/close-blaxel-handle/.gitignore | 3 + .../lifecycle/close-blaxel-handle/README.md | 47 +++++++ .../close-blaxel-handle/package-lock.json | 98 ++++++++++++++ .../close-blaxel-handle/package.json | 22 ++++ .../lifecycle/close-blaxel-handle/src/main.ts | 18 +++ .../close-blaxel-handle/tsconfig.json | 18 +++ .../delete-blaxel-sandbox/.env.example | 6 + .../delete-blaxel-sandbox/.gitignore | 3 + .../lifecycle/delete-blaxel-sandbox/README.md | 47 +++++++ .../delete-blaxel-sandbox/package-lock.json | 98 ++++++++++++++ .../delete-blaxel-sandbox/package.json | 22 ++++ .../delete-blaxel-sandbox/src/main.ts | 32 +++++ .../delete-blaxel-sandbox/tsconfig.json | 18 +++ .../lifecycle/get-blaxel-sandbox/.env.example | 6 + .../lifecycle/get-blaxel-sandbox/.gitignore | 3 + .../lifecycle/get-blaxel-sandbox/README.md | 47 +++++++ .../get-blaxel-sandbox/package-lock.json | 98 ++++++++++++++ .../lifecycle/get-blaxel-sandbox/package.json | 22 ++++ .../lifecycle/get-blaxel-sandbox/src/main.ts | 18 +++ .../get-blaxel-sandbox/tsconfig.json | 18 +++ .../refresh-blaxel-sandbox/.env.example | 6 + .../refresh-blaxel-sandbox/.gitignore | 3 + .../refresh-blaxel-sandbox/README.md | 46 +++++++ .../refresh-blaxel-sandbox/package-lock.json | 98 ++++++++++++++ .../refresh-blaxel-sandbox/package.json | 22 ++++ .../refresh-blaxel-sandbox/src/main.ts | 30 +++++ .../refresh-blaxel-sandbox/tsconfig.json | 18 +++ .../run-blaxel-check-exit/.env.example | 6 + .../process/run-blaxel-check-exit/.gitignore | 3 + .../process/run-blaxel-check-exit/README.md | 46 +++++++ .../run-blaxel-check-exit/package-lock.json | 98 ++++++++++++++ .../run-blaxel-check-exit/package.json | 22 ++++ .../process/run-blaxel-check-exit/src/main.ts | 19 +++ .../run-blaxel-check-exit/tsconfig.json | 18 +++ .../process/run-blaxel-command/.env.example | 6 + .../process/run-blaxel-command/.gitignore | 3 + .../process/run-blaxel-command/README.md | 47 +++++++ .../run-blaxel-command/package-lock.json | 98 ++++++++++++++ .../process/run-blaxel-command/package.json | 22 ++++ .../process/run-blaxel-command/src/main.ts | 20 +++ .../process/run-blaxel-command/tsconfig.json | 18 +++ .../run-blaxel-shell-command/.env.example | 6 + .../run-blaxel-shell-command/.gitignore | 3 + .../run-blaxel-shell-command/README.md | 46 +++++++ .../package-lock.json | 98 ++++++++++++++ .../run-blaxel-shell-command/package.json | 22 ++++ .../run-blaxel-shell-command/src/main.ts | 20 +++ .../run-blaxel-shell-command/tsconfig.json | 18 +++ .../process/run-blaxel-with-cwd/.env.example | 8 ++ .../process/run-blaxel-with-cwd/.gitignore | 3 + .../process/run-blaxel-with-cwd/README.md | 48 +++++++ .../run-blaxel-with-cwd/package-lock.json | 98 ++++++++++++++ .../process/run-blaxel-with-cwd/package.json | 22 ++++ .../process/run-blaxel-with-cwd/src/main.ts | 22 ++++ .../process/run-blaxel-with-cwd/tsconfig.json | 18 +++ .../process/run-blaxel-with-env/.env.example | 6 + .../process/run-blaxel-with-env/.gitignore | 3 + .../process/run-blaxel-with-env/README.md | 46 +++++++ .../run-blaxel-with-env/package-lock.json | 98 ++++++++++++++ .../process/run-blaxel-with-env/package.json | 22 ++++ .../process/run-blaxel-with-env/src/main.ts | 21 +++ .../process/run-blaxel-with-env/tsconfig.json | 18 +++ .../run-blaxel-with-timeout/.env.example | 6 + .../run-blaxel-with-timeout/.gitignore | 3 + .../process/run-blaxel-with-timeout/README.md | 47 +++++++ .../run-blaxel-with-timeout/package-lock.json | 98 ++++++++++++++ .../run-blaxel-with-timeout/package.json | 22 ++++ .../run-blaxel-with-timeout/src/main.ts | 21 +++ .../run-blaxel-with-timeout/tsconfig.json | 18 +++ go.work | 71 ++++++---- sdks/go/providers/blaxel/README.md | 3 +- sdks/go/providers/blaxel/creation.go | 53 +++++++- sdks/go/providers/blaxel/rejection_test.go | 36 +++++- sdks/typescript/package-lock.json | 30 ----- website/content/docs/blaxel.mdx | 121 ++++++++++++++++++ website/content/docs/examples.mdx | 65 ++++++++++ website/content/docs/meta.json | 1 + website/src/lib/providers.ts | 9 +- 347 files changed, 11586 insertions(+), 108 deletions(-) create mode 100644 examples/go/blaxel/.env.example create mode 100644 examples/go/blaxel/configuration/create-blaxel-client-timeout/.env.example create mode 100644 examples/go/blaxel/configuration/create-blaxel-client-timeout/README.md create mode 100644 examples/go/blaxel/configuration/create-blaxel-client-timeout/go.mod create mode 100644 examples/go/blaxel/configuration/create-blaxel-client-timeout/go.sum create mode 100644 examples/go/blaxel/configuration/create-blaxel-client-timeout/main.go create mode 100644 examples/go/blaxel/configuration/create-blaxel-idle/.env.example create mode 100644 examples/go/blaxel/configuration/create-blaxel-idle/README.md create mode 100644 examples/go/blaxel/configuration/create-blaxel-idle/go.mod create mode 100644 examples/go/blaxel/configuration/create-blaxel-idle/go.sum create mode 100644 examples/go/blaxel/configuration/create-blaxel-idle/main.go create mode 100644 examples/go/blaxel/configuration/create-blaxel-sandbox/.env.example create mode 100644 examples/go/blaxel/configuration/create-blaxel-sandbox/README.md create mode 100644 examples/go/blaxel/configuration/create-blaxel-sandbox/go.mod create mode 100644 examples/go/blaxel/configuration/create-blaxel-sandbox/go.sum create mode 100644 examples/go/blaxel/configuration/create-blaxel-sandbox/main.go create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-allowlists/.env.example create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-allowlists/README.md create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.mod create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.sum create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-allowlists/main.go create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-labels/.env.example create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-labels/README.md create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-labels/go.mod create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-labels/go.sum create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-labels/main.go create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-ports/.env.example create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-ports/README.md create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-ports/go.mod create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-ports/go.sum create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-ports/main.go create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-resources/.env.example create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-resources/README.md create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-resources/go.mod create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-resources/go.sum create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-resources/main.go create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-ttl/.env.example create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-ttl/README.md create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-ttl/go.mod create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-ttl/go.sum create mode 100644 examples/go/blaxel/configuration/create-blaxel-with-ttl/main.go create mode 100644 examples/go/blaxel/errors/handle-blaxel-errors/.env.example create mode 100644 examples/go/blaxel/errors/handle-blaxel-errors/README.md create mode 100644 examples/go/blaxel/errors/handle-blaxel-errors/go.mod create mode 100644 examples/go/blaxel/errors/handle-blaxel-errors/go.sum create mode 100644 examples/go/blaxel/errors/handle-blaxel-errors/main.go create mode 100644 examples/go/blaxel/filesystem/copy-file-from-blaxel/.env.example create mode 100644 examples/go/blaxel/filesystem/copy-file-from-blaxel/README.md create mode 100644 examples/go/blaxel/filesystem/copy-file-from-blaxel/go.mod create mode 100644 examples/go/blaxel/filesystem/copy-file-from-blaxel/go.sum create mode 100644 examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go create mode 100644 examples/go/blaxel/filesystem/copy-file-into-blaxel/.env.example create mode 100644 examples/go/blaxel/filesystem/copy-file-into-blaxel/README.md create mode 100644 examples/go/blaxel/filesystem/copy-file-into-blaxel/go.mod create mode 100644 examples/go/blaxel/filesystem/copy-file-into-blaxel/go.sum create mode 100644 examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go create mode 100644 examples/go/blaxel/filesystem/list-blaxel-directory/.env.example create mode 100644 examples/go/blaxel/filesystem/list-blaxel-directory/README.md create mode 100644 examples/go/blaxel/filesystem/list-blaxel-directory/go.mod create mode 100644 examples/go/blaxel/filesystem/list-blaxel-directory/go.sum create mode 100644 examples/go/blaxel/filesystem/list-blaxel-directory/main.go create mode 100644 examples/go/blaxel/filesystem/make-blaxel-directory/.env.example create mode 100644 examples/go/blaxel/filesystem/make-blaxel-directory/README.md create mode 100644 examples/go/blaxel/filesystem/make-blaxel-directory/go.mod create mode 100644 examples/go/blaxel/filesystem/make-blaxel-directory/go.sum create mode 100644 examples/go/blaxel/filesystem/make-blaxel-directory/main.go create mode 100644 examples/go/blaxel/filesystem/read-blaxel-file/.env.example create mode 100644 examples/go/blaxel/filesystem/read-blaxel-file/README.md create mode 100644 examples/go/blaxel/filesystem/read-blaxel-file/go.mod create mode 100644 examples/go/blaxel/filesystem/read-blaxel-file/go.sum create mode 100644 examples/go/blaxel/filesystem/read-blaxel-file/main.go create mode 100644 examples/go/blaxel/filesystem/remove-blaxel-file/.env.example create mode 100644 examples/go/blaxel/filesystem/remove-blaxel-file/README.md create mode 100644 examples/go/blaxel/filesystem/remove-blaxel-file/go.mod create mode 100644 examples/go/blaxel/filesystem/remove-blaxel-file/go.sum create mode 100644 examples/go/blaxel/filesystem/remove-blaxel-file/main.go create mode 100644 examples/go/blaxel/filesystem/stat-blaxel-file/.env.example create mode 100644 examples/go/blaxel/filesystem/stat-blaxel-file/README.md create mode 100644 examples/go/blaxel/filesystem/stat-blaxel-file/go.mod create mode 100644 examples/go/blaxel/filesystem/stat-blaxel-file/go.sum create mode 100644 examples/go/blaxel/filesystem/stat-blaxel-file/main.go create mode 100644 examples/go/blaxel/filesystem/write-blaxel-file/.env.example create mode 100644 examples/go/blaxel/filesystem/write-blaxel-file/README.md create mode 100644 examples/go/blaxel/filesystem/write-blaxel-file/go.mod create mode 100644 examples/go/blaxel/filesystem/write-blaxel-file/go.sum create mode 100644 examples/go/blaxel/filesystem/write-blaxel-file/main.go create mode 100644 examples/go/blaxel/lifecycle/close-blaxel-handle/.env.example create mode 100644 examples/go/blaxel/lifecycle/close-blaxel-handle/README.md create mode 100644 examples/go/blaxel/lifecycle/close-blaxel-handle/go.mod create mode 100644 examples/go/blaxel/lifecycle/close-blaxel-handle/go.sum create mode 100644 examples/go/blaxel/lifecycle/close-blaxel-handle/main.go create mode 100644 examples/go/blaxel/lifecycle/delete-blaxel-sandbox/.env.example create mode 100644 examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md create mode 100644 examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.mod create mode 100644 examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.sum create mode 100644 examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go create mode 100644 examples/go/blaxel/lifecycle/get-blaxel-sandbox/.env.example create mode 100644 examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md create mode 100644 examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.mod create mode 100644 examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.sum create mode 100644 examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go create mode 100644 examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example create mode 100644 examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md create mode 100644 examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.mod create mode 100644 examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.sum create mode 100644 examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go create mode 100644 examples/go/blaxel/process/run-blaxel-check-exit/.env.example create mode 100644 examples/go/blaxel/process/run-blaxel-check-exit/README.md create mode 100644 examples/go/blaxel/process/run-blaxel-check-exit/go.mod create mode 100644 examples/go/blaxel/process/run-blaxel-check-exit/go.sum create mode 100644 examples/go/blaxel/process/run-blaxel-check-exit/main.go create mode 100644 examples/go/blaxel/process/run-blaxel-command/.env.example create mode 100644 examples/go/blaxel/process/run-blaxel-command/README.md create mode 100644 examples/go/blaxel/process/run-blaxel-command/go.mod create mode 100644 examples/go/blaxel/process/run-blaxel-command/go.sum create mode 100644 examples/go/blaxel/process/run-blaxel-command/main.go create mode 100644 examples/go/blaxel/process/run-blaxel-shell-command/.env.example create mode 100644 examples/go/blaxel/process/run-blaxel-shell-command/README.md create mode 100644 examples/go/blaxel/process/run-blaxel-shell-command/go.mod create mode 100644 examples/go/blaxel/process/run-blaxel-shell-command/go.sum create mode 100644 examples/go/blaxel/process/run-blaxel-shell-command/main.go create mode 100644 examples/go/blaxel/process/run-blaxel-with-cwd/.env.example create mode 100644 examples/go/blaxel/process/run-blaxel-with-cwd/README.md create mode 100644 examples/go/blaxel/process/run-blaxel-with-cwd/go.mod create mode 100644 examples/go/blaxel/process/run-blaxel-with-cwd/go.sum create mode 100644 examples/go/blaxel/process/run-blaxel-with-cwd/main.go create mode 100644 examples/go/blaxel/process/run-blaxel-with-env/.env.example create mode 100644 examples/go/blaxel/process/run-blaxel-with-env/README.md create mode 100644 examples/go/blaxel/process/run-blaxel-with-env/go.mod create mode 100644 examples/go/blaxel/process/run-blaxel-with-env/go.sum create mode 100644 examples/go/blaxel/process/run-blaxel-with-env/main.go create mode 100644 examples/go/blaxel/process/run-blaxel-with-timeout/.env.example create mode 100644 examples/go/blaxel/process/run-blaxel-with-timeout/README.md create mode 100644 examples/go/blaxel/process/run-blaxel-with-timeout/go.mod create mode 100644 examples/go/blaxel/process/run-blaxel-with-timeout/go.sum create mode 100644 examples/go/blaxel/process/run-blaxel-with-timeout/main.go create mode 100644 examples/typescript/blaxel/.env.example create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-client-timeout/.env.example create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-client-timeout/.gitignore create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-client-timeout/README.md create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package-lock.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-client-timeout/src/main.ts create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-client-timeout/tsconfig.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-idle/.env.example create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-idle/.gitignore create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-idle/README.md create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-idle/package-lock.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-idle/package.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-idle/src/main.ts create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-idle/tsconfig.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-sandbox/.env.example create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-sandbox/.gitignore create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-sandbox/package-lock.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-sandbox/package.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-sandbox/src/main.ts create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-sandbox/tsconfig.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/.env.example create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/.gitignore create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/README.md create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package-lock.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/src/main.ts create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/tsconfig.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-labels/.env.example create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-labels/.gitignore create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-labels/README.md create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-labels/package-lock.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-labels/package.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-labels/src/main.ts create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-labels/tsconfig.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-ports/.env.example create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-ports/.gitignore create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-ports/package-lock.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-ports/package.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-ports/src/main.ts create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-ports/tsconfig.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-resources/.env.example create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-resources/.gitignore create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-resources/README.md create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-resources/package-lock.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-resources/package.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-resources/src/main.ts create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-resources/tsconfig.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-ttl/.env.example create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-ttl/.gitignore create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-ttl/README.md create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package-lock.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package.json create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-ttl/src/main.ts create mode 100644 examples/typescript/blaxel/configuration/create-blaxel-with-ttl/tsconfig.json create mode 100644 examples/typescript/blaxel/errors/handle-blaxel-errors/.env.example create mode 100644 examples/typescript/blaxel/errors/handle-blaxel-errors/.gitignore create mode 100644 examples/typescript/blaxel/errors/handle-blaxel-errors/README.md create mode 100644 examples/typescript/blaxel/errors/handle-blaxel-errors/package-lock.json create mode 100644 examples/typescript/blaxel/errors/handle-blaxel-errors/package.json create mode 100644 examples/typescript/blaxel/errors/handle-blaxel-errors/src/main.ts create mode 100644 examples/typescript/blaxel/errors/handle-blaxel-errors/tsconfig.json create mode 100644 examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.env.example create mode 100644 examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.gitignore create mode 100644 examples/typescript/blaxel/filesystem/copy-file-from-blaxel/README.md create mode 100644 examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package-lock.json create mode 100644 examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package.json create mode 100644 examples/typescript/blaxel/filesystem/copy-file-from-blaxel/src/main.ts create mode 100644 examples/typescript/blaxel/filesystem/copy-file-from-blaxel/tsconfig.json create mode 100644 examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.env.example create mode 100644 examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.gitignore create mode 100644 examples/typescript/blaxel/filesystem/copy-file-into-blaxel/README.md create mode 100644 examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package-lock.json create mode 100644 examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package.json create mode 100644 examples/typescript/blaxel/filesystem/copy-file-into-blaxel/src/main.ts create mode 100644 examples/typescript/blaxel/filesystem/copy-file-into-blaxel/tsconfig.json create mode 100644 examples/typescript/blaxel/filesystem/list-blaxel-directory/.env.example create mode 100644 examples/typescript/blaxel/filesystem/list-blaxel-directory/.gitignore create mode 100644 examples/typescript/blaxel/filesystem/list-blaxel-directory/README.md create mode 100644 examples/typescript/blaxel/filesystem/list-blaxel-directory/package-lock.json create mode 100644 examples/typescript/blaxel/filesystem/list-blaxel-directory/package.json create mode 100644 examples/typescript/blaxel/filesystem/list-blaxel-directory/src/main.ts create mode 100644 examples/typescript/blaxel/filesystem/list-blaxel-directory/tsconfig.json create mode 100644 examples/typescript/blaxel/filesystem/make-blaxel-directory/.env.example create mode 100644 examples/typescript/blaxel/filesystem/make-blaxel-directory/.gitignore create mode 100644 examples/typescript/blaxel/filesystem/make-blaxel-directory/README.md create mode 100644 examples/typescript/blaxel/filesystem/make-blaxel-directory/package-lock.json create mode 100644 examples/typescript/blaxel/filesystem/make-blaxel-directory/package.json create mode 100644 examples/typescript/blaxel/filesystem/make-blaxel-directory/src/main.ts create mode 100644 examples/typescript/blaxel/filesystem/make-blaxel-directory/tsconfig.json create mode 100644 examples/typescript/blaxel/filesystem/read-blaxel-file/.env.example create mode 100644 examples/typescript/blaxel/filesystem/read-blaxel-file/.gitignore create mode 100644 examples/typescript/blaxel/filesystem/read-blaxel-file/README.md create mode 100644 examples/typescript/blaxel/filesystem/read-blaxel-file/package-lock.json create mode 100644 examples/typescript/blaxel/filesystem/read-blaxel-file/package.json create mode 100644 examples/typescript/blaxel/filesystem/read-blaxel-file/src/main.ts create mode 100644 examples/typescript/blaxel/filesystem/read-blaxel-file/tsconfig.json create mode 100644 examples/typescript/blaxel/filesystem/remove-blaxel-file/.env.example create mode 100644 examples/typescript/blaxel/filesystem/remove-blaxel-file/.gitignore create mode 100644 examples/typescript/blaxel/filesystem/remove-blaxel-file/README.md create mode 100644 examples/typescript/blaxel/filesystem/remove-blaxel-file/package-lock.json create mode 100644 examples/typescript/blaxel/filesystem/remove-blaxel-file/package.json create mode 100644 examples/typescript/blaxel/filesystem/remove-blaxel-file/src/main.ts create mode 100644 examples/typescript/blaxel/filesystem/remove-blaxel-file/tsconfig.json create mode 100644 examples/typescript/blaxel/filesystem/stat-blaxel-file/.env.example create mode 100644 examples/typescript/blaxel/filesystem/stat-blaxel-file/.gitignore create mode 100644 examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md create mode 100644 examples/typescript/blaxel/filesystem/stat-blaxel-file/package-lock.json create mode 100644 examples/typescript/blaxel/filesystem/stat-blaxel-file/package.json create mode 100644 examples/typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts create mode 100644 examples/typescript/blaxel/filesystem/stat-blaxel-file/tsconfig.json create mode 100644 examples/typescript/blaxel/filesystem/write-blaxel-file/.env.example create mode 100644 examples/typescript/blaxel/filesystem/write-blaxel-file/.gitignore create mode 100644 examples/typescript/blaxel/filesystem/write-blaxel-file/README.md create mode 100644 examples/typescript/blaxel/filesystem/write-blaxel-file/package-lock.json create mode 100644 examples/typescript/blaxel/filesystem/write-blaxel-file/package.json create mode 100644 examples/typescript/blaxel/filesystem/write-blaxel-file/src/main.ts create mode 100644 examples/typescript/blaxel/filesystem/write-blaxel-file/tsconfig.json create mode 100644 examples/typescript/blaxel/lifecycle/close-blaxel-handle/.env.example create mode 100644 examples/typescript/blaxel/lifecycle/close-blaxel-handle/.gitignore create mode 100644 examples/typescript/blaxel/lifecycle/close-blaxel-handle/README.md create mode 100644 examples/typescript/blaxel/lifecycle/close-blaxel-handle/package-lock.json create mode 100644 examples/typescript/blaxel/lifecycle/close-blaxel-handle/package.json create mode 100644 examples/typescript/blaxel/lifecycle/close-blaxel-handle/src/main.ts create mode 100644 examples/typescript/blaxel/lifecycle/close-blaxel-handle/tsconfig.json create mode 100644 examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.env.example create mode 100644 examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.gitignore create mode 100644 examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md create mode 100644 examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json create mode 100644 examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package.json create mode 100644 examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts create mode 100644 examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/tsconfig.json create mode 100644 examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.env.example create mode 100644 examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.gitignore create mode 100644 examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md create mode 100644 examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package-lock.json create mode 100644 examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package.json create mode 100644 examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts create mode 100644 examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/tsconfig.json create mode 100644 examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example create mode 100644 examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.gitignore create mode 100644 examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md create mode 100644 examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json create mode 100644 examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package.json create mode 100644 examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts create mode 100644 examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/tsconfig.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-check-exit/.env.example create mode 100644 examples/typescript/blaxel/process/run-blaxel-check-exit/.gitignore create mode 100644 examples/typescript/blaxel/process/run-blaxel-check-exit/README.md create mode 100644 examples/typescript/blaxel/process/run-blaxel-check-exit/package-lock.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-check-exit/package.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-check-exit/src/main.ts create mode 100644 examples/typescript/blaxel/process/run-blaxel-check-exit/tsconfig.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-command/.env.example create mode 100644 examples/typescript/blaxel/process/run-blaxel-command/.gitignore create mode 100644 examples/typescript/blaxel/process/run-blaxel-command/README.md create mode 100644 examples/typescript/blaxel/process/run-blaxel-command/package-lock.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-command/package.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-command/src/main.ts create mode 100644 examples/typescript/blaxel/process/run-blaxel-command/tsconfig.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-shell-command/.env.example create mode 100644 examples/typescript/blaxel/process/run-blaxel-shell-command/.gitignore create mode 100644 examples/typescript/blaxel/process/run-blaxel-shell-command/README.md create mode 100644 examples/typescript/blaxel/process/run-blaxel-shell-command/package-lock.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-shell-command/package.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-shell-command/src/main.ts create mode 100644 examples/typescript/blaxel/process/run-blaxel-shell-command/tsconfig.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-cwd/.env.example create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-cwd/.gitignore create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-cwd/README.md create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-cwd/package-lock.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-cwd/package.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-cwd/tsconfig.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-env/.env.example create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-env/.gitignore create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-env/README.md create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-env/package-lock.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-env/package.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-env/src/main.ts create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-env/tsconfig.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-timeout/.env.example create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-timeout/.gitignore create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-timeout/package-lock.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-timeout/package.json create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-timeout/src/main.ts create mode 100644 examples/typescript/blaxel/process/run-blaxel-with-timeout/tsconfig.json create mode 100644 website/content/docs/blaxel.mdx diff --git a/README.md b/README.md index a6a3cebc..e3b73f9e 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ A multi-provider SDK for creating and managing cloud sandboxes. Choose a provider, install the core SDK and its provider package, then create and manage -sandboxes through a consistent client interface. Daytona and Modal are -supported. +sandboxes through a consistent client interface. Daytona, Modal, and Blaxel are supported. ## npm @@ -15,6 +14,7 @@ download npm has recorded for that package. | [`@sandbox-kit/sandbox`](https://www.npmjs.com/package/@sandbox-kit/sandbox) | [![npm](https://img.shields.io/npm/v/%40sandbox-kit%2Fsandbox)](https://www.npmjs.com/package/@sandbox-kit/sandbox) | [![downloads](https://img.shields.io/npm/d18m/%40sandbox-kit%2Fsandbox?label=downloads)](https://www.npmjs.com/package/@sandbox-kit/sandbox) | | [`@sandbox-kit/daytona`](https://www.npmjs.com/package/@sandbox-kit/daytona) | [![npm](https://img.shields.io/npm/v/%40sandbox-kit%2Fdaytona)](https://www.npmjs.com/package/@sandbox-kit/daytona) | [![downloads](https://img.shields.io/npm/d18m/%40sandbox-kit%2Fdaytona?label=downloads)](https://www.npmjs.com/package/@sandbox-kit/daytona) | | [`@sandbox-kit/modal`](https://www.npmjs.com/package/@sandbox-kit/modal) | [![npm](https://img.shields.io/npm/v/%40sandbox-kit%2Fmodal)](https://www.npmjs.com/package/@sandbox-kit/modal) | [![downloads](https://img.shields.io/npm/d18m/%40sandbox-kit%2Fmodal?label=downloads)](https://www.npmjs.com/package/@sandbox-kit/modal) | +| [`@sandbox-kit/blaxel`](https://www.npmjs.com/package/@sandbox-kit/blaxel) | [![npm](https://img.shields.io/npm/v/%40sandbox-kit%2Fblaxel)](https://www.npmjs.com/package/@sandbox-kit/blaxel) | [![downloads](https://img.shields.io/npm/d18m/%40sandbox-kit%2Fblaxel?label=downloads)](https://www.npmjs.com/package/@sandbox-kit/blaxel) | Install `@sandbox-kit/sandbox` and one provider. @@ -27,6 +27,7 @@ Requires Node.js 20, 22, or 24.15+. Install the core SDK and one provider: ```sh npm install @sandbox-kit/sandbox @sandbox-kit/daytona # or: npm install @sandbox-kit/sandbox @sandbox-kit/modal +# or: npm install @sandbox-kit/sandbox @sandbox-kit/blaxel ``` Set `DAYTONA_API_KEY`, then create a sandbox: @@ -64,6 +65,7 @@ Requires Go 1.26.0 or later. Install the core SDK and one provider: go get github.com/sandbox-kit/kit/sdks/go/sandbox@latest go get github.com/sandbox-kit/kit/sdks/go/providers/daytona@latest # or: go get github.com/sandbox-kit/kit/sdks/go/providers/modal@latest +# or: go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest ``` Set `DAYTONA_API_KEY`, then create a sandbox: @@ -118,7 +120,7 @@ func main() { | Language | Core SDK | Providers | | ---------- | -------------------------------------------- | ---------------------------------------------- | | TypeScript | `@sandbox-kit/sandbox` | `@sandbox-kit/daytona`, `@sandbox-kit/modal` | -| Go | `github.com/sandbox-kit/kit/sdks/go/sandbox` | `.../providers/daytona`, `.../providers/modal` | +| Go | `github.com/sandbox-kit/kit/sdks/go/sandbox` | `.../providers/daytona`, `.../providers/modal`, `.../providers/blaxel` | Install only the provider you use. The core SDK exposes the shared client interface; the provider package connects it to the provider's native SDK. @@ -284,10 +286,10 @@ These pages are the behavior reference for the same API: | [Sandbox lifecycle](docs/sandbox-lifecycle.md) | Get, attach, capabilities | | [Filesystem](docs/filesystem.md) | Read, write, copy, list, stat, mkdir, remove | | [Process](docs/process.md) | Run a command and wait for exit code + output | -| [Providers](docs/providers.md) | Daytona and Modal | +| [Providers](docs/providers.md) | Daytona, Modal, and Blaxel | | [Responses and errors](docs/responses-and-errors.md) | Error details and provider causes | -| [TS Daytona](sdks/typescript/providers/daytona/README.md) / [Modal](sdks/typescript/providers/modal/README.md) | TypeScript provider notes | -| [Go Daytona](sdks/go/providers/daytona/README.md) / [Modal](sdks/go/providers/modal/README.md) | Go provider notes | +| [TS Daytona](sdks/typescript/providers/daytona/README.md) / [Modal](sdks/typescript/providers/modal/README.md) / [Blaxel](sdks/typescript/providers/blaxel/README.md) | TypeScript provider notes | +| [Go Daytona](sdks/go/providers/daytona/README.md) / [Modal](sdks/go/providers/modal/README.md) / [Blaxel](sdks/go/providers/blaxel/README.md) | Go provider notes | ## License diff --git a/docs/configuration.md b/docs/configuration.md index eb57da33..240a066b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -22,7 +22,7 @@ await using client = new Client({ ``` Call `client.Close(ctx)` in Go, or `await client.close()` or `await using` in -TypeScript. That closes your connection to Daytona or Modal. The sandboxes keep +TypeScript. That closes your connection to Daytona, Modal, or Blaxel. The sandboxes keep running. You can keep using a handle. The next call returns the provider's result. Closing one handle does not close the client. @@ -30,9 +30,9 @@ result. Closing one handle does not close the client. | Go / TypeScript | Purpose | | ----------------------- | ------------------------------------------------------------------- | -| Provider / `provider` | `daytona.New()` / `daytona()`, or `modal.New()` / `modal()` | +| Provider / `provider` | `daytona.New()` / `daytona()`, `modal.New()` / `modal()`, or `blaxel.New()` / `blaxel()` | | `Auth` / `auth` | One credential shape. Omit it to use the provider's own credentials | -| `Scope` / `scope` | App, environment, organization, or project | +| `Scope` / `scope` | App, environment, organization, project, or workspace | | `Endpoint` / `endpoint` | Provider API URL, where the provider supports one | | `Region` / `region` | Provider target or default placement | | `Timeout` / `timeout` | Default deadline. Go: `sandbox.Duration`. TypeScript: milliseconds | @@ -46,20 +46,21 @@ does not change the handle. ## Provider support -The bindings use Modal v0.11.1 and Daytona v0.225.0. - -| Setting | Modal | Daytona | -| ---------------- | ---------------------------------------------------------- | --------------------------------- | -| API key | Unsupported | `APIKey` | -| Token pair | `TokenID` and `TokenSecret` | Unsupported | -| Bearer/JWT token | Unsupported | `JWTToken`; organization required | -| OAuth refresh | Refresh token, client ID, and one client secret or JWT key | Unsupported | -| Endpoint | Unsupported | API URL | -| Region | Default sandbox region preference | Target | -| App name | Required for app lookup | Unsupported | -| Environment | Client, app, and secret lookup environment | Unsupported | -| Organization ID | Unsupported | Organization | -| Project ID | Unsupported | Unsupported | +The bindings use Modal v0.11.1, Daytona v0.225.0, and Blaxel SDK Go/TS current pins. + +| Setting | Modal | Daytona | Blaxel | +| ---------------- | ---------------------------------------------------------- | --------------------------------- | --------------------------------- | +| API key | Unsupported | `APIKey` | `APIKey` | +| Token pair | `TokenID` and `TokenSecret` | Unsupported | Unsupported | +| Bearer/JWT token | Unsupported | `JWTToken`; organization required | Unsupported | +| OAuth refresh | Refresh token, client ID, and one client secret or JWT key | Unsupported | Unsupported | +| Endpoint | Unsupported | API URL | Unsupported | +| Region | Default sandbox region preference | Target | Required for create | +| App name | Required for app lookup | Unsupported | Unsupported | +| Environment | Client, app, and secret lookup environment | Unsupported | Unsupported | +| Organization ID | Unsupported | Organization | Unsupported | +| Project ID | Unsupported | Unsupported | Unsupported | +| Workspace | Unsupported | Unsupported | Required Blaxel workspace name | A setting the provider does not support fails before the provider client is created. Omit `auth` when the provider should use credentials it already finds diff --git a/docs/filesystem.md b/docs/filesystem.md index b95f3ebe..d6d244f0 100644 --- a/docs/filesystem.md +++ b/docs/filesystem.md @@ -1,6 +1,6 @@ # Filesystem -File calls live on the sandbox handle, next to lifecycle. Daytona and Modal +File calls live on the sandbox handle, next to lifecycle. Daytona, Modal, and Blaxel implement the same methods. Open the handle with `Create`, Daytona `Get`, or Modal `Attach`, then call `Filesystem` / `sandbox.filesystem`. diff --git a/docs/naming.md b/docs/naming.md index 7e762341..f9ba6cde 100644 --- a/docs/naming.md +++ b/docs/naming.md @@ -58,7 +58,7 @@ sdks/typescript/sandbox/ Public TypeScript SDK (@sandbox-kit/sandbox) src/contracts/ Public type aliases over protobuf messages src/metadata/ Metadata map helpers src/internal/ Package-private duration/oneof/strip helpers -sdks/typescript/providers/ Optional providers (@sandbox-kit/daytona, @sandbox-kit/modal) +sdks/typescript/providers/ Optional providers (@sandbox-kit/daytona, @sandbox-kit/modal, @sandbox-kit/blaxel) src/mappings/ Create/info field mappings src/checks/ Provider create checks src/config/ Native SDK client configuration diff --git a/docs/process.md b/docs/process.md index f8fe2571..4db477ba 100644 --- a/docs/process.md +++ b/docs/process.md @@ -1,7 +1,7 @@ # Process Run a command inside an open sandbox and wait for it to finish. Use the same -options on Daytona and Modal. +options on Daytona, Modal, and Blaxel. | You want to | Go | TypeScript | | ------------------------------------- | ----- | ---------- | @@ -79,7 +79,7 @@ Cancel behavior: - **Go** — cancel or deadline on the context ends the wait and returns an error. The handle stays busy until that provider call returns. - **TypeScript** — abort rejects the `await`. The remote command may keep - running; Daytona and Modal exec do not take an `AbortSignal`. The handle + running; Daytona, Modal, and Blaxel exec do not take an `AbortSignal`. The handle still stays busy until that provider call settles, so the next call on the same handle does not overlap it. diff --git a/docs/provider-verification.md b/docs/provider-verification.md index c7deb3a0..8dd3cc61 100644 --- a/docs/provider-verification.md +++ b/docs/provider-verification.md @@ -1,6 +1,6 @@ # Provider reference verification -Go and TypeScript bindings target Modal v0.11.1 and Daytona v0.225.0 +Go and TypeScript bindings target Modal v0.11.1, Daytona v0.225.0, and Blaxel (`@blaxel/core` / `github.com/blaxel-ai/sdk-go`) (`modal@0.11.1`, `@daytona/sdk@0.225.0`). Latest documentation helps discover APIs; pinned SDK source and compilation determine the signatures and serialization used by this checkout. diff --git a/docs/providers.md b/docs/providers.md index f648d5d7..78ab5ae2 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -2,10 +2,10 @@ Install the core package and the provider you use. -| | Daytona | Modal | -| ---------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------- | -| TypeScript | `@sandbox-kit/sandbox` and `@sandbox-kit/daytona` | `@sandbox-kit/sandbox` and `@sandbox-kit/modal` | -| Go | `github.com/sandbox-kit/kit/sdks/go/sandbox` and `.../providers/daytona` | `github.com/sandbox-kit/kit/sdks/go/sandbox` and `.../providers/modal` | +| | Daytona | Modal | Blaxel | +| ---------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| TypeScript | `@sandbox-kit/sandbox` and `@sandbox-kit/daytona` | `@sandbox-kit/sandbox` and `@sandbox-kit/modal` | `@sandbox-kit/sandbox` and `@sandbox-kit/blaxel` | +| Go | `github.com/sandbox-kit/kit/sdks/go/sandbox` and `.../providers/daytona` | `github.com/sandbox-kit/kit/sdks/go/sandbox` and `.../providers/modal` | `github.com/sandbox-kit/kit/sdks/go/sandbox` and `.../providers/blaxel` | ```go client, err := sandbox.NewClient(modal.New(), &sandbox.Config{ @@ -26,16 +26,39 @@ await using client = new Client({ }); ``` + +```go +client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: apiKey}, + }, + }, + Scope: &sandbox.Scope{Workspace: sandbox.Value("my-workspace")}, + Region: sandbox.Value("us-pdx-1"), +}) +``` + +```ts +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: apiKey } }, + scope: { workspace: "my-workspace" }, + region: "us-pdx-1", +}); +``` + ## What each provider can do -| Operation | Modal | Daytona | -| ----------------------------------- | ---------------------------------- | ------------------------------------------ | -| Create | Registry image and an existing app | Default snapshot, named snapshot, or image | -| Open an existing sandbox | `attach` (ID only) | `get` (provider lookup) | -| Start, stop, pause, archive, delete | Unsupported | Supported | -| Terminate | Supported | Unsupported | -| Filesystem | Same eight shared methods | Same eight shared methods | -| Process | Same blocking `Run` | Same blocking `Run` | +| Operation | Modal | Daytona | Blaxel | +| ----------------------------------- | ---------------------------------- | ------------------------------------------ | ------------------------------------------- | +| Create | Registry image and an existing app | Default snapshot, named snapshot, or image | Named image create | +| Open an existing sandbox | `attach` (ID only) | `get` (provider lookup) | `get` / `refresh` by name | +| Start, stop, pause, archive | Unsupported | Supported | Unsupported | +| Delete | Unsupported | Supported | Supported | +| Terminate | Supported | Unsupported | Unsupported | +| Filesystem | Same eight shared methods | Same eight shared methods | Same eight shared methods | +| Process | Same blocking `Run` | Same blocking `Run` | Same blocking `Run` | `supports` reports whether the provider implements that operation. A missing operation fails before the provider call. Daytona still accepts or rejects a @@ -43,7 +66,8 @@ supported call for a particular sandbox, and Kit returns that error. Set Daytona's target with `region` on the client. A placement block on the create request is rejected. Modal accepts cloud and region on the create -request, and rejects a placement target. +request, and rejects a placement target. Blaxel requires `region` on the +client for create and rejects per-request placement. Closing the client closes your connection to the provider. The sandboxes keep running. Closing a handle leaves the sandbox as it is. You can keep using the diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index 916366fe..69073a55 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -6,8 +6,8 @@ from lifecycle operations on an existing sandbox. ## Supported creation settings -Go and TypeScript integrations target Modal v0.11.1 and Daytona v0.225.0 -(`modal@0.11.1`, `@daytona/sdk@0.225.0`). +Go and TypeScript integrations target Modal v0.11.1, Daytona v0.225.0, and +Blaxel (`modal@0.11.1`, `@daytona/sdk@0.225.0`, `@blaxel/core` / `sdk-go`). | Setting | Modal | Daytona | | ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | @@ -23,6 +23,12 @@ Go and TypeScript integrations target Modal v0.11.1 and Daytona v0.225.0 | Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | | Observability | Verbose output | Telemetry endpoint | + +Blaxel create requires a nonblank name and an image source. It maps memory MiB, +labels, environment, ports, outbound domains, maximum lifetime, and idle +terminate. CPU/disk overrides, placement, runtime, isolation, security, and +most egress modes are rejected. Region comes from the client config. + Modal default, snapshot, and warm-pool sources are unavailable. Daytona warm pools and explicit snapshot kinds are unavailable. A field the provider does not support is rejected before the provider call. The error names that field, diff --git a/docs/sandbox-lifecycle.md b/docs/sandbox-lifecycle.md index 9df3e1ba..a7b60f54 100644 --- a/docs/sandbox-lifecycle.md +++ b/docs/sandbox-lifecycle.md @@ -27,7 +27,7 @@ treat zero as no operation timeout. A timeout that is not a whole number of seconds is rejected before the native call. A terminate timeout is how long Kit waits. Modal does not take that duration as its own terminate timeout. With wait, Modal polls until the sandbox exits or the deadline fires. -`Client.Close` / `client.close()` closes your connection to Daytona or Modal +`Client.Close` / `client.close()` closes your connection to Daytona, Modal, or Blaxel and returns that result. It does not stop or delete the sandbox. A later call uses that connection and returns the provider's result. `Sandbox.Close` / `sandbox.close()` does not change the sandbox. You can keep @@ -45,7 +45,7 @@ const sandbox = await client.create(request); await sandbox.stop({ timeout: 60_000 }); ``` -Disposing a client closes your connection to Daytona or Modal. Disposing a +Disposing a client closes your connection to Daytona, Modal, or Blaxel. Disposing a handle does not stop, terminate, or delete the sandbox. You can keep using the handle. diff --git a/examples/README.md b/examples/README.md index 46f9a9f7..bb7c1edb 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,7 +4,7 @@ Independent Go and TypeScript projects. Each selects one provider, loads its own `.env`, and demonstrates one operation. Projects for a provider sit in `filesystem`, `process`, `configuration`, `lifecycle`, or `errors`. Every project README explains what the example does for that provider, with edge -cases drawn from Daytona and Modal behavior. +cases drawn from Daytona, Modal, and Blaxel behavior. ## Run one @@ -24,13 +24,17 @@ Modal needs a token pair and an existing app: [Go](go/modal/configuration/create-modal-sandbox/README.md) · [TypeScript](typescript/modal/configuration/create-modal-sandbox/README.md). +Blaxel needs an API key and workspace: +[Go](go/blaxel/configuration/create-blaxel-sandbox/README.md) · +[TypeScript](typescript/blaxel/configuration/create-blaxel-sandbox/README.md). + Creation examples provision real resources. Filesystem, process, and lifecycle examples use the sandbox ID in `.env`. A filesystem example runs one file operation (read, write, copy, mkdir, list, stat, or remove) on the path you set. A process example runs one command. Stop, terminate, archive, and delete can destroy the sandbox. Error-handling examples fail locally. `Client.Close` closes your connection to -Daytona or Modal. `Sandbox.Close` does not change the sandbox, and you can keep +Daytona, Modal, or Blaxel. `Sandbox.Close` does not change the sandbox, and you can keep using the handle. Indexes: [Go](go/README.md) · [TypeScript](typescript/README.md). @@ -114,6 +118,33 @@ Indexes: [Go](go/README.md) · [TypeScript](typescript/README.md). | Modal termination request | [go](go/modal/lifecycle/request-modal-termination/README.md) | [typescript](typescript/modal/lifecycle/request-modal-termination/README.md) | | Modal close handle | [go](go/modal/lifecycle/close-modal-handle/README.md) | [typescript](typescript/modal/lifecycle/close-modal-handle/README.md) | | Modal errors | [go](go/modal/errors/handle-modal-errors/README.md) | [typescript](typescript/modal/errors/handle-modal-errors/README.md) | +| Blaxel named image create | [go](go/blaxel/configuration/create-blaxel-sandbox/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-sandbox/README.md) | +| Blaxel memory MiB | [go](go/blaxel/configuration/create-blaxel-with-resources/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-with-resources/README.md) | +| Blaxel labels and environment | [go](go/blaxel/configuration/create-blaxel-with-labels/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-with-labels/README.md) | +| Blaxel maximum lifetime TTL | [go](go/blaxel/configuration/create-blaxel-with-ttl/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-with-ttl/README.md) | +| Blaxel idle delete policy | [go](go/blaxel/configuration/create-blaxel-idle/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-idle/README.md) | +| Blaxel client timeout | [go](go/blaxel/configuration/create-blaxel-client-timeout/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-client-timeout/README.md) | +| Blaxel published ports | [go](go/blaxel/configuration/create-blaxel-with-ports/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-with-ports/README.md) | +| Blaxel outbound domains | [go](go/blaxel/configuration/create-blaxel-with-allowlists/README.md) | [typescript](typescript/blaxel/configuration/create-blaxel-with-allowlists/README.md) | +| Blaxel run one command | [go](go/blaxel/process/run-blaxel-command/README.md) | [typescript](typescript/blaxel/process/run-blaxel-command/README.md) | +| Blaxel run one shell string | [go](go/blaxel/process/run-blaxel-shell-command/README.md) | [typescript](typescript/blaxel/process/run-blaxel-shell-command/README.md) | +| Blaxel run with working directory | [go](go/blaxel/process/run-blaxel-with-cwd/README.md) | [typescript](typescript/blaxel/process/run-blaxel-with-cwd/README.md) | +| Blaxel run with environment variables | [go](go/blaxel/process/run-blaxel-with-env/README.md) | [typescript](typescript/blaxel/process/run-blaxel-with-env/README.md) | +| Blaxel run with a whole-second timeout | [go](go/blaxel/process/run-blaxel-with-timeout/README.md) | [typescript](typescript/blaxel/process/run-blaxel-with-timeout/README.md) | +| Blaxel non-zero exit in the result | [go](go/blaxel/process/run-blaxel-check-exit/README.md) | [typescript](typescript/blaxel/process/run-blaxel-check-exit/README.md) | +| Blaxel read one file | [go](go/blaxel/filesystem/read-blaxel-file/README.md) | [typescript](typescript/blaxel/filesystem/read-blaxel-file/README.md) | +| Blaxel write one file | [go](go/blaxel/filesystem/write-blaxel-file/README.md) | [typescript](typescript/blaxel/filesystem/write-blaxel-file/README.md) | +| Blaxel copy a local file in | [go](go/blaxel/filesystem/copy-file-into-blaxel/README.md) | [typescript](typescript/blaxel/filesystem/copy-file-into-blaxel/README.md) | +| Blaxel copy a sandbox file out | [go](go/blaxel/filesystem/copy-file-from-blaxel/README.md) | [typescript](typescript/blaxel/filesystem/copy-file-from-blaxel/README.md) | +| Blaxel create a directory | [go](go/blaxel/filesystem/make-blaxel-directory/README.md) | [typescript](typescript/blaxel/filesystem/make-blaxel-directory/README.md) | +| Blaxel list one directory | [go](go/blaxel/filesystem/list-blaxel-directory/README.md) | [typescript](typescript/blaxel/filesystem/list-blaxel-directory/README.md) | +| Blaxel stat one path | [go](go/blaxel/filesystem/stat-blaxel-file/README.md) | [typescript](typescript/blaxel/filesystem/stat-blaxel-file/README.md) | +| Blaxel remove one path | [go](go/blaxel/filesystem/remove-blaxel-file/README.md) | [typescript](typescript/blaxel/filesystem/remove-blaxel-file/README.md) | +| Blaxel remote retrieval and metadata | [go](go/blaxel/lifecycle/get-blaxel-sandbox/README.md) | [typescript](typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md) | +| Blaxel metadata refresh | [go](go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md) | [typescript](typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md) | +| Blaxel waited deletion | [go](go/blaxel/lifecycle/delete-blaxel-sandbox/README.md) | [typescript](typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md) | +| Blaxel close the handle; sandbox keeps running | [go](go/blaxel/lifecycle/close-blaxel-handle/README.md) | [typescript](typescript/blaxel/lifecycle/close-blaxel-handle/README.md) | +| Blaxel typed validation errors | [go](go/blaxel/errors/handle-blaxel-errors/README.md) | [typescript](typescript/blaxel/errors/handle-blaxel-errors/README.md) | ## Verify locally diff --git a/examples/go/README.md b/examples/go/README.md index 81a92c9d..7c1e6b16 100644 --- a/examples/go/README.md +++ b/examples/go/README.md @@ -22,7 +22,7 @@ child). Filesystem, process, and lifecycle programs use the configured sandbox ID. Read the guide before a destructive stop, terminate, archive, delete, or remove. Error-handling programs fail locally. Closing a handle leaves the sandbox as it is. Closing the client -closes your connection to Daytona or Modal. +closes your connection to Daytona, Modal, or Blaxel. ## Catalog @@ -132,3 +132,35 @@ Repository-wide: `sandbox-kit test go` from `tooling/`. Cleanup failures do not replace creation failures. Modal errors: unsupported disk. Daytona errors: invalid CPU. + +### Blaxel + +| Project | Demonstrates | +| -------------------------------------------------------------------------------------------- | --------------------------------------- | +| [create-blaxel-sandbox](blaxel/configuration/create-blaxel-sandbox/README.md) | named image create | +| [create-blaxel-with-resources](blaxel/configuration/create-blaxel-with-resources/README.md) | memory MiB | +| [create-blaxel-with-labels](blaxel/configuration/create-blaxel-with-labels/README.md) | labels and environment | +| [create-blaxel-with-ttl](blaxel/configuration/create-blaxel-with-ttl/README.md) | maximum lifetime TTL | +| [create-blaxel-idle](blaxel/configuration/create-blaxel-idle/README.md) | idle delete policy | +| [create-blaxel-client-timeout](blaxel/configuration/create-blaxel-client-timeout/README.md) | client timeout | +| [create-blaxel-with-ports](blaxel/configuration/create-blaxel-with-ports/README.md) | published ports | +| [create-blaxel-with-allowlists](blaxel/configuration/create-blaxel-with-allowlists/README.md) | outbound domains | +| [run-blaxel-command](blaxel/process/run-blaxel-command/README.md) | run one command | +| [run-blaxel-shell-command](blaxel/process/run-blaxel-shell-command/README.md) | run one shell string | +| [run-blaxel-with-cwd](blaxel/process/run-blaxel-with-cwd/README.md) | run with working directory | +| [run-blaxel-with-env](blaxel/process/run-blaxel-with-env/README.md) | run with environment variables | +| [run-blaxel-with-timeout](blaxel/process/run-blaxel-with-timeout/README.md) | run with a whole-second timeout | +| [run-blaxel-check-exit](blaxel/process/run-blaxel-check-exit/README.md) | non-zero exit in the result | +| [read-blaxel-file](blaxel/filesystem/read-blaxel-file/README.md) | read one file | +| [write-blaxel-file](blaxel/filesystem/write-blaxel-file/README.md) | write one file | +| [copy-file-into-blaxel](blaxel/filesystem/copy-file-into-blaxel/README.md) | copy a local file in | +| [copy-file-from-blaxel](blaxel/filesystem/copy-file-from-blaxel/README.md) | copy a sandbox file out | +| [make-blaxel-directory](blaxel/filesystem/make-blaxel-directory/README.md) | create a directory | +| [list-blaxel-directory](blaxel/filesystem/list-blaxel-directory/README.md) | list one directory | +| [stat-blaxel-file](blaxel/filesystem/stat-blaxel-file/README.md) | stat one path | +| [remove-blaxel-file](blaxel/filesystem/remove-blaxel-file/README.md) | remove one path | +| [get-blaxel-sandbox](blaxel/lifecycle/get-blaxel-sandbox/README.md) | remote retrieval and metadata | +| [refresh-blaxel-sandbox](blaxel/lifecycle/refresh-blaxel-sandbox/README.md) | metadata refresh | +| [delete-blaxel-sandbox](blaxel/lifecycle/delete-blaxel-sandbox/README.md) | waited deletion | +| [close-blaxel-handle](blaxel/lifecycle/close-blaxel-handle/README.md) | close the handle; sandbox keeps running | +| [handle-blaxel-errors](blaxel/errors/handle-blaxel-errors/README.md) | typed validation errors | diff --git a/examples/go/blaxel/.env.example b/examples/go/blaxel/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-client-timeout/.env.example b/examples/go/blaxel/configuration/create-blaxel-client-timeout/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-client-timeout/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-client-timeout/README.md b/examples/go/blaxel/configuration/create-blaxel-client-timeout/README.md new file mode 100644 index 00000000..2904afa3 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-client-timeout/README.md @@ -0,0 +1,43 @@ +# Create a Blaxel sandbox with a client timeout + +Create one Blaxel sandbox using a shorter client-wide timeout. + +## What this means + +Sets `Config.Timeout` / `timeout` on the shared client. This bounds local waiting for SDK calls, including create. + +## Edge cases + +- Create provisioning timeouts are not mapped on Go; use the client timeout instead. +- TypeScript can also pass a create `provisioning.timeout` (1–50 whole seconds). + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-client-timeout +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-client-timeout/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.mod b/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.mod new file mode 100644 index 00000000..6860e9e3 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-client-timeout + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.sum b/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-client-timeout/main.go b/examples/go/blaxel/configuration/create-blaxel-client-timeout/main.go new file mode 100644 index 00000000..df9546dc --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-client-timeout/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(90 * time.Second), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-client-timeout"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created with client timeout: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/configuration/create-blaxel-idle/.env.example b/examples/go/blaxel/configuration/create-blaxel-idle/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-idle/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-idle/README.md b/examples/go/blaxel/configuration/create-blaxel-idle/README.md new file mode 100644 index 00000000..1ed5e717 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-idle/README.md @@ -0,0 +1,43 @@ +# Create a Blaxel sandbox with idle delete + +Create one Blaxel sandbox that deletes after an idle period. + +## What this means + +Maps `lifetime.idleTerminate` (`PolicyModeAfter`) to a Blaxel `ttl-idle` expiration policy with action delete. + +## Edge cases + +- Disabling idle terminate is not representable and is rejected. +- Minimum idle duration follows Blaxel rules (1 minute). + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-idle +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-idle/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-idle/go.mod b/examples/go/blaxel/configuration/create-blaxel-idle/go.mod new file mode 100644 index 00000000..013a16c9 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-idle/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-idle + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-idle/go.sum b/examples/go/blaxel/configuration/create-blaxel-idle/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-idle/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-idle/main.go b/examples/go/blaxel/configuration/create-blaxel-idle/main.go new file mode 100644 index 00000000..b7f37190 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-idle/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-idle"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + Lifetime: &sandbox.LifetimePolicy{ + IdleTerminate: &sandbox.AutomaticAction{ + Mode: sandbox.PolicyModeAfter, + After: sandbox.Duration(60 * time.Second), + }, + }, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created with 60s idle delete: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/configuration/create-blaxel-sandbox/.env.example b/examples/go/blaxel/configuration/create-blaxel-sandbox/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-sandbox/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-sandbox/README.md b/examples/go/blaxel/configuration/create-blaxel-sandbox/README.md new file mode 100644 index 00000000..dbb8fa46 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-sandbox/README.md @@ -0,0 +1,44 @@ +# Create a Blaxel sandbox + +Create one Blaxel sandbox from a registry image with an explicit name. Prints the sandbox ID and leaves the sandbox running. + +## What this means + +Blaxel create requires a nonblank `name` and an image source. Kit calls native create (not create-if-not-exists). Region comes from the client config (`BL_REGION`, default `us-pdx-1`). Closing the Kit client only drops the local SDK connection. + +## Edge cases + +- Omitting `name` or `source.image` fails before Blaxel is called. +- A create error is not proof that no sandbox was provisioned. +- Reclaim sandboxes with TTL / idle policies or `delete`. + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-sandbox/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-sandbox/go.mod b/examples/go/blaxel/configuration/create-blaxel-sandbox/go.mod new file mode 100644 index 00000000..2d5238e3 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-sandbox/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-sandbox + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-sandbox/go.sum b/examples/go/blaxel/configuration/create-blaxel-sandbox/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-sandbox/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-sandbox/main.go b/examples/go/blaxel/configuration/create-blaxel-sandbox/main.go new file mode 100644 index 00000000..c46e8e40 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-sandbox/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-sandbox"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/.env.example b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/README.md b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/README.md new file mode 100644 index 00000000..38604ccb --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/README.md @@ -0,0 +1,43 @@ +# Create a Blaxel sandbox with outbound domains + +Create one Blaxel sandbox that allows selected outbound domains. + +## What this means + +Maps `network.outboundDomains` to Blaxel `allowedDomains`. Other egress controls are unsupported. + +## Edge cases + +- Restricted egress modes and CIDR allowlists are rejected. +- Domain entries follow Blaxel validation. + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-with-allowlists +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-with-allowlists/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.mod b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.mod new file mode 100644 index 00000000..3eeb6902 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-with-allowlists + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.sum b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/main.go b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/main.go new file mode 100644 index 00000000..4406056d --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-allowlists"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + Network: &sandbox.NetworkConfig{ + OutboundDomains: &sandbox.StringAllowlist{Entries: []string{"example.com"}}, + }, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created with outbound domains: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/configuration/create-blaxel-with-labels/.env.example b/examples/go/blaxel/configuration/create-blaxel-with-labels/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-labels/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-labels/README.md b/examples/go/blaxel/configuration/create-blaxel-with-labels/README.md new file mode 100644 index 00000000..f7580dbb --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-labels/README.md @@ -0,0 +1,42 @@ +# Create a Blaxel sandbox with labels and env + +Create one Blaxel sandbox with labels and environment variables. + +## What this means + +Labels become Blaxel metadata labels. Environment entries are injected into the sandbox runtime. + +## Edge cases + +- Invalid environment names are rejected before Blaxel is called. + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-with-labels +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-with-labels/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-labels/go.mod b/examples/go/blaxel/configuration/create-blaxel-with-labels/go.mod new file mode 100644 index 00000000..99f66c27 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-labels/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-with-labels + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-with-labels/go.sum b/examples/go/blaxel/configuration/create-blaxel-with-labels/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-labels/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-labels/main.go b/examples/go/blaxel/configuration/create-blaxel-with-labels/main.go new file mode 100644 index 00000000..11eee991 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-labels/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-labels"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + Labels: map[string]string{ + "team": "platform", + "purpose": "kit-example", + }, + Environment: map[string]string{ + "NODE_ENV": "development", + }, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created with labels and environment: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/.env.example b/examples/go/blaxel/configuration/create-blaxel-with-ports/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md b/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md new file mode 100644 index 00000000..a1c6ab13 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md @@ -0,0 +1,43 @@ +# Create a Blaxel sandbox with ports + +Create one Blaxel sandbox that publishes selected ports. + +## What this means + +Maps `network.ports` to Blaxel runtime ports. Default/plain → TCP, TLS → TLS, HTTP/2 over TLS → HTTP. + +## Edge cases + +- Other network settings (egress mode, CIDRs, private network, proxies) are rejected. +- Port numbers must be in range. + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-with-ports +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-with-ports/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/go.mod b/examples/go/blaxel/configuration/create-blaxel-with-ports/go.mod new file mode 100644 index 00000000..b1f0076f --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-with-ports + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/go.sum b/examples/go/blaxel/configuration/create-blaxel-with-ports/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/main.go b/examples/go/blaxel/configuration/create-blaxel-with-ports/main.go new file mode 100644 index 00000000..f6bd4983 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-ports"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + Network: &sandbox.NetworkConfig{ + Ports: []*sandbox.PortBinding{ + {Port: 443, Transport: sandbox.PortTransportTLS}, + {Port: 8080, Transport: sandbox.PortTransportPlain}, + }, + }, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created with ports: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/configuration/create-blaxel-with-resources/.env.example b/examples/go/blaxel/configuration/create-blaxel-with-resources/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-resources/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-resources/README.md b/examples/go/blaxel/configuration/create-blaxel-with-resources/README.md new file mode 100644 index 00000000..20fa9d31 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-resources/README.md @@ -0,0 +1,43 @@ +# Create a Blaxel sandbox with memory + +Create one Blaxel sandbox with an explicit memory size in MiB. Blaxel derives CPU from memory. + +## What this means + +Only `resources.memoryMib` is mapped. CPU cores, limits, and disk MiB overrides are rejected. Blaxel requires at least 256 MiB when memory is set. + +## Edge cases + +- Omitted memory keeps the Blaxel default. +- Fractional or sub-256 MiB values fail validation. + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-with-resources +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-with-resources/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-resources/go.mod b/examples/go/blaxel/configuration/create-blaxel-with-resources/go.mod new file mode 100644 index 00000000..f025d36c --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-resources/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-with-resources + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-with-resources/go.sum b/examples/go/blaxel/configuration/create-blaxel-with-resources/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-resources/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-resources/main.go b/examples/go/blaxel/configuration/create-blaxel-with-resources/main.go new file mode 100644 index 00000000..39984fbc --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-resources/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-resources"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + Resources: &sandbox.Resources{MemoryMib: sandbox.Value(uint64(4096))}, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created with 4096 MiB: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ttl/.env.example b/examples/go/blaxel/configuration/create-blaxel-with-ttl/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ttl/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ttl/README.md b/examples/go/blaxel/configuration/create-blaxel-with-ttl/README.md new file mode 100644 index 00000000..fd88526b --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ttl/README.md @@ -0,0 +1,43 @@ +# Create a Blaxel sandbox with maximum lifetime + +Create one Blaxel sandbox that deletes after a maximum lifetime TTL. + +## What this means + +Maps `lifetime.maximumLifetime` to Blaxel max-age TTL. Durations must be whole seconds and meet Blaxel minima (5 minutes for max-age). + +## Edge cases + +- Fractional-second durations are rejected. +- Idle cleanup uses `idleTerminate`, not maximum lifetime. + +## Run + +```sh +cd examples/go/blaxel/configuration/create-blaxel-with-ttl +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/configuration/create-blaxel-with-ttl/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.mod b/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.mod new file mode 100644 index 00000000..9d34f283 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/create-blaxel-with-ttl + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.sum b/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ttl/main.go b/examples/go/blaxel/configuration/create-blaxel-with-ttl/main.go new file mode 100644 index 00000000..d987159c --- /dev/null +++ b/examples/go/blaxel/configuration/create-blaxel-with-ttl/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-create-blaxel-ttl"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + Lifetime: &sandbox.LifetimePolicy{ + MaximumLifetime: sandbox.Duration(time.Hour), + }, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox created with 60-minute maximum lifetime: %s\n", instance.ID()) +} diff --git a/examples/go/blaxel/errors/handle-blaxel-errors/.env.example b/examples/go/blaxel/errors/handle-blaxel-errors/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/go/blaxel/errors/handle-blaxel-errors/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/errors/handle-blaxel-errors/README.md b/examples/go/blaxel/errors/handle-blaxel-errors/README.md new file mode 100644 index 00000000..e61c3202 --- /dev/null +++ b/examples/go/blaxel/errors/handle-blaxel-errors/README.md @@ -0,0 +1,43 @@ +# Handle Blaxel validation errors + +Demonstrate typed validation errors from the Blaxel provider without creating a sandbox. + +## What this means + +Requests an unsupported CPU override so Kit fails before calling Blaxel. Applications inspect `SandboxError` kind and field. + +## Edge cases + +- Does not provision cloud resources when validation fails as expected. +- Authentication still requires a configured client. + +## Run + +```sh +cd examples/go/blaxel/errors/handle-blaxel-errors +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/errors/handle-blaxel-errors/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/errors/handle-blaxel-errors/go.mod b/examples/go/blaxel/errors/handle-blaxel-errors/go.mod new file mode 100644 index 00000000..60a40bdb --- /dev/null +++ b/examples/go/blaxel/errors/handle-blaxel-errors/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/handle-blaxel-errors + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/errors/handle-blaxel-errors/go.sum b/examples/go/blaxel/errors/handle-blaxel-errors/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/errors/handle-blaxel-errors/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/errors/handle-blaxel-errors/main.go b/examples/go/blaxel/errors/handle-blaxel-errors/main.go new file mode 100644 index 00000000..0d659378 --- /dev/null +++ b/examples/go/blaxel/errors/handle-blaxel-errors/main.go @@ -0,0 +1,65 @@ +package main + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + _, err = client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-handle-blaxel-errors"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, + Resources: &sandbox.Resources{CpuCores: sandbox.Value(2.0)}, + }) + if err != nil { + var sandboxError *sandbox.Error + if !errors.As(err, &sandboxError) { + fmt.Fprintln(os.Stdout, err) + return + } + switch sandboxError.Kind { + case sandbox.ErrorKindInvalidArgument, sandbox.ErrorKindUnsupported: + fmt.Fprintf(os.Stdout, "Check %s: %s\n", sandboxError.Field, sandboxError.Message) + case sandbox.ErrorKindAuthentication: + fmt.Fprintln(os.Stdout, "Check your provider credentials.") + default: + fmt.Fprintln(os.Stdout, err) + } + return + } + fmt.Fprintln(os.Stdout, "Sandbox created.") +} diff --git a/examples/go/blaxel/filesystem/copy-file-from-blaxel/.env.example b/examples/go/blaxel/filesystem/copy-file-from-blaxel/.env.example new file mode 100644 index 00000000..38b9be92 --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-from-blaxel/.env.example @@ -0,0 +1,10 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt diff --git a/examples/go/blaxel/filesystem/copy-file-from-blaxel/README.md b/examples/go/blaxel/filesystem/copy-file-from-blaxel/README.md new file mode 100644 index 00000000..cbb28d22 --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-from-blaxel/README.md @@ -0,0 +1,48 @@ +# Copy a file from Blaxel + +Copy a sandbox file out to a local path. + +## What this means + +`copyToLocal` creates a missing local parent directory. + +## Edge cases + +- Remote path must exist. + +## Run + +```sh +cd examples/go/blaxel/filesystem/copy-file-from-blaxel +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/copy-file-from-blaxel/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.mod b/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.mod new file mode 100644 index 00000000..740728bd --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/copy-file-from-blaxel + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.sum b/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go b/examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go new file mode 100644 index 00000000..31048971 --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + local := os.Getenv("BL_LOCAL_PATH") + if err := files.CopyToLocal(context.Background(), remote, local, nil); err != nil { + log.Fatal(err) + } + fmt.Printf("copied %s to %s\n", remote, local) +} diff --git a/examples/go/blaxel/filesystem/copy-file-into-blaxel/.env.example b/examples/go/blaxel/filesystem/copy-file-into-blaxel/.env.example new file mode 100644 index 00000000..38b9be92 --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-into-blaxel/.env.example @@ -0,0 +1,10 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt diff --git a/examples/go/blaxel/filesystem/copy-file-into-blaxel/README.md b/examples/go/blaxel/filesystem/copy-file-into-blaxel/README.md new file mode 100644 index 00000000..a5445d0a --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-into-blaxel/README.md @@ -0,0 +1,48 @@ +# Copy a file into Blaxel + +Copy a local file into a Blaxel sandbox path. + +## What this means + +A missing local file is not found before Blaxel is called (`local_path`). A local directory is an invalid argument. + +## Edge cases + +- Create the local file before running. + +## Run + +```sh +cd examples/go/blaxel/filesystem/copy-file-into-blaxel +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/copy-file-into-blaxel/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.mod b/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.mod new file mode 100644 index 00000000..825811f3 --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/copy-file-into-blaxel + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.sum b/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go b/examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go new file mode 100644 index 00000000..9bbfd67e --- /dev/null +++ b/examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + local := os.Getenv("BL_LOCAL_PATH") + if err := files.CopyFromLocal(context.Background(), local, remote, nil); err != nil { + log.Fatal(err) + } + fmt.Printf("copied %s to %s\n", local, remote) +} diff --git a/examples/go/blaxel/filesystem/list-blaxel-directory/.env.example b/examples/go/blaxel/filesystem/list-blaxel-directory/.env.example new file mode 100644 index 00000000..a3459972 --- /dev/null +++ b/examples/go/blaxel/filesystem/list-blaxel-directory/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp diff --git a/examples/go/blaxel/filesystem/list-blaxel-directory/README.md b/examples/go/blaxel/filesystem/list-blaxel-directory/README.md new file mode 100644 index 00000000..10819665 --- /dev/null +++ b/examples/go/blaxel/filesystem/list-blaxel-directory/README.md @@ -0,0 +1,46 @@ +# List a Blaxel directory + +List entries in one absolute directory path. + +## What this means + +Returns shared directory entries with names and types. + +## Edge cases + +- Missing directories return not found. + +## Run + +```sh +cd examples/go/blaxel/filesystem/list-blaxel-directory +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/list-blaxel-directory/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/list-blaxel-directory/go.mod b/examples/go/blaxel/filesystem/list-blaxel-directory/go.mod new file mode 100644 index 00000000..7902cb79 --- /dev/null +++ b/examples/go/blaxel/filesystem/list-blaxel-directory/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/list-blaxel-directory + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/list-blaxel-directory/go.sum b/examples/go/blaxel/filesystem/list-blaxel-directory/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/list-blaxel-directory/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/list-blaxel-directory/main.go b/examples/go/blaxel/filesystem/list-blaxel-directory/main.go new file mode 100644 index 00000000..9dde6e3e --- /dev/null +++ b/examples/go/blaxel/filesystem/list-blaxel-directory/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + entries, err := files.ListDirectory(context.Background(), remote, nil) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%s (%d entries)\n", remote, len(entries)) + for _, entry := range entries { + fmt.Println(entry.GetName()) + } +} diff --git a/examples/go/blaxel/filesystem/make-blaxel-directory/.env.example b/examples/go/blaxel/filesystem/make-blaxel-directory/.env.example new file mode 100644 index 00000000..6f1368a8 --- /dev/null +++ b/examples/go/blaxel/filesystem/make-blaxel-directory/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example-dir diff --git a/examples/go/blaxel/filesystem/make-blaxel-directory/README.md b/examples/go/blaxel/filesystem/make-blaxel-directory/README.md new file mode 100644 index 00000000..7abdbb9b --- /dev/null +++ b/examples/go/blaxel/filesystem/make-blaxel-directory/README.md @@ -0,0 +1,46 @@ +# Make a Blaxel directory + +Create a directory at an absolute path in a Blaxel sandbox. + +## What this means + +`parents` defaults to true. + +## Edge cases + +- Absolute paths only. + +## Run + +```sh +cd examples/go/blaxel/filesystem/make-blaxel-directory +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example-dir +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/make-blaxel-directory/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/make-blaxel-directory/go.mod b/examples/go/blaxel/filesystem/make-blaxel-directory/go.mod new file mode 100644 index 00000000..22b1ef42 --- /dev/null +++ b/examples/go/blaxel/filesystem/make-blaxel-directory/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/make-blaxel-directory + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/make-blaxel-directory/go.sum b/examples/go/blaxel/filesystem/make-blaxel-directory/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/make-blaxel-directory/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/make-blaxel-directory/main.go b/examples/go/blaxel/filesystem/make-blaxel-directory/main.go new file mode 100644 index 00000000..5059615d --- /dev/null +++ b/examples/go/blaxel/filesystem/make-blaxel-directory/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + if err := files.MakeDirectory(context.Background(), remote, nil); err != nil { + log.Fatal(err) + } + fmt.Printf("created %s\n", remote) +} diff --git a/examples/go/blaxel/filesystem/read-blaxel-file/.env.example b/examples/go/blaxel/filesystem/read-blaxel-file/.env.example new file mode 100644 index 00000000..9b877a81 --- /dev/null +++ b/examples/go/blaxel/filesystem/read-blaxel-file/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt diff --git a/examples/go/blaxel/filesystem/read-blaxel-file/README.md b/examples/go/blaxel/filesystem/read-blaxel-file/README.md new file mode 100644 index 00000000..be6b67cc --- /dev/null +++ b/examples/go/blaxel/filesystem/read-blaxel-file/README.md @@ -0,0 +1,46 @@ +# Read a Blaxel file + +Read one absolute path from a Blaxel sandbox. + +## What this means + +Uses `filesystem.readFile` / `ReadFile`. Paths in the sandbox are absolute. + +## Edge cases + +- Missing files return not found from Blaxel. + +## Run + +```sh +cd examples/go/blaxel/filesystem/read-blaxel-file +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/read-blaxel-file/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/read-blaxel-file/go.mod b/examples/go/blaxel/filesystem/read-blaxel-file/go.mod new file mode 100644 index 00000000..1cf0c077 --- /dev/null +++ b/examples/go/blaxel/filesystem/read-blaxel-file/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/read-blaxel-file + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/read-blaxel-file/go.sum b/examples/go/blaxel/filesystem/read-blaxel-file/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/read-blaxel-file/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/read-blaxel-file/main.go b/examples/go/blaxel/filesystem/read-blaxel-file/main.go new file mode 100644 index 00000000..5287a2f5 --- /dev/null +++ b/examples/go/blaxel/filesystem/read-blaxel-file/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + data, err := files.ReadFile(context.Background(), remote, nil) + if err != nil { + log.Fatal(err) + } + fmt.Printf("read %s (%d bytes)\n", remote, len(data)) +} diff --git a/examples/go/blaxel/filesystem/remove-blaxel-file/.env.example b/examples/go/blaxel/filesystem/remove-blaxel-file/.env.example new file mode 100644 index 00000000..040c9339 --- /dev/null +++ b/examples/go/blaxel/filesystem/remove-blaxel-file/.env.example @@ -0,0 +1,10 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Set true for recursive remove. +BL_RECURSIVE= diff --git a/examples/go/blaxel/filesystem/remove-blaxel-file/README.md b/examples/go/blaxel/filesystem/remove-blaxel-file/README.md new file mode 100644 index 00000000..3e7d8de2 --- /dev/null +++ b/examples/go/blaxel/filesystem/remove-blaxel-file/README.md @@ -0,0 +1,48 @@ +# Remove a Blaxel path + +Remove one absolute path in a Blaxel sandbox. + +## What this means + +Set `BL_RECURSIVE=true` to remove recursively when supported by options. + +## Edge cases + +- Removing a missing path returns not found. + +## Run + +```sh +cd examples/go/blaxel/filesystem/remove-blaxel-file +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Set true for recursive remove. +BL_RECURSIVE= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/remove-blaxel-file/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/remove-blaxel-file/go.mod b/examples/go/blaxel/filesystem/remove-blaxel-file/go.mod new file mode 100644 index 00000000..59fac4e7 --- /dev/null +++ b/examples/go/blaxel/filesystem/remove-blaxel-file/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/remove-blaxel-file + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/remove-blaxel-file/go.sum b/examples/go/blaxel/filesystem/remove-blaxel-file/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/remove-blaxel-file/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/remove-blaxel-file/main.go b/examples/go/blaxel/filesystem/remove-blaxel-file/main.go new file mode 100644 index 00000000..f80182a6 --- /dev/null +++ b/examples/go/blaxel/filesystem/remove-blaxel-file/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + var options *sandbox.RemoveOptions + if os.Getenv("BL_RECURSIVE") == "true" { + options = &sandbox.RemoveOptions{Recursive: sandbox.Value(true)} + } + if err := files.Remove(context.Background(), remote, options); err != nil { + log.Fatal(err) + } + fmt.Printf("removed %s\n", remote) +} diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/.env.example b/examples/go/blaxel/filesystem/stat-blaxel-file/.env.example new file mode 100644 index 00000000..9b877a81 --- /dev/null +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/README.md b/examples/go/blaxel/filesystem/stat-blaxel-file/README.md new file mode 100644 index 00000000..0ea7fe23 --- /dev/null +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/README.md @@ -0,0 +1,46 @@ +# Stat a Blaxel path + +Stat one absolute path in a Blaxel sandbox. + +## What this means + +Returns type and size for the path. + +## Edge cases + +- Missing paths return not found. + +## Run + +```sh +cd examples/go/blaxel/filesystem/stat-blaxel-file +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/stat-blaxel-file/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/go.mod b/examples/go/blaxel/filesystem/stat-blaxel-file/go.mod new file mode 100644 index 00000000..86140b67 --- /dev/null +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/stat-blaxel-file + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/go.sum b/examples/go/blaxel/filesystem/stat-blaxel-file/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/main.go b/examples/go/blaxel/filesystem/stat-blaxel-file/main.go new file mode 100644 index 00000000..91c441bc --- /dev/null +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + info, err := files.Stat(context.Background(), remote, nil) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%s type=%s size=%d\n", info.GetPath(), info.GetType(), info.GetSize()) +} diff --git a/examples/go/blaxel/filesystem/write-blaxel-file/.env.example b/examples/go/blaxel/filesystem/write-blaxel-file/.env.example new file mode 100644 index 00000000..9b877a81 --- /dev/null +++ b/examples/go/blaxel/filesystem/write-blaxel-file/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt diff --git a/examples/go/blaxel/filesystem/write-blaxel-file/README.md b/examples/go/blaxel/filesystem/write-blaxel-file/README.md new file mode 100644 index 00000000..0e777d0e --- /dev/null +++ b/examples/go/blaxel/filesystem/write-blaxel-file/README.md @@ -0,0 +1,46 @@ +# Write a Blaxel file + +Write one absolute path in a Blaxel sandbox. + +## What this means + +`parents` defaults to true. `parents: false` is rejected. + +## Edge cases + +- Absolute remote paths only. + +## Run + +```sh +cd examples/go/blaxel/filesystem/write-blaxel-file +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/filesystem/write-blaxel-file/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/filesystem/write-blaxel-file/go.mod b/examples/go/blaxel/filesystem/write-blaxel-file/go.mod new file mode 100644 index 00000000..eedfe36e --- /dev/null +++ b/examples/go/blaxel/filesystem/write-blaxel-file/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/write-blaxel-file + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/filesystem/write-blaxel-file/go.sum b/examples/go/blaxel/filesystem/write-blaxel-file/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/filesystem/write-blaxel-file/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/filesystem/write-blaxel-file/main.go b/examples/go/blaxel/filesystem/write-blaxel-file/main.go new file mode 100644 index 00000000..0e9a0353 --- /dev/null +++ b/examples/go/blaxel/filesystem/write-blaxel-file/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + files := instance.Filesystem() + remote := os.Getenv("BL_REMOTE_PATH") + body := []byte("hello from sandbox-kit\n") + if err := files.WriteFile(context.Background(), remote, body, nil); err != nil { + log.Fatal(err) + } + fmt.Printf("wrote %s (%d bytes)\n", remote, len(body)) +} diff --git a/examples/go/blaxel/lifecycle/close-blaxel-handle/.env.example b/examples/go/blaxel/lifecycle/close-blaxel-handle/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/go/blaxel/lifecycle/close-blaxel-handle/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/lifecycle/close-blaxel-handle/README.md b/examples/go/blaxel/lifecycle/close-blaxel-handle/README.md new file mode 100644 index 00000000..960b8477 --- /dev/null +++ b/examples/go/blaxel/lifecycle/close-blaxel-handle/README.md @@ -0,0 +1,45 @@ +# Close a Blaxel sandbox handle + +Close one Blaxel sandbox handle without contacting Blaxel or changing the sandbox. + +## What this means + +`Close` releases local handle resources only. The sandbox keeps running until TTL, idle policy, or delete. + +## Edge cases + +- After close, further handle calls fail locally. +- Client close also does not delete sandboxes. + +## Run + +```sh +cd examples/go/blaxel/lifecycle/close-blaxel-handle +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/lifecycle/close-blaxel-handle/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/lifecycle/close-blaxel-handle/go.mod b/examples/go/blaxel/lifecycle/close-blaxel-handle/go.mod new file mode 100644 index 00000000..e672480a --- /dev/null +++ b/examples/go/blaxel/lifecycle/close-blaxel-handle/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/close-blaxel-handle + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/lifecycle/close-blaxel-handle/go.sum b/examples/go/blaxel/lifecycle/close-blaxel-handle/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/lifecycle/close-blaxel-handle/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/lifecycle/close-blaxel-handle/main.go b/examples/go/blaxel/lifecycle/close-blaxel-handle/main.go new file mode 100644 index 00000000..c5949309 --- /dev/null +++ b/examples/go/blaxel/lifecycle/close-blaxel-handle/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + if err := instance.Close(context.Background()); err != nil { + log.Fatal(err) + } + fmt.Printf("Closed the handle for Blaxel sandbox %s. The sandbox was not changed.\n", instance.ID()) +} diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/.env.example b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md new file mode 100644 index 00000000..a6a19ef6 --- /dev/null +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md @@ -0,0 +1,45 @@ +# Delete a Blaxel sandbox + +Delete an existing Blaxel sandbox by name and wait for completion. + +## What this means + +`Delete` permanently removes the Blaxel sandbox. Start, stop, pause, archive, and terminate are unsupported. + +## Edge cases + +- Deletion destroys the sandbox and its filesystem. +- Closing the handle is not deletion. + +## Run + +```sh +cd examples/go/blaxel/lifecycle/delete-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.mod b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.mod new file mode 100644 index 00000000..e0fa5cd2 --- /dev/null +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/delete-blaxel-sandbox + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.sum b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go new file mode 100644 index 00000000..7d3dfd2f --- /dev/null +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + if !instance.Supports(sandbox.LifecycleCapabilityDelete) { + log.Fatalf("sandbox %s does not support delete", instance.ID()) + } + if err := instance.Delete(context.Background(), &sandbox.DeleteOptions{Wait: sandbox.Value(true)}); err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel sandbox deleted: %s (terminal=%t)\n", instance.ID(), instance.Info().GetTerminal()) +} diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/.env.example b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md new file mode 100644 index 00000000..1af1eee0 --- /dev/null +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md @@ -0,0 +1,45 @@ +# Get a Blaxel sandbox + +Look up an existing Blaxel sandbox by name and print metadata. + +## What this means + +`Get` retrieves the sandbox by name (the Blaxel id). Use the name printed by a create example as `BL_SANDBOX_ID`. + +## Edge cases + +- Unknown names return not found. +- Attach is unsupported; use get. + +## Run + +```sh +cd examples/go/blaxel/lifecycle/get-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.mod b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.mod new file mode 100644 index 00000000..49ad536b --- /dev/null +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/get-blaxel-sandbox + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.sum b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go new file mode 100644 index 00000000..3752a153 --- /dev/null +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + info := instance.Info() + fmt.Printf("Blaxel sandbox: id=%s state=%s native=%s capabilities=%v\n", info.Id, info.State, info.GetProviderState(), instance.Capabilities()) +} diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md new file mode 100644 index 00000000..c4efbbca --- /dev/null +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md @@ -0,0 +1,44 @@ +# Refresh a Blaxel sandbox + +Refresh metadata for an existing Blaxel sandbox by name. + +## What this means + +`Refresh` re-fetches provider metadata onto the handle. + +## Edge cases + +- Unsupported when the handle does not report the refresh capability. + +## Run + +```sh +cd examples/go/blaxel/lifecycle/refresh-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.mod b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.mod new file mode 100644 index 00000000..c58da959 --- /dev/null +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/refresh-blaxel-sandbox + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.sum b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go new file mode 100644 index 00000000..db92d6b8 --- /dev/null +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + if !instance.Supports(sandbox.LifecycleCapabilityRefresh) { + log.Fatalf("sandbox %s does not support refresh", instance.ID()) + } + if err := instance.Refresh(context.Background()); err != nil { + log.Fatal(err) + } + fmt.Printf("Blaxel metadata refreshed: %s (%s)\n", instance.ID(), instance.Info().State) +} diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/.env.example b/examples/go/blaxel/process/run-blaxel-check-exit/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-check-exit/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/README.md b/examples/go/blaxel/process/run-blaxel-check-exit/README.md new file mode 100644 index 00000000..0297295a --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-check-exit/README.md @@ -0,0 +1,44 @@ +# Check a Blaxel non-zero exit + +Run a command that exits non-zero and read the exit code from the result. + +## What this means + +A non-zero exit is not an SDK error. Applications check `exitCode` / `GetExitCode`. + +## Edge cases + +- Transport and validation failures still raise errors. + +## Run + +```sh +cd examples/go/blaxel/process/run-blaxel-check-exit +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/process/run-blaxel-check-exit/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/go.mod b/examples/go/blaxel/process/run-blaxel-check-exit/go.mod new file mode 100644 index 00000000..7a818f73 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-check-exit/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/run-blaxel-check-exit + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/go.sum b/examples/go/blaxel/process/run-blaxel-check-exit/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-check-exit/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/main.go b/examples/go/blaxel/process/run-blaxel-check-exit/main.go new file mode 100644 index 00000000..06db9684 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-check-exit/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Shell: sandbox.Value("exit 7"), + }) + if err != nil { + log.Fatal(err) + } + if result.GetExitCode() != 7 { + log.Fatalf("want exit 7, got %d", result.GetExitCode()) + } + fmt.Printf("command finished with exit=%d\n", result.GetExitCode()) +} diff --git a/examples/go/blaxel/process/run-blaxel-command/.env.example b/examples/go/blaxel/process/run-blaxel-command/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-command/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/process/run-blaxel-command/README.md b/examples/go/blaxel/process/run-blaxel-command/README.md new file mode 100644 index 00000000..8d1e1379 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-command/README.md @@ -0,0 +1,45 @@ +# Run a Blaxel command + +Run one argv command in a Blaxel sandbox and print exit code plus streams. + +## What this means + +`Process.Run` is blocking. Set exactly one of `argv` or `shell`. Argv is sent as a POSIX-quoted shell string. + +## Edge cases + +- Non-zero exit codes are success for the SDK—read `exitCode`. +- Blaxel returns stdout and stderr. + +## Run + +```sh +cd examples/go/blaxel/process/run-blaxel-command +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/process/run-blaxel-command/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/process/run-blaxel-command/go.mod b/examples/go/blaxel/process/run-blaxel-command/go.mod new file mode 100644 index 00000000..6ea1b384 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-command/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/run-blaxel-command + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/process/run-blaxel-command/go.sum b/examples/go/blaxel/process/run-blaxel-command/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-command/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/process/run-blaxel-command/main.go b/examples/go/blaxel/process/run-blaxel-command/main.go new file mode 100644 index 00000000..8f2ab81e --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-command/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Argv: []string{"echo", "hello from kit"}, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("exit=%d stdout=%q stderr=%q\n", result.GetExitCode(), string(result.GetStdout()), string(result.GetStderr())) +} diff --git a/examples/go/blaxel/process/run-blaxel-shell-command/.env.example b/examples/go/blaxel/process/run-blaxel-shell-command/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-shell-command/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/process/run-blaxel-shell-command/README.md b/examples/go/blaxel/process/run-blaxel-shell-command/README.md new file mode 100644 index 00000000..dcd26a4d --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-shell-command/README.md @@ -0,0 +1,44 @@ +# Run a Blaxel shell command + +Run one shell string in a Blaxel sandbox. + +## What this means + +Uses `shell` instead of `argv`. Exactly one of the two must be set. + +## Edge cases + +- Setting both argv and shell is rejected. + +## Run + +```sh +cd examples/go/blaxel/process/run-blaxel-shell-command +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/process/run-blaxel-shell-command/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/process/run-blaxel-shell-command/go.mod b/examples/go/blaxel/process/run-blaxel-shell-command/go.mod new file mode 100644 index 00000000..e55c0e3b --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-shell-command/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/run-blaxel-shell-command + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/process/run-blaxel-shell-command/go.sum b/examples/go/blaxel/process/run-blaxel-shell-command/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-shell-command/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/process/run-blaxel-shell-command/main.go b/examples/go/blaxel/process/run-blaxel-shell-command/main.go new file mode 100644 index 00000000..ba79844a --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-shell-command/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Shell: sandbox.Value(`echo "hello from shell"`), + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("exit=%d stdout=%q\n", result.GetExitCode(), string(result.GetStdout())) +} diff --git a/examples/go/blaxel/process/run-blaxel-with-cwd/.env.example b/examples/go/blaxel/process/run-blaxel-with-cwd/.env.example new file mode 100644 index 00000000..c93fc1d4 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-cwd/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Working directory inside the sandbox. +BL_WORKING_DIRECTORY=/tmp diff --git a/examples/go/blaxel/process/run-blaxel-with-cwd/README.md b/examples/go/blaxel/process/run-blaxel-with-cwd/README.md new file mode 100644 index 00000000..cbdde84c --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-cwd/README.md @@ -0,0 +1,46 @@ +# Run a Blaxel command with cwd + +Run a command with an explicit working directory. + +## What this means + +Sets `workingDirectory` / `WorkingDirectory` for the process. + +## Edge cases + +- Paths in the sandbox are absolute. + +## Run + +```sh +cd examples/go/blaxel/process/run-blaxel-with-cwd +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Working directory inside the sandbox. +BL_WORKING_DIRECTORY=/tmp +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/process/run-blaxel-with-cwd/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/process/run-blaxel-with-cwd/go.mod b/examples/go/blaxel/process/run-blaxel-with-cwd/go.mod new file mode 100644 index 00000000..d4b67f0f --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-cwd/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/run-blaxel-with-cwd + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/process/run-blaxel-with-cwd/go.sum b/examples/go/blaxel/process/run-blaxel-with-cwd/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-cwd/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/process/run-blaxel-with-cwd/main.go b/examples/go/blaxel/process/run-blaxel-with-cwd/main.go new file mode 100644 index 00000000..ad5d1f4e --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-cwd/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + cwd := os.Getenv("BL_WORKING_DIRECTORY") + if cwd == "" { + cwd = "/tmp" + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Argv: []string{"pwd"}, + WorkingDirectory: sandbox.Value(cwd), + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("exit=%d cwd_out=%q\n", result.GetExitCode(), string(result.GetStdout())) +} diff --git a/examples/go/blaxel/process/run-blaxel-with-env/.env.example b/examples/go/blaxel/process/run-blaxel-with-env/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-env/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/process/run-blaxel-with-env/README.md b/examples/go/blaxel/process/run-blaxel-with-env/README.md new file mode 100644 index 00000000..bebb50f3 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-env/README.md @@ -0,0 +1,44 @@ +# Run a Blaxel command with env + +Run a command with environment variables for that process. + +## What this means + +Process environment is separate from create-time sandbox environment. + +## Edge cases + +- Invalid names follow the same env-name rules as create. + +## Run + +```sh +cd examples/go/blaxel/process/run-blaxel-with-env +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/process/run-blaxel-with-env/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/process/run-blaxel-with-env/go.mod b/examples/go/blaxel/process/run-blaxel-with-env/go.mod new file mode 100644 index 00000000..1d06483e --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-env/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/run-blaxel-with-env + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/process/run-blaxel-with-env/go.sum b/examples/go/blaxel/process/run-blaxel-with-env/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-env/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/process/run-blaxel-with-env/main.go b/examples/go/blaxel/process/run-blaxel-with-env/main.go new file mode 100644 index 00000000..95668282 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-env/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Argv: []string{"printenv", "KIT_DEMO"}, + Environment: map[string]string{"KIT_DEMO": "from-kit"}, + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("exit=%d stdout=%q\n", result.GetExitCode(), string(result.GetStdout())) +} diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/.env.example b/examples/go/blaxel/process/run-blaxel-with-timeout/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/README.md b/examples/go/blaxel/process/run-blaxel-with-timeout/README.md new file mode 100644 index 00000000..d07d3b78 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/README.md @@ -0,0 +1,45 @@ +# Run a Blaxel command with timeout + +Cap a Blaxel Process.Run with a whole-second timeout. + +## What this means + +Command timeouts must be whole seconds. Omit or zero for no Blaxel command limit. + +## Edge cases + +- `1500` ms is rejected; `2000` is two seconds. +- Canceling the local wait does not guarantee the remote command stops. + +## Run + +```sh +cd examples/go/blaxel/process/run-blaxel-with-timeout +cp .env.example .env +# Fill in the values below, then: +go run . +``` + +Requires Go 1.26.0+ and this checkout. Skip copying if `.env` already exists. +Local check: `GOWORK=off go test ./...`. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [typescript twin](../../../../typescript/blaxel/process/run-blaxel-with-timeout/README.md) +- Catalog: [examples/go/README.md](../../../README.md) diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/go.mod b/examples/go/blaxel/process/run-blaxel-with-timeout/go.mod new file mode 100644 index 00000000..23b8b637 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/go.mod @@ -0,0 +1,40 @@ +module github.com/sandbox-kit/kit/examples/go/blaxel/run-blaxel-with-timeout + +go 1.26.0 + +require ( + github.com/joho/godotenv v1.5.1 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect + buf.build/go/protovalidate v1.4.0 // indirect + cel.dev/cel-go v0.32.0 // indirect + cel.dev/expr v0.25.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/blaxel-ai/sdk-go v0.31.0 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/contracts v0.1.5 // indirect + github.com/sandbox-kit/kit/sdks/go/internal/sdk v0.1.5 // indirect + github.com/tidwall/gjson v1.19.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/text v0.41.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect + google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/sandbox-kit/kit/sdks/go/sandbox => ../../../../../sdks/go/sandbox + +replace github.com/sandbox-kit/kit/sdks/go/providers/blaxel => ../../../../../sdks/go/providers/blaxel + +replace github.com/sandbox-kit/kit/sdks/go/internal/contracts => ../../../../../sdks/go/internal/contracts + +replace github.com/sandbox-kit/kit/sdks/go/internal/sdk => ../../../../../sdks/go/internal/sdk diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/go.sum b/examples/go/blaxel/process/run-blaxel-with-timeout/go.sum new file mode 100644 index 00000000..6c920856 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/go.sum @@ -0,0 +1,51 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= +cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= +cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/blaxel-ai/sdk-go v0.31.0 h1:quGwQdIr1H7UzCGe37AK9Xag5rpMIAm0LqLeaj0S+5o= +github.com/blaxel-ai/sdk-go v0.31.0/go.mod h1:16T4C74vB/02MV8JjB3Uf9Ag4RbFbDKjdatrorX9E/U= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e h1:01Ju2A/fZKkci4zqx0eZxw//DnRYOnBiGJG14hFBhO8= +golang.org/x/exp v0.0.0-20260820142414-ca536658362e/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI= +google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/main.go b/examples/go/blaxel/process/run-blaxel-with-timeout/main.go new file mode 100644 index 00000000..b520e114 --- /dev/null +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/joho/godotenv" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("copy .env.example to .env and fill in its values: %v", err) + } + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{ + Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{ + Key: os.Getenv("BL_API_KEY"), + }, + }, + }, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + } + client, err := sandbox.NewClient(blaxel.New(), config) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + if err != nil { + log.Fatal(err) + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Argv: []string{"echo", "hello with timeout"}, + Timeout: sandbox.Duration(5 * time.Second), + }) + if err != nil { + log.Fatal(err) + } + fmt.Printf("exit=%d stdout=%q stderr=%q\n", result.GetExitCode(), string(result.GetStdout()), string(result.GetStderr())) +} diff --git a/examples/typescript/README.md b/examples/typescript/README.md index 2e3e90b1..e55bf2fd 100644 --- a/examples/typescript/README.md +++ b/examples/typescript/README.md @@ -22,7 +22,7 @@ npm start Creation examples provision real cloud resources. Filesystem, process, and lifecycle examples act on the sandbox ID in `.env`. Closing a handle leaves the sandbox as it is. Closing -the client closes your connection to Daytona or Modal. Modal setup: +the client closes your connection to Daytona, Modal, or Blaxel. Modal setup: [create-modal-sandbox](modal/configuration/create-modal-sandbox/README.md). ## Catalog @@ -121,3 +121,35 @@ GOWORK=off go run ./cmd/sandbox-kit test typescript ``` Builds and tests the SDK packages first, then every example with a `package.json`. + +### Blaxel + +| Project | Demonstrates | +| -------------------------------------------------------------------------------------------- | --------------------------------------- | +| [create-blaxel-sandbox](blaxel/configuration/create-blaxel-sandbox/README.md) | named image create | +| [create-blaxel-with-resources](blaxel/configuration/create-blaxel-with-resources/README.md) | memory MiB | +| [create-blaxel-with-labels](blaxel/configuration/create-blaxel-with-labels/README.md) | labels and environment | +| [create-blaxel-with-ttl](blaxel/configuration/create-blaxel-with-ttl/README.md) | maximum lifetime TTL | +| [create-blaxel-idle](blaxel/configuration/create-blaxel-idle/README.md) | idle delete policy | +| [create-blaxel-client-timeout](blaxel/configuration/create-blaxel-client-timeout/README.md) | client timeout | +| [create-blaxel-with-ports](blaxel/configuration/create-blaxel-with-ports/README.md) | published ports | +| [create-blaxel-with-allowlists](blaxel/configuration/create-blaxel-with-allowlists/README.md) | outbound domains | +| [run-blaxel-command](blaxel/process/run-blaxel-command/README.md) | run one command | +| [run-blaxel-shell-command](blaxel/process/run-blaxel-shell-command/README.md) | run one shell string | +| [run-blaxel-with-cwd](blaxel/process/run-blaxel-with-cwd/README.md) | run with working directory | +| [run-blaxel-with-env](blaxel/process/run-blaxel-with-env/README.md) | run with environment variables | +| [run-blaxel-with-timeout](blaxel/process/run-blaxel-with-timeout/README.md) | run with a whole-second timeout | +| [run-blaxel-check-exit](blaxel/process/run-blaxel-check-exit/README.md) | non-zero exit in the result | +| [read-blaxel-file](blaxel/filesystem/read-blaxel-file/README.md) | read one file | +| [write-blaxel-file](blaxel/filesystem/write-blaxel-file/README.md) | write one file | +| [copy-file-into-blaxel](blaxel/filesystem/copy-file-into-blaxel/README.md) | copy a local file in | +| [copy-file-from-blaxel](blaxel/filesystem/copy-file-from-blaxel/README.md) | copy a sandbox file out | +| [make-blaxel-directory](blaxel/filesystem/make-blaxel-directory/README.md) | create a directory | +| [list-blaxel-directory](blaxel/filesystem/list-blaxel-directory/README.md) | list one directory | +| [stat-blaxel-file](blaxel/filesystem/stat-blaxel-file/README.md) | stat one path | +| [remove-blaxel-file](blaxel/filesystem/remove-blaxel-file/README.md) | remove one path | +| [get-blaxel-sandbox](blaxel/lifecycle/get-blaxel-sandbox/README.md) | remote retrieval and metadata | +| [refresh-blaxel-sandbox](blaxel/lifecycle/refresh-blaxel-sandbox/README.md) | metadata refresh | +| [delete-blaxel-sandbox](blaxel/lifecycle/delete-blaxel-sandbox/README.md) | waited deletion | +| [close-blaxel-handle](blaxel/lifecycle/close-blaxel-handle/README.md) | close the handle; sandbox keeps running | +| [handle-blaxel-errors](blaxel/errors/handle-blaxel-errors/README.md) | typed validation errors | diff --git a/examples/typescript/blaxel/.env.example b/examples/typescript/blaxel/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/README.md b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/README.md new file mode 100644 index 00000000..6c7b1a04 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/README.md @@ -0,0 +1,45 @@ +# Create a Blaxel sandbox with a client timeout + +Create one Blaxel sandbox using a shorter client-wide timeout. + +## What this means + +Sets `Config.Timeout` / `timeout` on the shared client. This bounds local waiting for SDK calls, including create. + +## Edge cases + +- Create provisioning timeouts are not mapped on Go; use the client timeout instead. +- TypeScript can also pass a create `provisioning.timeout` (1–50 whole seconds). + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-client-timeout +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-client-timeout/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package-lock.json new file mode 100644 index 00000000..474e2db6 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-client-timeout", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-client-timeout", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package.json b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package.json new file mode 100644 index 00000000..eb5b69d4 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-client-timeout", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/src/main.ts new file mode 100644 index 00000000..7dc77d67 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/src/main.ts @@ -0,0 +1,18 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 90000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-client-timeout", + source: { image: { reference: "blaxel/base-image:latest" } }, +}); +console.log(`Blaxel sandbox created with client timeout: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-idle/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-idle/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/README.md b/examples/typescript/blaxel/configuration/create-blaxel-idle/README.md new file mode 100644 index 00000000..80a76660 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/README.md @@ -0,0 +1,45 @@ +# Create a Blaxel sandbox with idle delete + +Create one Blaxel sandbox that deletes after an idle period. + +## What this means + +Maps `lifetime.idleTerminate` (`PolicyModeAfter`) to a Blaxel `ttl-idle` expiration policy with action delete. + +## Edge cases + +- Disabling idle terminate is not representable and is rejected. +- Minimum idle duration follows Blaxel rules (1 minute). + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-idle +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-idle/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-idle/package-lock.json new file mode 100644 index 00000000..6420f637 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-idle", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-idle", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/package.json b/examples/typescript/blaxel/configuration/create-blaxel-idle/package.json new file mode 100644 index 00000000..40cbe33f --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-idle", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-idle/src/main.ts new file mode 100644 index 00000000..cac21075 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/src/main.ts @@ -0,0 +1,19 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client, PolicyMode } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-idle", + source: { image: { reference: "blaxel/base-image:latest" } }, + lifetime: { idleTerminate: { mode: PolicyMode.After, after: 60_000 } }, +}); +console.log(`Blaxel sandbox created with 60s idle delete: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-idle/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md new file mode 100644 index 00000000..93b6e20d --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md @@ -0,0 +1,46 @@ +# Create a Blaxel sandbox + +Create one Blaxel sandbox from a registry image with an explicit name. Prints the sandbox ID and leaves the sandbox running. + +## What this means + +Blaxel create requires a nonblank `name` and an image source. Kit calls native create (not create-if-not-exists). Region comes from the client config (`BL_REGION`, default `us-pdx-1`). Closing the Kit client only drops the local SDK connection. + +## Edge cases + +- Omitting `name` or `source.image` fails before Blaxel is called. +- A create error is not proof that no sandbox was provisioned. +- Reclaim sandboxes with TTL / idle policies or `delete`. + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-sandbox/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package-lock.json new file mode 100644 index 00000000..e60ca9fb --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-sandbox", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-sandbox", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package.json b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package.json new file mode 100644 index 00000000..377374ac --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-sandbox", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/src/main.ts new file mode 100644 index 00000000..5990aa71 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/src/main.ts @@ -0,0 +1,18 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-sandbox", + source: { image: { reference: "blaxel/base-image:latest" } }, +}); +console.log(`Blaxel sandbox created: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/README.md new file mode 100644 index 00000000..ed063530 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/README.md @@ -0,0 +1,45 @@ +# Create a Blaxel sandbox with outbound domains + +Create one Blaxel sandbox that allows selected outbound domains. + +## What this means + +Maps `network.outboundDomains` to Blaxel `allowedDomains`. Other egress controls are unsupported. + +## Edge cases + +- Restricted egress modes and CIDR allowlists are rejected. +- Domain entries follow Blaxel validation. + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-with-allowlists +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-with-allowlists/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package-lock.json new file mode 100644 index 00000000..ea081830 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-allowlists", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-with-allowlists", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package.json b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package.json new file mode 100644 index 00000000..633d5941 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-allowlists", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/src/main.ts new file mode 100644 index 00000000..61b7f9e2 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/src/main.ts @@ -0,0 +1,19 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-allowlists", + source: { image: { reference: "blaxel/base-image:latest" } }, + network: { outboundDomains: ["example.com"] }, +}); +console.log(`Blaxel sandbox created with outbound domains: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/README.md new file mode 100644 index 00000000..3b78ed50 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/README.md @@ -0,0 +1,44 @@ +# Create a Blaxel sandbox with labels and env + +Create one Blaxel sandbox with labels and environment variables. + +## What this means + +Labels become Blaxel metadata labels. Environment entries are injected into the sandbox runtime. + +## Edge cases + +- Invalid environment names are rejected before Blaxel is called. + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-with-labels +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-with-labels/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package-lock.json new file mode 100644 index 00000000..1785c040 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-labels", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-with-labels", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package.json b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package.json new file mode 100644 index 00000000..769ad97e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-labels", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/src/main.ts new file mode 100644 index 00000000..d0b43514 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/src/main.ts @@ -0,0 +1,20 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-labels", + source: { image: { reference: "blaxel/base-image:latest" } }, + labels: { team: "platform", purpose: "kit-example" }, + environment: { NODE_ENV: "development" }, +}); +console.log(`Blaxel sandbox created with labels and environment: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md new file mode 100644 index 00000000..f6347a84 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md @@ -0,0 +1,45 @@ +# Create a Blaxel sandbox with ports + +Create one Blaxel sandbox that publishes selected ports. + +## What this means + +Maps `network.ports` to Blaxel runtime ports. Default/plain → TCP, TLS → TLS, HTTP/2 over TLS → HTTP. + +## Edge cases + +- Other network settings (egress mode, CIDRs, private network, proxies) are rejected. +- Port numbers must be in range. + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-with-ports +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-with-ports/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package-lock.json new file mode 100644 index 00000000..e8d0c3a2 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-ports", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-with-ports", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package.json new file mode 100644 index 00000000..0d9ec92c --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-ports", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/src/main.ts new file mode 100644 index 00000000..53cc6c90 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/src/main.ts @@ -0,0 +1,24 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client, PortTransport } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-ports", + source: { image: { reference: "blaxel/base-image:latest" } }, + network: { + ports: [ + { port: 443, transport: PortTransport.Tls }, + { port: 8080, transport: PortTransport.Plain }, + ], + }, +}); +console.log(`Blaxel sandbox created with ports: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/README.md new file mode 100644 index 00000000..d4848b99 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/README.md @@ -0,0 +1,45 @@ +# Create a Blaxel sandbox with memory + +Create one Blaxel sandbox with an explicit memory size in MiB. Blaxel derives CPU from memory. + +## What this means + +Only `resources.memoryMib` is mapped. CPU cores, limits, and disk MiB overrides are rejected. Blaxel requires at least 256 MiB when memory is set. + +## Edge cases + +- Omitted memory keeps the Blaxel default. +- Fractional or sub-256 MiB values fail validation. + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-with-resources +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-with-resources/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package-lock.json new file mode 100644 index 00000000..2bf7c3bc --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-resources", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-with-resources", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package.json b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package.json new file mode 100644 index 00000000..d25ac342 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-resources", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/src/main.ts new file mode 100644 index 00000000..43844790 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/src/main.ts @@ -0,0 +1,19 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-resources", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 4096n }, +}); +console.log(`Blaxel sandbox created with 4096 MiB: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/.env.example b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/.gitignore b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/README.md new file mode 100644 index 00000000..6dd0a4e8 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/README.md @@ -0,0 +1,45 @@ +# Create a Blaxel sandbox with maximum lifetime + +Create one Blaxel sandbox that deletes after a maximum lifetime TTL. + +## What this means + +Maps `lifetime.maximumLifetime` to Blaxel max-age TTL. Durations must be whole seconds and meet Blaxel minima (5 minutes for max-age). + +## Edge cases + +- Fractional-second durations are rejected. +- Idle cleanup uses `idleTerminate`, not maximum lifetime. + +## Run + +```sh +cd examples/typescript/blaxel/configuration/create-blaxel-with-ttl +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/configuration/create-blaxel-with-ttl/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package-lock.json new file mode 100644 index 00000000..b73004ec --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-ttl", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-create-blaxel-with-ttl", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package.json new file mode 100644 index 00000000..9ce978e9 --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-create-blaxel-with-ttl", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/src/main.ts new file mode 100644 index 00000000..712eb45b --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/src/main.ts @@ -0,0 +1,19 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.create({ + name: "kit-create-blaxel-ttl", + source: { image: { reference: "blaxel/base-image:latest" } }, + lifetime: { maximumLifetime: 60 * 60_000 }, +}); +console.log(`Blaxel sandbox created with 60-minute maximum lifetime: ${sandbox.id}`); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/tsconfig.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/.env.example b/examples/typescript/blaxel/errors/handle-blaxel-errors/.env.example new file mode 100644 index 00000000..80af365b --- /dev/null +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/.env.example @@ -0,0 +1,4 @@ +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/.gitignore b/examples/typescript/blaxel/errors/handle-blaxel-errors/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/README.md b/examples/typescript/blaxel/errors/handle-blaxel-errors/README.md new file mode 100644 index 00000000..50d0d5e6 --- /dev/null +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/README.md @@ -0,0 +1,45 @@ +# Handle Blaxel validation errors + +Demonstrate typed validation errors from the Blaxel provider without creating a sandbox. + +## What this means + +Requests an unsupported CPU override so Kit fails before calling Blaxel. Applications inspect `SandboxError` kind and field. + +## Edge cases + +- Does not provision cloud resources when validation fails as expected. +- Authentication still requires a configured client. + +## Run + +```sh +cd examples/typescript/blaxel/errors/handle-blaxel-errors +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/errors/handle-blaxel-errors/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/package-lock.json b/examples/typescript/blaxel/errors/handle-blaxel-errors/package-lock.json new file mode 100644 index 00000000..9e0fc313 --- /dev/null +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-handle-blaxel-errors", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-handle-blaxel-errors", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/package.json b/examples/typescript/blaxel/errors/handle-blaxel-errors/package.json new file mode 100644 index 00000000..1b2cb966 --- /dev/null +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-handle-blaxel-errors", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/src/main.ts b/examples/typescript/blaxel/errors/handle-blaxel-errors/src/main.ts new file mode 100644 index 00000000..58f6f1d1 --- /dev/null +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/src/main.ts @@ -0,0 +1,29 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client, SandboxError } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +try { + await client.create({ + name: "kit-handle-blaxel-errors", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { cpuCores: 2 }, + }); + console.log("Sandbox created."); +} catch (error) { + if (error instanceof SandboxError) { + console.log( + `${error.kind}: ${error.message} (field=${error.field || "none"}, operation=${error.operation})`, + ); + } else { + throw error; + } +} diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/tsconfig.json b/examples/typescript/blaxel/errors/handle-blaxel-errors/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.env.example b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.env.example new file mode 100644 index 00000000..38b9be92 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.env.example @@ -0,0 +1,10 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.gitignore b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/README.md b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/README.md new file mode 100644 index 00000000..0169066f --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/README.md @@ -0,0 +1,50 @@ +# Copy a file from Blaxel + +Copy a sandbox file out to a local path. + +## What this means + +`copyToLocal` creates a missing local parent directory. + +## Edge cases + +- Remote path must exist. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/copy-file-from-blaxel +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/copy-file-from-blaxel/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package-lock.json b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package-lock.json new file mode 100644 index 00000000..09947371 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-copy-file-from-blaxel", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-copy-file-from-blaxel", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package.json b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package.json new file mode 100644 index 00000000..02e0c2c4 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-copy-file-from-blaxel", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/src/main.ts b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/src/main.ts new file mode 100644 index 00000000..050cbad7 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/src/main.ts @@ -0,0 +1,18 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const remote = process.env.BL_REMOTE_PATH!; +const local = process.env.BL_LOCAL_PATH!; +await sandbox.filesystem.copyToLocal(remote, local); +console.log(`copied ${remote} to ${local}`); diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/tsconfig.json b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.env.example b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.env.example new file mode 100644 index 00000000..38b9be92 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.env.example @@ -0,0 +1,10 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.gitignore b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/README.md b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/README.md new file mode 100644 index 00000000..39dba246 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/README.md @@ -0,0 +1,50 @@ +# Copy a file into Blaxel + +Copy a local file into a Blaxel sandbox path. + +## What this means + +A missing local file is not found before Blaxel is called (`local_path`). A local directory is an invalid argument. + +## Edge cases + +- Create the local file before running. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/copy-file-into-blaxel +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Absolute local path. +BL_LOCAL_PATH=./local-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/copy-file-into-blaxel/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package-lock.json b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package-lock.json new file mode 100644 index 00000000..a2e0b739 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-copy-file-into-blaxel", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-copy-file-into-blaxel", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package.json b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package.json new file mode 100644 index 00000000..a01a2a74 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-copy-file-into-blaxel", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/src/main.ts b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/src/main.ts new file mode 100644 index 00000000..20ab1678 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/src/main.ts @@ -0,0 +1,18 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const remote = process.env.BL_REMOTE_PATH!; +const local = process.env.BL_LOCAL_PATH!; +await sandbox.filesystem.copyFromLocal(local, remote); +console.log(`copied ${local} to ${remote}`); diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/tsconfig.json b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/.env.example b/examples/typescript/blaxel/filesystem/list-blaxel-directory/.env.example new file mode 100644 index 00000000..a3459972 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/.gitignore b/examples/typescript/blaxel/filesystem/list-blaxel-directory/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/README.md b/examples/typescript/blaxel/filesystem/list-blaxel-directory/README.md new file mode 100644 index 00000000..6cc02309 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/README.md @@ -0,0 +1,48 @@ +# List a Blaxel directory + +List entries in one absolute directory path. + +## What this means + +Returns shared directory entries with names and types. + +## Edge cases + +- Missing directories return not found. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/list-blaxel-directory +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/list-blaxel-directory/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/package-lock.json b/examples/typescript/blaxel/filesystem/list-blaxel-directory/package-lock.json new file mode 100644 index 00000000..afb02448 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-list-blaxel-directory", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-list-blaxel-directory", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/package.json b/examples/typescript/blaxel/filesystem/list-blaxel-directory/package.json new file mode 100644 index 00000000..ac2b11cb --- /dev/null +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-list-blaxel-directory", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/src/main.ts b/examples/typescript/blaxel/filesystem/list-blaxel-directory/src/main.ts new file mode 100644 index 00000000..bf2fa966 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/src/main.ts @@ -0,0 +1,20 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const remote = process.env.BL_REMOTE_PATH!; +const entries = await sandbox.filesystem.listDirectory(remote); +console.log(`${remote} (${entries.length} entries)`); +for (const entry of entries) { + console.log(entry.name); +} diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/tsconfig.json b/examples/typescript/blaxel/filesystem/list-blaxel-directory/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/.env.example b/examples/typescript/blaxel/filesystem/make-blaxel-directory/.env.example new file mode 100644 index 00000000..6f1368a8 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example-dir diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/.gitignore b/examples/typescript/blaxel/filesystem/make-blaxel-directory/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/README.md b/examples/typescript/blaxel/filesystem/make-blaxel-directory/README.md new file mode 100644 index 00000000..8b7c736b --- /dev/null +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/README.md @@ -0,0 +1,48 @@ +# Make a Blaxel directory + +Create a directory at an absolute path in a Blaxel sandbox. + +## What this means + +`parents` defaults to true. + +## Edge cases + +- Absolute paths only. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/make-blaxel-directory +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example-dir +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/make-blaxel-directory/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/package-lock.json b/examples/typescript/blaxel/filesystem/make-blaxel-directory/package-lock.json new file mode 100644 index 00000000..a34f7706 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-make-blaxel-directory", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-make-blaxel-directory", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/package.json b/examples/typescript/blaxel/filesystem/make-blaxel-directory/package.json new file mode 100644 index 00000000..c30302b9 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-make-blaxel-directory", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/src/main.ts b/examples/typescript/blaxel/filesystem/make-blaxel-directory/src/main.ts new file mode 100644 index 00000000..c07090ee --- /dev/null +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/src/main.ts @@ -0,0 +1,17 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const remote = process.env.BL_REMOTE_PATH!; +await sandbox.filesystem.makeDirectory(remote); +console.log(`created ${remote}`); diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/tsconfig.json b/examples/typescript/blaxel/filesystem/make-blaxel-directory/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/.env.example b/examples/typescript/blaxel/filesystem/read-blaxel-file/.env.example new file mode 100644 index 00000000..9b877a81 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/.gitignore b/examples/typescript/blaxel/filesystem/read-blaxel-file/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/read-blaxel-file/README.md new file mode 100644 index 00000000..301271de --- /dev/null +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/README.md @@ -0,0 +1,48 @@ +# Read a Blaxel file + +Read one absolute path from a Blaxel sandbox. + +## What this means + +Uses `filesystem.readFile` / `ReadFile`. Paths in the sandbox are absolute. + +## Edge cases + +- Missing files return not found from Blaxel. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/read-blaxel-file +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/read-blaxel-file/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/read-blaxel-file/package-lock.json new file mode 100644 index 00000000..d48295f4 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-read-blaxel-file", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-read-blaxel-file", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/package.json b/examples/typescript/blaxel/filesystem/read-blaxel-file/package.json new file mode 100644 index 00000000..cf353020 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-read-blaxel-file", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/read-blaxel-file/src/main.ts new file mode 100644 index 00000000..146896b6 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/src/main.ts @@ -0,0 +1,17 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const remote = process.env.BL_REMOTE_PATH!; +const data = await sandbox.filesystem.readFile(remote); +console.log(`read ${remote} (${data.byteLength} bytes)`); diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/tsconfig.json b/examples/typescript/blaxel/filesystem/read-blaxel-file/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/.env.example b/examples/typescript/blaxel/filesystem/remove-blaxel-file/.env.example new file mode 100644 index 00000000..040c9339 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/.env.example @@ -0,0 +1,10 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Set true for recursive remove. +BL_RECURSIVE= diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/.gitignore b/examples/typescript/blaxel/filesystem/remove-blaxel-file/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/remove-blaxel-file/README.md new file mode 100644 index 00000000..ac271a4e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/README.md @@ -0,0 +1,50 @@ +# Remove a Blaxel path + +Remove one absolute path in a Blaxel sandbox. + +## What this means + +Set `BL_RECURSIVE=true` to remove recursively when supported by options. + +## Edge cases + +- Removing a missing path returns not found. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/remove-blaxel-file +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +# Set true for recursive remove. +BL_RECURSIVE= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/remove-blaxel-file/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/remove-blaxel-file/package-lock.json new file mode 100644 index 00000000..00e6ae36 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-remove-blaxel-file", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-remove-blaxel-file", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/package.json b/examples/typescript/blaxel/filesystem/remove-blaxel-file/package.json new file mode 100644 index 00000000..ef817ced --- /dev/null +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-remove-blaxel-file", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/remove-blaxel-file/src/main.ts new file mode 100644 index 00000000..284f57d5 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/src/main.ts @@ -0,0 +1,18 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120_000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const remote = process.env.BL_REMOTE_PATH!; +const recursive = process.env.BL_RECURSIVE === "true"; +await sandbox.filesystem.remove(remote, recursive ? { recursive: true } : {}); +console.log(`removed ${remote}`); diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/tsconfig.json b/examples/typescript/blaxel/filesystem/remove-blaxel-file/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/.env.example b/examples/typescript/blaxel/filesystem/stat-blaxel-file/.env.example new file mode 100644 index 00000000..9b877a81 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/.gitignore b/examples/typescript/blaxel/filesystem/stat-blaxel-file/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md new file mode 100644 index 00000000..66fc91f7 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md @@ -0,0 +1,48 @@ +# Stat a Blaxel path + +Stat one absolute path in a Blaxel sandbox. + +## What this means + +Returns type and size for the path. + +## Edge cases + +- Missing paths return not found. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/stat-blaxel-file +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/stat-blaxel-file/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/stat-blaxel-file/package-lock.json new file mode 100644 index 00000000..862bce3a --- /dev/null +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-stat-blaxel-file", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-stat-blaxel-file", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/package.json b/examples/typescript/blaxel/filesystem/stat-blaxel-file/package.json new file mode 100644 index 00000000..8e3925bd --- /dev/null +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-stat-blaxel-file", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts new file mode 100644 index 00000000..a38eae4c --- /dev/null +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts @@ -0,0 +1,17 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const remote = process.env.BL_REMOTE_PATH!; +const info = await sandbox.filesystem.stat(remote); +console.log(`${info.path} type=${info.type} size=${info.size}`); diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/tsconfig.json b/examples/typescript/blaxel/filesystem/stat-blaxel-file/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/.env.example b/examples/typescript/blaxel/filesystem/write-blaxel-file/.env.example new file mode 100644 index 00000000..9b877a81 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/.gitignore b/examples/typescript/blaxel/filesystem/write-blaxel-file/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/write-blaxel-file/README.md new file mode 100644 index 00000000..3143bcd3 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/README.md @@ -0,0 +1,48 @@ +# Write a Blaxel file + +Write one absolute path in a Blaxel sandbox. + +## What this means + +`parents` defaults to true. `parents: false` is rejected. + +## Edge cases + +- Absolute remote paths only. + +## Run + +```sh +cd examples/typescript/blaxel/filesystem/write-blaxel-file +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Absolute path inside the sandbox. +BL_REMOTE_PATH=/tmp/kit-example.txt +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/filesystem/write-blaxel-file/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/write-blaxel-file/package-lock.json new file mode 100644 index 00000000..b69229c9 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-write-blaxel-file", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-write-blaxel-file", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/package.json b/examples/typescript/blaxel/filesystem/write-blaxel-file/package.json new file mode 100644 index 00000000..a354570e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-write-blaxel-file", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/write-blaxel-file/src/main.ts new file mode 100644 index 00000000..869bac29 --- /dev/null +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/src/main.ts @@ -0,0 +1,18 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const remote = process.env.BL_REMOTE_PATH!; +const body = new TextEncoder().encode("hello from sandbox-kit\n"); +await sandbox.filesystem.writeFile(remote, body); +console.log(`wrote ${remote} (${body.byteLength} bytes)`); diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/tsconfig.json b/examples/typescript/blaxel/filesystem/write-blaxel-file/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.env.example b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.gitignore b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/README.md b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/README.md new file mode 100644 index 00000000..6fb6b47d --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/README.md @@ -0,0 +1,47 @@ +# Close a Blaxel sandbox handle + +Close one Blaxel sandbox handle without contacting Blaxel or changing the sandbox. + +## What this means + +`Close` releases local handle resources only. The sandbox keeps running until TTL, idle policy, or delete. + +## Edge cases + +- After close, further handle calls fail locally. +- Client close also does not delete sandboxes. + +## Run + +```sh +cd examples/typescript/blaxel/lifecycle/close-blaxel-handle +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/lifecycle/close-blaxel-handle/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package-lock.json b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package-lock.json new file mode 100644 index 00000000..ac06116d --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-close-blaxel-handle", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-close-blaxel-handle", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package.json b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package.json new file mode 100644 index 00000000..17a18c8f --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-close-blaxel-handle", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/src/main.ts b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/src/main.ts new file mode 100644 index 00000000..bb48d580 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/src/main.ts @@ -0,0 +1,18 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +await sandbox.close(); +console.log( + `Closed the handle for Blaxel sandbox ${sandbox.id}. The sandbox was not changed.`, +); diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/tsconfig.json b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.env.example b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.gitignore b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md new file mode 100644 index 00000000..ef989d92 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md @@ -0,0 +1,47 @@ +# Delete a Blaxel sandbox + +Delete an existing Blaxel sandbox by name and wait for completion. + +## What this means + +`Delete` permanently removes the Blaxel sandbox. Start, stop, pause, archive, and terminate are unsupported. + +## Edge cases + +- Deletion destroys the sandbox and its filesystem. +- Closing the handle is not deletion. + +## Run + +```sh +cd examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/lifecycle/delete-blaxel-sandbox/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json new file mode 100644 index 00000000..c322b9f3 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-delete-blaxel-sandbox", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-delete-blaxel-sandbox", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package.json b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package.json new file mode 100644 index 00000000..11cd3414 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-delete-blaxel-sandbox", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts new file mode 100644 index 00000000..4771a2bc --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts @@ -0,0 +1,32 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { + Client, + LifecycleCapability, + type Sandbox, +} from "@sandbox-kit/sandbox"; + +function assertCapability( + sandbox: Sandbox, + capability: LifecycleCapability, +): void { + if (!sandbox.supports(capability)) { + throw new Error(`sandbox ${sandbox.id} does not support ${capability}`); + } +} + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120_000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +assertCapability(sandbox, LifecycleCapability.Delete); +await sandbox.delete({ wait: true }); +console.log( + `Blaxel sandbox deleted: ${sandbox.id} (terminal=${sandbox.info().terminal ?? false})`, +); diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/tsconfig.json b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.env.example b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.gitignore b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md new file mode 100644 index 00000000..01f6b3b9 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md @@ -0,0 +1,47 @@ +# Get a Blaxel sandbox + +Look up an existing Blaxel sandbox by name and print metadata. + +## What this means + +`Get` retrieves the sandbox by name (the Blaxel id). Use the name printed by a create example as `BL_SANDBOX_ID`. + +## Edge cases + +- Unknown names return not found. +- Attach is unsupported; use get. + +## Run + +```sh +cd examples/typescript/blaxel/lifecycle/get-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/lifecycle/get-blaxel-sandbox/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package-lock.json new file mode 100644 index 00000000..bbd278e8 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-get-blaxel-sandbox", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-get-blaxel-sandbox", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package.json b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package.json new file mode 100644 index 00000000..fdf65730 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-get-blaxel-sandbox", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts new file mode 100644 index 00000000..8d009d9f --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts @@ -0,0 +1,18 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120_000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const info = sandbox.info(); +console.log( + `Blaxel sandbox: id=${info.id} state=${info.state} native=${info.providerState ?? ""} capabilities=${sandbox.capabilities().join(",")}`, +); diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/tsconfig.json b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.gitignore b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md new file mode 100644 index 00000000..df36f16c --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md @@ -0,0 +1,46 @@ +# Refresh a Blaxel sandbox + +Refresh metadata for an existing Blaxel sandbox by name. + +## What this means + +`Refresh` re-fetches provider metadata onto the handle. + +## Edge cases + +- Unsupported when the handle does not report the refresh capability. + +## Run + +```sh +cd examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json new file mode 100644 index 00000000..30a19e95 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-refresh-blaxel-sandbox", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-refresh-blaxel-sandbox", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package.json b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package.json new file mode 100644 index 00000000..e617e356 --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-refresh-blaxel-sandbox", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts new file mode 100644 index 00000000..0a330b7a --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts @@ -0,0 +1,30 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { + Client, + LifecycleCapability, + type Sandbox, +} from "@sandbox-kit/sandbox"; + +function assertCapability( + sandbox: Sandbox, + capability: LifecycleCapability, +): void { + if (!sandbox.supports(capability)) { + throw new Error(`sandbox ${sandbox.id} does not support ${capability}`); + } +} + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120_000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +assertCapability(sandbox, LifecycleCapability.Refresh); +const info = await sandbox.refresh(); +console.log(`Blaxel metadata refreshed: ${sandbox.id} (${info.state})`); diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/tsconfig.json b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/.env.example b/examples/typescript/blaxel/process/run-blaxel-check-exit/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/.gitignore b/examples/typescript/blaxel/process/run-blaxel-check-exit/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/README.md b/examples/typescript/blaxel/process/run-blaxel-check-exit/README.md new file mode 100644 index 00000000..0c97b517 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/README.md @@ -0,0 +1,46 @@ +# Check a Blaxel non-zero exit + +Run a command that exits non-zero and read the exit code from the result. + +## What this means + +A non-zero exit is not an SDK error. Applications check `exitCode` / `GetExitCode`. + +## Edge cases + +- Transport and validation failures still raise errors. + +## Run + +```sh +cd examples/typescript/blaxel/process/run-blaxel-check-exit +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/process/run-blaxel-check-exit/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-check-exit/package-lock.json new file mode 100644 index 00000000..5432501d --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-run-blaxel-check-exit", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-run-blaxel-check-exit", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/package.json b/examples/typescript/blaxel/process/run-blaxel-check-exit/package.json new file mode 100644 index 00000000..55cfeeef --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-run-blaxel-check-exit", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-check-exit/src/main.ts new file mode 100644 index 00000000..e2da0fa6 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/src/main.ts @@ -0,0 +1,19 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const result = await sandbox.process.run({ shell: "exit 7" }); +if (result.exitCode !== 7) { + throw new Error(`want exit 7, got ${result.exitCode}`); +} +console.log(`command finished with exit=${result.exitCode}`); diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/tsconfig.json b/examples/typescript/blaxel/process/run-blaxel-check-exit/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/process/run-blaxel-command/.env.example b/examples/typescript/blaxel/process/run-blaxel-command/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-command/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/process/run-blaxel-command/.gitignore b/examples/typescript/blaxel/process/run-blaxel-command/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-command/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/process/run-blaxel-command/README.md b/examples/typescript/blaxel/process/run-blaxel-command/README.md new file mode 100644 index 00000000..ecff18b9 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-command/README.md @@ -0,0 +1,47 @@ +# Run a Blaxel command + +Run one argv command in a Blaxel sandbox and print exit code plus streams. + +## What this means + +`Process.Run` is blocking. Set exactly one of `argv` or `shell`. Argv is sent as a POSIX-quoted shell string. + +## Edge cases + +- Non-zero exit codes are success for the SDK—read `exitCode`. +- Blaxel returns stdout and stderr. + +## Run + +```sh +cd examples/typescript/blaxel/process/run-blaxel-command +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/process/run-blaxel-command/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/process/run-blaxel-command/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-command/package-lock.json new file mode 100644 index 00000000..ba25286d --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-command/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-run-blaxel-command", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-run-blaxel-command", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-command/package.json b/examples/typescript/blaxel/process/run-blaxel-command/package.json new file mode 100644 index 00000000..9da6cb8c --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-command/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-run-blaxel-command", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-command/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-command/src/main.ts new file mode 100644 index 00000000..65c1d6db --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-command/src/main.ts @@ -0,0 +1,20 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const result = await sandbox.process.run({ + argv: ["echo", "hello from kit"], +}); +console.log( + `exit=${result.exitCode} stdout=${JSON.stringify(new TextDecoder().decode(result.stdout))} stderr=${JSON.stringify(new TextDecoder().decode(result.stderr))}`, +); diff --git a/examples/typescript/blaxel/process/run-blaxel-command/tsconfig.json b/examples/typescript/blaxel/process/run-blaxel-command/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-command/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/.env.example b/examples/typescript/blaxel/process/run-blaxel-shell-command/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/.gitignore b/examples/typescript/blaxel/process/run-blaxel-shell-command/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/README.md b/examples/typescript/blaxel/process/run-blaxel-shell-command/README.md new file mode 100644 index 00000000..2eac1bdb --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/README.md @@ -0,0 +1,46 @@ +# Run a Blaxel shell command + +Run one shell string in a Blaxel sandbox. + +## What this means + +Uses `shell` instead of `argv`. Exactly one of the two must be set. + +## Edge cases + +- Setting both argv and shell is rejected. + +## Run + +```sh +cd examples/typescript/blaxel/process/run-blaxel-shell-command +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/process/run-blaxel-shell-command/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-shell-command/package-lock.json new file mode 100644 index 00000000..a29ca0d3 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-run-blaxel-shell-command", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-run-blaxel-shell-command", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/package.json b/examples/typescript/blaxel/process/run-blaxel-shell-command/package.json new file mode 100644 index 00000000..08817058 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-run-blaxel-shell-command", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-shell-command/src/main.ts new file mode 100644 index 00000000..685c843f --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/src/main.ts @@ -0,0 +1,20 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const result = await sandbox.process.run({ + shell: `echo "hello from shell"`, +}); +console.log( + `exit=${result.exitCode} stdout=${JSON.stringify(new TextDecoder().decode(result.stdout))}`, +); diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/tsconfig.json b/examples/typescript/blaxel/process/run-blaxel-shell-command/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/.env.example b/examples/typescript/blaxel/process/run-blaxel-with-cwd/.env.example new file mode 100644 index 00000000..c93fc1d4 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/.env.example @@ -0,0 +1,8 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Working directory inside the sandbox. +BL_WORKING_DIRECTORY=/tmp diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/.gitignore b/examples/typescript/blaxel/process/run-blaxel-with-cwd/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/README.md b/examples/typescript/blaxel/process/run-blaxel-with-cwd/README.md new file mode 100644 index 00000000..3f34a471 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/README.md @@ -0,0 +1,48 @@ +# Run a Blaxel command with cwd + +Run a command with an explicit working directory. + +## What this means + +Sets `workingDirectory` / `WorkingDirectory` for the process. + +## Edge cases + +- Paths in the sandbox are absolute. + +## Run + +```sh +cd examples/typescript/blaxel/process/run-blaxel-with-cwd +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +# Working directory inside the sandbox. +BL_WORKING_DIRECTORY=/tmp +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/process/run-blaxel-with-cwd/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-with-cwd/package-lock.json new file mode 100644 index 00000000..4cf6ad51 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-run-blaxel-with-cwd", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-run-blaxel-with-cwd", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/package.json b/examples/typescript/blaxel/process/run-blaxel-with-cwd/package.json new file mode 100644 index 00000000..7337ae61 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-run-blaxel-with-cwd", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts new file mode 100644 index 00000000..9d0cf714 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts @@ -0,0 +1,22 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const cwd = process.env.BL_WORKING_DIRECTORY || "/tmp"; +const result = await sandbox.process.run({ + argv: ["pwd"], + workingDirectory: cwd, +}); +console.log( + `exit=${result.exitCode} cwd_out=${JSON.stringify(new TextDecoder().decode(result.stdout))}`, +); diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/tsconfig.json b/examples/typescript/blaxel/process/run-blaxel-with-cwd/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/.env.example b/examples/typescript/blaxel/process/run-blaxel-with-env/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/.gitignore b/examples/typescript/blaxel/process/run-blaxel-with-env/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/README.md b/examples/typescript/blaxel/process/run-blaxel-with-env/README.md new file mode 100644 index 00000000..6137c2ab --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/README.md @@ -0,0 +1,46 @@ +# Run a Blaxel command with env + +Run a command with environment variables for that process. + +## What this means + +Process environment is separate from create-time sandbox environment. + +## Edge cases + +- Invalid names follow the same env-name rules as create. + +## Run + +```sh +cd examples/typescript/blaxel/process/run-blaxel-with-env +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/process/run-blaxel-with-env/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-with-env/package-lock.json new file mode 100644 index 00000000..a8656f8e --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-run-blaxel-with-env", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-run-blaxel-with-env", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/package.json b/examples/typescript/blaxel/process/run-blaxel-with-env/package.json new file mode 100644 index 00000000..fe2b4c19 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-run-blaxel-with-env", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-with-env/src/main.ts new file mode 100644 index 00000000..cb470f18 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/src/main.ts @@ -0,0 +1,21 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const result = await sandbox.process.run({ + argv: ["printenv", "KIT_DEMO"], + environment: { KIT_DEMO: "from-kit" }, +}); +console.log( + `exit=${result.exitCode} stdout=${JSON.stringify(new TextDecoder().decode(result.stdout))}`, +); diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/tsconfig.json b/examples/typescript/blaxel/process/run-blaxel-with-env/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/.env.example b/examples/typescript/blaxel/process/run-blaxel-with-timeout/.env.example new file mode 100644 index 00000000..0ba3e326 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/.env.example @@ -0,0 +1,6 @@ +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/.gitignore b/examples/typescript/blaxel/process/run-blaxel-with-timeout/.gitignore new file mode 100644 index 00000000..a8b14857 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +.env diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md b/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md new file mode 100644 index 00000000..d32d8da5 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md @@ -0,0 +1,47 @@ +# Run a Blaxel command with timeout + +Cap a Blaxel Process.Run with a whole-second timeout. + +## What this means + +Command timeouts must be whole seconds. Omit or zero for no Blaxel command limit. + +## Edge cases + +- `1500` ms is rejected; `2000` is two seconds. +- Canceling the local wait does not guarantee the remote command stops. + +## Run + +```sh +cd examples/typescript/blaxel/process/run-blaxel-with-timeout +cp .env.example .env +# Fill in the values below, then: +npm install +npm run build +npm start +``` + +Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if +`.env` already exists. + +### Environment + +```dotenv +BL_API_KEY= +BL_WORKSPACE= +# Sandbox name (Blaxel id is the name). +BL_SANDBOX_ID= +# Optional region override (default us-pdx-1). +BL_REGION= +``` + +## Warnings + +- Acts on **real** Blaxel resources when credentials succeed. +- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. + +## Related + +- [go twin](../../../../go/blaxel/process/run-blaxel-with-timeout/README.md) +- Catalog: [examples/typescript/README.md](../../../README.md) diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-with-timeout/package-lock.json new file mode 100644 index 00000000..36fdd42c --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/package-lock.json @@ -0,0 +1,98 @@ +{ + "name": "sandbox-kit-example-run-blaxel-with-timeout", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-kit-example-run-blaxel-with-timeout", + "version": "0.1.0", + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/providers/blaxel": { + "name": "@sandbox-kit/blaxel", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@blaxel/core": "0.3.35", + "@bufbuild/protobuf": "2.16.0", + "@sandbox-kit/sandbox": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "../../../../../sdks/typescript/sandbox": { + "name": "@sandbox-kit/sandbox", + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "2.16.0", + "@bufbuild/protovalidate": "1.3.0", + "@sandbox-kit/contracts": "0.1.7" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3", + "vitest": "4.1.11" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + } + }, + "node_modules/@sandbox-kit/blaxel": { + "resolved": "../../../../../sdks/typescript/providers/blaxel", + "link": true + }, + "node_modules/@sandbox-kit/sandbox": { + "resolved": "../../../../../sdks/typescript/sandbox", + "link": true + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/package.json b/examples/typescript/blaxel/process/run-blaxel-with-timeout/package.json new file mode 100644 index 00000000..1e5941b5 --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/package.json @@ -0,0 +1,22 @@ +{ + "name": "sandbox-kit-example-run-blaxel-with-timeout", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.19.0", + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.15.0" + }, + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node --env-file-if-exists=.env dist/main.js" + }, + "dependencies": { + "@sandbox-kit/blaxel": "file:../../../../../sdks/typescript/providers/blaxel", + "@sandbox-kit/sandbox": "file:../../../../../sdks/typescript/sandbox" + }, + "devDependencies": { + "@types/node": "24.10.1", + "typescript": "5.9.3" + } +} diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-with-timeout/src/main.ts new file mode 100644 index 00000000..553392fd --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/src/main.ts @@ -0,0 +1,21 @@ +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +const region = process.env.BL_REGION || "us-pdx-1"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region, + timeout: 120000, +}); + +const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const result = await sandbox.process.run({ + argv: ["echo", "hello with timeout"], + timeout: 5_000, +}); +console.log( + `exit=${result.exitCode} stdout=${JSON.stringify(new TextDecoder().decode(result.stdout))} stderr=${JSON.stringify(new TextDecoder().decode(result.stderr))}`, +); diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/tsconfig.json b/examples/typescript/blaxel/process/run-blaxel-with-timeout/tsconfig.json new file mode 100644 index 00000000..b1e6e13e --- /dev/null +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM", "ESNext.Disposable"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/go.work b/go.work index 98592419..aa6d76c9 100644 --- a/go.work +++ b/go.work @@ -1,8 +1,33 @@ go 1.26.0 use ( - ./examples/go/daytona/lifecycle/archive-daytona-sandbox - ./examples/go/daytona/lifecycle/close-daytona-handle + ./examples/go/blaxel/configuration/create-blaxel-client-timeout + ./examples/go/blaxel/configuration/create-blaxel-idle + ./examples/go/blaxel/configuration/create-blaxel-sandbox + ./examples/go/blaxel/configuration/create-blaxel-with-allowlists + ./examples/go/blaxel/configuration/create-blaxel-with-labels + ./examples/go/blaxel/configuration/create-blaxel-with-ports + ./examples/go/blaxel/configuration/create-blaxel-with-resources + ./examples/go/blaxel/configuration/create-blaxel-with-ttl + ./examples/go/blaxel/errors/handle-blaxel-errors + ./examples/go/blaxel/filesystem/copy-file-from-blaxel + ./examples/go/blaxel/filesystem/copy-file-into-blaxel + ./examples/go/blaxel/filesystem/list-blaxel-directory + ./examples/go/blaxel/filesystem/make-blaxel-directory + ./examples/go/blaxel/filesystem/read-blaxel-file + ./examples/go/blaxel/filesystem/remove-blaxel-file + ./examples/go/blaxel/filesystem/stat-blaxel-file + ./examples/go/blaxel/filesystem/write-blaxel-file + ./examples/go/blaxel/lifecycle/close-blaxel-handle + ./examples/go/blaxel/lifecycle/delete-blaxel-sandbox + ./examples/go/blaxel/lifecycle/get-blaxel-sandbox + ./examples/go/blaxel/lifecycle/refresh-blaxel-sandbox + ./examples/go/blaxel/process/run-blaxel-check-exit + ./examples/go/blaxel/process/run-blaxel-command + ./examples/go/blaxel/process/run-blaxel-shell-command + ./examples/go/blaxel/process/run-blaxel-with-cwd + ./examples/go/blaxel/process/run-blaxel-with-env + ./examples/go/blaxel/process/run-blaxel-with-timeout ./examples/go/daytona/configuration/create-daytona-ephemeral ./examples/go/daytona/configuration/create-daytona-from-image ./examples/go/daytona/configuration/create-daytona-from-snapshot @@ -15,18 +40,32 @@ use ( ./examples/go/daytona/configuration/create-daytona-with-runtime ./examples/go/daytona/configuration/create-daytona-with-secrets ./examples/go/daytona/configuration/create-daytona-with-ttl + ./examples/go/daytona/errors/handle-daytona-errors + ./examples/go/daytona/filesystem/copy-file-from-daytona + ./examples/go/daytona/filesystem/copy-file-into-daytona + ./examples/go/daytona/filesystem/list-daytona-directory + ./examples/go/daytona/filesystem/make-daytona-directory + ./examples/go/daytona/filesystem/read-daytona-file + ./examples/go/daytona/filesystem/remove-daytona-file + ./examples/go/daytona/filesystem/stat-daytona-file + ./examples/go/daytona/filesystem/write-daytona-file + ./examples/go/daytona/lifecycle/archive-daytona-sandbox + ./examples/go/daytona/lifecycle/close-daytona-handle ./examples/go/daytona/lifecycle/delete-daytona-sandbox ./examples/go/daytona/lifecycle/force-stop-daytona-sandbox ./examples/go/daytona/lifecycle/get-daytona-sandbox - ./examples/go/daytona/errors/handle-daytona-errors ./examples/go/daytona/lifecycle/pause-daytona-sandbox ./examples/go/daytona/lifecycle/refresh-daytona-sandbox ./examples/go/daytona/lifecycle/request-daytona-deletion ./examples/go/daytona/lifecycle/start-daytona-sandbox ./examples/go/daytona/lifecycle/start-daytona-with-timeout ./examples/go/daytona/lifecycle/stop-daytona-sandbox - ./examples/go/modal/lifecycle/attach-modal-sandbox - ./examples/go/modal/lifecycle/close-modal-handle + ./examples/go/daytona/process/run-daytona-check-exit + ./examples/go/daytona/process/run-daytona-command + ./examples/go/daytona/process/run-daytona-shell-command + ./examples/go/daytona/process/run-daytona-with-cwd + ./examples/go/daytona/process/run-daytona-with-env + ./examples/go/daytona/process/run-daytona-with-timeout ./examples/go/modal/configuration/create-modal-client-timeout ./examples/go/modal/configuration/create-modal-gvisor ./examples/go/modal/configuration/create-modal-idle @@ -45,23 +84,6 @@ use ( ./examples/go/modal/configuration/create-modal-with-runtime ./examples/go/modal/configuration/create-modal-with-secrets ./examples/go/modal/errors/handle-modal-errors - ./examples/go/modal/lifecycle/request-modal-termination - ./examples/go/modal/lifecycle/terminate-modal-sandbox - ./examples/go/modal/lifecycle/terminate-modal-with-timeout - ./examples/go/daytona/filesystem/copy-file-from-daytona - ./examples/go/daytona/filesystem/copy-file-into-daytona - ./examples/go/daytona/filesystem/list-daytona-directory - ./examples/go/daytona/filesystem/make-daytona-directory - ./examples/go/daytona/filesystem/read-daytona-file - ./examples/go/daytona/filesystem/remove-daytona-file - ./examples/go/daytona/filesystem/stat-daytona-file - ./examples/go/daytona/filesystem/write-daytona-file - ./examples/go/daytona/process/run-daytona-check-exit - ./examples/go/daytona/process/run-daytona-command - ./examples/go/daytona/process/run-daytona-shell-command - ./examples/go/daytona/process/run-daytona-with-cwd - ./examples/go/daytona/process/run-daytona-with-env - ./examples/go/daytona/process/run-daytona-with-timeout ./examples/go/modal/filesystem/copy-file-from-modal ./examples/go/modal/filesystem/copy-file-into-modal ./examples/go/modal/filesystem/list-modal-directory @@ -70,6 +92,11 @@ use ( ./examples/go/modal/filesystem/remove-modal-file ./examples/go/modal/filesystem/stat-modal-file ./examples/go/modal/filesystem/write-modal-file + ./examples/go/modal/lifecycle/attach-modal-sandbox + ./examples/go/modal/lifecycle/close-modal-handle + ./examples/go/modal/lifecycle/request-modal-termination + ./examples/go/modal/lifecycle/terminate-modal-sandbox + ./examples/go/modal/lifecycle/terminate-modal-with-timeout ./examples/go/modal/process/run-modal-check-exit ./examples/go/modal/process/run-modal-command ./examples/go/modal/process/run-modal-shell-command diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 43c9f6aa..9912a5db 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -61,7 +61,8 @@ rejected. `Scope` fields other than `Workspace` are rejected. ## Operations Create from a registry image with an explicit name. You can set labels, -environment variables, memory, maximum lifetime, and idle terminate. +environment variables, memory, ports, outbound domains, maximum lifetime, and +idle terminate. On the handle: `Get`, `Refresh`, `Delete`, and `Close`. `Get` and `Refresh` look up the sandbox by name. `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and diff --git a/sdks/go/providers/blaxel/creation.go b/sdks/go/providers/blaxel/creation.go index 6c240f32..a37db775 100644 --- a/sdks/go/providers/blaxel/creation.go +++ b/sdks/go/providers/blaxel/creation.go @@ -194,7 +194,58 @@ func (c *creation) network() error { if c.request.Network == nil { return nil } - return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldNetwork, "Blaxel network fields are not mapped") + config := cloneMessage(c.request.Network) + if config.Egress != sandbox.EgressModeDefault || + config.OutboundCidrs != nil || + config.InboundCidrs != nil || + config.PrivateNetwork != nil || + config.LinkedSandbox != nil || + config.ProxyReference != nil || + config.OutboundProxyUrl != nil || + config.CustomDomain != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldNetwork, "requested network settings are unsupported by Blaxel") + } + if config.OutboundDomains != nil { + c.params.Sandbox.Spec.Network.AllowedDomains = append([]string(nil), config.OutboundDomains.Entries...) + config.OutboundDomains = nil + } + if len(config.Ports) > 0 { + ports := make([]blaxel.PortParam, 0, len(config.Ports)) + for _, binding := range config.Ports { + if binding == nil { + continue + } + protocol, err := blaxelPortProtocol(binding.Transport) + if err != nil { + return err + } + ports = append(ports, blaxel.PortParam{ + Target: int64(binding.Port), + Protocol: protocol, + }) + } + c.params.Sandbox.Spec.Runtime.Ports = ports + config.Ports = nil + } + config.Egress = 0 + if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldNetwork, config); err != nil { + return err + } + c.rest.Network = nil + return nil +} + +func blaxelPortProtocol(transport sandbox.PortTransport) (blaxel.PortProtocol, error) { + switch transport { + case sandbox.PortTransportDefault, sandbox.PortTransportPlain: + return blaxel.PortProtocolTcp, nil + case sandbox.PortTransportTLS: + return blaxel.PortProtocolTls, nil + case sandbox.PortTransportHTTP2TLS: + return blaxel.PortProtocolHTTP, nil + default: + return "", providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldNetworkPortsTransport, "requested port transport is unsupported") + } } func (c *creation) provisioning() error { diff --git a/sdks/go/providers/blaxel/rejection_test.go b/sdks/go/providers/blaxel/rejection_test.go index 33d9708c..37978a9c 100644 --- a/sdks/go/providers/blaxel/rejection_test.go +++ b/sdks/go/providers/blaxel/rejection_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + blaxel "github.com/blaxel-ai/sdk-go" "github.com/sandbox-kit/kit/sdks/go/sandbox" ) @@ -60,13 +61,21 @@ func TestRejectedCreateSettings(t *testing.T) { field: sandbox.CreateFieldSecurity, }, { - name: "network", + name: "private network", request: namedImage(&sandbox.CreateOptions{ Network: &sandbox.NetworkConfig{PrivateNetwork: sandbox.Value(true)}, }), kind: sandbox.ErrorKindUnsupported, field: sandbox.CreateFieldNetwork, }, + { + name: "egress mode", + request: namedImage(&sandbox.CreateOptions{ + Network: &sandbox.NetworkConfig{Egress: sandbox.EgressModeRestricted}, + }), + kind: sandbox.ErrorKindUnsupported, + field: sandbox.CreateFieldNetwork, + }, { name: "ephemeral", request: namedImage(&sandbox.CreateOptions{ @@ -171,3 +180,28 @@ func namedImage(request *sandbox.CreateOptions) *sandbox.CreateOptions { request.Name = sandbox.Value("sandbox") return imaged(request) } + +func TestMappedNetworkPortsAndDomains(t *testing.T) { + plan, err := planCreate(namedImage(&sandbox.CreateOptions{ + Network: &sandbox.NetworkConfig{ + OutboundDomains: &sandbox.StringAllowlist{Entries: []string{"example.com"}}, + Ports: []*sandbox.PortBinding{ + {Port: 443, Transport: sandbox.PortTransportTLS}, + {Port: 8080, Transport: sandbox.PortTransportPlain}, + }, + }, + }), "us-pdx-1") + if err != nil { + t.Fatal(err) + } + if got := plan.params.Sandbox.Spec.Network.AllowedDomains; len(got) != 1 || got[0] != "example.com" { + t.Fatalf("AllowedDomains = %#v", got) + } + ports := plan.params.Sandbox.Spec.Runtime.Ports + if len(ports) != 2 || ports[0].Target != 443 || ports[0].Protocol != blaxel.PortProtocolTls { + t.Fatalf("Ports = %#v", ports) + } + if ports[1].Target != 8080 || ports[1].Protocol != blaxel.PortProtocolTcp { + t.Fatalf("Ports[1] = %#v", ports[1]) + } +} diff --git a/sdks/typescript/package-lock.json b/sdks/typescript/package-lock.json index b5dfae37..d6229afa 100644 --- a/sdks/typescript/package-lock.json +++ b/sdks/typescript/package-lock.json @@ -1744,9 +1744,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1764,9 +1761,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1784,9 +1778,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1804,9 +1795,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1824,9 +1812,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1844,9 +1829,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4484,9 +4466,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4508,9 +4487,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4532,9 +4508,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4556,9 +4529,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx new file mode 100644 index 00000000..99fbd57e --- /dev/null +++ b/website/content/docs/blaxel.mdx @@ -0,0 +1,121 @@ +--- +title: Blaxel +description: Install, authenticate, and create a Blaxel sandbox. +--- + +Use Blaxel when you want named image creates, get/refresh by name, filesystem, blocking process runs, and delete. + +## Install + + + + +```sh +npm install @sandbox-kit/sandbox @sandbox-kit/blaxel +``` + + + + +```sh +go get github.com/sandbox-kit/kit/sdks/go/sandbox@latest +go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest +``` + + + + +## Create + +Set `BL_API_KEY` and `BL_WORKSPACE`. Create requires a name and an image. Set `region` on the client (default examples use `us-pdx-1`). + + + + +```ts +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region: process.env.BL_REGION || "us-pdx-1", + timeout: 120_000, +}); + +const sandbox = await client.create({ + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, +}); +console.log(sandbox.id); +``` + +`timeout` is milliseconds. Prefer `await using`; otherwise call `await client.close()` when finished. + + + + +```go +package main + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), + }) + if err != nil { + panic(err) + } + defer client.Close(context.Background()) + + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + }) + if err != nil { + panic(err) + } + fmt.Println(instance.ID()) +} +``` + + + + +## What Blaxel supports + +| Area | Notes | +| ---------- | --------------------------------------------------------------------- | +| Create | Name + image; memory, labels, env, ports, outbound domains, TTL/idle | +| Open | `get` / `refresh` by sandbox name | +| Lifecycle | `delete`, `close` — no start/stop/pause/archive/terminate/attach | +| Filesystem | Shared eight methods; absolute sandbox paths | +| Process | Blocking `run` with argv or shell | + +Closing the client closes your connection to Blaxel. Closing a handle leaves the sandbox as it is. + +## Next + +- [Configuration](/docs/configuration) +- [Create](/docs/create) +- [Lifecycle](/docs/lifecycle) +- [Examples](/docs/examples) diff --git a/website/content/docs/examples.mdx b/website/content/docs/examples.mdx index 97f6cc9a..f13d3be0 100644 --- a/website/content/docs/examples.mdx +++ b/website/content/docs/examples.mdx @@ -36,6 +36,17 @@ cp .env.example .env # set MODAL_TOKEN_ID, MODAL_TOKEN_SECRET, MODAL_APP_NAME npm install && npm run build && npm start ``` + + + + +```sh +git clone https://github.com/sandbox-kit/kit.git +cd kit/examples/typescript/blaxel/configuration/create-blaxel-sandbox +cp .env.example .env # set BL_API_KEY, BL_WORKSPACE +npm install && npm run build && npm start +``` + @@ -63,6 +74,17 @@ cp .env.example .env # set MODAL_TOKEN_ID, MODAL_TOKEN_SECRET, MODAL_APP_NAME go run . ``` + + + + +```sh +git clone https://github.com/sandbox-kit/kit.git +cd kit/examples/go/blaxel/configuration/create-blaxel-sandbox +cp .env.example .env # set BL_API_KEY, BL_WORKSPACE +go run . +``` + @@ -399,3 +421,46 @@ go run . + + + +## Catalog + +### Create + + + + +| Example | Project | +| --------------- | ------- | +| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-sandbox) | +| Resources | [create-blaxel-with-resources](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-resources) | +| Labels | [create-blaxel-with-labels](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-labels) | +| TTL | [create-blaxel-with-ttl](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-ttl) | +| Idle delete | [create-blaxel-idle](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-idle) | +| Client timeout | [create-blaxel-client-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-client-timeout) | +| Ports | [create-blaxel-with-ports](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-ports) | +| Outbound domains| [create-blaxel-with-allowlists](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists) | + + + + +| Example | Project | +| --------------- | ------- | +| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-sandbox) | +| Resources | [create-blaxel-with-resources](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-resources) | +| Labels | [create-blaxel-with-labels](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-labels) | +| TTL | [create-blaxel-with-ttl](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-ttl) | +| Idle delete | [create-blaxel-idle](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-idle) | +| Client timeout | [create-blaxel-client-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-client-timeout) | +| Ports | [create-blaxel-with-ports](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-ports) | +| Outbound domains| [create-blaxel-with-allowlists](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-allowlists) | + + + + +### Process / Filesystem / Lifecycle / Errors + +Indexes: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel). Full catalogs live in [examples/README.md](https://github.com/sandbox-kit/kit/blob/main/examples/README.md). + + diff --git a/website/content/docs/meta.json b/website/content/docs/meta.json index 77885484..4ffd4b2c 100644 --- a/website/content/docs/meta.json +++ b/website/content/docs/meta.json @@ -6,6 +6,7 @@ "---Providers---", "daytona", "modal", + "blaxel", "---Guides---", "configuration", "create", diff --git a/website/src/lib/providers.ts b/website/src/lib/providers.ts index 4a65eee5..38a0d1de 100644 --- a/website/src/lib/providers.ts +++ b/website/src/lib/providers.ts @@ -1,6 +1,7 @@ export const DOC_PROVIDERS = [ { id: "daytona", label: "Daytona" }, { id: "modal", label: "Modal" }, + { id: "blaxel", label: "Blaxel" }, ] as const; export type DocProviderId = (typeof DOC_PROVIDERS)[number]["id"]; @@ -24,20 +25,20 @@ export const PROVIDER_SCOPED_HEADINGS: Record< > = { lifecycle: { "stop-start-pause-and-archive": ["daytona"], - delete: ["daytona"], + delete: ["daytona", "blaxel"], terminate: ["modal"], }, client: { - get: ["daytona"], + get: ["daytona", "blaxel"], attach: ["modal"], }, sandbox: { - refresh: ["daytona"], + refresh: ["daytona", "blaxel"], start: ["daytona"], stop: ["daytona"], pause: ["daytona"], archive: ["daytona"], - delete: ["daytona"], + delete: ["daytona", "blaxel"], terminate: ["modal"], }, }; From 56349c67de5df9a9a77f127c613b27d811e770c1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Nov 2026 13:37:56 +0000 Subject: [PATCH 05/60] Align Blaxel wiring with sibling providers and Blaxel floors. Fix reserved ports in examples, add Go TTL/memory floors to match TS, mark delete as Deleted/terminal in TS like Go, reject idle-disable consistently, and correct region/scope docs without inventing Go create timeout or private-field standby mapping. Co-authored-by: Nabhag Motivaras --- docs/providers.md | 5 ++-- .../create-blaxel-with-ports/README.md | 1 + .../create-blaxel-with-ports/main.go | 4 +-- .../create-blaxel-with-ports/README.md | 1 + .../create-blaxel-with-ports/src/main.ts | 4 +-- sdks/go/providers/blaxel/README.md | 8 +++-- sdks/go/providers/blaxel/creation.go | 4 +-- sdks/go/providers/blaxel/provider_checks.go | 3 ++ sdks/go/providers/blaxel/provider_mappings.go | 6 +++- sdks/go/providers/blaxel/rejection_test.go | 29 +++++++++++++++++++ sdks/go/providers/blaxel/sandbox_test.go | 27 +++++++++-------- sdks/typescript/providers/blaxel/README.md | 6 ++-- .../providers/blaxel/src/blaxel.test.ts | 28 +++++++++++++++++- .../typescript/providers/blaxel/src/blaxel.ts | 8 ++++- .../providers/blaxel/src/mappings/mappings.ts | 6 +++- .../blaxel/src/mappings/provider_mappings.ts | 3 +- specs/providers/blaxel.yaml | 1 + website/content/docs/configuration.mdx | 3 +- 18 files changed, 115 insertions(+), 32 deletions(-) diff --git a/docs/providers.md b/docs/providers.md index 78ab5ae2..96ef1be6 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -66,8 +66,9 @@ supported call for a particular sandbox, and Kit returns that error. Set Daytona's target with `region` on the client. A placement block on the create request is rejected. Modal accepts cloud and region on the create -request, and rejects a placement target. Blaxel requires `region` on the -client for create and rejects per-request placement. +request, and rejects a placement target. Blaxel create needs a region via +`Config.region` or `BL_REGION`; Go requires `Config.Region`, and TypeScript +accepts either. Per-request placement is rejected. Closing the client closes your connection to the provider. The sandboxes keep running. Closing a handle leaves the sandbox as it is. You can keep using the diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md b/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md index a1c6ab13..d86058c1 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md @@ -10,6 +10,7 @@ Maps `network.ports` to Blaxel runtime ports. Default/plain → TCP, TLS → TLS - Other network settings (egress mode, CIDRs, private network, proxies) are rejected. - Port numbers must be in range. +- Blaxel reserves ports 80, 443, and 8080; this example uses 3000 and 3001. ## Run diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/main.go b/examples/go/blaxel/configuration/create-blaxel-with-ports/main.go index f6bd4983..d0dbd73c 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-ports/main.go +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/main.go @@ -44,8 +44,8 @@ func main() { Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}}, Network: &sandbox.NetworkConfig{ Ports: []*sandbox.PortBinding{ - {Port: 443, Transport: sandbox.PortTransportTLS}, - {Port: 8080, Transport: sandbox.PortTransportPlain}, + {Port: 3000, Transport: sandbox.PortTransportTLS}, + {Port: 3001, Transport: sandbox.PortTransportPlain}, }, }, }) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md index f6347a84..96293ab9 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md @@ -10,6 +10,7 @@ Maps `network.ports` to Blaxel runtime ports. Default/plain → TCP, TLS → TLS - Other network settings (egress mode, CIDRs, private network, proxies) are rejected. - Port numbers must be in range. +- Blaxel reserves ports 80, 443, and 8080; this example uses 3000 and 3001. ## Run diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/src/main.ts index 53cc6c90..dfcca774 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/src/main.ts +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/src/main.ts @@ -16,8 +16,8 @@ const sandbox = await client.create({ source: { image: { reference: "blaxel/base-image:latest" } }, network: { ports: [ - { port: 443, transport: PortTransport.Tls }, - { port: 8080, transport: PortTransport.Plain }, + { port: 3000, transport: PortTransport.Tls }, + { port: 3001, transport: PortTransport.Plain }, ], }, }); diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 9912a5db..14291e63 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -62,7 +62,9 @@ rejected. `Scope` fields other than `Workspace` are rejected. Create from a registry image with an explicit name. You can set labels, environment variables, memory, ports, outbound domains, maximum lifetime, and -idle terminate. +idle terminate. Max-age TTL must be at least 5m; idle TTL must be at least 1m. +Create provisioning timeout is not mapped (the Go Blaxel SDK has no create +timeout option). On the handle: `Get`, `Refresh`, `Delete`, and `Close`. `Get` and `Refresh` look up the sandbox by name. `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and @@ -83,8 +85,8 @@ exit code is success for the SDK—read `GetExitCode`. Blaxel returns stdout and stderr. Omit or zero `Timeout` for no Blaxel command limit. Command timeouts must be whole seconds. Cancel the context to stop waiting. -Memory is allocated in MiB. Blaxel derives CPU from memory, so `cpu_cores` and -disk MiB overrides are rejected. +Memory is allocated in MiB and must be at least 256 when set. Blaxel derives +CPU from memory, so `cpu_cores` and disk MiB overrides are rejected. ## License diff --git a/sdks/go/providers/blaxel/creation.go b/sdks/go/providers/blaxel/creation.go index a37db775..75a9a4c8 100644 --- a/sdks/go/providers/blaxel/creation.go +++ b/sdks/go/providers/blaxel/creation.go @@ -93,7 +93,7 @@ func (c *creation) lifetime() error { } config := cloneMessage(c.request.Lifetime) if config.MaximumLifetime != nil { - formatted, err := formatBlaxelDuration(config.MaximumLifetime.AsDuration()) + formatted, err := formatBlaxelDuration(config.MaximumLifetime.AsDuration(), 5*time.Minute) if err != nil { return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeMaximumLifetime, err.Error()) } @@ -136,7 +136,7 @@ func mapIdleTerminate(action *sandbox.AutomaticAction) (*blaxel.ExpirationPolicy if action.After == nil { return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminate, "duration is required") } - formatted, err := formatBlaxelDuration(action.After.AsDuration()) + formatted, err := formatBlaxelDuration(action.After.AsDuration(), time.Minute) if err != nil { return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminate, err.Error()) } diff --git a/sdks/go/providers/blaxel/provider_checks.go b/sdks/go/providers/blaxel/provider_checks.go index 5c52e0ac..b5bddfc0 100644 --- a/sdks/go/providers/blaxel/provider_checks.go +++ b/sdks/go/providers/blaxel/provider_checks.go @@ -32,6 +32,9 @@ func validateProviderCreate(request *sandbox.CreateOptions) error { if resources.CpuLimitCores != nil { return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResourcesCpuLimitCores, "Blaxel resource limits are not mapped") } + if resources.MemoryMib != nil && *resources.MemoryMib < 256 { + return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldResourcesMemoryMib, "resources.memory_mib is outside supported values") + } if resources.MemoryLimitMib != nil { return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResourcesMemoryLimitMib, "Blaxel resource limits are not mapped") } diff --git a/sdks/go/providers/blaxel/provider_mappings.go b/sdks/go/providers/blaxel/provider_mappings.go index c22d0095..7e3eb7da 100644 --- a/sdks/go/providers/blaxel/provider_mappings.go +++ b/sdks/go/providers/blaxel/provider_mappings.go @@ -102,10 +102,14 @@ func mapAllocatedResources(source *blaxel.Sandbox) (*sandbox.Resources, error) { } // formatBlaxelDuration formats a whole-second duration as a Blaxel TTL string. -func formatBlaxelDuration(value time.Duration) (string, error) { +// minimum is the Blaxel floor for that policy (max-age TTL: 5m, idle TTL: 1m). +func formatBlaxelDuration(value, minimum time.Duration) (string, error) { if value <= 0 || value%time.Second != 0 { return "", fmt.Errorf("duration must be a positive whole number of seconds") } + if value < minimum { + return "", fmt.Errorf("duration must be at least %s", minimum) + } seconds := int64(value / time.Second) switch { case seconds%86400 == 0: diff --git a/sdks/go/providers/blaxel/rejection_test.go b/sdks/go/providers/blaxel/rejection_test.go index 37978a9c..5ceace45 100644 --- a/sdks/go/providers/blaxel/rejection_test.go +++ b/sdks/go/providers/blaxel/rejection_test.go @@ -92,6 +92,35 @@ func TestRejectedCreateSettings(t *testing.T) { kind: sandbox.ErrorKindInvalidArgument, field: sandbox.CreateFieldLifetimeMaximumLifetime, }, + { + name: "maximum lifetime below floor", + request: namedImage(&sandbox.CreateOptions{ + Lifetime: &sandbox.LifetimePolicy{MaximumLifetime: sandbox.Duration(4 * time.Minute)}, + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldLifetimeMaximumLifetime, + }, + { + name: "idle terminate below floor", + request: namedImage(&sandbox.CreateOptions{ + Lifetime: &sandbox.LifetimePolicy{ + IdleTerminate: &sandbox.AutomaticAction{ + Mode: sandbox.PolicyModeAfter, + After: sandbox.Duration(30 * time.Second), + }, + }, + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldLifetimeIdleTerminate, + }, + { + name: "memory below floor", + request: namedImage(&sandbox.CreateOptions{ + Resources: &sandbox.Resources{MemoryMib: sandbox.Value(uint64(255))}, + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldResourcesMemoryMib, + }, } { t.Run(tc.name, func(t *testing.T) { _, err := planCreate(tc.request, "us-pdx-1") diff --git a/sdks/go/providers/blaxel/sandbox_test.go b/sdks/go/providers/blaxel/sandbox_test.go index a381c6a9..a7939fa2 100644 --- a/sdks/go/providers/blaxel/sandbox_test.go +++ b/sdks/go/providers/blaxel/sandbox_test.go @@ -87,26 +87,29 @@ func TestMapSandboxInfoUsesNameAsID(t *testing.T) { func TestFormatBlaxelDuration(t *testing.T) { for _, tc := range []struct { - in time.Duration - want string - ok bool + in time.Duration + minimum time.Duration + want string + ok bool }{ - {30 * time.Second, "30s", true}, - {5 * time.Minute, "5m", true}, - {2 * time.Hour, "2h", true}, - {48 * time.Hour, "2d", true}, - {1500 * time.Millisecond, "", false}, - {0, "", false}, + {30 * time.Second, time.Second, "30s", true}, + {5 * time.Minute, 5 * time.Minute, "5m", true}, + {2 * time.Hour, 5 * time.Minute, "2h", true}, + {48 * time.Hour, 5 * time.Minute, "2d", true}, + {30 * time.Second, time.Minute, "", false}, + {4 * time.Minute, 5 * time.Minute, "", false}, + {1500 * time.Millisecond, time.Second, "", false}, + {0, time.Second, "", false}, } { - got, err := formatBlaxelDuration(tc.in) + got, err := formatBlaxelDuration(tc.in, tc.minimum) if tc.ok { if err != nil || got != tc.want { - t.Fatalf("%v: got %q err %v", tc.in, got, err) + t.Fatalf("%v (min %v): got %q err %v", tc.in, tc.minimum, got, err) } continue } if err == nil { - t.Fatalf("%v: expected error", tc.in) + t.Fatalf("%v (min %v): expected error", tc.in, tc.minimum) } } } diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index d6a33cb9..b8972df4 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -54,9 +54,9 @@ than `workspace` are rejected. **Create** requires a nonblank `name` and an image source. Optional memory (MiB; Blaxel CPU follows memory), labels, environment variables, ports, outbound -domains, max-age TTL, idle-delete policy, and a create timeout (1–50 whole -seconds). Creation calls native `SandboxInstance.create`, not -`createIfNotExists`. +domains, max-age TTL (at least 5m), idle-delete policy (at least 1m; explicit +disable is rejected), and a create timeout (1–50 whole seconds). Creation +calls native `SandboxInstance.create`, not `createIfNotExists`. **Lifecycle** on the handle: `get` / `refresh` (lookup by name), `delete`, and `close`. `close` does not delete the sandbox. Start, stop, pause, archive, diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index d2a3e1a4..174543c3 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -6,10 +6,12 @@ import { ErrorKind, PolicyMode, PortTransport, + SandboxState, WaitCondition, ConfigFieldAuthApiKeyKey, ConfigFieldEndpoint, CreateFieldEnvironment, + CreateFieldLifetimeIdleTerminate, CreateFieldLifetimeMaximumLifetime, CreateFieldName, CreateFieldResourcesMemoryMib, @@ -181,6 +183,24 @@ describe("Blaxel mappings", () => { ); }); + it("rejects disabling idle terminate", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + lifetime: { idleTerminate: { mode: PolicyMode.Disabled } }, + }, + { scope: { workspace: "ws" } }, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.Unsupported, + field: CreateFieldLifetimeIdleTerminate, + }), + ); + }); + it("classifies native ResponseError status codes", () => { const native = new ResponseError( new Response(null, { status: 404, statusText: "Not Found" }), @@ -333,8 +353,14 @@ describe("Blaxel provider orchestration", () => { scope: { workspace: "ws" }, }); if (backend.delete === undefined) throw new Error("delete is missing"); - await backend.delete("doomed", {}, {}); + const info = await backend.delete("doomed", {}, {}); expect(deleted).toEqual(["doomed"]); + expect(info).toMatchObject({ + id: "doomed", + state: SandboxState.Deleted, + terminal: true, + providerState: "TERMINATED", + }); }); it("does not create a sandbox after the signal aborts during mapping", async () => { diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index bb878a03..2f4bad22 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -15,6 +15,7 @@ import { ExecFieldTimeout, FileFieldParents, FileType, + SandboxState, type FileInfo, type OperationOptions, type Provider, @@ -190,7 +191,12 @@ class BlaxelBackend extends BackendBase implements Backend { throwIfAborted(options.signal); const info = mapSandboxInfo(sandbox); await sandbox.delete(); - return info; + return { + ...info, + state: SandboxState.Deleted, + terminal: true, + providerState: "TERMINATED", + }; } catch (error) { throw mapBlaxelError(error, "delete"); } diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index 7b3e4db4..69d410ea 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -18,6 +18,7 @@ import { ConfigFieldRegion, CreateFieldIsolation, CreateFieldLifetime, + CreateFieldLifetimeIdleTerminate, CreateFieldLifetimeIdleTerminateAfter, CreateFieldLifetimeIdleTerminateMode, CreateFieldLifetimeMaximumLifetime, @@ -234,7 +235,10 @@ function mapLifetime(r: CreateOptions, p: SandboxCreateConfiguration): void { if (v.idleTerminate) { const mode = v.idleTerminate.mode ?? PolicyMode.Default; if (mode === PolicyMode.Disabled) { - // Explicit disable: omit idle policy. + unsupported( + CreateFieldLifetimeIdleTerminate, + "disabling idle terminate is not representable", + ); } else if (mode === PolicyMode.After) { if (v.idleTerminate.after === undefined) invalid(CreateFieldLifetimeIdleTerminateAfter, "duration is required"); diff --git a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts index ef514130..66a102c1 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts @@ -134,7 +134,8 @@ function mapSandboxState(status: string): SandboxState | undefined { return SandboxState.Error; case "TERMINATED": case "DELETED": - return SandboxState.Terminated; + case "DEACTIVATED": + return SandboxState.Deleted; default: return undefined; } diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml index 99276338..2f6fc233 100644 --- a/specs/providers/blaxel.yaml +++ b/specs/providers/blaxel.yaml @@ -8,6 +8,7 @@ checks: - { path: placement, unsupported: true } - { path: resources.cpu_cores, unsupported: true } - { path: resources.cpu_limit_cores, unsupported: true } + - { path: resources.memory_mib, minimum: 256 } - { path: resources.memory_limit_mib, unsupported: true } - { path: resources.disk_mib, unsupported: true } client: diff --git a/website/content/docs/configuration.mdx b/website/content/docs/configuration.mdx index 22951eca..7a05c606 100644 --- a/website/content/docs/configuration.mdx +++ b/website/content/docs/configuration.mdx @@ -42,7 +42,7 @@ if err != nil { | ---------- | ------------------------------------------------------------------------------------------- | | `provider` | Provider constructor | | `auth` | One of `apiKey`, `tokenPair`, `bearerToken`, `oauthRefresh`. Omit to use provider env creds | -| `scope` | App, environment, or organization | +| `scope` | App, environment, organization, or workspace | | `endpoint` | Provider endpoint URL override | | `region` | Target / default placement | | `timeout` | Default deadline for calls that omit their own | @@ -51,6 +51,7 @@ if err != nil { Provider setup: [Daytona](/docs/daytona). Provider setup: [Modal](/docs/modal). +Provider setup: [Blaxel](/docs/blaxel). ## Client From 531ddaea84658555c67c3f1093cd190078460063 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Nov 2026 14:06:46 +0000 Subject: [PATCH 06/60] Fix Blaxel Ready conflation and drop unproven floors. Reject WaitCondition.Ready in TS (sandbox.wait is a deprecated no-op), map provisioning.timeout only as Blaxel create timeout, drop the undocumented 256 MiB memory floor, pre-reject reserved ports, align SANDBOX_ALREADY_EXISTS to AlreadyExists, and document global settings. Co-authored-by: Nabhag Motivaras --- .../create-blaxel-with-resources/README.md | 5 +- .../create-blaxel-with-resources/README.md | 5 +- sdks/go/providers/blaxel/README.md | 8 ++-- sdks/go/providers/blaxel/creation.go | 3 ++ sdks/go/providers/blaxel/errors.go | 3 ++ sdks/go/providers/blaxel/errors_test.go | 17 +++++++ sdks/go/providers/blaxel/filesystem.go | 3 ++ sdks/go/providers/blaxel/provider_checks.go | 3 -- sdks/go/providers/blaxel/rejection_test.go | 18 ++++--- sdks/typescript/providers/blaxel/README.md | 15 ++++-- .../providers/blaxel/src/blaxel.test.ts | 48 +++++++++++++++---- .../typescript/providers/blaxel/src/blaxel.ts | 12 ----- .../blaxel/src/checks/provider_checks.ts | 10 ---- .../providers/blaxel/src/mappings/mappings.ts | 43 +++++------------ specs/providers/blaxel.yaml | 1 - 15 files changed, 108 insertions(+), 86 deletions(-) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-resources/README.md b/examples/go/blaxel/configuration/create-blaxel-with-resources/README.md index 20fa9d31..deb277cf 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-resources/README.md +++ b/examples/go/blaxel/configuration/create-blaxel-with-resources/README.md @@ -4,12 +4,11 @@ Create one Blaxel sandbox with an explicit memory size in MiB. Blaxel derives CP ## What this means -Only `resources.memoryMib` is mapped. CPU cores, limits, and disk MiB overrides are rejected. Blaxel requires at least 256 MiB when memory is set. +Only `resources.memoryMib` is mapped. CPU cores, limits, and disk MiB overrides are rejected. Omitted memory keeps the Blaxel default. ## Edge cases -- Omitted memory keeps the Blaxel default. -- Fractional or sub-256 MiB values fail validation. +- Memory values that Blaxel rejects are returned as provider errors. ## Run diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/README.md index d4848b99..20d973a8 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/README.md @@ -4,12 +4,11 @@ Create one Blaxel sandbox with an explicit memory size in MiB. Blaxel derives CP ## What this means -Only `resources.memoryMib` is mapped. CPU cores, limits, and disk MiB overrides are rejected. Blaxel requires at least 256 MiB when memory is set. +Only `resources.memoryMib` is mapped. CPU cores, limits, and disk MiB overrides are rejected. Omitted memory keeps the Blaxel default. ## Edge cases -- Omitted memory keeps the Blaxel default. -- Fractional or sub-256 MiB values fail validation. +- Memory values that Blaxel rejects are returned as provider errors. ## Run diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 14291e63..78a1af60 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -63,8 +63,8 @@ rejected. `Scope` fields other than `Workspace` are rejected. Create from a registry image with an explicit name. You can set labels, environment variables, memory, ports, outbound domains, maximum lifetime, and idle terminate. Max-age TTL must be at least 5m; idle TTL must be at least 1m. -Create provisioning timeout is not mapped (the Go Blaxel SDK has no create -timeout option). +Ports 80, 443, and 8080 are rejected (Blaxel-reserved). Create provisioning +timeout is not mapped (the Go Blaxel SDK has no create timeout option). On the handle: `Get`, `Refresh`, `Delete`, and `Close`. `Get` and `Refresh` look up the sandbox by name. `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and @@ -85,8 +85,8 @@ exit code is success for the SDK—read `GetExitCode`. Blaxel returns stdout and stderr. Omit or zero `Timeout` for no Blaxel command limit. Command timeouts must be whole seconds. Cancel the context to stop waiting. -Memory is allocated in MiB and must be at least 256 when set. Blaxel derives -CPU from memory, so `cpu_cores` and disk MiB overrides are rejected. +Memory is allocated in MiB. Blaxel derives CPU from memory, so `cpu_cores` and +disk MiB overrides are rejected. ## License diff --git a/sdks/go/providers/blaxel/creation.go b/sdks/go/providers/blaxel/creation.go index 75a9a4c8..29c04ec7 100644 --- a/sdks/go/providers/blaxel/creation.go +++ b/sdks/go/providers/blaxel/creation.go @@ -215,6 +215,9 @@ func (c *creation) network() error { if binding == nil { continue } + if binding.Port == 80 || binding.Port == 443 || binding.Port == 8080 { + return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldNetworkPortsPort, "ports 80, 443, and 8080 are reserved by Blaxel") + } protocol, err := blaxelPortProtocol(binding.Transport) if err != nil { return err diff --git a/sdks/go/providers/blaxel/errors.go b/sdks/go/providers/blaxel/errors.go index 272e7ca4..2504f528 100644 --- a/sdks/go/providers/blaxel/errors.go +++ b/sdks/go/providers/blaxel/errors.go @@ -52,6 +52,9 @@ func mapProviderError(err error, operation string) error { kind = sandbox.ErrorKindResourceExhausted case sdkblaxel.ErrBadRequest, sdkblaxel.ErrPolicyViolation: kind = sandbox.ErrorKindInvalidArgument + case "SANDBOX_ALREADY_EXISTS", "VOLUME_ALREADY_ATTACHED": + // Control-plane codes (not in gateway apierror constants). + kind = sandbox.ErrorKindAlreadyExists } } if kind == sandbox.ErrorKindUnknown { diff --git a/sdks/go/providers/blaxel/errors_test.go b/sdks/go/providers/blaxel/errors_test.go index 5fc414f8..98fb6568 100644 --- a/sdks/go/providers/blaxel/errors_test.go +++ b/sdks/go/providers/blaxel/errors_test.go @@ -35,3 +35,20 @@ func TestMapProviderErrorPreservesContext(t *testing.T) { t.Fatalf("error = %v detail = %#v", err, detail) } } + +func TestMapProviderErrorSandboxAlreadyExists(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes/demo") + if err != nil { + t.Fatal(err) + } + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusConflict, + ErrorCode: "SANDBOX_ALREADY_EXISTS", + Request: &http.Request{Method: http.MethodPost, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusConflict}, + }, "create") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindAlreadyExists { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } +} diff --git a/sdks/go/providers/blaxel/filesystem.go b/sdks/go/providers/blaxel/filesystem.go index 76dbd7b9..6356d5c0 100644 --- a/sdks/go/providers/blaxel/filesystem.go +++ b/sdks/go/providers/blaxel/filesystem.go @@ -122,6 +122,9 @@ func (b *backend) Stat(ctx context.Context, id, path string, _ *sandbox.StatOpti if err != nil { return nil, err } + // SandboxInstanceFSService has no public Get/Stat; metadata is on the lower + // SandboxFilesystemService. Rebuild with the instance base URL so Stat uses + // the same sandbox endpoint as instance.FS without private-field access. fs := b.filesystemService(instance) res, err := fs.Get(ctx, path, blaxel.SandboxFilesystemGetParams{}) if err != nil { diff --git a/sdks/go/providers/blaxel/provider_checks.go b/sdks/go/providers/blaxel/provider_checks.go index b5bddfc0..5c52e0ac 100644 --- a/sdks/go/providers/blaxel/provider_checks.go +++ b/sdks/go/providers/blaxel/provider_checks.go @@ -32,9 +32,6 @@ func validateProviderCreate(request *sandbox.CreateOptions) error { if resources.CpuLimitCores != nil { return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResourcesCpuLimitCores, "Blaxel resource limits are not mapped") } - if resources.MemoryMib != nil && *resources.MemoryMib < 256 { - return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldResourcesMemoryMib, "resources.memory_mib is outside supported values") - } if resources.MemoryLimitMib != nil { return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResourcesMemoryLimitMib, "Blaxel resource limits are not mapped") } diff --git a/sdks/go/providers/blaxel/rejection_test.go b/sdks/go/providers/blaxel/rejection_test.go index 5ceace45..4e7c8015 100644 --- a/sdks/go/providers/blaxel/rejection_test.go +++ b/sdks/go/providers/blaxel/rejection_test.go @@ -114,12 +114,16 @@ func TestRejectedCreateSettings(t *testing.T) { field: sandbox.CreateFieldLifetimeIdleTerminate, }, { - name: "memory below floor", + name: "reserved port", request: namedImage(&sandbox.CreateOptions{ - Resources: &sandbox.Resources{MemoryMib: sandbox.Value(uint64(255))}, + Network: &sandbox.NetworkConfig{ + Ports: []*sandbox.PortBinding{ + {Port: 443, Transport: sandbox.PortTransportTLS}, + }, + }, }), kind: sandbox.ErrorKindInvalidArgument, - field: sandbox.CreateFieldResourcesMemoryMib, + field: sandbox.CreateFieldNetworkPortsPort, }, } { t.Run(tc.name, func(t *testing.T) { @@ -215,8 +219,8 @@ func TestMappedNetworkPortsAndDomains(t *testing.T) { Network: &sandbox.NetworkConfig{ OutboundDomains: &sandbox.StringAllowlist{Entries: []string{"example.com"}}, Ports: []*sandbox.PortBinding{ - {Port: 443, Transport: sandbox.PortTransportTLS}, - {Port: 8080, Transport: sandbox.PortTransportPlain}, + {Port: 3000, Transport: sandbox.PortTransportTLS}, + {Port: 3001, Transport: sandbox.PortTransportPlain}, }, }, }), "us-pdx-1") @@ -227,10 +231,10 @@ func TestMappedNetworkPortsAndDomains(t *testing.T) { t.Fatalf("AllowedDomains = %#v", got) } ports := plan.params.Sandbox.Spec.Runtime.Ports - if len(ports) != 2 || ports[0].Target != 443 || ports[0].Protocol != blaxel.PortProtocolTls { + if len(ports) != 2 || ports[0].Target != 3000 || ports[0].Protocol != blaxel.PortProtocolTls { t.Fatalf("Ports = %#v", ports) } - if ports[1].Target != 8080 || ports[1].Protocol != blaxel.PortProtocolTcp { + if ports[1].Target != 3001 || ports[1].Protocol != blaxel.PortProtocolTcp { t.Fatalf("Ports[1] = %#v", ports[1]) } } diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index b8972df4..1a06aad6 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -55,8 +55,10 @@ than `workspace` are rejected. **Create** requires a nonblank `name` and an image source. Optional memory (MiB; Blaxel CPU follows memory), labels, environment variables, ports, outbound domains, max-age TTL (at least 5m), idle-delete policy (at least 1m; explicit -disable is rejected), and a create timeout (1–50 whole seconds). Creation -calls native `SandboxInstance.create`, not `createIfNotExists`. +disable is rejected), and a create timeout (1–50 whole seconds). +`provisioning.waitFor` Ready is unsupported (`sandbox.wait` is a deprecated +no-op in `@blaxel/core`). Creation calls native `SandboxInstance.create`, not +`createIfNotExists`. Ports 80, 443, and 8080 are rejected (Blaxel-reserved). **Lifecycle** on the handle: `get` / `refresh` (lookup by name), `delete`, and `close`. `close` does not delete the sandbox. Start, stop, pause, archive, @@ -78,9 +80,12 @@ command limit. Command timeouts must be whole seconds (`2000` is two seconds; `1500` is rejected). Aborting the signal rejects the `await`; the remote command may keep running. -When you set memory, Blaxel requires at least 256 MiB. Omitted memory keeps -the Blaxel default. CPU, CPU limit, memory limit, and disk overrides are -rejected. +Omitted memory keeps the Blaxel default. CPU, CPU limit, memory limit, and +disk overrides are rejected. + +`@blaxel/core` applies credentials through process-global `settings`. Two Kit +Blaxel clients in one process overwrite each other; construct one client per +process or reconfigure before each use. ## License diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 174543c3..63a3de47 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -14,7 +14,8 @@ import { CreateFieldLifetimeIdleTerminate, CreateFieldLifetimeMaximumLifetime, CreateFieldName, - CreateFieldResourcesMemoryMib, + CreateFieldNetworkPortsPort, + CreateFieldProvisioningWaitFor, FileFieldLocalPath, } from "@sandbox-kit/sandbox"; import { mapClientConfig, mapCreateRequest } from "#mappings/mappings.js"; @@ -116,7 +117,6 @@ describe("Blaxel mappings", () => { }, provisioning: { timeout: 30_000, - waitFor: WaitCondition.Ready, }, labels: { purpose: "test" }, environment: { NODE_ENV: "test" }, @@ -124,8 +124,6 @@ describe("Blaxel mappings", () => { { scope: { workspace: "ws" }, region: "us-pdx-1" }, ); expect(plan).toMatchObject({ - waitUntilReady: true, - readyTimeoutMs: 30_000, options: { timeout: 30 }, params: { name: "example", @@ -146,8 +144,8 @@ describe("Blaxel mappings", () => { }); }); - it("rejects memory below the Blaxel floor", () => { - expect(() => + it("maps memory without a Kit-side floor", () => { + expect( mapCreateRequest( { name: "example", @@ -155,14 +153,48 @@ describe("Blaxel mappings", () => { resources: { memoryMib: 128n }, }, { scope: { workspace: "ws" } }, + ).params.memory, + ).toBe(128); + expect(mapResources({ memoryMib: 4096n })).toEqual({ memory: 4096 }); + }); + + it("rejects WaitCondition.Ready", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + provisioning: { + timeout: 30_000, + waitFor: WaitCondition.Ready, + }, + }, + { scope: { workspace: "ws" } }, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.Unsupported, + field: CreateFieldProvisioningWaitFor, + }), + ); + }); + + it("rejects Blaxel-reserved ports", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + network: { ports: [{ port: 443, transport: PortTransport.Tls }] }, + }, + { scope: { workspace: "ws" } }, ), ).toThrowError( expect.objectContaining({ kind: ErrorKind.InvalidArgument, - field: CreateFieldResourcesMemoryMib, + field: CreateFieldNetworkPortsPort, }), ); - expect(mapResources({ memoryMib: 4096n })).toEqual({ memory: 4096 }); }); it("rejects lifetime durations that are not whole seconds", () => { diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index 2f4bad22..039902be 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -83,10 +83,6 @@ interface NativeSandboxHandle { stderr?: string; }>; }; - wait(options?: { - maxWait?: number; - interval?: number; - }): Promise; delete(): Promise; } @@ -155,14 +151,6 @@ class BlaxelBackend extends BackendBase implements Backend { const plan = mapCreateRequest(request, this.config); throwIfAborted(options.signal); const sandbox = await this.native.create(plan.params, plan.options); - if (plan.waitUntilReady) { - throwIfAborted(options.signal); - await sandbox.wait({ - ...(plan.readyTimeoutMs === undefined - ? {} - : { maxWait: plan.readyTimeoutMs }), - }); - } return { sandbox: mapSandboxInfo(sandbox, request) }; } catch (error) { throw mapBlaxelError(error, "create"); diff --git a/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts b/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts index de2c3f86..b4aa2d5e 100644 --- a/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts +++ b/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts @@ -1,7 +1,6 @@ import { ErrorKind, CreateFieldEnvironment, - CreateFieldResourcesMemoryMib, validEnvName, } from "@sandbox-kit/sandbox"; import type { CreateOptions } from "@sandbox-kit/sandbox"; @@ -10,15 +9,6 @@ import { providerError } from "#errors.js"; // validateProviderCreate rejects creation settings this provider cannot honor. // Rules are inventoried in specs/providers/blaxel.yaml checks when present. export function validateProviderCreate(request: CreateOptions): void { - const memoryMib = request.resources?.memoryMib; - if (memoryMib !== undefined && memoryMib < BigInt("256")) { - providerError({ - kind: ErrorKind.InvalidArgument, - operation: "create", - field: CreateFieldResourcesMemoryMib, - message: "resources.memory_mib is outside supported values", - }); - } for (const value of Object.keys(request.environment ?? {})) { if (!validEnvName(value)) { providerError({ diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index 69d410ea..1d4f81aa 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -52,8 +52,6 @@ import { providerError } from "#errors.js"; export interface BlaxelCreatePlan { readonly params: SandboxCreateConfiguration; readonly options?: SandboxCreateOptions; - readonly waitUntilReady: boolean; - readonly readyTimeoutMs?: number; } export function mapClientConfig(config: Config): NativeClientConfig { @@ -91,15 +89,11 @@ export function mapCreateRequest( mapLifetime(request, params); mapReadiness(request); mapObservability(request); - const wait = mapProvisioning(request); + const options = mapProvisioning(request); return { params, - ...(wait.options === undefined ? {} : { options: wait.options }), - waitUntilReady: wait.waitUntilReady, - ...(wait.readyTimeoutMs === undefined - ? {} - : { readyTimeoutMs: wait.readyTimeoutMs }), + ...(options === undefined ? {} : { options }), }; } @@ -174,6 +168,11 @@ function mapNetwork(r: CreateOptions, p: SandboxCreateConfiguration): void { const port = x.port; if (port === undefined) invalid(CreateFieldNetworkPortsPort, "port is required"); + if (port === 80 || port === 443 || port === 8080) + invalid( + CreateFieldNetworkPortsPort, + "ports 80, 443, and 8080 are reserved by Blaxel", + ); ports.push({ target: port, protocol: blaxelPortProtocol( @@ -269,13 +268,12 @@ function mapObservability(r: CreateOptions): void { ); } -function mapProvisioning(r: CreateOptions): { - waitUntilReady: boolean; - readyTimeoutMs?: number; - options?: SandboxCreateOptions; -} { +// mapProvisioning maps create timeout only. WaitCondition.Ready is rejected: +// @blaxel/core sandbox.wait() is a deprecated no-op, and Ready previously +// conflated that with Blaxel's destructive creation deadline (1–50s). +function mapProvisioning(r: CreateOptions): SandboxCreateOptions | undefined { const v = r.provisioning; - if (!v) return { waitUntilReady: false }; + if (!v) return undefined; if (v.queueTimeout !== undefined) unsupported( CreateFieldProvisioningQueueTimeout, @@ -299,22 +297,7 @@ function mapProvisioning(r: CreateOptions): { const waitFor = v.waitFor ?? WaitCondition.Default; if (waitFor === WaitCondition.Default || waitFor === WaitCondition.Scheduled) - return { - waitUntilReady: false, - ...(options === undefined ? {} : { options }), - }; - if (waitFor === WaitCondition.Ready) { - if (v.timeout === undefined || v.timeout <= 0) - invalid( - CreateFieldProvisioningTimeout, - "ready wait requires a positive timeout", - ); - return { - waitUntilReady: true, - readyTimeoutMs: v.timeout, - ...(options === undefined ? {} : { options }), - }; - } + return options; unsupported( CreateFieldProvisioningWaitFor, "requested wait condition is unsupported", diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml index 2f6fc233..99276338 100644 --- a/specs/providers/blaxel.yaml +++ b/specs/providers/blaxel.yaml @@ -8,7 +8,6 @@ checks: - { path: placement, unsupported: true } - { path: resources.cpu_cores, unsupported: true } - { path: resources.cpu_limit_cores, unsupported: true } - - { path: resources.memory_mib, minimum: 256 } - { path: resources.memory_limit_mib, unsupported: true } - { path: resources.disk_mib, unsupported: true } client: From abaecfbee736c16684a11332a33afc9e48b08ea8 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 15:07:45 +0530 Subject: [PATCH 07/60] Align Blaxel Go/TS create and state mapping. Require client region and reject provisioning create timeout in both languages, map DEPLOYED+STANDBY to paused in TypeScript, and pin @blaxel/core 0.3.36. Co-authored-by: Cursor Entire-Checkpoint: 01M72D46E7HQX3GFKVGVZYM9A6 --- docs/configuration.md | 3 +- docs/provider-verification.md | 3 +- docs/providers.md | 5 +- docs/sandbox-creation.md | 12 +- .../create-blaxel-sandbox/README.md | 5 +- sdks/go/providers/blaxel/README.md | 2 +- sdks/go/providers/blaxel/rejection_test.go | 13 +++ sdks/go/providers/blaxel/sandbox_test.go | 12 ++ sdks/typescript/package-lock.json | 8 +- sdks/typescript/providers/blaxel/README.md | 16 +-- sdks/typescript/providers/blaxel/package.json | 2 +- .../providers/blaxel/src/blaxel.test.ts | 107 +++++++++++++++--- .../typescript/providers/blaxel/src/blaxel.ts | 1 + .../providers/blaxel/src/mappings/mappings.ts | 47 +++----- .../blaxel/src/mappings/provider_mappings.ts | 77 +++++++++---- .../providers/blaxel/src/rejection.test.ts | 27 ++++- specs/providers/blaxel.yaml | 2 +- 17 files changed, 242 insertions(+), 100 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 240a066b..494c8293 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -46,7 +46,8 @@ does not change the handle. ## Provider support -The bindings use Modal v0.11.1, Daytona v0.225.0, and Blaxel SDK Go/TS current pins. +The bindings use Modal v0.11.1, Daytona v0.225.0, `@blaxel/core@0.3.36`, and +`github.com/blaxel-ai/sdk-go@v0.31.0`. | Setting | Modal | Daytona | Blaxel | | ---------------- | ---------------------------------------------------------- | --------------------------------- | --------------------------------- | diff --git a/docs/provider-verification.md b/docs/provider-verification.md index 8dd3cc61..5e73ffef 100644 --- a/docs/provider-verification.md +++ b/docs/provider-verification.md @@ -1,6 +1,7 @@ # Provider reference verification -Go and TypeScript bindings target Modal v0.11.1, Daytona v0.225.0, and Blaxel (`@blaxel/core` / `github.com/blaxel-ai/sdk-go`) +Go and TypeScript bindings target Modal v0.11.1, Daytona v0.225.0, and Blaxel +`@blaxel/core@0.3.36` / `github.com/blaxel-ai/sdk-go@v0.31.0` (`modal@0.11.1`, `@daytona/sdk@0.225.0`). Latest documentation helps discover APIs; pinned SDK source and compilation determine the signatures and serialization used by this checkout. diff --git a/docs/providers.md b/docs/providers.md index 96ef1be6..272f229b 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -66,9 +66,8 @@ supported call for a particular sandbox, and Kit returns that error. Set Daytona's target with `region` on the client. A placement block on the create request is rejected. Modal accepts cloud and region on the create -request, and rejects a placement target. Blaxel create needs a region via -`Config.region` or `BL_REGION`; Go requires `Config.Region`, and TypeScript -accepts either. Per-request placement is rejected. +request, and rejects a placement target. Blaxel create requires +`Config.Region` / `region` on the client. Per-request placement is rejected. Closing the client closes your connection to the provider. The sandboxes keep running. Closing a handle leaves the sandbox as it is. You can keep using the diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index 69073a55..155eb9aa 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -7,7 +7,8 @@ from lifecycle operations on an existing sandbox. ## Supported creation settings Go and TypeScript integrations target Modal v0.11.1, Daytona v0.225.0, and -Blaxel (`modal@0.11.1`, `@daytona/sdk@0.225.0`, `@blaxel/core` / `sdk-go`). +Blaxel (`modal@0.11.1`, `@daytona/sdk@0.225.0`, `@blaxel/core@0.3.36` / +`github.com/blaxel-ai/sdk-go@v0.31.0`). | Setting | Modal | Daytona | | ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | @@ -24,10 +25,11 @@ Blaxel (`modal@0.11.1`, `@daytona/sdk@0.225.0`, `@blaxel/core` / `sdk-go`). | Observability | Verbose output | Telemetry endpoint | -Blaxel create requires a nonblank name and an image source. It maps memory MiB, -labels, environment, ports, outbound domains, maximum lifetime, and idle -terminate. CPU/disk overrides, placement, runtime, isolation, security, and -most egress modes are rejected. Region comes from the client config. +Blaxel create requires a nonblank name, an image source, and a client +`region`. It maps memory MiB, labels, environment, ports, outbound domains, +maximum lifetime, and idle terminate. CPU/disk overrides, placement, runtime, +isolation, security, provisioning create timeout, and most egress modes are +rejected. Modal default, snapshot, and warm-pool sources are unavailable. Daytona warm pools and explicit snapshot kinds are unavailable. A field the provider does diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md index 93b6e20d..b2a1e73a 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md @@ -4,7 +4,10 @@ Create one Blaxel sandbox from a registry image with an explicit name. Prints th ## What this means -Blaxel create requires a nonblank `name` and an image source. Kit calls native create (not create-if-not-exists). Region comes from the client config (`BL_REGION`, default `us-pdx-1`). Closing the Kit client only drops the local SDK connection. +Blaxel create requires a nonblank `name`, an image source, and client `region` +(examples read `BL_REGION`, default `us-pdx-1`). Kit calls native create (not +create-if-not-exists). Closing the Kit client only drops the local SDK +connection. ## Edge cases diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 78a1af60..ec73f1e2 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -64,7 +64,7 @@ Create from a registry image with an explicit name. You can set labels, environment variables, memory, ports, outbound domains, maximum lifetime, and idle terminate. Max-age TTL must be at least 5m; idle TTL must be at least 1m. Ports 80, 443, and 8080 are rejected (Blaxel-reserved). Create provisioning -timeout is not mapped (the Go Blaxel SDK has no create timeout option). +timeout is not mapped. On the handle: `Get`, `Refresh`, `Delete`, and `Close`. `Get` and `Refresh` look up the sandbox by name. `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and diff --git a/sdks/go/providers/blaxel/rejection_test.go b/sdks/go/providers/blaxel/rejection_test.go index 4e7c8015..7e240e15 100644 --- a/sdks/go/providers/blaxel/rejection_test.go +++ b/sdks/go/providers/blaxel/rejection_test.go @@ -204,6 +204,19 @@ func TestMissingRegionRejected(t *testing.T) { } } +func TestCreateTimeoutRejected(t *testing.T) { + _, err := planCreate(namedImage(&sandbox.CreateOptions{ + Provisioning: &sandbox.ProvisioningOptions{Timeout: sandbox.Duration(30 * time.Second)}, + }), "us-pdx-1") + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindUnsupported || detail.Field != sandbox.CreateFieldProvisioningTimeout { + t.Fatalf("error = %v detail = %#v", err, detail) + } + if detail.Message != "sandbox-kit blaxel: Blaxel create timeout is not mapped" { + t.Fatalf("message = %q", detail.Message) + } +} + func imaged(request *sandbox.CreateOptions) *sandbox.CreateOptions { request.Source = &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}} return request diff --git a/sdks/go/providers/blaxel/sandbox_test.go b/sdks/go/providers/blaxel/sandbox_test.go index a7939fa2..a807ff0d 100644 --- a/sdks/go/providers/blaxel/sandbox_test.go +++ b/sdks/go/providers/blaxel/sandbox_test.go @@ -83,6 +83,18 @@ func TestMapSandboxInfoUsesNameAsID(t *testing.T) { if observed.State != sandbox.SandboxStateRunning || observed.Resources.GetMemoryMib() != 2048 { t.Fatalf("observed = %#v", observed) } + + standby, err := sandboxInfo(&blaxel.Sandbox{ + Metadata: blaxel.Metadata{Name: "demo"}, + Status: blaxel.StatusDeployed, + State: blaxel.SandboxStateStandby, + }) + if err != nil { + t.Fatal(err) + } + if standby.State != sandbox.SandboxStatePaused { + t.Fatalf("standby state = %#v", standby.State) + } } func TestFormatBlaxelDuration(t *testing.T) { diff --git a/sdks/typescript/package-lock.json b/sdks/typescript/package-lock.json index d6229afa..9ee871c9 100644 --- a/sdks/typescript/package-lock.json +++ b/sdks/typescript/package-lock.json @@ -387,9 +387,9 @@ } }, "node_modules/@blaxel/core": { - "version": "0.3.35", - "resolved": "https://registry.npmjs.org/@blaxel/core/-/core-0.3.35.tgz", - "integrity": "sha512-R71Qus/sQ5tuF3Em+0p2h44cPrCGg58pwXlMbz/35CIEDPr9HaNmUQAzlVklCdBACOWUTzIwsqp2nB0jFEqZug==", + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@blaxel/core/-/core-0.3.36.tgz", + "integrity": "sha512-nzwK3QMe6kxcw/8kihdYQjpm+Lu2LnOGm/QTySjCJLk7h8nFRAhNQU/fbKCOT0GB5UhuMtTsP0meWWMmFtDuLQ==", "license": "MIT", "dependencies": { "@hey-api/client-fetch": "^0.10.0", @@ -6562,7 +6562,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index 1a06aad6..4a7b2b7b 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -44,7 +44,7 @@ TTL / idle policies or `delete`. | API key | `auth: { apiKey: { key } }` | | Omitted `auth` | Blaxel SDK environment / CLI credentials | | Workspace | `scope.workspace` — Blaxel workspace name | -| Region | `region` — default create placement | +| Region | `region` — required for create | | Endpoint | Not supported | Token-pair, bearer, and OAuth client modes are rejected. Scope fields other @@ -52,13 +52,13 @@ than `workspace` are rejected. ## What you can do -**Create** requires a nonblank `name` and an image source. Optional memory -(MiB; Blaxel CPU follows memory), labels, environment variables, ports, outbound -domains, max-age TTL (at least 5m), idle-delete policy (at least 1m; explicit -disable is rejected), and a create timeout (1–50 whole seconds). -`provisioning.waitFor` Ready is unsupported (`sandbox.wait` is a deprecated -no-op in `@blaxel/core`). Creation calls native `SandboxInstance.create`, not -`createIfNotExists`. Ports 80, 443, and 8080 are rejected (Blaxel-reserved). +**Create** requires a nonblank `name`, an image source, and client `region`. +Optional memory (MiB; Blaxel CPU follows memory), labels, environment +variables, ports, outbound domains, max-age TTL (at least 5m), and idle-delete +policy (at least 1m; explicit disable is rejected). +`provisioning.timeout` and `provisioning.waitFor` Ready are unsupported. +Creation calls native `SandboxInstance.create`, not `createIfNotExists`. +Ports 80, 443, and 8080 are rejected (Blaxel-reserved). **Lifecycle** on the handle: `get` / `refresh` (lookup by name), `delete`, and `close`. `close` does not delete the sandbox. Start, stop, pause, archive, diff --git a/sdks/typescript/providers/blaxel/package.json b/sdks/typescript/providers/blaxel/package.json index 21197e73..ab11352c 100644 --- a/sdks/typescript/providers/blaxel/package.json +++ b/sdks/typescript/providers/blaxel/package.json @@ -65,7 +65,7 @@ "test": "tsc -p tsconfig.test.json && vitest run" }, "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 63a3de47..151ad40e 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -10,21 +10,28 @@ import { WaitCondition, ConfigFieldAuthApiKeyKey, ConfigFieldEndpoint, + ConfigFieldRegion, CreateFieldEnvironment, CreateFieldLifetimeIdleTerminate, CreateFieldLifetimeMaximumLifetime, CreateFieldName, CreateFieldNetworkPortsPort, + CreateFieldProvisioningTimeout, CreateFieldProvisioningWaitFor, FileFieldLocalPath, } from "@sandbox-kit/sandbox"; import { mapClientConfig, mapCreateRequest } from "#mappings/mappings.js"; import { BlaxelProvider } from "./blaxel.js"; -import { mapResources } from "#mappings/provider_mappings.js"; +import { + mapResources, + mapSandboxIdentity, +} from "#mappings/provider_mappings.js"; import { captureProviderState } from "#config/provider_config.js"; import { mapBlaxelError } from "#errors.js"; import { ResponseError } from "@blaxel/core"; +const provider = { scope: { workspace: "ws" }, region: "us-pdx-1" }; + describe("Blaxel mappings", () => { it("maps API key authentication and workspace scope", () => { expect( @@ -51,11 +58,10 @@ describe("Blaxel mappings", () => { }); it("requires name and image on create", () => { - const config = { scope: { workspace: "ws" } }; expect(() => mapCreateRequest( { source: { image: { reference: "blaxel/base-image:latest" } } }, - config, + provider, ), ).toThrowError( expect.objectContaining({ @@ -65,6 +71,23 @@ describe("Blaxel mappings", () => { ); }); + it("requires client region on create", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + }, + { scope: { workspace: "ws" } }, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: ConfigFieldRegion, + }), + ); + }); + it("rejects blank auth credentials in Client construction", () => { expect( () => @@ -115,16 +138,13 @@ describe("Blaxel mappings", () => { after: 30 * 60_000, }, }, - provisioning: { - timeout: 30_000, - }, labels: { purpose: "test" }, environment: { NODE_ENV: "test" }, }, - { scope: { workspace: "ws" }, region: "us-pdx-1" }, + provider, ); + expect(plan.options).toBeUndefined(); expect(plan).toMatchObject({ - options: { timeout: 30 }, params: { name: "example", image: "blaxel/base-image:latest", @@ -152,12 +172,30 @@ describe("Blaxel mappings", () => { source: { image: { reference: "blaxel/base-image:latest" } }, resources: { memoryMib: 128n }, }, - { scope: { workspace: "ws" } }, + provider, ).params.memory, ).toBe(128); expect(mapResources({ memoryMib: 4096n })).toEqual({ memory: 4096 }); }); + it("rejects create timeout", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + provisioning: { timeout: 30_000 }, + }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.Unsupported, + field: CreateFieldProvisioningTimeout, + }), + ); + }); + it("rejects WaitCondition.Ready", () => { expect(() => mapCreateRequest( @@ -165,11 +203,10 @@ describe("Blaxel mappings", () => { name: "example", source: { image: { reference: "blaxel/base-image:latest" } }, provisioning: { - timeout: 30_000, waitFor: WaitCondition.Ready, }, }, - { scope: { workspace: "ws" } }, + provider, ), ).toThrowError( expect.objectContaining({ @@ -187,7 +224,7 @@ describe("Blaxel mappings", () => { source: { image: { reference: "blaxel/base-image:latest" } }, network: { ports: [{ port: 443, transport: PortTransport.Tls }] }, }, - { scope: { workspace: "ws" } }, + provider, ), ).toThrowError( expect.objectContaining({ @@ -205,7 +242,7 @@ describe("Blaxel mappings", () => { source: { image: { reference: "blaxel/base-image:latest" } }, lifetime: { maximumLifetime: 1_500 }, }, - { scope: { workspace: "ws" } }, + provider, ), ).toThrowError( expect.objectContaining({ @@ -223,7 +260,7 @@ describe("Blaxel mappings", () => { source: { image: { reference: "blaxel/base-image:latest" } }, lifetime: { idleTerminate: { mode: PolicyMode.Disabled } }, }, - { scope: { workspace: "ws" } }, + provider, ), ).toThrowError( expect.objectContaining({ @@ -255,7 +292,7 @@ describe("Blaxel mappings", () => { source: { image: { reference: "blaxel/base-image:latest" } }, environment: { "NOT VALID": "value" }, }, - { scope: { workspace: "ws" } }, + provider, ), ).toThrowError( expect.objectContaining({ @@ -264,6 +301,46 @@ describe("Blaxel mappings", () => { }), ); }); + + it("maps DEPLOYED plus runtime STANDBY to paused", () => { + expect( + mapSandboxIdentity({ + metadata: { name: "demo" }, + status: "DEPLOYED", + state: "STANDBY", + spec: { region: "us-pdx-1" }, + }), + ).toMatchObject({ + id: "demo", + providerState: "DEPLOYED", + state: SandboxState.Paused, + region: "us-pdx-1", + }); + }); + + it("maps DEPLOYED plus runtime RUNNING to running", () => { + expect( + mapSandboxIdentity({ + metadata: { name: "demo" }, + status: "DEPLOYED", + state: "RUNNING", + }), + ).toMatchObject({ + providerState: "DEPLOYED", + state: SandboxState.Running, + }); + }); + + it("reads runtime state from SandboxInstance wire model", () => { + expect( + mapSandboxIdentity({ + metadata: { name: "demo" }, + status: "DEPLOYED", + // SandboxInstance exposes runtime state only on its private wire model. + ...({ sandbox: { state: "STANDBY" } } as object), + }).state, + ).toBe(SandboxState.Paused); + }); }); describe("Blaxel provider orchestration", () => { diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index 039902be..20b6cfbd 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -61,6 +61,7 @@ interface NativeSandboxHandle { readonly labels?: Readonly>; }; readonly status?: unknown; + readonly state?: unknown; readonly spec?: { readonly region?: string }; readonly fs: { readBinary(path: string): Promise; diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index 1d4f81aa..c101b9c8 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -67,6 +67,8 @@ export function mapCreateRequest( invalid(CreateFieldName, "Blaxel requires an explicit sandbox name"); if (request.source?.image === undefined) unsupported(CreateFieldSource, "Blaxel requires an explicit image source"); + if (config.region === undefined || config.region.trim() === "") + invalid(ConfigFieldRegion, "Config.region is required"); const params: SandboxCreateConfiguration = generatedMapCommonCreateFields( request, @@ -76,9 +78,7 @@ export function mapCreateRequest( request.source.image.reference, CreateFieldSourceImageReference, ); - - if (config.region !== undefined) - params.region = nonblankConfig(config.region, ConfigFieldRegion); + params.region = nonblank(config.region, ConfigFieldRegion); mapRuntime(request); mapIsolation(request); @@ -89,12 +89,9 @@ export function mapCreateRequest( mapLifetime(request, params); mapReadiness(request); mapObservability(request); - const options = mapProvisioning(request); + mapProvisioning(request); - return { - params, - ...(options === undefined ? {} : { options }), - }; + return { params }; } function mapRuntime(r: CreateOptions): void { @@ -268,36 +265,34 @@ function mapObservability(r: CreateOptions): void { ); } -// mapProvisioning maps create timeout only. WaitCondition.Ready is rejected: -// @blaxel/core sandbox.wait() is a deprecated no-op, and Ready previously -// conflated that with Blaxel's destructive creation deadline (1–50s). -function mapProvisioning(r: CreateOptions): SandboxCreateOptions | undefined { +// mapProvisioning rejects create timeout until both languages share a native +// mapping. WaitCondition.Ready is rejected: @blaxel/core sandbox.wait() is a +// deprecated no-op. +function mapProvisioning(r: CreateOptions): void { const v = r.provisioning; - if (!v) return undefined; + if (!v) return; if (v.queueTimeout !== undefined) unsupported( CreateFieldProvisioningQueueTimeout, "Blaxel queue timeout is unsupported", ); - let options: SandboxCreateOptions | undefined; if (v.timeout !== undefined) { const seconds = wholeSeconds( v.timeout, "create", CreateFieldProvisioningTimeout, ); - if (seconds === undefined || seconds < 1 || seconds > 50) - invalid( + if (seconds !== undefined && seconds > 0) + unsupported( CreateFieldProvisioningTimeout, - "create timeout must be a whole number of seconds between 1 and 50", + "Blaxel create timeout is not mapped", ); - options = { timeout: seconds }; } const waitFor = v.waitFor ?? WaitCondition.Default; if (waitFor === WaitCondition.Default || waitFor === WaitCondition.Scheduled) - return options; + return; unsupported( CreateFieldProvisioningWaitFor, "requested wait condition is unsupported", @@ -337,11 +332,6 @@ function nonblank(v: string, f: string): string { return v; } -function nonblankConfig(v: string, f: string): string { - if (!v.trim()) configInvalid(f, "must not be blank"); - return v; -} - /** Converts Kit millisecond durations into Blaxel duration strings (s/m/h/d). */ function durationString(ms: number, field: string, minimumMs: number): string { if (!Number.isFinite(ms) || ms <= 0 || ms % 1_000 !== 0) @@ -388,12 +378,3 @@ function unsupported(field: string, message: string): never { message, }); } - -function configInvalid(field: string, message: string): never { - providerError({ - kind: ErrorKind.InvalidArgument, - operation: "initialize", - field, - message, - }); -} diff --git a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts index 66a102c1..4e86f5f5 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts @@ -27,7 +27,10 @@ export interface BlaxelSandboxIdentity { readonly name?: string; readonly labels?: Readonly>; }; + /** Deployment status (DEPLOYED, FAILED, …). */ readonly status?: unknown; + /** Runtime state (RUNNING / STANDBY). */ + readonly state?: unknown; readonly spec?: { readonly region?: string }; } @@ -42,11 +45,14 @@ export function mapSandboxIdentity( target.id = name; target.name = name; } - if (source.status !== undefined) { + const runtime = runtimeState(source); + if (source.status !== undefined && String(source.status) !== "") { target.providerState = String(source.status); - const state = mapSandboxState(String(source.status)); - if (state !== undefined) target.state = state; + } else if (runtime !== undefined) { + target.providerState = runtime; } + const state = mapSandboxState(source.status, runtime); + if (state !== undefined) target.state = state; const region = source.spec?.region; if (region !== undefined && region !== "") target.region = region; const labels = source.metadata?.labels; @@ -112,32 +118,59 @@ export function mapAPIKeyCredentials( return target; } -function mapSandboxState(status: string): SandboxState | undefined { - const normalized = status.toUpperCase(); +function runtimeState(source: BlaxelSandboxIdentity): string | undefined { + if (source.state !== undefined && String(source.state) !== "") + return String(source.state); + // SandboxInstance keeps the wire model on a private field; read it when present. + const nested = (source as { sandbox?: { state?: unknown } }).sandbox?.state; + if (nested !== undefined && String(nested) !== "") return String(nested); + return undefined; +} + +/** Mirrors Go metadata.go: deployment status, then runtime standby. */ +function mapSandboxState( + status: unknown, + runtime: string | undefined, +): SandboxState | undefined { + const normalized = + status === undefined || String(status) === "" + ? "" + : String(status).toUpperCase(); + const runtimeNormalized = runtime?.toUpperCase(); switch (normalized) { - case "RUNNING": - case "READY": - case "DEPLOYED": - return SandboxState.Running; - case "PENDING": - case "CREATING": + case "UPLOADING": + case "BUILDING": case "DEPLOYING": - case "STARTING": - return SandboxState.Starting; - case "STANDBY": - case "SUSPENDED": - case "SLEEPING": - return SandboxState.Paused; + case "BUILT": + return SandboxState.Queued; + case "DEPLOYED": + return runtimeNormalized === "STANDBY" + ? SandboxState.Paused + : SandboxState.Running; + case "DELETING": + case "DEACTIVATING": + return SandboxState.Deleting; + case "TERMINATED": + case "DEACTIVATED": + case "DELETED": + return SandboxState.Deleted; + case "ARCHIVING": + return SandboxState.Archiving; case "ARCHIVED": return SandboxState.Archived; + case "UNARCHIVING": + return SandboxState.Starting; case "FAILED": return SandboxState.Error; - case "TERMINATED": - case "DELETED": - case "DEACTIVATED": - return SandboxState.Deleted; default: - return undefined; + switch (runtimeNormalized) { + case "RUNNING": + return SandboxState.Running; + case "STANDBY": + return SandboxState.Paused; + default: + return undefined; + } } } diff --git a/sdks/typescript/providers/blaxel/src/rejection.test.ts b/sdks/typescript/providers/blaxel/src/rejection.test.ts index b19e22ed..1df46f3c 100644 --- a/sdks/typescript/providers/blaxel/src/rejection.test.ts +++ b/sdks/typescript/providers/blaxel/src/rejection.test.ts @@ -1,6 +1,7 @@ import { ConfigFieldAuth, ConfigFieldEndpoint, + ConfigFieldRegion, ConfigFieldScopeAppName, ConfigFieldScopeOrganizationId, CreateFieldIsolation, @@ -10,6 +11,7 @@ import { CreateFieldObservability, CreateFieldPlacement, CreateFieldProvisioningQueueTimeout, + CreateFieldProvisioningTimeout, CreateFieldReadiness, CreateFieldResources, CreateFieldRuntime, @@ -24,7 +26,7 @@ import { describe, expect, it } from "vitest"; import { mapClientConfig, mapCreateRequest } from "#mappings/mappings.js"; -const workspace = { scope: { workspace: "ws" } }; +const provider = { scope: { workspace: "ws" }, region: "us-pdx-1" }; describe("rejected Blaxel create settings", () => { const cases: { @@ -57,6 +59,12 @@ describe("rejected Blaxel create settings", () => { kind: ErrorKind.Unsupported, field: CreateFieldProvisioningQueueTimeout, }, + { + name: "create timeout", + request: { provisioning: { timeout: 30_000 } }, + kind: ErrorKind.Unsupported, + field: CreateFieldProvisioningTimeout, + }, { name: "readiness", request: { readiness: { tcpPort: 8080 } }, @@ -107,7 +115,7 @@ describe("rejected Blaxel create settings", () => { expect(() => mapCreateRequest( { source: { image: { reference: "blaxel/base-image:latest" } } }, - workspace, + provider, ), ).toThrowError( expect.objectContaining({ @@ -118,7 +126,7 @@ describe("rejected Blaxel create settings", () => { }); it("missing image", () => { - expect(() => mapCreateRequest({ name: "box" }, workspace)).toThrowError( + expect(() => mapCreateRequest({ name: "box" }, provider)).toThrowError( expect.objectContaining({ kind: ErrorKind.Unsupported, field: CreateFieldSource, @@ -126,10 +134,21 @@ describe("rejected Blaxel create settings", () => { ); }); + it("missing region", () => { + expect(() => + mapCreateRequest(namedImage({}), { scope: { workspace: "ws" } }), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: ConfigFieldRegion, + }), + ); + }); + for (const item of cases) { it(item.name, () => { expect(() => - mapCreateRequest(namedImage(item.request), workspace), + mapCreateRequest(namedImage(item.request), provider), ).toThrowError( expect.objectContaining({ kind: item.kind, field: item.field }), ); diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml index 99276338..67d94f49 100644 --- a/specs/providers/blaxel.yaml +++ b/specs/providers/blaxel.yaml @@ -49,7 +49,7 @@ runtime: type: string, optional: true, capture: region, - doc: "is the captured default region. Omission leaves create without a region unless the environment supplies one.", + doc: "is the captured default region. Omission means create rejects with Config.region required.", } cleanup: { method: close, accepts_context: false, returns_error: false } From c5b68b9a663921e9f6a37c75eccca0a548a447cf Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 15:28:03 +0530 Subject: [PATCH 08/60] Harden Blaxel create validation and timeout handling. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Accept Kit create timeout as the client deadline (Modal pattern), align Go/TS rejection paths, and document memory MiB→MB pass-through. Co-authored-by: Cursor Entire-Checkpoint: 01M72E9BJ8TVSP0DN6KT4ENVXT --- docs/configuration.md | 3 +- docs/sandbox-creation.md | 9 +- sdks/go/providers/blaxel/README.md | 11 ++- sdks/go/providers/blaxel/creation.go | 29 +++--- sdks/go/providers/blaxel/errors.go | 44 ++++++--- sdks/go/providers/blaxel/provider_checks.go | 9 ++ sdks/go/providers/blaxel/rejection_test.go | 37 ++++++- sdks/typescript/providers/blaxel/README.md | 14 +-- .../providers/blaxel/src/blaxel.test.ts | 16 ++- .../blaxel/src/checks/provider_checks.ts | 79 +++++++++++++++ .../providers/blaxel/src/mappings/mappings.ts | 97 ++++++------------- .../blaxel/src/mappings/provider_mappings.ts | 2 +- .../providers/blaxel/src/rejection.test.ts | 44 ++++++--- specs/providers/blaxel.yaml | 4 + website/content/docs/blaxel.mdx | 4 + 15 files changed, 265 insertions(+), 137 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 494c8293..64466ba0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -87,7 +87,8 @@ deadline or abort signal that arrives earlier still applies. Daytona create keeps its 60-second default when the timeout is omitted and when it is explicit zero. Daytona start, stop, pause, and delete use that 60-second default when the timeout is omitted, and treat explicit zero as no -operation timeout. +operation timeout. Blaxel uses the Kit deadline only; it has no separate +native create-wait timeout. ## Credentials diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index 155eb9aa..77c494ee 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -26,10 +26,11 @@ Blaxel (`modal@0.11.1`, `@daytona/sdk@0.225.0`, `@blaxel/core@0.3.36` / Blaxel create requires a nonblank name, an image source, and a client -`region`. It maps memory MiB, labels, environment, ports, outbound domains, -maximum lifetime, and idle terminate. CPU/disk overrides, placement, runtime, -isolation, security, provisioning create timeout, and most egress modes are -rejected. +`region`. It maps `memory_mib` (passed through as Blaxel megabytes with no +conversion), labels, environment, ports, outbound domains, maximum lifetime, +and idle terminate. CPU/disk overrides, placement, non-default isolation, +runtime overrides, security, readiness, queue timeout, Ready wait, and most +egress modes are rejected. Kit client/create timeout is the create deadline. Modal default, snapshot, and warm-pool sources are unavailable. Daytona warm pools and explicit snapshot kinds are unavailable. A field the provider does diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index ec73f1e2..883f565c 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -54,6 +54,7 @@ sandbox. Call `Delete`, or set a lifetime policy. | API key | `Auth` with `APIKey` | | Workspace | `Scope.Workspace` | | Region | `Config.Region` (required for create) | +| Timeout | `Config.Timeout` (create/op deadline) | `Config.Endpoint` is not supported. Token pair, OAuth, and bearer auth are rejected. `Scope` fields other than `Workspace` are rejected. @@ -63,8 +64,9 @@ rejected. `Scope` fields other than `Workspace` are rejected. Create from a registry image with an explicit name. You can set labels, environment variables, memory, ports, outbound domains, maximum lifetime, and idle terminate. Max-age TTL must be at least 5m; idle TTL must be at least 1m. -Ports 80, 443, and 8080 are rejected (Blaxel-reserved). Create provisioning -timeout is not mapped. +Ports 80, 443, and 8080 are rejected (Blaxel-reserved). Kit +`Config.Timeout` / create call timeout is the create deadline; Blaxel has no +separate native create-wait timeout. `WaitConditionReady` is rejected. On the handle: `Get`, `Refresh`, `Delete`, and `Close`. `Get` and `Refresh` look up the sandbox by name. `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and @@ -85,8 +87,9 @@ exit code is success for the SDK—read `GetExitCode`. Blaxel returns stdout and stderr. Omit or zero `Timeout` for no Blaxel command limit. Command timeouts must be whole seconds. Cancel the context to stop waiting. -Memory is allocated in MiB. Blaxel derives CPU from memory, so `cpu_cores` and -disk MiB overrides are rejected. +`resources.memory_mib` is passed through as Blaxel's memory integer (SDK unit: +megabytes) with no conversion. Blaxel derives CPU from that value, so +`cpu_cores` and disk overrides are rejected. ## License diff --git a/sdks/go/providers/blaxel/creation.go b/sdks/go/providers/blaxel/creation.go index 29c04ec7..b608c092 100644 --- a/sdks/go/providers/blaxel/creation.go +++ b/sdks/go/providers/blaxel/creation.go @@ -47,6 +47,7 @@ func planCreate(request *sandbox.CreateOptions, region string) (createPlan, erro planned.security, planned.network, planned.provisioning, + planned.readiness, planned.observability, } { if err := apply(); err != nil { @@ -256,21 +257,18 @@ func (c *creation) provisioning() error { return nil } config := cloneMessage(c.request.Provisioning) - if config.Timeout != nil { - timeout := config.Timeout.AsDuration() - if timeout < 0 || timeout%time.Second != 0 { - return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldProvisioningTimeout, "timeout must be a whole number of seconds") - } - // Blaxel create does not accept a create-wait timeout on NewInstance. - if timeout > 0 { - return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldProvisioningTimeout, "Blaxel create timeout is not mapped") - } - } + // Client.Create already applies this as the context deadline. Blaxel has no + // separate native create-wait timeout on NewInstance. config.Timeout = nil - if config.QueueTimeout != nil || config.WaitFor != sandbox.WaitConditionDefault { - return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldProvisioning, "Blaxel provisioning wait settings are not mapped") + if config.QueueTimeout != nil { + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldProvisioningQueueTimeout, "Blaxel queue timeout is unsupported") } config.QueueTimeout = nil + switch config.WaitFor { + case sandbox.WaitConditionDefault, sandbox.WaitConditionScheduled: + default: + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldProvisioningWaitFor, "requested wait condition is unsupported") + } config.WaitFor = 0 if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldProvisioning, config); err != nil { return err @@ -279,6 +277,13 @@ func (c *creation) provisioning() error { return nil } +func (c *creation) readiness() error { + if c.request.Readiness == nil { + return nil + } + return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldReadiness, "Blaxel readiness probes are not mapped") +} + func (c *creation) observability() error { if c.request.Observability == nil { return nil diff --git a/sdks/go/providers/blaxel/errors.go b/sdks/go/providers/blaxel/errors.go index 2504f528..a4e4d3d5 100644 --- a/sdks/go/providers/blaxel/errors.go +++ b/sdks/go/providers/blaxel/errors.go @@ -40,43 +40,55 @@ func mapProviderError(err error, operation string) error { } if kind == sandbox.ErrorKindUnknown { switch native.ErrorCode { - case sdkblaxel.ErrAuthenticationRequired, sdkblaxel.ErrAuthenticationFailed: + case sdkblaxel.ErrAuthenticationRequired, sdkblaxel.ErrAuthenticationFailed, "TOKEN_REVOKED": kind = sandbox.ErrorKindAuthentication - case sdkblaxel.ErrForbidden: + case sdkblaxel.ErrForbidden, "UNAUTHORIZED", "UNAUTHORIZED_WORKSPACE": kind = sandbox.ErrorKindPermissionDenied - case sdkblaxel.ErrRouteNotFound, sdkblaxel.ErrWorkloadNotFound, sdkblaxel.ErrWorkspaceNotFound: + case sdkblaxel.ErrRouteNotFound, sdkblaxel.ErrWorkloadNotFound, sdkblaxel.ErrWorkspaceNotFound, + "IMAGE_NOT_FOUND", "VOLUME_NOT_FOUND", "ACCOUNT_NOT_FOUND", "SANDBOX_DELETED", "VOLUME_DELETED": kind = sandbox.ErrorKindNotFound - case sdkblaxel.ErrWorkloadUnavailable: - kind = sandbox.ErrorKindUnavailable - case sdkblaxel.ErrUsageLimitExceeded: - kind = sandbox.ErrorKindResourceExhausted - case sdkblaxel.ErrBadRequest, sdkblaxel.ErrPolicyViolation: - kind = sandbox.ErrorKindInvalidArgument case "SANDBOX_ALREADY_EXISTS", "VOLUME_ALREADY_ATTACHED": - // Control-plane codes (not in gateway apierror constants). + // Control-plane codes (not all are gateway apierror constants). kind = sandbox.ErrorKindAlreadyExists + case sdkblaxel.ErrBadRequest, sdkblaxel.ErrPolicyViolation, + "INVALID_INPUT", "INVALID_IMAGE", "INVALID_PORTS", "INVALID_REGION", + "INVALID_VOLUME", "INVALID_VOLUME_COUNT", "INVALID_VOLUME_REGION", + "VALIDATION_FAILED", "VALIDATION_ERROR", "WORKSPACE_REQUIRED": + kind = sandbox.ErrorKindInvalidArgument + case "CREATION_TIMEOUT", "UPSTREAM_CONNECT_TIMEOUT", "UPSTREAM_TIMEOUT": + kind = sandbox.ErrorKindTimeout + case sdkblaxel.ErrUsageLimitExceeded, "QUOTA_EXCEEDED": + kind = sandbox.ErrorKindResourceExhausted + case "RATE_LIMIT_EXCEEDED", "WORKSPACE_RATE_LIMIT_EXCEEDED": + kind = sandbox.ErrorKindRateLimited + case "SANDBOX_DELETION_IN_PROGRESS": + kind = sandbox.ErrorKindConflict + case sdkblaxel.ErrWorkloadUnavailable, "ROUTING_UNAVAILABLE", "SERVICE_UNAVAILABLE", + "AUTHENTICATION_UNAVAILABLE", "UPSTREAM_CONNECT_FAILED", "UPSTREAM_ERROR", + "GATEWAY_INTERNAL_ERROR": + kind = sandbox.ErrorKindUnavailable + case "WORKLOAD_FAILED", "INTERNAL_ERROR", "HANDLER_ERROR", "DATABASE_ERROR": + kind = sandbox.ErrorKindInternal } } if kind == sandbox.ErrorKindUnknown { switch native.StatusCode { - case 400: + case 400, 422: kind = sandbox.ErrorKindInvalidArgument case 401: kind = sandbox.ErrorKindAuthentication + case 402: + kind = sandbox.ErrorKindResourceExhausted case 403: kind = sandbox.ErrorKindPermissionDenied - case 404: + case 404, 410: kind = sandbox.ErrorKindNotFound case 408: kind = sandbox.ErrorKindTimeout case 409: kind = sandbox.ErrorKindConflict - case 410: - kind = sandbox.ErrorKindNotFound case 412: kind = sandbox.ErrorKindFailedPrecondition - case 422: - kind = sandbox.ErrorKindInvalidArgument case 429: kind = sandbox.ErrorKindRateLimited case 500: diff --git a/sdks/go/providers/blaxel/provider_checks.go b/sdks/go/providers/blaxel/provider_checks.go index 5c52e0ac..e935277a 100644 --- a/sdks/go/providers/blaxel/provider_checks.go +++ b/sdks/go/providers/blaxel/provider_checks.go @@ -1,9 +1,13 @@ package blaxel import ( + "regexp" + "github.com/sandbox-kit/kit/sdks/go/sandbox" ) +var providerEnvNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) + // validateProviderCreate rejects creation settings this provider cannot honor. // It runs before the native create call. // @@ -39,6 +43,11 @@ func validateProviderCreate(request *sandbox.CreateOptions) error { return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResourcesDiskMib, "Blaxel disk size is not mapped as MiB") } } + for name := range request.Environment { + if !providerEnvNamePattern.MatchString(name) { + return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldEnvironment, "environment is outside supported values") + } + } return nil } diff --git a/sdks/go/providers/blaxel/rejection_test.go b/sdks/go/providers/blaxel/rejection_test.go index 7e240e15..4d3c594e 100644 --- a/sdks/go/providers/blaxel/rejection_test.go +++ b/sdks/go/providers/blaxel/rejection_test.go @@ -204,16 +204,43 @@ func TestMissingRegionRejected(t *testing.T) { } } -func TestCreateTimeoutRejected(t *testing.T) { - _, err := planCreate(namedImage(&sandbox.CreateOptions{ +func TestCreateTimeoutAcceptedAsKitDeadline(t *testing.T) { + // Shared Client.Create injects Config.Timeout into provisioning.timeout and + // applies it as the context deadline. Blaxel must not reject that field. + if _, err := planCreate(namedImage(&sandbox.CreateOptions{ Provisioning: &sandbox.ProvisioningOptions{Timeout: sandbox.Duration(30 * time.Second)}, + }), "us-pdx-1"); err != nil { + t.Fatal(err) + } +} + +func TestReadinessRejected(t *testing.T) { + _, err := planCreate(namedImage(&sandbox.CreateOptions{ + Readiness: &sandbox.ReadinessProbe{Check: &sandbox.TCPPort{TcpPort: 8080}}, }), "us-pdx-1") var detail *sandbox.Error - if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindUnsupported || detail.Field != sandbox.CreateFieldProvisioningTimeout { + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindUnsupported || detail.Field != sandbox.CreateFieldReadiness { t.Fatalf("error = %v detail = %#v", err, detail) } - if detail.Message != "sandbox-kit blaxel: Blaxel create timeout is not mapped" { - t.Fatalf("message = %q", detail.Message) +} + +func TestWaitForReadyRejected(t *testing.T) { + _, err := planCreate(namedImage(&sandbox.CreateOptions{ + Provisioning: &sandbox.ProvisioningOptions{WaitFor: sandbox.WaitConditionReady}, + }), "us-pdx-1") + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindUnsupported || detail.Field != sandbox.CreateFieldProvisioningWaitFor { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} + +func TestInvalidEnvironmentNameRejected(t *testing.T) { + _, err := planCreate(namedImage(&sandbox.CreateOptions{ + Environment: map[string]string{"NOT VALID": "value"}, + }), "us-pdx-1") + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidArgument || detail.Field != sandbox.CreateFieldEnvironment { + t.Fatalf("error = %v detail = %#v", err, detail) } } diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index 4a7b2b7b..df419175 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -53,12 +53,14 @@ than `workspace` are rejected. ## What you can do **Create** requires a nonblank `name`, an image source, and client `region`. -Optional memory (MiB; Blaxel CPU follows memory), labels, environment -variables, ports, outbound domains, max-age TTL (at least 5m), and idle-delete -policy (at least 1m; explicit disable is rejected). -`provisioning.timeout` and `provisioning.waitFor` Ready are unsupported. -Creation calls native `SandboxInstance.create`, not `createIfNotExists`. -Ports 80, 443, and 8080 are rejected (Blaxel-reserved). +Optional memory (`memoryMib` number is sent as Blaxel megabytes with no +conversion; Blaxel CPU follows memory), labels, environment variables, ports, +outbound domains, max-age TTL (at least 5m), and idle-delete policy (at least +1m; explicit disable is rejected). Client / create `timeout` is the Kit +deadline; Blaxel has no separate native create-wait timeout. +`provisioning.waitFor` Ready is unsupported. Creation calls native +`SandboxInstance.create`, not `createIfNotExists`. Ports 80, 443, and 8080 are +rejected (Blaxel-reserved). **Lifecycle** on the handle: `get` / `refresh` (lookup by name), `delete`, and `close`. `close` does not delete the sandbox. Start, stop, pause, archive, diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 151ad40e..6fe5eab7 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -16,7 +16,6 @@ import { CreateFieldLifetimeMaximumLifetime, CreateFieldName, CreateFieldNetworkPortsPort, - CreateFieldProvisioningTimeout, CreateFieldProvisioningWaitFor, FileFieldLocalPath, } from "@sandbox-kit/sandbox"; @@ -178,7 +177,7 @@ describe("Blaxel mappings", () => { expect(mapResources({ memoryMib: 4096n })).toEqual({ memory: 4096 }); }); - it("rejects create timeout", () => { + it("accepts kit create timeout as the client deadline", () => { expect(() => mapCreateRequest( { @@ -188,12 +187,7 @@ describe("Blaxel mappings", () => { }, provider, ), - ).toThrowError( - expect.objectContaining({ - kind: ErrorKind.Unsupported, - field: CreateFieldProvisioningTimeout, - }), - ); + ).not.toThrow(); }); it("rejects WaitCondition.Ready", () => { @@ -351,7 +345,8 @@ describe("Blaxel provider orchestration", () => { let configured: unknown; const nativeSandbox = { metadata: { name: "example" }, - status: "RUNNING", + status: "DEPLOYED", + state: "RUNNING", spec: { region: "us-pdx-1" }, fs: {} as never, process: {} as never, @@ -411,7 +406,8 @@ describe("Blaxel provider orchestration", () => { lookedUp = name; return { metadata: { name }, - status: "RUNNING", + status: "DEPLOYED", + state: "RUNNING", fs: {} as never, process: {} as never, async wait() { diff --git a/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts b/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts index b4aa2d5e..e470357c 100644 --- a/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts +++ b/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts @@ -1,6 +1,14 @@ import { ErrorKind, CreateFieldEnvironment, + CreateFieldName, + CreateFieldPlacement, + CreateFieldResourcesCpuCores, + CreateFieldResourcesCpuLimitCores, + CreateFieldResourcesDiskMib, + CreateFieldResourcesMemoryLimitMib, + CreateFieldSource, + CreateFieldSourceImage, validEnvName, } from "@sandbox-kit/sandbox"; import type { CreateOptions } from "@sandbox-kit/sandbox"; @@ -9,6 +17,77 @@ import { providerError } from "#errors.js"; // validateProviderCreate rejects creation settings this provider cannot honor. // Rules are inventoried in specs/providers/blaxel.yaml checks when present. export function validateProviderCreate(request: CreateOptions): void { + if (request.name === undefined || request.name.trim() === "") { + providerError({ + kind: ErrorKind.InvalidArgument, + operation: "create", + field: CreateFieldName, + message: "name is required", + }); + } + if (request.source?.image === undefined) { + providerError({ + kind: ErrorKind.InvalidArgument, + operation: "create", + field: CreateFieldSourceImage, + message: "an explicit image source is required", + }); + } + if ( + request.source?.snapshot !== undefined || + request.source?.warmPool !== undefined + ) { + providerError({ + kind: ErrorKind.Unsupported, + operation: "create", + field: CreateFieldSource, + message: "only an image source is supported", + }); + } + if (request.placement !== undefined) { + providerError({ + kind: ErrorKind.Unsupported, + operation: "create", + field: CreateFieldPlacement, + message: + "per-request Blaxel placement is unsupported; use Config.region", + }); + } + const resources = request.resources; + if (resources !== undefined) { + if (resources.cpuCores !== undefined) { + providerError({ + kind: ErrorKind.Unsupported, + operation: "create", + field: CreateFieldResourcesCpuCores, + message: "Blaxel derives CPU from memory; cpu_cores is not mapped", + }); + } + if (resources.cpuLimitCores !== undefined) { + providerError({ + kind: ErrorKind.Unsupported, + operation: "create", + field: CreateFieldResourcesCpuLimitCores, + message: "Blaxel resource limits are not mapped", + }); + } + if (resources.memoryLimitMib !== undefined) { + providerError({ + kind: ErrorKind.Unsupported, + operation: "create", + field: CreateFieldResourcesMemoryLimitMib, + message: "Blaxel resource limits are not mapped", + }); + } + if (resources.diskMib !== undefined) { + providerError({ + kind: ErrorKind.Unsupported, + operation: "create", + field: CreateFieldResourcesDiskMib, + message: "Blaxel disk size is not mapped as MiB", + }); + } + } for (const value of Object.keys(request.environment ?? {})) { if (!validEnvName(value)) { providerError({ diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index c101b9c8..c5dab20b 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -8,6 +8,7 @@ import type { } from "@blaxel/core"; import { ErrorKind, + IsolationKind, PolicyMode, PortTransport, SandboxState, @@ -27,15 +28,12 @@ import { CreateFieldNetworkPortsPort, CreateFieldNetworkPortsTransport, CreateFieldObservability, - CreateFieldPlacement, CreateFieldProvisioningQueueTimeout, - CreateFieldProvisioningTimeout, CreateFieldProvisioningWaitFor, CreateFieldReadiness, - CreateFieldResources, CreateFieldRuntime, + CreateFieldRuntimeWorkingDirectory, CreateFieldSecurity, - CreateFieldSource, CreateFieldSourceImageReference, } from "@sandbox-kit/sandbox"; import { @@ -63,19 +61,15 @@ export function mapCreateRequest( config: ProviderState, ): BlaxelCreatePlan { validateProviderCreate(request); - if (request.name === undefined) - invalid(CreateFieldName, "Blaxel requires an explicit sandbox name"); - if (request.source?.image === undefined) - unsupported(CreateFieldSource, "Blaxel requires an explicit image source"); if (config.region === undefined || config.region.trim() === "") invalid(ConfigFieldRegion, "Config.region is required"); const params: SandboxCreateConfiguration = generatedMapCommonCreateFields( request, ); - params.name = nonblank(request.name, CreateFieldName); + params.name = nonblank(request.name!, CreateFieldName); params.image = nonblank( - request.source.image.reference, + request.source!.image!.reference, CreateFieldSourceImageReference, ); params.region = nonblank(config.region, ConfigFieldRegion); @@ -83,7 +77,6 @@ export function mapCreateRequest( mapRuntime(request); mapIsolation(request); mapResources(request, params); - mapPlacement(request); mapNetwork(request, params); mapSecurity(request); mapLifetime(request, params); @@ -95,16 +88,35 @@ export function mapCreateRequest( } function mapRuntime(r: CreateOptions): void { - if (r.runtime !== undefined) + const v = r.runtime; + if (!v) return; + if (v.workingDirectory !== undefined) + unsupported( + CreateFieldRuntimeWorkingDirectory, + "Blaxel create-time working directory is not mapped", + ); + if ( + v.entrypoint !== undefined || + v.pty !== undefined || + v.user !== undefined || + v.language !== undefined || + v.version !== undefined + ) unsupported( CreateFieldRuntime, - "runtime settings must come from the Blaxel image", + "runtime user, language, version, entrypoint, and PTY are not mapped by Blaxel", ); } function mapIsolation(r: CreateOptions): void { - if (r.isolation !== undefined) - unsupported(CreateFieldIsolation, "Blaxel isolation is not configurable"); + const v = r.isolation; + if (!v) return; + if ( + (v.kind !== undefined && v.kind !== IsolationKind.Default) || + v.providerKind !== undefined || + v.nestedVirtualization !== undefined + ) + unsupported(CreateFieldIsolation, "Blaxel isolation class is not mapped"); } function mapResources( @@ -113,27 +125,9 @@ function mapResources( ): void { const v = r.resources; if (!v) return; - if ( - v.cpuCores !== undefined || - v.cpuLimitCores !== undefined || - v.memoryLimitMib !== undefined || - v.diskMib !== undefined - ) - unsupported( - CreateFieldResources, - "Blaxel only maps resources.memory_mib; CPU follows memory", - ); Object.assign(p, generatedMapResources(v)); } -function mapPlacement(r: CreateOptions): void { - if (r.placement !== undefined) - unsupported( - CreateFieldPlacement, - "set client region instead of create placement", - ); -} - function mapNetwork(r: CreateOptions, p: SandboxCreateConfiguration): void { const v = r.network; if (!v) return; @@ -190,6 +184,7 @@ function blaxelPortProtocol( ) return "TCP"; if (transport === PortTransport.Tls) return "TLS"; + // Blaxel exposes HTTP (not H2). Kit HTTP/2+TLS maps to that HTTP protocol. if (transport === PortTransport.Http2Tls) return "HTTP"; invalid( CreateFieldNetworkPortsTransport, @@ -265,9 +260,8 @@ function mapObservability(r: CreateOptions): void { ); } -// mapProvisioning rejects create timeout until both languages share a native -// mapping. WaitCondition.Ready is rejected: @blaxel/core sandbox.wait() is a -// deprecated no-op. +// Kit Client.create applies provisioning.timeout as the abort deadline. +// Blaxel has no separate native create-wait timeout beyond that. function mapProvisioning(r: CreateOptions): void { const v = r.provisioning; if (!v) return; @@ -277,19 +271,6 @@ function mapProvisioning(r: CreateOptions): void { "Blaxel queue timeout is unsupported", ); - if (v.timeout !== undefined) { - const seconds = wholeSeconds( - v.timeout, - "create", - CreateFieldProvisioningTimeout, - ); - if (seconds !== undefined && seconds > 0) - unsupported( - CreateFieldProvisioningTimeout, - "Blaxel create timeout is not mapped", - ); - } - const waitFor = v.waitFor ?? WaitCondition.Default; if (waitFor === WaitCondition.Default || waitFor === WaitCondition.Scheduled) return; @@ -308,7 +289,7 @@ export function mapSandboxInfo( const result: SandboxInfo = { id, provider: "blaxel", - state: mapped.state ?? SandboxState.Running, + state: mapped.state ?? SandboxState.Unknown, labels: mapped.labels ?? {}, }; if (mapped.name !== undefined) mutable(result).name = mapped.name; @@ -345,22 +326,6 @@ function durationString(ms: number, field: string, minimumMs: number): string { return `${seconds}s`; } -function wholeSeconds( - value: number | undefined, - operation: string, - field: string, -): number | undefined { - if (value === undefined) return undefined; - if (!Number.isFinite(value) || value < 0 || value % 1_000 !== 0) - providerError({ - kind: ErrorKind.InvalidArgument, - operation, - field, - message: "timeout must be a whole number of seconds", - }); - return value / 1_000; -} - function invalid(field: string, message: string): never { providerError({ kind: ErrorKind.InvalidArgument, diff --git a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts index 4e86f5f5..5c545374 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts @@ -96,7 +96,7 @@ export function mapResources( if (source === undefined) return target; if (source.memoryMib !== undefined) { const value = source.memoryMib; - if (value > BigInt(Number.MAX_SAFE_INTEGER)) + if (value === 0n || value > BigInt(Number.MAX_SAFE_INTEGER)) mappingError({ kind: ErrorKind.InvalidArgument, field: CreateFieldResourcesMemoryMib, diff --git a/sdks/typescript/providers/blaxel/src/rejection.test.ts b/sdks/typescript/providers/blaxel/src/rejection.test.ts index 1df46f3c..c8db0aee 100644 --- a/sdks/typescript/providers/blaxel/src/rejection.test.ts +++ b/sdks/typescript/providers/blaxel/src/rejection.test.ts @@ -11,14 +11,16 @@ import { CreateFieldObservability, CreateFieldPlacement, CreateFieldProvisioningQueueTimeout, - CreateFieldProvisioningTimeout, + CreateFieldProvisioningWaitFor, CreateFieldReadiness, - CreateFieldResources, + CreateFieldResourcesCpuCores, + CreateFieldResourcesDiskMib, CreateFieldRuntime, CreateFieldSecurity, - CreateFieldSource, + CreateFieldSourceImage, ErrorKind, IsolationKind, + WaitCondition, type Config, type CreateOptions, } from "@sandbox-kit/sandbox"; @@ -39,13 +41,13 @@ describe("rejected Blaxel create settings", () => { name: "cpu resources", request: { resources: { cpuCores: 1 } }, kind: ErrorKind.Unsupported, - field: CreateFieldResources, + field: CreateFieldResourcesCpuCores, }, { name: "disk", request: { resources: { diskMib: 1024n } }, kind: ErrorKind.Unsupported, - field: CreateFieldResources, + field: CreateFieldResourcesDiskMib, }, { name: "placement", @@ -60,10 +62,10 @@ describe("rejected Blaxel create settings", () => { field: CreateFieldProvisioningQueueTimeout, }, { - name: "create timeout", - request: { provisioning: { timeout: 30_000 } }, + name: "wait for ready", + request: { provisioning: { waitFor: WaitCondition.Ready } }, kind: ErrorKind.Unsupported, - field: CreateFieldProvisioningTimeout, + field: CreateFieldProvisioningWaitFor, }, { name: "readiness", @@ -98,13 +100,13 @@ describe("rejected Blaxel create settings", () => { field: CreateFieldLifetime, }, { - name: "runtime", + name: "runtime language", request: { runtime: { language: "python" } }, kind: ErrorKind.Unsupported, field: CreateFieldRuntime, }, { - name: "isolation", + name: "isolation container", request: { isolation: { kind: IsolationKind.Container } }, kind: ErrorKind.Unsupported, field: CreateFieldIsolation, @@ -128,8 +130,8 @@ describe("rejected Blaxel create settings", () => { it("missing image", () => { expect(() => mapCreateRequest({ name: "box" }, provider)).toThrowError( expect.objectContaining({ - kind: ErrorKind.Unsupported, - field: CreateFieldSource, + kind: ErrorKind.InvalidArgument, + field: CreateFieldSourceImage, }), ); }); @@ -145,6 +147,24 @@ describe("rejected Blaxel create settings", () => { ); }); + it("accepts kit create timeout as deadline", () => { + expect(() => + mapCreateRequest( + namedImage({ provisioning: { timeout: 30_000 } }), + provider, + ), + ).not.toThrow(); + }); + + it("accepts default isolation", () => { + expect(() => + mapCreateRequest( + namedImage({ isolation: { kind: IsolationKind.Default } }), + provider, + ), + ).not.toThrow(); + }); + for (const item of cases) { it(item.name, () => { expect(() => diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml index 67d94f49..ca5d9726 100644 --- a/specs/providers/blaxel.yaml +++ b/specs/providers/blaxel.yaml @@ -5,11 +5,15 @@ provider: blaxel checks: - { path: name, required: true } - { path: source.image, required: true } + - { path: environment, format: env_name } - { path: placement, unsupported: true } - { path: resources.cpu_cores, unsupported: true } - { path: resources.cpu_limit_cores, unsupported: true } - { path: resources.memory_limit_mib, unsupported: true } - { path: resources.disk_mib, unsupported: true } + - { path: readiness, unsupported: true } + - { path: provisioning.queue_timeout, unsupported: true } + - { path: provisioning.wait_for, unsupported: true, notes: "Ready and Started rejected; Default/Scheduled accepted" } client: target: name: ClientOptions diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index 99fbd57e..6f0bb1f0 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -111,6 +111,10 @@ func main() { | Filesystem | Shared eight methods; absolute sandbox paths | | Process | Blocking `run` with argv or shell | +`resources.memoryMib` / `memory_mib` is sent as Blaxel's memory integer +(megabytes) with no unit conversion. Client `timeout` is the create/operation +deadline; Ready wait and readiness probes are unsupported. + Closing the client closes your connection to Blaxel. Closing a handle leaves the sandbox as it is. ## Next From 72d84a3d1c50e76efff3e830aaad6a02b298f0d7 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 15:28:25 +0530 Subject: [PATCH 09/60] Document Blaxel Kit-only create deadline on the website. Co-authored-by: Cursor --- website/content/docs/configuration.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/content/docs/configuration.mdx b/website/content/docs/configuration.mdx index 7a05c606..f6e81ff6 100644 --- a/website/content/docs/configuration.mdx +++ b/website/content/docs/configuration.mdx @@ -385,6 +385,11 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ rejected. + + Blaxel uses the Kit deadline only. It has no separate native create-wait + timeout. Ready wait and readiness probes are unsupported. + + ## Credentials Construction does not contact the provider. Bad credentials fail on the first create or open. See [Errors](/docs/errors). From 87a671147bdc81593085392b9f9486ef3b384524 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 15:36:07 +0530 Subject: [PATCH 10/60] Document Blaxel memory pass-through, volumes out of scope, and TS settings. Lock the approved product decisions into current-state docs so callers know Kit maps memoryMib 1:1, omits portable volumes, and shares one @blaxel/core credential set per process. Co-authored-by: Cursor --- docs/configuration.md | 4 +++ docs/providers.md | 10 +++++-- docs/sandbox-creation.md | 35 ++++++++++++---------- sdks/go/providers/blaxel/README.md | 7 +++-- sdks/typescript/providers/blaxel/README.md | 23 +++++++------- specs/providers/blaxel.yaml | 1 + website/content/docs/blaxel.mdx | 14 ++++++--- website/content/docs/configuration.mdx | 4 ++- website/content/docs/create.mdx | 1 + 9 files changed, 61 insertions(+), 38 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 64466ba0..4fe294a9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -68,6 +68,10 @@ created. Omit `auth` when the provider should use credentials it already finds in the environment. Daytona can take the organization from that environment when you use a bearer token and leave organization unset. +TypeScript Blaxel clients share `@blaxel/core` process-global `settings`. Use +one Blaxel Kit client, or one credential set, per process; a second client +overwrites the first. + A remote endpoint uses HTTPS. HTTP is accepted for `localhost` or a loopback address. A URL with a username, password, or fragment is rejected. Daytona uses `endpoint` when you set it, and otherwise uses `https://app.daytona.io/api`. diff --git a/docs/providers.md b/docs/providers.md index 272f229b..fee50dc2 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -74,9 +74,13 @@ running. Closing a handle leaves the sandbox as it is. You can keep using the handle. The next call returns the provider's result. The public API covers create, lifecycle, files, and blocking command execution -inside the sandbox. GPU requests, resizing, storage volumes, browsers, -streaming process handles, sessions, PTY, and macOS or Windows creation are -outside it. +inside the sandbox. GPU requests, resizing, storage volumes (no portable +volume fields; Blaxel, Daytona, and Modal volumes stay out of create), +browsers, streaming process handles, sessions, PTY, and macOS or Windows +creation are outside it. + +TypeScript Blaxel uses `@blaxel/core` process-global `settings`: one Blaxel +Kit client, or one credential set, per process. See [configuration](configuration.md), [creation](sandbox-creation.md), [lifecycle](sandbox-lifecycle.md), [filesystem](filesystem.md), and diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index 77c494ee..0f3e46c2 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -26,11 +26,13 @@ Blaxel (`modal@0.11.1`, `@daytona/sdk@0.225.0`, `@blaxel/core@0.3.36` / Blaxel create requires a nonblank name, an image source, and a client -`region`. It maps `memory_mib` (passed through as Blaxel megabytes with no -conversion), labels, environment, ports, outbound domains, maximum lifetime, -and idle terminate. CPU/disk overrides, placement, non-default isolation, -runtime overrides, security, readiness, queue timeout, Ready wait, and most -egress modes are rejected. Kit client/create timeout is the create deadline. +`region`. It maps `memory_mib` 1:1 onto Blaxel's memory field (Blaxel labels +that integer MB; Kit does not convert), labels, environment, ports, outbound +domains, maximum lifetime, and idle terminate. CPU/disk overrides, placement, +non-default isolation, runtime overrides, security, readiness, queue timeout, +Ready wait, and most egress modes are rejected. Kit client/create timeout is +the create deadline. Kit has no portable volume fields; Blaxel volumes stay +out of create the same way Daytona and Modal volumes do. Modal default, snapshot, and warm-pool sources are unavailable. Daytona warm pools and explicit snapshot kinds are unavailable. A field the provider does @@ -55,17 +57,18 @@ field was omitted. Invalid combinations fail in the client before the provider call. A field the selected provider does not support fails there too. -| Resource | Modal | Daytona | -| ---------------- | ----------------------------------------------- | -------------------------------------------- | -| CPU | Physical cores; minimum 0.125, precision 0.001 | Whole CPU cores | -| Memory | MiB; minimum request 128 | Whole GiB expressed as MiB | -| Disk | Creation override unsupported | Whole GiB expressed as MiB | -| Maximum lifetime | Positive whole seconds, up to 24 hours (TS: ms) | Whole-minute TTL (TS: ms); zero disables TTL | +| Resource | Modal | Daytona | Blaxel | +| ---------------- | ----------------------------------------------- | -------------------------------------------- | -------------------------------------------------------------- | +| CPU | Physical cores; minimum 0.125, precision 0.001 | Whole CPU cores | Derived by Blaxel from memory; Kit rejects CPU overrides | +| Memory | MiB; minimum request 128 | Whole GiB expressed as MiB | `memory_mib` / `memoryMib` passed 1:1; Blaxel labels the unit MB | +| Disk | Creation override unsupported | Whole GiB expressed as MiB | Creation override unsupported | +| Maximum lifetime | Positive whole seconds, up to 24 hours (TS: ms) | Whole-minute TTL (TS: ms); zero disables TTL | Blaxel TTL string (at least 5m when set) | Daytona request conversion divides MiB exactly by 1024. Allocated metadata converts GiB back to MiB. Snapshot creation inherits snapshot resources. Positive CPU or memory limits require a corresponding request and cannot be -lower than that request. +lower than that request. Blaxel memory is a 1:1 integer pass-through; Kit +does not convert MiB to MB. Local checks cover syntax, known enums, documented bounds, precision, and supported combinations. Account quotas, class-specific permissions, image @@ -140,10 +143,10 @@ handle. The next call returns the provider's result. Lifecycle operations are described in the [lifecycle guide](sandbox-lifecycle.md). Files inside a sandbox are described in the [filesystem guide](filesystem.md). -Creation covers the sandbox itself. GPU requests, resizing, storage volumes, -browsers, streaming process handles, sessions, PTY, and macOS or Windows -creation are outside it. Blocking command execution is covered in -[process](process.md). +Creation covers the sandbox itself. GPU requests, resizing, storage volumes +(including Blaxel volumes), browsers, streaming process handles, sessions, +PTY, and macOS or Windows creation are outside it. Blocking command execution +is covered in [process](process.md). See [configuration](configuration.md), [lifecycle](sandbox-lifecycle.md), and [filesystem](filesystem.md). diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 883f565c..ec8276f2 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -87,9 +87,10 @@ exit code is success for the SDK—read `GetExitCode`. Blaxel returns stdout and stderr. Omit or zero `Timeout` for no Blaxel command limit. Command timeouts must be whole seconds. Cancel the context to stop waiting. -`resources.memory_mib` is passed through as Blaxel's memory integer (SDK unit: -megabytes) with no conversion. Blaxel derives CPU from that value, so -`cpu_cores` and disk overrides are rejected. +`resources.memory_mib` maps 1:1 onto Blaxel's memory field. Blaxel labels that +integer MB; Kit does not convert. Blaxel derives CPU from that value, so +`cpu_cores` and disk overrides are rejected. Kit has no portable volume +fields; Blaxel volumes are outside create. ## License diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index df419175..b0a45c8c 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -53,14 +53,15 @@ than `workspace` are rejected. ## What you can do **Create** requires a nonblank `name`, an image source, and client `region`. -Optional memory (`memoryMib` number is sent as Blaxel megabytes with no -conversion; Blaxel CPU follows memory), labels, environment variables, ports, -outbound domains, max-age TTL (at least 5m), and idle-delete policy (at least -1m; explicit disable is rejected). Client / create `timeout` is the Kit -deadline; Blaxel has no separate native create-wait timeout. -`provisioning.waitFor` Ready is unsupported. Creation calls native -`SandboxInstance.create`, not `createIfNotExists`. Ports 80, 443, and 8080 are -rejected (Blaxel-reserved). +Optional `memoryMib` maps 1:1 onto Blaxel's memory field (Blaxel labels that +integer MB; Kit does not convert; Blaxel CPU follows memory), labels, +environment variables, ports, outbound domains, max-age TTL (at least 5m), and +idle-delete policy (at least 1m; explicit disable is rejected). Client / +create `timeout` is the Kit deadline; Blaxel has no separate native +create-wait timeout. `provisioning.waitFor` Ready is unsupported. Creation +calls native `SandboxInstance.create`, not `createIfNotExists`. Ports 80, +443, and 8080 are rejected (Blaxel-reserved). Kit has no portable volume +fields; Blaxel volumes are outside create. **Lifecycle** on the handle: `get` / `refresh` (lookup by name), `delete`, and `close`. `close` does not delete the sandbox. Start, stop, pause, archive, @@ -85,9 +86,9 @@ command may keep running. Omitted memory keeps the Blaxel default. CPU, CPU limit, memory limit, and disk overrides are rejected. -`@blaxel/core` applies credentials through process-global `settings`. Two Kit -Blaxel clients in one process overwrite each other; construct one client per -process or reconfigure before each use. +`@blaxel/core` applies credentials through process-global `settings`. Use one +Blaxel Kit client, or one credential set, per process; a second client +overwrites the first. ## License diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml index ca5d9726..0e7446ad 100644 --- a/specs/providers/blaxel.yaml +++ b/specs/providers/blaxel.yaml @@ -120,6 +120,7 @@ groups: - { from: name, to: name } - { from: environment, to: environment } - { from: labels, to: labels } + # memory_mib → Blaxel memory is 1:1; Blaxel labels the unit MB; Kit does not convert. - name: mapResources direction: request source: { name: Resources } diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index 6f0bb1f0..37992488 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -105,15 +105,21 @@ func main() { | Area | Notes | | ---------- | --------------------------------------------------------------------- | -| Create | Name + image; memory, labels, env, ports, outbound domains, TTL/idle | +| Create | Name + image; memory 1:1, labels, env, ports, outbound domains, TTL/idle | +| Volumes | Not in Kit create (same as Daytona/Modal); no portable volume fields | | Open | `get` / `refresh` by sandbox name | | Lifecycle | `delete`, `close` — no start/stop/pause/archive/terminate/attach | | Filesystem | Shared eight methods; absolute sandbox paths | | Process | Blocking `run` with argv or shell | -`resources.memoryMib` / `memory_mib` is sent as Blaxel's memory integer -(megabytes) with no unit conversion. Client `timeout` is the create/operation -deadline; Ready wait and readiness probes are unsupported. +`resources.memoryMib` / `memory_mib` maps 1:1 onto Blaxel's memory field. +Blaxel labels that integer MB; Kit does not convert. Kit has no portable +volume fields; Blaxel volumes stay out of create the same way Daytona and +Modal volumes do. Client `timeout` is the create/operation deadline; Ready +wait and readiness probes are unsupported. + +TypeScript: `@blaxel/core` stores credentials in process-global `settings`. +Use one Blaxel Kit client, or one credential set, per process. Closing the client closes your connection to Blaxel. Closing a handle leaves the sandbox as it is. diff --git a/website/content/docs/configuration.mdx b/website/content/docs/configuration.mdx index f6e81ff6..f690e3b5 100644 --- a/website/content/docs/configuration.mdx +++ b/website/content/docs/configuration.mdx @@ -387,7 +387,9 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ Blaxel uses the Kit deadline only. It has no separate native create-wait - timeout. Ready wait and readiness probes are unsupported. + timeout. Ready wait and readiness probes are unsupported. TypeScript Blaxel + clients share `@blaxel/core` process-global `settings`: use one Blaxel Kit + client, or one credential set, per process. ## Credentials diff --git a/website/content/docs/create.mdx b/website/content/docs/create.mdx index cd36e918..a774a73f 100644 --- a/website/content/docs/create.mdx +++ b/website/content/docs/create.mdx @@ -9,6 +9,7 @@ Create returns a sandbox handle. Build the client first. Provider setup: [Daytona](/docs/daytona). Provider setup: [Modal](/docs/modal). +Provider setup: [Blaxel](/docs/blaxel). Omitted fields keep provider defaults. Unsupported fields fail before the provider call. From 1be77eda15e91b0e85df56c72f58b3a1bf74c5aa Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 15:48:56 +0530 Subject: [PATCH 11/60] Document Blaxel Go examples and public API comments. Add pkg.go.dev ExampleNew_* coverage and align provider README Examples with Daytona/Modal depth, including TypeScript blaxel() JSDoc for workspace, region, memory, and settings. Co-authored-by: Cursor --- sdks/go/providers/blaxel/README.md | 16 + sdks/go/providers/blaxel/client.go | 6 +- sdks/go/providers/blaxel/doc.go | 13 +- sdks/go/providers/blaxel/example_test.go | 355 ++++++++++++++++++ sdks/go/providers/blaxel/provider.go | 10 +- sdks/typescript/providers/blaxel/README.md | 12 + .../typescript/providers/blaxel/src/blaxel.ts | 11 +- 7 files changed, 415 insertions(+), 8 deletions(-) create mode 100644 sdks/go/providers/blaxel/example_test.go diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index ec8276f2..ef54b2d0 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -92,6 +92,22 @@ integer MB; Kit does not convert. Blaxel derives CPU from that value, so `cpu_cores` and disk overrides are rejected. Kit has no portable volume fields; Blaxel volumes are outside create. +## Examples + +| Operation | Program | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| Create | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-sandbox) | +| Run a command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-command) | +| Shell / cwd / env / exit | [process examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process) | +| Read a file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/read-blaxel-file) | +| Filesystem | [filesystem examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem) | +| Look up | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/get-blaxel-sandbox) | +| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/delete-blaxel-sandbox) | +| All | [Go examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go) | + +pkg.go.dev also shows `ExampleNew_*` samples in this package for create, +lifecycle, process, and filesystem. + ## License MIT diff --git a/sdks/go/providers/blaxel/client.go b/sdks/go/providers/blaxel/client.go index 433a3a7e..4ada8b90 100644 --- a/sdks/go/providers/blaxel/client.go +++ b/sdks/go/providers/blaxel/client.go @@ -6,8 +6,10 @@ import ( ) // mapClientConfig maps shared client configuration into native SDK options. -// Unsupported supplied settings fail before the SDK is constructed. -// Shared Config validation runs in sandbox.NewClient; this maps only. +// Endpoint is unsupported. Scope.Workspace maps to the native workspace +// option when set. Unsupported supplied settings fail before the SDK is +// constructed. Shared Config validation runs in sandbox.NewClient; this +// maps only. type clientSettings struct { source *sandbox.Config opts []option.RequestOption diff --git a/sdks/go/providers/blaxel/doc.go b/sdks/go/providers/blaxel/doc.go index 02e887d8..1c850d66 100644 --- a/sdks/go/providers/blaxel/doc.go +++ b/sdks/go/providers/blaxel/doc.go @@ -1,8 +1,13 @@ // Package blaxel connects the sandbox client to Blaxel. // -// Pass [New] to sandbox.NewClient. Create requires a sandbox name and an image -// source. The backend implements shared filesystem operations through +// Pass [New] to sandbox.NewClient. Create requires a sandbox name, an image +// source, and [sandbox.Config.Region]. Set Scope.Workspace and an API key, or +// omit them so the Blaxel SDK can use BL_API_KEY and BL_WORKSPACE from the +// environment. The backend implements shared filesystem operations through // [sandbox.SandboxFiles] and blocking command execution through -// [sandbox.SandboxProcess]. Closing the client does not delete the Blaxel -// sandbox. +// [sandbox.SandboxProcess]. Get and Refresh look up a sandbox by name. Delete +// removes the Blaxel resource. Closing the client releases local provider +// state and leaves the Blaxel sandbox running. Start, Stop, Pause, Archive, +// Attach, and Terminate are unsupported. resources.memory_mib maps 1:1 onto +// Blaxel's memory field; Kit has no portable volume fields. package blaxel diff --git a/sdks/go/providers/blaxel/example_test.go b/sdks/go/providers/blaxel/example_test.go new file mode 100644 index 00000000..a331b0d4 --- /dev/null +++ b/sdks/go/providers/blaxel/example_test.go @@ -0,0 +1,355 @@ +package blaxel_test + +import ( + "context" + "errors" + "fmt" + "os" + "time" + + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func ExampleNew() { + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + Timeout: sandbox.Duration(2 * time.Minute), + }) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + }) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(instance.ProviderName(), instance.ID()) +} + +func ExampleNew_image() { + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + }) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example-memory"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Resources: &sandbox.Resources{ + MemoryMib: sandbox.Value(uint64(4096)), + }, + }) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(instance.ID()) +} + +func ExampleNew_get() { + client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + }) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + if err != nil { + fmt.Println(err) + return + } + if err = instance.Refresh(context.Background()); err != nil { + fmt.Println(err) + } +} + +func ExampleNew_delete() { + client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + }) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + if err != nil { + fmt.Println(err) + return + } + err = instance.Delete(context.Background(), &sandbox.DeleteOptions{}) + if err != nil { + fmt.Println(err) + } +} + +func ExampleNew_close() { + region := os.Getenv("BL_REGION") + if region == "" { + region = "us-pdx-1" + } + client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value(region), + }) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example-close"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + }) + if err != nil { + fmt.Println(err) + return + } + // Close leaves the Blaxel sandbox running. + err = instance.Close(context.Background()) + if err != nil { + fmt.Println(err) + } +} + +func ExampleNew_errors() { + _, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Endpoint: sandbox.Value("https://example.com"), + }) + var detail *sandbox.Error + if errors.As(err, &detail) { + fmt.Println(detail.Field) + } + // Output: + // endpoint +} + +func ExampleNew_process() { + client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + }) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + if err != nil { + fmt.Println(err) + return + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Argv: []string{"echo", "hello from kit"}, + }) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(result.GetExitCode(), string(result.GetStdout())) +} + +func ExampleNew_processShell() { + client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + }) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + if err != nil { + fmt.Println(err) + return + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Shell: sandbox.Value(`echo "hello from shell"`), + }) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(result.GetExitCode()) +} + +func ExampleNew_processEnv() { + client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + }) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + if err != nil { + fmt.Println(err) + return + } + result, err := instance.Process().Run(context.Background(), &sandbox.ExecOptions{ + Argv: []string{"printenv", "KIT_DEMO"}, + Environment: map[string]string{"KIT_DEMO": "from-kit"}, + }) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(string(result.GetStdout())) +} + +func ExampleNew_filesystem() { + client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + }) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + if err != nil { + fmt.Println(err) + return + } + files := instance.Filesystem() + if err := files.WriteFile(context.Background(), "/tmp/note.txt", []byte("hello\n"), nil); err != nil { + fmt.Println(err) + return + } + data, err := files.ReadFile(context.Background(), "/tmp/note.txt", nil) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(string(data)) +} + +func ExampleNew_makeDirectory() { + client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + }) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + if err != nil { + fmt.Println(err) + return + } + err = instance.Filesystem().MakeDirectory(context.Background(), "/tmp/kit-dir", nil) + if err != nil { + fmt.Println(err) + } +} + +func ExampleNew_listDirectory() { + client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + }) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + if err != nil { + fmt.Println(err) + return + } + entries, err := instance.Filesystem().ListDirectory(context.Background(), "/tmp", nil) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(len(entries)) +} + +func ExampleNew_stat() { + client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + }) + if err != nil { + fmt.Println(err) + return + } + defer client.Close(context.Background()) + + instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + if err != nil { + fmt.Println(err) + return + } + info, err := instance.Filesystem().Stat(context.Background(), "/tmp/note.txt", nil) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(info.GetPath(), info.GetSize()) +} diff --git a/sdks/go/providers/blaxel/provider.go b/sdks/go/providers/blaxel/provider.go index c7149aad..a1e4a2d4 100644 --- a/sdks/go/providers/blaxel/provider.go +++ b/sdks/go/providers/blaxel/provider.go @@ -25,7 +25,15 @@ type backend struct { // // Example: // -// client, err := sandbox.NewClient(New(), &sandbox.Config{}) +// client, err := sandbox.NewClient(New(), &sandbox.Config{ +// Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ +// ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, +// }}, +// Scope: &sandbox.Scope{ +// Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), +// }, +// Region: sandbox.Value("us-pdx-1"), +// }) func New() *Provider { return &Provider{} } diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index b0a45c8c..7e920b6d 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -90,6 +90,18 @@ disk overrides are rejected. Blaxel Kit client, or one credential set, per process; a second client overwrites the first. +## Examples + +| Example | Project | +| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-sandbox) | +| Run a command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-command) | +| Process | [process examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process) | +| Read a file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/read-blaxel-file) | +| Filesystem | [filesystem examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem) | +| Retrieval | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox) | +| Full index | [TypeScript examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript) | + ## License MIT diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index 20b6cfbd..c20bb79d 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -434,7 +434,16 @@ function execTimeoutSeconds(timeout: number | undefined): number | undefined { return timeout / 1_000; } -/** Selects Blaxel as the provider for the shared Sandbox Kit client. */ +/** + * Selects Blaxel as the provider for the shared Sandbox Kit client. + * + * Create needs `name`, an image source, and client `region`. Set + * `auth.apiKey` and `scope.workspace`, or omit them so `@blaxel/core` can use + * `BL_API_KEY` / `BL_WORKSPACE`. `memoryMib` maps 1:1 onto Blaxel's memory + * field; Kit has no portable volume fields. `@blaxel/core` stores credentials + * in process-global `settings` — use one Blaxel Kit client, or one credential + * set, per process. + */ export function blaxel(): Provider { return new BlaxelProvider(); } From c508ac69ecb1b1312781db75cf099acd7741787b Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 15:48:56 +0530 Subject: [PATCH 12/60] Bring repo Markdown Blaxel coverage in line with Daytona and Modal. Extend creation/lifecycle/process/filesystem/error guides and example indexes so every Blaxel example is discoverable with the same structure as the other providers. Co-authored-by: Cursor --- README.md | 9 +++++-- docs/filesystem.md | 46 +++++++++++++++++++++++----------- docs/process.md | 31 +++++++++++++---------- docs/provider-verification.md | 29 +++++++++++++-------- docs/responses-and-errors.md | 12 +++++---- docs/sandbox-creation.md | 39 ++++++++++++----------------- docs/sandbox-lifecycle.md | 28 ++++++++++++--------- examples/go/README.md | 47 +++++++++++++++++++---------------- examples/typescript/README.md | 21 ++++++++-------- 9 files changed, 151 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index e3b73f9e..257f15c1 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ func main() { | Language | Core SDK | Providers | | ---------- | -------------------------------------------- | ---------------------------------------------- | -| TypeScript | `@sandbox-kit/sandbox` | `@sandbox-kit/daytona`, `@sandbox-kit/modal` | +| TypeScript | `@sandbox-kit/sandbox` | `@sandbox-kit/daytona`, `@sandbox-kit/modal`, `@sandbox-kit/blaxel` | | Go | `github.com/sandbox-kit/kit/sdks/go/sandbox` | `.../providers/daytona`, `.../providers/modal`, `.../providers/blaxel` | Install only the provider you use. The core SDK exposes the shared client @@ -155,6 +155,10 @@ Modal needs a token pair and an existing app: [TypeScript](examples/typescript/modal/configuration/create-modal-sandbox/README.md) · [Go](examples/go/modal/configuration/create-modal-sandbox/README.md). +Blaxel needs an API key, a workspace, and a client region: +[TypeScript](examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md) · +[Go](examples/go/blaxel/configuration/create-blaxel-sandbox/README.md). + Creation examples provision real sandboxes. Filesystem, process, and lifecycle examples use the ID in `.env` (stop / terminate / archive / delete can destroy that resource; filesystem examples use the path you set; process examples run one @@ -258,7 +262,8 @@ More detail: [responses and errors](docs/responses-and-errors.md). Go helpers: `Value(v)` for optional scalars, `Duration(d)` for timeouts. - Unsupported settings fail before provider calls; quotas stay provider-enforced. - Memory and disk are MiB. Modal CPU: fractional cores from 0.125 (0.001 steps). - Daytona: whole cores and whole GiB expressed as MiB. + Daytona: whole cores and whole GiB expressed as MiB. Blaxel: `memoryMib` / + `memory_mib` maps 1:1; Blaxel derives CPU from memory. Guides: [configuration](docs/configuration.md) · [creation](docs/sandbox-creation.md) · diff --git a/docs/filesystem.md b/docs/filesystem.md index d6d244f0..52f63c26 100644 --- a/docs/filesystem.md +++ b/docs/filesystem.md @@ -1,7 +1,7 @@ # Filesystem File calls live on the sandbox handle, next to lifecycle. Daytona, Modal, and Blaxel -implement the same methods. Open the handle with `Create`, Daytona `Get`, or +implement the same methods. Open the handle with `Create`, Daytona or Blaxel `Get`, or Modal `Attach`, then call `Filesystem` / `sandbox.filesystem`. | You want to | Go | TypeScript | @@ -56,8 +56,9 @@ omitted, the abort signal carries the client deadline. `makeDirectory`, `listDirectory`, `stat`, and `remove` use the abort signal. `1500` ends the call at 1.5 seconds. Every Go file call uses the context as the deadline, so `1500 * time.Millisecond` ends the call at 1.5 seconds. Modal -file calls use the context or the abort signal. `1500` milliseconds ends the -call at 1.5 seconds. +and Blaxel file calls use the context or the abort signal. `1500` milliseconds +ends the call at 1.5 seconds. Blaxel rejects `parents: false` before the +provider call (field `parents`). ```ts await sandbox.filesystem.readFile("/tmp/note.txt", { timeout: 2_000 }); @@ -87,7 +88,9 @@ data, err := instance.Filesystem().ReadFile(ctx, "/tmp/note.txt", nil) [Go Daytona](../examples/go/daytona/filesystem/read-daytona-file/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/read-daytona-file/README.md) · [Go Modal](../examples/go/modal/filesystem/read-modal-file/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/read-modal-file/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/read-modal-file/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/read-blaxel-file/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/read-blaxel-file/README.md). ## Write a file @@ -106,7 +109,9 @@ err := instance.Filesystem().WriteFile(ctx, "/tmp/kit-files/note.txt", []byte("h [Go Daytona](../examples/go/daytona/filesystem/write-daytona-file/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/write-daytona-file/README.md) · [Go Modal](../examples/go/modal/filesystem/write-modal-file/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/write-modal-file/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/write-modal-file/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/write-blaxel-file/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/write-blaxel-file/README.md). ## Copy a file @@ -140,24 +145,29 @@ Into the sandbox: [Go Daytona](../examples/go/daytona/filesystem/copy-file-into-daytona/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/copy-file-into-daytona/README.md) · [Go Modal](../examples/go/modal/filesystem/copy-file-into-modal/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/copy-file-into-modal/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/copy-file-into-modal/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/copy-file-into-blaxel/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/copy-file-into-blaxel/README.md). Out of the sandbox: [Go Daytona](../examples/go/daytona/filesystem/copy-file-from-daytona/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/copy-file-from-daytona/README.md) · [Go Modal](../examples/go/modal/filesystem/copy-file-from-modal/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/copy-file-from-modal/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/copy-file-from-modal/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/copy-file-from-blaxel/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/copy-file-from-blaxel/README.md). ## Create a directory `parents` defaults to true, so the call creates missing parents. Modal accepts a directory that already exists. Daytona returns the provider error when it -already exists. +already exists. Blaxel creates missing parents when `parents` is omitted or +true. Set `parents` to false when the parent directory must already exist. Modal then requires that parent, and returns an error if the new directory is -already there. Daytona rejects `false` before the call. The error field is -`parents`. +already there. Daytona and Blaxel reject `false` before the call. The error +field is `parents`. ```ts await sandbox.filesystem.makeDirectory("/tmp/kit-files"); @@ -176,7 +186,9 @@ err = instance.Filesystem().MakeDirectory(ctx, "/tmp/kit-files/nested", &sandbox [Go Daytona](../examples/go/daytona/filesystem/make-daytona-directory/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/make-daytona-directory/README.md) · [Go Modal](../examples/go/modal/filesystem/make-modal-directory/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/make-modal-directory/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/make-modal-directory/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/make-blaxel-directory/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/make-blaxel-directory/README.md). ## List a directory @@ -197,7 +209,9 @@ entries, err := instance.Filesystem().ListDirectory(ctx, "/tmp/kit-files", nil) [Go Daytona](../examples/go/daytona/filesystem/list-daytona-directory/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/list-daytona-directory/README.md) · [Go Modal](../examples/go/modal/filesystem/list-modal-directory/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/list-modal-directory/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/list-modal-directory/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/list-blaxel-directory/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/list-blaxel-directory/README.md). ## Read metadata @@ -219,7 +233,9 @@ info, err := instance.Filesystem().Stat(ctx, "/tmp/note.txt", nil) [Go Daytona](../examples/go/daytona/filesystem/stat-daytona-file/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/stat-daytona-file/README.md) · [Go Modal](../examples/go/modal/filesystem/stat-modal-file/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/stat-modal-file/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/stat-modal-file/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/stat-blaxel-file/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md). ## Delete a file or directory @@ -248,4 +264,6 @@ if err := files.Remove(ctx, "/tmp/kit-files", &sandbox.RemoveOptions{ [Go Daytona](../examples/go/daytona/filesystem/remove-daytona-file/README.md) · [TypeScript Daytona](../examples/typescript/daytona/filesystem/remove-daytona-file/README.md) · [Go Modal](../examples/go/modal/filesystem/remove-modal-file/README.md) · -[TypeScript Modal](../examples/typescript/modal/filesystem/remove-modal-file/README.md). +[TypeScript Modal](../examples/typescript/modal/filesystem/remove-modal-file/README.md) · +[Go Blaxel](../examples/go/blaxel/filesystem/remove-blaxel-file/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/filesystem/remove-blaxel-file/README.md). diff --git a/docs/process.md b/docs/process.md index 4db477ba..2385231e 100644 --- a/docs/process.md +++ b/docs/process.md @@ -7,7 +7,7 @@ options on Daytona, Modal, and Blaxel. | ------------------------------------- | ----- | ---------- | | Run a command and wait for the result | `Run` | `run` | -Open a handle with `Create`, Daytona `Get`, or Modal `Attach`, then call +Open a handle with `Create`, Daytona or Blaxel `Get`, or Modal `Attach`, then call `Process()` / `sandbox.process`. Calls on one handle run one at a time (including filesystem and lifecycle). A @@ -25,10 +25,10 @@ Setting both or neither fails with field `argv`. A blank or whitespace-only How providers run the command: -| You pass | Daytona | Modal | -| -------- | ------------------------- | ------------------------ | -| `argv` | POSIX-quoted shell string | argument list as-is | -| `shell` | shell string as-is | `bash -c` with that text | +| You pass | Daytona | Modal | Blaxel | +| -------- | ------------------------- | ------------------------ | ------------------------- | +| `argv` | POSIX-quoted shell string | argument list as-is | POSIX-quoted shell string | +| `shell` | shell string as-is | `bash -c` with that text | shell string as-is | ## Working directory and environment @@ -56,6 +56,7 @@ code. | -------- | ----------------------------------------------------------------- | | Daytona | stdout only; `stderr` is empty (Go returns a non-nil empty slice) | | Modal | both stdout and stderr | +| Blaxel | both stdout and stderr | ## Timeouts and cancel @@ -73,6 +74,8 @@ quirks: capped at 10 seconds. - **Modal** — omit/zero adds no Modal command limit. In TypeScript the client omits `timeoutMs` (sending `0` is rejected by Modal). +- **Blaxel** — omit/zero adds no Blaxel command limit. Timeouts must be whole + seconds. Cancel behavior: @@ -113,17 +116,19 @@ result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ [Go Daytona](../examples/go/daytona/process/run-daytona-command/README.md) · [TypeScript Daytona](../examples/typescript/daytona/process/run-daytona-command/README.md) · [Go Modal](../examples/go/modal/process/run-modal-command/README.md) · -[TypeScript Modal](../examples/typescript/modal/process/run-modal-command/README.md). +[TypeScript Modal](../examples/typescript/modal/process/run-modal-command/README.md) · +[Go Blaxel](../examples/go/blaxel/process/run-blaxel-command/README.md) · +[TypeScript Blaxel](../examples/typescript/blaxel/process/run-blaxel-command/README.md). ## More examples -| Topic | Go Daytona | TypeScript Daytona | Go Modal | TypeScript Modal | -| ------------------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -| Shell string | [run-daytona-shell-command](../examples/go/daytona/process/run-daytona-shell-command/README.md) | [run-daytona-shell-command](../examples/typescript/daytona/process/run-daytona-shell-command/README.md) | [run-modal-shell-command](../examples/go/modal/process/run-modal-shell-command/README.md) | [run-modal-shell-command](../examples/typescript/modal/process/run-modal-shell-command/README.md) | -| Working directory | [run-daytona-with-cwd](../examples/go/daytona/process/run-daytona-with-cwd/README.md) | [run-daytona-with-cwd](../examples/typescript/daytona/process/run-daytona-with-cwd/README.md) | [run-modal-with-cwd](../examples/go/modal/process/run-modal-with-cwd/README.md) | [run-modal-with-cwd](../examples/typescript/modal/process/run-modal-with-cwd/README.md) | -| Environment | [run-daytona-with-env](../examples/go/daytona/process/run-daytona-with-env/README.md) | [run-daytona-with-env](../examples/typescript/daytona/process/run-daytona-with-env/README.md) | [run-modal-with-env](../examples/go/modal/process/run-modal-with-env/README.md) | [run-modal-with-env](../examples/typescript/modal/process/run-modal-with-env/README.md) | -| Timeout | [run-daytona-with-timeout](../examples/go/daytona/process/run-daytona-with-timeout/README.md) | [run-daytona-with-timeout](../examples/typescript/daytona/process/run-daytona-with-timeout/README.md) | [run-modal-with-timeout](../examples/go/modal/process/run-modal-with-timeout/README.md) | [run-modal-with-timeout](../examples/typescript/modal/process/run-modal-with-timeout/README.md) | -| Non-zero exit code | [run-daytona-check-exit](../examples/go/daytona/process/run-daytona-check-exit/README.md) | [run-daytona-check-exit](../examples/typescript/daytona/process/run-daytona-check-exit/README.md) | [run-modal-check-exit](../examples/go/modal/process/run-modal-check-exit/README.md) | [run-modal-check-exit](../examples/typescript/modal/process/run-modal-check-exit/README.md) | +| Topic | Go Daytona | TypeScript Daytona | Go Modal | TypeScript Modal | Go Blaxel | TypeScript Blaxel | +| ------------------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Shell string | [run-daytona-shell-command](../examples/go/daytona/process/run-daytona-shell-command/README.md) | [run-daytona-shell-command](../examples/typescript/daytona/process/run-daytona-shell-command/README.md) | [run-modal-shell-command](../examples/go/modal/process/run-modal-shell-command/README.md) | [run-modal-shell-command](../examples/typescript/modal/process/run-modal-shell-command/README.md) | [run-blaxel-shell-command](../examples/go/blaxel/process/run-blaxel-shell-command/README.md) | [run-blaxel-shell-command](../examples/typescript/blaxel/process/run-blaxel-shell-command/README.md) | +| Working directory | [run-daytona-with-cwd](../examples/go/daytona/process/run-daytona-with-cwd/README.md) | [run-daytona-with-cwd](../examples/typescript/daytona/process/run-daytona-with-cwd/README.md) | [run-modal-with-cwd](../examples/go/modal/process/run-modal-with-cwd/README.md) | [run-modal-with-cwd](../examples/typescript/modal/process/run-modal-with-cwd/README.md) | [run-blaxel-with-cwd](../examples/go/blaxel/process/run-blaxel-with-cwd/README.md) | [run-blaxel-with-cwd](../examples/typescript/blaxel/process/run-blaxel-with-cwd/README.md) | +| Environment | [run-daytona-with-env](../examples/go/daytona/process/run-daytona-with-env/README.md) | [run-daytona-with-env](../examples/typescript/daytona/process/run-daytona-with-env/README.md) | [run-modal-with-env](../examples/go/modal/process/run-modal-with-env/README.md) | [run-modal-with-env](../examples/typescript/modal/process/run-modal-with-env/README.md) | [run-blaxel-with-env](../examples/go/blaxel/process/run-blaxel-with-env/README.md) | [run-blaxel-with-env](../examples/typescript/blaxel/process/run-blaxel-with-env/README.md) | +| Timeout | [run-daytona-with-timeout](../examples/go/daytona/process/run-daytona-with-timeout/README.md) | [run-daytona-with-timeout](../examples/typescript/daytona/process/run-daytona-with-timeout/README.md) | [run-modal-with-timeout](../examples/go/modal/process/run-modal-with-timeout/README.md) | [run-modal-with-timeout](../examples/typescript/modal/process/run-modal-with-timeout/README.md) | [run-blaxel-with-timeout](../examples/go/blaxel/process/run-blaxel-with-timeout/README.md) | [run-blaxel-with-timeout](../examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md) | +| Non-zero exit code | [run-daytona-check-exit](../examples/go/daytona/process/run-daytona-check-exit/README.md) | [run-daytona-check-exit](../examples/typescript/daytona/process/run-daytona-check-exit/README.md) | [run-modal-check-exit](../examples/go/modal/process/run-modal-check-exit/README.md) | [run-modal-check-exit](../examples/typescript/modal/process/run-modal-check-exit/README.md) | [run-blaxel-check-exit](../examples/go/blaxel/process/run-blaxel-check-exit/README.md) | [run-blaxel-check-exit](../examples/typescript/blaxel/process/run-blaxel-check-exit/README.md) | ## Limits diff --git a/docs/provider-verification.md b/docs/provider-verification.md index 5e73ffef..e52480f7 100644 --- a/docs/provider-verification.md +++ b/docs/provider-verification.md @@ -15,11 +15,13 @@ resolution, and error translation without provisioning cloud resources. Filesystem tests cover path validation, local copy classification, timeouts, and provider file APIs (Modal `readBytes` / `writeBytes` / `copyFromLocal` / `listFiles`; Daytona high-level `FileSystem` plus toolbox list/stat, and -`downloadFiles` with timeout `0` when Kit adds no transfer limit). Process -tests cover argv/shell exclusivity, working-directory checks, whole-second -command timeouts, and command mapping (Daytona POSIX-quoted argv; Modal -`bash -c` for shell). Provider tests use the pinned SDK's transport/service -seams; they do not expose those native clients through the public SDK. +`downloadFiles` with timeout `0` when Kit adds no transfer limit; Blaxel +filesystem and process adapters against the pinned `@blaxel/core` / +`sdk-go` seams). Process tests cover argv/shell exclusivity, +working-directory checks, whole-second command timeouts, and command mapping +(Daytona and Blaxel POSIX-quoted argv; Modal `bash -c` for shell). Provider +tests use the pinned SDK's transport/service seams; they do not expose those +native clients through the public SDK. ## Reference sources @@ -27,6 +29,7 @@ seams; they do not expose those native clients through the public SDK. | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Modal | [Go client](https://modal.com/docs/sdk/go/latest/Client), [Go sandbox](https://modal.com/docs/sdk/go/latest/Sandbox), [JavaScript/Go SDK guide](https://modal.com/docs/guide/sdk-javascript-go) | [Resources](https://modal.com/docs/guide/resources), [Sandbox resources](https://modal.com/docs/guide/sandbox-resources), [Sandboxes](https://modal.com/docs/guide/sandboxes), [Regions](https://modal.com/docs/guide/region-selection) | | Daytona | [Go client/sandbox](https://www.daytona.io/docs/en/go-sdk/daytona/), [Go types](https://www.daytona.io/docs/en/go-sdk/types/), [TypeScript SDK](https://www.daytona.io/docs/en/typescript-sdk/) | [Sandboxes](https://www.daytona.io/docs/en/sandboxes/), [Limits](https://www.daytona.io/docs/limits) | +| Blaxel | [TypeScript SDK](https://docs.blaxel.ai/sdk-reference/sdk-ts), [Go SDK](https://docs.blaxel.ai/sdk-reference/sdk-go) | [Sandboxes](https://docs.blaxel.ai/Sandboxes/Overview), [Regions](https://docs.blaxel.ai/Infrastructure/Regions) | Modal creation dependencies also use the [Secret](https://modal.com/docs/sdk/go/latest/Secret), [Probe](https://modal.com/docs/sdk/go/latest/Probe), @@ -47,11 +50,16 @@ retain native behavior. Handwritten error classification exposes shared `ErrorKind` values while preserving native classification through the original cause. -The TypeScript adapters construct `ModalClient` and `Daytona` directly. Modal -looks up the app, image, secrets, and attached sandboxes through the pinned -client services. Daytona uses its asynchronous create/get and lifecycle methods. -Handwritten mappings and error classification preserve the same portable -semantics in both languages. +The TypeScript adapters construct `ModalClient`, `Daytona`, and Blaxel +`SandboxInstance` / `settings` directly. Modal looks up the app, image, +secrets, and attached sandboxes through the pinned client services. Daytona +uses its asynchronous create/get and lifecycle methods. Blaxel create uses +`SandboxInstance.create` (not create-if-not-exists); get/refresh look up by +sandbox name; delete removes the resource. Handwritten mappings and error +classification preserve the same portable semantics in both languages. +Blaxel memory is a 1:1 integer pass-through onto Blaxel's memory field; Kit +rejects CPU/disk overrides and has no portable volume fields. TypeScript +Blaxel credentials go through process-global `@blaxel/core` `settings`. ## Conflicting Daytona policy documentation @@ -74,6 +82,7 @@ See the [policy matrix](sandbox-creation.md#policy-semantics). | Modal | 0.125 physical CPU cores and 128 MiB | Five-minute maximum lifetime | | Daytona image creation | 1 vCPU, 1 GiB memory, 3 GiB disk | Provider/class defaults | | Daytona snapshot creation | Snapshot resource allocation | Provider/class defaults | +| Blaxel | Provider default when memory omitted | Provider defaults; TTL ≥5m, idle ≥1m when set | Kit leaves omitted fields to the provider. It does not inject these documented defaults into requests. diff --git a/docs/responses-and-errors.md b/docs/responses-and-errors.md index b6a0160d..07a9ee2b 100644 --- a/docs/responses-and-errors.md +++ b/docs/responses-and-errors.md @@ -43,10 +43,11 @@ if errors.As(err, &detail) { } ``` -The same switch works for either provider. Runnable examples: -[Modal unsupported settings](../examples/go/modal/errors/handle-modal-errors/README.md) +The same switch works for any provider. Runnable examples: +[Modal unsupported settings](../examples/go/modal/errors/handle-modal-errors/README.md), +[Daytona invalid configuration](../examples/go/daytona/errors/handle-daytona-errors/README.md), and -[Daytona invalid configuration](../examples/go/daytona/errors/handle-daytona-errors/README.md). +[Blaxel invalid configuration](../examples/go/blaxel/errors/handle-blaxel-errors/README.md). Go and TypeScript use the same field string. A rule about one setting names that setting, such as `resources.cpu_limit_cores`. A rule about several @@ -78,9 +79,10 @@ try { ``` TypeScript examples: -[handle-daytona-errors](../examples/typescript/daytona/errors/handle-daytona-errors/README.md) +[handle-daytona-errors](../examples/typescript/daytona/errors/handle-daytona-errors/README.md), +[handle-modal-errors](../examples/typescript/modal/errors/handle-modal-errors/README.md), and -[handle-modal-errors](../examples/typescript/modal/errors/handle-modal-errors/README.md). +[handle-blaxel-errors](../examples/typescript/blaxel/errors/handle-blaxel-errors/README.md). ## Provider errors diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index 0f3e46c2..17d6317b 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -10,29 +10,22 @@ Go and TypeScript integrations target Modal v0.11.1, Daytona v0.225.0, and Blaxel (`modal@0.11.1`, `@daytona/sdk@0.225.0`, `@blaxel/core@0.3.36` / `github.com/blaxel-ai/sdk-go@v0.31.0`). -| Setting | Modal | Daytona | -| ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | -| Source | Explicit registry image and an existing app | Default/specified snapshot or registry image | -| Runtime | Entrypoint, absolute working directory, PTY | Toolbox language and user | -| Isolation | Default, gVisor container, Linux VM | Nested KVM flag; class inherited from source | -| Resources | Fractional CPU, CPU limit, memory request/limit | Whole CPU and GiB memory/disk; image source required for overrides | -| Placement | Cloud and region preferences | Client target; request placement unsupported | -| Environment and labels | Environment map and tags | Environment map and labels | -| Security | Named secrets and workload identity | Egress-placeholder secrets and public access | -| Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | -| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | -| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | -| Observability | Verbose output | Telemetry endpoint | - - -Blaxel create requires a nonblank name, an image source, and a client -`region`. It maps `memory_mib` 1:1 onto Blaxel's memory field (Blaxel labels -that integer MB; Kit does not convert), labels, environment, ports, outbound -domains, maximum lifetime, and idle terminate. CPU/disk overrides, placement, -non-default isolation, runtime overrides, security, readiness, queue timeout, -Ready wait, and most egress modes are rejected. Kit client/create timeout is -the create deadline. Kit has no portable volume fields; Blaxel volumes stay -out of create the same way Daytona and Modal volumes do. +| Setting | Modal | Daytona | Blaxel | +| ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | +| Source | Explicit registry image and an existing app | Default/specified snapshot or registry image | Named create; registry image required | +| Runtime | Entrypoint, absolute working directory, PTY | Toolbox language and user | Unsupported | +| Isolation | Default, gVisor container, Linux VM | Nested KVM flag; class inherited from source | Unsupported | +| Resources | Fractional CPU, CPU limit, memory request/limit | Whole CPU and GiB memory/disk; image source required for overrides | Memory 1:1 (`memory_mib`); CPU/disk overrides rejected | +| Placement | Cloud and region preferences | Client target; request placement unsupported | Client `region` required for create; request placement unsupported | +| Environment and labels | Environment map and tags | Environment map and labels | Environment map and labels | +| Security | Named secrets and workload identity | Egress-placeholder secrets and public access | Unsupported | +| Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | Ports (not 80/443/8080); outbound domains | +| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m) | +| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Kit client/create timeout only; Ready wait unsupported | +| Observability | Verbose output | Telemetry endpoint | Unsupported | + +Kit has no portable volume fields; Blaxel volumes stay out of create the same +way Daytona and Modal volumes do. Modal default, snapshot, and warm-pool sources are unavailable. Daytona warm pools and explicit snapshot kinds are unavailable. A field the provider does diff --git a/docs/sandbox-lifecycle.md b/docs/sandbox-lifecycle.md index a7b60f54..aa5102e6 100644 --- a/docs/sandbox-lifecycle.md +++ b/docs/sandbox-lifecycle.md @@ -86,16 +86,16 @@ canceled before it starts never reaches the provider. ## Provider support -| Operation | Modal | Daytona | -| ----------- | -------------------------------------- | ----------- | -| Attach | Supported; ID only, no provider lookup | Unsupported | -| Refresh/Get | Unsupported | Supported | -| Start | Unsupported | Supported | -| Stop | Unsupported | Supported | -| Pause | Unsupported | Supported | -| Terminate | Supported | Unsupported | -| Archive | Unsupported | Supported | -| Delete | Unsupported | Supported | +| Operation | Modal | Daytona | Blaxel | +| ----------- | -------------------------------------- | ----------- | ---------------------------------------- | +| Attach | Supported; ID only, no provider lookup | Unsupported | Unsupported | +| Refresh/Get | Unsupported | Supported | Supported (lookup by sandbox name) | +| Start | Unsupported | Supported | Unsupported | +| Stop | Unsupported | Supported | Unsupported | +| Pause | Unsupported | Supported | Unsupported | +| Terminate | Supported | Unsupported | Unsupported | +| Archive | Unsupported | Supported | Unsupported | +| Delete | Unsupported | Supported | Supported | Daytona reports its resolved sandbox class after creation. Kit maps container and Linux VM to shared isolation kinds and preserves Windows as a provider-specific @@ -108,6 +108,10 @@ Modal gVisor and Linux VM sandboxes share Modal's terminal execution lifecycle. `Stop` or `Delete`. Modal's native `FromID` performs no remote lookup, so Kit exposes it as `Client.Attach`; Modal does not advertise `Get` or `Refresh`. +Blaxel looks up sandboxes by name for `Get` and `Refresh`. `Delete` removes the +Blaxel resource. `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and `Terminate` +are unsupported. Closing a Blaxel handle does not contact Blaxel. + File calls on the same handle are documented in [filesystem](filesystem.md). -Both providers implement the same eight methods; each handle serializes those -calls with its lifecycle transitions. +Daytona, Modal, and Blaxel implement the same eight methods; each handle +serializes those calls with its lifecycle transitions. diff --git a/examples/go/README.md b/examples/go/README.md index 7c1e6b16..041cd1c1 100644 --- a/examples/go/README.md +++ b/examples/go/README.md @@ -16,6 +16,7 @@ go run . Skip copying if `.env` already exists. Programs load `.env` with `godotenv`; shell variables win. The SDK does not load files. Modal app setup: [create-modal-sandbox](modal/configuration/create-modal-sandbox/README.md). +Blaxel setup: [create-blaxel-sandbox](blaxel/configuration/create-blaxel-sandbox/README.md). Creation programs provision real sandboxes (linked Daytona creates a parent and child). Filesystem, process, and lifecycle programs use the configured sandbox @@ -111,28 +112,6 @@ closes your connection to Daytona, Modal, or Blaxel. | [request-modal-termination](modal/lifecycle/request-modal-termination/README.md) | non-waited termination | | [close-modal-handle](modal/lifecycle/close-modal-handle/README.md) | close the handle; sandbox keeps running | -## Verify without credentials - -```sh -GOWORK=off go test -v ./... -``` - -Most examples compile only. Local tests live in `handle-modal-errors`, -`handle-daytona-errors`, `create-modal-with-resources`, and -`create-daytona-with-policies` (dummy credentials and local servers; they do not -schedule real provider work). Local tests may bind a port. - -Repository-wide: `sandbox-kit test go` from `tooling/`. - -## Error handling - -[handle-modal-errors](modal/errors/handle-modal-errors/README.md) and -[handle-daytona-errors](daytona/errors/handle-daytona-errors/README.md) switch on -`*sandbox.Error` `Kind` in `main.go`. Other create projects print and exit. -Cleanup failures do not replace creation failures. - -Modal errors: unsupported disk. Daytona errors: invalid CPU. - ### Blaxel | Project | Demonstrates | @@ -164,3 +143,27 @@ Modal errors: unsupported disk. Daytona errors: invalid CPU. | [delete-blaxel-sandbox](blaxel/lifecycle/delete-blaxel-sandbox/README.md) | waited deletion | | [close-blaxel-handle](blaxel/lifecycle/close-blaxel-handle/README.md) | close the handle; sandbox keeps running | | [handle-blaxel-errors](blaxel/errors/handle-blaxel-errors/README.md) | typed validation errors | + +## Verify without credentials + +```sh +GOWORK=off go test -v ./... +``` + +Most examples compile only. Local tests live in `handle-modal-errors`, +`handle-daytona-errors`, `create-modal-with-resources`, and +`create-daytona-with-policies` (dummy credentials and local servers; they do not +schedule real provider work). Local tests may bind a port. + +Repository-wide: `sandbox-kit test go` from `tooling/`. + +## Error handling + +[handle-modal-errors](modal/errors/handle-modal-errors/README.md), +[handle-daytona-errors](daytona/errors/handle-daytona-errors/README.md), and +[handle-blaxel-errors](blaxel/errors/handle-blaxel-errors/README.md) switch on +`*sandbox.Error` `Kind` in `main.go`. Other create projects print and exit. +Cleanup failures do not replace creation failures. + +Modal errors: unsupported disk. Daytona errors: invalid CPU. Blaxel errors: +invalid create configuration. diff --git a/examples/typescript/README.md b/examples/typescript/README.md index e55bf2fd..c74adcac 100644 --- a/examples/typescript/README.md +++ b/examples/typescript/README.md @@ -24,6 +24,7 @@ Creation examples provision real cloud resources. Filesystem, process, and lifec examples act on the sandbox ID in `.env`. Closing a handle leaves the sandbox as it is. Closing the client closes your connection to Daytona, Modal, or Blaxel. Modal setup: [create-modal-sandbox](modal/configuration/create-modal-sandbox/README.md). +Blaxel setup: [create-blaxel-sandbox](blaxel/configuration/create-blaxel-sandbox/README.md). ## Catalog @@ -112,16 +113,6 @@ the client closes your connection to Daytona, Modal, or Blaxel. Modal setup: | [request-modal-termination](modal/lifecycle/request-modal-termination/README.md) | non-waited termination | | [close-modal-handle](modal/lifecycle/close-modal-handle/README.md) | close the handle; sandbox keeps running | -## Verify all TypeScript projects - -From `tooling/`: - -```sh -GOWORK=off go run ./cmd/sandbox-kit test typescript -``` - -Builds and tests the SDK packages first, then every example with a `package.json`. - ### Blaxel | Project | Demonstrates | @@ -153,3 +144,13 @@ Builds and tests the SDK packages first, then every example with a `package.json | [delete-blaxel-sandbox](blaxel/lifecycle/delete-blaxel-sandbox/README.md) | waited deletion | | [close-blaxel-handle](blaxel/lifecycle/close-blaxel-handle/README.md) | close the handle; sandbox keeps running | | [handle-blaxel-errors](blaxel/errors/handle-blaxel-errors/README.md) | typed validation errors | + +## Verify all TypeScript projects + +From `tooling/`: + +```sh +GOWORK=off go run ./cmd/sandbox-kit test typescript +``` + +Builds and tests the SDK packages first, then every example with a `package.json`. From d707e22a0ad46d8e4fe6a8373801340fa4fa6fe2 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 15:48:57 +0530 Subject: [PATCH 13/60] Fill website Blaxel ForProvider panes and provider page parity. Match Daytona/Modal structure on blaxel.mdx, shared guides, SDK reference, and the full examples catalog, using feat/blaxel-provider links where main paths would 404. Co-authored-by: Cursor --- website/content/docs/blaxel.mdx | 103 +++++++++--- website/content/docs/concepts.mdx | 72 +++++++- website/content/docs/configuration.mdx | 176 ++++++++++++++++++++ website/content/docs/create.mdx | 156 +++++++++++++++++ website/content/docs/errors.mdx | 43 +++++ website/content/docs/examples.mdx | 146 +++++++++++++--- website/content/docs/filesystem.mdx | 33 ++++ website/content/docs/index.mdx | 93 +++++++++++ website/content/docs/lifecycle.mdx | 113 +++++++++++++ website/content/docs/process.mdx | 38 +++++ website/content/docs/sdk/client.mdx | 70 ++++++++ website/content/docs/sdk/configuration.mdx | 11 ++ website/content/docs/sdk/create-options.mdx | 28 ++++ website/content/docs/sdk/process.mdx | 11 ++ website/content/docs/sdk/sandbox.mdx | 77 +++++++++ website/src/components/provider-context.tsx | 2 +- 16 files changed, 1127 insertions(+), 45 deletions(-) diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index 37992488..8437542e 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -96,36 +96,97 @@ func main() { } fmt.Println(instance.ID()) } +``` + +Optional strings use `sandbox.Value`. + + + + +Omit `auth` and `scope.workspace` to let the Blaxel SDK pick credentials from its environment (`BL_API_KEY`, `BL_WORKSPACE`). `region` is still required for create. `endpoint` is rejected. Closing the client does not delete the sandbox. + +## Configuration + + + + +| Setting | Notes | +| -------------- | ------------------------------------------ | +| API key | `auth.apiKey` | +| Omitted `auth` | Blaxel SDK environment / CLI credentials | +| Workspace | `scope.workspace` — required for create when auth is set explicitly, or via env | +| Region | `region` — required for create | +| Endpoint | Unsupported | + +```ts +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region: process.env.BL_REGION || "us-pdx-1", + timeout: 120_000, +}); +``` + + + + +| Setting | Notes | +| --------- | ----------------------------------------- | +| API key | `Auth` with `APIKey` | +| Workspace | `Scope.Workspace` | +| Region | `Config.Region` — required for create | +| Endpoint | Unsupported | + +```go +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), +} +client, err := sandbox.NewClient(blaxel.New(), config) ``` -## What Blaxel supports +Token-pair, bearer, and OAuth client modes are rejected. Scope fields other than workspace are rejected. TypeScript: `@blaxel/core` stores credentials in process-global `settings` — use one Blaxel Kit client, or one credential set, per process. Full rules: [Configuration](/docs/configuration). + +## What works -| Area | Notes | -| ---------- | --------------------------------------------------------------------- | -| Create | Name + image; memory 1:1, labels, env, ports, outbound domains, TTL/idle | -| Volumes | Not in Kit create (same as Daytona/Modal); no portable volume fields | -| Open | `get` / `refresh` by sandbox name | -| Lifecycle | `delete`, `close` — no start/stop/pause/archive/terminate/attach | -| Filesystem | Shared eight methods; absolute sandbox paths | -| Process | Blocking `run` with argv or shell | +| Area | Notes | +| --------- | --------------------------------------------------------------------- | +| Create | Name + image; memory 1:1, labels, env, ports, outbound domains, TTL/idle | +| Volumes | Not in Kit create; no portable volume fields | +| Open | `get` / `refresh` by sandbox name | +| Lifecycle | `delete`, `close` — no start/stop/pause/archive/terminate/attach | +| Files | Filesystem methods — [Filesystem](/docs/filesystem) | +| Process | `run` — [Run a command](/docs/process) | `resources.memoryMib` / `memory_mib` maps 1:1 onto Blaxel's memory field. -Blaxel labels that integer MB; Kit does not convert. Kit has no portable -volume fields; Blaxel volumes stay out of create the same way Daytona and -Modal volumes do. Client `timeout` is the create/operation deadline; Ready -wait and readiness probes are unsupported. +Blaxel labels that integer MB; Kit does not convert. Blaxel derives CPU from +memory, so CPU and disk overrides are rejected. Client `timeout` is the +create/operation deadline; Ready wait and readiness probes are unsupported. -TypeScript: `@blaxel/core` stores credentials in process-global `settings`. -Use one Blaxel Kit client, or one credential set, per process. +## Related -Closing the client closes your connection to Blaxel. Closing a handle leaves the sandbox as it is. +- Labels, ports, TTL/idle — [Create](/docs/create) +- get / refresh / delete — [Lifecycle](/docs/lifecycle) -## Next + + + +Example: [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-sandbox) -- [Configuration](/docs/configuration) -- [Create](/docs/create) -- [Lifecycle](/docs/lifecycle) -- [Examples](/docs/examples) + + + +Example: [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-sandbox) + + + diff --git a/website/content/docs/concepts.mdx b/website/content/docs/concepts.mdx index 5474d76a..0afe94fa 100644 --- a/website/content/docs/concepts.mdx +++ b/website/content/docs/concepts.mdx @@ -143,6 +143,18 @@ const sandbox = await client.create({ console.log(sandbox.id); ``` + + + + +```ts +const sandbox = await client.create({ + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, +}); +console.log(sandbox.id); +``` + @@ -178,6 +190,23 @@ if err != nil { fmt.Println(instance.ID()) ``` + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, +}) +if err != nil { + return err +} +fmt.Println(instance.ID()) +``` + @@ -259,7 +288,7 @@ fmt.Println(result) Each provider is its own package. Pass it into the client. Auth, create defaults, and which lifecycle calls exist depend on the provider; the client methods stay the same. -The client talks to Daytona or Modal directly. There is no separate Kit cloud in the middle. +The client talks to Daytona, Modal, or Blaxel directly. There is no separate Kit cloud in the middle. @@ -300,6 +329,24 @@ await using client = new Client({ Setup: [Modal](/docs/modal). + + + + +```ts +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region: process.env.BL_REGION || "us-pdx-1", +}); +``` + +Setup: [Blaxel](/docs/blaxel). + @@ -348,6 +395,29 @@ client, err := sandbox.NewClient(modal.New(), &sandbox.Config{ Setup: [Modal](/docs/modal). + + + + +```go +import ( + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value("us-pdx-1"), +}) +``` + +Setup: [Blaxel](/docs/blaxel). + diff --git a/website/content/docs/configuration.mdx b/website/content/docs/configuration.mdx index f690e3b5..68bf4ffd 100644 --- a/website/content/docs/configuration.mdx +++ b/website/content/docs/configuration.mdx @@ -97,6 +97,23 @@ await using client = new Client({ }); ``` + + + + +```ts +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region: process.env.BL_REGION || "us-pdx-1", + timeout: 120_000, +}); +``` + @@ -157,6 +174,35 @@ if region := os.Getenv("MODAL_REGION"); region != "" { client, err := sandbox.NewClient(modal.New(), config) ``` + + + + +```go +import ( + "os" + "time" + + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), +} +if region := os.Getenv("BL_REGION"); region != "" { + config.Region = sandbox.Value(region) +} +client, err := sandbox.NewClient(blaxel.New(), config) +``` + @@ -288,6 +334,60 @@ client, err := sandbox.NewClient(modal.New(), config) + + + + + +| Setting | What to pass | +| -------------- | ---------------------------------------------- | +| API key | `auth.apiKey.key` | +| Omitted `auth` | Blaxel SDK environment / CLI credentials | +| Workspace | `scope.workspace` | +| Region | `region` — required for create | +| Endpoint | Unsupported | + +```ts +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region: process.env.BL_REGION || "us-pdx-1", + timeout: 120_000, +}); +``` + + + + +| Setting | What to pass | +| --------- | --------------------------------------------- | +| API key | `APIKey` | +| Workspace | `Scope.Workspace` | +| Region | `Config.Region` — required for create | +| Endpoint | Unsupported | + +```go +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), +} +client, err := sandbox.NewClient(blaxel.New(), config) +``` + + + + +Token-pair, bearer, and OAuth modes are rejected. Scope fields other than workspace are rejected. + + + ## Timeouts `timeout` on the client is the default deadline. Create and lifecycle calls can override it. @@ -327,6 +427,25 @@ const sandbox = await client.create( ); ``` + + + + +```ts +await using client = new Client({ + provider: blaxel(), + timeout: 120_000, // default for calls that omit timeout +}); + +const sandbox = await client.create( + { + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, + }, + { timeout: 60_000 }, // override for this call; 0 = no deadline +); +``` + @@ -374,6 +493,29 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ }) ``` + + + + +```go +client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Timeout: sandbox.Duration(2 * time.Minute), // default deadline +}) +if err != nil { + return err +} + +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Provisioning: &sandbox.ProvisioningOptions{ + Timeout: sandbox.Duration(60 * time.Second), // override; 0 = no deadline + }, +}) +``` + @@ -431,6 +573,22 @@ const config = { await using client = new Client(config); ``` + + + + +```ts +const config = { + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region: process.env.BL_REGION || "us-pdx-1", + timeout: 120_000, +}; + +await using client = new Client(config); +``` + @@ -469,6 +627,24 @@ config := &sandbox.Config{ client, err := sandbox.NewClient(modal.New(), config) ``` + + + + +```go +config := &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), +} +client, err := sandbox.NewClient(blaxel.New(), config) +``` + diff --git a/website/content/docs/create.mdx b/website/content/docs/create.mdx index a774a73f..cf01aa6b 100644 --- a/website/content/docs/create.mdx +++ b/website/content/docs/create.mdx @@ -42,6 +42,21 @@ const sandbox = await client.create( ); ``` + + + + +```ts +const sandbox = await client.create( + { + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 4096n }, + }, + { timeout: 60_000 }, // milliseconds +); +``` + @@ -83,6 +98,25 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ }) ``` + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Resources: &sandbox.Resources{ + MemoryMib: sandbox.Value(uint64(4096)), + }, + Provisioning: &sandbox.ProvisioningOptions{ + Timeout: sandbox.Duration(60 * time.Second), + }, +}) +``` + @@ -233,6 +267,64 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ + + +| | Blaxel | +| --------- | --------------------------------------------------------------------- | +| Source | Registry image and nonblank `name` required | +| CPU | Derived by Blaxel from memory; Kit rejects CPU overrides | +| Memory | `memoryMib` / `memory_mib` passed 1:1; Blaxel labels the unit MB | +| Disk | Cannot be set | +| Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m) | +| Placement | Client `region` required. A placement block on this request is rejected | + +Kit has no portable volume fields. + + + + +```ts +import { PolicyMode } from "@sandbox-kit/sandbox"; + +await client.create({ + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 4096n }, + lifetime: { + maximumLifetime: 24 * 60 * 60_000, + idleTerminate: { mode: PolicyMode.After, after: 60_000 }, + }, +}); +``` + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Resources: &sandbox.Resources{ + MemoryMib: sandbox.Value(uint64(4096)), + }, + Lifetime: &sandbox.LifetimePolicy{ + MaximumLifetime: sandbox.Duration(24 * time.Hour), + IdleTerminate: &sandbox.AutomaticAction{ + Mode: sandbox.PolicyModeAfter, + After: sandbox.Duration(time.Minute), + }, + }, +}) +``` + + + + + + + ## Example @@ -285,6 +377,28 @@ const sandbox = await client.create({ `PolicyMode.After` starts the idle timer. `PolicyMode.Disabled` turns idle termination off. + + + + +```ts +import { PolicyMode } from "@sandbox-kit/sandbox"; + +const sandbox = await client.create({ + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 4096n }, + lifetime: { + maximumLifetime: 24 * 60 * 60_000, + idleTerminate: { mode: PolicyMode.After, after: 60_000 }, + }, + network: { + ports: [{ port: 3000 }], + outboundDomains: ["example.com"], + }, +}); +``` + @@ -359,6 +473,39 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ }) ``` + + + + +```go +import ( + "time" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Resources: &sandbox.Resources{ + MemoryMib: sandbox.Value(uint64(4096)), + }, + Lifetime: &sandbox.LifetimePolicy{ + MaximumLifetime: sandbox.Duration(24 * time.Hour), + IdleTerminate: &sandbox.AutomaticAction{ + Mode: sandbox.PolicyModeAfter, + After: sandbox.Duration(time.Minute), + }, + }, + Network: &sandbox.NetworkConfig{ + Ports: []*sandbox.PortBinding{{Port: 3000}}, + OutboundDomains: &sandbox.StringAllowlist{Entries: []string{"example.com"}}, + }, +}) +``` + @@ -459,6 +606,15 @@ See [Examples](/docs/examples). + + +Create needs a nonblank `name`, an image, and client `region`. Memory maps 1:1; +CPU, disk, placement, readiness, and volumes are rejected. Ports 80, 443, and +8080 are reserved. See [Examples](/docs/examples). + + + + ## Result Metadata from info is a **copy**. Mutating it does not change the sandbox. Reported CPU and memory can differ from what you requested. diff --git a/website/content/docs/errors.mdx b/website/content/docs/errors.mdx index e1ed9b58..242c019d 100644 --- a/website/content/docs/errors.mdx +++ b/website/content/docs/errors.mdx @@ -297,3 +297,46 @@ _ = instance See [Examples](/docs/examples). + + + +`get` before retry so you know whether the sandbox exists (lookup by name). + + + + +```ts +try { + await client.create(request); +} catch (error) { + if (knownName) { + const sandbox = await client.get(knownName); + console.log(sandbox.info()); + } + throw error; +} +``` + + + + +```go +instance, err := client.Create(ctx, opts) +if err != nil { + if knownName != "" { + existing, getErr := client.Get(ctx, knownName) + if getErr == nil { + fmt.Println(existing.Info()) + } + } + return err +} +_ = instance +``` + + + + +See [Examples](/docs/examples). + + diff --git a/website/content/docs/examples.mdx b/website/content/docs/examples.mdx index f13d3be0..54ec82c4 100644 --- a/website/content/docs/examples.mdx +++ b/website/content/docs/examples.mdx @@ -431,36 +431,138 @@ go run . -| Example | Project | -| --------------- | ------- | -| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-sandbox) | -| Resources | [create-blaxel-with-resources](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-resources) | -| Labels | [create-blaxel-with-labels](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-labels) | -| TTL | [create-blaxel-with-ttl](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-ttl) | -| Idle delete | [create-blaxel-idle](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-idle) | -| Client timeout | [create-blaxel-client-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-client-timeout) | -| Ports | [create-blaxel-with-ports](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-ports) | -| Outbound domains| [create-blaxel-with-allowlists](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists) | +| Example | Project | +| ------- | ------- | +| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-sandbox) | +| Resources | [create-blaxel-with-resources](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-with-resources) | +| Labels | [create-blaxel-with-labels](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-with-labels) | +| TTL | [create-blaxel-with-ttl](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-with-ttl) | +| Idle delete | [create-blaxel-idle](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-idle) | +| Client timeout | [create-blaxel-client-timeout](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-client-timeout) | +| Ports | [create-blaxel-with-ports](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-with-ports) | +| Outbound domains | [create-blaxel-with-allowlists](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists) | -| Example | Project | -| --------------- | ------- | -| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-sandbox) | -| Resources | [create-blaxel-with-resources](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-resources) | -| Labels | [create-blaxel-with-labels](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-labels) | -| TTL | [create-blaxel-with-ttl](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-ttl) | -| Idle delete | [create-blaxel-idle](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-idle) | -| Client timeout | [create-blaxel-client-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-client-timeout) | -| Ports | [create-blaxel-with-ports](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-ports) | -| Outbound domains| [create-blaxel-with-allowlists](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-allowlists) | +| Example | Project | +| ------- | ------- | +| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-sandbox) | +| Resources | [create-blaxel-with-resources](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-with-resources) | +| Labels | [create-blaxel-with-labels](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-with-labels) | +| TTL | [create-blaxel-with-ttl](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-with-ttl) | +| Idle delete | [create-blaxel-idle](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-idle) | +| Client timeout | [create-blaxel-client-timeout](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-client-timeout) | +| Ports | [create-blaxel-with-ports](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-with-ports) | +| Outbound domains | [create-blaxel-with-allowlists](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-with-allowlists) | -### Process / Filesystem / Lifecycle / Errors +### Process + + + + +| Example | Project | +| ------- | ------- | +| Run command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-command) | +| Shell command | [run-blaxel-shell-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-shell-command) | +| Run with cwd | [run-blaxel-with-cwd](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-with-cwd) | +| Run with env | [run-blaxel-with-env](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-with-env) | +| Run with timeout | [run-blaxel-with-timeout](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-with-timeout) | +| Check exit | [run-blaxel-check-exit](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-check-exit) | + + + + +| Example | Project | +| ------- | ------- | +| Run command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-command) | +| Shell command | [run-blaxel-shell-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-shell-command) | +| Run with cwd | [run-blaxel-with-cwd](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-with-cwd) | +| Run with env | [run-blaxel-with-env](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-with-env) | +| Run with timeout | [run-blaxel-with-timeout](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-with-timeout) | +| Check exit | [run-blaxel-check-exit](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-check-exit) | + + + + +### Filesystem + + + + +| Example | Project | +| ------- | ------- | +| Read file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/read-blaxel-file) | +| Write file | [write-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/write-blaxel-file) | +| Copy in | [copy-file-into-blaxel](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/copy-file-into-blaxel) | +| Copy out | [copy-file-from-blaxel](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/copy-file-from-blaxel) | +| Directory | [make-blaxel-directory](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/make-blaxel-directory) | +| List | [list-blaxel-directory](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/list-blaxel-directory) | +| Stat | [stat-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/stat-blaxel-file) | +| Remove | [remove-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/remove-blaxel-file) | + + + + +| Example | Project | +| ------- | ------- | +| Read file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/read-blaxel-file) | +| Write file | [write-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/write-blaxel-file) | +| Copy in | [copy-file-into-blaxel](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/copy-file-into-blaxel) | +| Copy out | [copy-file-from-blaxel](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/copy-file-from-blaxel) | +| Directory | [make-blaxel-directory](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/make-blaxel-directory) | +| List | [list-blaxel-directory](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/list-blaxel-directory) | +| Stat | [stat-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/stat-blaxel-file) | +| Remove | [remove-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/remove-blaxel-file) | + + + -Indexes: [TypeScript](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel) · [Go](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel). Full catalogs live in [examples/README.md](https://github.com/sandbox-kit/kit/blob/main/examples/README.md). +### Lifecycle + + + + +| Example | Project | +| ------- | ------- | +| Get | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox) | +| Refresh | [refresh-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox) | +| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox) | +| Close handle | [close-blaxel-handle](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/lifecycle/close-blaxel-handle) | + + + + +| Example | Project | +| ------- | ------- | +| Get | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/get-blaxel-sandbox) | +| Refresh | [refresh-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox) | +| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/delete-blaxel-sandbox) | +| Close handle | [close-blaxel-handle](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/close-blaxel-handle) | + + + + +### Errors + + + + +| Example | Project | +| ------- | ------- | +| Errors | [handle-blaxel-errors](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/errors/handle-blaxel-errors) | + + + + +| Example | Project | +| ------- | ------- | +| Errors | [handle-blaxel-errors](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/errors/handle-blaxel-errors) | + + + diff --git a/website/content/docs/filesystem.mdx b/website/content/docs/filesystem.mdx index f3ac43a0..f3ff5d19 100644 --- a/website/content/docs/filesystem.mdx +++ b/website/content/docs/filesystem.mdx @@ -112,6 +112,34 @@ data, err := instance.Filesystem().ReadFile(ctx, "/tmp/note.txt", &sandbox.FileR + + + + + +Timeouts are milliseconds. File calls use the abort signal / Kit deadline. `1500` ms ends at 1.5s. + +```ts +await sandbox.filesystem.readFile("/tmp/note.txt", { timeout: 1_500 }); +``` + + + + +Every Go file call uses the context deadline. + +```go +data, err := instance.Filesystem().ReadFile(ctx, "/tmp/note.txt", &sandbox.FileReadOptions{ + Timeout: sandbox.Duration(1500 * time.Millisecond), +}) +``` + + + + + + + ## Read a file Returns the bytes of one file. A missing file is not found. A directory path fails at the provider. @@ -219,6 +247,11 @@ err := instance.Filesystem().CopyToLocal(ctx, "/tmp/note.txt", "note.txt", nil) parent must exist and the new directory must not. + + Blaxel creates missing parents when `parents` is omitted or true, and rejects + `parents: false` before the call (field `parents`). + + diff --git a/website/content/docs/index.mdx b/website/content/docs/index.mdx index feb585a2..eb6882f1 100644 --- a/website/content/docs/index.mdx +++ b/website/content/docs/index.mdx @@ -39,6 +39,14 @@ npm install @sandbox-kit/sandbox @sandbox-kit/daytona npm install @sandbox-kit/sandbox @sandbox-kit/modal ``` + + + + +```sh +npm install @sandbox-kit/sandbox @sandbox-kit/blaxel +``` + @@ -60,6 +68,15 @@ go get github.com/sandbox-kit/kit/sdks/go/sandbox@latest go get github.com/sandbox-kit/kit/sdks/go/providers/modal@latest ``` + + + + +```sh +go get github.com/sandbox-kit/kit/sdks/go/sandbox@latest +go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest +``` + @@ -73,6 +90,10 @@ go get github.com/sandbox-kit/kit/sdks/go/providers/modal@latest Set a Modal token pair and app name ([Modal](/docs/modal)), then: + + Set `BL_API_KEY`, `BL_WORKSPACE`, and a client `region` ([Blaxel](/docs/blaxel)), then: + + @@ -121,6 +142,29 @@ const sandbox = await client.create({ console.log(sandbox.id); ``` + + + + +```ts +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region: process.env.BL_REGION || "us-pdx-1", + timeout: 120_000, // default client deadline (ms) +}); + +const sandbox = await client.create({ + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, +}); +console.log(sandbox.id); +``` + @@ -211,6 +255,53 @@ func main() { } ``` + + + + +```go +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func main() { + client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), + }) + if err != nil { + log.Fatal(err) + } + defer client.Close(context.Background()) + + instance, err := client.Create(context.Background(), &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + }) + if err != nil { + log.Fatal(err) + } + fmt.Println(instance.ID()) +} +``` + @@ -220,6 +311,7 @@ func main() { Provider setup: [Daytona](/docs/daytona). Provider setup: [Modal](/docs/modal). +Provider setup: [Blaxel](/docs/blaxel). ## 3. Run a command @@ -306,5 +398,6 @@ Sandbox paths are absolute. More: [Filesystem](/docs/filesystem). | Types and methods | [SDK reference](/docs/sdk) · [Client](/docs/sdk/client) · [Sandbox](/docs/sdk/sandbox) | | Daytona setup | [Daytona](/docs/daytona) | | Modal setup | [Modal](/docs/modal) | +| Blaxel setup | [Blaxel](/docs/blaxel) | | Auth, region, timeouts | [Configuration](/docs/configuration) | | Runnable example projects | [Examples](/docs/examples) | diff --git a/website/content/docs/lifecycle.mdx b/website/content/docs/lifecycle.mdx index 40a9da20..b0481e82 100644 --- a/website/content/docs/lifecycle.mdx +++ b/website/content/docs/lifecycle.mdx @@ -43,6 +43,19 @@ Daytona may still reject a supported op for a given sandbox. Snippets assume a c + + +| Operation | Support | +| ------------------------------ | ------------------------------------------ | +| Open existing | [`get`](#open-a-sandbox) (lookup by name) | +| start / stop / pause / archive | Unsupported | +| Destroy | [`delete`](#delete) | +| Local disconnect | [`close`](#close-the-client) | + +Snippets assume a constructed client — [Blaxel](/docs/blaxel). + + + ## Open a sandbox @@ -112,6 +125,40 @@ if err != nil { + + +`get` loads a sandbox that already exists by **name** (Blaxel id is the name). `refresh` loads it again. `attach` is not available. + + + + +```ts +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +const info = await sandbox.refresh(); +console.log(info); +``` + + + + +```go +import "os" + +instance, err := client.Get(ctx, os.Getenv("BL_SANDBOX_NAME")) +if err != nil { + return err +} +if err := instance.Refresh(ctx); err != nil { + return err +} +fmt.Println(instance.Info()) +``` + + + + + + ## Stop, start, pause, and archive @@ -217,6 +264,47 @@ if err := instance.Delete(ctx, &sandbox.DeleteOptions{ + + +## Delete + +`delete` removes a Blaxel sandbox. + + + + +```ts +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); +await sandbox.delete({ timeout: 60_000 }); +``` + + + + +```go +import ( + "os" + "time" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +instance, err := client.Get(ctx, os.Getenv("BL_SANDBOX_NAME")) +if err != nil { + return err +} +if err := instance.Delete(ctx, &sandbox.DeleteOptions{ + Timeout: sandbox.Duration(time.Minute), +}); err != nil { + return err +} +``` + + + + + + ## Terminate @@ -318,6 +406,11 @@ state. See [Errors](/docs/errors). `refresh` or `get` on Modal. + + After a lifecycle call, call `refresh` (or `get` again) before assuming the + sandbox is gone. + + @@ -336,6 +429,15 @@ const refreshed = await sandbox.refresh(); console.log(refreshed); ``` + + + + +```ts +const refreshed = await sandbox.refresh(); +console.log(refreshed); +``` + @@ -358,6 +460,17 @@ if err := instance.Refresh(ctx); err != nil { fmt.Println(instance.Info()) ``` + + + + +```go +if err := instance.Refresh(ctx); err != nil { + return err +} +fmt.Println(instance.Info()) +``` + diff --git a/website/content/docs/process.mdx b/website/content/docs/process.mdx index 30edebab..d5cbf014 100644 --- a/website/content/docs/process.mdx +++ b/website/content/docs/process.mdx @@ -151,6 +151,44 @@ fmt.Println(result) + + + + +| You pass | On Blaxel | +| ---------------------- | ----------------------------- | +| `argv` | POSIX-quoted shell string | +| `shell` | shell string as-is | +| omit / zero timeout | no command limit | +| fractional (`1500` ms) | rejected (`timeout`) | + +Blaxel returns both `stdout` and `stderr`. Runs are blocking. + + + + +```ts +const result = await sandbox.process.run({ + argv: ["echo", "hello"], // sent as a POSIX-quoted shell string +}); +// result: { exitCode, stdout, stderr } +console.log(result); +``` + + + + +```go +result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ + Argv: []string{"echo", "hello"}, // sent as a POSIX-quoted shell string +}) +// result: ExitCode, Stdout, Stderr +fmt.Println(result) +``` + + + + A non-zero exit code is **not** an SDK error. diff --git a/website/content/docs/sdk/client.mdx b/website/content/docs/sdk/client.mdx index dd6812fb..9c562ee4 100644 --- a/website/content/docs/sdk/client.mdx +++ b/website/content/docs/sdk/client.mdx @@ -141,6 +141,21 @@ const sandbox = await client.create( console.log(sandbox.id); ``` + + + + +```ts +const sandbox = await client.create( + { + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, + }, + { timeout: 60_000 }, // deadline for this call (ms) +); +console.log(sandbox.id); +``` + @@ -193,6 +208,22 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ }) ``` + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Provisioning: &sandbox.ProvisioningOptions{ + Timeout: sandbox.Duration(60 * time.Second), // deadline for this call + }, +}) +``` + @@ -264,6 +295,45 @@ fmt.Println(instance.Info()) + + +### get + +Looks up a sandbox by **name** (Blaxel id is the name) and returns a verified handle. + + + + +```ts +get(id: string, options?: OperationOptions): Promise +``` + +**Example** + +```ts +const sandbox = await client.get(knownName); +console.log(sandbox.info()); +``` + + + + +```go +func (c *Client) Get(ctx context.Context, id string) (*Sandbox, error) +``` + +**Example** + +```go +instance, err := client.Get(ctx, knownName) +fmt.Println(instance.Info()) +``` + + + + + + ### attach diff --git a/website/content/docs/sdk/configuration.mdx b/website/content/docs/sdk/configuration.mdx index fcca0720..e4903e62 100644 --- a/website/content/docs/sdk/configuration.mdx +++ b/website/content/docs/sdk/configuration.mdx @@ -47,6 +47,12 @@ await using client = new Client({ omits its own timeout, Daytona still uses a **60s** default on its side. + + Blaxel uses the Kit deadline only (no separate native create-wait timeout). + `region` is required for create. `endpoint` is unsupported. TypeScript + credentials go through process-global `@blaxel/core` `settings`. + + @@ -87,6 +93,11 @@ client, err := sandbox.NewClient(provider, &sandbox.Config{ omits its own timeout, Daytona still uses a **60s** default on its side. + + Blaxel uses the Kit deadline only (no separate native create-wait timeout). + `Region` is required for create. `Endpoint` is unsupported. + + diff --git a/website/content/docs/sdk/create-options.mdx b/website/content/docs/sdk/create-options.mdx index 672adb69..89f85b47 100644 --- a/website/content/docs/sdk/create-options.mdx +++ b/website/content/docs/sdk/create-options.mdx @@ -71,6 +71,18 @@ const sandbox = await client.create({ }); ``` + + + + +```ts +const sandbox = await client.create({ + name: "kit-example", + source: { image: { reference: "blaxel/base-image:latest" } }, + resources: { memoryMib: 4096n }, +}); +``` + @@ -146,6 +158,22 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ }) ``` + + + + +```go +instance, err := client.Create(ctx, &sandbox.CreateOptions{ + Name: sandbox.Value("kit-example"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}, + }}, + Resources: &sandbox.Resources{ + MemoryMib: sandbox.Value(uint64(4096)), + }, +}) +``` + diff --git a/website/content/docs/sdk/process.mdx b/website/content/docs/sdk/process.mdx index bc0eac08..1e64619b 100644 --- a/website/content/docs/sdk/process.mdx +++ b/website/content/docs/sdk/process.mdx @@ -123,6 +123,12 @@ Outcome of a finished command. explicit 0). + + Blaxel returns both `stdout` and `stderr`. `argv` is sent as a POSIX-quoted + shell string. Omit/zero timeout means no Blaxel command limit. Runs are + blocking. + + @@ -144,5 +150,10 @@ Outcome of a finished command. explicit 0). + + Blaxel returns both Stdout and Stderr. Argv is sent as a POSIX-quoted shell + string. Omit/zero Timeout means no Blaxel command limit. Runs are blocking. + + diff --git a/website/content/docs/sdk/sandbox.mdx b/website/content/docs/sdk/sandbox.mdx index 0b1173b5..b22102c6 100644 --- a/website/content/docs/sdk/sandbox.mdx +++ b/website/content/docs/sdk/sandbox.mdx @@ -123,6 +123,16 @@ if (!sandbox.supports(LifecycleCapability.Terminate)) { } ``` + + + + +```ts +if (!sandbox.supports(LifecycleCapability.Delete)) { + throw new Error(`cannot delete ${sandbox.id}`); +} +``` + @@ -165,6 +175,16 @@ if !instance.Supports(sandbox.LifecycleCapabilityTerminate) { } ``` + + + + +```go +if !instance.Supports(sandbox.LifecycleCapabilityDelete) { + return fmt.Errorf("cannot delete %s", instance.ID()) +} +``` + @@ -428,6 +448,63 @@ If you omit the timeout (or pass `0`), Daytona still uses its own **60s** defaul + + +### Refresh + + + + +```ts +refresh(options?: OperationOptions): Promise +``` + +Reloads metadata from Blaxel by sandbox name. + + + + +```go +func (s *Sandbox) Refresh(ctx context.Context) error +``` + + + + +### Delete + + + + +```ts +delete(options?: DeleteOptions): Promise +``` + +Removes the Blaxel sandbox. + +```ts +await sandbox.delete({ timeout: 60_000 }); +``` + + + + +```go +func (s *Sandbox) Delete(ctx context.Context, options *DeleteOptions) error +``` + +```go +err := instance.Delete(ctx, &sandbox.DeleteOptions{ + Timeout: sandbox.Duration(time.Minute), +}) +``` + + + + + + + ### Terminate diff --git a/website/src/components/provider-context.tsx b/website/src/components/provider-context.tsx index b3d36068..c7c55244 100644 --- a/website/src/components/provider-context.tsx +++ b/website/src/components/provider-context.tsx @@ -159,7 +159,7 @@ export function ProviderSelect({ className }: { className?: string }) { } /** Pages that are already scoped to one provider — no page-level selector. */ -const PROVIDER_SCOPED_PAGES = new Set(["daytona", "modal"]); +const PROVIDER_SCOPED_PAGES = new Set(["daytona", "modal", "blaxel"]); export function DocsProviderSelect({ path }: { path: string }) { const slug = From fa0d26d3616c36b5fa73a3a861aae1d04fdce224 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 15:54:46 +0530 Subject: [PATCH 14/60] Close the Modal ForProvider pane in process docs. Co-authored-by: Cursor --- website/content/docs/process.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/process.mdx b/website/content/docs/process.mdx index d5cbf014..7576f136 100644 --- a/website/content/docs/process.mdx +++ b/website/content/docs/process.mdx @@ -151,7 +151,7 @@ fmt.Println(result) - + From 561610b2890be7fb59ed2c341132d833476bb874 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 16:16:23 +0530 Subject: [PATCH 15/60] Sync Blaxel TypeScript example locks to @blaxel/core 0.3.36. Example package.json/tsconfig already match Daytona/Modal for @types/node; refresh lock metadata after the provider pin so npm install stays clean. Co-authored-by: Cursor --- .../create-blaxel-client-timeout/package-lock.json | 2 +- .../blaxel/configuration/create-blaxel-idle/package-lock.json | 2 +- .../configuration/create-blaxel-sandbox/package-lock.json | 2 +- .../create-blaxel-with-allowlists/package-lock.json | 2 +- .../configuration/create-blaxel-with-labels/package-lock.json | 2 +- .../configuration/create-blaxel-with-ports/package-lock.json | 2 +- .../create-blaxel-with-resources/package-lock.json | 2 +- .../configuration/create-blaxel-with-ttl/package-lock.json | 2 +- .../blaxel/errors/handle-blaxel-errors/package-lock.json | 2 +- .../blaxel/filesystem/copy-file-from-blaxel/package-lock.json | 2 +- .../blaxel/filesystem/copy-file-into-blaxel/package-lock.json | 2 +- .../blaxel/filesystem/list-blaxel-directory/package-lock.json | 2 +- .../blaxel/filesystem/make-blaxel-directory/package-lock.json | 2 +- .../blaxel/filesystem/read-blaxel-file/package-lock.json | 2 +- .../blaxel/filesystem/remove-blaxel-file/package-lock.json | 2 +- .../blaxel/filesystem/stat-blaxel-file/package-lock.json | 2 +- .../blaxel/filesystem/write-blaxel-file/package-lock.json | 2 +- .../blaxel/lifecycle/close-blaxel-handle/package-lock.json | 2 +- .../blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json | 2 +- .../blaxel/lifecycle/get-blaxel-sandbox/package-lock.json | 2 +- .../blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json | 2 +- .../blaxel/process/run-blaxel-check-exit/package-lock.json | 2 +- .../blaxel/process/run-blaxel-command/package-lock.json | 2 +- .../blaxel/process/run-blaxel-shell-command/package-lock.json | 2 +- .../blaxel/process/run-blaxel-with-cwd/package-lock.json | 2 +- .../blaxel/process/run-blaxel-with-env/package-lock.json | 2 +- .../blaxel/process/run-blaxel-with-timeout/package-lock.json | 2 +- 27 files changed, 27 insertions(+), 27 deletions(-) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package-lock.json index 474e2db6..c55880f8 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-idle/package-lock.json index 6420f637..e7ed325c 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-idle/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package-lock.json index e60ca9fb..14bab028 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package-lock.json index ea081830..f964f393 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package-lock.json index 1785c040..2fe39f31 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package-lock.json index e8d0c3a2..19d718f5 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package-lock.json index 2bf7c3bc..6e67c188 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package-lock.json index b73004ec..41dd6004 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/package-lock.json b/examples/typescript/blaxel/errors/handle-blaxel-errors/package-lock.json index 9e0fc313..46edf3de 100644 --- a/examples/typescript/blaxel/errors/handle-blaxel-errors/package-lock.json +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package-lock.json b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package-lock.json index 09947371..1598b682 100644 --- a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package-lock.json +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package-lock.json b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package-lock.json index a2e0b739..268fc7cb 100644 --- a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package-lock.json +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/package-lock.json b/examples/typescript/blaxel/filesystem/list-blaxel-directory/package-lock.json index afb02448..afcb97eb 100644 --- a/examples/typescript/blaxel/filesystem/list-blaxel-directory/package-lock.json +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/package-lock.json b/examples/typescript/blaxel/filesystem/make-blaxel-directory/package-lock.json index a34f7706..1b3043b7 100644 --- a/examples/typescript/blaxel/filesystem/make-blaxel-directory/package-lock.json +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/read-blaxel-file/package-lock.json index d48295f4..75ac891e 100644 --- a/examples/typescript/blaxel/filesystem/read-blaxel-file/package-lock.json +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/remove-blaxel-file/package-lock.json index 00e6ae36..19bf527f 100644 --- a/examples/typescript/blaxel/filesystem/remove-blaxel-file/package-lock.json +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/stat-blaxel-file/package-lock.json index 862bce3a..49ce3e5a 100644 --- a/examples/typescript/blaxel/filesystem/stat-blaxel-file/package-lock.json +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/write-blaxel-file/package-lock.json index b69229c9..765ff996 100644 --- a/examples/typescript/blaxel/filesystem/write-blaxel-file/package-lock.json +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package-lock.json b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package-lock.json index ac06116d..53f310af 100644 --- a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package-lock.json +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json index c322b9f3..6136eaaa 100644 --- a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package-lock.json index bbd278e8..033c9404 100644 --- a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package-lock.json +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json index 30a19e95..0eb80f4a 100644 --- a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-check-exit/package-lock.json index 5432501d..d25cf0d2 100644 --- a/examples/typescript/blaxel/process/run-blaxel-check-exit/package-lock.json +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/process/run-blaxel-command/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-command/package-lock.json index ba25286d..b5c2a53b 100644 --- a/examples/typescript/blaxel/process/run-blaxel-command/package-lock.json +++ b/examples/typescript/blaxel/process/run-blaxel-command/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-shell-command/package-lock.json index a29ca0d3..df701949 100644 --- a/examples/typescript/blaxel/process/run-blaxel-shell-command/package-lock.json +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-with-cwd/package-lock.json index 4cf6ad51..9aeec1a8 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-cwd/package-lock.json +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-with-env/package-lock.json index a8656f8e..a7470d4c 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-env/package-lock.json +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-with-timeout/package-lock.json index 36fdd42c..cf7a9021 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-timeout/package-lock.json +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/package-lock.json @@ -24,7 +24,7 @@ "version": "0.1.7", "license": "MIT", "dependencies": { - "@blaxel/core": "0.3.35", + "@blaxel/core": "0.3.36", "@bufbuild/protobuf": "2.16.0", "@sandbox-kit/sandbox": "0.1.7" }, From 94e50099a955be673b5b37f6aec6733c50474d9d Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 17:10:09 +0530 Subject: [PATCH 16/60] Remove unused Blaxel provider-level env templates. Examples only load each project's local .env; document that godotenv and Node --env-file do not override shell env. Co-authored-by: Cursor --- examples/go/README.md | 5 +++-- examples/go/blaxel/.env.example | 4 ---- examples/typescript/README.md | 2 ++ examples/typescript/blaxel/.env.example | 4 ---- 4 files changed, 5 insertions(+), 10 deletions(-) delete mode 100644 examples/go/blaxel/.env.example delete mode 100644 examples/typescript/blaxel/.env.example diff --git a/examples/go/README.md b/examples/go/README.md index 041cd1c1..a4a6cd2b 100644 --- a/examples/go/README.md +++ b/examples/go/README.md @@ -13,8 +13,9 @@ cp .env.example .env go run . ``` -Skip copying if `.env` already exists. Programs load `.env` with `godotenv`; -shell variables win. The SDK does not load files. Modal app setup: +Skip copying if `.env` already exists. Programs load only that project’s +`.env` with `godotenv` (it does not override existing shell env). There is no +provider-level `.env`. The SDK does not load files. Modal app setup: [create-modal-sandbox](modal/configuration/create-modal-sandbox/README.md). Blaxel setup: [create-blaxel-sandbox](blaxel/configuration/create-blaxel-sandbox/README.md). diff --git a/examples/go/blaxel/.env.example b/examples/go/blaxel/.env.example deleted file mode 100644 index 80af365b..00000000 --- a/examples/go/blaxel/.env.example +++ /dev/null @@ -1,4 +0,0 @@ -BL_API_KEY= -BL_WORKSPACE= -# Optional region override (default us-pdx-1). -BL_REGION= diff --git a/examples/typescript/README.md b/examples/typescript/README.md index c74adcac..31e0523a 100644 --- a/examples/typescript/README.md +++ b/examples/typescript/README.md @@ -20,6 +20,8 @@ npm run build npm start ``` +`npm start` loads only that project’s `.env` via `--env-file-if-exists` (it +does not override existing shell env). There is no provider-level `.env`. Creation examples provision real cloud resources. Filesystem, process, and lifecycle examples act on the sandbox ID in `.env`. Closing a handle leaves the sandbox as it is. Closing the client closes your connection to Daytona, Modal, or Blaxel. Modal setup: diff --git a/examples/typescript/blaxel/.env.example b/examples/typescript/blaxel/.env.example deleted file mode 100644 index 80af365b..00000000 --- a/examples/typescript/blaxel/.env.example +++ /dev/null @@ -1,4 +0,0 @@ -BL_API_KEY= -BL_WORKSPACE= -# Optional region override (default us-pdx-1). -BL_REGION= From 0bd2db50dd1a7a52499acefd1860cd571c67881f Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 17:43:49 +0530 Subject: [PATCH 17/60] Align Blaxel create/process mappings with Blaxel defaults and docs. Leave Default port transport to Blaxel (HTTP), validate sandbox names, classify edge 504 as timeout, and correct user-facing docs that claimed otherwise. Co-authored-by: Cursor Entire-Checkpoint: 01M72P1YKGK73RQSKQ2J4Q8ARF --- docs/configuration.md | 45 ++++++++-------- docs/process.md | 14 ++--- docs/providers.md | 19 ++++--- docs/sandbox-creation.md | 36 ++++++------- .../create-blaxel-client-timeout/README.md | 4 +- .../create-blaxel-sandbox/README.md | 8 ++- .../create-blaxel-with-ports/README.md | 3 +- .../process/run-blaxel-with-timeout/README.md | 3 +- .../create-blaxel-client-timeout/README.md | 4 +- .../create-blaxel-sandbox/README.md | 6 ++- .../create-blaxel-with-ports/README.md | 3 +- .../process/run-blaxel-with-timeout/README.md | 3 +- sdks/go/providers/blaxel/README.md | 48 +++++++++-------- sdks/go/providers/blaxel/creation.go | 11 ++-- sdks/go/providers/blaxel/doc.go | 9 ++-- sdks/go/providers/blaxel/errors_test.go | 16 ++++++ sdks/go/providers/blaxel/process.go | 2 +- sdks/go/providers/blaxel/provider_checks.go | 11 +++- sdks/go/providers/blaxel/rejection_test.go | 25 +++++++++ sdks/go/sandbox/sandbox_data_contracts.go | 7 +-- sdks/typescript/providers/blaxel/README.md | 51 ++++++++++--------- .../providers/blaxel/src/blaxel.test.ts | 33 ++++++++++-- .../typescript/providers/blaxel/src/blaxel.ts | 1 + .../blaxel/src/checks/provider_checks.ts | 19 ++++++- .../typescript/providers/blaxel/src/errors.ts | 12 ++++- .../providers/blaxel/src/mappings/mappings.ts | 42 ++++++--------- .../providers/blaxel/src/rejection.test.ts | 17 +++++++ specs/providers/blaxel.yaml | 41 ++++++--------- website/content/docs/blaxel.mdx | 51 ++++++++++--------- website/content/docs/configuration.mdx | 35 ++++++------- website/content/docs/create.mdx | 28 +++++----- website/content/docs/process.mdx | 18 ++++--- website/content/docs/sdk/configuration.mdx | 12 +++-- website/content/docs/sdk/create-options.mdx | 8 +++ website/content/docs/sdk/enums.mdx | 8 +++ website/content/docs/sdk/process.mdx | 7 +-- 36 files changed, 406 insertions(+), 254 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 4fe294a9..c2a56f5d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -28,14 +28,14 @@ result. Closing one handle does not close the client. ## Configuration fields -| Go / TypeScript | Purpose | -| ----------------------- | ------------------------------------------------------------------- | +| Go / TypeScript | Purpose | +| ----------------------- | ---------------------------------------------------------------------------------------- | | Provider / `provider` | `daytona.New()` / `daytona()`, `modal.New()` / `modal()`, or `blaxel.New()` / `blaxel()` | -| `Auth` / `auth` | One credential shape. Omit it to use the provider's own credentials | -| `Scope` / `scope` | App, environment, organization, project, or workspace | -| `Endpoint` / `endpoint` | Provider API URL, where the provider supports one | -| `Region` / `region` | Provider target or default placement | -| `Timeout` / `timeout` | Default deadline. Go: `sandbox.Duration`. TypeScript: milliseconds | +| `Auth` / `auth` | One credential shape. Omit it to use the provider's own credentials | +| `Scope` / `scope` | App, environment, organization, project, or workspace | +| `Endpoint` / `endpoint` | Provider API URL, where the provider supports one | +| `Region` / `region` | Provider target or default placement | +| `Timeout` / `timeout` | Default deadline. Go: `sandbox.Duration`. TypeScript: milliseconds | TypeScript auth is one nested shape: `auth: { apiKey: { key } }`, `tokenPair`, `bearerToken`, or `oauthRefresh`. @@ -49,19 +49,19 @@ does not change the handle. The bindings use Modal v0.11.1, Daytona v0.225.0, `@blaxel/core@0.3.36`, and `github.com/blaxel-ai/sdk-go@v0.31.0`. -| Setting | Modal | Daytona | Blaxel | -| ---------------- | ---------------------------------------------------------- | --------------------------------- | --------------------------------- | -| API key | Unsupported | `APIKey` | `APIKey` | -| Token pair | `TokenID` and `TokenSecret` | Unsupported | Unsupported | -| Bearer/JWT token | Unsupported | `JWTToken`; organization required | Unsupported | -| OAuth refresh | Refresh token, client ID, and one client secret or JWT key | Unsupported | Unsupported | -| Endpoint | Unsupported | API URL | Unsupported | -| Region | Default sandbox region preference | Target | Required for create | -| App name | Required for app lookup | Unsupported | Unsupported | -| Environment | Client, app, and secret lookup environment | Unsupported | Unsupported | -| Organization ID | Unsupported | Organization | Unsupported | -| Project ID | Unsupported | Unsupported | Unsupported | -| Workspace | Unsupported | Unsupported | Required Blaxel workspace name | +| Setting | Modal | Daytona | Blaxel | +| ---------------- | ---------------------------------------------------------- | --------------------------------- | ------------------------------ | +| API key | Unsupported | `APIKey` | `APIKey` | +| Token pair | `TokenID` and `TokenSecret` | Unsupported | Unsupported | +| Bearer/JWT token | Unsupported | `JWTToken`; organization required | Unsupported | +| OAuth refresh | Refresh token, client ID, and one client secret or JWT key | Unsupported | Unsupported | +| Endpoint | Unsupported | API URL | Unsupported | +| Region | Default sandbox region preference | Target | Required for create | +| App name | Required for app lookup | Unsupported | Unsupported | +| Environment | Client, app, and secret lookup environment | Unsupported | Unsupported | +| Organization ID | Unsupported | Organization | Unsupported | +| Project ID | Unsupported | Unsupported | Unsupported | +| Workspace | Unsupported | Unsupported | Required Blaxel workspace name | A setting the provider does not support fails before the provider client is created. Omit `auth` when the provider should use credentials it already finds @@ -91,8 +91,9 @@ deadline or abort signal that arrives earlier still applies. Daytona create keeps its 60-second default when the timeout is omitted and when it is explicit zero. Daytona start, stop, pause, and delete use that 60-second default when the timeout is omitted, and treat explicit zero as no -operation timeout. Blaxel uses the Kit deadline only; it has no separate -native create-wait timeout. +operation timeout. Blaxel uses the Kit deadline for create. `@blaxel/core` +also accepts a native creation timeout (1–50 whole seconds); this provider +does not forward it. The Go Blaxel SDK has no equivalent argument. ## Credentials diff --git a/docs/process.md b/docs/process.md index 2385231e..cfbe93bc 100644 --- a/docs/process.md +++ b/docs/process.md @@ -66,16 +66,18 @@ code. | `timeout: 0` / `Duration(0)` | Do not add an extra client deadline for this call | | Positive timeout | Cap this call; must be a whole number of seconds (`5000` ms or `5 * time.Second` is fine; `1500` ms is rejected, field `timeout`) | -Omit or zero also means **no separate provider command limit**. Provider -quirks: +Omit or zero also means **no separate provider process auto-kill** (see +provider quirks). Provider quirks: - **Daytona** — if you omit a native timeout, Daytona defaults to 10 seconds. This client therefore uses timeout `0` for omit/zero so the command is not capped at 10 seconds. - **Modal** — omit/zero adds no Modal command limit. In TypeScript the client omits `timeoutMs` (sending `0` is rejected by Modal). -- **Blaxel** — omit/zero adds no Blaxel command limit. Timeouts must be whole - seconds. +- **Blaxel** — omit/zero adds no Blaxel process auto-kill. Timeouts must be + whole seconds. Blocking `run` uses `waitForCompletion`; Blaxel's edge still + ends that wait after about a minute (public docs) to two minutes (SDK), while + the remote command may keep running. Cancel behavior: @@ -122,8 +124,8 @@ result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ ## More examples -| Topic | Go Daytona | TypeScript Daytona | Go Modal | TypeScript Modal | Go Blaxel | TypeScript Blaxel | -| ------------------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Topic | Go Daytona | TypeScript Daytona | Go Modal | TypeScript Modal | Go Blaxel | TypeScript Blaxel | +| ------------------ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | Shell string | [run-daytona-shell-command](../examples/go/daytona/process/run-daytona-shell-command/README.md) | [run-daytona-shell-command](../examples/typescript/daytona/process/run-daytona-shell-command/README.md) | [run-modal-shell-command](../examples/go/modal/process/run-modal-shell-command/README.md) | [run-modal-shell-command](../examples/typescript/modal/process/run-modal-shell-command/README.md) | [run-blaxel-shell-command](../examples/go/blaxel/process/run-blaxel-shell-command/README.md) | [run-blaxel-shell-command](../examples/typescript/blaxel/process/run-blaxel-shell-command/README.md) | | Working directory | [run-daytona-with-cwd](../examples/go/daytona/process/run-daytona-with-cwd/README.md) | [run-daytona-with-cwd](../examples/typescript/daytona/process/run-daytona-with-cwd/README.md) | [run-modal-with-cwd](../examples/go/modal/process/run-modal-with-cwd/README.md) | [run-modal-with-cwd](../examples/typescript/modal/process/run-modal-with-cwd/README.md) | [run-blaxel-with-cwd](../examples/go/blaxel/process/run-blaxel-with-cwd/README.md) | [run-blaxel-with-cwd](../examples/typescript/blaxel/process/run-blaxel-with-cwd/README.md) | | Environment | [run-daytona-with-env](../examples/go/daytona/process/run-daytona-with-env/README.md) | [run-daytona-with-env](../examples/typescript/daytona/process/run-daytona-with-env/README.md) | [run-modal-with-env](../examples/go/modal/process/run-modal-with-env/README.md) | [run-modal-with-env](../examples/typescript/modal/process/run-modal-with-env/README.md) | [run-blaxel-with-env](../examples/go/blaxel/process/run-blaxel-with-env/README.md) | [run-blaxel-with-env](../examples/typescript/blaxel/process/run-blaxel-with-env/README.md) | diff --git a/docs/providers.md b/docs/providers.md index fee50dc2..07d94fb9 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -26,7 +26,6 @@ await using client = new Client({ }); ``` - ```go client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ Auth: &sandbox.AuthConfig{ @@ -50,15 +49,15 @@ await using client = new Client({ ## What each provider can do -| Operation | Modal | Daytona | Blaxel | -| ----------------------------------- | ---------------------------------- | ------------------------------------------ | ------------------------------------------- | -| Create | Registry image and an existing app | Default snapshot, named snapshot, or image | Named image create | -| Open an existing sandbox | `attach` (ID only) | `get` (provider lookup) | `get` / `refresh` by name | -| Start, stop, pause, archive | Unsupported | Supported | Unsupported | -| Delete | Unsupported | Supported | Supported | -| Terminate | Supported | Unsupported | Unsupported | -| Filesystem | Same eight shared methods | Same eight shared methods | Same eight shared methods | -| Process | Same blocking `Run` | Same blocking `Run` | Same blocking `Run` | +| Operation | Modal | Daytona | Blaxel | +| --------------------------- | ---------------------------------- | ------------------------------------------ | -------------------------------------- | +| Create | Registry image and an existing app | Default snapshot, named snapshot, or image | Named image create (Blaxel name rules) | +| Open an existing sandbox | `attach` (ID only) | `get` (provider lookup) | `get` / `refresh` by name | +| Start, stop, pause, archive | Unsupported | Supported | Unsupported | +| Delete | Unsupported | Supported | Supported | +| Terminate | Supported | Unsupported | Unsupported | +| Filesystem | Same eight shared methods | Same eight shared methods | Same eight shared methods | +| Process | Same blocking `Run` | Same blocking `Run` | Same blocking `Run` | `supports` reports whether the provider implements that operation. A missing operation fails before the provider call. Daytona still accepts or rejects a diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index 17d6317b..307a7452 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -10,19 +10,19 @@ Go and TypeScript integrations target Modal v0.11.1, Daytona v0.225.0, and Blaxel (`modal@0.11.1`, `@daytona/sdk@0.225.0`, `@blaxel/core@0.3.36` / `github.com/blaxel-ai/sdk-go@v0.31.0`). -| Setting | Modal | Daytona | Blaxel | -| ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | -| Source | Explicit registry image and an existing app | Default/specified snapshot or registry image | Named create; registry image required | -| Runtime | Entrypoint, absolute working directory, PTY | Toolbox language and user | Unsupported | -| Isolation | Default, gVisor container, Linux VM | Nested KVM flag; class inherited from source | Unsupported | -| Resources | Fractional CPU, CPU limit, memory request/limit | Whole CPU and GiB memory/disk; image source required for overrides | Memory 1:1 (`memory_mib`); CPU/disk overrides rejected | -| Placement | Cloud and region preferences | Client target; request placement unsupported | Client `region` required for create; request placement unsupported | -| Environment and labels | Environment map and tags | Environment map and labels | Environment map and labels | -| Security | Named secrets and workload identity | Egress-placeholder secrets and public access | Unsupported | -| Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | Ports (not 80/443/8080); outbound domains | -| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m) | -| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Kit client/create timeout only; Ready wait unsupported | -| Observability | Verbose output | Telemetry endpoint | Unsupported | +| Setting | Modal | Daytona | Blaxel | +| ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- | +| Source | Explicit registry image and an existing app | Default/specified snapshot or registry image | Named create (Blaxel name rules); registry image required | +| Runtime | Entrypoint, absolute working directory, PTY | Toolbox language and user | Unsupported | +| Isolation | Default, gVisor container, Linux VM | Nested KVM flag; class inherited from source | Unsupported | +| Resources | Fractional CPU, CPU limit, memory request/limit | Whole CPU and GiB memory/disk; image source required for overrides | Memory 1:1 (`memory_mib`); CPU/disk overrides rejected | +| Placement | Cloud and region preferences | Client target; request placement unsupported | Client `region` required for create; request placement unsupported | +| Environment and labels | Environment map and tags | Environment map and labels | Environment map and labels | +| Security | Named secrets and workload identity | Egress-placeholder secrets and public access | Unsupported | +| Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | Ports (not 80/443/8080; Default → Blaxel HTTP); outbound domains | +| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m) | +| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Kit client/create timeout; Blaxel native create timeout not forwarded; Ready wait unsupported | +| Observability | Verbose output | Telemetry endpoint | Unsupported | Kit has no portable volume fields; Blaxel volumes stay out of create the same way Daytona and Modal volumes do. @@ -50,12 +50,12 @@ field was omitted. Invalid combinations fail in the client before the provider call. A field the selected provider does not support fails there too. -| Resource | Modal | Daytona | Blaxel | -| ---------------- | ----------------------------------------------- | -------------------------------------------- | -------------------------------------------------------------- | -| CPU | Physical cores; minimum 0.125, precision 0.001 | Whole CPU cores | Derived by Blaxel from memory; Kit rejects CPU overrides | +| Resource | Modal | Daytona | Blaxel | +| ---------------- | ----------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------- | +| CPU | Physical cores; minimum 0.125, precision 0.001 | Whole CPU cores | Derived by Blaxel from memory; Kit rejects CPU overrides | | Memory | MiB; minimum request 128 | Whole GiB expressed as MiB | `memory_mib` / `memoryMib` passed 1:1; Blaxel labels the unit MB | -| Disk | Creation override unsupported | Whole GiB expressed as MiB | Creation override unsupported | -| Maximum lifetime | Positive whole seconds, up to 24 hours (TS: ms) | Whole-minute TTL (TS: ms); zero disables TTL | Blaxel TTL string (at least 5m when set) | +| Disk | Creation override unsupported | Whole GiB expressed as MiB | Creation override unsupported | +| Maximum lifetime | Positive whole seconds, up to 24 hours (TS: ms) | Whole-minute TTL (TS: ms); zero disables TTL | Blaxel TTL string (at least 5m when set) | Daytona request conversion divides MiB exactly by 1024. Allocated metadata converts GiB back to MiB. Snapshot creation inherits snapshot resources. diff --git a/examples/go/blaxel/configuration/create-blaxel-client-timeout/README.md b/examples/go/blaxel/configuration/create-blaxel-client-timeout/README.md index 2904afa3..be7ff9bc 100644 --- a/examples/go/blaxel/configuration/create-blaxel-client-timeout/README.md +++ b/examples/go/blaxel/configuration/create-blaxel-client-timeout/README.md @@ -8,8 +8,8 @@ Sets `Config.Timeout` / `timeout` on the shared client. This bounds local waitin ## Edge cases -- Create provisioning timeouts are not mapped on Go; use the client timeout instead. -- TypeScript can also pass a create `provisioning.timeout` (1–50 whole seconds). +- Kit `Timeout` / `provisioning.timeout` is the local create deadline. +- The Go Blaxel SDK has no separate creation-timeout argument. ## Run diff --git a/examples/go/blaxel/configuration/create-blaxel-sandbox/README.md b/examples/go/blaxel/configuration/create-blaxel-sandbox/README.md index dbb8fa46..a4f71ea7 100644 --- a/examples/go/blaxel/configuration/create-blaxel-sandbox/README.md +++ b/examples/go/blaxel/configuration/create-blaxel-sandbox/README.md @@ -4,11 +4,15 @@ Create one Blaxel sandbox from a registry image with an explicit name. Prints th ## What this means -Blaxel create requires a nonblank `name` and an image source. Kit calls native create (not create-if-not-exists). Region comes from the client config (`BL_REGION`, default `us-pdx-1`). Closing the Kit client only drops the local SDK connection. +Blaxel create requires a Blaxel-valid `name` (lowercase alphanumeric with +single hyphens, at most 49 characters) and an image source. Kit calls native +create (not create-if-not-exists). Region comes from the client config +(`BL_REGION`, default `us-pdx-1`). Closing the Kit client only drops the local +SDK connection. ## Edge cases -- Omitting `name` or `source.image` fails before Blaxel is called. +- Omitting `name` or `source.image`, or using an invalid name, fails before Blaxel is called. - A create error is not proof that no sandbox was provisioned. - Reclaim sandboxes with TTL / idle policies or `delete`. diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md b/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md index d86058c1..2138a42e 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md @@ -4,7 +4,8 @@ Create one Blaxel sandbox that publishes selected ports. ## What this means -Maps `network.ports` to Blaxel runtime ports. Default/plain → TCP, TLS → TLS, HTTP/2 over TLS → HTTP. +Maps `network.ports` to Blaxel runtime ports. Default leaves the protocol to +Blaxel (HTTP). Plain → TCP, TLS → TLS, HTTP/2 over TLS → HTTP. ## Edge cases diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/README.md b/examples/go/blaxel/process/run-blaxel-with-timeout/README.md index d07d3b78..e447091b 100644 --- a/examples/go/blaxel/process/run-blaxel-with-timeout/README.md +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/README.md @@ -4,7 +4,8 @@ Cap a Blaxel Process.Run with a whole-second timeout. ## What this means -Command timeouts must be whole seconds. Omit or zero for no Blaxel command limit. +Command timeouts must be whole seconds. Omit or zero for no Blaxel process +auto-kill; the blocking wait can still end at Blaxel's edge hold. ## Edge cases diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/README.md b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/README.md index 6c7b1a04..ec9a7b1f 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/README.md @@ -8,8 +8,8 @@ Sets `Config.Timeout` / `timeout` on the shared client. This bounds local waitin ## Edge cases -- Create provisioning timeouts are not mapped on Go; use the client timeout instead. -- TypeScript can also pass a create `provisioning.timeout` (1–50 whole seconds). +- Kit `timeout` / `provisioning.timeout` is the local create deadline. +- Blaxel's native creation timeout (1–50s on `@blaxel/core`) is not forwarded. ## Run diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md index b2a1e73a..34e61c14 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md @@ -4,14 +4,16 @@ Create one Blaxel sandbox from a registry image with an explicit name. Prints th ## What this means -Blaxel create requires a nonblank `name`, an image source, and client `region` +Blaxel create requires a Blaxel-valid `name` (lowercase alphanumeric with +single hyphens, at most 49 characters), an image source, and client `region` (examples read `BL_REGION`, default `us-pdx-1`). Kit calls native create (not create-if-not-exists). Closing the Kit client only drops the local SDK connection. ## Edge cases -- Omitting `name` or `source.image` fails before Blaxel is called. +- Omitting `name` or `source.image`, or using an invalid name, fails before + Blaxel is called. - A create error is not proof that no sandbox was provisioned. - Reclaim sandboxes with TTL / idle policies or `delete`. diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md index 96293ab9..edab67d4 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md @@ -4,7 +4,8 @@ Create one Blaxel sandbox that publishes selected ports. ## What this means -Maps `network.ports` to Blaxel runtime ports. Default/plain → TCP, TLS → TLS, HTTP/2 over TLS → HTTP. +Maps `network.ports` to Blaxel runtime ports. Default leaves the protocol to +Blaxel (HTTP). Plain → TCP, TLS → TLS, HTTP/2 over TLS → HTTP. ## Edge cases diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md b/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md index d32d8da5..394cb836 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md @@ -4,7 +4,8 @@ Cap a Blaxel Process.Run with a whole-second timeout. ## What this means -Command timeouts must be whole seconds. Omit or zero for no Blaxel command limit. +Command timeouts must be whole seconds. Omit or zero for no Blaxel process +auto-kill; the blocking wait can still end at Blaxel's edge hold. ## Edge cases diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index ef54b2d0..32b8556d 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -49,24 +49,26 @@ sandbox. Call `Delete`, or set a lifetime policy. ## Settings -| Setting | How to set it | -| --------- | -------------------------------------- | -| API key | `Auth` with `APIKey` | -| Workspace | `Scope.Workspace` | -| Region | `Config.Region` (required for create) | -| Timeout | `Config.Timeout` (create/op deadline) | +| Setting | How to set it | +| --------- | ------------------------------------- | +| API key | `Auth` with `APIKey` | +| Workspace | `Scope.Workspace` | +| Region | `Config.Region` (required for create) | +| Timeout | `Config.Timeout` (create/op deadline) | `Config.Endpoint` is not supported. Token pair, OAuth, and bearer auth are rejected. `Scope` fields other than `Workspace` are rejected. ## Operations -Create from a registry image with an explicit name. You can set labels, +Create from a registry image with an explicit name. The name must be lowercase +alphanumeric with single hyphens and at most 49 characters. You can set labels, environment variables, memory, ports, outbound domains, maximum lifetime, and idle terminate. Max-age TTL must be at least 5m; idle TTL must be at least 1m. -Ports 80, 443, and 8080 are rejected (Blaxel-reserved). Kit -`Config.Timeout` / create call timeout is the create deadline; Blaxel has no -separate native create-wait timeout. `WaitConditionReady` is rejected. +Ports 80, 443, and 8080 are rejected (Blaxel-reserved). Default port transport +leaves the protocol to Blaxel (HTTP); plain maps to TCP. Kit +`Config.Timeout` / create call timeout is the create deadline. The Go Blaxel +SDK has no separate creation-timeout argument. `WaitConditionReady` is rejected. On the handle: `Get`, `Refresh`, `Delete`, and `Close`. `Get` and `Refresh` look up the sandbox by name. `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and @@ -84,8 +86,10 @@ missing file passed to `CopyFromLocal` is not found, and the error field is `Process().Run` runs one command and waits for the result. Set exactly one of `Argv` or `Shell`. `Argv` is sent as a POSIX-quoted shell string. A non-zero exit code is success for the SDK—read `GetExitCode`. Blaxel returns stdout and -stderr. Omit or zero `Timeout` for no Blaxel command limit. Command timeouts -must be whole seconds. Cancel the context to stop waiting. +stderr. Omit or zero `Timeout` for no Blaxel process auto-kill. The blocking +wait can still end at Blaxel's edge hold (about one to two minutes) while the +command keeps running. Command timeouts must be whole seconds. Cancel the +context to stop waiting. `resources.memory_mib` maps 1:1 onto Blaxel's memory field. Blaxel labels that integer MB; Kit does not convert. Blaxel derives CPU from that value, so @@ -94,16 +98,16 @@ fields; Blaxel volumes are outside create. ## Examples -| Operation | Program | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | -| Create | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-sandbox) | -| Run a command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-command) | -| Shell / cwd / env / exit | [process examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process) | -| Read a file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/read-blaxel-file) | -| Filesystem | [filesystem examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem) | -| Look up | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/get-blaxel-sandbox) | -| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/delete-blaxel-sandbox) | -| All | [Go examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go) | +| Operation | Program | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | +| Create | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-sandbox) | +| Run a command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-command) | +| Shell / cwd / env / exit | [process examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process) | +| Read a file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/read-blaxel-file) | +| Filesystem | [filesystem examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem) | +| Look up | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/get-blaxel-sandbox) | +| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/delete-blaxel-sandbox) | +| All | [Go examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go) | pkg.go.dev also shows `ExampleNew_*` samples in this package for create, lifecycle, process, and filesystem. diff --git a/sdks/go/providers/blaxel/creation.go b/sdks/go/providers/blaxel/creation.go index b608c092..bc355550 100644 --- a/sdks/go/providers/blaxel/creation.go +++ b/sdks/go/providers/blaxel/creation.go @@ -241,11 +241,16 @@ func (c *creation) network() error { func blaxelPortProtocol(transport sandbox.PortTransport) (blaxel.PortProtocol, error) { switch transport { - case sandbox.PortTransportDefault, sandbox.PortTransportPlain: + case sandbox.PortTransportDefault: + // Kit Default leaves the protocol to the provider. Blaxel defaults to HTTP + // when protocol is omitted (omitzero on PortParam). + return "", nil + case sandbox.PortTransportPlain: return blaxel.PortProtocolTcp, nil case sandbox.PortTransportTLS: return blaxel.PortProtocolTls, nil case sandbox.PortTransportHTTP2TLS: + // Blaxel exposes HTTP (not H2). Kit HTTP/2+TLS maps to that HTTP protocol. return blaxel.PortProtocolHTTP, nil default: return "", providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldNetworkPortsTransport, "requested port transport is unsupported") @@ -257,8 +262,8 @@ func (c *creation) provisioning() error { return nil } config := cloneMessage(c.request.Provisioning) - // Client.Create already applies this as the context deadline. Blaxel has no - // separate native create-wait timeout on NewInstance. + // Client.Create already applies this as the context deadline. The Go Blaxel + // SDK's NewInstance has no separate creation-timeout argument. config.Timeout = nil if config.QueueTimeout != nil { return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldProvisioningQueueTimeout, "Blaxel queue timeout is unsupported") diff --git a/sdks/go/providers/blaxel/doc.go b/sdks/go/providers/blaxel/doc.go index 1c850d66..41906517 100644 --- a/sdks/go/providers/blaxel/doc.go +++ b/sdks/go/providers/blaxel/doc.go @@ -1,9 +1,10 @@ // Package blaxel connects the sandbox client to Blaxel. // -// Pass [New] to sandbox.NewClient. Create requires a sandbox name, an image -// source, and [sandbox.Config.Region]. Set Scope.Workspace and an API key, or -// omit them so the Blaxel SDK can use BL_API_KEY and BL_WORKSPACE from the -// environment. The backend implements shared filesystem operations through +// Pass [New] to sandbox.NewClient. Create requires a Blaxel-valid sandbox name +// (lowercase alphanumeric with single hyphens, at most 49 characters), an +// image source, and [sandbox.Config.Region]. Set Scope.Workspace and an API +// key, or omit them so the Blaxel SDK can use BL_API_KEY and BL_WORKSPACE from +// the environment. The backend implements shared filesystem operations through // [sandbox.SandboxFiles] and blocking command execution through // [sandbox.SandboxProcess]. Get and Refresh look up a sandbox by name. Delete // removes the Blaxel resource. Closing the client releases local provider diff --git a/sdks/go/providers/blaxel/errors_test.go b/sdks/go/providers/blaxel/errors_test.go index 98fb6568..89272f2e 100644 --- a/sdks/go/providers/blaxel/errors_test.go +++ b/sdks/go/providers/blaxel/errors_test.go @@ -52,3 +52,19 @@ func TestMapProviderErrorSandboxAlreadyExists(t *testing.T) { t.Fatalf("error = %v detail = %#v", mapped, detail) } } + +func TestMapProviderErrorGatewayTimeout(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes/demo/process") + if err != nil { + t.Fatal(err) + } + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusGatewayTimeout, + Request: &http.Request{Method: http.MethodPost, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusGatewayTimeout}, + }, "run") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindTimeout { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } +} diff --git a/sdks/go/providers/blaxel/process.go b/sdks/go/providers/blaxel/process.go index 223b8715..21d94645 100644 --- a/sdks/go/providers/blaxel/process.go +++ b/sdks/go/providers/blaxel/process.go @@ -94,7 +94,7 @@ func blaxelCommand(request *sandbox.ExecOptions) (string, error) { } // commandTimeout maps Kit ExecOptions.timeout to Blaxel's whole-second timeout. -// Nil and explicit zero become 0 so Blaxel adds no command limit. +// Nil and explicit zero become 0 so Blaxel adds no process auto-kill. func commandTimeout(operation string, value *durationpb.Duration) (time.Duration, error) { timeout, ok := sdk.WholeSecondDuration(value) if !ok { diff --git a/sdks/go/providers/blaxel/provider_checks.go b/sdks/go/providers/blaxel/provider_checks.go index e935277a..12f7c37c 100644 --- a/sdks/go/providers/blaxel/provider_checks.go +++ b/sdks/go/providers/blaxel/provider_checks.go @@ -8,6 +8,11 @@ import ( var providerEnvNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) +// Blaxel Metadata.Name: lowercase alphanumeric with single hyphens, max 49. +var providerSandboxNamePattern = regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$`) + +const providerSandboxNameMaxLen = 49 + // validateProviderCreate rejects creation settings this provider cannot honor. // It runs before the native create call. // @@ -17,9 +22,13 @@ func validateProviderCreate(request *sandbox.CreateOptions) error { if request == nil { return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldName, "name is required") } - if request.GetName() == "" { + name := request.GetName() + if name == "" { return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldName, "name is required") } + if len(name) > providerSandboxNameMaxLen || !providerSandboxNamePattern.MatchString(name) { + return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldName, "name must be lowercase alphanumeric with single hyphens, at most 49 characters") + } if request.GetSource().GetImage() == nil || request.GetSource().GetImage().Reference == "" { return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldSourceImage, "an explicit image source is required") } diff --git a/sdks/go/providers/blaxel/rejection_test.go b/sdks/go/providers/blaxel/rejection_test.go index 4d3c594e..5be68f73 100644 --- a/sdks/go/providers/blaxel/rejection_test.go +++ b/sdks/go/providers/blaxel/rejection_test.go @@ -22,6 +22,14 @@ func TestRejectedCreateSettings(t *testing.T) { kind: sandbox.ErrorKindInvalidArgument, field: sandbox.CreateFieldName, }, + { + name: "invalid name", + request: imaged(&sandbox.CreateOptions{ + Name: sandbox.Value("My_Sandbox"), + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldName, + }, { name: "missing image", request: &sandbox.CreateOptions{Name: sandbox.Value("sandbox")}, @@ -278,3 +286,20 @@ func TestMappedNetworkPortsAndDomains(t *testing.T) { t.Fatalf("Ports[1] = %#v", ports[1]) } } + +func TestDefaultPortOmitsProtocol(t *testing.T) { + plan, err := planCreate(namedImage(&sandbox.CreateOptions{ + Network: &sandbox.NetworkConfig{ + Ports: []*sandbox.PortBinding{ + {Port: 3000, Transport: sandbox.PortTransportDefault}, + }, + }, + }), "us-pdx-1") + if err != nil { + t.Fatal(err) + } + ports := plan.params.Sandbox.Spec.Runtime.Ports + if len(ports) != 1 || ports[0].Target != 3000 || ports[0].Protocol != "" { + t.Fatalf("Ports = %#v", ports) + } +} diff --git a/sdks/go/sandbox/sandbox_data_contracts.go b/sdks/go/sandbox/sandbox_data_contracts.go index e5082fc9..ff6a8a25 100644 --- a/sdks/go/sandbox/sandbox_data_contracts.go +++ b/sdks/go/sandbox/sandbox_data_contracts.go @@ -152,9 +152,10 @@ type ( // ExecOptions configures Process.Run. Set exactly one of Argv or Shell. // An omitted Timeout uses Config.Timeout as the call deadline. Explicit // zero adds no extra deadline for this call. Both omit and zero also mean - // no provider command limit (Daytona uses 0 so its 10s native default does - // not apply; Modal adds no command limit). Command timeouts must be whole - // seconds. + // no provider process auto-kill (Daytona uses 0 so its 10s native default + // does not apply; Modal adds no command limit; Blaxel adds no auto-kill, + // though its blocking wait can still end at the edge hold). Command + // timeouts must be whole seconds. ExecOptions = sandboxv1.ExecOptions // ExecResult is the outcome of Process.Run. A non-zero ExitCode is not an error. diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index 7e920b6d..ba455013 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -39,28 +39,30 @@ TTL / idle policies or `delete`. ## Configuration -| Setting | Notes | -| -------------- | ------------------------------------------------------------- | -| API key | `auth: { apiKey: { key } }` | -| Omitted `auth` | Blaxel SDK environment / CLI credentials | -| Workspace | `scope.workspace` — Blaxel workspace name | -| Region | `region` — required for create | -| Endpoint | Not supported | +| Setting | Notes | +| -------------- | ----------------------------------------- | +| API key | `auth: { apiKey: { key } }` | +| Omitted `auth` | Blaxel SDK environment / CLI credentials | +| Workspace | `scope.workspace` — Blaxel workspace name | +| Region | `region` — required for create | +| Endpoint | Not supported | Token-pair, bearer, and OAuth client modes are rejected. Scope fields other than `workspace` are rejected. ## What you can do -**Create** requires a nonblank `name`, an image source, and client `region`. +**Create** requires a Blaxel-valid `name` (lowercase alphanumeric with single +hyphens, at most 49 characters), an image source, and client `region`. Optional `memoryMib` maps 1:1 onto Blaxel's memory field (Blaxel labels that integer MB; Kit does not convert; Blaxel CPU follows memory), labels, environment variables, ports, outbound domains, max-age TTL (at least 5m), and idle-delete policy (at least 1m; explicit disable is rejected). Client / -create `timeout` is the Kit deadline; Blaxel has no separate native -create-wait timeout. `provisioning.waitFor` Ready is unsupported. Creation -calls native `SandboxInstance.create`, not `createIfNotExists`. Ports 80, -443, and 8080 are rejected (Blaxel-reserved). Kit has no portable volume +create `timeout` is the Kit deadline. `@blaxel/core`'s native creation timeout +(1–50s) is not forwarded. `provisioning.waitFor` Ready is unsupported. +Creation calls native `SandboxInstance.create`, not `createIfNotExists`. Ports +80, 443, and 8080 are rejected (Blaxel-reserved). Default port transport leaves +the protocol to Blaxel (HTTP); plain maps to TCP. Kit has no portable volume fields; Blaxel volumes are outside create. **Lifecycle** on the handle: `get` / `refresh` (lookup by name), `delete`, and @@ -79,9 +81,10 @@ missing local parent directory. `shell`. `argv` is sent as a POSIX-quoted shell string. Runs are blocking (`waitForCompletion: true`). A non-zero exit code is not an SDK error—check `exitCode`. Blaxel returns stdout and stderr. Omit or zero `timeout` for no -command limit. Command timeouts must be whole seconds (`2000` is two seconds; -`1500` is rejected). Aborting the signal rejects the `await`; the remote -command may keep running. +process auto-kill. The blocking wait can still end at Blaxel's edge hold +(about one to two minutes) while the command keeps running. Command timeouts +must be whole seconds (`2000` is two seconds; `1500` is rejected). Aborting +the signal rejects the `await`; the remote command may keep running. Omitted memory keeps the Blaxel default. CPU, CPU limit, memory limit, and disk overrides are rejected. @@ -92,15 +95,15 @@ overwrites the first. ## Examples -| Example | Project | -| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-sandbox) | -| Run a command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-command) | -| Process | [process examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process) | -| Read a file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/read-blaxel-file) | -| Filesystem | [filesystem examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem) | -| Retrieval | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox) | -| Full index | [TypeScript examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript) | +| Example | Project | +| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-sandbox) | +| Run a command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-command) | +| Process | [process examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process) | +| Read a file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/read-blaxel-file) | +| Filesystem | [filesystem examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem) | +| Retrieval | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox) | +| Full index | [TypeScript examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript) | ## License diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 6fe5eab7..4cfdb8cc 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -278,6 +278,32 @@ describe("Blaxel mappings", () => { }); }); + it("classifies edge gateway 504 as timeout", () => { + const native = new ResponseError( + new Response(null, { status: 504, statusText: "Gateway Timeout" }), + { error: { message: "gateway timeout" } }, + new Error("gateway timeout"), + ); + expect(mapBlaxelError(native, "run")).toMatchObject({ + kind: ErrorKind.Timeout, + provider: "blaxel", + operation: "run", + cause: native, + }); + }); + + it("omits protocol for default port transport", () => { + const plan = mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + network: { ports: [{ port: 3000 }] }, + }, + provider, + ); + expect(plan.params.ports).toEqual([{ target: 3000 }]); + }); + it("applies environment checks before mapping", () => { expect(() => mapCreateRequest( @@ -339,9 +365,7 @@ describe("Blaxel mappings", () => { describe("Blaxel provider orchestration", () => { it("creates with SandboxInstance.create and never createIfNotExists", async () => { - let created: - | { params: unknown; options: unknown } - | undefined; + let created: { params: unknown; options: unknown } | undefined; let configured: unknown; const nativeSandbox = { metadata: { name: "example" }, @@ -586,7 +610,8 @@ describe("Blaxel provider orchestration", () => { }; if (request.waitForCompletion !== undefined) entry.waitForCompletion = request.waitForCompletion; - if (request.timeout !== undefined) entry.timeout = request.timeout; + if (request.timeout !== undefined) + entry.timeout = request.timeout; seen.push(entry); return { exitCode: 0, stdout: "ok", stderr: "" }; }, diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index c20bb79d..ed31cced 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -337,6 +337,7 @@ class BlaxelBackend extends BackendBase implements Backend { command: blaxelCommand(request), waitForCompletion: true, // Blaxel: omit/0 means no auto-kill. Kit omit/0 map to 0. + // waitForCompletion waits are still cut by Blaxel's edge hold. timeout: timeout ?? 0, ...(request.workingDirectory === undefined ? {} diff --git a/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts b/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts index e470357c..97480ff0 100644 --- a/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts +++ b/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts @@ -14,6 +14,10 @@ import { import type { CreateOptions } from "@sandbox-kit/sandbox"; import { providerError } from "#errors.js"; +// Blaxel Metadata.Name: lowercase alphanumeric with single hyphens, max 49. +const PROVIDER_SANDBOX_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; +const PROVIDER_SANDBOX_NAME_MAX = 49; + // validateProviderCreate rejects creation settings this provider cannot honor. // Rules are inventoried in specs/providers/blaxel.yaml checks when present. export function validateProviderCreate(request: CreateOptions): void { @@ -25,6 +29,18 @@ export function validateProviderCreate(request: CreateOptions): void { message: "name is required", }); } + if ( + request.name.length > PROVIDER_SANDBOX_NAME_MAX || + !PROVIDER_SANDBOX_NAME.test(request.name) + ) { + providerError({ + kind: ErrorKind.InvalidArgument, + operation: "create", + field: CreateFieldName, + message: + "name must be lowercase alphanumeric with single hyphens, at most 49 characters", + }); + } if (request.source?.image === undefined) { providerError({ kind: ErrorKind.InvalidArgument, @@ -49,8 +65,7 @@ export function validateProviderCreate(request: CreateOptions): void { kind: ErrorKind.Unsupported, operation: "create", field: CreateFieldPlacement, - message: - "per-request Blaxel placement is unsupported; use Config.region", + message: "per-request Blaxel placement is unsupported; use Config.region", }); } const resources = request.resources; diff --git a/sdks/typescript/providers/blaxel/src/errors.ts b/sdks/typescript/providers/blaxel/src/errors.ts index 5fb98b6e..2698ae3c 100644 --- a/sdks/typescript/providers/blaxel/src/errors.ts +++ b/sdks/typescript/providers/blaxel/src/errors.ts @@ -12,6 +12,7 @@ import { getBlaxelErrorStatus, isCreationTimeoutError, isGatewayError, + isGatewayTimeout, ResponseError, SandboxCreationTimeoutError, SandboxGatewayError, @@ -41,7 +42,10 @@ export interface ProviderErrorInput { message: string; } -export function mapBlaxelError(error: unknown, operation: string): SandboxError { +export function mapBlaxelError( + error: unknown, + operation: string, +): SandboxError { if (error instanceof SandboxError) return error; if (error instanceof DOMException && error.name === "AbortError") return create({ kind: ErrorKind.Canceled, error, operation }); @@ -55,9 +59,13 @@ export function mapBlaxelError(error: unknown, operation: string): SandboxError if (error instanceof CredentialsError) kind = ErrorKind.Authentication; if ( kind === ErrorKind.Unknown && - (error instanceof SandboxCreationTimeoutError || isCreationTimeoutError(error)) + (error instanceof SandboxCreationTimeoutError || + isCreationTimeoutError(error)) ) kind = ErrorKind.Timeout; + // Edge 504 (waitForCompletion hold / origin-read) is a timeout, matching Go. + if (kind === ErrorKind.Unknown && isGatewayTimeout(error)) + kind = ErrorKind.Timeout; if ( kind === ErrorKind.Unknown && (error instanceof SandboxGatewayError || isGatewayError(error)) diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index c5dab20b..dd7d6d18 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -64,9 +64,8 @@ export function mapCreateRequest( if (config.region === undefined || config.region.trim() === "") invalid(ConfigFieldRegion, "Config.region is required"); - const params: SandboxCreateConfiguration = generatedMapCommonCreateFields( - request, - ); + const params: SandboxCreateConfiguration = + generatedMapCommonCreateFields(request); params.name = nonblank(request.name!, CreateFieldName); params.image = nonblank( request.source!.image!.reference, @@ -119,10 +118,7 @@ function mapIsolation(r: CreateOptions): void { unsupported(CreateFieldIsolation, "Blaxel isolation class is not mapped"); } -function mapResources( - r: CreateOptions, - p: SandboxCreateConfiguration, -): void { +function mapResources(r: CreateOptions, p: SandboxCreateConfiguration): void { const v = r.resources; if (!v) return; Object.assign(p, generatedMapResources(v)); @@ -164,12 +160,10 @@ function mapNetwork(r: CreateOptions, p: SandboxCreateConfiguration): void { CreateFieldNetworkPortsPort, "ports 80, 443, and 8080 are reserved by Blaxel", ); - ports.push({ - target: port, - protocol: blaxelPortProtocol( - x.transport ?? PortTransport.Default, - ), - }); + const protocol = blaxelPortProtocol(x.transport ?? PortTransport.Default); + ports.push( + protocol === undefined ? { target: port } : { target: port, protocol }, + ); } p.ports = ports; } @@ -177,12 +171,11 @@ function mapNetwork(r: CreateOptions, p: SandboxCreateConfiguration): void { function blaxelPortProtocol( transport: PortTransport, -): NonNullable { - if ( - transport === PortTransport.Default || - transport === PortTransport.Plain - ) - return "TCP"; +): Port["protocol"] | undefined { + // Kit Default leaves the protocol to the provider. Blaxel defaults to HTTP + // when protocol is omitted (normalizePorts). + if (transport === PortTransport.Default) return undefined; + if (transport === PortTransport.Plain) return "TCP"; if (transport === PortTransport.Tls) return "TLS"; // Blaxel exposes HTTP (not H2). Kit HTTP/2+TLS maps to that HTTP protocol. if (transport === PortTransport.Http2Tls) return "HTTP"; @@ -194,10 +187,7 @@ function blaxelPortProtocol( function mapSecurity(r: CreateOptions): void { if (r.security !== undefined) - unsupported( - CreateFieldSecurity, - "Blaxel security settings are not mapped", - ); + unsupported(CreateFieldSecurity, "Blaxel security settings are not mapped"); } function mapLifetime(r: CreateOptions, p: SandboxCreateConfiguration): void { @@ -261,7 +251,8 @@ function mapObservability(r: CreateOptions): void { } // Kit Client.create applies provisioning.timeout as the abort deadline. -// Blaxel has no separate native create-wait timeout beyond that. +// @blaxel/core also accepts SandboxCreateOptions.timeout (1–50s); this +// provider does not forward it — use the Kit deadline instead. function mapProvisioning(r: CreateOptions): void { const v = r.provisioning; if (!v) return; @@ -317,8 +308,7 @@ function nonblank(v: string, f: string): string { function durationString(ms: number, field: string, minimumMs: number): string { if (!Number.isFinite(ms) || ms <= 0 || ms % 1_000 !== 0) invalid(field, "must be a whole number of seconds"); - if (ms < minimumMs) - invalid(field, "is below the Blaxel minimum duration"); + if (ms < minimumMs) invalid(field, "is below the Blaxel minimum duration"); const seconds = ms / 1_000; if (seconds % 86_400 === 0) return `${seconds / 86_400}d`; if (seconds % 3_600 === 0) return `${seconds / 3_600}h`; diff --git a/sdks/typescript/providers/blaxel/src/rejection.test.ts b/sdks/typescript/providers/blaxel/src/rejection.test.ts index c8db0aee..5fecf9cc 100644 --- a/sdks/typescript/providers/blaxel/src/rejection.test.ts +++ b/sdks/typescript/providers/blaxel/src/rejection.test.ts @@ -127,6 +127,23 @@ describe("rejected Blaxel create settings", () => { ); }); + it("invalid name", () => { + expect(() => + mapCreateRequest( + { + name: "My_Sandbox", + source: { image: { reference: "blaxel/base-image:latest" } }, + }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldName, + }), + ); + }); + it("missing image", () => { expect(() => mapCreateRequest({ name: "box" }, provider)).toThrowError( expect.objectContaining({ diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml index 0e7446ad..bf738e36 100644 --- a/specs/providers/blaxel.yaml +++ b/specs/providers/blaxel.yaml @@ -3,7 +3,12 @@ provider: blaxel # Provider create checks are handwritten in sdks/*/providers/blaxel (provider_checks). # Keep this list in sync with those implementations; it is not emitted. checks: - - { path: name, required: true } + - { + path: name, + required: true, + format: blaxel_sandbox_name, + notes: "lowercase alphanumeric with single hyphens, max 49", + } - { path: source.image, required: true } - { path: environment, format: env_name } - { path: placement, unsupported: true } @@ -13,16 +18,16 @@ checks: - { path: resources.disk_mib, unsupported: true } - { path: readiness, unsupported: true } - { path: provisioning.queue_timeout, unsupported: true } - - { path: provisioning.wait_for, unsupported: true, notes: "Ready and Started rejected; Default/Scheduled accepted" } + - { + path: provisioning.wait_for, + unsupported: true, + notes: "Ready and Started rejected; Default/Scheduled accepted", + } client: target: name: ClientOptions bindings: - go: - { - import: github.com/blaxel-ai/sdk-go/option, - name: RequestOption, - } + go: { import: github.com/blaxel-ai/sdk-go/option, name: RequestOption } typescript: { import: "@blaxel/core", name: ClientOptions } managed: [provider, timeout] retained: [region] @@ -55,8 +60,7 @@ runtime: capture: region, doc: "is the captured default region. Omission means create rejects with Config.region required.", } - cleanup: - { method: close, accepts_context: false, returns_error: false } + cleanup: { method: close, accepts_context: false, returns_error: false } # Mapping groups are handwritten in sdks/*/providers/blaxel (provider_mappings). # Keep this inventory in sync with those implementations; it is not emitted. groups: @@ -136,12 +140,7 @@ groups: name: SandboxCreateRequest fields: { memory: spec.runtime.memory } fields: - - { - from: memory_mib, - to: memory, - maximum: 2147483647, - cast: integer, - } + - { from: memory_mib, to: memory, maximum: 2147483647, cast: integer } - name: mapLifetimeDuration direction: request source: { name: LifetimePolicy } @@ -157,11 +156,7 @@ groups: name: SandboxCreateRequest fields: { ttl: spec.runtime.ttl } fields: - - { - from: maximum_lifetime, - to: ttl, - transform: duration_blaxel, - } + - { from: maximum_lifetime, to: ttl, transform: duration_blaxel } - name: mapIdleDuration error_path: lifetime.idle_terminate direction: request @@ -178,11 +173,7 @@ groups: name: SandboxCreateRequest fields: { lifecycle: spec.lifecycle.expirationPolicies } fields: - - { - from: after, - to: lifecycle, - transform: duration_blaxel_idle, - } + - { from: after, to: lifecycle, transform: duration_blaxel_idle } - name: mapSandboxInfo direction: response source: diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index 8437542e..da33d5f1 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -27,7 +27,9 @@ go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest ## Create -Set `BL_API_KEY` and `BL_WORKSPACE`. Create requires a name and an image. Set `region` on the client (default examples use `us-pdx-1`). +Set `BL_API_KEY` and `BL_WORKSPACE`. Create requires a Blaxel-valid name +(lowercase alphanumeric with single hyphens, at most 49 characters) and an +image. Set `region` on the client (default examples use `us-pdx-1`). @@ -110,13 +112,13 @@ Omit `auth` and `scope.workspace` to let the Blaxel SDK pick credentials from it -| Setting | Notes | -| -------------- | ------------------------------------------ | -| API key | `auth.apiKey` | -| Omitted `auth` | Blaxel SDK environment / CLI credentials | +| Setting | Notes | +| -------------- | ------------------------------------------------------------------------------- | +| API key | `auth.apiKey` | +| Omitted `auth` | Blaxel SDK environment / CLI credentials | | Workspace | `scope.workspace` — required for create when auth is set explicitly, or via env | -| Region | `region` — required for create | -| Endpoint | Unsupported | +| Region | `region` — required for create | +| Endpoint | Unsupported | ```ts await using client = new Client({ @@ -131,12 +133,12 @@ await using client = new Client({ -| Setting | Notes | -| --------- | ----------------------------------------- | -| API key | `Auth` with `APIKey` | -| Workspace | `Scope.Workspace` | -| Region | `Config.Region` — required for create | -| Endpoint | Unsupported | +| Setting | Notes | +| --------- | ------------------------------------- | +| API key | `Auth` with `APIKey` | +| Workspace | `Scope.Workspace` | +| Region | `Config.Region` — required for create | +| Endpoint | Unsupported | ```go config := &sandbox.Config{ @@ -159,19 +161,22 @@ Token-pair, bearer, and OAuth client modes are rejected. Scope fields other than ## What works -| Area | Notes | -| --------- | --------------------------------------------------------------------- | -| Create | Name + image; memory 1:1, labels, env, ports, outbound domains, TTL/idle | -| Volumes | Not in Kit create; no portable volume fields | -| Open | `get` / `refresh` by sandbox name | -| Lifecycle | `delete`, `close` — no start/stop/pause/archive/terminate/attach | -| Files | Filesystem methods — [Filesystem](/docs/filesystem) | -| Process | `run` — [Run a command](/docs/process) | +| Area | Notes | +| --------- | ------------------------------------------------------------------------------ | +| Create | Valid name + image; memory 1:1, labels, env, ports, outbound domains, TTL/idle | +| Volumes | Not in Kit create; no portable volume fields | +| Open | `get` / `refresh` by sandbox name | +| Lifecycle | `delete`, `close` — no start/stop/pause/archive/terminate/attach | +| Files | Filesystem methods — [Filesystem](/docs/filesystem) | +| Process | Blocking `run` — [Run a command](/docs/process) | `resources.memoryMib` / `memory_mib` maps 1:1 onto Blaxel's memory field. Blaxel labels that integer MB; Kit does not convert. Blaxel derives CPU from -memory, so CPU and disk overrides are rejected. Client `timeout` is the -create/operation deadline; Ready wait and readiness probes are unsupported. +memory, so CPU and disk overrides are rejected. Default port transport leaves +the protocol to Blaxel (HTTP); plain maps to TCP. Client `timeout` is the +Kit create/operation deadline. `@blaxel/core`'s native creation timeout +(1–50s) is not forwarded; the Go SDK has no equivalent. Ready wait and +readiness probes are unsupported. ## Related diff --git a/website/content/docs/configuration.mdx b/website/content/docs/configuration.mdx index 68bf4ffd..5d55b295 100644 --- a/website/content/docs/configuration.mdx +++ b/website/content/docs/configuration.mdx @@ -339,13 +339,13 @@ client, err := sandbox.NewClient(modal.New(), config) -| Setting | What to pass | -| -------------- | ---------------------------------------------- | -| API key | `auth.apiKey.key` | -| Omitted `auth` | Blaxel SDK environment / CLI credentials | -| Workspace | `scope.workspace` | -| Region | `region` — required for create | -| Endpoint | Unsupported | +| Setting | What to pass | +| -------------- | ---------------------------------------- | +| API key | `auth.apiKey.key` | +| Omitted `auth` | Blaxel SDK environment / CLI credentials | +| Workspace | `scope.workspace` | +| Region | `region` — required for create | +| Endpoint | Unsupported | ```ts await using client = new Client({ @@ -360,12 +360,12 @@ await using client = new Client({ -| Setting | What to pass | -| --------- | --------------------------------------------- | -| API key | `APIKey` | -| Workspace | `Scope.Workspace` | -| Region | `Config.Region` — required for create | -| Endpoint | Unsupported | +| Setting | What to pass | +| --------- | ------------------------------------- | +| API key | `APIKey` | +| Workspace | `Scope.Workspace` | +| Region | `Config.Region` — required for create | +| Endpoint | Unsupported | ```go config := &sandbox.Config{ @@ -528,10 +528,11 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ - Blaxel uses the Kit deadline only. It has no separate native create-wait - timeout. Ready wait and readiness probes are unsupported. TypeScript Blaxel - clients share `@blaxel/core` process-global `settings`: use one Blaxel Kit - client, or one credential set, per process. + Blaxel uses the Kit deadline for create. `@blaxel/core` also accepts a native + creation timeout (1–50 whole seconds); this provider does not forward it. The + Go Blaxel SDK has no equivalent argument. Ready wait and readiness probes are + unsupported. TypeScript Blaxel clients share `@blaxel/core` process-global + `settings`: use one Blaxel Kit client, or one credential set, per process. ## Credentials diff --git a/website/content/docs/create.mdx b/website/content/docs/create.mdx index cf01aa6b..fc95c0b0 100644 --- a/website/content/docs/create.mdx +++ b/website/content/docs/create.mdx @@ -269,14 +269,15 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ -| | Blaxel | -| --------- | --------------------------------------------------------------------- | -| Source | Registry image and nonblank `name` required | -| CPU | Derived by Blaxel from memory; Kit rejects CPU overrides | -| Memory | `memoryMib` / `memory_mib` passed 1:1; Blaxel labels the unit MB | -| Disk | Cannot be set | -| Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m) | -| Placement | Client `region` required. A placement block on this request is rejected | +| | Blaxel | +| --------- | ------------------------------------------------------------------------------------------------- | +| Source | Registry image and Blaxel-valid `name` required (lowercase alphanumeric with single hyphens, ≤49) | +| CPU | Derived by Blaxel from memory; Kit rejects CPU overrides | +| Memory | `memoryMib` / `memory_mib` passed 1:1; Blaxel labels the unit MB | +| Disk | Cannot be set | +| Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m) | +| Placement | Client `region` required. A placement block on this request is rejected | +| Ports | Default leaves protocol to Blaxel (HTTP); plain → TCP; 80/443/8080 reserved | Kit has no portable volume fields. @@ -324,7 +325,6 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ - ## Example @@ -608,13 +608,15 @@ See [Examples](/docs/examples). -Create needs a nonblank `name`, an image, and client `region`. Memory maps 1:1; -CPU, disk, placement, readiness, and volumes are rejected. Ports 80, 443, and -8080 are reserved. See [Examples](/docs/examples). +Create needs a Blaxel-valid `name` (lowercase alphanumeric with single hyphens, +at most 49 characters), an image, and client `region`. Memory maps 1:1; CPU, +disk, placement, readiness, and volumes are rejected. Ports 80, 443, and 8080 +are reserved. Default port transport leaves the protocol to Blaxel (HTTP). +Kit create timeout is the local deadline; Blaxel's native creation timeout is +not forwarded. See [Examples](/docs/examples). - ## Result Metadata from info is a **copy**. Mutating it does not change the sandbox. Reported CPU and memory can differ from what you requested. diff --git a/website/content/docs/process.mdx b/website/content/docs/process.mdx index 7576f136..b12a5704 100644 --- a/website/content/docs/process.mdx +++ b/website/content/docs/process.mdx @@ -155,14 +155,15 @@ fmt.Println(result) -| You pass | On Blaxel | -| ---------------------- | ----------------------------- | -| `argv` | POSIX-quoted shell string | -| `shell` | shell string as-is | -| omit / zero timeout | no command limit | -| fractional (`1500` ms) | rejected (`timeout`) | +| You pass | On Blaxel | +| ---------------------- | -------------------------------------------------------------------- | +| `argv` | POSIX-quoted shell string | +| `shell` | shell string as-is | +| omit / zero timeout | no process auto-kill; blocking wait still ends at Blaxel's edge hold | +| fractional (`1500` ms) | rejected (`timeout`) | -Blaxel returns both `stdout` and `stderr`. Runs are blocking. +Blaxel returns both `stdout` and `stderr`. Runs are blocking +(`waitForCompletion`). @@ -201,7 +202,8 @@ A non-zero exit code is **not** an SDK error. | `0` | No extra client deadline for this call | | Positive value | Cap this call; must be whole seconds (`5000` ok, `1500` rejected) | -Omit or zero also means no separate provider command limit. +Omit or zero also means no separate provider process auto-kill (see provider +tables above for Blaxel's blocking-wait edge hold). diff --git a/website/content/docs/sdk/configuration.mdx b/website/content/docs/sdk/configuration.mdx index e4903e62..30afbf65 100644 --- a/website/content/docs/sdk/configuration.mdx +++ b/website/content/docs/sdk/configuration.mdx @@ -48,9 +48,10 @@ await using client = new Client({ - Blaxel uses the Kit deadline only (no separate native create-wait timeout). - `region` is required for create. `endpoint` is unsupported. TypeScript - credentials go through process-global `@blaxel/core` `settings`. + Blaxel uses the Kit deadline for create. `@blaxel/core`'s native creation + timeout (1–50s) is not forwarded. `region` is required for create. `endpoint` + is unsupported. TypeScript credentials go through process-global + `@blaxel/core` `settings`. @@ -94,8 +95,9 @@ client, err := sandbox.NewClient(provider, &sandbox.Config{ - Blaxel uses the Kit deadline only (no separate native create-wait timeout). - `Region` is required for create. `Endpoint` is unsupported. + Blaxel uses the Kit deadline for create. The Go Blaxel SDK has no separate + creation-timeout argument. `Region` is required for create. `Endpoint` is + unsupported. diff --git a/website/content/docs/sdk/create-options.mdx b/website/content/docs/sdk/create-options.mdx index 89f85b47..5fa8f35a 100644 --- a/website/content/docs/sdk/create-options.mdx +++ b/website/content/docs/sdk/create-options.mdx @@ -219,6 +219,14 @@ Set exactly one variant. Unsupported variants fail before the provider call. and `idleTerminate` only. + + Blaxel requires an `image` source and a Blaxel-valid `name` (lowercase + alphanumeric with single hyphens, at most 49 characters). Client `region` is + required. Default port transport leaves the protocol to Blaxel (HTTP); plain + maps to TCP. Ports 80, 443, and 8080 are reserved. Kit create timeout is the + local deadline; `@blaxel/core`'s native creation timeout is not forwarded. + + ## RuntimeConfig diff --git a/website/content/docs/sdk/enums.mdx b/website/content/docs/sdk/enums.mdx index b79e3bd3..87a2cfd0 100644 --- a/website/content/docs/sdk/enums.mdx +++ b/website/content/docs/sdk/enums.mdx @@ -84,6 +84,14 @@ Transport for a published port. | `Http2Tls` | HTTP/2 over TLS | | `Plain` | Unencrypted / plain transport | + + Modal requires an explicit transport (`Default` is rejected). + + + Blaxel: `Default` omits protocol (Blaxel defaults to HTTP). `Plain` → TCP, + `Tls` → TLS, `Http2Tls` → HTTP. + + ## IsolationKind Sandbox isolation class. diff --git a/website/content/docs/sdk/process.mdx b/website/content/docs/sdk/process.mdx index 1e64619b..223775eb 100644 --- a/website/content/docs/sdk/process.mdx +++ b/website/content/docs/sdk/process.mdx @@ -125,8 +125,8 @@ Outcome of a finished command. Blaxel returns both `stdout` and `stderr`. `argv` is sent as a POSIX-quoted - shell string. Omit/zero timeout means no Blaxel command limit. Runs are - blocking. + shell string. Omit/zero timeout means no Blaxel process auto-kill; the + blocking wait can still end at Blaxel's edge hold. Runs are blocking. @@ -152,7 +152,8 @@ Outcome of a finished command. Blaxel returns both Stdout and Stderr. Argv is sent as a POSIX-quoted shell - string. Omit/zero Timeout means no Blaxel command limit. Runs are blocking. + string. Omit/zero Timeout means no Blaxel process auto-kill; the blocking wait + can still end at Blaxel's edge hold. Runs are blocking. From d4b7c452c468060cc769a65f81634d0524c4de6e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 10 Nov 2026 12:21:36 +0000 Subject: [PATCH 18/60] Map Blaxel POLICY_VIOLATION to invalid argument. Go already classifies sdkblaxel.ErrPolicyViolation as invalid argument. The TypeScript provider now does the same for that reserved gateway code. Co-authored-by: Nabhag Motivaras --- .../providers/blaxel/src/blaxel.test.ts | 22 +++++++++++++++++++ .../typescript/providers/blaxel/src/errors.ts | 1 + 2 files changed, 23 insertions(+) diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 4cfdb8cc..f2477066 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -278,6 +278,28 @@ describe("Blaxel mappings", () => { }); }); + it("classifies POLICY_VIOLATION as invalid argument", () => { + const native = new ResponseError( + new Response(null, { status: 403, statusText: "Forbidden" }), + { + error: { + code: "POLICY_VIOLATION", + message: "policy", + status: 403, + }, + }, + new Error("policy"), + ); + expect(mapBlaxelError(native, "create")).toMatchObject({ + kind: ErrorKind.InvalidArgument, + provider: "blaxel", + operation: "create", + statusCode: 403, + providerCode: "POLICY_VIOLATION", + cause: native, + }); + }); + it("classifies edge gateway 504 as timeout", () => { const native = new ResponseError( new Response(null, { status: 504, statusText: "Gateway Timeout" }), diff --git a/sdks/typescript/providers/blaxel/src/errors.ts b/sdks/typescript/providers/blaxel/src/errors.ts index 2698ae3c..52e8c795 100644 --- a/sdks/typescript/providers/blaxel/src/errors.ts +++ b/sdks/typescript/providers/blaxel/src/errors.ts @@ -117,6 +117,7 @@ function kindFromCode(code: string): ErrorInfo["kind"] { case "VOLUME_ALREADY_ATTACHED": return ErrorKind.AlreadyExists; case "BAD_REQUEST": + case "POLICY_VIOLATION": // reserved gateway code; matches Go ErrPolicyViolation case "INVALID_INPUT": case "INVALID_IMAGE": case "INVALID_PORTS": From f221fec11c741bf914a146ff778dfe1c1de3ad8a Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 19:21:04 +0530 Subject: [PATCH 19/60] Fix Blaxel long-wait process path, Http2Tls rejection, and TS stat. Poll past Blaxel's completion hold for omit/zero/long timeouts, reject unsupported HTTP/2 port transport, and stop inventing directory metadata on TypeScript stat. Co-authored-by: Cursor Entire-Checkpoint: 01M72VM0WBGMGGMHV8XVMWYNVN --- docs/process.md | 20 +- .../create-blaxel-with-ports/README.md | 2 +- .../process/run-blaxel-with-timeout/README.md | 6 +- .../create-blaxel-with-ports/README.md | 2 +- .../process/run-blaxel-with-timeout/README.md | 6 +- sdks/go/providers/blaxel/README.md | 17 +- sdks/go/providers/blaxel/creation.go | 4 +- sdks/go/providers/blaxel/process.go | 88 +++- sdks/go/providers/blaxel/process_test.go | 270 ++++++++++++ sdks/go/providers/blaxel/rejection_test.go | 16 + sdks/go/sandbox/sandbox_data_contracts.go | 4 +- sdks/typescript/providers/blaxel/README.md | 21 +- .../providers/blaxel/src/blaxel.test.ts | 390 +++++++++++++++++- .../typescript/providers/blaxel/src/blaxel.ts | 141 +++++-- .../typescript/providers/blaxel/src/errors.ts | 26 +- .../providers/blaxel/src/mappings/mappings.ts | 3 +- website/content/docs/blaxel.mdx | 8 +- website/content/docs/create.mdx | 9 +- website/content/docs/process.mdx | 21 +- website/content/docs/sdk/create-options.mdx | 5 +- website/content/docs/sdk/enums.mdx | 2 +- website/content/docs/sdk/process.mdx | 12 +- 22 files changed, 956 insertions(+), 117 deletions(-) diff --git a/docs/process.md b/docs/process.md index cfbe93bc..2ce968be 100644 --- a/docs/process.md +++ b/docs/process.md @@ -74,19 +74,23 @@ provider quirks). Provider quirks: capped at 10 seconds. - **Modal** — omit/zero adds no Modal command limit. In TypeScript the client omits `timeoutMs` (sending `0` is rejected by Modal). -- **Blaxel** — omit/zero adds no Blaxel process auto-kill. Timeouts must be - whole seconds. Blocking `run` uses `waitForCompletion`; Blaxel's edge still - ends that wait after about a minute (public docs) to two minutes (SDK), while - the remote command may keep running. +- **Blaxel** — omit or zero adds no Blaxel process auto-kill. Timeouts must + be whole seconds. A positive timeout of at most 60 seconds uses Blaxel's + blocking completion request. Omit, zero, and longer timeouts start the + command and poll until it ends. The Kit deadline and cancel stop that + wait and leave the remote command running. A positive timeout still sets + Blaxel's process auto-kill. An HTTP 504 during the wait is a timeout error. Cancel behavior: -- **Go** — cancel or deadline on the context ends the wait and returns an +- **Go** — cancel or a deadline on the context ends the wait and returns an error. The handle stays busy until that provider call returns. - **TypeScript** — abort rejects the `await`. The remote command may keep - running; Daytona, Modal, and Blaxel exec do not take an `AbortSignal`. The handle - still stays busy until that provider call settles, so the next call on the - same handle does not overlap it. + running. Daytona and Modal do not observe the signal inside exec. Blaxel + polls stop when the signal aborts. A Blaxel run whose timeout is 1–60 + seconds uses a completion request that does not observe the signal. The + handle stays busy until the provider call settles, so the next call on + the same handle does not overlap it. ```ts const result = await sandbox.process.run({ diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md b/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md index 2138a42e..958c3ba8 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md @@ -5,7 +5,7 @@ Create one Blaxel sandbox that publishes selected ports. ## What this means Maps `network.ports` to Blaxel runtime ports. Default leaves the protocol to -Blaxel (HTTP). Plain → TCP, TLS → TLS, HTTP/2 over TLS → HTTP. +Blaxel (HTTP). Plain → TCP, TLS → TLS. HTTP/2 over TLS is rejected. ## Edge cases diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/README.md b/examples/go/blaxel/process/run-blaxel-with-timeout/README.md index e447091b..cb8c7ef7 100644 --- a/examples/go/blaxel/process/run-blaxel-with-timeout/README.md +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/README.md @@ -4,8 +4,10 @@ Cap a Blaxel Process.Run with a whole-second timeout. ## What this means -Command timeouts must be whole seconds. Omit or zero for no Blaxel process -auto-kill; the blocking wait can still end at Blaxel's edge hold. +Command timeouts must be whole seconds. This run uses 5 seconds, so Blaxel +kills the process at 5 seconds and waits on its completion request. Omit or +zero adds no process auto-kill; Kit polls until the command ends or the call +is canceled. A timeout longer than 60 seconds sets the auto-kill and polls. ## Edge cases diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md index edab67d4..20a57e3f 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md @@ -5,7 +5,7 @@ Create one Blaxel sandbox that publishes selected ports. ## What this means Maps `network.ports` to Blaxel runtime ports. Default leaves the protocol to -Blaxel (HTTP). Plain → TCP, TLS → TLS, HTTP/2 over TLS → HTTP. +Blaxel (HTTP). Plain → TCP, TLS → TLS. HTTP/2 over TLS is rejected. ## Edge cases diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md b/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md index 394cb836..3fb9d51b 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md @@ -4,8 +4,10 @@ Cap a Blaxel Process.Run with a whole-second timeout. ## What this means -Command timeouts must be whole seconds. Omit or zero for no Blaxel process -auto-kill; the blocking wait can still end at Blaxel's edge hold. +Command timeouts must be whole seconds. This run uses 5 seconds, so Blaxel +kills the process at 5 seconds and waits on its completion request. Omit or +zero adds no process auto-kill; Kit polls until the command ends or the call +is canceled. A timeout longer than 60 seconds sets the auto-kill and polls. ## Edge cases diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 32b8556d..e0391784 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -66,9 +66,10 @@ alphanumeric with single hyphens and at most 49 characters. You can set labels, environment variables, memory, ports, outbound domains, maximum lifetime, and idle terminate. Max-age TTL must be at least 5m; idle TTL must be at least 1m. Ports 80, 443, and 8080 are rejected (Blaxel-reserved). Default port transport -leaves the protocol to Blaxel (HTTP); plain maps to TCP. Kit -`Config.Timeout` / create call timeout is the create deadline. The Go Blaxel -SDK has no separate creation-timeout argument. `WaitConditionReady` is rejected. +leaves the protocol to Blaxel (HTTP); plain maps to TCP; TLS maps to TLS. +HTTP/2 over TLS is rejected. Kit `Config.Timeout` / create call timeout is the +create deadline. The Go Blaxel SDK has no separate creation-timeout argument. +`WaitConditionReady` is rejected. On the handle: `Get`, `Refresh`, `Delete`, and `Close`. `Get` and `Refresh` look up the sandbox by name. `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and @@ -86,10 +87,12 @@ missing file passed to `CopyFromLocal` is not found, and the error field is `Process().Run` runs one command and waits for the result. Set exactly one of `Argv` or `Shell`. `Argv` is sent as a POSIX-quoted shell string. A non-zero exit code is success for the SDK—read `GetExitCode`. Blaxel returns stdout and -stderr. Omit or zero `Timeout` for no Blaxel process auto-kill. The blocking -wait can still end at Blaxel's edge hold (about one to two minutes) while the -command keeps running. Command timeouts must be whole seconds. Cancel the -context to stop waiting. +stderr. Omit or zero `Timeout` for no Blaxel process auto-kill; Kit then polls +until the command ends or the context ends. A positive timeout of at most 60 +seconds uses Blaxel's blocking completion request. A longer timeout sets the +process auto-kill and polls. Command timeouts must be whole seconds. Cancel +the context to stop waiting; the remote command may keep running. An HTTP 504 +during the wait is a timeout error. `resources.memory_mib` maps 1:1 onto Blaxel's memory field. Blaxel labels that integer MB; Kit does not convert. Blaxel derives CPU from that value, so diff --git a/sdks/go/providers/blaxel/creation.go b/sdks/go/providers/blaxel/creation.go index bc355550..63c97224 100644 --- a/sdks/go/providers/blaxel/creation.go +++ b/sdks/go/providers/blaxel/creation.go @@ -249,10 +249,8 @@ func blaxelPortProtocol(transport sandbox.PortTransport) (blaxel.PortProtocol, e return blaxel.PortProtocolTcp, nil case sandbox.PortTransportTLS: return blaxel.PortProtocolTls, nil - case sandbox.PortTransportHTTP2TLS: - // Blaxel exposes HTTP (not H2). Kit HTTP/2+TLS maps to that HTTP protocol. - return blaxel.PortProtocolHTTP, nil default: + // Blaxel has no HTTP/2 port protocol (Kit Http2Tls included). return "", providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldNetworkPortsTransport, "requested port transport is unsupported") } } diff --git a/sdks/go/providers/blaxel/process.go b/sdks/go/providers/blaxel/process.go index 21d94645..6b779433 100644 --- a/sdks/go/providers/blaxel/process.go +++ b/sdks/go/providers/blaxel/process.go @@ -11,6 +11,19 @@ import ( "google.golang.org/protobuf/types/known/durationpb" ) +// blaxelCompletionHold is the longest positive Kit timeout that stays on +// Blaxel's waitForCompletion request. Blaxel's public docs limit that hold +// to 60 seconds; the edge in front of the sandbox can close a longer one. +const blaxelCompletionHold = 60 * time.Second + +// blaxelWaitUnlimited is Process.Wait's maxWait sentinel for no added +// deadline. The SDK treats 0 as one minute and only -1 as unlimited. +const blaxelWaitUnlimited = time.Duration(-1) + +// processPollInterval is the gap between process status reads. It matches +// the Blaxel SDK default. +const processPollInterval = time.Second + var _ sandbox.SandboxProcess = (*backend)(nil) func (b *backend) RunCommand(ctx context.Context, id string, request *sandbox.ExecOptions) (*sandbox.ExecResult, error) { @@ -27,8 +40,10 @@ func (b *backend) RunCommand(ctx context.Context, id string, request *sandbox.Ex return nil, err } body := blaxel.ProcessRequestParam{ - Command: command, - WaitForCompletion: blaxel.Bool(true), + Command: command, + // Explicit zero disables Blaxel's default process kill window. + Timeout: blaxel.Int(int64(timeout / time.Second)), + WaitForCompletion: blaxel.Bool(completionHold(timeout)), } if request.WorkingDirectory != nil { body.WorkingDir = blaxel.String(*request.WorkingDirectory) @@ -36,16 +51,73 @@ func (b *backend) RunCommand(ctx context.Context, id string, request *sandbox.Ex if request.Environment != nil { body.Env = request.Environment } - if timeout > 0 { - body.Timeout = blaxel.Int(int64(timeout / time.Second)) - } else { - // Explicit zero disables Blaxel's keep-alive default kill window. - body.Timeout = blaxel.Int(0) + started, err := instance.Process.New(ctx, body) + if err != nil { + return nil, mapProviderError(err, "run") + } + if started == nil || completionHold(timeout) || processFinished(started) { + return execResult(started) } - response, err := instance.Process.New(ctx, body) + identifier := processIdentifier(started) + if identifier == "" { + return nil, sandbox.NewError(&sandbox.ErrorInfo{ + Kind: sandbox.ErrorKindInvalidResponse, + Provider: "blaxel", + Operation: "run", + Message: "sandbox-kit blaxel: SDK returned no process identifier", + }, nil) + } + // Wait polls until a terminal status or until ctx ends. A gateway 504 on + // one read is retried inside Wait; the deadline surfaces as Timeout. + finished, err := instance.Process.Wait(ctx, identifier, waitBudget(timeout), processPollInterval) if err != nil { return nil, mapProviderError(err, "run") } + return execResult(finished) +} + +// completionHold reports whether this timeout can stay on Blaxel's +// waitForCompletion request. Omit, zero, and anything above 60 seconds +// start the process and poll instead. +func completionHold(timeout time.Duration) bool { + return timeout > 0 && timeout <= blaxelCompletionHold +} + +// waitBudget is the Process.Wait maxWait for a Kit timeout. Omit and zero +// add no deadline beyond ctx. +func waitBudget(timeout time.Duration) time.Duration { + if timeout <= 0 { + return blaxelWaitUnlimited + } + return timeout +} + +func processFinished(response *blaxel.ProcessResponse) bool { + if response == nil { + return false + } + switch response.Status { + case blaxel.ProcessResponseStatusCompleted, + blaxel.ProcessResponseStatusFailed, + blaxel.ProcessResponseStatusKilled, + blaxel.ProcessResponseStatusStopped: + return true + default: + return false + } +} + +func processIdentifier(response *blaxel.ProcessResponse) string { + if response == nil { + return "" + } + if response.Pid != "" { + return response.Pid + } + return response.Name +} + +func execResult(response *blaxel.ProcessResponse) (*sandbox.ExecResult, error) { if response == nil { return nil, sandbox.NewError(&sandbox.ErrorInfo{ Kind: sandbox.ErrorKindInvalidResponse, diff --git a/sdks/go/providers/blaxel/process_test.go b/sdks/go/providers/blaxel/process_test.go index 94494a4e..ddd53f9e 100644 --- a/sdks/go/providers/blaxel/process_test.go +++ b/sdks/go/providers/blaxel/process_test.go @@ -1,10 +1,19 @@ package blaxel import ( + "context" + "encoding/json" "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "sync/atomic" "testing" "time" + blaxelsdk "github.com/blaxel-ai/sdk-go" + "github.com/blaxel-ai/sdk-go/option" "github.com/sandbox-kit/kit/sdks/go/sandbox" ) @@ -30,3 +39,264 @@ func TestCommandTimeoutWholeSeconds(t *testing.T) { t.Fatalf("error = %v detail = %#v", err, detail) } } + +func TestCompletionHoldAndWaitBudget(t *testing.T) { + if completionHold(0) || completionHold(61*time.Second) || !completionHold(60*time.Second) || !completionHold(time.Second) { + t.Fatal("completion hold boundary") + } + if waitBudget(0) != blaxelWaitUnlimited || waitBudget(90*time.Second) != 90*time.Second { + t.Fatal("wait budget") + } +} + +func TestRunCommandUsesCompletionHoldForShortTimeouts(t *testing.T) { + var posts []map[string]any + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/sbx": + writeJSON(t, w, map[string]any{"metadata": map[string]string{"name": "sbx"}, "spec": map[string]any{}}) + case r.Method == http.MethodPost && r.URL.Path == "/process": + body := readJSON(t, r) + posts = append(posts, body) + writeProcess(t, w, "completed", 0, "ok", "") + default: + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + } + }) + for _, timeout := range []*sandbox.ExecOptions{ + {Argv: []string{"echo", "hi"}, Timeout: sandbox.Duration(5 * time.Second), WorkingDirectory: sandbox.Value("/workspace"), Environment: map[string]string{"MODE": "test"}}, + {Argv: []string{"echo", "hi"}, Timeout: sandbox.Duration(60 * time.Second)}, + } { + result, err := backend.RunCommand(context.Background(), "sbx", timeout) + if err != nil { + t.Fatal(err) + } + if result.GetExitCode() != 0 || string(result.GetStdout()) != "ok" || string(result.GetStderr()) != "" { + t.Fatalf("result = %#v", result) + } + } + if len(posts) != 2 { + t.Fatalf("posts = %#v", posts) + } + if posts[0]["waitForCompletion"] != true || posts[0]["timeout"] != float64(5) || posts[0]["command"] != "'echo' 'hi'" || posts[0]["workingDir"] != "/workspace" { + t.Fatalf("short post = %#v", posts[0]) + } + env, _ := posts[0]["env"].(map[string]any) + if env["MODE"] != "test" { + t.Fatalf("env = %#v", posts[0]["env"]) + } + if posts[1]["waitForCompletion"] != true || posts[1]["timeout"] != float64(60) { + t.Fatalf("60s post = %#v", posts[1]) + } +} + +func TestRunCommandPollsWhenTheCompletionHoldIsUnsafe(t *testing.T) { + var gets atomic.Int32 + var posts []map[string]any + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/sbx": + writeJSON(t, w, map[string]any{"metadata": map[string]string{"name": "sbx"}, "spec": map[string]any{}}) + case r.Method == http.MethodPost && r.URL.Path == "/process": + posts = append(posts, readJSON(t, r)) + writeProcess(t, w, "running", 0, "", "") + case r.Method == http.MethodGet && r.URL.Path == "/process/123": + if gets.Add(1) == 1 { + http.Error(w, "gateway timeout", http.StatusGatewayTimeout) + return + } + writeProcess(t, w, "failed", 7, "out", "err") + default: + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + } + }) + cases := []struct { + name string + timeout *sandbox.ExecOptions + seconds float64 + }{ + {"omit", &sandbox.ExecOptions{Shell: sandbox.Value("echo hi")}, 0}, + {"zero", &sandbox.ExecOptions{Argv: []string{"echo", "hi"}, Timeout: sandbox.Duration(0)}, 0}, + {"long", &sandbox.ExecOptions{Argv: []string{"echo", "hi"}, Timeout: sandbox.Duration(61 * time.Second)}, 61}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + gets.Store(0) + result, err := backend.RunCommand(context.Background(), "sbx", tc.timeout) + if err != nil { + t.Fatal(err) + } + if result.GetExitCode() != 7 || string(result.GetStdout()) != "out" || string(result.GetStderr()) != "err" { + t.Fatalf("result = %#v", result) + } + if gets.Load() != 2 { + t.Fatalf("gets = %d", gets.Load()) + } + }) + } + if len(posts) != len(cases) { + t.Fatalf("posts = %d", len(posts)) + } + for i, tc := range cases { + if posts[i]["waitForCompletion"] != false || posts[i]["timeout"] != tc.seconds { + t.Fatalf("%s post = %#v", tc.name, posts[i]) + } + } +} + +func TestRunCommandReturnsAFinishedStartWithoutPolling(t *testing.T) { + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/sbx": + writeJSON(t, w, map[string]any{"metadata": map[string]string{"name": "sbx"}, "spec": map[string]any{}}) + case r.Method == http.MethodPost && r.URL.Path == "/process": + writeProcess(t, w, "killed", 9, "partial", "stopped") + default: + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + } + }) + result, err := backend.RunCommand(context.Background(), "sbx", &sandbox.ExecOptions{Argv: []string{"sleep", "1"}}) + if err != nil { + t.Fatal(err) + } + if result.GetExitCode() != 9 || string(result.GetStdout()) != "partial" || string(result.GetStderr()) != "stopped" { + t.Fatalf("result = %#v", result) + } +} + +func TestRunCommandRequiresAProcessIdentifier(t *testing.T) { + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/sbx": + writeJSON(t, w, map[string]any{"metadata": map[string]string{"name": "sbx"}, "spec": map[string]any{}}) + case r.Method == http.MethodPost && r.URL.Path == "/process": + writeJSON(t, w, map[string]any{"status": "running", "exitCode": 0, "stdout": "", "stderr": ""}) + default: + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + } + }) + _, err := backend.RunCommand(context.Background(), "sbx", &sandbox.ExecOptions{Argv: []string{"echo", "hi"}}) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidResponse || detail.Operation != "run" { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} + +func TestRunCommandCancelAndGatewayTimeout(t *testing.T) { + t.Run("cancel", func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/sbx": + writeJSON(t, w, map[string]any{"metadata": map[string]string{"name": "sbx"}, "spec": map[string]any{}}) + case r.Method == http.MethodPost && r.URL.Path == "/process": + writeProcess(t, w, "running", 0, "", "") + case r.Method == http.MethodGet && r.URL.Path == "/process/123": + cancel() + writeProcess(t, w, "running", 0, "", "") + default: + http.NotFound(w, r) + } + }) + _, err := backend.RunCommand(ctx, "sbx", &sandbox.ExecOptions{Argv: []string{"sleep", "30"}, Timeout: sandbox.Duration(0)}) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindCanceled { + t.Fatalf("error = %v detail = %#v", err, detail) + } + }) + t.Run("gateway timeout", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) + defer cancel() + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/sbx": + writeJSON(t, w, map[string]any{"metadata": map[string]string{"name": "sbx"}, "spec": map[string]any{}}) + case r.Method == http.MethodPost && r.URL.Path == "/process": + writeProcess(t, w, "running", 0, "", "") + case r.Method == http.MethodGet && r.URL.Path == "/process/123": + http.Error(w, "gateway timeout", http.StatusGatewayTimeout) + default: + http.NotFound(w, r) + } + }) + _, err := backend.RunCommand(ctx, "sbx", &sandbox.ExecOptions{Argv: []string{"sleep", "90"}, Timeout: sandbox.Duration(90 * time.Second)}) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindTimeout { + t.Fatalf("error = %v detail = %#v", err, detail) + } + }) +} + +func processBackend(t *testing.T, handler http.HandlerFunc) *backend { + t.Helper() + server := httptest.NewServer(handler) + t.Cleanup(server.Close) + client := blaxelsdk.NewClient( + option.WithBaseURL(server.URL), + option.WithAPIKey("test"), + option.WithMaxRetries(0), + ) + return &backend{client: &client} +} + +func writeProcess(t *testing.T, w http.ResponseWriter, status string, exitCode int, stdout, stderr string) { + t.Helper() + writeJSON(t, w, map[string]any{ + "command": "echo", + "completedAt": "", + "exitCode": exitCode, + "logs": stdout + stderr, + "name": "proc", + "pid": "123", + "startedAt": "", + "status": status, + "stderr": stderr, + "stdout": stdout, + "workingDir": "/", + }) +} + +func writeJSON(t *testing.T, w http.ResponseWriter, body any) { + t.Helper() + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(body); err != nil { + t.Errorf("encode: %v", err) + } +} + +func readJSON(t *testing.T, r *http.Request) map[string]any { + t.Helper() + data, err := io.ReadAll(r.Body) + if err != nil { + t.Fatalf("read body: %v", err) + } + var body map[string]any + if err := json.Unmarshal(data, &body); err != nil { + t.Fatalf("decode %s: %v", data, err) + } + return body +} + +func TestMapProviderErrorWaitDeadlineKeepsGatewayTimeout(t *testing.T) { + parsed, err := http.NewRequest(http.MethodGet, "https://example.invalid/process/123", nil) + if err != nil { + t.Fatal(err) + } + apiErr := &blaxelsdk.Error{ + StatusCode: http.StatusGatewayTimeout, + Request: parsed, + Response: &http.Response{StatusCode: http.StatusGatewayTimeout}, + } + mapped := mapProviderError(fmt.Errorf( + "process 123 did not finish in time; it may still be running: %w", + errors.Join(context.DeadlineExceeded, apiErr), + ), "run") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindTimeout || detail.GetStatusCode() != http.StatusGatewayTimeout { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } +} diff --git a/sdks/go/providers/blaxel/rejection_test.go b/sdks/go/providers/blaxel/rejection_test.go index 5be68f73..9e477689 100644 --- a/sdks/go/providers/blaxel/rejection_test.go +++ b/sdks/go/providers/blaxel/rejection_test.go @@ -303,3 +303,19 @@ func TestDefaultPortOmitsProtocol(t *testing.T) { t.Fatalf("Ports = %#v", ports) } } + +func TestRejectsHttp2TlsPortTransport(t *testing.T) { + _, err := planCreate(namedImage(&sandbox.CreateOptions{ + Network: &sandbox.NetworkConfig{ + Ports: []*sandbox.PortBinding{ + {Port: 3000, Transport: sandbox.PortTransportHTTP2TLS}, + }, + }, + }), "us-pdx-1") + var detail *sandbox.Error + if !errors.As(err, &detail) || + detail.Kind != sandbox.ErrorKindInvalidArgument || + detail.Field != sandbox.CreateFieldNetworkPortsTransport { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} diff --git a/sdks/go/sandbox/sandbox_data_contracts.go b/sdks/go/sandbox/sandbox_data_contracts.go index ff6a8a25..8c5c15c6 100644 --- a/sdks/go/sandbox/sandbox_data_contracts.go +++ b/sdks/go/sandbox/sandbox_data_contracts.go @@ -153,8 +153,8 @@ type ( // An omitted Timeout uses Config.Timeout as the call deadline. Explicit // zero adds no extra deadline for this call. Both omit and zero also mean // no provider process auto-kill (Daytona uses 0 so its 10s native default - // does not apply; Modal adds no command limit; Blaxel adds no auto-kill, - // though its blocking wait can still end at the edge hold). Command + // does not apply; Modal adds no command limit; Blaxel adds no auto-kill + // and polls until the command ends). Command // timeouts must be whole seconds. ExecOptions = sandboxv1.ExecOptions diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index ba455013..194cb310 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -62,8 +62,9 @@ create `timeout` is the Kit deadline. `@blaxel/core`'s native creation timeout (1–50s) is not forwarded. `provisioning.waitFor` Ready is unsupported. Creation calls native `SandboxInstance.create`, not `createIfNotExists`. Ports 80, 443, and 8080 are rejected (Blaxel-reserved). Default port transport leaves -the protocol to Blaxel (HTTP); plain maps to TCP. Kit has no portable volume -fields; Blaxel volumes are outside create. +the protocol to Blaxel (HTTP); plain maps to TCP; TLS maps to TLS. HTTP/2 over +TLS is rejected. Kit has no portable volume fields; Blaxel volumes are outside +create. **Lifecycle** on the handle: `get` / `refresh` (lookup by name), `delete`, and `close`. `close` does not delete the sandbox. Start, stop, pause, archive, @@ -78,13 +79,15 @@ directory is an invalid argument with the same field. `copyToLocal` creates a missing local parent directory. **Commands** through `sandbox.process.run`. Set exactly one of `argv` or -`shell`. `argv` is sent as a POSIX-quoted shell string. Runs are blocking -(`waitForCompletion: true`). A non-zero exit code is not an SDK error—check -`exitCode`. Blaxel returns stdout and stderr. Omit or zero `timeout` for no -process auto-kill. The blocking wait can still end at Blaxel's edge hold -(about one to two minutes) while the command keeps running. Command timeouts -must be whole seconds (`2000` is two seconds; `1500` is rejected). Aborting -the signal rejects the `await`; the remote command may keep running. +`shell`. `argv` is sent as a POSIX-quoted shell string. `run` waits until the +command ends. A non-zero exit code is not an SDK error—check `exitCode`. +Blaxel returns stdout and stderr. Omit or zero `timeout` for no process +auto-kill; Kit then polls until the command ends or the signal aborts. A +positive timeout of at most 60 seconds uses Blaxel's blocking completion +request. A longer timeout sets the process auto-kill and polls. Command +timeouts must be whole seconds (`2000` is two seconds; `1500` is rejected). +Aborting the signal rejects the `await`; the remote command may keep running. +An HTTP 504 during the wait is a timeout error. Omitted memory keeps the Blaxel default. CPU, CPU limit, memory limit, and disk overrides are rejected. diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index f2477066..f9a8e581 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -4,6 +4,7 @@ import { describe, expect, it } from "vitest"; import { Client, ErrorKind, + FileType, PolicyMode, PortTransport, SandboxState, @@ -16,8 +17,10 @@ import { CreateFieldLifetimeMaximumLifetime, CreateFieldName, CreateFieldNetworkPortsPort, + CreateFieldNetworkPortsTransport, CreateFieldProvisioningWaitFor, FileFieldLocalPath, + FileFieldPath, } from "@sandbox-kit/sandbox"; import { mapClientConfig, mapCreateRequest } from "#mappings/mappings.js"; import { BlaxelProvider } from "./blaxel.js"; @@ -128,7 +131,7 @@ describe("Blaxel mappings", () => { resources: { memoryMib: 4096n }, network: { outboundDomains: ["example.com"], - ports: [{ port: 3000, transport: PortTransport.Http2Tls }], + ports: [{ port: 3000, transport: PortTransport.Tls }], }, lifetime: { maximumLifetime: 24 * 60 * 60_000, @@ -152,7 +155,7 @@ describe("Blaxel mappings", () => { ttl: "1d", labels: { purpose: "test" }, envs: [{ name: "NODE_ENV", value: "test" }], - ports: [{ target: 3000, protocol: "HTTP" }], + ports: [{ target: 3000, protocol: "TLS" }], network: { allowedDomains: ["example.com"] }, lifecycle: { expirationPolicies: [ @@ -312,6 +315,16 @@ describe("Blaxel mappings", () => { operation: "run", cause: native, }); + const waited = new Error( + "Process did not finish in time (proc); it may still be running", + { cause: native }, + ); + expect(mapBlaxelError(waited, "run")).toMatchObject({ + kind: ErrorKind.Timeout, + operation: "run", + statusCode: 504, + cause: waited, + }); }); it("omits protocol for default port transport", () => { @@ -326,6 +339,26 @@ describe("Blaxel mappings", () => { expect(plan.params.ports).toEqual([{ target: 3000 }]); }); + it("rejects HTTP/2 TLS port transport", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + network: { + ports: [{ port: 3000, transport: PortTransport.Http2Tls }], + }, + }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldNetworkPortsTransport, + }), + ); + }); + it("applies environment checks before mapping", () => { expect(() => mapCreateRequest( @@ -610,12 +643,21 @@ describe("Blaxel provider orchestration", () => { expect(lookedUp).toBe(false); }); - it("runs commands with waitForCompletion and second-based timeouts", async () => { + it("keeps short runs on waitForCompletion and polls the rest", async () => { const seen: Array<{ command: string; waitForCompletion?: boolean; timeout?: number; + workingDir?: string; + env?: Record; + }> = []; + const waits: Array<{ + identifier: string; + maxWait?: number; + interval?: number; + signal?: AbortSignal; }> = []; + const signal = new AbortController().signal; const backend = new BlaxelProvider(() => ({ configure() {}, async create() { @@ -634,8 +676,47 @@ describe("Blaxel provider orchestration", () => { entry.waitForCompletion = request.waitForCompletion; if (request.timeout !== undefined) entry.timeout = request.timeout; + if (request.workingDir !== undefined) + entry.workingDir = request.workingDir; + if (request.env !== undefined) entry.env = request.env; seen.push(entry); - return { exitCode: 0, stdout: "ok", stderr: "" }; + if (request.waitForCompletion) { + return { + exitCode: 0, + stdout: "ok", + stderr: "", + status: "completed", + pid: "short", + }; + } + return { + exitCode: 0, + stdout: "", + stderr: "", + status: "running", + pid: "long", + name: "proc", + }; + }, + async wait(identifier, options) { + waits.push({ + identifier, + ...(options?.maxWait === undefined + ? {} + : { maxWait: options.maxWait }), + ...(options?.interval === undefined + ? {} + : { interval: options.interval }), + ...(options?.signal === undefined + ? {} + : { signal: options.signal }), + }); + return { + exitCode: 7, + stdout: "out", + stderr: "err", + status: "failed", + }; }, }, async wait() { @@ -653,13 +734,28 @@ describe("Blaxel provider orchestration", () => { }); if (backend.run === undefined) throw new Error("run is missing"); - await backend.run("sandbox-1", { argv: ["echo", "hi"] }, {}); + const polled = await backend.run("sandbox-1", { argv: ["echo", "hi"] }, {}); await backend.run("sandbox-1", { argv: ["echo", "hi"], timeout: 0 }, {}); + const held = await backend.run( + "sandbox-1", + { + argv: ["echo", "hi"], + timeout: 5_000, + workingDirectory: "/workspace", + environment: { MODE: "test" }, + }, + {}, + ); await backend.run( "sandbox-1", - { argv: ["echo", "hi"], timeout: 5_000 }, + { argv: ["echo", "hi"], timeout: 60_000 }, {}, ); + await backend.run( + "sandbox-1", + { argv: ["echo", "hi"], timeout: 61_000 }, + { signal }, + ); await expect( backend.run("sandbox-1", { argv: ["echo", "hi"], timeout: 1_500 }, {}), ).rejects.toMatchObject({ @@ -668,10 +764,286 @@ describe("Blaxel provider orchestration", () => { operation: "run", }); + expect(polled).toMatchObject({ exitCode: 7 }); + expect(polled.stdout).toEqual(new TextEncoder().encode("out")); + expect(polled.stderr).toEqual(new TextEncoder().encode("err")); + expect(held).toMatchObject({ exitCode: 0 }); + expect(held.stdout).toEqual(new TextEncoder().encode("ok")); expect(seen).toEqual([ - { command: "'echo' 'hi'", waitForCompletion: true, timeout: 0 }, - { command: "'echo' 'hi'", waitForCompletion: true, timeout: 0 }, - { command: "'echo' 'hi'", waitForCompletion: true, timeout: 5 }, + { command: "'echo' 'hi'", waitForCompletion: false, timeout: 0 }, + { command: "'echo' 'hi'", waitForCompletion: false, timeout: 0 }, + { + command: "'echo' 'hi'", + waitForCompletion: true, + timeout: 5, + workingDir: "/workspace", + env: { MODE: "test" }, + }, + { command: "'echo' 'hi'", waitForCompletion: true, timeout: 60 }, + { command: "'echo' 'hi'", waitForCompletion: false, timeout: 61 }, + ]); + expect(waits).toEqual([ + { identifier: "long", maxWait: -1, interval: 1_000 }, + { identifier: "long", maxWait: -1, interval: 1_000 }, + { identifier: "long", maxWait: 61_000, interval: 1_000, signal }, ]); }); + + it("returns a finished process without polling", async () => { + const backend = runBackend({ + async exec() { + return { + status: "killed", + exitCode: 9, + stdout: "partial", + stderr: "stopped", + }; + }, + async wait() { + throw new Error("wait must not run"); + }, + }); + const result = await backend.run!( + "sandbox-1", + { argv: ["sleep", "1"] }, + {}, + ); + expect(result.exitCode).toBe(9); + expect(result.stdout).toEqual(new TextEncoder().encode("partial")); + expect(result.stderr).toEqual(new TextEncoder().encode("stopped")); + }); + + it("rejects a running process with no identifier", async () => { + const backend = runBackend({ + async exec() { + return { status: "running", exitCode: 0, stdout: "", stderr: "" }; + }, + async wait() { + throw new Error("wait must not run"); + }, + }); + await expect( + backend.run!("sandbox-1", { argv: ["echo", "hi"] }, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.InvalidResponse, + operation: "run", + }); + }); + + it("maps an aborted process wait and a wait deadline", async () => { + const controller = new AbortController(); + const aborted = runBackend({ + async exec() { + return { status: "running", pid: "long", stdout: "", stderr: "" }; + }, + async wait(_identifier, options) { + controller.abort(); + options?.signal?.throwIfAborted(); + return { status: "completed", exitCode: 0, stdout: "", stderr: "" }; + }, + }); + await expect( + aborted.run!( + "sandbox-1", + { argv: ["sleep", "30"], timeout: 0 }, + { signal: controller.signal }, + ), + ).rejects.toMatchObject({ kind: ErrorKind.Canceled, operation: "run" }); + + const deadline = runBackend({ + async exec() { + return { status: "running", pid: "long", stdout: "", stderr: "" }; + }, + async wait() { + throw new Error( + "Process did not finish in time (long); it may still be running", + ); + }, + }); + await expect( + deadline.run!( + "sandbox-1", + { argv: ["sleep", "90"], timeout: 61_000 }, + {}, + ), + ).rejects.toMatchObject({ kind: ErrorKind.Timeout, operation: "run" }); + }); +}); + +describe("Blaxel filesystem.stat", () => { + it("returns directory metadata from ls on the path", async () => { + const backend = fsBackend({ + async ls(path) { + expect(path).toBe("/app"); + return { + name: "app", + path: "/app", + files: [], + subdirectories: [], + }; + }, + }); + await expect(backend.stat!("sandbox-1", "/app", {}, {})).resolves.toEqual({ + name: "app", + path: "/app", + type: FileType.Directory, + size: 0n, + }); + }); + + it("reads file metadata from the parent listing when ls rejects a file path", async () => { + const backend = fsBackend({ + async ls(path) { + if (path === "/app/readme.txt") { + throw new Error(JSON.stringify({ error: "Directory not found" })); + } + expect(path).toBe("/app"); + return { + name: "app", + path: "/app", + files: [ + { + name: "readme.txt", + path: "/app/readme.txt", + size: 12, + permissions: "0644", + owner: "user", + group: "user", + lastModified: "2024-01-02T03:04:05Z", + }, + ], + subdirectories: [], + }; + }, + }); + await expect( + backend.stat!("sandbox-1", "/app/readme.txt", {}, {}), + ).resolves.toMatchObject({ + name: "readme.txt", + path: "/app/readme.txt", + type: FileType.File, + size: 12n, + permissions: "0644", + }); + }); + + it("propagates non-file ls errors and reports missing files", async () => { + const missing = fsBackend({ + async ls() { + throw new Error("boom"); + }, + }); + await expect( + missing.stat!("sandbox-1", "/missing", {}, {}), + ).rejects.toMatchObject({ kind: ErrorKind.Unknown, operation: "stat" }); + + const gone = fsBackend({ + async ls(path) { + if (path === "/app/gone.txt") { + throw new Error(JSON.stringify({ error: "Directory not found" })); + } + return { + name: "app", + path: "/app", + files: [], + subdirectories: [], + }; + }, + }); + await expect( + gone.stat!("sandbox-1", "/app/gone.txt", {}, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.NotFound, + operation: "stat", + field: FileFieldPath, + }); + }); }); + +function fsBackend(fs: { + ls(path: string): Promise<{ + name: string; + path: string; + files: Array<{ + name: string; + path: string; + size: number; + permissions?: string; + owner?: string; + group?: string; + lastModified?: string; + }>; + subdirectories: Array<{ name: string; path: string }>; + }>; +}) { + return new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + return { + metadata: { name: "sandbox-1" }, + fs: fs as never, + process: {} as never, + async wait() { + return this; + }, + async delete() {}, + }; + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); +} + +function runBackend(process: { + exec(request: { + command: string; + waitForCompletion?: boolean; + timeout?: number; + }): Promise<{ + pid?: string; + status?: string; + exitCode?: number; + stdout?: string; + stderr?: string; + }>; + wait( + identifier: string, + options?: { signal?: AbortSignal }, + ): Promise<{ + exitCode?: number; + stdout?: string; + stderr?: string; + status?: string; + }>; +}) { + return new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + return { + metadata: { name: "sandbox-1" }, + fs: {} as never, + process, + async wait() { + return this; + }, + async delete() {}, + }; + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); +} diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index ed31cced..e12bea50 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -14,8 +14,10 @@ import { ErrorKind, ExecFieldTimeout, FileFieldParents, + FileFieldPath, FileType, SandboxState, + sandboxError, type FileInfo, type OperationOptions, type Provider, @@ -55,6 +57,15 @@ import { type ProviderState, } from "#config/provider_config.js"; +interface BlaxelProcessResult { + readonly pid?: string; + readonly name?: string; + readonly status?: string; + readonly exitCode?: number; + readonly stdout?: string; + readonly stderr?: string; +} + interface NativeSandboxHandle { readonly metadata?: { readonly name?: string; @@ -78,11 +89,15 @@ interface NativeSandboxHandle { timeout?: number; workingDir?: string; env?: Record; - }): Promise<{ - exitCode?: number; - stdout?: string; - stderr?: string; - }>; + }): Promise; + wait( + identifier: string, + options?: { + maxWait?: number; + interval?: number; + signal?: AbortSignal; + }, + ): Promise; }; delete(): Promise; } @@ -283,35 +298,32 @@ class BlaxelBackend extends BackendBase implements Backend { options: OperationOptions, ): Promise { return this.#withSandbox("stat", id, options, async (sandbox) => { - const parent = dirname(path); - const name = basename(path); - if (name === "" || name === "/" || path === "/") { + // Blaxel's public FS API has no Stat. GET-by-path is what Go uses; here + // ls wraps the same GET and rejects non-directories with a fixed error. + try { const listing = await sandbox.fs.ls(path); return { - name: listing.name || "/", + name: listing.name || basename(path) || "/", path: listing.path || path, type: FileType.Directory, size: 0n, }; + } catch (error) { + if (!isBlaxelFilePath(error)) throw error; } - try { - const listing = await sandbox.fs.ls(parent === "." ? "/" : parent); - const file = listing.files.find((entry) => entry.name === name); - if (file) return fileInfo(file); - const directory = listing.subdirectories.find( - (entry) => entry.name === name, - ); - if (directory) return directoryInfo(directory); - } catch { - // Fall through to treating the path as a directory. - } - const self = await sandbox.fs.ls(path); - return { - name: self.name || name, - path: self.path || path, - type: FileType.Directory, - size: 0n, - }; + // Path is a file: metadata is on the parent directory listing. + const parent = dirname(path); + const name = basename(path); + const listing = await sandbox.fs.ls(parent === "." ? "/" : parent); + const file = listing.files.find((entry) => entry.name === name); + if (file) return fileInfo(file); + throw sandboxError({ + kind: ErrorKind.NotFound, + provider: "blaxel", + operation: "stat", + field: FileFieldPath, + message: `sandbox-kit blaxel: path not found: ${path}`, + }); }); } @@ -333,11 +345,13 @@ class BlaxelBackend extends BackendBase implements Backend { ): Promise { return this.#withSandbox("run", id, options, async (sandbox) => { const timeout = execTimeoutSeconds(request.timeout); - const result = await sandbox.process.exec({ + const started = await sandbox.process.exec({ command: blaxelCommand(request), - waitForCompletion: true, + // A positive timeout of at most 60 seconds can stay on Blaxel's + // completion request. Omit, zero, and longer timeouts start the + // process and poll so the edge hold does not fail the call. + waitForCompletion: holdsCompletion(timeout), // Blaxel: omit/0 means no auto-kill. Kit omit/0 map to 0. - // waitForCompletion waits are still cut by Blaxel's edge hold. timeout: timeout ?? 0, ...(request.workingDirectory === undefined ? {} @@ -346,11 +360,27 @@ class BlaxelBackend extends BackendBase implements Backend { ? {} : { env: { ...request.environment } }), }); - return { - exitCode: result.exitCode ?? 0, - stdout: new TextEncoder().encode(result.stdout ?? ""), - stderr: new TextEncoder().encode(result.stderr ?? ""), - }; + if (holdsCompletion(timeout) || processFinished(started.status)) { + return execResult(started); + } + const identifier = started.pid || started.name; + if (!identifier) { + throw sandboxError({ + kind: ErrorKind.InvalidResponse, + provider: "blaxel", + operation: "run", + field: "", + message: "sandbox-kit blaxel: SDK returned no process identifier", + }); + } + // wait polls until a terminal status or until the signal / maxWait ends. + // A gateway 504 on one read is retried inside wait; the deadline is a timeout. + const finished = await sandbox.process.wait(identifier, { + maxWait: timeout === undefined ? -1 : timeout * 1_000, + interval: 1_000, + ...(options.signal === undefined ? {} : { signal: options.signal }), + }); + return execResult(finished); }); } @@ -404,6 +434,47 @@ function throwIfAborted(signal: AbortSignal | undefined): void { signal?.throwIfAborted(); } +/** Blaxel `fs.ls` throws this when GET returns a file instead of a directory. */ +function isBlaxelFilePath(error: unknown): boolean { + if (!(error instanceof Error)) return false; + try { + const body = JSON.parse(error.message) as { error?: unknown }; + return body.error === "Directory not found"; + } catch { + return false; + } +} + +/** Blaxel's documented limit for a single waitForCompletion request. */ +const blaxelCompletionHoldSeconds = 60; + +function holdsCompletion(timeoutSeconds: number | undefined): boolean { + return ( + timeoutSeconds !== undefined && + timeoutSeconds > 0 && + timeoutSeconds <= blaxelCompletionHoldSeconds + ); +} + +const terminalProcessStatus = new Set([ + "completed", + "failed", + "killed", + "stopped", +]); + +function processFinished(status: string | undefined): boolean { + return status !== undefined && terminalProcessStatus.has(status); +} + +function execResult(result: BlaxelProcessResult): ExecResult { + return { + exitCode: result.exitCode ?? 0, + stdout: new TextEncoder().encode(result.stdout ?? ""), + stderr: new TextEncoder().encode(result.stderr ?? ""), + }; +} + function blaxelCommand(request: ExecOptions): string { if (request.shell !== undefined) return request.shell; return posixShellJoin(request.argv ?? []); diff --git a/sdks/typescript/providers/blaxel/src/errors.ts b/sdks/typescript/providers/blaxel/src/errors.ts index 52e8c795..39dbed33 100644 --- a/sdks/typescript/providers/blaxel/src/errors.ts +++ b/sdks/typescript/providers/blaxel/src/errors.ts @@ -52,8 +52,10 @@ export function mapBlaxelError( if (error instanceof DOMException && error.name === "TimeoutError") return create({ kind: ErrorKind.Timeout, error, operation }); + const cause = causeOf(error); let kind: ErrorInfo["kind"] = ErrorKind.Unknown; - const statusCode = getBlaxelErrorStatus(error); + const statusCode = + getBlaxelErrorStatus(error) ?? getBlaxelErrorStatus(cause); const providerCode = stringifyCode(getBlaxelErrorCode(error)); if (error instanceof CredentialsError) kind = ErrorKind.Authentication; @@ -63,8 +65,15 @@ export function mapBlaxelError( isCreationTimeoutError(error)) ) kind = ErrorKind.Timeout; - // Edge 504 (waitForCompletion hold / origin-read) is a timeout, matching Go. - if (kind === ErrorKind.Unknown && isGatewayTimeout(error)) + // process.wait reports its deadline as a plain Error. A 504 on one poll is + // retried inside wait and may be that error's cause. A bare edge 504 + // (completion hold or origin read) is a timeout too, matching Go. + if ( + kind === ErrorKind.Unknown && + (isProcessWaitTimeout(error) || + isGatewayTimeout(error) || + isGatewayTimeout(cause)) + ) kind = ErrorKind.Timeout; if ( kind === ErrorKind.Unknown && @@ -193,6 +202,17 @@ function kindFromStatus(status: number): ErrorInfo["kind"] { } } +function isProcessWaitTimeout(error: unknown): boolean { + return ( + error instanceof Error && + error.message.startsWith("Process did not finish in time") + ); +} + +function causeOf(error: unknown): unknown { + return error instanceof Error ? error.cause : undefined; +} + function stringifyCode(code: unknown): string | undefined { if (typeof code === "string" && code !== "") return code; if (typeof code === "number") return String(code); diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index dd7d6d18..8c86b6d8 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -177,8 +177,7 @@ function blaxelPortProtocol( if (transport === PortTransport.Default) return undefined; if (transport === PortTransport.Plain) return "TCP"; if (transport === PortTransport.Tls) return "TLS"; - // Blaxel exposes HTTP (not H2). Kit HTTP/2+TLS maps to that HTTP protocol. - if (transport === PortTransport.Http2Tls) return "HTTP"; + // Blaxel has no HTTP/2 port protocol (Kit Http2Tls included). invalid( CreateFieldNetworkPortsTransport, "requested port transport is unsupported", diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index da33d5f1..d9ac9820 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -173,10 +173,10 @@ Token-pair, bearer, and OAuth client modes are rejected. Scope fields other than `resources.memoryMib` / `memory_mib` maps 1:1 onto Blaxel's memory field. Blaxel labels that integer MB; Kit does not convert. Blaxel derives CPU from memory, so CPU and disk overrides are rejected. Default port transport leaves -the protocol to Blaxel (HTTP); plain maps to TCP. Client `timeout` is the -Kit create/operation deadline. `@blaxel/core`'s native creation timeout -(1–50s) is not forwarded; the Go SDK has no equivalent. Ready wait and -readiness probes are unsupported. +the protocol to Blaxel (HTTP); plain maps to TCP; TLS maps to TLS; HTTP/2 over +TLS is rejected. Client `timeout` is the Kit create/operation deadline. +`@blaxel/core`'s native creation timeout (1–50s) is not forwarded; the Go SDK +has no equivalent. Ready wait and readiness probes are unsupported. ## Related diff --git a/website/content/docs/create.mdx b/website/content/docs/create.mdx index fc95c0b0..80aaf1b3 100644 --- a/website/content/docs/create.mdx +++ b/website/content/docs/create.mdx @@ -277,7 +277,7 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ | Disk | Cannot be set | | Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m) | | Placement | Client `region` required. A placement block on this request is rejected | -| Ports | Default leaves protocol to Blaxel (HTTP); plain → TCP; 80/443/8080 reserved | +| Ports | Default → Blaxel HTTP; plain → TCP; TLS → TLS; Http2Tls rejected; 80/443/8080 reserved | Kit has no portable volume fields. @@ -611,9 +611,10 @@ See [Examples](/docs/examples). Create needs a Blaxel-valid `name` (lowercase alphanumeric with single hyphens, at most 49 characters), an image, and client `region`. Memory maps 1:1; CPU, disk, placement, readiness, and volumes are rejected. Ports 80, 443, and 8080 -are reserved. Default port transport leaves the protocol to Blaxel (HTTP). -Kit create timeout is the local deadline; Blaxel's native creation timeout is -not forwarded. See [Examples](/docs/examples). +are reserved. Default port transport leaves the protocol to Blaxel (HTTP); +plain maps to TCP; TLS maps to TLS; HTTP/2 over TLS is rejected. Kit create +timeout is the local deadline; Blaxel's native creation timeout is not +forwarded. See [Examples](/docs/examples). diff --git a/website/content/docs/process.mdx b/website/content/docs/process.mdx index b12a5704..25d1486e 100644 --- a/website/content/docs/process.mdx +++ b/website/content/docs/process.mdx @@ -155,15 +155,16 @@ fmt.Println(result) -| You pass | On Blaxel | -| ---------------------- | -------------------------------------------------------------------- | -| `argv` | POSIX-quoted shell string | -| `shell` | shell string as-is | -| omit / zero timeout | no process auto-kill; blocking wait still ends at Blaxel's edge hold | -| fractional (`1500` ms) | rejected (`timeout`) | +| You pass | On Blaxel | +| ---------------------- | -------------------------------------------------------------------------------------- | +| `argv` | POSIX-quoted shell string | +| `shell` | shell string as-is | +| omit / zero timeout | no process auto-kill; Kit polls until the command ends or the call is canceled | +| 1–60 seconds | process auto-kill; blocking completion request | +| longer than 60 seconds | process auto-kill; Kit polls until the command ends or the call is canceled | +| fractional (`1500` ms) | rejected (`timeout`) | -Blaxel returns both `stdout` and `stderr`. Runs are blocking -(`waitForCompletion`). +Blaxel returns both `stdout` and `stderr`. `run` waits until the command ends. @@ -202,8 +203,8 @@ A non-zero exit code is **not** an SDK error. | `0` | No extra client deadline for this call | | Positive value | Cap this call; must be whole seconds (`5000` ok, `1500` rejected) | -Omit or zero also means no separate provider process auto-kill (see provider -tables above for Blaxel's blocking-wait edge hold). +Omit or zero also means no separate provider process auto-kill (see the +provider tables above). diff --git a/website/content/docs/sdk/create-options.mdx b/website/content/docs/sdk/create-options.mdx index 5fa8f35a..3a8b5a05 100644 --- a/website/content/docs/sdk/create-options.mdx +++ b/website/content/docs/sdk/create-options.mdx @@ -223,8 +223,9 @@ Set exactly one variant. Unsupported variants fail before the provider call. Blaxel requires an `image` source and a Blaxel-valid `name` (lowercase alphanumeric with single hyphens, at most 49 characters). Client `region` is required. Default port transport leaves the protocol to Blaxel (HTTP); plain - maps to TCP. Ports 80, 443, and 8080 are reserved. Kit create timeout is the - local deadline; `@blaxel/core`'s native creation timeout is not forwarded. + maps to TCP; TLS maps to TLS; HTTP/2 over TLS is rejected. Ports 80, 443, and + 8080 are reserved. Kit create timeout is the local deadline; `@blaxel/core`'s + native creation timeout is not forwarded. ## RuntimeConfig diff --git a/website/content/docs/sdk/enums.mdx b/website/content/docs/sdk/enums.mdx index 87a2cfd0..2a6af230 100644 --- a/website/content/docs/sdk/enums.mdx +++ b/website/content/docs/sdk/enums.mdx @@ -89,7 +89,7 @@ Transport for a published port. Blaxel: `Default` omits protocol (Blaxel defaults to HTTP). `Plain` → TCP, - `Tls` → TLS, `Http2Tls` → HTTP. + `Tls` → TLS. `Http2Tls` is rejected. ## IsolationKind diff --git a/website/content/docs/sdk/process.mdx b/website/content/docs/sdk/process.mdx index 223775eb..0ce31a11 100644 --- a/website/content/docs/sdk/process.mdx +++ b/website/content/docs/sdk/process.mdx @@ -125,8 +125,10 @@ Outcome of a finished command. Blaxel returns both `stdout` and `stderr`. `argv` is sent as a POSIX-quoted - shell string. Omit/zero timeout means no Blaxel process auto-kill; the - blocking wait can still end at Blaxel's edge hold. Runs are blocking. + shell string. Omit or zero timeout means no process auto-kill; Kit polls + until the command ends or the call is canceled. A timeout of at most 60 + seconds uses Blaxel's blocking completion request. A longer timeout sets + the process auto-kill and polls. `run` waits until the command ends. @@ -152,8 +154,10 @@ Outcome of a finished command. Blaxel returns both Stdout and Stderr. Argv is sent as a POSIX-quoted shell - string. Omit/zero Timeout means no Blaxel process auto-kill; the blocking wait - can still end at Blaxel's edge hold. Runs are blocking. + string. Omit or zero Timeout means no process auto-kill; Kit polls until the + command ends or the call is canceled. A Timeout of at most 60 seconds uses + Blaxel's blocking completion request. A longer Timeout sets the process + auto-kill and polls. Run waits until the command ends. From e8c2f89e37c4256ec2e4509cb3b3a527295db9d3 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 20:34:39 +0530 Subject: [PATCH 20/60] Refactor Blaxel Go/TS providers for leaner structure and readability. Split orchestration from process/filesystem/local helpers, replace dense create/network conditionals with named predicates, and use internal param objects so behavior stays identical while the modules stay easier to follow. Co-authored-by: Cursor Entire-Checkpoint: 01M72ZTRYCA99YYTPBBQ3ARHZP --- sdks/go/providers/blaxel/client.go | 58 +-- sdks/go/providers/blaxel/creation.go | 120 +++-- sdks/go/providers/blaxel/errors.go | 180 +++++--- sdks/go/providers/blaxel/filesystem.go | 236 ++++------ sdks/go/providers/blaxel/instance.go | 24 + sdks/go/providers/blaxel/lifecycle.go | 17 +- sdks/go/providers/blaxel/local.go | 76 +++ sdks/go/providers/blaxel/metadata.go | 32 +- sdks/go/providers/blaxel/process.go | 128 +++-- sdks/go/providers/blaxel/process_test.go | 35 ++ sdks/go/providers/blaxel/provider.go | 50 +- sdks/go/providers/blaxel/provider_checks.go | 48 +- sdks/go/providers/blaxel/provider_mappings.go | 12 +- sdks/go/providers/blaxel/sandbox.go | 30 +- .../providers/blaxel/src/blaxel.test.ts | 93 +++- .../typescript/providers/blaxel/src/blaxel.ts | 436 +++++------------- .../blaxel/src/checks/provider_checks.ts | 145 +++--- .../blaxel/src/config/provider_config.ts | 29 +- .../typescript/providers/blaxel/src/errors.ts | 367 +++++---------- .../providers/blaxel/src/filesystem.ts | 76 +++ sdks/typescript/providers/blaxel/src/local.ts | 96 ++++ .../providers/blaxel/src/mappings/mappings.ts | 245 ++++++---- .../blaxel/src/mappings/provider_mappings.ts | 24 +- .../typescript/providers/blaxel/src/native.ts | 79 ++++ .../providers/blaxel/src/process.ts | 134 ++++++ specs/providers/blaxel.yaml | 13 +- 26 files changed, 1551 insertions(+), 1232 deletions(-) create mode 100644 sdks/go/providers/blaxel/instance.go create mode 100644 sdks/go/providers/blaxel/local.go create mode 100644 sdks/typescript/providers/blaxel/src/filesystem.ts create mode 100644 sdks/typescript/providers/blaxel/src/local.ts create mode 100644 sdks/typescript/providers/blaxel/src/native.ts create mode 100644 sdks/typescript/providers/blaxel/src/process.ts diff --git a/sdks/go/providers/blaxel/client.go b/sdks/go/providers/blaxel/client.go index 4ada8b90..c0dba657 100644 --- a/sdks/go/providers/blaxel/client.go +++ b/sdks/go/providers/blaxel/client.go @@ -36,35 +36,31 @@ func (s *clientSettings) applyEndpoint() error { if s.source.Endpoint == nil { return nil } - return sandbox.NewError(&sandbox.ErrorInfo{ - Kind: sandbox.ErrorKindUnsupported, - Provider: "blaxel", - Operation: "initialize", - Field: sandbox.ConfigFieldEndpoint, - Message: "sandbox-kit blaxel: Endpoint cannot be configured through this SDK's public client constructor", - }, nil) + return unsupportedClientField(sandbox.ConfigFieldEndpoint, "Endpoint cannot be configured through this SDK's public client constructor") } func (s *clientSettings) applyScope() error { if s.source.Scope == nil { return nil } - if s.source.Scope.AppName != nil { - return unsupportedClientField(sandbox.ConfigFieldScopeAppName, "unsupported client configuration field") - } - if s.source.Scope.Environment != nil { - return unsupportedClientField(sandbox.ConfigFieldScopeEnvironment, "unsupported client configuration field") - } - if s.source.Scope.OrganizationId != nil { - return unsupportedClientField(sandbox.ConfigFieldScopeOrganizationId, "unsupported client configuration field") - } - if s.source.Scope.ProjectId != nil { - return unsupportedClientField(sandbox.ConfigFieldScopeProjectId, "unsupported client configuration field") + scope := s.source.Scope + for _, check := range []struct { + set bool + field string + }{ + {scope.AppName != nil, sandbox.ConfigFieldScopeAppName}, + {scope.Environment != nil, sandbox.ConfigFieldScopeEnvironment}, + {scope.OrganizationId != nil, sandbox.ConfigFieldScopeOrganizationId}, + {scope.ProjectId != nil, sandbox.ConfigFieldScopeProjectId}, + } { + if check.set { + return unsupportedClientField(check.field, "unsupported client configuration field") + } } - if s.source.Scope.Workspace == nil { + if scope.Workspace == nil { return nil } - workspace := s.source.Scope.GetWorkspace() + workspace := scope.GetWorkspace() if workspace == "" { return invalidClientField(sandbox.ConfigFieldScopeWorkspace, "workspace requires a non-blank value") } @@ -87,33 +83,25 @@ func (s *clientSettings) applyAuth() error { } s.opts = append(s.opts, option.WithAPIKey(credentials.ApiKey.Key)) default: - return sandbox.NewError(&sandbox.ErrorInfo{ - Kind: sandbox.ErrorKindUnsupported, - Provider: "blaxel", - Operation: "initialize", - Field: sandbox.ConfigFieldAuth, - Message: "sandbox-kit blaxel: unsupported authentication mode", - }, nil) + return unsupportedClientField(sandbox.ConfigFieldAuth, "unsupported authentication mode") } return nil } func invalidClientField(field, message string) error { - return sandbox.NewError(&sandbox.ErrorInfo{ + return kitError(kitFault{ Kind: sandbox.ErrorKindInvalidArgument, - Provider: "blaxel", Operation: "initialize", Field: field, - Message: "sandbox-kit blaxel: " + message, - }, nil) + Message: message, + }) } func unsupportedClientField(field, message string) error { - return sandbox.NewError(&sandbox.ErrorInfo{ + return kitError(kitFault{ Kind: sandbox.ErrorKindUnsupported, - Provider: "blaxel", Operation: "initialize", Field: field, - Message: "sandbox-kit blaxel: " + message, - }, nil) + Message: message, + }) } diff --git a/sdks/go/providers/blaxel/creation.go b/sdks/go/providers/blaxel/creation.go index 63c97224..9d3c5fdd 100644 --- a/sdks/go/providers/blaxel/creation.go +++ b/sdks/go/providers/blaxel/creation.go @@ -21,6 +21,12 @@ type creation struct { region string } +var reservedBlaxelPorts = map[uint32]struct{}{ + 80: {}, + 443: {}, + 8080: {}, +} + // planCreate checks the request and builds SDK arguments without contacting Blaxel. func planCreate(request *sandbox.CreateOptions, region string) (createPlan, error) { if request == nil { @@ -75,7 +81,7 @@ func (c *creation) resources() error { if err != nil { return err } - if remaining.CpuCores != nil || remaining.CpuLimitCores != nil || remaining.MemoryLimitMib != nil || remaining.DiskMib != nil { + if hasUnmappedResourceFields(remaining) { return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResources, "one or more Blaxel resource fields are not mapped") } if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldResources, remaining); err != nil { @@ -114,7 +120,7 @@ func (c *creation) lifetime() error { } } config.IdleTerminate = nil - if config.Ephemeral != nil || config.IdleStop != nil || config.IdlePause != nil || config.StoppedArchive != nil || config.StoppedDelete != nil { + if hasUnsupportedLifetimeFields(config) { return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldLifetime, "one or more Blaxel lifetime fields are not mapped") } if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldLifetime, config); err != nil { @@ -159,7 +165,7 @@ func (c *creation) runtime() error { if config.WorkingDirectory != nil { return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldRuntimeWorkingDirectory, "Blaxel create-time working directory is not mapped") } - if config.Entrypoint != nil || config.Pty != nil || config.User != nil || config.Language != nil || config.Version != nil { + if hasUnsupportedRuntimeFields(config) { return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldRuntime, "runtime user, language, version, entrypoint, and PTY are not mapped by Blaxel") } if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldRuntime, config); err != nil { @@ -174,7 +180,7 @@ func (c *creation) isolation() error { return nil } config := cloneMessage(c.request.Isolation) - if config.Kind != sandbox.IsolationKindDefault || config.ProviderKind != nil || config.NestedVirtualization != nil { + if hasUnsupportedIsolation(config) { return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldIsolation, "Blaxel isolation class is not mapped") } if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldIsolation, config); err != nil { @@ -185,10 +191,7 @@ func (c *creation) isolation() error { } func (c *creation) security() error { - if c.request.Security == nil { - return nil - } - return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldSecurity, "Blaxel security fields are not mapped") + return rejectIfSet(c.request.Security != nil, sandbox.CreateFieldSecurity, "Blaxel security fields are not mapped") } func (c *creation) network() error { @@ -196,14 +199,7 @@ func (c *creation) network() error { return nil } config := cloneMessage(c.request.Network) - if config.Egress != sandbox.EgressModeDefault || - config.OutboundCidrs != nil || - config.InboundCidrs != nil || - config.PrivateNetwork != nil || - config.LinkedSandbox != nil || - config.ProxyReference != nil || - config.OutboundProxyUrl != nil || - config.CustomDomain != nil { + if hasUnsupportedNetwork(config) { return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldNetwork, "requested network settings are unsupported by Blaxel") } if config.OutboundDomains != nil { @@ -211,22 +207,9 @@ func (c *creation) network() error { config.OutboundDomains = nil } if len(config.Ports) > 0 { - ports := make([]blaxel.PortParam, 0, len(config.Ports)) - for _, binding := range config.Ports { - if binding == nil { - continue - } - if binding.Port == 80 || binding.Port == 443 || binding.Port == 8080 { - return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldNetworkPortsPort, "ports 80, 443, and 8080 are reserved by Blaxel") - } - protocol, err := blaxelPortProtocol(binding.Transport) - if err != nil { - return err - } - ports = append(ports, blaxel.PortParam{ - Target: int64(binding.Port), - Protocol: protocol, - }) + ports, err := mapPorts(config.Ports) + if err != nil { + return err } c.params.Sandbox.Spec.Runtime.Ports = ports config.Ports = nil @@ -239,18 +222,36 @@ func (c *creation) network() error { return nil } +func mapPorts(bindings []*sandbox.PortBinding) ([]blaxel.PortParam, error) { + ports := make([]blaxel.PortParam, 0, len(bindings)) + for _, binding := range bindings { + if binding == nil { + continue + } + if isReservedPort(binding.Port) { + return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldNetworkPortsPort, "ports 80, 443, and 8080 are reserved by Blaxel") + } + protocol, err := blaxelPortProtocol(binding.Transport) + if err != nil { + return nil, err + } + ports = append(ports, blaxel.PortParam{ + Target: int64(binding.Port), + Protocol: protocol, + }) + } + return ports, nil +} + func blaxelPortProtocol(transport sandbox.PortTransport) (blaxel.PortProtocol, error) { switch transport { case sandbox.PortTransportDefault: - // Kit Default leaves the protocol to the provider. Blaxel defaults to HTTP - // when protocol is omitted (omitzero on PortParam). return "", nil case sandbox.PortTransportPlain: return blaxel.PortProtocolTcp, nil case sandbox.PortTransportTLS: return blaxel.PortProtocolTls, nil default: - // Blaxel has no HTTP/2 port protocol (Kit Http2Tls included). return "", providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldNetworkPortsTransport, "requested port transport is unsupported") } } @@ -260,8 +261,7 @@ func (c *creation) provisioning() error { return nil } config := cloneMessage(c.request.Provisioning) - // Client.Create already applies this as the context deadline. The Go Blaxel - // SDK's NewInstance has no separate creation-timeout argument. + // Client.Create already applies Timeout as the context deadline. config.Timeout = nil if config.QueueTimeout != nil { return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldProvisioningQueueTimeout, "Blaxel queue timeout is unsupported") @@ -281,15 +281,49 @@ func (c *creation) provisioning() error { } func (c *creation) readiness() error { - if c.request.Readiness == nil { - return nil - } - return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldReadiness, "Blaxel readiness probes are not mapped") + return rejectIfSet(c.request.Readiness != nil, sandbox.CreateFieldReadiness, "Blaxel readiness probes are not mapped") } func (c *creation) observability() error { - if c.request.Observability == nil { + return rejectIfSet(c.request.Observability != nil, sandbox.CreateFieldObservability, "Blaxel observability fields are not mapped") +} + +func rejectIfSet(set bool, field, message string) error { + if !set { return nil } - return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldObservability, "Blaxel observability fields are not mapped") + return providerCheckError(sandbox.ErrorKindUnsupported, field, message) +} + +func isReservedPort(port uint32) bool { + _, ok := reservedBlaxelPorts[port] + return ok +} + +func hasUnmappedResourceFields(r *sandbox.Resources) bool { + return r.CpuCores != nil || r.CpuLimitCores != nil || r.MemoryLimitMib != nil || r.DiskMib != nil +} + +func hasUnsupportedLifetimeFields(c *sandbox.LifetimePolicy) bool { + return c.Ephemeral != nil || c.IdleStop != nil || c.IdlePause != nil || + c.StoppedArchive != nil || c.StoppedDelete != nil +} + +func hasUnsupportedRuntimeFields(c *sandbox.RuntimeConfig) bool { + return c.Entrypoint != nil || c.Pty != nil || c.User != nil || c.Language != nil || c.Version != nil +} + +func hasUnsupportedIsolation(c *sandbox.IsolationConfig) bool { + return c.Kind != sandbox.IsolationKindDefault || c.ProviderKind != nil || c.NestedVirtualization != nil +} + +func hasUnsupportedNetwork(c *sandbox.NetworkConfig) bool { + return c.Egress != sandbox.EgressModeDefault || + c.OutboundCidrs != nil || + c.InboundCidrs != nil || + c.PrivateNetwork != nil || + c.LinkedSandbox != nil || + c.ProxyReference != nil || + c.OutboundProxyUrl != nil || + c.CustomDomain != nil } diff --git a/sdks/go/providers/blaxel/errors.go b/sdks/go/providers/blaxel/errors.go index a4e4d3d5..5df67a40 100644 --- a/sdks/go/providers/blaxel/errors.go +++ b/sdks/go/providers/blaxel/errors.go @@ -9,6 +9,84 @@ import ( "github.com/sandbox-kit/kit/sdks/go/sandbox" ) +var providerCodeKinds = map[string]sandbox.ErrorKind{ + sdkblaxel.ErrAuthenticationRequired: sandbox.ErrorKindAuthentication, + sdkblaxel.ErrAuthenticationFailed: sandbox.ErrorKindAuthentication, + "TOKEN_REVOKED": sandbox.ErrorKindAuthentication, + + sdkblaxel.ErrForbidden: sandbox.ErrorKindPermissionDenied, + "UNAUTHORIZED": sandbox.ErrorKindPermissionDenied, + "UNAUTHORIZED_WORKSPACE": sandbox.ErrorKindPermissionDenied, + + sdkblaxel.ErrRouteNotFound: sandbox.ErrorKindNotFound, + sdkblaxel.ErrWorkloadNotFound: sandbox.ErrorKindNotFound, + sdkblaxel.ErrWorkspaceNotFound: sandbox.ErrorKindNotFound, + "IMAGE_NOT_FOUND": sandbox.ErrorKindNotFound, + "VOLUME_NOT_FOUND": sandbox.ErrorKindNotFound, + "ACCOUNT_NOT_FOUND": sandbox.ErrorKindNotFound, + "SANDBOX_DELETED": sandbox.ErrorKindNotFound, + "VOLUME_DELETED": sandbox.ErrorKindNotFound, + + "SANDBOX_ALREADY_EXISTS": sandbox.ErrorKindAlreadyExists, + "VOLUME_ALREADY_ATTACHED": sandbox.ErrorKindAlreadyExists, + + sdkblaxel.ErrBadRequest: sandbox.ErrorKindInvalidArgument, + sdkblaxel.ErrPolicyViolation: sandbox.ErrorKindInvalidArgument, + "INVALID_INPUT": sandbox.ErrorKindInvalidArgument, + "INVALID_IMAGE": sandbox.ErrorKindInvalidArgument, + "INVALID_PORTS": sandbox.ErrorKindInvalidArgument, + "INVALID_REGION": sandbox.ErrorKindInvalidArgument, + "INVALID_VOLUME": sandbox.ErrorKindInvalidArgument, + "INVALID_VOLUME_COUNT": sandbox.ErrorKindInvalidArgument, + "INVALID_VOLUME_REGION": sandbox.ErrorKindInvalidArgument, + "VALIDATION_FAILED": sandbox.ErrorKindInvalidArgument, + "VALIDATION_ERROR": sandbox.ErrorKindInvalidArgument, + "WORKSPACE_REQUIRED": sandbox.ErrorKindInvalidArgument, + + "CREATION_TIMEOUT": sandbox.ErrorKindTimeout, + "UPSTREAM_CONNECT_TIMEOUT": sandbox.ErrorKindTimeout, + "UPSTREAM_TIMEOUT": sandbox.ErrorKindTimeout, + + sdkblaxel.ErrUsageLimitExceeded: sandbox.ErrorKindResourceExhausted, + "QUOTA_EXCEEDED": sandbox.ErrorKindResourceExhausted, + + "RATE_LIMIT_EXCEEDED": sandbox.ErrorKindRateLimited, + "WORKSPACE_RATE_LIMIT_EXCEEDED": sandbox.ErrorKindRateLimited, + + "SANDBOX_DELETION_IN_PROGRESS": sandbox.ErrorKindConflict, + + sdkblaxel.ErrWorkloadUnavailable: sandbox.ErrorKindUnavailable, + "ROUTING_UNAVAILABLE": sandbox.ErrorKindUnavailable, + "SERVICE_UNAVAILABLE": sandbox.ErrorKindUnavailable, + "AUTHENTICATION_UNAVAILABLE": sandbox.ErrorKindUnavailable, + "UPSTREAM_CONNECT_FAILED": sandbox.ErrorKindUnavailable, + "UPSTREAM_ERROR": sandbox.ErrorKindUnavailable, + "GATEWAY_INTERNAL_ERROR": sandbox.ErrorKindUnavailable, + + "WORKLOAD_FAILED": sandbox.ErrorKindInternal, + "INTERNAL_ERROR": sandbox.ErrorKindInternal, + "HANDLER_ERROR": sandbox.ErrorKindInternal, + "DATABASE_ERROR": sandbox.ErrorKindInternal, +} + +var providerStatusKinds = map[int]sandbox.ErrorKind{ + 400: sandbox.ErrorKindInvalidArgument, + 422: sandbox.ErrorKindInvalidArgument, + 401: sandbox.ErrorKindAuthentication, + 402: sandbox.ErrorKindResourceExhausted, + 403: sandbox.ErrorKindPermissionDenied, + 404: sandbox.ErrorKindNotFound, + 410: sandbox.ErrorKindNotFound, + 408: sandbox.ErrorKindTimeout, + 409: sandbox.ErrorKindConflict, + 412: sandbox.ErrorKindFailedPrecondition, + 429: sandbox.ErrorKindRateLimited, + 500: sandbox.ErrorKindInternal, + 502: sandbox.ErrorKindUnavailable, + 503: sandbox.ErrorKindUnavailable, + 504: sandbox.ErrorKindTimeout, +} + func mapProviderError(err error, operation string) error { if err == nil { return nil @@ -17,14 +95,17 @@ func mapProviderError(err error, operation string) error { if errors.As(err, &local) { return sdk.WithErrorContext(err, "blaxel", operation) } + kind := sandbox.ErrorKindUnknown var statusCode *uint32 var providerCode, providerSource *string - if errors.Is(err, context.Canceled) { + switch { + case errors.Is(err, context.Canceled): kind = sandbox.ErrorKindCanceled - } else if errors.Is(err, context.DeadlineExceeded) { + case errors.Is(err, context.DeadlineExceeded): kind = sandbox.ErrorKindTimeout } + var native *sdkblaxel.Error if errors.As(err, &native) { if native.StatusCode > 0 { @@ -33,73 +114,21 @@ func mapProviderError(err error, operation string) error { if native.ErrorCode != "" { providerCode = sandbox.Value(native.ErrorCode) } - if native.BlaxelSource != "" { + switch { + case native.BlaxelSource != "": providerSource = sandbox.Value(native.BlaxelSource) - } else if native.Origin != "" { + case native.Origin != "": providerSource = sandbox.Value(native.Origin) } if kind == sandbox.ErrorKindUnknown { - switch native.ErrorCode { - case sdkblaxel.ErrAuthenticationRequired, sdkblaxel.ErrAuthenticationFailed, "TOKEN_REVOKED": - kind = sandbox.ErrorKindAuthentication - case sdkblaxel.ErrForbidden, "UNAUTHORIZED", "UNAUTHORIZED_WORKSPACE": - kind = sandbox.ErrorKindPermissionDenied - case sdkblaxel.ErrRouteNotFound, sdkblaxel.ErrWorkloadNotFound, sdkblaxel.ErrWorkspaceNotFound, - "IMAGE_NOT_FOUND", "VOLUME_NOT_FOUND", "ACCOUNT_NOT_FOUND", "SANDBOX_DELETED", "VOLUME_DELETED": - kind = sandbox.ErrorKindNotFound - case "SANDBOX_ALREADY_EXISTS", "VOLUME_ALREADY_ATTACHED": - // Control-plane codes (not all are gateway apierror constants). - kind = sandbox.ErrorKindAlreadyExists - case sdkblaxel.ErrBadRequest, sdkblaxel.ErrPolicyViolation, - "INVALID_INPUT", "INVALID_IMAGE", "INVALID_PORTS", "INVALID_REGION", - "INVALID_VOLUME", "INVALID_VOLUME_COUNT", "INVALID_VOLUME_REGION", - "VALIDATION_FAILED", "VALIDATION_ERROR", "WORKSPACE_REQUIRED": - kind = sandbox.ErrorKindInvalidArgument - case "CREATION_TIMEOUT", "UPSTREAM_CONNECT_TIMEOUT", "UPSTREAM_TIMEOUT": - kind = sandbox.ErrorKindTimeout - case sdkblaxel.ErrUsageLimitExceeded, "QUOTA_EXCEEDED": - kind = sandbox.ErrorKindResourceExhausted - case "RATE_LIMIT_EXCEEDED", "WORKSPACE_RATE_LIMIT_EXCEEDED": - kind = sandbox.ErrorKindRateLimited - case "SANDBOX_DELETION_IN_PROGRESS": - kind = sandbox.ErrorKindConflict - case sdkblaxel.ErrWorkloadUnavailable, "ROUTING_UNAVAILABLE", "SERVICE_UNAVAILABLE", - "AUTHENTICATION_UNAVAILABLE", "UPSTREAM_CONNECT_FAILED", "UPSTREAM_ERROR", - "GATEWAY_INTERNAL_ERROR": - kind = sandbox.ErrorKindUnavailable - case "WORKLOAD_FAILED", "INTERNAL_ERROR", "HANDLER_ERROR", "DATABASE_ERROR": - kind = sandbox.ErrorKindInternal - } - } - if kind == sandbox.ErrorKindUnknown { - switch native.StatusCode { - case 400, 422: - kind = sandbox.ErrorKindInvalidArgument - case 401: - kind = sandbox.ErrorKindAuthentication - case 402: - kind = sandbox.ErrorKindResourceExhausted - case 403: - kind = sandbox.ErrorKindPermissionDenied - case 404, 410: - kind = sandbox.ErrorKindNotFound - case 408: - kind = sandbox.ErrorKindTimeout - case 409: - kind = sandbox.ErrorKindConflict - case 412: - kind = sandbox.ErrorKindFailedPrecondition - case 429: - kind = sandbox.ErrorKindRateLimited - case 500: - kind = sandbox.ErrorKindInternal - case 502, 503: - kind = sandbox.ErrorKindUnavailable - case 504: - kind = sandbox.ErrorKindTimeout + if mapped, ok := providerCodeKinds[native.ErrorCode]; ok { + kind = mapped + } else if mapped, ok := providerStatusKinds[native.StatusCode]; ok { + kind = mapped } } } + return sandbox.NewError(&sandbox.ErrorInfo{ Kind: kind, Provider: "blaxel", @@ -110,3 +139,30 @@ func mapProviderError(err error, operation string) error { ProviderSource: providerSource, }, err) } + +// kitFault is the internal shape for Blaxel-scoped validation / response errors. +type kitFault struct { + Kind sandbox.ErrorKind + Operation string + Field string + Message string +} + +func invalidResponse(operation, field, message string) error { + return kitError(kitFault{ + Kind: sandbox.ErrorKindInvalidResponse, + Operation: operation, + Field: field, + Message: message, + }) +} + +func kitError(fault kitFault) error { + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: fault.Kind, + Provider: "blaxel", + Operation: fault.Operation, + Field: fault.Field, + Message: "sandbox-kit blaxel: " + fault.Message, + }, nil) +} diff --git a/sdks/go/providers/blaxel/filesystem.go b/sdks/go/providers/blaxel/filesystem.go index 6356d5c0..f3275cde 100644 --- a/sdks/go/providers/blaxel/filesystem.go +++ b/sdks/go/providers/blaxel/filesystem.go @@ -4,7 +4,6 @@ import ( "context" "errors" "os" - "path/filepath" "time" blaxel "github.com/blaxel-ai/sdk-go" @@ -16,29 +15,20 @@ import ( var _ sandbox.SandboxFiles = (*backend)(nil) func (b *backend) ReadFile(ctx context.Context, id, path string, _ *sandbox.FileReadOptions) ([]byte, error) { - instance, err := b.fileSandbox(ctx, id, "read_file") - if err != nil { - return nil, err - } - data, err := instance.FS.ReadBinary(ctx, path) - if err != nil { - return nil, mapProviderError(err, "read_file") - } - return data, nil + return withFS(b, ctx, fsCall{ID: id, Operation: "read_file"}, func(fs *blaxel.SandboxInstanceFSService) ([]byte, error) { + return fs.ReadBinary(ctx, path) + }) } func (b *backend) WriteFile(ctx context.Context, id, path string, data []byte, _ *sandbox.FileWriteOptions) error { - instance, err := b.fileSandbox(ctx, id, "write_file") - if err != nil { - return err - } if data == nil { data = []byte{} } - if _, err := instance.FS.WriteBinary(ctx, path, data, ""); err != nil { - return mapProviderError(err, "write_file") - } - return nil + _, err := withFS(b, ctx, fsCall{ID: id, Operation: "write_file"}, func(fs *blaxel.SandboxInstanceFSService) (struct{}, error) { + _, err := fs.WriteBinary(ctx, path, data, "") + return struct{}{}, err + }) + return err } func (b *backend) CopyFromLocal(ctx context.Context, id, localPath, remotePath string, _ *sandbox.FileCopyOptions) error { @@ -49,14 +39,11 @@ func (b *backend) CopyFromLocal(ctx context.Context, id, localPath, remotePath s if err != nil { return localPathError(err, "copy_from_local") } - instance, err := b.fileSandbox(ctx, id, "copy_from_local") - if err != nil { - return err - } - if _, err := instance.FS.WriteBinary(ctx, remotePath, data, ""); err != nil { - return mapProviderError(err, "copy_from_local") - } - return nil + _, err = withFS(b, ctx, fsCall{ID: id, Operation: "copy_from_local"}, func(fs *blaxel.SandboxInstanceFSService) (struct{}, error) { + _, err := fs.WriteBinary(ctx, remotePath, data, "") + return struct{}{}, err + }) + return err } func (b *backend) CopyToLocal(ctx context.Context, id, remotePath, localPath string, _ *sandbox.FileCopyOptions) error { @@ -80,41 +67,36 @@ func (b *backend) MakeDirectory(ctx context.Context, id, path string, options *s if err := rejectParents(options); err != nil { return err } - instance, err := b.fileSandbox(ctx, id, "make_directory") - if err != nil { - return err - } - if _, err := instance.FS.Mkdir(ctx, path, ""); err != nil { - return mapProviderError(err, "make_directory") - } - return nil + _, err := withFS(b, ctx, fsCall{ID: id, Operation: "make_directory"}, func(fs *blaxel.SandboxInstanceFSService) (struct{}, error) { + _, err := fs.Mkdir(ctx, path, "") + return struct{}{}, err + }) + return err } func (b *backend) ListDirectory(ctx context.Context, id, path string, _ *sandbox.ListDirectoryOptions) ([]*sandbox.FileInfo, error) { - instance, err := b.fileSandbox(ctx, id, "list_directory") - if err != nil { - return nil, err - } - listing, err := instance.FS.LS(ctx, path) - if err != nil { - return nil, mapProviderError(err, "list_directory") - } - result := make([]*sandbox.FileInfo, 0, len(listing.Files)+len(listing.Subdirectories)) - for _, entry := range listing.Files { - info, err := blaxelFileInfo(entry, sandbox.FileTypeFile, "list_directory") + return withFS(b, ctx, fsCall{ID: id, Operation: "list_directory"}, func(fs *blaxel.SandboxInstanceFSService) ([]*sandbox.FileInfo, error) { + listing, err := fs.LS(ctx, path) if err != nil { return nil, err } - result = append(result, info) - } - for _, entry := range listing.Subdirectories { - result = append(result, &sandbox.FileInfo{ - Name: entry.Name, - Path: entry.Path, - Type: sandbox.FileTypeDirectory, - }) - } - return result, nil + result := make([]*sandbox.FileInfo, 0, len(listing.Files)+len(listing.Subdirectories)) + for _, entry := range listing.Files { + info, err := blaxelFileInfo(entry, sandbox.FileTypeFile, "list_directory") + if err != nil { + return nil, err + } + result = append(result, info) + } + for _, entry := range listing.Subdirectories { + result = append(result, &sandbox.FileInfo{ + Name: entry.Name, + Path: entry.Path, + Type: sandbox.FileTypeDirectory, + }) + } + return result, nil + }) } func (b *backend) Stat(ctx context.Context, id, path string, _ *sandbox.StatOptions) (*sandbox.FileInfo, error) { @@ -122,22 +104,15 @@ func (b *backend) Stat(ctx context.Context, id, path string, _ *sandbox.StatOpti if err != nil { return nil, err } - // SandboxInstanceFSService has no public Get/Stat; metadata is on the lower - // SandboxFilesystemService. Rebuild with the instance base URL so Stat uses - // the same sandbox endpoint as instance.FS without private-field access. + // No public Stat on SandboxInstanceFSService; rebuild the lower FS client + // against the instance URL so metadata uses the same sandbox endpoint. fs := b.filesystemService(instance) res, err := fs.Get(ctx, path, blaxel.SandboxFilesystemGetParams{}) if err != nil { return nil, mapProviderError(err, "stat") } if res == nil { - return nil, sandbox.NewError(&sandbox.ErrorInfo{ - Kind: sandbox.ErrorKindInvalidResponse, - Provider: "blaxel", - Operation: "stat", - Field: sandbox.FileFieldPath, - Message: "sandbox-kit blaxel: SDK returned no file metadata", - }, nil) + return nil, invalidResponse("stat", sandbox.FileFieldPath, "SDK returned no file metadata") } if res.JSON.Files.Valid() || res.JSON.Subdirectories.Valid() { dir := res.AsDirectory() @@ -160,29 +135,49 @@ func (b *backend) Stat(ctx context.Context, id, path string, _ *sandbox.StatOpti } func (b *backend) Remove(ctx context.Context, id, path string, options *sandbox.RemoveOptions) error { - instance, err := b.fileSandbox(ctx, id, "remove") + recursive := options != nil && options.GetRecursive() + _, err := withFS(b, ctx, fsCall{ID: id, Operation: "remove"}, func(fs *blaxel.SandboxInstanceFSService) (struct{}, error) { + _, err := fs.RM(ctx, path, recursive) + return struct{}{}, err + }) + return err +} + +type fsCall struct { + ID string + Operation string +} + +func withFS[T any]( + b *backend, + ctx context.Context, + call fsCall, + fn func(*blaxel.SandboxInstanceFSService) (T, error), +) (T, error) { + var zero T + instance, err := b.fileSandbox(ctx, call.ID, call.Operation) if err != nil { - return err + return zero, err } - recursive := options != nil && options.GetRecursive() - if _, err := instance.FS.RM(ctx, path, recursive); err != nil { - return mapProviderError(err, "remove") + result, err := fn(instance.FS) + if err != nil { + // Preserve SandboxErrors raised inside the callback (e.g. list mapping). + var local *sandbox.Error + if errors.As(err, &local) { + return zero, err + } + return zero, mapProviderError(err, call.Operation) } - return nil + return result, nil } func (b *backend) fileSandbox(ctx context.Context, id, operation string) (*blaxel.SandboxInstance, error) { - instance, err := b.client.Sandboxes.GetInstance(ctx, id) + instance, err := b.getInstance(ctx, id, operation) if err != nil { - return nil, mapProviderError(err, operation) + return nil, err } if instance == nil || instance.FS == nil { - return nil, sandbox.NewError(&sandbox.ErrorInfo{ - Kind: sandbox.ErrorKindInvalidResponse, - Provider: "blaxel", - Operation: operation, - Message: "sandbox-kit blaxel: SDK returned no filesystem", - }, nil) + return nil, invalidResponse(operation, "", "SDK returned no filesystem") } return instance, nil } @@ -199,92 +194,17 @@ func rejectParents(options *sandbox.MakeDirectoryOptions) error { if options == nil || options.Parents == nil || *options.Parents { return nil } - return sandbox.NewError(&sandbox.ErrorInfo{ + return kitError(kitFault{ Kind: sandbox.ErrorKindUnsupported, - Provider: "blaxel", Operation: "make_directory", Field: sandbox.FileFieldParents, - Message: "sandbox-kit blaxel: parents false is not available", - }, nil) -} - -func localSourceFile(path, operation string) error { - info, err := os.Stat(path) - if err != nil { - return localPathError(err, operation) - } - if info.IsDir() { - return localDirectoryError(operation) - } - return nil -} - -func localDestination(path, operation string) error { - info, err := os.Stat(path) - if errors.Is(err, os.ErrNotExist) { - return nil - } - if err != nil { - return localPathError(err, operation) - } - if info.IsDir() { - return localDirectoryError(operation) - } - return nil -} - -func ensureLocalParent(path, operation string) error { - parent := filepath.Dir(path) - if parent == "." || parent == string(filepath.Separator) { - return nil - } - if err := os.MkdirAll(parent, 0o755); err != nil { - return localPathError(err, operation) - } - return nil -} - -func localDirectoryError(operation string) error { - return sandbox.NewError(&sandbox.ErrorInfo{ - Kind: sandbox.ErrorKindInvalidArgument, - Provider: "blaxel", - Operation: operation, - Field: sandbox.FileFieldLocalPath, - Message: "sandbox-kit blaxel: local path is a directory", - }, nil) -} - -func localPathError(err error, operation string) error { - kind := sandbox.ErrorKindInvalidArgument - if errors.Is(err, os.ErrNotExist) { - kind = sandbox.ErrorKindNotFound - } - return sandbox.NewError(&sandbox.ErrorInfo{ - Kind: kind, - Provider: "blaxel", - Operation: operation, - Field: sandbox.FileFieldLocalPath, - Message: err.Error(), - }, err) -} - -func mapCallerPathError(err error, operation, localPath string) error { - var pathError *os.PathError - if errors.As(err, &pathError) && filepath.Clean(pathError.Path) == filepath.Clean(localPath) { - return localPathError(pathError, operation) - } - return mapProviderError(err, operation) + Message: "parents false is not available", + }) } func blaxelFileInfo(entry blaxel.FilesystemRead, kind sandbox.FileType, operation string) (*sandbox.FileInfo, error) { if entry.Path == "" && entry.Name == "" { - return nil, sandbox.NewError(&sandbox.ErrorInfo{ - Kind: sandbox.ErrorKindInvalidResponse, - Provider: "blaxel", - Operation: operation, - Field: sandbox.FileFieldPath, - Message: "sandbox-kit blaxel: SDK returned no file metadata", - }, nil) + return nil, invalidResponse(operation, sandbox.FileFieldPath, "SDK returned no file metadata") } result := &sandbox.FileInfo{ Name: entry.Name, diff --git a/sdks/go/providers/blaxel/instance.go b/sdks/go/providers/blaxel/instance.go new file mode 100644 index 00000000..3d0d4af6 --- /dev/null +++ b/sdks/go/providers/blaxel/instance.go @@ -0,0 +1,24 @@ +package blaxel + +import ( + "context" + + blaxel "github.com/blaxel-ai/sdk-go" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +// getInstance loads a sandbox handle. Callers check FS / Process / Sandbox. +func (b *backend) getInstance(ctx context.Context, id, operation string) (*blaxel.SandboxInstance, error) { + instance, err := b.client.Sandboxes.GetInstance(ctx, id) + if err != nil { + return nil, mapProviderError(err, operation) + } + return instance, nil +} + +func requireSandbox(instance *blaxel.SandboxInstance, operation string) (*blaxel.Sandbox, error) { + if instance == nil || instance.Sandbox == nil { + return nil, invalidResponse(operation, sandbox.InfoFieldId, "SDK returned no sandbox") + } + return instance.Sandbox, nil +} diff --git a/sdks/go/providers/blaxel/lifecycle.go b/sdks/go/providers/blaxel/lifecycle.go index 408be25e..0eb3bc2f 100644 --- a/sdks/go/providers/blaxel/lifecycle.go +++ b/sdks/go/providers/blaxel/lifecycle.go @@ -12,20 +12,15 @@ var _ sandbox.SandboxDeleter = (*backend)(nil) // DeleteSandbox permanently deletes a Blaxel sandbox by name. // Start, stop, pause, archive, and terminate are not implemented. func (b *backend) DeleteSandbox(ctx context.Context, id string, _ *sandbox.DeleteOptions) (*sandbox.SandboxInfo, error) { - instance, err := b.client.Sandboxes.GetInstance(ctx, id) + instance, err := b.getInstance(ctx, id, "delete") if err != nil { - return nil, mapProviderError(err, "delete") + return nil, err } - if instance == nil || instance.Sandbox == nil { - return nil, sandbox.NewError(&sandbox.ErrorInfo{ - Kind: sandbox.ErrorKindInvalidResponse, - Provider: "blaxel", - Operation: "delete", - Field: sandbox.InfoFieldId, - Message: "sandbox-kit blaxel: SDK returned no sandbox", - }, nil) + native, err := requireSandbox(instance, "delete") + if err != nil { + return nil, err } - info, err := sandboxInfo(instance.Sandbox) + info, err := sandboxInfo(native) if err != nil { return nil, err } diff --git a/sdks/go/providers/blaxel/local.go b/sdks/go/providers/blaxel/local.go new file mode 100644 index 00000000..a89c8d5f --- /dev/null +++ b/sdks/go/providers/blaxel/local.go @@ -0,0 +1,76 @@ +package blaxel + +import ( + "errors" + "os" + "path/filepath" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func localSourceFile(path, operation string) error { + info, err := os.Stat(path) + if err != nil { + return localPathError(err, operation) + } + if info.IsDir() { + return localDirectoryError(operation) + } + return nil +} + +func localDestination(path, operation string) error { + info, err := os.Stat(path) + if errors.Is(err, os.ErrNotExist) { + return nil + } + if err != nil { + return localPathError(err, operation) + } + if info.IsDir() { + return localDirectoryError(operation) + } + return nil +} + +func ensureLocalParent(path, operation string) error { + parent := filepath.Dir(path) + if parent == "." || parent == string(filepath.Separator) { + return nil + } + if err := os.MkdirAll(parent, 0o755); err != nil { + return localPathError(err, operation) + } + return nil +} + +func localDirectoryError(operation string) error { + return kitError(kitFault{ + Kind: sandbox.ErrorKindInvalidArgument, + Operation: operation, + Field: sandbox.FileFieldLocalPath, + Message: "local path is a directory", + }) +} + +func localPathError(err error, operation string) error { + kind := sandbox.ErrorKindInvalidArgument + if errors.Is(err, os.ErrNotExist) { + kind = sandbox.ErrorKindNotFound + } + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: kind, + Provider: "blaxel", + Operation: operation, + Field: sandbox.FileFieldLocalPath, + Message: err.Error(), + }, err) +} + +func mapCallerPathError(err error, operation, localPath string) error { + var pathError *os.PathError + if errors.As(err, &pathError) && filepath.Clean(pathError.Path) == filepath.Clean(localPath) { + return localPathError(pathError, operation) + } + return mapProviderError(err, operation) +} diff --git a/sdks/go/providers/blaxel/metadata.go b/sdks/go/providers/blaxel/metadata.go index 80011120..375edcd5 100644 --- a/sdks/go/providers/blaxel/metadata.go +++ b/sdks/go/providers/blaxel/metadata.go @@ -41,12 +41,7 @@ func (s *observedSandbox) state() { case blaxel.StatusUploading, blaxel.StatusBuilding, blaxel.StatusDeploying, blaxel.StatusBuilt: s.info.State = sandbox.SandboxStateQueued case blaxel.StatusDeployed: - switch s.native.State { - case blaxel.SandboxStateStandby: - s.info.State = sandbox.SandboxStatePaused - default: - s.info.State = sandbox.SandboxStateRunning - } + s.info.State = runtimeState(s.native.State, sandbox.SandboxStateRunning) case blaxel.StatusDeleting, blaxel.StatusDeactivating: s.info.State = sandbox.SandboxStateDeleting case blaxel.StatusTerminated, blaxel.StatusDeactivated: @@ -61,15 +56,30 @@ func (s *observedSandbox) state() { case blaxel.StatusFailed: s.info.State = sandbox.SandboxStateError default: - switch s.native.State { - case blaxel.SandboxStateRunning: - s.info.State = sandbox.SandboxStateRunning - case blaxel.SandboxStateStandby: - s.info.State = sandbox.SandboxStatePaused + if mapped, ok := runtimeOnlyState(s.native.State); ok { + s.info.State = mapped } } } +func runtimeState(runtime blaxel.SandboxState, fallback sandbox.SandboxState) sandbox.SandboxState { + if runtime == blaxel.SandboxStateStandby { + return sandbox.SandboxStatePaused + } + return fallback +} + +func runtimeOnlyState(runtime blaxel.SandboxState) (sandbox.SandboxState, bool) { + switch runtime { + case blaxel.SandboxStateRunning: + return sandbox.SandboxStateRunning, true + case blaxel.SandboxStateStandby: + return sandbox.SandboxStatePaused, true + default: + return 0, false + } +} + func (s *observedSandbox) metadata() { native := s.native values := make(map[string]*sandbox.MetadataValue, 12) diff --git a/sdks/go/providers/blaxel/process.go b/sdks/go/providers/blaxel/process.go index 6b779433..bfe75122 100644 --- a/sdks/go/providers/blaxel/process.go +++ b/sdks/go/providers/blaxel/process.go @@ -11,18 +11,13 @@ import ( "google.golang.org/protobuf/types/known/durationpb" ) -// blaxelCompletionHold is the longest positive Kit timeout that stays on -// Blaxel's waitForCompletion request. Blaxel's public docs limit that hold -// to 60 seconds; the edge in front of the sandbox can close a longer one. -const blaxelCompletionHold = 60 * time.Second - -// blaxelWaitUnlimited is Process.Wait's maxWait sentinel for no added -// deadline. The SDK treats 0 as one minute and only -1 as unlimited. -const blaxelWaitUnlimited = time.Duration(-1) - -// processPollInterval is the gap between process status reads. It matches -// the Blaxel SDK default. -const processPollInterval = time.Second +const ( + // Docs limit waitForCompletion to 60s; longer holds can be closed by the edge. + blaxelCompletionHold = 60 * time.Second + // Process.Wait treats 0 as 1m; only -1 is unlimited. + blaxelWaitUnlimited = time.Duration(-1) + processPollInterval = time.Second // Blaxel SDK default poll gap +) var _ sandbox.SandboxProcess = (*backend)(nil) @@ -31,17 +26,34 @@ func (b *backend) RunCommand(ctx context.Context, id string, request *sandbox.Ex if err != nil { return nil, err } - command, err := blaxelCommand(request) + timeout, err := commandTimeout("run", request.GetTimeout()) if err != nil { return nil, err } - timeout, err := commandTimeout("run", request.GetTimeout()) + body, err := processRequest(request, timeout) if err != nil { return nil, err } + started, err := instance.Process.New(ctx, body) + if err != nil { + return nil, mapProviderError(err, "run") + } + return finishProcess(ctx, finishProcessArgs{ + Instance: instance, + Started: started, + Timeout: timeout, + }) +} + +// processRequest builds Process.New. Timeout 0 disables Blaxel auto-kill; +// WaitForCompletion is only set for 1–60s. +func processRequest(request *sandbox.ExecOptions, timeout time.Duration) (blaxel.ProcessRequestParam, error) { + command, err := blaxelCommand(request) + if err != nil { + return blaxel.ProcessRequestParam{}, err + } body := blaxel.ProcessRequestParam{ - Command: command, - // Explicit zero disables Blaxel's default process kill window. + Command: command, Timeout: blaxel.Int(int64(timeout / time.Second)), WaitForCompletion: blaxel.Bool(completionHold(timeout)), } @@ -51,40 +63,36 @@ func (b *backend) RunCommand(ctx context.Context, id string, request *sandbox.Ex if request.Environment != nil { body.Env = request.Environment } - started, err := instance.Process.New(ctx, body) - if err != nil { - return nil, mapProviderError(err, "run") - } - if started == nil || completionHold(timeout) || processFinished(started) { - return execResult(started) + return body, nil +} + +type finishProcessArgs struct { + Instance *blaxel.SandboxInstance + Started *blaxel.ProcessResponse + Timeout time.Duration +} + +// finishProcess returns a terminal start as-is; otherwise polls with Wait +// (also when waitForCompletion left the process running). Wait retries 504. +func finishProcess(ctx context.Context, args finishProcessArgs) (*sandbox.ExecResult, error) { + if args.Started == nil || processFinished(args.Started) { + return execResult(args.Started) } - identifier := processIdentifier(started) + identifier := processIdentifier(args.Started) if identifier == "" { - return nil, sandbox.NewError(&sandbox.ErrorInfo{ - Kind: sandbox.ErrorKindInvalidResponse, - Provider: "blaxel", - Operation: "run", - Message: "sandbox-kit blaxel: SDK returned no process identifier", - }, nil) + return nil, invalidResponse("run", "", "SDK returned no process identifier") } - // Wait polls until a terminal status or until ctx ends. A gateway 504 on - // one read is retried inside Wait; the deadline surfaces as Timeout. - finished, err := instance.Process.Wait(ctx, identifier, waitBudget(timeout), processPollInterval) + finished, err := args.Instance.Process.Wait(ctx, identifier, waitBudget(args.Timeout), processPollInterval) if err != nil { return nil, mapProviderError(err, "run") } return execResult(finished) } -// completionHold reports whether this timeout can stay on Blaxel's -// waitForCompletion request. Omit, zero, and anything above 60 seconds -// start the process and poll instead. func completionHold(timeout time.Duration) bool { return timeout > 0 && timeout <= blaxelCompletionHold } -// waitBudget is the Process.Wait maxWait for a Kit timeout. Omit and zero -// add no deadline beyond ctx. func waitBudget(timeout time.Duration) time.Duration { if timeout <= 0 { return blaxelWaitUnlimited @@ -92,19 +100,19 @@ func waitBudget(timeout time.Duration) time.Duration { return timeout } +var terminalProcessStatus = map[blaxel.ProcessResponseStatus]struct{}{ + blaxel.ProcessResponseStatusCompleted: {}, + blaxel.ProcessResponseStatusFailed: {}, + blaxel.ProcessResponseStatusKilled: {}, + blaxel.ProcessResponseStatusStopped: {}, +} + func processFinished(response *blaxel.ProcessResponse) bool { if response == nil { return false } - switch response.Status { - case blaxel.ProcessResponseStatusCompleted, - blaxel.ProcessResponseStatusFailed, - blaxel.ProcessResponseStatusKilled, - blaxel.ProcessResponseStatusStopped: - return true - default: - return false - } + _, ok := terminalProcessStatus[response.Status] + return ok } func processIdentifier(response *blaxel.ProcessResponse) string { @@ -119,12 +127,7 @@ func processIdentifier(response *blaxel.ProcessResponse) string { func execResult(response *blaxel.ProcessResponse) (*sandbox.ExecResult, error) { if response == nil { - return nil, sandbox.NewError(&sandbox.ErrorInfo{ - Kind: sandbox.ErrorKindInvalidResponse, - Provider: "blaxel", - Operation: "run", - Message: "sandbox-kit blaxel: SDK returned no exec result", - }, nil) + return nil, invalidResponse("run", "", "SDK returned no exec result") } return &sandbox.ExecResult{ ExitCode: int32(response.ExitCode), @@ -134,17 +137,12 @@ func execResult(response *blaxel.ProcessResponse) (*sandbox.ExecResult, error) { } func (b *backend) processSandbox(ctx context.Context, id, operation string) (*blaxel.SandboxInstance, error) { - instance, err := b.client.Sandboxes.GetInstance(ctx, id) + instance, err := b.getInstance(ctx, id, operation) if err != nil { - return nil, mapProviderError(err, operation) + return nil, err } if instance == nil || instance.Process == nil { - return nil, sandbox.NewError(&sandbox.ErrorInfo{ - Kind: sandbox.ErrorKindInvalidResponse, - Provider: "blaxel", - Operation: operation, - Message: "sandbox-kit blaxel: SDK returned no process API", - }, nil) + return nil, invalidResponse(operation, "", "SDK returned no process API") } return instance, nil } @@ -154,6 +152,7 @@ func blaxelCommand(request *sandbox.ExecOptions) (string, error) { return *request.Shell, nil } if len(request.Argv) == 0 { + // Shared kit wording (no provider infix) — keep identical across providers. return "", sandbox.NewError(&sandbox.ErrorInfo{ Kind: sandbox.ErrorKindInvalidArgument, Provider: "blaxel", @@ -165,18 +164,15 @@ func blaxelCommand(request *sandbox.ExecOptions) (string, error) { return posixShellJoin(request.Argv), nil } -// commandTimeout maps Kit ExecOptions.timeout to Blaxel's whole-second timeout. -// Nil and explicit zero become 0 so Blaxel adds no process auto-kill. func commandTimeout(operation string, value *durationpb.Duration) (time.Duration, error) { timeout, ok := sdk.WholeSecondDuration(value) if !ok { - return 0, sandbox.NewError(&sandbox.ErrorInfo{ + return 0, kitError(kitFault{ Kind: sandbox.ErrorKindInvalidArgument, - Provider: "blaxel", Operation: operation, Field: sandbox.ExecFieldTimeout, - Message: "sandbox-kit blaxel: timeout must be a whole number of seconds", - }, nil) + Message: "timeout must be a whole number of seconds", + }) } return timeout, nil } diff --git a/sdks/go/providers/blaxel/process_test.go b/sdks/go/providers/blaxel/process_test.go index ddd53f9e..e43cdde7 100644 --- a/sdks/go/providers/blaxel/process_test.go +++ b/sdks/go/providers/blaxel/process_test.go @@ -146,6 +146,41 @@ func TestRunCommandPollsWhenTheCompletionHoldIsUnsafe(t *testing.T) { } } +func TestRunCommandPollsWhenCompletionHoldStillReturnsRunning(t *testing.T) { + var gets atomic.Int32 + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/sbx": + writeJSON(t, w, map[string]any{"metadata": map[string]string{"name": "sbx"}, "spec": map[string]any{}}) + case r.Method == http.MethodPost && r.URL.Path == "/process": + body := readJSON(t, r) + if body["waitForCompletion"] != true || body["timeout"] != float64(5) { + t.Fatalf("post = %#v", body) + } + writeProcess(t, w, "running", 0, "", "") + case r.Method == http.MethodGet && r.URL.Path == "/process/123": + gets.Add(1) + writeProcess(t, w, "completed", 0, "late", "") + default: + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + } + }) + result, err := backend.RunCommand(context.Background(), "sbx", &sandbox.ExecOptions{ + Argv: []string{"echo", "hi"}, + Timeout: sandbox.Duration(5 * time.Second), + }) + if err != nil { + t.Fatal(err) + } + if result.GetExitCode() != 0 || string(result.GetStdout()) != "late" { + t.Fatalf("result = %#v", result) + } + if gets.Load() < 1 { + t.Fatalf("gets = %d", gets.Load()) + } +} + func TestRunCommandReturnsAFinishedStartWithoutPolling(t *testing.T) { backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { switch { diff --git a/sdks/go/providers/blaxel/provider.go b/sdks/go/providers/blaxel/provider.go index a1e4a2d4..53986a3d 100644 --- a/sdks/go/providers/blaxel/provider.go +++ b/sdks/go/providers/blaxel/provider.go @@ -8,20 +8,16 @@ import ( ) // Provider is the Blaxel provider. Pass New to sandbox.NewClient. -type Provider struct { -} +type Provider struct{} // backend owns the initialized native SDK and captured configuration. type backend struct { - // client is the initialized native SDK client. It is not part of the public - // sandbox API. client *blaxel.Client - // region is the captured default region. Nil means the caller omitted it; - // create requires a region. + // region is the captured default region; create requires it when set on Config. region *string } -// New returns the Blaxel provider. Pass it to sandbox.NewClient. +// New returns the Blaxel provider for sandbox.NewClient. // // Example: // @@ -34,29 +30,18 @@ type backend struct { // }, // Region: sandbox.Value("us-pdx-1"), // }) -func New() *Provider { - return &Provider{} -} +func New() *Provider { return &Provider{} } -// Name returns "blaxel". -func (*Provider) Name() string { - return "blaxel" -} - -// Name returns this integration's provider identifier. -func (*backend) Name() string { - return "blaxel" -} +func (*Provider) Name() string { return "blaxel" } +func (*backend) Name() string { return "blaxel" } // NewClient opens the Blaxel SDK from config. -// Pass New to sandbox.NewClient. Calling NewClient directly skips the shared client. +// Prefer sandbox.NewClient(New(), config); calling this directly skips shared client wiring. func (*Provider) NewClient(config *sandbox.Config) (sandbox.Backend, error) { backend, err := newBackend(config) return backend, mapProviderError(err, "initialize") } -// newBackend maps config, constructs the native SDK, and captures provider -// state before returning the backend. func newBackend(config *sandbox.Config) (sandbox.Backend, error) { if config == nil { config = &sandbox.Config{} @@ -71,28 +56,20 @@ func newBackend(config *sandbox.Config) (sandbox.Backend, error) { return result, nil } -// capture copies retained configuration onto the backend. Later changes to the -// caller config do not affect the copy. func (b *backend) capture(config *sandbox.Config) { - if config == nil { + if config == nil || config.Region == nil { return } - if config.Region != nil { - value := *config.Region - b.region = &value - } + value := *config.Region + b.region = &value } -// Create creates one sandbox through the native SDK and returns shared -// metadata. func (b *backend) Create(ctx context.Context, request *sandbox.CreateOptions) (*sandbox.CreateResult, error) { response, err := b.create(ctx, request) return response, mapProviderError(err, "create") } -// Close releases local provider state. It does not stop or delete the cloud -// sandbox. The Blaxel SDK client has no Close method; context cancellation is -// honored before return. +// Close releases local provider state only. It does not stop or delete cloud sandboxes. func (b *backend) Close(ctx context.Context) error { if err := ctx.Err(); err != nil { return mapProviderError(err, "close") @@ -100,10 +77,7 @@ func (b *backend) Close(ctx context.Context) error { return nil } -// CloseSandbox does not contact Blaxel and does not change the sandbox. -func (*backend) CloseSandbox(context.Context, string) error { - return nil -} +func (*backend) CloseSandbox(context.Context, string) error { return nil } var _ sandbox.Provider = (*Provider)(nil) var _ sandbox.Backend = (*backend)(nil) diff --git a/sdks/go/providers/blaxel/provider_checks.go b/sdks/go/providers/blaxel/provider_checks.go index 12f7c37c..bfb2c3cb 100644 --- a/sdks/go/providers/blaxel/provider_checks.go +++ b/sdks/go/providers/blaxel/provider_checks.go @@ -14,10 +14,7 @@ var providerSandboxNamePattern = regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$` const providerSandboxNameMaxLen = 49 // validateProviderCreate rejects creation settings this provider cannot honor. -// It runs before the native create call. -// -// Rules are inventoried in specs/providers/blaxel.yaml checks and kept in sync -// here by hand (not emitted). +// Inventoried in specs/providers/blaxel.yaml checks; kept in sync by hand. func validateProviderCreate(request *sandbox.CreateOptions) error { if request == nil { return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldName, "name is required") @@ -38,19 +35,8 @@ func validateProviderCreate(request *sandbox.CreateOptions) error { if request.Placement != nil { return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldPlacement, "per-request Blaxel placement is unsupported; use Config.region") } - if resources := request.Resources; resources != nil { - if resources.CpuCores != nil { - return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResourcesCpuCores, "Blaxel derives CPU from memory; cpu_cores is not mapped") - } - if resources.CpuLimitCores != nil { - return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResourcesCpuLimitCores, "Blaxel resource limits are not mapped") - } - if resources.MemoryLimitMib != nil { - return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResourcesMemoryLimitMib, "Blaxel resource limits are not mapped") - } - if resources.DiskMib != nil { - return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResourcesDiskMib, "Blaxel disk size is not mapped as MiB") - } + if err := rejectUnsupportedResources(request.Resources); err != nil { + return err } for name := range request.Environment { if !providerEnvNamePattern.MatchString(name) { @@ -60,12 +46,32 @@ func validateProviderCreate(request *sandbox.CreateOptions) error { return nil } +func rejectUnsupportedResources(resources *sandbox.Resources) error { + if resources == nil { + return nil + } + for _, check := range []struct { + set bool + field string + msg string + }{ + {resources.CpuCores != nil, sandbox.CreateFieldResourcesCpuCores, "Blaxel derives CPU from memory; cpu_cores is not mapped"}, + {resources.CpuLimitCores != nil, sandbox.CreateFieldResourcesCpuLimitCores, "Blaxel resource limits are not mapped"}, + {resources.MemoryLimitMib != nil, sandbox.CreateFieldResourcesMemoryLimitMib, "Blaxel resource limits are not mapped"}, + {resources.DiskMib != nil, sandbox.CreateFieldResourcesDiskMib, "Blaxel disk size is not mapped as MiB"}, + } { + if check.set { + return providerCheckError(sandbox.ErrorKindUnsupported, check.field, check.msg) + } + } + return nil +} + func providerCheckError(kind sandbox.ErrorKind, field, message string) error { - return sandbox.NewError(&sandbox.ErrorInfo{ + return kitError(kitFault{ Kind: kind, - Provider: "blaxel", Operation: "create", Field: field, - Message: "sandbox-kit blaxel: " + message, - }, nil) + Message: message, + }) } diff --git a/sdks/go/providers/blaxel/provider_mappings.go b/sdks/go/providers/blaxel/provider_mappings.go index 7e3eb7da..ac37d173 100644 --- a/sdks/go/providers/blaxel/provider_mappings.go +++ b/sdks/go/providers/blaxel/provider_mappings.go @@ -51,13 +51,11 @@ func mapResources(source *sandbox.Resources) (blaxel.SandboxRuntimeParam, *sandb if source.MemoryMib != nil { value := source.GetMemoryMib() if value == 0 || value > math.MaxInt64 { - return target, remaining, sandbox.NewError(&sandbox.ErrorInfo{ - Kind: sandbox.ErrorKindInvalidArgument, - Provider: "blaxel", - Operation: "create", - Field: sandbox.CreateFieldResourcesMemoryMib, - Message: "sandbox-kit blaxel: memory_mib cannot be represented", - }, nil) + return target, remaining, providerCheckError( + sandbox.ErrorKindInvalidArgument, + sandbox.CreateFieldResourcesMemoryMib, + "memory_mib cannot be represented", + ) } target.Memory = blaxel.Int(int64(value)) } diff --git a/sdks/go/providers/blaxel/sandbox.go b/sdks/go/providers/blaxel/sandbox.go index 589acdcd..a6a8ce70 100644 --- a/sdks/go/providers/blaxel/sandbox.go +++ b/sdks/go/providers/blaxel/sandbox.go @@ -25,16 +25,11 @@ func (b *backend) create(ctx context.Context, request *sandbox.CreateOptions) (* if err != nil { return nil, err } - if instance == nil || instance.Sandbox == nil { - return nil, sandbox.NewError(&sandbox.ErrorInfo{ - Kind: sandbox.ErrorKindInvalidResponse, - Provider: "blaxel", - Operation: "create", - Field: sandbox.InfoFieldId, - Message: "sandbox-kit blaxel: SDK returned no sandbox", - }, nil) + native, err := requireSandbox(instance, "create") + if err != nil { + return nil, err } - info, err := sandboxInfo(instance.Sandbox) + info, err := sandboxInfo(native) if err != nil { return nil, err } @@ -43,24 +38,15 @@ func (b *backend) create(ctx context.Context, request *sandbox.CreateOptions) (* // RefreshSandbox retrieves current metadata for an existing Blaxel sandbox by name. func (b *backend) RefreshSandbox(ctx context.Context, id string) (*sandbox.SandboxInfo, error) { - instance, err := b.client.Sandboxes.GetInstance(ctx, id) + instance, err := b.getInstance(ctx, id, "refresh") if err != nil { - return nil, mapProviderError(err, "refresh") - } - if instance == nil || instance.Sandbox == nil { - return nil, sandbox.NewError(&sandbox.ErrorInfo{ - Kind: sandbox.ErrorKindInvalidResponse, - Provider: "blaxel", - Operation: "refresh", - Field: sandbox.InfoFieldId, - Message: "sandbox-kit blaxel: SDK returned no sandbox", - }, nil) + return nil, err } - info, err := sandboxInfo(instance.Sandbox) + native, err := requireSandbox(instance, "refresh") if err != nil { return nil, err } - return info, nil + return sandboxInfo(native) } var _ sandbox.SandboxRefresher = (*backend)(nil) diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index f9a8e581..2fc1f0e9 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -3,6 +3,7 @@ import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { Client, + EgressMode, ErrorKind, FileType, PolicyMode, @@ -22,9 +23,14 @@ import { FileFieldLocalPath, FileFieldPath, } from "@sandbox-kit/sandbox"; -import { mapClientConfig, mapCreateRequest } from "#mappings/mappings.js"; +import { + mapClientConfig, + mapCreateRequest, + mapSandboxInfo, +} from "#mappings/mappings.js"; import { BlaxelProvider } from "./blaxel.js"; import { + mapAllocatedResources, mapResources, mapSandboxIdentity, } from "#mappings/provider_mappings.js"; @@ -327,6 +333,64 @@ describe("Blaxel mappings", () => { }); }); + it("reads provider codes from error.cause", () => { + const native = new ResponseError( + new Response(null, { status: 403, statusText: "Forbidden" }), + { + error: { + code: "POLICY_VIOLATION", + message: "policy", + status: 403, + }, + }, + new Error("policy"), + ); + const wrapped = new Error("wrapped", { cause: native }); + expect(mapBlaxelError(wrapped, "create")).toMatchObject({ + kind: ErrorKind.InvalidArgument, + providerCode: "POLICY_VIOLATION", + statusCode: 403, + cause: wrapped, + }); + }); + + it("accepts default egress with outbound domains", () => { + const plan = mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + network: { + egress: EgressMode.Default, + outboundDomains: ["example.com"], + }, + }, + provider, + ); + expect(plan.params.network).toEqual({ + allowedDomains: ["example.com"], + }); + }); + + it("maps allocated memory onto SandboxInfo resources", () => { + expect( + mapAllocatedResources({ + metadata: { name: "demo" }, + spec: { runtime: { memory: 2048 } }, + }), + ).toEqual({ memoryMib: 2048n }); + expect( + mapSandboxInfo({ + metadata: { name: "demo" }, + status: "DEPLOYED", + state: "RUNNING", + spec: { region: "us-pdx-1", runtime: { memory: 4096 } }, + }), + ).toMatchObject({ + id: "demo", + resources: { memoryMib: 4096n }, + }); + }); + it("omits protocol for default port transport", () => { const plan = mapCreateRequest( { @@ -813,6 +877,33 @@ describe("Blaxel provider orchestration", () => { expect(result.stderr).toEqual(new TextEncoder().encode("stopped")); }); + it("polls when a completion-hold start still returns running", async () => { + const waits: string[] = []; + const backend = runBackend({ + async exec(request) { + expect(request.waitForCompletion).toBe(true); + expect(request.timeout).toBe(5); + return { status: "running", pid: "held", stdout: "", stderr: "" }; + }, + async wait(identifier) { + waits.push(identifier); + return { + status: "completed", + exitCode: 0, + stdout: "late", + stderr: "", + }; + }, + }); + const result = await backend.run!( + "sandbox-1", + { argv: ["echo", "hi"], timeout: 5_000 }, + {}, + ); + expect(waits).toEqual(["held"]); + expect(result.stdout).toEqual(new TextEncoder().encode("late")); + }); + it("rejects a running process with no identifier", async () => { const backend = runBackend({ async exec() { diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index e12bea50..de204673 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -1,23 +1,7 @@ -import { basename, dirname } from "node:path"; -import { - SandboxInstance, - settings, - type Config as NativeClientConfig, - type Directory, - type File as BlaxelFile, - type SandboxCreateConfiguration, - type SandboxCreateOptions, -} from "@blaxel/core"; - -type BlaxelSubdirectory = Directory["subdirectories"][number]; import { ErrorKind, - ExecFieldTimeout, FileFieldParents, - FileFieldPath, - FileType, SandboxState, - sandboxError, type FileInfo, type OperationOptions, type Provider, @@ -44,96 +28,34 @@ import { mapCreateRequest, mapSandboxInfo, } from "#mappings/mappings.js"; -import { - ensureLocalParent, - mapBlaxelError, - providerError, - readLocalFile, - requireLocalDestination, -} from "#errors.js"; +import { mapBlaxelError, providerError } from "#errors.js"; import { BackendBase, ProviderBase } from "./provider.js"; import { captureProviderState, type ProviderState, } from "#config/provider_config.js"; - -interface BlaxelProcessResult { - readonly pid?: string; - readonly name?: string; - readonly status?: string; - readonly exitCode?: number; - readonly stdout?: string; - readonly stderr?: string; -} - -interface NativeSandboxHandle { - readonly metadata?: { - readonly name?: string; - readonly labels?: Readonly>; - }; - readonly status?: unknown; - readonly state?: unknown; - readonly spec?: { readonly region?: string }; - readonly fs: { - readBinary(path: string): Promise; - writeBinary(path: string, content: Uint8Array): Promise; - mkdir(path: string, permissions?: string): Promise; - ls(path: string): Promise; - rm(path: string, recursive?: boolean): Promise; - download(src: string, destinationPath: string): Promise; - }; - readonly process: { - exec(request: { - command: string; - waitForCompletion?: boolean; - timeout?: number; - workingDir?: string; - env?: Record; - }): Promise; - wait( - identifier: string, - options?: { - maxWait?: number; - interval?: number; - signal?: AbortSignal; - }, - ): Promise; - }; - delete(): Promise; -} - -interface NativeBlaxel { - configure(config: NativeClientConfig): void; - create( - params: SandboxCreateConfiguration, - options?: SandboxCreateOptions, - ): Promise; - get(name: string): Promise; - delete(name: string): Promise; -} - -type NativeFactory = (config: NativeClientConfig) => NativeBlaxel; +import { blaxelStat, directoryInfo, fileInfo } from "./filesystem.js"; +import { + ensureLocalParent, + readLocalFile, + requireLocalDestination, +} from "./local.js"; +import { + defaultNativeFactory, + type NativeBlaxel, + type NativeFactory, + type NativeSandboxHandle, +} from "./native.js"; +import { + execTimeoutSeconds, + finishProcess, + processRequest, +} from "./process.js"; /** @internal Exported for adapter boundary tests, but not from the package root. */ export class BlaxelProvider extends ProviderBase { constructor( - private readonly nativeFactory: NativeFactory = (config) => { - settings.setConfig(config); - return { - configure(next: NativeClientConfig): void { - settings.setConfig(next); - }, - create(params, options) { - return SandboxInstance.create(params, options); - }, - get(name) { - return SandboxInstance.get(name); - }, - delete(name) { - return SandboxInstance.delete(name); - }, - }; - }, + private readonly nativeFactory: NativeFactory = defaultNativeFactory, ) { super(); } @@ -150,6 +72,13 @@ export class BlaxelProvider extends ProviderBase { } } +interface WithSandboxArgs { + operation: string; + id: string; + options: OperationOptions; + body: (sandbox: NativeSandboxHandle) => Promise; +} + class BlaxelBackend extends BackendBase implements Backend { constructor( private readonly native: NativeBlaxel, @@ -174,14 +103,12 @@ class BlaxelBackend extends BackendBase implements Backend { } async refresh(id: string, options: OperationOptions): Promise { - try { - throwIfAborted(options.signal); - const sandbox = await this.native.get(id); - throwIfAborted(options.signal); - return mapSandboxInfo(sandbox); - } catch (error) { - throw mapBlaxelError(error, "refresh"); - } + return this.#withSandbox({ + operation: "refresh", + id, + options, + body: async (sandbox) => mapSandboxInfo(sandbox), + }); } async delete( @@ -189,21 +116,21 @@ class BlaxelBackend extends BackendBase implements Backend { _request: DeleteOptions, options: OperationOptions, ): Promise { - try { - throwIfAborted(options.signal); - const sandbox = await this.native.get(id); - throwIfAborted(options.signal); - const info = mapSandboxInfo(sandbox); - await sandbox.delete(); - return { - ...info, - state: SandboxState.Deleted, - terminal: true, - providerState: "TERMINATED", - }; - } catch (error) { - throw mapBlaxelError(error, "delete"); - } + return this.#withSandbox({ + operation: "delete", + id, + options, + body: async (sandbox) => { + const info = mapSandboxInfo(sandbox); + await sandbox.delete(); + return { + ...info, + state: SandboxState.Deleted, + terminal: true, + providerState: "TERMINATED", + }; + }, + }); } async readFile( @@ -212,9 +139,14 @@ class BlaxelBackend extends BackendBase implements Backend { _request: FileReadOptions, options: OperationOptions, ): Promise { - return this.#withSandbox("read_file", id, options, async (sandbox) => { - const blob = await sandbox.fs.readBinary(path); - return new Uint8Array(await blob.arrayBuffer()); + return this.#withSandbox({ + operation: "read_file", + id, + options, + body: async (sandbox) => { + const blob = await sandbox.fs.readBinary(path); + return new Uint8Array(await blob.arrayBuffer()); + }, }); } @@ -225,8 +157,13 @@ class BlaxelBackend extends BackendBase implements Backend { _request: FileWriteOptions, options: OperationOptions, ): Promise { - await this.#withSandbox("write_file", id, options, async (sandbox) => { - await sandbox.fs.writeBinary(path, data); + await this.#withSandbox({ + operation: "write_file", + id, + options, + body: async (sandbox) => { + await sandbox.fs.writeBinary(path, data); + }, }); } @@ -238,8 +175,13 @@ class BlaxelBackend extends BackendBase implements Backend { options: OperationOptions, ): Promise { const data = await readLocalFile(localPath, "copy_from_local"); - await this.#withSandbox("copy_from_local", id, options, async (sandbox) => { - await sandbox.fs.writeBinary(remotePath, data); + await this.#withSandbox({ + operation: "copy_from_local", + id, + options, + body: async (sandbox) => { + await sandbox.fs.writeBinary(remotePath, data); + }, }); } @@ -252,8 +194,13 @@ class BlaxelBackend extends BackendBase implements Backend { ): Promise { await requireLocalDestination(localPath, "copy_to_local"); await ensureLocalParent(localPath, "copy_to_local"); - await this.#withSandbox("copy_to_local", id, options, async (sandbox) => { - await sandbox.fs.download(remotePath, localPath); + await this.#withSandbox({ + operation: "copy_to_local", + id, + options, + body: async (sandbox) => { + await sandbox.fs.download(remotePath, localPath); + }, }); } @@ -271,8 +218,13 @@ class BlaxelBackend extends BackendBase implements Backend { message: "parents false is not available", }); } - await this.#withSandbox("make_directory", id, options, async (sandbox) => { - await sandbox.fs.mkdir(path); + await this.#withSandbox({ + operation: "make_directory", + id, + options, + body: async (sandbox) => { + await sandbox.fs.mkdir(path); + }, }); } @@ -282,12 +234,17 @@ class BlaxelBackend extends BackendBase implements Backend { _request: ListDirectoryOptions, options: OperationOptions, ): Promise { - return this.#withSandbox("list_directory", id, options, async (sandbox) => { - const listing = await sandbox.fs.ls(path); - return [ - ...listing.subdirectories.map((entry) => directoryInfo(entry)), - ...listing.files.map((entry) => fileInfo(entry)), - ]; + return this.#withSandbox({ + operation: "list_directory", + id, + options, + body: async (sandbox) => { + const listing = await sandbox.fs.ls(path); + return [ + ...listing.subdirectories.map((entry) => directoryInfo(entry)), + ...listing.files.map((entry) => fileInfo(entry)), + ]; + }, }); } @@ -297,33 +254,11 @@ class BlaxelBackend extends BackendBase implements Backend { _request: StatOptions, options: OperationOptions, ): Promise { - return this.#withSandbox("stat", id, options, async (sandbox) => { - // Blaxel's public FS API has no Stat. GET-by-path is what Go uses; here - // ls wraps the same GET and rejects non-directories with a fixed error. - try { - const listing = await sandbox.fs.ls(path); - return { - name: listing.name || basename(path) || "/", - path: listing.path || path, - type: FileType.Directory, - size: 0n, - }; - } catch (error) { - if (!isBlaxelFilePath(error)) throw error; - } - // Path is a file: metadata is on the parent directory listing. - const parent = dirname(path); - const name = basename(path); - const listing = await sandbox.fs.ls(parent === "." ? "/" : parent); - const file = listing.files.find((entry) => entry.name === name); - if (file) return fileInfo(file); - throw sandboxError({ - kind: ErrorKind.NotFound, - provider: "blaxel", - operation: "stat", - field: FileFieldPath, - message: `sandbox-kit blaxel: path not found: ${path}`, - }); + return this.#withSandbox({ + operation: "stat", + id, + options, + body: async (sandbox) => blaxelStat(sandbox.fs, path), }); } @@ -333,8 +268,13 @@ class BlaxelBackend extends BackendBase implements Backend { request: RemoveOptions, options: OperationOptions, ): Promise { - await this.#withSandbox("remove", id, options, async (sandbox) => { - await sandbox.fs.rm(path, request.recursive === true); + await this.#withSandbox({ + operation: "remove", + id, + options, + body: async (sandbox) => { + await sandbox.fs.rm(path, request.recursive === true); + }, }); } @@ -343,60 +283,33 @@ class BlaxelBackend extends BackendBase implements Backend { request: ExecOptions, options: OperationOptions, ): Promise { - return this.#withSandbox("run", id, options, async (sandbox) => { - const timeout = execTimeoutSeconds(request.timeout); - const started = await sandbox.process.exec({ - command: blaxelCommand(request), - // A positive timeout of at most 60 seconds can stay on Blaxel's - // completion request. Omit, zero, and longer timeouts start the - // process and poll so the edge hold does not fail the call. - waitForCompletion: holdsCompletion(timeout), - // Blaxel: omit/0 means no auto-kill. Kit omit/0 map to 0. - timeout: timeout ?? 0, - ...(request.workingDirectory === undefined - ? {} - : { workingDir: request.workingDirectory }), - ...(request.environment === undefined - ? {} - : { env: { ...request.environment } }), - }); - if (holdsCompletion(timeout) || processFinished(started.status)) { - return execResult(started); - } - const identifier = started.pid || started.name; - if (!identifier) { - throw sandboxError({ - kind: ErrorKind.InvalidResponse, - provider: "blaxel", - operation: "run", - field: "", - message: "sandbox-kit blaxel: SDK returned no process identifier", + return this.#withSandbox({ + operation: "run", + id, + options, + body: async (sandbox) => { + const timeout = execTimeoutSeconds(request.timeout); + const started = await sandbox.process.exec( + processRequest(request, timeout), + ); + return finishProcess({ + process: sandbox.process, + started, + timeout, + signal: options.signal, }); - } - // wait polls until a terminal status or until the signal / maxWait ends. - // A gateway 504 on one read is retried inside wait; the deadline is a timeout. - const finished = await sandbox.process.wait(identifier, { - maxWait: timeout === undefined ? -1 : timeout * 1_000, - interval: 1_000, - ...(options.signal === undefined ? {} : { signal: options.signal }), - }); - return execResult(finished); + }, }); } - async #withSandbox( - operation: string, - id: string, - options: OperationOptions, - body: (sandbox: NativeSandboxHandle) => Promise, - ): Promise { + async #withSandbox(args: WithSandboxArgs): Promise { try { - throwIfAborted(options.signal); - const sandbox = await this.native.get(id); - throwIfAborted(options.signal); - return await body(sandbox); + throwIfAborted(args.options.signal); + const sandbox = await this.native.get(args.id); + throwIfAborted(args.options.signal); + return await args.body(sandbox); } catch (error) { - throw mapBlaxelError(error, operation); + throw mapBlaxelError(error, args.operation); } } @@ -407,105 +320,10 @@ class BlaxelBackend extends BackendBase implements Backend { } } -function fileInfo(info: BlaxelFile): FileInfo { - const modified = Date.parse(info.lastModified); - return { - name: info.name, - path: info.path, - type: FileType.File, - size: BigInt(Math.trunc(info.size)), - ...(info.permissions ? { permissions: info.permissions } : {}), - ...(info.owner ? { owner: info.owner } : {}), - ...(info.group ? { group: info.group } : {}), - ...(Number.isNaN(modified) ? {} : { modifiedAt: new Date(modified) }), - }; -} - -function directoryInfo(info: BlaxelSubdirectory): FileInfo { - return { - name: info.name, - path: info.path, - type: FileType.Directory, - size: 0n, - }; -} - function throwIfAborted(signal: AbortSignal | undefined): void { signal?.throwIfAborted(); } -/** Blaxel `fs.ls` throws this when GET returns a file instead of a directory. */ -function isBlaxelFilePath(error: unknown): boolean { - if (!(error instanceof Error)) return false; - try { - const body = JSON.parse(error.message) as { error?: unknown }; - return body.error === "Directory not found"; - } catch { - return false; - } -} - -/** Blaxel's documented limit for a single waitForCompletion request. */ -const blaxelCompletionHoldSeconds = 60; - -function holdsCompletion(timeoutSeconds: number | undefined): boolean { - return ( - timeoutSeconds !== undefined && - timeoutSeconds > 0 && - timeoutSeconds <= blaxelCompletionHoldSeconds - ); -} - -const terminalProcessStatus = new Set([ - "completed", - "failed", - "killed", - "stopped", -]); - -function processFinished(status: string | undefined): boolean { - return status !== undefined && terminalProcessStatus.has(status); -} - -function execResult(result: BlaxelProcessResult): ExecResult { - return { - exitCode: result.exitCode ?? 0, - stdout: new TextEncoder().encode(result.stdout ?? ""), - stderr: new TextEncoder().encode(result.stderr ?? ""), - }; -} - -function blaxelCommand(request: ExecOptions): string { - if (request.shell !== undefined) return request.shell; - return posixShellJoin(request.argv ?? []); -} - -function posixShellJoin(argv: readonly string[]): string { - return argv.map(posixQuote).join(" "); -} - -function posixQuote(value: string): string { - if (value === "") return "''"; - return "'" + value.replaceAll("'", `'"'"'`) + "'"; -} - -/** - * Maps Kit ExecOptions.timeout (ms) to Blaxel's process timeout (seconds). - * Omitted and explicit zero become undefined so the caller sends 0 (no limit). - */ -function execTimeoutSeconds(timeout: number | undefined): number | undefined { - if (timeout === undefined || timeout === 0) return undefined; - if (!Number.isFinite(timeout) || timeout < 0 || timeout % 1_000 !== 0) { - providerError({ - kind: ErrorKind.InvalidArgument, - operation: "run", - field: ExecFieldTimeout, - message: "timeout must be a whole number of seconds", - }); - } - return timeout / 1_000; -} - /** * Selects Blaxel as the provider for the shared Sandbox Kit client. * diff --git a/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts b/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts index 97480ff0..4ba152ac 100644 --- a/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts +++ b/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts @@ -10,8 +10,9 @@ import { CreateFieldSource, CreateFieldSourceImage, validEnvName, + type ErrorInfo, } from "@sandbox-kit/sandbox"; -import type { CreateOptions } from "@sandbox-kit/sandbox"; +import type { CreateOptions, Resources } from "@sandbox-kit/sandbox"; import { providerError } from "#errors.js"; // Blaxel Metadata.Name: lowercase alphanumeric with single hyphens, max 49. @@ -19,98 +20,94 @@ const PROVIDER_SANDBOX_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/; const PROVIDER_SANDBOX_NAME_MAX = 49; // validateProviderCreate rejects creation settings this provider cannot honor. -// Rules are inventoried in specs/providers/blaxel.yaml checks when present. +// Inventoried in specs/providers/blaxel.yaml checks when present. export function validateProviderCreate(request: CreateOptions): void { if (request.name === undefined || request.name.trim() === "") { - providerError({ - kind: ErrorKind.InvalidArgument, - operation: "create", - field: CreateFieldName, - message: "name is required", - }); + reject(ErrorKind.InvalidArgument, CreateFieldName, "name is required"); } if ( request.name.length > PROVIDER_SANDBOX_NAME_MAX || !PROVIDER_SANDBOX_NAME.test(request.name) ) { - providerError({ - kind: ErrorKind.InvalidArgument, - operation: "create", - field: CreateFieldName, - message: - "name must be lowercase alphanumeric with single hyphens, at most 49 characters", - }); + reject( + ErrorKind.InvalidArgument, + CreateFieldName, + "name must be lowercase alphanumeric with single hyphens, at most 49 characters", + ); } if (request.source?.image === undefined) { - providerError({ - kind: ErrorKind.InvalidArgument, - operation: "create", - field: CreateFieldSourceImage, - message: "an explicit image source is required", - }); + reject( + ErrorKind.InvalidArgument, + CreateFieldSourceImage, + "an explicit image source is required", + ); } if ( request.source?.snapshot !== undefined || request.source?.warmPool !== undefined ) { - providerError({ - kind: ErrorKind.Unsupported, - operation: "create", - field: CreateFieldSource, - message: "only an image source is supported", - }); + reject( + ErrorKind.Unsupported, + CreateFieldSource, + "only an image source is supported", + ); } if (request.placement !== undefined) { - providerError({ - kind: ErrorKind.Unsupported, - operation: "create", - field: CreateFieldPlacement, - message: "per-request Blaxel placement is unsupported; use Config.region", - }); - } - const resources = request.resources; - if (resources !== undefined) { - if (resources.cpuCores !== undefined) { - providerError({ - kind: ErrorKind.Unsupported, - operation: "create", - field: CreateFieldResourcesCpuCores, - message: "Blaxel derives CPU from memory; cpu_cores is not mapped", - }); - } - if (resources.cpuLimitCores !== undefined) { - providerError({ - kind: ErrorKind.Unsupported, - operation: "create", - field: CreateFieldResourcesCpuLimitCores, - message: "Blaxel resource limits are not mapped", - }); - } - if (resources.memoryLimitMib !== undefined) { - providerError({ - kind: ErrorKind.Unsupported, - operation: "create", - field: CreateFieldResourcesMemoryLimitMib, - message: "Blaxel resource limits are not mapped", - }); - } - if (resources.diskMib !== undefined) { - providerError({ - kind: ErrorKind.Unsupported, - operation: "create", - field: CreateFieldResourcesDiskMib, - message: "Blaxel disk size is not mapped as MiB", - }); - } + reject( + ErrorKind.Unsupported, + CreateFieldPlacement, + "per-request Blaxel placement is unsupported; use Config.region", + ); } + rejectUnsupportedResources(request.resources); for (const value of Object.keys(request.environment ?? {})) { if (!validEnvName(value)) { - providerError({ - kind: ErrorKind.InvalidArgument, - operation: "create", - field: CreateFieldEnvironment, - message: "environment is outside supported values", - }); + reject( + ErrorKind.InvalidArgument, + CreateFieldEnvironment, + "environment is outside supported values", + ); } } } + +function rejectUnsupportedResources(resources: Resources | undefined): void { + if (resources === undefined) return; + const checks: ReadonlyArray<{ + set: boolean; + field: string; + message: string; + }> = [ + { + set: resources.cpuCores !== undefined, + field: CreateFieldResourcesCpuCores, + message: "Blaxel derives CPU from memory; cpu_cores is not mapped", + }, + { + set: resources.cpuLimitCores !== undefined, + field: CreateFieldResourcesCpuLimitCores, + message: "Blaxel resource limits are not mapped", + }, + { + set: resources.memoryLimitMib !== undefined, + field: CreateFieldResourcesMemoryLimitMib, + message: "Blaxel resource limits are not mapped", + }, + { + set: resources.diskMib !== undefined, + field: CreateFieldResourcesDiskMib, + message: "Blaxel disk size is not mapped as MiB", + }, + ]; + for (const check of checks) { + if (check.set) reject(ErrorKind.Unsupported, check.field, check.message); + } +} + +function reject( + kind: ErrorInfo["kind"], + field: string, + message: string, +): never { + providerError({ kind, operation: "create", field, message }); +} diff --git a/sdks/typescript/providers/blaxel/src/config/provider_config.ts b/sdks/typescript/providers/blaxel/src/config/provider_config.ts index 31557b73..ac176b8d 100644 --- a/sdks/typescript/providers/blaxel/src/config/provider_config.ts +++ b/sdks/typescript/providers/blaxel/src/config/provider_config.ts @@ -29,26 +29,17 @@ export function mapGeneratedClientConfig(config: Config): NativeClientConfig { ); if (source.scope !== undefined) { Object.assign(target, mapClientScope(source.scope, "initialize")); - if (source.scope.appName !== undefined) - unsupported( - ConfigFieldScopeAppName, - "unsupported client configuration field", - ); - if (source.scope.environment !== undefined) - unsupported( - ConfigFieldScopeEnvironment, - "unsupported client configuration field", - ); - if (source.scope.organizationId !== undefined) - unsupported( + for (const [set, field] of [ + [source.scope.appName !== undefined, ConfigFieldScopeAppName], + [source.scope.environment !== undefined, ConfigFieldScopeEnvironment], + [ + source.scope.organizationId !== undefined, ConfigFieldScopeOrganizationId, - "unsupported client configuration field", - ); - if (source.scope.projectId !== undefined) - unsupported( - ConfigFieldScopeProjectId, - "unsupported client configuration field", - ); + ], + [source.scope.projectId !== undefined, ConfigFieldScopeProjectId], + ] as const) { + if (set) unsupported(field, "unsupported client configuration field"); + } } if (source.auth?.apiKey !== undefined) { Object.assign( diff --git a/sdks/typescript/providers/blaxel/src/errors.ts b/sdks/typescript/providers/blaxel/src/errors.ts index 39dbed33..2884b2af 100644 --- a/sdks/typescript/providers/blaxel/src/errors.ts +++ b/sdks/typescript/providers/blaxel/src/errors.ts @@ -3,9 +3,6 @@ // then Blaxel helpers / ResponseError status, then unknown. // Cause stays on SandboxError.cause. -import { mkdir, readFile, stat, writeFile } from "node:fs/promises"; -import { dirname } from "node:path"; - import { CredentialsError, getBlaxelErrorCode, @@ -19,7 +16,6 @@ import { } from "@blaxel/core"; import { ErrorKind, - FileFieldLocalPath, SandboxError, sandboxError, type ErrorInfo, @@ -42,6 +38,74 @@ export interface ProviderErrorInput { message: string; } +const CODE_KINDS: Record = { + AUTHENTICATION_REQUIRED: ErrorKind.Authentication, + AUTHENTICATION_FAILED: ErrorKind.Authentication, + TOKEN_REVOKED: ErrorKind.Authentication, + FORBIDDEN: ErrorKind.PermissionDenied, + UNAUTHORIZED: ErrorKind.PermissionDenied, + UNAUTHORIZED_WORKSPACE: ErrorKind.PermissionDenied, + ROUTE_NOT_FOUND: ErrorKind.NotFound, + WORKLOAD_NOT_FOUND: ErrorKind.NotFound, + WORKSPACE_NOT_FOUND: ErrorKind.NotFound, + IMAGE_NOT_FOUND: ErrorKind.NotFound, + VOLUME_NOT_FOUND: ErrorKind.NotFound, + ACCOUNT_NOT_FOUND: ErrorKind.NotFound, + SANDBOX_DELETED: ErrorKind.NotFound, + VOLUME_DELETED: ErrorKind.NotFound, + SANDBOX_ALREADY_EXISTS: ErrorKind.AlreadyExists, + VOLUME_ALREADY_ATTACHED: ErrorKind.AlreadyExists, + BAD_REQUEST: ErrorKind.InvalidArgument, + POLICY_VIOLATION: ErrorKind.InvalidArgument, + INVALID_INPUT: ErrorKind.InvalidArgument, + INVALID_IMAGE: ErrorKind.InvalidArgument, + INVALID_PORTS: ErrorKind.InvalidArgument, + INVALID_REGION: ErrorKind.InvalidArgument, + INVALID_VOLUME: ErrorKind.InvalidArgument, + INVALID_VOLUME_COUNT: ErrorKind.InvalidArgument, + INVALID_VOLUME_REGION: ErrorKind.InvalidArgument, + VALIDATION_FAILED: ErrorKind.InvalidArgument, + VALIDATION_ERROR: ErrorKind.InvalidArgument, + WORKSPACE_REQUIRED: ErrorKind.InvalidArgument, + CREATION_TIMEOUT: ErrorKind.Timeout, + UPSTREAM_CONNECT_TIMEOUT: ErrorKind.Timeout, + UPSTREAM_TIMEOUT: ErrorKind.Timeout, + QUOTA_EXCEEDED: ErrorKind.ResourceExhausted, + USAGE_LIMIT_EXCEEDED: ErrorKind.ResourceExhausted, + RATE_LIMIT_EXCEEDED: ErrorKind.RateLimited, + WORKSPACE_RATE_LIMIT_EXCEEDED: ErrorKind.RateLimited, + SANDBOX_DELETION_IN_PROGRESS: ErrorKind.Conflict, + WORKLOAD_UNAVAILABLE: ErrorKind.Unavailable, + ROUTING_UNAVAILABLE: ErrorKind.Unavailable, + SERVICE_UNAVAILABLE: ErrorKind.Unavailable, + AUTHENTICATION_UNAVAILABLE: ErrorKind.Unavailable, + UPSTREAM_CONNECT_FAILED: ErrorKind.Unavailable, + UPSTREAM_ERROR: ErrorKind.Unavailable, + GATEWAY_INTERNAL_ERROR: ErrorKind.Unavailable, + WORKLOAD_FAILED: ErrorKind.Internal, + INTERNAL_ERROR: ErrorKind.Internal, + HANDLER_ERROR: ErrorKind.Internal, + DATABASE_ERROR: ErrorKind.Internal, +}; + +const STATUS_KINDS: Record = { + 400: ErrorKind.InvalidArgument, + 422: ErrorKind.InvalidArgument, + 401: ErrorKind.Authentication, + 402: ErrorKind.ResourceExhausted, + 403: ErrorKind.PermissionDenied, + 404: ErrorKind.NotFound, + 410: ErrorKind.NotFound, + 408: ErrorKind.Timeout, + 409: ErrorKind.Conflict, + 412: ErrorKind.FailedPrecondition, + 429: ErrorKind.RateLimited, + 500: ErrorKind.Internal, + 502: ErrorKind.Unavailable, + 503: ErrorKind.Unavailable, + 504: ErrorKind.Timeout, +}; + export function mapBlaxelError( error: unknown, operation: string, @@ -53,49 +117,12 @@ export function mapBlaxelError( return create({ kind: ErrorKind.Timeout, error, operation }); const cause = causeOf(error); - let kind: ErrorInfo["kind"] = ErrorKind.Unknown; - const statusCode = - getBlaxelErrorStatus(error) ?? getBlaxelErrorStatus(cause); - const providerCode = stringifyCode(getBlaxelErrorCode(error)); - - if (error instanceof CredentialsError) kind = ErrorKind.Authentication; - if ( - kind === ErrorKind.Unknown && - (error instanceof SandboxCreationTimeoutError || - isCreationTimeoutError(error)) - ) - kind = ErrorKind.Timeout; - // process.wait reports its deadline as a plain Error. A 504 on one poll is - // retried inside wait and may be that error's cause. A bare edge 504 - // (completion hold or origin read) is a timeout too, matching Go. - if ( - kind === ErrorKind.Unknown && - (isProcessWaitTimeout(error) || - isGatewayTimeout(error) || - isGatewayTimeout(cause)) - ) - kind = ErrorKind.Timeout; - if ( - kind === ErrorKind.Unknown && - (error instanceof SandboxGatewayError || isGatewayError(error)) - ) - kind = ErrorKind.Unavailable; - - if (kind === ErrorKind.Unknown && providerCode !== undefined) - kind = kindFromCode(providerCode); - - if (kind === ErrorKind.Unknown && statusCode !== undefined) - kind = kindFromStatus(statusCode); - - if ( - kind === ErrorKind.Unknown && - error instanceof ResponseError && - error.status !== undefined - ) - kind = kindFromStatus(error.status); - + const statusCode = getBlaxelErrorStatus(error) ?? getBlaxelErrorStatus(cause); + const providerCode = stringifyCode( + getBlaxelErrorCode(error) ?? getBlaxelErrorCode(cause), + ); return create({ - kind, + kind: classifyKind({ error, cause, providerCode, statusCode }), error, operation, ...(statusCode === undefined ? {} : { statusCode }), @@ -103,103 +130,40 @@ export function mapBlaxelError( }); } -function kindFromCode(code: string): ErrorInfo["kind"] { - switch (code) { - case "AUTHENTICATION_REQUIRED": - case "AUTHENTICATION_FAILED": - case "TOKEN_REVOKED": - return ErrorKind.Authentication; - case "FORBIDDEN": - case "UNAUTHORIZED": - case "UNAUTHORIZED_WORKSPACE": - return ErrorKind.PermissionDenied; - case "ROUTE_NOT_FOUND": - case "WORKLOAD_NOT_FOUND": - case "WORKSPACE_NOT_FOUND": - case "IMAGE_NOT_FOUND": - case "VOLUME_NOT_FOUND": - case "ACCOUNT_NOT_FOUND": - case "SANDBOX_DELETED": - case "VOLUME_DELETED": - return ErrorKind.NotFound; - case "SANDBOX_ALREADY_EXISTS": - case "VOLUME_ALREADY_ATTACHED": - return ErrorKind.AlreadyExists; - case "BAD_REQUEST": - case "POLICY_VIOLATION": // reserved gateway code; matches Go ErrPolicyViolation - case "INVALID_INPUT": - case "INVALID_IMAGE": - case "INVALID_PORTS": - case "INVALID_REGION": - case "INVALID_VOLUME": - case "INVALID_VOLUME_COUNT": - case "INVALID_VOLUME_REGION": - case "VALIDATION_FAILED": - case "VALIDATION_ERROR": - case "WORKSPACE_REQUIRED": - return ErrorKind.InvalidArgument; - case "CREATION_TIMEOUT": - case "UPSTREAM_CONNECT_TIMEOUT": - case "UPSTREAM_TIMEOUT": - return ErrorKind.Timeout; - case "QUOTA_EXCEEDED": - case "USAGE_LIMIT_EXCEEDED": - return ErrorKind.ResourceExhausted; - case "RATE_LIMIT_EXCEEDED": - case "WORKSPACE_RATE_LIMIT_EXCEEDED": - return ErrorKind.RateLimited; - case "SANDBOX_DELETION_IN_PROGRESS": - return ErrorKind.Conflict; - case "WORKLOAD_UNAVAILABLE": - case "ROUTING_UNAVAILABLE": - case "SERVICE_UNAVAILABLE": - case "AUTHENTICATION_UNAVAILABLE": - case "UPSTREAM_CONNECT_FAILED": - case "UPSTREAM_ERROR": - case "GATEWAY_INTERNAL_ERROR": - return ErrorKind.Unavailable; - case "WORKLOAD_FAILED": - case "INTERNAL_ERROR": - case "HANDLER_ERROR": - case "DATABASE_ERROR": - return ErrorKind.Internal; - default: - return ErrorKind.Unknown; - } +interface ClassifyArgs { + error: unknown; + cause: unknown; + providerCode: string | undefined; + statusCode: number | undefined; } -function kindFromStatus(status: number): ErrorInfo["kind"] { - switch (status) { - case 400: - case 422: - return ErrorKind.InvalidArgument; - case 401: - return ErrorKind.Authentication; - case 402: - return ErrorKind.ResourceExhausted; - case 403: - return ErrorKind.PermissionDenied; - case 404: - case 410: - return ErrorKind.NotFound; - case 408: - return ErrorKind.Timeout; - case 409: - return ErrorKind.Conflict; - case 412: - return ErrorKind.FailedPrecondition; - case 429: - return ErrorKind.RateLimited; - case 500: - return ErrorKind.Internal; - case 502: - case 503: - return ErrorKind.Unavailable; - case 504: - return ErrorKind.Timeout; - default: - return ErrorKind.Unknown; - } +function classifyKind(args: ClassifyArgs): ErrorInfo["kind"] { + const { error, cause, providerCode, statusCode } = args; + if (error instanceof CredentialsError) return ErrorKind.Authentication; + if ( + error instanceof SandboxCreationTimeoutError || + isCreationTimeoutError(error) + ) + return ErrorKind.Timeout; + // process.wait deadline is a plain Error; a 504 on one poll may be its cause. + // A bare edge 504 is a timeout too, matching Go. + if ( + isProcessWaitTimeout(error) || + isGatewayTimeout(error) || + isGatewayTimeout(cause) + ) + return ErrorKind.Timeout; + if (error instanceof SandboxGatewayError || isGatewayError(error)) + return ErrorKind.Unavailable; + + // Unknown codes fall through so status can still classify the failure. + if (providerCode !== undefined && CODE_KINDS[providerCode] !== undefined) + return CODE_KINDS[providerCode]!; + if (statusCode !== undefined && STATUS_KINDS[statusCode] !== undefined) + return STATUS_KINDS[statusCode]!; + if (error instanceof ResponseError && error.status !== undefined) + return STATUS_KINDS[error.status] ?? ErrorKind.Unknown; + return ErrorKind.Unknown; } function isProcessWaitTimeout(error: unknown): boolean { @@ -238,127 +202,6 @@ function create(input: MappedErrorInput): SandboxError { ); } -/** Rejects a missing local file or a directory before Blaxel reads it. */ -export async function requireLocalFile( - localPath: string, - operation: string, -): Promise { - let info; - try { - info = await stat(localPath); - } catch (error) { - if (nodeCode(error)) throw localFileError(error, operation); - throw error; - } - if (info.isDirectory()) localDirectory(operation); -} - -/** Rejects a local destination that is already a directory. */ -export async function requireLocalDestination( - localPath: string, - operation: string, -): Promise { - try { - const info = await stat(localPath); - if (info.isDirectory()) localDirectory(operation); - } catch (error) { - if (nodeCode(error) === "ENOENT") return; - if (nodeCode(error)) throw localFileError(error, operation); - throw error; - } -} - -/** Creates the parent directory for a local destination path. */ -export async function ensureLocalParent( - localPath: string, - operation: string, -): Promise { - try { - await mkdir(dirname(localPath), { recursive: true }); - } catch (error) { - throw localFileError(error, operation); - } -} - -/** Reads a local file for upload into the sandbox. */ -export async function readLocalFile( - localPath: string, - operation: string, -): Promise { - await requireLocalFile(localPath, operation); - try { - return await readFile(localPath); - } catch (error) { - throw localFileError(error, operation); - } -} - -/** Writes downloaded sandbox bytes to a local path. */ -export async function writeLocalFile( - localPath: string, - data: Uint8Array, - operation: string, -): Promise { - await requireLocalDestination(localPath, operation); - await ensureLocalParent(localPath, operation); - try { - await writeFile(localPath, data); - } catch (error) { - throw localFileError(error, operation); - } -} - -/** Classifies a local filesystem failure. Other errors stay with provider mapping. */ -export function translateLocalCopyError( - error: unknown, - operation: string, -): unknown { - if (error instanceof SandboxError) return error; - const code = nodeCode(error); - if (!code || !localCodes.has(code)) return error; - return localFileError(error, operation); -} - -function localDirectory(operation: string): never { - providerError({ - kind: ErrorKind.InvalidArgument, - operation, - field: FileFieldLocalPath, - message: "local path is a directory", - }); -} - -function localFileError(error: unknown, operation: string): SandboxError { - return sandboxError( - { - kind: - nodeCode(error) === "ENOENT" - ? ErrorKind.NotFound - : ErrorKind.InvalidArgument, - provider: "blaxel", - operation, - field: FileFieldLocalPath, - message: error instanceof Error ? error.message : "local path failed", - }, - error, - ); -} - -function nodeCode(error: unknown): string | undefined { - if (typeof error !== "object" || !error || !("code" in error)) - return undefined; - return typeof error.code === "string" ? error.code : undefined; -} - -const localCodes = new Set([ - "ENOENT", - "EISDIR", - "ENOTDIR", - "EACCES", - "EPERM", - "ENOSPC", -]); - /** Local throw helper for mappings, checks, and config. */ export function providerError(input: ProviderErrorInput): never { throw sandboxError({ diff --git a/sdks/typescript/providers/blaxel/src/filesystem.ts b/sdks/typescript/providers/blaxel/src/filesystem.ts new file mode 100644 index 00000000..a7cc33b8 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/filesystem.ts @@ -0,0 +1,76 @@ +import { basename, dirname } from "node:path"; +import type { Directory, File as BlaxelFile } from "@blaxel/core"; +import { + ErrorKind, + FileFieldPath, + FileType, + sandboxError, + type FileInfo, +} from "@sandbox-kit/sandbox"; +import type { NativeSandboxHandle } from "./native.js"; + +type BlaxelSubdirectory = Directory["subdirectories"][number]; + +export function fileInfo(info: BlaxelFile): FileInfo { + const modified = Date.parse(info.lastModified); + return { + name: info.name, + path: info.path, + type: FileType.File, + size: BigInt(Math.trunc(info.size)), + ...(info.permissions ? { permissions: info.permissions } : {}), + ...(info.owner ? { owner: info.owner } : {}), + ...(info.group ? { group: info.group } : {}), + ...(Number.isNaN(modified) ? {} : { modifiedAt: new Date(modified) }), + }; +} + +export function directoryInfo(info: BlaxelSubdirectory): FileInfo { + return { + name: info.name, + path: info.path, + type: FileType.Directory, + size: 0n, + }; +} + +/** Blaxel has no Stat; ls wraps GET and rejects file paths with this error. */ +export async function blaxelStat( + fs: NativeSandboxHandle["fs"], + path: string, +): Promise { + try { + const listing = await fs.ls(path); + return { + name: listing.name || basename(path) || "/", + path: listing.path || path, + type: FileType.Directory, + size: 0n, + }; + } catch (error) { + if (!isBlaxelFilePath(error)) throw error; + } + const parent = dirname(path); + const name = basename(path); + const listing = await fs.ls(parent === "." ? "/" : parent); + const file = listing.files.find((entry) => entry.name === name); + if (file) return fileInfo(file); + throw sandboxError({ + kind: ErrorKind.NotFound, + provider: "blaxel", + operation: "stat", + field: FileFieldPath, + message: `sandbox-kit blaxel: path not found: ${path}`, + }); +} + +/** Blaxel `fs.ls` throws this when GET returns a file instead of a directory. */ +function isBlaxelFilePath(error: unknown): boolean { + if (!(error instanceof Error)) return false; + try { + const body = JSON.parse(error.message) as { error?: unknown }; + return body.error === "Directory not found"; + } catch { + return false; + } +} diff --git a/sdks/typescript/providers/blaxel/src/local.ts b/sdks/typescript/providers/blaxel/src/local.ts new file mode 100644 index 00000000..610df783 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/local.ts @@ -0,0 +1,96 @@ +import { mkdir, readFile, stat } from "node:fs/promises"; +import { dirname } from "node:path"; + +import { + ErrorKind, + FileFieldLocalPath, + SandboxError, + sandboxError, +} from "@sandbox-kit/sandbox"; +import { providerError } from "./errors.js"; + +/** Rejects a missing local file or a directory before Blaxel reads it. */ +export async function requireLocalFile( + localPath: string, + operation: string, +): Promise { + let info; + try { + info = await stat(localPath); + } catch (error) { + if (nodeCode(error)) throw localFileError(error, operation); + throw error; + } + if (info.isDirectory()) localDirectory(operation); +} + +/** Rejects a local destination that is already a directory. */ +export async function requireLocalDestination( + localPath: string, + operation: string, +): Promise { + try { + const info = await stat(localPath); + if (info.isDirectory()) localDirectory(operation); + } catch (error) { + if (nodeCode(error) === "ENOENT") return; + if (nodeCode(error)) throw localFileError(error, operation); + throw error; + } +} + +/** Creates the parent directory for a local destination path. */ +export async function ensureLocalParent( + localPath: string, + operation: string, +): Promise { + try { + await mkdir(dirname(localPath), { recursive: true }); + } catch (error) { + throw localFileError(error, operation); + } +} + +/** Reads a local file for upload into the sandbox. */ +export async function readLocalFile( + localPath: string, + operation: string, +): Promise { + await requireLocalFile(localPath, operation); + try { + return await readFile(localPath); + } catch (error) { + throw localFileError(error, operation); + } +} + +function localDirectory(operation: string): never { + providerError({ + kind: ErrorKind.InvalidArgument, + operation, + field: FileFieldLocalPath, + message: "local path is a directory", + }); +} + +function localFileError(error: unknown, operation: string): SandboxError { + return sandboxError( + { + kind: + nodeCode(error) === "ENOENT" + ? ErrorKind.NotFound + : ErrorKind.InvalidArgument, + provider: "blaxel", + operation, + field: FileFieldLocalPath, + message: error instanceof Error ? error.message : "local path failed", + }, + error, + ); +} + +function nodeCode(error: unknown): string | undefined { + if (typeof error !== "object" || !error || !("code" in error)) + return undefined; + return typeof error.code === "string" ? error.code : undefined; +} diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index 8c86b6d8..52d8c529 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -7,6 +7,7 @@ import type { SandboxLifecycle, } from "@blaxel/core"; import { + EgressMode, ErrorKind, IsolationKind, PolicyMode, @@ -37,6 +38,7 @@ import { CreateFieldSourceImageReference, } from "@sandbox-kit/sandbox"; import { + mapAllocatedResources, mapCommonCreateFields as generatedMapCommonCreateFields, mapResources as generatedMapResources, mapSandboxIdentity as generatedMapSandboxIdentity, @@ -94,13 +96,7 @@ function mapRuntime(r: CreateOptions): void { CreateFieldRuntimeWorkingDirectory, "Blaxel create-time working directory is not mapped", ); - if ( - v.entrypoint !== undefined || - v.pty !== undefined || - v.user !== undefined || - v.language !== undefined || - v.version !== undefined - ) + if (hasUnsupportedRuntimeFields(v)) unsupported( CreateFieldRuntime, "runtime user, language, version, entrypoint, and PTY are not mapped by Blaxel", @@ -110,11 +106,7 @@ function mapRuntime(r: CreateOptions): void { function mapIsolation(r: CreateOptions): void { const v = r.isolation; if (!v) return; - if ( - (v.kind !== undefined && v.kind !== IsolationKind.Default) || - v.providerKind !== undefined || - v.nestedVirtualization !== undefined - ) + if (hasUnsupportedIsolation(v)) unsupported(CreateFieldIsolation, "Blaxel isolation class is not mapped"); } @@ -127,20 +119,7 @@ function mapResources(r: CreateOptions, p: SandboxCreateConfiguration): void { function mapNetwork(r: CreateOptions, p: SandboxCreateConfiguration): void { const v = r.network; if (!v) return; - if ( - v.egress !== undefined || - v.outboundCidrs !== undefined || - v.inboundCidrs !== undefined || - v.privateNetwork !== undefined || - v.linkedSandbox !== undefined || - v.proxyReference !== undefined || - v.outboundProxyUrl !== undefined || - v.customDomain !== undefined - ) - unsupported( - CreateFieldNetwork, - "requested network settings are unsupported by Blaxel", - ); + rejectUnsupportedNetwork(v); if (v.outboundDomains !== undefined) { p.network = { @@ -149,23 +128,41 @@ function mapNetwork(r: CreateOptions, p: SandboxCreateConfiguration): void { }; } - if (v.ports) { - const ports: Port[] = []; - for (const x of v.ports) { - const port = x.port; - if (port === undefined) - invalid(CreateFieldNetworkPortsPort, "port is required"); - if (port === 80 || port === 443 || port === 8080) - invalid( - CreateFieldNetworkPortsPort, - "ports 80, 443, and 8080 are reserved by Blaxel", - ); - const protocol = blaxelPortProtocol(x.transport ?? PortTransport.Default); - ports.push( - protocol === undefined ? { target: port } : { target: port, protocol }, + if (v.ports) p.ports = mapPorts(v.ports); +} + +const reservedBlaxelPorts = new Set([80, 443, 8080]); + +function mapPorts( + bindings: NonNullable["ports"]>, +): Port[] { + const ports: Port[] = []; + for (const x of bindings) { + const port = x.port; + if (port === undefined) + invalid(CreateFieldNetworkPortsPort, "port is required"); + if (reservedBlaxelPorts.has(port)) + invalid( + CreateFieldNetworkPortsPort, + "ports 80, 443, and 8080 are reserved by Blaxel", ); - } - p.ports = ports; + const protocol = blaxelPortProtocol(x.transport ?? PortTransport.Default); + ports.push( + protocol === undefined ? { target: port } : { target: port, protocol }, + ); + } + return ports; +} + +/** Default egress is a no-op (matches Go). Everything else here is unsupported. */ +function rejectUnsupportedNetwork( + network: NonNullable, +): void { + if (hasUnsupportedNetwork(network)) { + unsupported( + CreateFieldNetwork, + "requested network settings are unsupported by Blaxel", + ); } } @@ -185,68 +182,77 @@ function blaxelPortProtocol( } function mapSecurity(r: CreateOptions): void { - if (r.security !== undefined) - unsupported(CreateFieldSecurity, "Blaxel security settings are not mapped"); + rejectIfSet( + r.security !== undefined, + CreateFieldSecurity, + "Blaxel security settings are not mapped", + ); } function mapLifetime(r: CreateOptions, p: SandboxCreateConfiguration): void { const v = r.lifetime; if (!v) return; - if ( - v.ephemeral !== undefined || - v.idleStop !== undefined || - v.idlePause !== undefined || - v.stoppedArchive !== undefined || - v.stoppedDelete !== undefined - ) + if (hasUnsupportedLifetimeFields(v)) unsupported( CreateFieldLifetime, "requested lifecycle policy is unsupported by Blaxel", ); if (v.maximumLifetime !== undefined) { - p.ttl = durationString( - v.maximumLifetime, - CreateFieldLifetimeMaximumLifetime, - 5 * 60_000, - ); + p.ttl = durationString({ + ms: v.maximumLifetime, + field: CreateFieldLifetimeMaximumLifetime, + minimumMs: 5 * 60_000, + }); } if (v.idleTerminate) { - const mode = v.idleTerminate.mode ?? PolicyMode.Default; - if (mode === PolicyMode.Disabled) { - unsupported( - CreateFieldLifetimeIdleTerminate, - "disabling idle terminate is not representable", - ); - } else if (mode === PolicyMode.After) { - if (v.idleTerminate.after === undefined) - invalid(CreateFieldLifetimeIdleTerminateAfter, "duration is required"); - const value = durationString( - v.idleTerminate.after, - CreateFieldLifetimeIdleTerminateAfter, - 60_000, - ); - const lifecycle: SandboxLifecycle = { - expirationPolicies: [{ type: "ttl-idle", action: "delete", value }], - }; - p.lifecycle = lifecycle; - } else if (mode !== PolicyMode.Default) - invalid(CreateFieldLifetimeIdleTerminateMode, "unknown policy mode"); + const lifecycle = mapIdleTerminate(v.idleTerminate); + if (lifecycle !== undefined) p.lifecycle = lifecycle; + } +} + +function mapIdleTerminate( + action: NonNullable["idleTerminate"]>, +): SandboxLifecycle | undefined { + const mode = action.mode ?? PolicyMode.Default; + if (mode === PolicyMode.Disabled) { + unsupported( + CreateFieldLifetimeIdleTerminate, + "disabling idle terminate is not representable", + ); + } + if (mode === PolicyMode.After) { + if (action.after === undefined) + invalid(CreateFieldLifetimeIdleTerminateAfter, "duration is required"); + const value = durationString({ + ms: action.after, + field: CreateFieldLifetimeIdleTerminateAfter, + minimumMs: 60_000, + }); + return { + expirationPolicies: [{ type: "ttl-idle", action: "delete", value }], + }; } + if (mode !== PolicyMode.Default) + invalid(CreateFieldLifetimeIdleTerminateMode, "unknown policy mode"); + return undefined; } function mapReadiness(r: CreateOptions): void { - if (r.readiness !== undefined) - unsupported(CreateFieldReadiness, "Blaxel readiness probes are not mapped"); + rejectIfSet( + r.readiness !== undefined, + CreateFieldReadiness, + "Blaxel readiness probes are not mapped", + ); } function mapObservability(r: CreateOptions): void { - if (r.observability !== undefined) - unsupported( - CreateFieldObservability, - "Blaxel observability settings are not mapped", - ); + rejectIfSet( + r.observability !== undefined, + CreateFieldObservability, + "Blaxel observability settings are not mapped", + ); } // Kit Client.create applies provisioning.timeout as the abort deadline. @@ -289,6 +295,8 @@ export function mapSandboxInfo( if (mapped.region !== undefined) mutable(result).region = mapped.region; if (mapped.providerState !== undefined) mutable(result).providerState = mapped.providerState; + const resources = mapAllocatedResources(value); + if (resources !== undefined) mutable(result).resources = resources; return result; } @@ -303,8 +311,15 @@ function nonblank(v: string, f: string): string { return v; } +interface DurationStringArgs { + ms: number; + field: string; + minimumMs: number; +} + /** Converts Kit millisecond durations into Blaxel duration strings (s/m/h/d). */ -function durationString(ms: number, field: string, minimumMs: number): string { +function durationString(args: DurationStringArgs): string { + const { ms, field, minimumMs } = args; if (!Number.isFinite(ms) || ms <= 0 || ms % 1_000 !== 0) invalid(field, "must be a whole number of seconds"); if (ms < minimumMs) invalid(field, "is below the Blaxel minimum duration"); @@ -315,6 +330,68 @@ function durationString(ms: number, field: string, minimumMs: number): string { return `${seconds}s`; } +function rejectIfSet(set: boolean, field: string, message: string): void { + if (set) unsupported(field, message); +} + +function isDefined(value: T | undefined): value is T { + return value !== undefined; +} + +function hasUnsupportedRuntimeFields( + runtime: NonNullable, +): boolean { + return [ + runtime.entrypoint, + runtime.pty, + runtime.user, + runtime.language, + runtime.version, + ].some(isDefined); +} + +function hasUnsupportedIsolation( + isolation: NonNullable, +): boolean { + return ( + (isolation.kind !== undefined && + isolation.kind !== IsolationKind.Default) || + isolation.providerKind !== undefined || + isolation.nestedVirtualization !== undefined + ); +} + +function hasUnsupportedLifetimeFields( + lifetime: NonNullable, +): boolean { + return [ + lifetime.ephemeral, + lifetime.idleStop, + lifetime.idlePause, + lifetime.stoppedArchive, + lifetime.stoppedDelete, + ].some(isDefined); +} + +function hasUnsupportedNetwork( + network: NonNullable, +): boolean { + const nonDefaultEgress = + network.egress !== undefined && network.egress !== EgressMode.Default; + return ( + nonDefaultEgress || + [ + network.outboundCidrs, + network.inboundCidrs, + network.privateNetwork, + network.linkedSandbox, + network.proxyReference, + network.outboundProxyUrl, + network.customDomain, + ].some(isDefined) + ); +} + function invalid(field: string, message: string): never { providerError({ kind: ErrorKind.InvalidArgument, diff --git a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts index 5c545374..27425527 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts @@ -17,7 +17,7 @@ import type { Config as NativeClientConfig, SandboxCreateConfiguration, } from "@blaxel/core"; -import { providerError, type ProviderErrorInput } from "#errors.js"; +import { providerError } from "#errors.js"; type Mutable = { -readonly [K in keyof T]: T[K] }; @@ -31,7 +31,10 @@ export interface BlaxelSandboxIdentity { readonly status?: unknown; /** Runtime state (RUNNING / STANDBY). */ readonly state?: unknown; - readonly spec?: { readonly region?: string }; + readonly spec?: { + readonly region?: string; + readonly runtime?: { readonly memory?: number }; + }; } export function mapSandboxIdentity( @@ -97,7 +100,7 @@ export function mapResources( if (source.memoryMib !== undefined) { const value = source.memoryMib; if (value === 0n || value > BigInt(Number.MAX_SAFE_INTEGER)) - mappingError({ + providerError({ kind: ErrorKind.InvalidArgument, field: CreateFieldResourcesMemoryMib, message: "memory_mib cannot be represented", @@ -118,6 +121,17 @@ export function mapAPIKeyCredentials( return target; } +/** Maps native Blaxel memory (MB) onto Kit `resources.memoryMib` 1:1. */ +export function mapAllocatedResources( + source: BlaxelSandboxIdentity | undefined, +): Resources | undefined { + const memory = source?.spec?.runtime?.memory; + if (memory === undefined || !Number.isFinite(memory) || memory <= 0) { + return undefined; + } + return { memoryMib: BigInt(Math.trunc(memory)) }; +} + function runtimeState(source: BlaxelSandboxIdentity): string | undefined { if (source.state !== undefined && String(source.state) !== "") return String(source.state); @@ -173,7 +187,3 @@ function mapSandboxState( } } } - -function mappingError(input: ProviderErrorInput): never { - providerError(input); -} diff --git a/sdks/typescript/providers/blaxel/src/native.ts b/sdks/typescript/providers/blaxel/src/native.ts new file mode 100644 index 00000000..fbcadfeb --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/native.ts @@ -0,0 +1,79 @@ +import { + SandboxInstance, + settings, + type Config as NativeClientConfig, + type Directory, + type SandboxCreateConfiguration, + type SandboxCreateOptions, +} from "@blaxel/core"; +import type { BlaxelSandboxIdentity } from "#mappings/provider_mappings.js"; + +export interface BlaxelProcessResult { + readonly pid?: string; + readonly name?: string; + readonly status?: string; + readonly exitCode?: number; + readonly stdout?: string; + readonly stderr?: string; +} + +export interface ProcessExecRequest { + command: string; + waitForCompletion?: boolean; + timeout?: number; + workingDir?: string; + env?: Record; +} + +export interface NativeSandboxHandle extends BlaxelSandboxIdentity { + readonly fs: { + readBinary(path: string): Promise; + writeBinary(path: string, content: Uint8Array): Promise; + mkdir(path: string, permissions?: string): Promise; + ls(path: string): Promise; + rm(path: string, recursive?: boolean): Promise; + download(src: string, destinationPath: string): Promise; + }; + readonly process: { + exec(request: ProcessExecRequest): Promise; + wait( + identifier: string, + options?: { + maxWait?: number; + interval?: number; + signal?: AbortSignal; + }, + ): Promise; + }; + delete(): Promise; +} + +export interface NativeBlaxel { + configure(config: NativeClientConfig): void; + create( + params: SandboxCreateConfiguration, + options?: SandboxCreateOptions, + ): Promise; + get(name: string): Promise; + delete(name: string): Promise; +} + +export type NativeFactory = (config: NativeClientConfig) => NativeBlaxel; + +export function defaultNativeFactory(config: NativeClientConfig): NativeBlaxel { + settings.setConfig(config); + return { + configure(next: NativeClientConfig): void { + settings.setConfig(next); + }, + create(params, options) { + return SandboxInstance.create(params, options); + }, + get(name) { + return SandboxInstance.get(name); + }, + delete(name) { + return SandboxInstance.delete(name); + }, + }; +} diff --git a/sdks/typescript/providers/blaxel/src/process.ts b/sdks/typescript/providers/blaxel/src/process.ts new file mode 100644 index 00000000..c98a2330 --- /dev/null +++ b/sdks/typescript/providers/blaxel/src/process.ts @@ -0,0 +1,134 @@ +import { + ErrorKind, + ExecFieldTimeout, + sandboxError, + type ExecOptions, + type ExecResult, +} from "@sandbox-kit/sandbox"; +import { providerError } from "./errors.js"; +import type { + BlaxelProcessResult, + NativeSandboxHandle, + ProcessExecRequest, +} from "./native.js"; + +/** Blaxel's documented limit for a single waitForCompletion request. */ +const blaxelCompletionHoldSeconds = 60; + +const terminalProcessStatus = new Set([ + "completed", + "failed", + "killed", + "stopped", +]); + +export function processRequest( + request: ExecOptions, + timeout: number | undefined, +): ProcessExecRequest { + const body: ProcessExecRequest = { + command: blaxelCommand(request), + // 1–60s can use Blaxel's completion hold; omit/0/longer timeouts poll. + waitForCompletion: holdsCompletion(timeout), + // omit/0 → 0 disables Blaxel's default process auto-kill. + timeout: timeout ?? 0, + }; + if (request.workingDirectory !== undefined) { + body.workingDir = request.workingDirectory; + } + if (request.environment !== undefined) { + body.env = { ...request.environment }; + } + return body; +} + +/** Internal wait orchestration after Process.exec. */ +export interface FinishProcessArgs { + process: NativeSandboxHandle["process"]; + started: BlaxelProcessResult; + timeout: number | undefined; + signal: AbortSignal | undefined; +} + +export async function finishProcess( + args: FinishProcessArgs, +): Promise { + if (processFinished(args.started.status)) return execResult(args.started); + const identifier = args.started.pid || args.started.name; + if (!identifier) { + throw sandboxError({ + kind: ErrorKind.InvalidResponse, + provider: "blaxel", + operation: "run", + field: "", + message: "sandbox-kit blaxel: SDK returned no process identifier", + }); + } + const waitOptions: { + maxWait: number; + interval: number; + signal?: AbortSignal; + } = { + maxWait: args.timeout === undefined ? -1 : args.timeout * 1_000, + interval: 1_000, + }; + if (args.signal !== undefined) waitOptions.signal = args.signal; + return execResult(await args.process.wait(identifier, waitOptions)); +} + +/** + * Maps Kit ExecOptions.timeout (ms) to Blaxel seconds. + * Omitted and explicit zero become undefined so the caller sends 0 (no limit). + */ +export function execTimeoutSeconds( + timeout: number | undefined, +): number | undefined { + if (timeout === undefined || timeout === 0) return undefined; + if (!isWholeSecondTimeout(timeout)) { + providerError({ + kind: ErrorKind.InvalidArgument, + operation: "run", + field: ExecFieldTimeout, + message: "timeout must be a whole number of seconds", + }); + } + return timeout / 1_000; +} + +function isWholeSecondTimeout(timeout: number): boolean { + return Number.isFinite(timeout) && timeout >= 0 && timeout % 1_000 === 0; +} + +function holdsCompletion(timeoutSeconds: number | undefined): boolean { + return ( + timeoutSeconds !== undefined && + timeoutSeconds > 0 && + timeoutSeconds <= blaxelCompletionHoldSeconds + ); +} + +function processFinished(status: string | undefined): boolean { + return status !== undefined && terminalProcessStatus.has(status); +} + +function execResult(result: BlaxelProcessResult): ExecResult { + return { + exitCode: result.exitCode ?? 0, + stdout: new TextEncoder().encode(result.stdout ?? ""), + stderr: new TextEncoder().encode(result.stderr ?? ""), + }; +} + +function blaxelCommand(request: ExecOptions): string { + if (request.shell !== undefined) return request.shell; + return posixShellJoin(request.argv ?? []); +} + +function posixShellJoin(argv: readonly string[]): string { + return argv.map(posixQuote).join(" "); +} + +function posixQuote(value: string): string { + if (value === "") return "''"; + return "'" + value.replaceAll("'", `'"'"'`) + "'"; +} diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml index bf738e36..a7a65969 100644 --- a/specs/providers/blaxel.yaml +++ b/specs/providers/blaxel.yaml @@ -10,6 +10,8 @@ checks: notes: "lowercase alphanumeric with single hyphens, max 49", } - { path: source.image, required: true } + - { path: source.snapshot, unsupported: true } + - { path: source.warm_pool, unsupported: true } - { path: environment, format: env_name } - { path: placement, unsupported: true } - { path: resources.cpu_cores, unsupported: true } @@ -17,11 +19,13 @@ checks: - { path: resources.memory_limit_mib, unsupported: true } - { path: resources.disk_mib, unsupported: true } - { path: readiness, unsupported: true } + - { path: security, unsupported: true } + - { path: observability, unsupported: true } - { path: provisioning.queue_timeout, unsupported: true } - { path: provisioning.wait_for, unsupported: true, - notes: "Ready and Started rejected; Default/Scheduled accepted", + notes: "only Default and Scheduled accepted; Ready/Started and others rejected", } client: target: @@ -140,7 +144,12 @@ groups: name: SandboxCreateRequest fields: { memory: spec.runtime.memory } fields: - - { from: memory_mib, to: memory, maximum: 2147483647, cast: integer } + - { + from: memory_mib, + to: memory, + cast: integer, + notes: "reject 0; Go caps at MaxInt64, TypeScript at MAX_SAFE_INTEGER", + } - name: mapLifetimeDuration direction: request source: { name: LifetimePolicy } From a224d5facd07fd3f93b1fd6a32d88d64a5a9aa38 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 21:13:57 +0530 Subject: [PATCH 21/60] Point Blaxel example links at main for post-merge docs. Co-authored-by: Cursor Entire-Checkpoint: 01M7322PYZ9A5G3N0A1G5VHNP2 --- sdks/go/providers/blaxel/README.md | 16 +-- sdks/typescript/providers/blaxel/README.md | 14 +-- website/content/docs/blaxel.mdx | 4 +- website/content/docs/examples.mdx | 108 ++++++++++----------- 4 files changed, 71 insertions(+), 71 deletions(-) diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index e0391784..62d7fe97 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -103,14 +103,14 @@ fields; Blaxel volumes are outside create. | Operation | Program | | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | -| Create | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-sandbox) | -| Run a command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-command) | -| Shell / cwd / env / exit | [process examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process) | -| Read a file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/read-blaxel-file) | -| Filesystem | [filesystem examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem) | -| Look up | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/get-blaxel-sandbox) | -| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/delete-blaxel-sandbox) | -| All | [Go examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go) | +| Create | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-sandbox) | +| Run a command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process/run-blaxel-command) | +| Shell / cwd / env / exit | [process examples](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process) | +| Read a file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/read-blaxel-file) | +| Filesystem | [filesystem examples](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem) | +| Look up | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/lifecycle/get-blaxel-sandbox) | +| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/lifecycle/delete-blaxel-sandbox) | +| All | [Go examples](https://github.com/sandbox-kit/kit/tree/main/examples/go) | pkg.go.dev also shows `ExampleNew_*` samples in this package for create, lifecycle, process, and filesystem. diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index 194cb310..f53a72e3 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -100,13 +100,13 @@ overwrites the first. | Example | Project | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-sandbox) | -| Run a command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-command) | -| Process | [process examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process) | -| Read a file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/read-blaxel-file) | -| Filesystem | [filesystem examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem) | -| Retrieval | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox) | -| Full index | [TypeScript examples](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript) | +| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-sandbox) | +| Run a command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process/run-blaxel-command) | +| Process | [process examples](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process) | +| Read a file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/read-blaxel-file) | +| Filesystem | [filesystem examples](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem) | +| Retrieval | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox) | +| Full index | [TypeScript examples](https://github.com/sandbox-kit/kit/tree/main/examples/typescript) | ## License diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index d9ac9820..6b54e851 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -186,12 +186,12 @@ has no equivalent. Ready wait and readiness probes are unsupported. -Example: [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-sandbox) +Example: [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-sandbox) -Example: [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-sandbox) +Example: [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-sandbox) diff --git a/website/content/docs/examples.mdx b/website/content/docs/examples.mdx index 54ec82c4..a16e5b95 100644 --- a/website/content/docs/examples.mdx +++ b/website/content/docs/examples.mdx @@ -433,28 +433,28 @@ go run . | Example | Project | | ------- | ------- | -| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-sandbox) | -| Resources | [create-blaxel-with-resources](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-with-resources) | -| Labels | [create-blaxel-with-labels](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-with-labels) | -| TTL | [create-blaxel-with-ttl](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-with-ttl) | -| Idle delete | [create-blaxel-idle](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-idle) | -| Client timeout | [create-blaxel-client-timeout](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-client-timeout) | -| Ports | [create-blaxel-with-ports](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-with-ports) | -| Outbound domains | [create-blaxel-with-allowlists](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists) | +| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-sandbox) | +| Resources | [create-blaxel-with-resources](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-resources) | +| Labels | [create-blaxel-with-labels](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-labels) | +| TTL | [create-blaxel-with-ttl](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-ttl) | +| Idle delete | [create-blaxel-idle](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-idle) | +| Client timeout | [create-blaxel-client-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-client-timeout) | +| Ports | [create-blaxel-with-ports](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-ports) | +| Outbound domains | [create-blaxel-with-allowlists](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists) | | Example | Project | | ------- | ------- | -| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-sandbox) | -| Resources | [create-blaxel-with-resources](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-with-resources) | -| Labels | [create-blaxel-with-labels](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-with-labels) | -| TTL | [create-blaxel-with-ttl](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-with-ttl) | -| Idle delete | [create-blaxel-idle](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-idle) | -| Client timeout | [create-blaxel-client-timeout](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-client-timeout) | -| Ports | [create-blaxel-with-ports](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-with-ports) | -| Outbound domains | [create-blaxel-with-allowlists](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/configuration/create-blaxel-with-allowlists) | +| Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-sandbox) | +| Resources | [create-blaxel-with-resources](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-resources) | +| Labels | [create-blaxel-with-labels](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-labels) | +| TTL | [create-blaxel-with-ttl](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-ttl) | +| Idle delete | [create-blaxel-idle](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-idle) | +| Client timeout | [create-blaxel-client-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-client-timeout) | +| Ports | [create-blaxel-with-ports](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-ports) | +| Outbound domains | [create-blaxel-with-allowlists](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-with-allowlists) | @@ -466,24 +466,24 @@ go run . | Example | Project | | ------- | ------- | -| Run command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-command) | -| Shell command | [run-blaxel-shell-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-shell-command) | -| Run with cwd | [run-blaxel-with-cwd](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-with-cwd) | -| Run with env | [run-blaxel-with-env](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-with-env) | -| Run with timeout | [run-blaxel-with-timeout](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-with-timeout) | -| Check exit | [run-blaxel-check-exit](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/process/run-blaxel-check-exit) | +| Run command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process/run-blaxel-command) | +| Shell command | [run-blaxel-shell-command](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process/run-blaxel-shell-command) | +| Run with cwd | [run-blaxel-with-cwd](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process/run-blaxel-with-cwd) | +| Run with env | [run-blaxel-with-env](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process/run-blaxel-with-env) | +| Run with timeout | [run-blaxel-with-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process/run-blaxel-with-timeout) | +| Check exit | [run-blaxel-check-exit](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process/run-blaxel-check-exit) | | Example | Project | | ------- | ------- | -| Run command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-command) | -| Shell command | [run-blaxel-shell-command](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-shell-command) | -| Run with cwd | [run-blaxel-with-cwd](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-with-cwd) | -| Run with env | [run-blaxel-with-env](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-with-env) | -| Run with timeout | [run-blaxel-with-timeout](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-with-timeout) | -| Check exit | [run-blaxel-check-exit](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/process/run-blaxel-check-exit) | +| Run command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process/run-blaxel-command) | +| Shell command | [run-blaxel-shell-command](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process/run-blaxel-shell-command) | +| Run with cwd | [run-blaxel-with-cwd](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process/run-blaxel-with-cwd) | +| Run with env | [run-blaxel-with-env](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process/run-blaxel-with-env) | +| Run with timeout | [run-blaxel-with-timeout](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process/run-blaxel-with-timeout) | +| Check exit | [run-blaxel-check-exit](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process/run-blaxel-check-exit) | @@ -495,28 +495,28 @@ go run . | Example | Project | | ------- | ------- | -| Read file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/read-blaxel-file) | -| Write file | [write-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/write-blaxel-file) | -| Copy in | [copy-file-into-blaxel](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/copy-file-into-blaxel) | -| Copy out | [copy-file-from-blaxel](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/copy-file-from-blaxel) | -| Directory | [make-blaxel-directory](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/make-blaxel-directory) | -| List | [list-blaxel-directory](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/list-blaxel-directory) | -| Stat | [stat-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/stat-blaxel-file) | -| Remove | [remove-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/filesystem/remove-blaxel-file) | +| Read file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/read-blaxel-file) | +| Write file | [write-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/write-blaxel-file) | +| Copy in | [copy-file-into-blaxel](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/copy-file-into-blaxel) | +| Copy out | [copy-file-from-blaxel](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/copy-file-from-blaxel) | +| Directory | [make-blaxel-directory](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/make-blaxel-directory) | +| List | [list-blaxel-directory](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/list-blaxel-directory) | +| Stat | [stat-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/stat-blaxel-file) | +| Remove | [remove-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/remove-blaxel-file) | | Example | Project | | ------- | ------- | -| Read file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/read-blaxel-file) | -| Write file | [write-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/write-blaxel-file) | -| Copy in | [copy-file-into-blaxel](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/copy-file-into-blaxel) | -| Copy out | [copy-file-from-blaxel](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/copy-file-from-blaxel) | -| Directory | [make-blaxel-directory](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/make-blaxel-directory) | -| List | [list-blaxel-directory](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/list-blaxel-directory) | -| Stat | [stat-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/stat-blaxel-file) | -| Remove | [remove-blaxel-file](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/filesystem/remove-blaxel-file) | +| Read file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/read-blaxel-file) | +| Write file | [write-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/write-blaxel-file) | +| Copy in | [copy-file-into-blaxel](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/copy-file-into-blaxel) | +| Copy out | [copy-file-from-blaxel](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/copy-file-from-blaxel) | +| Directory | [make-blaxel-directory](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/make-blaxel-directory) | +| List | [list-blaxel-directory](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/list-blaxel-directory) | +| Stat | [stat-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/stat-blaxel-file) | +| Remove | [remove-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/filesystem/remove-blaxel-file) | @@ -528,20 +528,20 @@ go run . | Example | Project | | ------- | ------- | -| Get | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox) | -| Refresh | [refresh-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox) | -| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox) | -| Close handle | [close-blaxel-handle](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/lifecycle/close-blaxel-handle) | +| Get | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox) | +| Refresh | [refresh-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox) | +| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox) | +| Close handle | [close-blaxel-handle](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/lifecycle/close-blaxel-handle) | | Example | Project | | ------- | ------- | -| Get | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/get-blaxel-sandbox) | -| Refresh | [refresh-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox) | -| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/delete-blaxel-sandbox) | -| Close handle | [close-blaxel-handle](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/lifecycle/close-blaxel-handle) | +| Get | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/lifecycle/get-blaxel-sandbox) | +| Refresh | [refresh-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox) | +| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/lifecycle/delete-blaxel-sandbox) | +| Close handle | [close-blaxel-handle](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/lifecycle/close-blaxel-handle) | @@ -553,14 +553,14 @@ go run . | Example | Project | | ------- | ------- | -| Errors | [handle-blaxel-errors](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/typescript/blaxel/errors/handle-blaxel-errors) | +| Errors | [handle-blaxel-errors](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/errors/handle-blaxel-errors) | | Example | Project | | ------- | ------- | -| Errors | [handle-blaxel-errors](https://github.com/sandbox-kit/kit/tree/feat/blaxel-provider/examples/go/blaxel/errors/handle-blaxel-errors) | +| Errors | [handle-blaxel-errors](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/errors/handle-blaxel-errors) | From 4d048004ed639ddb67e6033c786b21e57d1d21da Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 21:21:50 +0530 Subject: [PATCH 22/60] Rename Blaxel example env key to BL_SANDBOX_NAME. Blaxel sandbox id is the create name; align .env, code, and READMEs with the lifecycle docs. Co-authored-by: Cursor Entire-Checkpoint: 01M732H503V5HEPGTN5B0P29PF --- .../go/blaxel/filesystem/copy-file-from-blaxel/.env.example | 2 +- examples/go/blaxel/filesystem/copy-file-from-blaxel/README.md | 2 +- examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go | 2 +- .../go/blaxel/filesystem/copy-file-into-blaxel/.env.example | 2 +- examples/go/blaxel/filesystem/copy-file-into-blaxel/README.md | 2 +- examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go | 2 +- .../go/blaxel/filesystem/list-blaxel-directory/.env.example | 2 +- examples/go/blaxel/filesystem/list-blaxel-directory/README.md | 2 +- examples/go/blaxel/filesystem/list-blaxel-directory/main.go | 2 +- .../go/blaxel/filesystem/make-blaxel-directory/.env.example | 2 +- examples/go/blaxel/filesystem/make-blaxel-directory/README.md | 2 +- examples/go/blaxel/filesystem/make-blaxel-directory/main.go | 2 +- examples/go/blaxel/filesystem/read-blaxel-file/.env.example | 2 +- examples/go/blaxel/filesystem/read-blaxel-file/README.md | 2 +- examples/go/blaxel/filesystem/read-blaxel-file/main.go | 2 +- examples/go/blaxel/filesystem/remove-blaxel-file/.env.example | 2 +- examples/go/blaxel/filesystem/remove-blaxel-file/README.md | 2 +- examples/go/blaxel/filesystem/remove-blaxel-file/main.go | 2 +- examples/go/blaxel/filesystem/stat-blaxel-file/.env.example | 2 +- examples/go/blaxel/filesystem/stat-blaxel-file/README.md | 2 +- examples/go/blaxel/filesystem/stat-blaxel-file/main.go | 2 +- examples/go/blaxel/filesystem/write-blaxel-file/.env.example | 2 +- examples/go/blaxel/filesystem/write-blaxel-file/README.md | 2 +- examples/go/blaxel/filesystem/write-blaxel-file/main.go | 2 +- examples/go/blaxel/lifecycle/close-blaxel-handle/.env.example | 2 +- examples/go/blaxel/lifecycle/close-blaxel-handle/README.md | 2 +- examples/go/blaxel/lifecycle/close-blaxel-handle/main.go | 2 +- .../go/blaxel/lifecycle/delete-blaxel-sandbox/.env.example | 2 +- examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md | 2 +- examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go | 2 +- examples/go/blaxel/lifecycle/get-blaxel-sandbox/.env.example | 2 +- examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md | 4 ++-- examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go | 2 +- .../go/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example | 2 +- examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md | 2 +- examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go | 2 +- examples/go/blaxel/process/run-blaxel-check-exit/.env.example | 2 +- examples/go/blaxel/process/run-blaxel-check-exit/README.md | 2 +- examples/go/blaxel/process/run-blaxel-check-exit/main.go | 2 +- examples/go/blaxel/process/run-blaxel-command/.env.example | 2 +- examples/go/blaxel/process/run-blaxel-command/README.md | 2 +- examples/go/blaxel/process/run-blaxel-command/main.go | 2 +- .../go/blaxel/process/run-blaxel-shell-command/.env.example | 2 +- examples/go/blaxel/process/run-blaxel-shell-command/README.md | 2 +- examples/go/blaxel/process/run-blaxel-shell-command/main.go | 2 +- examples/go/blaxel/process/run-blaxel-with-cwd/.env.example | 2 +- examples/go/blaxel/process/run-blaxel-with-cwd/README.md | 2 +- examples/go/blaxel/process/run-blaxel-with-cwd/main.go | 2 +- examples/go/blaxel/process/run-blaxel-with-env/.env.example | 2 +- examples/go/blaxel/process/run-blaxel-with-env/README.md | 2 +- examples/go/blaxel/process/run-blaxel-with-env/main.go | 2 +- .../go/blaxel/process/run-blaxel-with-timeout/.env.example | 2 +- examples/go/blaxel/process/run-blaxel-with-timeout/README.md | 2 +- examples/go/blaxel/process/run-blaxel-with-timeout/main.go | 2 +- .../blaxel/filesystem/copy-file-from-blaxel/.env.example | 2 +- .../blaxel/filesystem/copy-file-from-blaxel/README.md | 2 +- .../blaxel/filesystem/copy-file-from-blaxel/src/main.ts | 2 +- .../blaxel/filesystem/copy-file-into-blaxel/.env.example | 2 +- .../blaxel/filesystem/copy-file-into-blaxel/README.md | 2 +- .../blaxel/filesystem/copy-file-into-blaxel/src/main.ts | 2 +- .../blaxel/filesystem/list-blaxel-directory/.env.example | 2 +- .../blaxel/filesystem/list-blaxel-directory/README.md | 2 +- .../blaxel/filesystem/list-blaxel-directory/src/main.ts | 2 +- .../blaxel/filesystem/make-blaxel-directory/.env.example | 2 +- .../blaxel/filesystem/make-blaxel-directory/README.md | 2 +- .../blaxel/filesystem/make-blaxel-directory/src/main.ts | 2 +- .../blaxel/filesystem/read-blaxel-file/.env.example | 2 +- .../typescript/blaxel/filesystem/read-blaxel-file/README.md | 2 +- .../typescript/blaxel/filesystem/read-blaxel-file/src/main.ts | 2 +- .../blaxel/filesystem/remove-blaxel-file/.env.example | 2 +- .../typescript/blaxel/filesystem/remove-blaxel-file/README.md | 2 +- .../blaxel/filesystem/remove-blaxel-file/src/main.ts | 2 +- .../blaxel/filesystem/stat-blaxel-file/.env.example | 2 +- .../typescript/blaxel/filesystem/stat-blaxel-file/README.md | 2 +- .../typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts | 2 +- .../blaxel/filesystem/write-blaxel-file/.env.example | 2 +- .../typescript/blaxel/filesystem/write-blaxel-file/README.md | 2 +- .../blaxel/filesystem/write-blaxel-file/src/main.ts | 2 +- .../blaxel/lifecycle/close-blaxel-handle/.env.example | 2 +- .../typescript/blaxel/lifecycle/close-blaxel-handle/README.md | 2 +- .../blaxel/lifecycle/close-blaxel-handle/src/main.ts | 2 +- .../blaxel/lifecycle/delete-blaxel-sandbox/.env.example | 2 +- .../blaxel/lifecycle/delete-blaxel-sandbox/README.md | 2 +- .../blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts | 2 +- .../blaxel/lifecycle/get-blaxel-sandbox/.env.example | 2 +- .../typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md | 4 ++-- .../blaxel/lifecycle/get-blaxel-sandbox/src/main.ts | 2 +- .../blaxel/lifecycle/refresh-blaxel-sandbox/.env.example | 2 +- .../blaxel/lifecycle/refresh-blaxel-sandbox/README.md | 2 +- .../blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts | 2 +- .../blaxel/process/run-blaxel-check-exit/.env.example | 2 +- .../typescript/blaxel/process/run-blaxel-check-exit/README.md | 2 +- .../blaxel/process/run-blaxel-check-exit/src/main.ts | 2 +- .../typescript/blaxel/process/run-blaxel-command/.env.example | 2 +- .../typescript/blaxel/process/run-blaxel-command/README.md | 2 +- .../typescript/blaxel/process/run-blaxel-command/src/main.ts | 2 +- .../blaxel/process/run-blaxel-shell-command/.env.example | 2 +- .../blaxel/process/run-blaxel-shell-command/README.md | 2 +- .../blaxel/process/run-blaxel-shell-command/src/main.ts | 2 +- .../blaxel/process/run-blaxel-with-cwd/.env.example | 2 +- .../typescript/blaxel/process/run-blaxel-with-cwd/README.md | 2 +- .../typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts | 2 +- .../blaxel/process/run-blaxel-with-env/.env.example | 2 +- .../typescript/blaxel/process/run-blaxel-with-env/README.md | 2 +- .../typescript/blaxel/process/run-blaxel-with-env/src/main.ts | 2 +- .../blaxel/process/run-blaxel-with-timeout/.env.example | 2 +- .../blaxel/process/run-blaxel-with-timeout/README.md | 2 +- .../blaxel/process/run-blaxel-with-timeout/src/main.ts | 2 +- 108 files changed, 110 insertions(+), 110 deletions(-) diff --git a/examples/go/blaxel/filesystem/copy-file-from-blaxel/.env.example b/examples/go/blaxel/filesystem/copy-file-from-blaxel/.env.example index 38b9be92..d6ad3fe0 100644 --- a/examples/go/blaxel/filesystem/copy-file-from-blaxel/.env.example +++ b/examples/go/blaxel/filesystem/copy-file-from-blaxel/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/copy-file-from-blaxel/README.md b/examples/go/blaxel/filesystem/copy-file-from-blaxel/README.md index cbb28d22..2978db1d 100644 --- a/examples/go/blaxel/filesystem/copy-file-from-blaxel/README.md +++ b/examples/go/blaxel/filesystem/copy-file-from-blaxel/README.md @@ -28,7 +28,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go b/examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go index 31048971..fee28a06 100644 --- a/examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go +++ b/examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/filesystem/copy-file-into-blaxel/.env.example b/examples/go/blaxel/filesystem/copy-file-into-blaxel/.env.example index 38b9be92..d6ad3fe0 100644 --- a/examples/go/blaxel/filesystem/copy-file-into-blaxel/.env.example +++ b/examples/go/blaxel/filesystem/copy-file-into-blaxel/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/copy-file-into-blaxel/README.md b/examples/go/blaxel/filesystem/copy-file-into-blaxel/README.md index a5445d0a..150f5d1c 100644 --- a/examples/go/blaxel/filesystem/copy-file-into-blaxel/README.md +++ b/examples/go/blaxel/filesystem/copy-file-into-blaxel/README.md @@ -28,7 +28,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go b/examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go index 9bbfd67e..2907e00c 100644 --- a/examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go +++ b/examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/filesystem/list-blaxel-directory/.env.example b/examples/go/blaxel/filesystem/list-blaxel-directory/.env.example index a3459972..f6619de3 100644 --- a/examples/go/blaxel/filesystem/list-blaxel-directory/.env.example +++ b/examples/go/blaxel/filesystem/list-blaxel-directory/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/list-blaxel-directory/README.md b/examples/go/blaxel/filesystem/list-blaxel-directory/README.md index 10819665..a423f98c 100644 --- a/examples/go/blaxel/filesystem/list-blaxel-directory/README.md +++ b/examples/go/blaxel/filesystem/list-blaxel-directory/README.md @@ -28,7 +28,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/list-blaxel-directory/main.go b/examples/go/blaxel/filesystem/list-blaxel-directory/main.go index 9dde6e3e..3b82c83a 100644 --- a/examples/go/blaxel/filesystem/list-blaxel-directory/main.go +++ b/examples/go/blaxel/filesystem/list-blaxel-directory/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/filesystem/make-blaxel-directory/.env.example b/examples/go/blaxel/filesystem/make-blaxel-directory/.env.example index 6f1368a8..3ec608ba 100644 --- a/examples/go/blaxel/filesystem/make-blaxel-directory/.env.example +++ b/examples/go/blaxel/filesystem/make-blaxel-directory/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/make-blaxel-directory/README.md b/examples/go/blaxel/filesystem/make-blaxel-directory/README.md index 7abdbb9b..ed946c39 100644 --- a/examples/go/blaxel/filesystem/make-blaxel-directory/README.md +++ b/examples/go/blaxel/filesystem/make-blaxel-directory/README.md @@ -28,7 +28,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/make-blaxel-directory/main.go b/examples/go/blaxel/filesystem/make-blaxel-directory/main.go index 5059615d..6978669c 100644 --- a/examples/go/blaxel/filesystem/make-blaxel-directory/main.go +++ b/examples/go/blaxel/filesystem/make-blaxel-directory/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/filesystem/read-blaxel-file/.env.example b/examples/go/blaxel/filesystem/read-blaxel-file/.env.example index 9b877a81..f7524e0d 100644 --- a/examples/go/blaxel/filesystem/read-blaxel-file/.env.example +++ b/examples/go/blaxel/filesystem/read-blaxel-file/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/read-blaxel-file/README.md b/examples/go/blaxel/filesystem/read-blaxel-file/README.md index be6b67cc..613de447 100644 --- a/examples/go/blaxel/filesystem/read-blaxel-file/README.md +++ b/examples/go/blaxel/filesystem/read-blaxel-file/README.md @@ -28,7 +28,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/read-blaxel-file/main.go b/examples/go/blaxel/filesystem/read-blaxel-file/main.go index 5287a2f5..f05784b3 100644 --- a/examples/go/blaxel/filesystem/read-blaxel-file/main.go +++ b/examples/go/blaxel/filesystem/read-blaxel-file/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/filesystem/remove-blaxel-file/.env.example b/examples/go/blaxel/filesystem/remove-blaxel-file/.env.example index 040c9339..de34add4 100644 --- a/examples/go/blaxel/filesystem/remove-blaxel-file/.env.example +++ b/examples/go/blaxel/filesystem/remove-blaxel-file/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/remove-blaxel-file/README.md b/examples/go/blaxel/filesystem/remove-blaxel-file/README.md index 3e7d8de2..a4c29c64 100644 --- a/examples/go/blaxel/filesystem/remove-blaxel-file/README.md +++ b/examples/go/blaxel/filesystem/remove-blaxel-file/README.md @@ -28,7 +28,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/remove-blaxel-file/main.go b/examples/go/blaxel/filesystem/remove-blaxel-file/main.go index f80182a6..083b7d49 100644 --- a/examples/go/blaxel/filesystem/remove-blaxel-file/main.go +++ b/examples/go/blaxel/filesystem/remove-blaxel-file/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/.env.example b/examples/go/blaxel/filesystem/stat-blaxel-file/.env.example index 9b877a81..f7524e0d 100644 --- a/examples/go/blaxel/filesystem/stat-blaxel-file/.env.example +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/README.md b/examples/go/blaxel/filesystem/stat-blaxel-file/README.md index 0ea7fe23..d3c34bcf 100644 --- a/examples/go/blaxel/filesystem/stat-blaxel-file/README.md +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/README.md @@ -28,7 +28,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/main.go b/examples/go/blaxel/filesystem/stat-blaxel-file/main.go index 91c441bc..4e23cbb2 100644 --- a/examples/go/blaxel/filesystem/stat-blaxel-file/main.go +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/filesystem/write-blaxel-file/.env.example b/examples/go/blaxel/filesystem/write-blaxel-file/.env.example index 9b877a81..f7524e0d 100644 --- a/examples/go/blaxel/filesystem/write-blaxel-file/.env.example +++ b/examples/go/blaxel/filesystem/write-blaxel-file/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/write-blaxel-file/README.md b/examples/go/blaxel/filesystem/write-blaxel-file/README.md index 0e777d0e..ee180bda 100644 --- a/examples/go/blaxel/filesystem/write-blaxel-file/README.md +++ b/examples/go/blaxel/filesystem/write-blaxel-file/README.md @@ -28,7 +28,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/go/blaxel/filesystem/write-blaxel-file/main.go b/examples/go/blaxel/filesystem/write-blaxel-file/main.go index 0e9a0353..04a6e752 100644 --- a/examples/go/blaxel/filesystem/write-blaxel-file/main.go +++ b/examples/go/blaxel/filesystem/write-blaxel-file/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/lifecycle/close-blaxel-handle/.env.example b/examples/go/blaxel/lifecycle/close-blaxel-handle/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/go/blaxel/lifecycle/close-blaxel-handle/.env.example +++ b/examples/go/blaxel/lifecycle/close-blaxel-handle/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/go/blaxel/lifecycle/close-blaxel-handle/README.md b/examples/go/blaxel/lifecycle/close-blaxel-handle/README.md index 960b8477..b4f390c9 100644 --- a/examples/go/blaxel/lifecycle/close-blaxel-handle/README.md +++ b/examples/go/blaxel/lifecycle/close-blaxel-handle/README.md @@ -29,7 +29,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/go/blaxel/lifecycle/close-blaxel-handle/main.go b/examples/go/blaxel/lifecycle/close-blaxel-handle/main.go index c5949309..d7662e63 100644 --- a/examples/go/blaxel/lifecycle/close-blaxel-handle/main.go +++ b/examples/go/blaxel/lifecycle/close-blaxel-handle/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/.env.example b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/.env.example +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md index a6a19ef6..f5c15d06 100644 --- a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md @@ -29,7 +29,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go index 7d3dfd2f..6e0150c0 100644 --- a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/.env.example b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/.env.example +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md index 1af1eee0..7dd59b04 100644 --- a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md @@ -4,7 +4,7 @@ Look up an existing Blaxel sandbox by name and print metadata. ## What this means -`Get` retrieves the sandbox by name (the Blaxel id). Use the name printed by a create example as `BL_SANDBOX_ID`. +`Get` retrieves the sandbox by name (the Blaxel id). Use the name printed by a create example as `BL_SANDBOX_NAME`. ## Edge cases @@ -29,7 +29,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go index 3752a153..631c801d 100644 --- a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md index c4efbbca..7f515ed1 100644 --- a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md @@ -28,7 +28,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go index db92d6b8..02d2225f 100644 --- a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/.env.example b/examples/go/blaxel/process/run-blaxel-check-exit/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/go/blaxel/process/run-blaxel-check-exit/.env.example +++ b/examples/go/blaxel/process/run-blaxel-check-exit/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/README.md b/examples/go/blaxel/process/run-blaxel-check-exit/README.md index 0297295a..404dd054 100644 --- a/examples/go/blaxel/process/run-blaxel-check-exit/README.md +++ b/examples/go/blaxel/process/run-blaxel-check-exit/README.md @@ -28,7 +28,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/main.go b/examples/go/blaxel/process/run-blaxel-check-exit/main.go index 06db9684..ea7bab76 100644 --- a/examples/go/blaxel/process/run-blaxel-check-exit/main.go +++ b/examples/go/blaxel/process/run-blaxel-check-exit/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/process/run-blaxel-command/.env.example b/examples/go/blaxel/process/run-blaxel-command/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/go/blaxel/process/run-blaxel-command/.env.example +++ b/examples/go/blaxel/process/run-blaxel-command/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/go/blaxel/process/run-blaxel-command/README.md b/examples/go/blaxel/process/run-blaxel-command/README.md index 8d1e1379..c4752777 100644 --- a/examples/go/blaxel/process/run-blaxel-command/README.md +++ b/examples/go/blaxel/process/run-blaxel-command/README.md @@ -29,7 +29,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/go/blaxel/process/run-blaxel-command/main.go b/examples/go/blaxel/process/run-blaxel-command/main.go index 8f2ab81e..600015e7 100644 --- a/examples/go/blaxel/process/run-blaxel-command/main.go +++ b/examples/go/blaxel/process/run-blaxel-command/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/process/run-blaxel-shell-command/.env.example b/examples/go/blaxel/process/run-blaxel-shell-command/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/go/blaxel/process/run-blaxel-shell-command/.env.example +++ b/examples/go/blaxel/process/run-blaxel-shell-command/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/go/blaxel/process/run-blaxel-shell-command/README.md b/examples/go/blaxel/process/run-blaxel-shell-command/README.md index dcd26a4d..057bf252 100644 --- a/examples/go/blaxel/process/run-blaxel-shell-command/README.md +++ b/examples/go/blaxel/process/run-blaxel-shell-command/README.md @@ -28,7 +28,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/go/blaxel/process/run-blaxel-shell-command/main.go b/examples/go/blaxel/process/run-blaxel-shell-command/main.go index ba79844a..251ff3af 100644 --- a/examples/go/blaxel/process/run-blaxel-shell-command/main.go +++ b/examples/go/blaxel/process/run-blaxel-shell-command/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/process/run-blaxel-with-cwd/.env.example b/examples/go/blaxel/process/run-blaxel-with-cwd/.env.example index c93fc1d4..21c42a00 100644 --- a/examples/go/blaxel/process/run-blaxel-with-cwd/.env.example +++ b/examples/go/blaxel/process/run-blaxel-with-cwd/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Working directory inside the sandbox. diff --git a/examples/go/blaxel/process/run-blaxel-with-cwd/README.md b/examples/go/blaxel/process/run-blaxel-with-cwd/README.md index cbdde84c..470363d1 100644 --- a/examples/go/blaxel/process/run-blaxel-with-cwd/README.md +++ b/examples/go/blaxel/process/run-blaxel-with-cwd/README.md @@ -28,7 +28,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Working directory inside the sandbox. diff --git a/examples/go/blaxel/process/run-blaxel-with-cwd/main.go b/examples/go/blaxel/process/run-blaxel-with-cwd/main.go index ad5d1f4e..fec0ef56 100644 --- a/examples/go/blaxel/process/run-blaxel-with-cwd/main.go +++ b/examples/go/blaxel/process/run-blaxel-with-cwd/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/process/run-blaxel-with-env/.env.example b/examples/go/blaxel/process/run-blaxel-with-env/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/go/blaxel/process/run-blaxel-with-env/.env.example +++ b/examples/go/blaxel/process/run-blaxel-with-env/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/go/blaxel/process/run-blaxel-with-env/README.md b/examples/go/blaxel/process/run-blaxel-with-env/README.md index bebb50f3..fbddcf20 100644 --- a/examples/go/blaxel/process/run-blaxel-with-env/README.md +++ b/examples/go/blaxel/process/run-blaxel-with-env/README.md @@ -28,7 +28,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/go/blaxel/process/run-blaxel-with-env/main.go b/examples/go/blaxel/process/run-blaxel-with-env/main.go index 95668282..9af6c599 100644 --- a/examples/go/blaxel/process/run-blaxel-with-env/main.go +++ b/examples/go/blaxel/process/run-blaxel-with-env/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/.env.example b/examples/go/blaxel/process/run-blaxel-with-timeout/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/go/blaxel/process/run-blaxel-with-timeout/.env.example +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/README.md b/examples/go/blaxel/process/run-blaxel-with-timeout/README.md index cb8c7ef7..10484231 100644 --- a/examples/go/blaxel/process/run-blaxel-with-timeout/README.md +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/README.md @@ -32,7 +32,7 @@ Local check: `GOWORK=off go test ./...`. BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/main.go b/examples/go/blaxel/process/run-blaxel-with-timeout/main.go index b520e114..5e8e42f5 100644 --- a/examples/go/blaxel/process/run-blaxel-with-timeout/main.go +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/main.go @@ -39,7 +39,7 @@ func main() { log.Fatal(err) } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { log.Fatal(err) } diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.env.example b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.env.example index 38b9be92..d6ad3fe0 100644 --- a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.env.example +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/README.md b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/README.md index 0169066f..2f350f3b 100644 --- a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/README.md +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/README.md @@ -30,7 +30,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/src/main.ts b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/src/main.ts index 050cbad7..ab903f27 100644 --- a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/src/main.ts +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const remote = process.env.BL_REMOTE_PATH!; const local = process.env.BL_LOCAL_PATH!; await sandbox.filesystem.copyToLocal(remote, local); diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.env.example b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.env.example index 38b9be92..d6ad3fe0 100644 --- a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.env.example +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/README.md b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/README.md index 39dba246..13569e8e 100644 --- a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/README.md +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/README.md @@ -30,7 +30,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/src/main.ts b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/src/main.ts index 20ab1678..60f8c71e 100644 --- a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/src/main.ts +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const remote = process.env.BL_REMOTE_PATH!; const local = process.env.BL_LOCAL_PATH!; await sandbox.filesystem.copyFromLocal(local, remote); diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/.env.example b/examples/typescript/blaxel/filesystem/list-blaxel-directory/.env.example index a3459972..f6619de3 100644 --- a/examples/typescript/blaxel/filesystem/list-blaxel-directory/.env.example +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/README.md b/examples/typescript/blaxel/filesystem/list-blaxel-directory/README.md index 6cc02309..54cacc13 100644 --- a/examples/typescript/blaxel/filesystem/list-blaxel-directory/README.md +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/README.md @@ -30,7 +30,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/src/main.ts b/examples/typescript/blaxel/filesystem/list-blaxel-directory/src/main.ts index bf2fa966..e741e9f7 100644 --- a/examples/typescript/blaxel/filesystem/list-blaxel-directory/src/main.ts +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const remote = process.env.BL_REMOTE_PATH!; const entries = await sandbox.filesystem.listDirectory(remote); console.log(`${remote} (${entries.length} entries)`); diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/.env.example b/examples/typescript/blaxel/filesystem/make-blaxel-directory/.env.example index 6f1368a8..3ec608ba 100644 --- a/examples/typescript/blaxel/filesystem/make-blaxel-directory/.env.example +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/README.md b/examples/typescript/blaxel/filesystem/make-blaxel-directory/README.md index 8b7c736b..a732cda2 100644 --- a/examples/typescript/blaxel/filesystem/make-blaxel-directory/README.md +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/README.md @@ -30,7 +30,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/src/main.ts b/examples/typescript/blaxel/filesystem/make-blaxel-directory/src/main.ts index c07090ee..695fda6d 100644 --- a/examples/typescript/blaxel/filesystem/make-blaxel-directory/src/main.ts +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const remote = process.env.BL_REMOTE_PATH!; await sandbox.filesystem.makeDirectory(remote); console.log(`created ${remote}`); diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/.env.example b/examples/typescript/blaxel/filesystem/read-blaxel-file/.env.example index 9b877a81..f7524e0d 100644 --- a/examples/typescript/blaxel/filesystem/read-blaxel-file/.env.example +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/read-blaxel-file/README.md index 301271de..bf3fee26 100644 --- a/examples/typescript/blaxel/filesystem/read-blaxel-file/README.md +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/README.md @@ -30,7 +30,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/read-blaxel-file/src/main.ts index 146896b6..61f2afc2 100644 --- a/examples/typescript/blaxel/filesystem/read-blaxel-file/src/main.ts +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const remote = process.env.BL_REMOTE_PATH!; const data = await sandbox.filesystem.readFile(remote); console.log(`read ${remote} (${data.byteLength} bytes)`); diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/.env.example b/examples/typescript/blaxel/filesystem/remove-blaxel-file/.env.example index 040c9339..de34add4 100644 --- a/examples/typescript/blaxel/filesystem/remove-blaxel-file/.env.example +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/remove-blaxel-file/README.md index ac271a4e..84c85513 100644 --- a/examples/typescript/blaxel/filesystem/remove-blaxel-file/README.md +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/README.md @@ -30,7 +30,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/remove-blaxel-file/src/main.ts index 284f57d5..09464ff4 100644 --- a/examples/typescript/blaxel/filesystem/remove-blaxel-file/src/main.ts +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120_000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const remote = process.env.BL_REMOTE_PATH!; const recursive = process.env.BL_RECURSIVE === "true"; await sandbox.filesystem.remove(remote, recursive ? { recursive: true } : {}); diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/.env.example b/examples/typescript/blaxel/filesystem/stat-blaxel-file/.env.example index 9b877a81..f7524e0d 100644 --- a/examples/typescript/blaxel/filesystem/stat-blaxel-file/.env.example +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md index 66fc91f7..f09f82c0 100644 --- a/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md @@ -30,7 +30,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts index a38eae4c..9ce75411 100644 --- a/examples/typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const remote = process.env.BL_REMOTE_PATH!; const info = await sandbox.filesystem.stat(remote); console.log(`${info.path} type=${info.type} size=${info.size}`); diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/.env.example b/examples/typescript/blaxel/filesystem/write-blaxel-file/.env.example index 9b877a81..f7524e0d 100644 --- a/examples/typescript/blaxel/filesystem/write-blaxel-file/.env.example +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/write-blaxel-file/README.md index 3143bcd3..0acbe27a 100644 --- a/examples/typescript/blaxel/filesystem/write-blaxel-file/README.md +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/README.md @@ -30,7 +30,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Absolute path inside the sandbox. diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/write-blaxel-file/src/main.ts index 869bac29..18ea66e8 100644 --- a/examples/typescript/blaxel/filesystem/write-blaxel-file/src/main.ts +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const remote = process.env.BL_REMOTE_PATH!; const body = new TextEncoder().encode("hello from sandbox-kit\n"); await sandbox.filesystem.writeFile(remote, body); diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.env.example b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.env.example +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/README.md b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/README.md index 6fb6b47d..64cc8e85 100644 --- a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/README.md +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/README.md @@ -31,7 +31,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/src/main.ts b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/src/main.ts index bb48d580..2841d90e 100644 --- a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/src/main.ts +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); await sandbox.close(); console.log( `Closed the handle for Blaxel sandbox ${sandbox.id}. The sandbox was not changed.`, diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.env.example b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.env.example +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md index ef989d92..4fdaa8a5 100644 --- a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md @@ -31,7 +31,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts index 4771a2bc..6339e7e2 100644 --- a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts @@ -24,7 +24,7 @@ await using client = new Client({ timeout: 120_000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); assertCapability(sandbox, LifecycleCapability.Delete); await sandbox.delete({ wait: true }); console.log( diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.env.example b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.env.example +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md index 01f6b3b9..f1992552 100644 --- a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md @@ -4,7 +4,7 @@ Look up an existing Blaxel sandbox by name and print metadata. ## What this means -`Get` retrieves the sandbox by name (the Blaxel id). Use the name printed by a create example as `BL_SANDBOX_ID`. +`Get` retrieves the sandbox by name (the Blaxel id). Use the name printed by a create example as `BL_SANDBOX_NAME`. ## Edge cases @@ -31,7 +31,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts index 8d009d9f..716b0a2c 100644 --- a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120_000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const info = sandbox.info(); console.log( `Blaxel sandbox: id=${info.id} state=${info.state} native=${info.providerState ?? ""} capabilities=${sandbox.capabilities().join(",")}`, diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md index df36f16c..081c167b 100644 --- a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md @@ -30,7 +30,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts index 0a330b7a..6e9afd26 100644 --- a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts @@ -24,7 +24,7 @@ await using client = new Client({ timeout: 120_000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); assertCapability(sandbox, LifecycleCapability.Refresh); const info = await sandbox.refresh(); console.log(`Blaxel metadata refreshed: ${sandbox.id} (${info.state})`); diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/.env.example b/examples/typescript/blaxel/process/run-blaxel-check-exit/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/typescript/blaxel/process/run-blaxel-check-exit/.env.example +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/README.md b/examples/typescript/blaxel/process/run-blaxel-check-exit/README.md index 0c97b517..b7a3bc69 100644 --- a/examples/typescript/blaxel/process/run-blaxel-check-exit/README.md +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/README.md @@ -30,7 +30,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-check-exit/src/main.ts index e2da0fa6..e10f077e 100644 --- a/examples/typescript/blaxel/process/run-blaxel-check-exit/src/main.ts +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const result = await sandbox.process.run({ shell: "exit 7" }); if (result.exitCode !== 7) { throw new Error(`want exit 7, got ${result.exitCode}`); diff --git a/examples/typescript/blaxel/process/run-blaxel-command/.env.example b/examples/typescript/blaxel/process/run-blaxel-command/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/typescript/blaxel/process/run-blaxel-command/.env.example +++ b/examples/typescript/blaxel/process/run-blaxel-command/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/typescript/blaxel/process/run-blaxel-command/README.md b/examples/typescript/blaxel/process/run-blaxel-command/README.md index ecff18b9..919567a5 100644 --- a/examples/typescript/blaxel/process/run-blaxel-command/README.md +++ b/examples/typescript/blaxel/process/run-blaxel-command/README.md @@ -31,7 +31,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/typescript/blaxel/process/run-blaxel-command/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-command/src/main.ts index 65c1d6db..c040996c 100644 --- a/examples/typescript/blaxel/process/run-blaxel-command/src/main.ts +++ b/examples/typescript/blaxel/process/run-blaxel-command/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const result = await sandbox.process.run({ argv: ["echo", "hello from kit"], }); diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/.env.example b/examples/typescript/blaxel/process/run-blaxel-shell-command/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/typescript/blaxel/process/run-blaxel-shell-command/.env.example +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/README.md b/examples/typescript/blaxel/process/run-blaxel-shell-command/README.md index 2eac1bdb..478bb9a4 100644 --- a/examples/typescript/blaxel/process/run-blaxel-shell-command/README.md +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/README.md @@ -30,7 +30,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-shell-command/src/main.ts index 685c843f..c5b4d830 100644 --- a/examples/typescript/blaxel/process/run-blaxel-shell-command/src/main.ts +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const result = await sandbox.process.run({ shell: `echo "hello from shell"`, }); diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/.env.example b/examples/typescript/blaxel/process/run-blaxel-with-cwd/.env.example index c93fc1d4..21c42a00 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-cwd/.env.example +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/.env.example @@ -1,7 +1,7 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Working directory inside the sandbox. diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/README.md b/examples/typescript/blaxel/process/run-blaxel-with-cwd/README.md index 3f34a471..25dd2509 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-cwd/README.md +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/README.md @@ -30,7 +30,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= # Working directory inside the sandbox. diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts index 9d0cf714..d376a5d2 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const cwd = process.env.BL_WORKING_DIRECTORY || "/tmp"; const result = await sandbox.process.run({ argv: ["pwd"], diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/.env.example b/examples/typescript/blaxel/process/run-blaxel-with-env/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-env/.env.example +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/README.md b/examples/typescript/blaxel/process/run-blaxel-with-env/README.md index 6137c2ab..066ac151 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-env/README.md +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/README.md @@ -30,7 +30,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-with-env/src/main.ts index cb470f18..eaef0dc4 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-env/src/main.ts +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const result = await sandbox.process.run({ argv: ["printenv", "KIT_DEMO"], environment: { KIT_DEMO: "from-kit" }, diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/.env.example b/examples/typescript/blaxel/process/run-blaxel-with-timeout/.env.example index 0ba3e326..3470ef8a 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-timeout/.env.example +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/.env.example @@ -1,6 +1,6 @@ BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md b/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md index 3fb9d51b..0c054906 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md @@ -34,7 +34,7 @@ Requires Node.js 20/22/24.15+, npm 11.19.0, and this checkout. Skip copying if BL_API_KEY= BL_WORKSPACE= # Sandbox name (Blaxel id is the name). -BL_SANDBOX_ID= +BL_SANDBOX_NAME= # Optional region override (default us-pdx-1). BL_REGION= ``` diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-with-timeout/src/main.ts index 553392fd..bed95ad4 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-timeout/src/main.ts +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/src/main.ts @@ -11,7 +11,7 @@ await using client = new Client({ timeout: 120000, }); -const sandbox = await client.get(process.env.BL_SANDBOX_ID!); +const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const result = await sandbox.process.run({ argv: ["echo", "hello with timeout"], timeout: 5_000, From f46190d97e91ac29f9ab075e07b4f56886079481 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 21:28:31 +0530 Subject: [PATCH 23/60] Tighten Blaxel user docs so create, wait, idle, and env keys need no guesswork. Co-authored-by: Cursor Entire-Checkpoint: 01M732XCQX85CNYAHZCVPWW61W --- README.md | 11 +++--- docs/sandbox-creation.md | 8 +++- examples/README.md | 10 +++-- examples/go/README.md | 10 +++-- examples/typescript/README.md | 11 ++++-- sdks/go/providers/blaxel/README.md | 21 +++++----- sdks/typescript/providers/blaxel/README.md | 7 ++-- website/content/docs/blaxel.mdx | 8 ++-- website/content/docs/concepts.mdx | 4 +- website/content/docs/configuration.mdx | 3 ++ website/content/docs/create.mdx | 18 +++++---- website/content/docs/index.mdx | 4 +- website/content/docs/lifecycle.mdx | 6 +++ website/content/docs/process.mdx | 2 + website/content/docs/sdk/create-options.mdx | 44 +++++++++++++++++++++ website/content/docs/sdk/enums.mdx | 5 +++ website/content/docs/sdk/filesystem.mdx | 10 +++++ website/content/docs/sdk/process.mdx | 6 ++- 18 files changed, 142 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 257f15c1..d356bb2e 100644 --- a/README.md +++ b/README.md @@ -159,11 +159,12 @@ Blaxel needs an API key, a workspace, and a client region: [TypeScript](examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md) · [Go](examples/go/blaxel/configuration/create-blaxel-sandbox/README.md). -Creation examples provision real sandboxes. Filesystem, process, and lifecycle examples -use the ID in `.env` (stop / terminate / archive / delete can destroy that -resource; filesystem examples use the path you set; process examples run one -command). Error-handling examples -fail locally and create nothing. Skip copying `.env.example` if you already +Creation examples provision real sandboxes. Filesystem, process, and lifecycle +examples use the sandbox id in `.env` (`DAYTONA_SANDBOX_ID`, `MODAL_SANDBOX_ID`, +or Blaxel `BL_SANDBOX_NAME` — Blaxel id is the create name). Stop / terminate / +archive / delete can destroy that resource; filesystem examples use the path +you set; process examples run one command. Error-handling examples fail +locally and create nothing. Skip copying `.env.example` if you already have a configured `.env`. Full catalog: [examples/README.md](examples/README.md) · diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index 307a7452..df8f3416 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -20,8 +20,8 @@ Blaxel (`modal@0.11.1`, `@daytona/sdk@0.225.0`, `@blaxel/core@0.3.36` / | Environment and labels | Environment map and tags | Environment map and labels | Environment map and labels | | Security | Named secrets and workload identity | Egress-placeholder secrets and public access | Unsupported | | Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | Ports (not 80/443/8080; Default → Blaxel HTTP); outbound domains | -| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m) | -| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Kit client/create timeout; Blaxel native create timeout not forwarded; Ready wait unsupported | +| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m); explicit idle disable rejected | +| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Kit client/create timeout; native create timeout not forwarded; waitFor Default/Scheduled only | | Observability | Verbose output | Telemetry endpoint | Unsupported | Kit has no portable volume fields; Blaxel volumes stay out of create the same @@ -99,6 +99,10 @@ before provider calls unless `lifetime.ephemeral` is explicitly true. Modal idle termination supports disabled or positive whole-second delays. Its maximum lifetime and idle delay remain separate settings. +Blaxel idle terminate supports only `PolicyModeAfter` with a delay of at least +1m, or provider default. `PolicyModeDisabled` on idle terminate is rejected. +Maximum lifetime, when set, must be at least 5m. + Image references support lowercase repository paths, optional registry hostnames and ports, tags up to 128 characters, and SHA-256 digests, within 512 characters. IPv6 registry literals and other digest algorithms are unsupported. diff --git a/examples/README.md b/examples/README.md index bb7c1edb..0833c621 100644 --- a/examples/README.md +++ b/examples/README.md @@ -24,14 +24,16 @@ Modal needs a token pair and an existing app: [Go](go/modal/configuration/create-modal-sandbox/README.md) · [TypeScript](typescript/modal/configuration/create-modal-sandbox/README.md). -Blaxel needs an API key and workspace: +Blaxel needs an API key, a workspace, and a client region (examples use +`BL_REGION`, default `us-pdx-1`): [Go](go/blaxel/configuration/create-blaxel-sandbox/README.md) · [TypeScript](typescript/blaxel/configuration/create-blaxel-sandbox/README.md). Creation examples provision real resources. Filesystem, process, and lifecycle -examples use the sandbox ID in `.env`. A filesystem example runs one file -operation (read, write, copy, mkdir, list, stat, or remove) on the path you -set. A process example runs one command. Stop, +examples use the sandbox id in `.env` (`DAYTONA_SANDBOX_ID`, `MODAL_SANDBOX_ID`, +or Blaxel `BL_SANDBOX_NAME` — Blaxel id is the create name). A filesystem +example runs one file operation (read, write, copy, mkdir, list, stat, or +remove) on the path you set. A process example runs one command. Stop, terminate, archive, and delete can destroy the sandbox. Error-handling examples fail locally. `Client.Close` closes your connection to Daytona, Modal, or Blaxel. `Sandbox.Close` does not change the sandbox, and you can keep diff --git a/examples/go/README.md b/examples/go/README.md index a4a6cd2b..e9aed6be 100644 --- a/examples/go/README.md +++ b/examples/go/README.md @@ -17,12 +17,14 @@ Skip copying if `.env` already exists. Programs load only that project’s `.env` with `godotenv` (it does not override existing shell env). There is no provider-level `.env`. The SDK does not load files. Modal app setup: [create-modal-sandbox](modal/configuration/create-modal-sandbox/README.md). -Blaxel setup: [create-blaxel-sandbox](blaxel/configuration/create-blaxel-sandbox/README.md). +Blaxel needs an API key, workspace, and client region: +[create-blaxel-sandbox](blaxel/configuration/create-blaxel-sandbox/README.md). Creation programs provision real sandboxes (linked Daytona creates a parent and -child). Filesystem, process, and lifecycle programs use the configured sandbox -ID. Read the guide before a destructive stop, terminate, archive, delete, or -remove. +child). Filesystem, process, and lifecycle programs use the sandbox id in +`.env` (`DAYTONA_SANDBOX_ID`, `MODAL_SANDBOX_ID`, or Blaxel `BL_SANDBOX_NAME` — +Blaxel id is the create name). Read the guide before a destructive stop, +terminate, archive, delete, or remove. Error-handling programs fail locally. Closing a handle leaves the sandbox as it is. Closing the client closes your connection to Daytona, Modal, or Blaxel. diff --git a/examples/typescript/README.md b/examples/typescript/README.md index 31e0523a..54a0b9fe 100644 --- a/examples/typescript/README.md +++ b/examples/typescript/README.md @@ -22,11 +22,14 @@ npm start `npm start` loads only that project’s `.env` via `--env-file-if-exists` (it does not override existing shell env). There is no provider-level `.env`. -Creation examples provision real cloud resources. Filesystem, process, and lifecycle -examples act on the sandbox ID in `.env`. Closing a handle leaves the sandbox as it is. Closing -the client closes your connection to Daytona, Modal, or Blaxel. Modal setup: +Creation examples provision real cloud resources. Filesystem, process, and +lifecycle examples use the sandbox id in `.env` (`DAYTONA_SANDBOX_ID`, +`MODAL_SANDBOX_ID`, or Blaxel `BL_SANDBOX_NAME` — Blaxel id is the create name). +Closing a handle leaves the sandbox as it is. Closing the client closes your +connection to Daytona, Modal, or Blaxel. Modal setup: [create-modal-sandbox](modal/configuration/create-modal-sandbox/README.md). -Blaxel setup: [create-blaxel-sandbox](blaxel/configuration/create-blaxel-sandbox/README.md). +Blaxel needs an API key, workspace, and client region: +[create-blaxel-sandbox](blaxel/configuration/create-blaxel-sandbox/README.md). ## Catalog diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 62d7fe97..9045da3f 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -44,7 +44,8 @@ if err != nil { fmt.Println(instance.ID()) ``` -Create requires a name and an image. Closing the client does not delete the +Create requires a name and an image. `ID()` is that name — use it with `Get` +and example env `BL_SANDBOX_NAME`. Closing the client does not delete the sandbox. Call `Delete`, or set a lifetime policy. ## Settings @@ -65,16 +66,18 @@ Create from a registry image with an explicit name. The name must be lowercase alphanumeric with single hyphens and at most 49 characters. You can set labels, environment variables, memory, ports, outbound domains, maximum lifetime, and idle terminate. Max-age TTL must be at least 5m; idle TTL must be at least 1m. -Ports 80, 443, and 8080 are rejected (Blaxel-reserved). Default port transport -leaves the protocol to Blaxel (HTTP); plain maps to TCP; TLS maps to TLS. -HTTP/2 over TLS is rejected. Kit `Config.Timeout` / create call timeout is the -create deadline. The Go Blaxel SDK has no separate creation-timeout argument. -`WaitConditionReady` is rejected. +`PolicyModeDisabled` on idle terminate is rejected. Ports 80, 443, and 8080 are +rejected (Blaxel-reserved). Default port transport leaves the protocol to +Blaxel (HTTP); plain maps to TCP; TLS maps to TLS. HTTP/2 over TLS is rejected. +Kit `Config.Timeout` / create call timeout is the create deadline. The Go +Blaxel SDK has no separate creation-timeout argument. `WaitFor` accepts only +Default and Scheduled; `WaitConditionReady` is rejected. Create always uses +the native create path (not create-if-not-exists). On the handle: `Get`, `Refresh`, `Delete`, and `Close`. `Get` and `Refresh` look -up the sandbox by name. `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and -`Terminate` are not supported. `Close` does not contact Blaxel and does not -change the sandbox. +up the sandbox by name (`ID()` equals the create name). `Attach`, `Start`, +`Stop`, `Pause`, `Archive`, and `Terminate` are not supported. `Close` does not +contact Blaxel and does not change the sandbox. `Filesystem` supports the shared file calls: `ReadFile`, `WriteFile`, `CopyFromLocal`, `CopyToLocal`, `MakeDirectory`, `ListDirectory`, `Stat`, and diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index f53a72e3..93c309da 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -31,7 +31,7 @@ const sandbox = await client.create({ resources: { memoryMib: 4096n }, lifetime: { maximumLifetime: 24 * 60 * 60_000 }, }); -console.log(sandbox.id); +console.log(sandbox.id); // same as create name; use with get / BL_SANDBOX_NAME ``` Closing the client releases local SDK resources only. Reclaim sandboxes with @@ -59,8 +59,9 @@ integer MB; Kit does not convert; Blaxel CPU follows memory), labels, environment variables, ports, outbound domains, max-age TTL (at least 5m), and idle-delete policy (at least 1m; explicit disable is rejected). Client / create `timeout` is the Kit deadline. `@blaxel/core`'s native creation timeout -(1–50s) is not forwarded. `provisioning.waitFor` Ready is unsupported. -Creation calls native `SandboxInstance.create`, not `createIfNotExists`. Ports +(1–50s) is not forwarded. `provisioning.waitFor` accepts only `Default` and +`Scheduled`; Ready (and Started / Submitted) are unsupported. Creation calls +native `SandboxInstance.create`, not `createIfNotExists`. Ports 80, 443, and 8080 are rejected (Blaxel-reserved). Default port transport leaves the protocol to Blaxel (HTTP); plain maps to TCP; TLS maps to TLS. HTTP/2 over TLS is rejected. Kit has no portable volume fields; Blaxel volumes are outside diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index 6b54e851..1f8031f8 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -50,7 +50,7 @@ const sandbox = await client.create({ name: "kit-example", source: { image: { reference: "blaxel/base-image:latest" } }, }); -console.log(sandbox.id); +console.log(sandbox.id); // same as create name; use with get / BL_SANDBOX_NAME ``` `timeout` is milliseconds. Prefer `await using`; otherwise call `await client.close()` when finished. @@ -96,7 +96,7 @@ func main() { if err != nil { panic(err) } - fmt.Println(instance.ID()) + fmt.Println(instance.ID()) // same as create name; use with Get / BL_SANDBOX_NAME } ``` @@ -176,7 +176,9 @@ memory, so CPU and disk overrides are rejected. Default port transport leaves the protocol to Blaxel (HTTP); plain maps to TCP; TLS maps to TLS; HTTP/2 over TLS is rejected. Client `timeout` is the Kit create/operation deadline. `@blaxel/core`'s native creation timeout (1–50s) is not forwarded; the Go SDK -has no equivalent. Ready wait and readiness probes are unsupported. +has no equivalent. `provisioning.waitFor` accepts only `Default` and +`Scheduled`; `Ready` (and `Started` / `Submitted`) are rejected. Readiness +probes are unsupported. Explicit idle-terminate disable is rejected. ## Related diff --git a/website/content/docs/concepts.mdx b/website/content/docs/concepts.mdx index 0afe94fa..9cfd15cb 100644 --- a/website/content/docs/concepts.mdx +++ b/website/content/docs/concepts.mdx @@ -152,7 +152,7 @@ const sandbox = await client.create({ name: "kit-example", source: { image: { reference: "blaxel/base-image:latest" } }, }); -console.log(sandbox.id); +console.log(sandbox.id); // same as create name ``` @@ -204,7 +204,7 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ if err != nil { return err } -fmt.Println(instance.ID()) +fmt.Println(instance.ID()) // same as create name ``` diff --git a/website/content/docs/configuration.mdx b/website/content/docs/configuration.mdx index 5d55b295..47d1fc92 100644 --- a/website/content/docs/configuration.mdx +++ b/website/content/docs/configuration.mdx @@ -347,6 +347,9 @@ client, err := sandbox.NewClient(modal.New(), config) | Region | `region` — required for create | | Endpoint | Unsupported | +`@blaxel/core` stores credentials in process-global `settings`. Use one Blaxel +Kit client, or one credential set, per process. + ```ts await using client = new Client({ provider: blaxel(), diff --git a/website/content/docs/create.mdx b/website/content/docs/create.mdx index 80aaf1b3..0adb21c0 100644 --- a/website/content/docs/create.mdx +++ b/website/content/docs/create.mdx @@ -275,9 +275,11 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ | CPU | Derived by Blaxel from memory; Kit rejects CPU overrides | | Memory | `memoryMib` / `memory_mib` passed 1:1; Blaxel labels the unit MB | | Disk | Cannot be set | -| Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m) | +| Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m); explicit idle disable rejected | | Placement | Client `region` required. A placement block on this request is rejected | | Ports | Default → Blaxel HTTP; plain → TCP; TLS → TLS; Http2Tls rejected; 80/443/8080 reserved | +| Wait | `waitFor` only `Default` / `Scheduled`; `Ready` rejected | +| Runtime | Unsupported | Kit has no portable volume fields. @@ -609,12 +611,14 @@ See [Examples](/docs/examples). Create needs a Blaxel-valid `name` (lowercase alphanumeric with single hyphens, -at most 49 characters), an image, and client `region`. Memory maps 1:1; CPU, -disk, placement, readiness, and volumes are rejected. Ports 80, 443, and 8080 -are reserved. Default port transport leaves the protocol to Blaxel (HTTP); -plain maps to TCP; TLS maps to TLS; HTTP/2 over TLS is rejected. Kit create -timeout is the local deadline; Blaxel's native creation timeout is not -forwarded. See [Examples](/docs/examples). +at most 49 characters), an image, and client `region`. The returned id is that +name. Memory maps 1:1; CPU, disk, placement, readiness, runtime, and volumes +are rejected. Ports 80, 443, and 8080 are reserved. Default port transport +leaves the protocol to Blaxel (HTTP); plain maps to TCP; TLS maps to TLS; +HTTP/2 over TLS is rejected. Max-age TTL ≥5m; idle terminate ≥1m; explicit +idle disable is rejected. `waitFor` only `Default` / `Scheduled`; `Ready` is +rejected. Kit create timeout is the local deadline; Blaxel's native creation +timeout is not forwarded. See [Examples](/docs/examples). diff --git a/website/content/docs/index.mdx b/website/content/docs/index.mdx index eb6882f1..56b9f91c 100644 --- a/website/content/docs/index.mdx +++ b/website/content/docs/index.mdx @@ -162,7 +162,7 @@ const sandbox = await client.create({ name: "kit-example", source: { image: { reference: "blaxel/base-image:latest" } }, }); -console.log(sandbox.id); +console.log(sandbox.id); // same as create name ``` @@ -298,7 +298,7 @@ func main() { if err != nil { log.Fatal(err) } - fmt.Println(instance.ID()) + fmt.Println(instance.ID()) // same as create name } ``` diff --git a/website/content/docs/lifecycle.mdx b/website/content/docs/lifecycle.mdx index b0481e82..cf467c60 100644 --- a/website/content/docs/lifecycle.mdx +++ b/website/content/docs/lifecycle.mdx @@ -387,6 +387,12 @@ defer client.Close(context.Background()) `Sandbox.Close` does not stop or delete the sandbox. Later calls on that handle still reach the provider. + + On Blaxel, handle `close` releases local state only and does not contact + Blaxel. The sandbox keeps running until you `delete` it or a lifetime policy + ends it. + + diff --git a/website/content/docs/process.mdx b/website/content/docs/process.mdx index 25d1486e..18c80741 100644 --- a/website/content/docs/process.mdx +++ b/website/content/docs/process.mdx @@ -165,6 +165,8 @@ fmt.Println(result) | fractional (`1500` ms) | rejected (`timeout`) | Blaxel returns both `stdout` and `stderr`. `run` waits until the command ends. +Canceling the context or aborting the signal stops the wait only; the remote +command may keep running. An HTTP 504 while waiting is a timeout error. diff --git a/website/content/docs/sdk/create-options.mdx b/website/content/docs/sdk/create-options.mdx index 3a8b5a05..47718559 100644 --- a/website/content/docs/sdk/create-options.mdx +++ b/website/content/docs/sdk/create-options.mdx @@ -242,6 +242,10 @@ Set exactly one variant. Unsupported variants fail before the provider call. | `entrypoint` | Entrypoint argv | | `pty` | Request a PTY when supported | + + Blaxel rejects create-time runtime fields before the call. + + @@ -254,6 +258,10 @@ Set exactly one variant. Unsupported variants fail before the provider call. | `Entrypoint` | Entrypoint argv | | `Pty` | Request a PTY when supported | + + Blaxel rejects create-time runtime fields before the call. + + @@ -333,6 +341,13 @@ Set exactly one variant. Unsupported variants fail before the provider call. only. Ephemeral, idle stop/pause, and stopped archive/delete are rejected. + + Blaxel supports `maximumLifetime` (≥5m when set) and `idleTerminate` (≥1m + when `PolicyMode.After`). `idleTerminate` with `PolicyMode.Disabled` is + rejected. Ephemeral, idle stop/pause, and stopped archive/delete are + rejected. + + @@ -357,6 +372,13 @@ Set exactly one variant. Unsupported variants fail before the provider call. only. Ephemeral, idle stop/pause, and stopped archive/delete are rejected. + + Blaxel supports `MaximumLifetime` (≥5m when set) and `IdleTerminate` (≥1m + when `PolicyModeAfter`). `IdleTerminate` with `PolicyModeDisabled` is + rejected. Ephemeral, idle stop/pause, and stopped archive/delete are + rejected. + + @@ -403,6 +425,13 @@ Set exactly one variant. Unsupported variants fail before the provider call. positive timeout. `queueTimeout` is unsupported. + + Kit `timeout` / client timeout is the create deadline. `queueTimeout` is + unsupported. `waitFor` accepts only `Default` and `Scheduled`; `Ready`, + `Started`, and `Submitted` are rejected. `@blaxel/core`'s native creation + timeout (1–50s) is not forwarded. + + @@ -423,6 +452,13 @@ Set exactly one variant. Unsupported variants fail before the provider call. positive timeout. `QueueTimeout` is unsupported. + + Kit `Timeout` / client timeout is the create deadline. `QueueTimeout` is + unsupported. `WaitFor` accepts only `Default` and `Scheduled`; `Ready`, + `Started`, and `Submitted` are rejected. The Go Blaxel SDK has no separate + creation-timeout argument. + + @@ -438,6 +474,10 @@ Set either a TCP port or a command. | `tcpPort` | Port `1`–`65535` | | `command.argv` | Command argv that must succeed | + + Blaxel rejects readiness probes before the call. + + @@ -446,6 +486,10 @@ Set either a TCP port or a command. | `TcpPort` | Port `1`–`65535` | | `Command` with `Argv` | Command argv that must succeed | + + Blaxel rejects readiness probes before the call. + + diff --git a/website/content/docs/sdk/enums.mdx b/website/content/docs/sdk/enums.mdx index 2a6af230..962459bb 100644 --- a/website/content/docs/sdk/enums.mdx +++ b/website/content/docs/sdk/enums.mdx @@ -62,6 +62,11 @@ How far provisioning wait-for waits before create returns. | `Started` | Return once the sandbox has started | | `Ready` | Return once readiness succeeds (probe + positive timeout when required) | + + Blaxel accepts only `Default` and `Scheduled`. `Ready`, `Started`, and + `Submitted` are rejected. Readiness probes are unsupported. + + ## EgressMode Outbound network policy on create. diff --git a/website/content/docs/sdk/filesystem.mdx b/website/content/docs/sdk/filesystem.mdx index 90055e93..31ba9457 100644 --- a/website/content/docs/sdk/filesystem.mdx +++ b/website/content/docs/sdk/filesystem.mdx @@ -260,6 +260,11 @@ Creates the directory at `path`. An omitted `parents` flag is `true` (create mis filesystem calls. + + Blaxel rejects `parents: false` before the call (field `parents`). Omitted + or `true` creates missing parents. + + @@ -289,6 +294,11 @@ Creates the directory at `path`. An omitted `Parents` flag is true. parent must already exist. + + Blaxel rejects `Parents: false` before the call (field `parents`). Omitted + or true creates missing parents. + + diff --git a/website/content/docs/sdk/process.mdx b/website/content/docs/sdk/process.mdx index 0ce31a11..8f71c884 100644 --- a/website/content/docs/sdk/process.mdx +++ b/website/content/docs/sdk/process.mdx @@ -129,6 +129,8 @@ Outcome of a finished command. until the command ends or the call is canceled. A timeout of at most 60 seconds uses Blaxel's blocking completion request. A longer timeout sets the process auto-kill and polls. `run` waits until the command ends. + Aborting the signal stops the wait only; the remote command may keep + running. An HTTP 504 while waiting is a timeout error. @@ -157,7 +159,9 @@ Outcome of a finished command. string. Omit or zero Timeout means no process auto-kill; Kit polls until the command ends or the call is canceled. A Timeout of at most 60 seconds uses Blaxel's blocking completion request. A longer Timeout sets the process - auto-kill and polls. Run waits until the command ends. + auto-kill and polls. Run waits until the command ends. Canceling the + context stops the wait only; the remote command may keep running. An HTTP + 504 while waiting is a timeout error. From 34fb696304066e0e4fcc20b2558011af72674782 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 21:35:53 +0530 Subject: [PATCH 24/60] Reformat Blaxel doc tables and wrapped prose for consistency. Co-authored-by: Cursor Entire-Checkpoint: 01M733AW6Z498HMPY9KEDRZSTF --- docs/sandbox-creation.md | 24 ++++++++++----------- sdks/go/providers/blaxel/README.md | 4 ++-- sdks/typescript/providers/blaxel/README.md | 4 ++-- website/content/docs/index.mdx | 3 ++- website/content/docs/lifecycle.mdx | 12 +++++------ website/content/docs/process.mdx | 16 +++++++------- website/content/docs/sdk/create-options.mdx | 14 ++++++------ website/content/docs/sdk/filesystem.mdx | 8 +++---- website/content/docs/sdk/process.mdx | 18 ++++++++-------- 9 files changed, 51 insertions(+), 52 deletions(-) diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index df8f3416..5ae71d3d 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -10,19 +10,19 @@ Go and TypeScript integrations target Modal v0.11.1, Daytona v0.225.0, and Blaxel (`modal@0.11.1`, `@daytona/sdk@0.225.0`, `@blaxel/core@0.3.36` / `github.com/blaxel-ai/sdk-go@v0.31.0`). -| Setting | Modal | Daytona | Blaxel | -| ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- | -| Source | Explicit registry image and an existing app | Default/specified snapshot or registry image | Named create (Blaxel name rules); registry image required | -| Runtime | Entrypoint, absolute working directory, PTY | Toolbox language and user | Unsupported | -| Isolation | Default, gVisor container, Linux VM | Nested KVM flag; class inherited from source | Unsupported | -| Resources | Fractional CPU, CPU limit, memory request/limit | Whole CPU and GiB memory/disk; image source required for overrides | Memory 1:1 (`memory_mib`); CPU/disk overrides rejected | -| Placement | Cloud and region preferences | Client target; request placement unsupported | Client `region` required for create; request placement unsupported | -| Environment and labels | Environment map and tags | Environment map and labels | Environment map and labels | -| Security | Named secrets and workload identity | Egress-placeholder secrets and public access | Unsupported | -| Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | Ports (not 80/443/8080; Default → Blaxel HTTP); outbound domains | -| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m); explicit idle disable rejected | +| Setting | Modal | Daytona | Blaxel | +| ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- | +| Source | Explicit registry image and an existing app | Default/specified snapshot or registry image | Named create (Blaxel name rules); registry image required | +| Runtime | Entrypoint, absolute working directory, PTY | Toolbox language and user | Unsupported | +| Isolation | Default, gVisor container, Linux VM | Nested KVM flag; class inherited from source | Unsupported | +| Resources | Fractional CPU, CPU limit, memory request/limit | Whole CPU and GiB memory/disk; image source required for overrides | Memory 1:1 (`memory_mib`); CPU/disk overrides rejected | +| Placement | Cloud and region preferences | Client target; request placement unsupported | Client `region` required for create; request placement unsupported | +| Environment and labels | Environment map and tags | Environment map and labels | Environment map and labels | +| Security | Named secrets and workload identity | Egress-placeholder secrets and public access | Unsupported | +| Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | Ports (not 80/443/8080; Default → Blaxel HTTP); outbound domains | +| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m); explicit idle disable rejected | | Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Kit client/create timeout; native create timeout not forwarded; waitFor Default/Scheduled only | -| Observability | Verbose output | Telemetry endpoint | Unsupported | +| Observability | Verbose output | Telemetry endpoint | Unsupported | Kit has no portable volume fields; Blaxel volumes stay out of create the same way Daytona and Modal volumes do. diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 9045da3f..cb045c82 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -104,8 +104,8 @@ fields; Blaxel volumes are outside create. ## Examples -| Operation | Program | -| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | +| Operation | Program | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | | Create | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/configuration/create-blaxel-sandbox) | | Run a command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process/run-blaxel-command) | | Shell / cwd / env / exit | [process examples](https://github.com/sandbox-kit/kit/tree/main/examples/go/blaxel/process) | diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index 93c309da..ef16bbfe 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -99,8 +99,8 @@ overwrites the first. ## Examples -| Example | Project | -| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| Example | Project | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | Creation | [create-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/configuration/create-blaxel-sandbox) | | Run a command | [run-blaxel-command](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process/run-blaxel-command) | | Process | [process examples](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/process) | diff --git a/website/content/docs/index.mdx b/website/content/docs/index.mdx index 56b9f91c..7d7cfeab 100644 --- a/website/content/docs/index.mdx +++ b/website/content/docs/index.mdx @@ -91,7 +91,8 @@ go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest - Set `BL_API_KEY`, `BL_WORKSPACE`, and a client `region` ([Blaxel](/docs/blaxel)), then: + Set `BL_API_KEY`, `BL_WORKSPACE`, and a client `region` + ([Blaxel](/docs/blaxel)), then: diff --git a/website/content/docs/lifecycle.mdx b/website/content/docs/lifecycle.mdx index cf467c60..efe98c57 100644 --- a/website/content/docs/lifecycle.mdx +++ b/website/content/docs/lifecycle.mdx @@ -45,12 +45,12 @@ Daytona may still reject a supported op for a given sandbox. Snippets assume a c -| Operation | Support | -| ------------------------------ | ------------------------------------------ | -| Open existing | [`get`](#open-a-sandbox) (lookup by name) | -| start / stop / pause / archive | Unsupported | -| Destroy | [`delete`](#delete) | -| Local disconnect | [`close`](#close-the-client) | +| Operation | Support | +| ------------------------------ | ----------------------------------------- | +| Open existing | [`get`](#open-a-sandbox) (lookup by name) | +| start / stop / pause / archive | Unsupported | +| Destroy | [`delete`](#delete) | +| Local disconnect | [`close`](#close-the-client) | Snippets assume a constructed client — [Blaxel](/docs/blaxel). diff --git a/website/content/docs/process.mdx b/website/content/docs/process.mdx index 18c80741..7f99c4fa 100644 --- a/website/content/docs/process.mdx +++ b/website/content/docs/process.mdx @@ -155,14 +155,14 @@ fmt.Println(result) -| You pass | On Blaxel | -| ---------------------- | -------------------------------------------------------------------------------------- | -| `argv` | POSIX-quoted shell string | -| `shell` | shell string as-is | -| omit / zero timeout | no process auto-kill; Kit polls until the command ends or the call is canceled | -| 1–60 seconds | process auto-kill; blocking completion request | -| longer than 60 seconds | process auto-kill; Kit polls until the command ends or the call is canceled | -| fractional (`1500` ms) | rejected (`timeout`) | +| You pass | On Blaxel | +| ---------------------- | ------------------------------------------------------------------------------ | +| `argv` | POSIX-quoted shell string | +| `shell` | shell string as-is | +| omit / zero timeout | no process auto-kill; Kit polls until the command ends or the call is canceled | +| 1–60 seconds | process auto-kill; blocking completion request | +| longer than 60 seconds | process auto-kill; Kit polls until the command ends or the call is canceled | +| fractional (`1500` ms) | rejected (`timeout`) | Blaxel returns both `stdout` and `stderr`. `run` waits until the command ends. Canceling the context or aborting the signal stops the wait only; the remote diff --git a/website/content/docs/sdk/create-options.mdx b/website/content/docs/sdk/create-options.mdx index 47718559..22f8d26c 100644 --- a/website/content/docs/sdk/create-options.mdx +++ b/website/content/docs/sdk/create-options.mdx @@ -342,10 +342,9 @@ Set exactly one variant. Unsupported variants fail before the provider call. - Blaxel supports `maximumLifetime` (≥5m when set) and `idleTerminate` (≥1m - when `PolicyMode.After`). `idleTerminate` with `PolicyMode.Disabled` is - rejected. Ephemeral, idle stop/pause, and stopped archive/delete are - rejected. + Blaxel supports `maximumLifetime` (≥5m when set) and `idleTerminate` (≥1m when + `PolicyMode.After`). `idleTerminate` with `PolicyMode.Disabled` is rejected. + Ephemeral, idle stop/pause, and stopped archive/delete are rejected. @@ -373,10 +372,9 @@ Set exactly one variant. Unsupported variants fail before the provider call. - Blaxel supports `MaximumLifetime` (≥5m when set) and `IdleTerminate` (≥1m - when `PolicyModeAfter`). `IdleTerminate` with `PolicyModeDisabled` is - rejected. Ephemeral, idle stop/pause, and stopped archive/delete are - rejected. + Blaxel supports `MaximumLifetime` (≥5m when set) and `IdleTerminate` (≥1m when + `PolicyModeAfter`). `IdleTerminate` with `PolicyModeDisabled` is rejected. + Ephemeral, idle stop/pause, and stopped archive/delete are rejected. diff --git a/website/content/docs/sdk/filesystem.mdx b/website/content/docs/sdk/filesystem.mdx index 31ba9457..cf6d66f5 100644 --- a/website/content/docs/sdk/filesystem.mdx +++ b/website/content/docs/sdk/filesystem.mdx @@ -261,8 +261,8 @@ Creates the directory at `path`. An omitted `parents` flag is `true` (create mis - Blaxel rejects `parents: false` before the call (field `parents`). Omitted - or `true` creates missing parents. + Blaxel rejects `parents: false` before the call (field `parents`). Omitted or + `true` creates missing parents. @@ -295,8 +295,8 @@ Creates the directory at `path`. An omitted `Parents` flag is true. - Blaxel rejects `Parents: false` before the call (field `parents`). Omitted - or true creates missing parents. + Blaxel rejects `Parents: false` before the call (field `parents`). Omitted or + true creates missing parents. diff --git a/website/content/docs/sdk/process.mdx b/website/content/docs/sdk/process.mdx index 8f71c884..43d03308 100644 --- a/website/content/docs/sdk/process.mdx +++ b/website/content/docs/sdk/process.mdx @@ -125,12 +125,12 @@ Outcome of a finished command. Blaxel returns both `stdout` and `stderr`. `argv` is sent as a POSIX-quoted - shell string. Omit or zero timeout means no process auto-kill; Kit polls - until the command ends or the call is canceled. A timeout of at most 60 - seconds uses Blaxel's blocking completion request. A longer timeout sets - the process auto-kill and polls. `run` waits until the command ends. - Aborting the signal stops the wait only; the remote command may keep - running. An HTTP 504 while waiting is a timeout error. + shell string. Omit or zero timeout means no process auto-kill; Kit polls until + the command ends or the call is canceled. A timeout of at most 60 seconds uses + Blaxel's blocking completion request. A longer timeout sets the process + auto-kill and polls. `run` waits until the command ends. Aborting the signal + stops the wait only; the remote command may keep running. An HTTP 504 while + waiting is a timeout error. @@ -159,9 +159,9 @@ Outcome of a finished command. string. Omit or zero Timeout means no process auto-kill; Kit polls until the command ends or the call is canceled. A Timeout of at most 60 seconds uses Blaxel's blocking completion request. A longer Timeout sets the process - auto-kill and polls. Run waits until the command ends. Canceling the - context stops the wait only; the remote command may keep running. An HTTP - 504 while waiting is a timeout error. + auto-kill and polls. Run waits until the command ends. Canceling the context + stops the wait only; the remote command may keep running. An HTTP 504 while + waiting is a timeout error. From 64d6d15dee0907eea68f6648c39d6337810c2128 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 22:04:33 +0530 Subject: [PATCH 25/60] Align Blaxel Go/TS workspace, idle-terminate fields, and DELETED state. Reject blank TypeScript scope.workspace like Go, point idle duration errors at lifetime.idle_terminate.after, map wire DELETED to Deleted, and correct workspace docs. Co-authored-by: Cursor Entire-Checkpoint: 01M734ZBP5RHJETJZP04RHVFHY --- sdks/go/providers/blaxel/client_test.go | 10 ++++++++++ sdks/go/providers/blaxel/creation.go | 4 ++-- sdks/go/providers/blaxel/metadata.go | 4 +++- sdks/go/providers/blaxel/rejection_test.go | 12 +++++++++++- sdks/go/providers/blaxel/sandbox_test.go | 11 +++++++++++ .../providers/blaxel/src/blaxel.test.ts | 11 +++++++++++ .../blaxel/src/mappings/provider_mappings.ts | 15 +++++++++++++-- website/content/docs/blaxel.mdx | 14 +++++++------- 8 files changed, 68 insertions(+), 13 deletions(-) diff --git a/sdks/go/providers/blaxel/client_test.go b/sdks/go/providers/blaxel/client_test.go index 57f45fca..b591764a 100644 --- a/sdks/go/providers/blaxel/client_test.go +++ b/sdks/go/providers/blaxel/client_test.go @@ -100,3 +100,13 @@ func TestAuthReportsTheMissingCredential(t *testing.T) { }) } } + +func TestBlankWorkspaceRejected(t *testing.T) { + _, err := mapClientConfig(&sandbox.Config{ + Scope: &sandbox.Scope{Workspace: sandbox.Value("")}, + }) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidArgument || detail.Field != sandbox.ConfigFieldScopeWorkspace { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} diff --git a/sdks/go/providers/blaxel/creation.go b/sdks/go/providers/blaxel/creation.go index 9d3c5fdd..e9036cee 100644 --- a/sdks/go/providers/blaxel/creation.go +++ b/sdks/go/providers/blaxel/creation.go @@ -141,11 +141,11 @@ func mapIdleTerminate(action *sandbox.AutomaticAction) (*blaxel.ExpirationPolicy return nil, providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldLifetimeIdleTerminate, "disabling idle terminate is not representable") case sandbox.PolicyModeAfter: if action.After == nil { - return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminate, "duration is required") + return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminateAfter, "duration is required") } formatted, err := formatBlaxelDuration(action.After.AsDuration(), time.Minute) if err != nil { - return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminate, err.Error()) + return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminateAfter, err.Error()) } return &blaxel.ExpirationPolicyParam{ Action: blaxel.ExpirationPolicyActionDelete, diff --git a/sdks/go/providers/blaxel/metadata.go b/sdks/go/providers/blaxel/metadata.go index 375edcd5..3e6b95a3 100644 --- a/sdks/go/providers/blaxel/metadata.go +++ b/sdks/go/providers/blaxel/metadata.go @@ -44,7 +44,9 @@ func (s *observedSandbox) state() { s.info.State = runtimeState(s.native.State, sandbox.SandboxStateRunning) case blaxel.StatusDeleting, blaxel.StatusDeactivating: s.info.State = sandbox.SandboxStateDeleting - case blaxel.StatusTerminated, blaxel.StatusDeactivated: + case blaxel.StatusTerminated, blaxel.StatusDeactivated, blaxel.Status("DELETED"): + // DELETED is not a typed Status constant in sdk-go v0.31.0; accept the + // wire string so refresh matches TypeScript mapSandboxState. s.info.State = sandbox.SandboxStateDeleted s.info.Terminal = sandbox.Value(true) case blaxel.StatusArchiving: diff --git a/sdks/go/providers/blaxel/rejection_test.go b/sdks/go/providers/blaxel/rejection_test.go index 9e477689..04a48b72 100644 --- a/sdks/go/providers/blaxel/rejection_test.go +++ b/sdks/go/providers/blaxel/rejection_test.go @@ -119,7 +119,17 @@ func TestRejectedCreateSettings(t *testing.T) { }, }), kind: sandbox.ErrorKindInvalidArgument, - field: sandbox.CreateFieldLifetimeIdleTerminate, + field: sandbox.CreateFieldLifetimeIdleTerminateAfter, + }, + { + name: "idle terminate after required", + request: namedImage(&sandbox.CreateOptions{ + Lifetime: &sandbox.LifetimePolicy{ + IdleTerminate: &sandbox.AutomaticAction{Mode: sandbox.PolicyModeAfter}, + }, + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldLifetimeIdleTerminateAfter, }, { name: "reserved port", diff --git a/sdks/go/providers/blaxel/sandbox_test.go b/sdks/go/providers/blaxel/sandbox_test.go index a807ff0d..58e13177 100644 --- a/sdks/go/providers/blaxel/sandbox_test.go +++ b/sdks/go/providers/blaxel/sandbox_test.go @@ -95,6 +95,17 @@ func TestMapSandboxInfoUsesNameAsID(t *testing.T) { if standby.State != sandbox.SandboxStatePaused { t.Fatalf("standby state = %#v", standby.State) } + + deleted, err := sandboxInfo(&blaxel.Sandbox{ + Metadata: blaxel.Metadata{Name: "demo"}, + Status: blaxel.Status("DELETED"), + }) + if err != nil { + t.Fatal(err) + } + if deleted.State != sandbox.SandboxStateDeleted || deleted.GetTerminal() != true { + t.Fatalf("deleted = %#v", deleted) + } } func TestFormatBlaxelDuration(t *testing.T) { diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 2fc1f0e9..3246468f 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -13,6 +13,7 @@ import { ConfigFieldAuthApiKeyKey, ConfigFieldEndpoint, ConfigFieldRegion, + ConfigFieldScopeWorkspace, CreateFieldEnvironment, CreateFieldLifetimeIdleTerminate, CreateFieldLifetimeMaximumLifetime, @@ -65,6 +66,16 @@ describe("Blaxel mappings", () => { ); }); + it("rejects blank workspace scope", () => { + expect(() => mapClientConfig({ scope: { workspace: " " } })).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: ConfigFieldScopeWorkspace, + operation: "initialize", + }), + ); + }); + it("requires name and image on create", () => { expect(() => mapCreateRequest( diff --git a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts index 27425527..da4b9315 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts @@ -2,6 +2,7 @@ // Inventoried in specs/providers/blaxel.yaml groups when present; kept in sync by hand. import { + ConfigFieldScopeWorkspace, ErrorKind, SandboxState, type SandboxInfo, @@ -66,11 +67,21 @@ export function mapSandboxIdentity( export function mapClientScope( source: Scope | undefined, - _operation: string = "initialize", + operation: string = "initialize", ): NativeClientConfig { const target: Mutable = {}; if (source === undefined) return target; - if (source.workspace !== undefined) target.workspace = source.workspace; + if (source.workspace !== undefined) { + if (source.workspace.trim() === "") { + providerError({ + kind: ErrorKind.InvalidArgument, + operation, + field: ConfigFieldScopeWorkspace, + message: "workspace requires a non-blank value", + }); + } + target.workspace = source.workspace; + } return target; } diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index 1f8031f8..b83c8e72 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -116,7 +116,7 @@ Omit `auth` and `scope.workspace` to let the Blaxel SDK pick credentials from it | -------------- | ------------------------------------------------------------------------------- | | API key | `auth.apiKey` | | Omitted `auth` | Blaxel SDK environment / CLI credentials | -| Workspace | `scope.workspace` — required for create when auth is set explicitly, or via env | +| Workspace | `scope.workspace` — or `BL_WORKSPACE` when omitted | | Region | `region` — required for create | | Endpoint | Unsupported | @@ -133,12 +133,12 @@ await using client = new Client({ -| Setting | Notes | -| --------- | ------------------------------------- | -| API key | `Auth` with `APIKey` | -| Workspace | `Scope.Workspace` | -| Region | `Config.Region` — required for create | -| Endpoint | Unsupported | +| Setting | Notes | +| --------- | ------------------------------------------------------ | +| API key | `Auth` with `APIKey` | +| Workspace | `Scope.Workspace` — or `BL_WORKSPACE` when omitted | +| Region | `Config.Region` — required for create | +| Endpoint | Unsupported | ```go config := &sandbox.Config{ From 2b8ae9c8527fe93e4c897d11943f94533bcc43c5 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 22:06:20 +0530 Subject: [PATCH 26/60] Reformat Blaxel docs settings tables after workspace wording update. Co-authored-by: Cursor Entire-Checkpoint: 01M7352M61HJZQKZ4TXVM64AH7 --- website/content/docs/blaxel.mdx | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index b83c8e72..cae3bb3e 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -112,13 +112,13 @@ Omit `auth` and `scope.workspace` to let the Blaxel SDK pick credentials from it -| Setting | Notes | -| -------------- | ------------------------------------------------------------------------------- | -| API key | `auth.apiKey` | -| Omitted `auth` | Blaxel SDK environment / CLI credentials | -| Workspace | `scope.workspace` — or `BL_WORKSPACE` when omitted | -| Region | `region` — required for create | -| Endpoint | Unsupported | +| Setting | Notes | +| -------------- | -------------------------------------------------- | +| API key | `auth.apiKey` | +| Omitted `auth` | Blaxel SDK environment / CLI credentials | +| Workspace | `scope.workspace` — or `BL_WORKSPACE` when omitted | +| Region | `region` — required for create | +| Endpoint | Unsupported | ```ts await using client = new Client({ @@ -133,12 +133,12 @@ await using client = new Client({ -| Setting | Notes | -| --------- | ------------------------------------------------------ | -| API key | `Auth` with `APIKey` | -| Workspace | `Scope.Workspace` — or `BL_WORKSPACE` when omitted | -| Region | `Config.Region` — required for create | -| Endpoint | Unsupported | +| Setting | Notes | +| --------- | -------------------------------------------------- | +| API key | `Auth` with `APIKey` | +| Workspace | `Scope.Workspace` — or `BL_WORKSPACE` when omitted | +| Region | `Config.Region` — required for create | +| Endpoint | Unsupported | ```go config := &sandbox.Config{ From e47cb902843d286ec4d7e37c20d671b2aeab32be Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 23:20:52 +0530 Subject: [PATCH 27/60] Set terminal on Blaxel TypeScript Deleted refresh. Match Go and Daytona so get/refresh of TERMINATED, DEACTIVATED, or DELETED sandboxes expose terminal: true, not only delete(). Co-authored-by: Cursor Entire-Checkpoint: 01M739B43ZHXT711DXK8P5D6RD --- .../providers/blaxel/src/blaxel.test.ts | 16 ++++++++++++++++ .../providers/blaxel/src/mappings/mappings.ts | 1 + 2 files changed, 17 insertions(+) diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 3246468f..42356d06 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -402,6 +402,22 @@ describe("Blaxel mappings", () => { }); }); + it("marks DELETED and TERMINATED refresh as terminal", () => { + for (const status of ["DELETED", "TERMINATED", "DEACTIVATED"] as const) { + expect( + mapSandboxInfo({ + metadata: { name: "gone" }, + status, + }), + ).toMatchObject({ + id: "gone", + state: SandboxState.Deleted, + terminal: true, + providerState: status, + }); + } + }); + it("omits protocol for default port transport", () => { const plan = mapCreateRequest( { diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index 52d8c529..71c287e5 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -295,6 +295,7 @@ export function mapSandboxInfo( if (mapped.region !== undefined) mutable(result).region = mapped.region; if (mapped.providerState !== undefined) mutable(result).providerState = mapped.providerState; + if (result.state === SandboxState.Deleted) mutable(result).terminal = true; const resources = mapAllocatedResources(value); if (resources !== undefined) mutable(result).resources = resources; return result; From cec4d3c9f58df844f1c548a88094cbd0719e7639 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 23:23:34 +0530 Subject: [PATCH 28/60] Align Blaxel provider YAML inventory with handwritten SDKs. Document where create checks actually live, record wait_for and port rules accurately, and fix TypeScript constructor and create-target type names. Co-authored-by: Cursor Entire-Checkpoint: 01M739G22K4WN1JMDWGQV5ANGF --- specs/providers/blaxel.yaml | 144 ++++++++++++++++++++++++++++++------ 1 file changed, 123 insertions(+), 21 deletions(-) diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml index a7a65969..8ac9246f 100644 --- a/specs/providers/blaxel.yaml +++ b/specs/providers/blaxel.yaml @@ -1,7 +1,9 @@ version: 1 provider: blaxel -# Provider create checks are handwritten in sdks/*/providers/blaxel (provider_checks). -# Keep this list in sync with those implementations; it is not emitted. +# Create checks are handwritten. Name/image/placement/resources/env live in +# provider_checks; readiness/security/observability/provisioning/ports/network +# live in create mapping (Go creation.go, TS mappings/mappings.ts). Keep this +# list in sync; it is not emitted. checks: - { path: name, @@ -18,21 +20,40 @@ checks: - { path: resources.cpu_limit_cores, unsupported: true } - { path: resources.memory_limit_mib, unsupported: true } - { path: resources.disk_mib, unsupported: true } + - { + path: network.ports.port, + notes: "reject reserved 80, 443, and 8080; enforced in create mapping", + } + - { + path: network.ports.transport, + allowed: [default, plain, tls], + notes: "Default omits protocol (Blaxel HTTP); Plain→TCP; TLS→TLS; Http2Tls rejected in create mapping", + } + - { + path: network.outbound_domains, + notes: "mapped to allowedDomains; no Kit-side domain format check", + } + - { + path: network.outbound_cidrs, + unsupported: true, + notes: "with outbound_proxy_url, inbound_cidrs, private_network, and related fields", + } - { path: readiness, unsupported: true } - { path: security, unsupported: true } - { path: observability, unsupported: true } - { path: provisioning.queue_timeout, unsupported: true } - { path: provisioning.wait_for, - unsupported: true, - notes: "only Default and Scheduled accepted; Ready/Started and others rejected", + allowed: [default, scheduled], + notes: "Ready/Started/Submitted rejected; enforced in create mapping", } client: target: name: ClientOptions bindings: go: { import: github.com/blaxel-ai/sdk-go/option, name: RequestOption } - typescript: { import: "@blaxel/core", name: ClientOptions } + # Handwritten TS maps into @blaxel/core settings Config (process-global). + typescript: { import: "@blaxel/core", name: Config } managed: [provider, timeout] retained: [region] rejected: @@ -44,6 +65,7 @@ client: - { field: api_key, group: mapAPIKeyCredentials } runtime: go: + # Native SDK entry used by Provider.NewClient. constructor: { function: NewClient } state: - { @@ -55,7 +77,8 @@ runtime: } cleanup: { method: Close, accepts_context: true, returns_error: false } typescript: - constructor: { function: BlaxelApiClient } + # @blaxel/core has no client class; credentials go through process-global settings. + constructor: { function: settings.setConfig } state: - { name: region, @@ -65,8 +88,8 @@ runtime: doc: "is the captured default region. Omission means create rejects with Config.region required.", } cleanup: { method: close, accepts_context: false, returns_error: false } -# Mapping groups are handwritten in sdks/*/providers/blaxel (provider_mappings). -# Keep this inventory in sync with those implementations; it is not emitted. +# Mapping groups are handwritten in sdks/*/providers/blaxel (provider_mappings +# plus create mapping for image/region/ports/network). Keep in sync; not emitted. groups: - name: mapClientScope direction: request @@ -80,7 +103,7 @@ groups: fields: { workspace: WithWorkspace } typescript: import: "@blaxel/core" - name: ClientOptions + name: Config fields: { workspace: workspace } fields: - { from: workspace, to: workspace } @@ -96,7 +119,8 @@ groups: fields: { key: WithAPIKey } typescript: import: "@blaxel/core" - name: ClientOptions + name: Config + # Handwritten field is apiKey; @blaxel/core Config documents apikey. fields: { key: apiKey } fields: - { from: key, to: key } @@ -117,17 +141,53 @@ groups: } typescript: import: "@blaxel/core" - name: SandboxCreateRequest + name: SandboxCreateConfiguration fields: { - name: metadata.name, - environment: spec.runtime.envs, - labels: metadata.labels, + name: name, + environment: envs, + labels: labels, } fields: - { from: name, to: name } - { from: environment, to: environment } - { from: labels, to: labels } + - name: mapImage + direction: request + source: { name: ImageSource } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: { image: Sandbox.Spec.Runtime.Image } + typescript: + import: "@blaxel/core" + name: SandboxCreateConfiguration + fields: { image: image } + fields: + - { from: reference, to: image } + - name: mapCreateRegion + direction: request + source: { name: Config } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: { region: Sandbox.Spec.Region } + typescript: + import: "@blaxel/core" + name: SandboxCreateConfiguration + fields: { region: region } + fields: + - { + from: region, + to: region, + notes: "required for create; taken from captured client Config.region", + } # memory_mib → Blaxel memory is 1:1; Blaxel labels the unit MB; Kit does not convert. - name: mapResources direction: request @@ -141,8 +201,8 @@ groups: fields: { memory: Sandbox.Spec.Runtime.Memory } typescript: import: "@blaxel/core" - name: SandboxCreateRequest - fields: { memory: spec.runtime.memory } + name: SandboxCreateConfiguration + fields: { memory: memory } fields: - { from: memory_mib, @@ -162,8 +222,8 @@ groups: fields: { ttl: Sandbox.Spec.Runtime.Ttl } typescript: import: "@blaxel/core" - name: SandboxCreateRequest - fields: { ttl: spec.runtime.ttl } + name: SandboxCreateConfiguration + fields: { ttl: ttl } fields: - { from: maximum_lifetime, to: ttl, transform: duration_blaxel } - name: mapIdleDuration @@ -179,10 +239,47 @@ groups: fields: { lifecycle: Sandbox.Spec.Lifecycle.ExpirationPolicies } typescript: import: "@blaxel/core" - name: SandboxCreateRequest - fields: { lifecycle: spec.lifecycle.expirationPolicies } + name: SandboxCreateConfiguration + fields: { lifecycle: lifecycle } fields: - { from: after, to: lifecycle, transform: duration_blaxel_idle } + - name: mapPorts + direction: request + source: { name: PortBinding } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: { ports: Sandbox.Spec.Runtime.Ports } + typescript: + import: "@blaxel/core" + name: SandboxCreateConfiguration + fields: { ports: ports } + fields: + - { from: port, to: target } + - { + from: transport, + to: protocol, + notes: "Default omits protocol; Plain→TCP; TLS→TLS", + } + - name: mapOutboundDomains + direction: request + source: { name: NetworkConfig } + target: + name: SandboxNewParams + bindings: + go: + import: github.com/blaxel-ai/sdk-go + name: SandboxNewParams + fields: { outbound_domains: Sandbox.Spec.Network.AllowedDomains } + typescript: + import: "@blaxel/core" + name: SandboxCreateConfiguration + fields: { outbound_domains: network.allowedDomains } + fields: + - { from: outbound_domains, to: outbound_domains } - name: mapSandboxInfo direction: response source: @@ -214,7 +311,12 @@ groups: fields: - { from: id, to: id } - { from: name, to: name } - - { from: state, to: provider_state, cast: string } + - { + from: state, + to: provider_state, + cast: string, + notes: "Status preferred; runtime State used when Status empty; DELETED/TERMINATED/DEACTIVATED → Deleted with terminal=true", + } - { from: region, to: region } - { from: labels, to: labels } - name: mapAllocatedResources From 2e5e3a8ddeb7776d13e5cfc625e7cfa86ed01304 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 23:24:13 +0530 Subject: [PATCH 29/60] Compact Blaxel create-field YAML mapping style. Co-authored-by: Cursor Entire-Checkpoint: 01M739H7ZK0PGG642GM5CYWTJA --- specs/providers/blaxel.yaml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml index 8ac9246f..c0595404 100644 --- a/specs/providers/blaxel.yaml +++ b/specs/providers/blaxel.yaml @@ -142,12 +142,7 @@ groups: typescript: import: "@blaxel/core" name: SandboxCreateConfiguration - fields: - { - name: name, - environment: envs, - labels: labels, - } + fields: { name: name, environment: envs, labels: labels } fields: - { from: name, to: name } - { from: environment, to: environment } From f13dd89810b920de33abcb832133ca3fb01a88c4 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Tue, 10 Nov 2026 23:57:22 +0530 Subject: [PATCH 30/60] Harden Blaxel create checks and process wait budgets. Validate outbound domains and blank image references in both SDKs, subtract completion-hold elapsed time from process polls, and align Go/TS rejection wording. Co-authored-by: Cursor Entire-Checkpoint: 01M73BDYW54VYEZW0BW7KVYB4G --- sdks/go/providers/blaxel/process.go | 26 +++++++++++++++-- sdks/go/providers/blaxel/process_test.go | 13 +++++++-- sdks/go/providers/blaxel/provider_checks.go | 26 ++++++++++++++++- sdks/go/providers/blaxel/rejection_test.go | 21 ++++++++++++++ .../providers/blaxel/src/blaxel.test.ts | 8 +++++ .../typescript/providers/blaxel/src/blaxel.ts | 2 ++ .../blaxel/src/checks/provider_checks.ts | 16 +++++++++- .../providers/blaxel/src/mappings/mappings.ts | 6 ++-- .../providers/blaxel/src/process.ts | 24 ++++++++++++++- .../providers/blaxel/src/rejection.test.ts | 29 +++++++++++++++++++ specs/providers/blaxel.yaml | 5 ++-- 11 files changed, 163 insertions(+), 13 deletions(-) diff --git a/sdks/go/providers/blaxel/process.go b/sdks/go/providers/blaxel/process.go index bfe75122..4300c5c2 100644 --- a/sdks/go/providers/blaxel/process.go +++ b/sdks/go/providers/blaxel/process.go @@ -34,6 +34,7 @@ func (b *backend) RunCommand(ctx context.Context, id string, request *sandbox.Ex if err != nil { return nil, err } + startedAt := time.Now() started, err := instance.Process.New(ctx, body) if err != nil { return nil, mapProviderError(err, "run") @@ -42,6 +43,7 @@ func (b *backend) RunCommand(ctx context.Context, id string, request *sandbox.Ex Instance: instance, Started: started, Timeout: timeout, + Elapsed: time.Since(startedAt), }) } @@ -70,6 +72,8 @@ type finishProcessArgs struct { Instance *blaxel.SandboxInstance Started *blaxel.ProcessResponse Timeout time.Duration + // Elapsed covers Process.New, including any completion-hold wait. + Elapsed time.Duration } // finishProcess returns a terminal start as-is; otherwise polls with Wait @@ -82,7 +86,16 @@ func finishProcess(ctx context.Context, args finishProcessArgs) (*sandbox.ExecRe if identifier == "" { return nil, invalidResponse("run", "", "SDK returned no process identifier") } - finished, err := args.Instance.Process.Wait(ctx, identifier, waitBudget(args.Timeout), processPollInterval) + budget := remainingWaitBudget(args.Timeout, args.Elapsed) + if args.Timeout > 0 && budget <= 0 { + return nil, kitError(kitFault{ + Kind: sandbox.ErrorKindTimeout, + Operation: "run", + Field: sandbox.ExecFieldTimeout, + Message: "process did not finish before the timeout", + }) + } + finished, err := args.Instance.Process.Wait(ctx, identifier, budget, processPollInterval) if err != nil { return nil, mapProviderError(err, "run") } @@ -93,11 +106,18 @@ func completionHold(timeout time.Duration) bool { return timeout > 0 && timeout <= blaxelCompletionHold } -func waitBudget(timeout time.Duration) time.Duration { +// remainingWaitBudget is the poll window after Process.New. Omit/zero stays +// unlimited. A fully consumed positive timeout yields 0 (caller times out); +// Blaxel Wait treats 0 as 1m, so finishProcess must not pass that through. +func remainingWaitBudget(timeout, elapsed time.Duration) time.Duration { if timeout <= 0 { return blaxelWaitUnlimited } - return timeout + remaining := timeout - elapsed + if remaining <= 0 { + return 0 + } + return remaining } var terminalProcessStatus = map[blaxel.ProcessResponseStatus]struct{}{ diff --git a/sdks/go/providers/blaxel/process_test.go b/sdks/go/providers/blaxel/process_test.go index e43cdde7..dfd48907 100644 --- a/sdks/go/providers/blaxel/process_test.go +++ b/sdks/go/providers/blaxel/process_test.go @@ -44,8 +44,17 @@ func TestCompletionHoldAndWaitBudget(t *testing.T) { if completionHold(0) || completionHold(61*time.Second) || !completionHold(60*time.Second) || !completionHold(time.Second) { t.Fatal("completion hold boundary") } - if waitBudget(0) != blaxelWaitUnlimited || waitBudget(90*time.Second) != 90*time.Second { - t.Fatal("wait budget") + if remainingWaitBudget(0, time.Minute) != blaxelWaitUnlimited { + t.Fatal("unlimited wait budget") + } + if remainingWaitBudget(90*time.Second, 0) != 90*time.Second { + t.Fatal("full wait budget") + } + if remainingWaitBudget(90*time.Second, 30*time.Second) != 60*time.Second { + t.Fatal("remaining wait budget") + } + if remainingWaitBudget(5*time.Second, 5*time.Second) != 0 || remainingWaitBudget(5*time.Second, 6*time.Second) != 0 { + t.Fatal("exhausted wait budget") } } diff --git a/sdks/go/providers/blaxel/provider_checks.go b/sdks/go/providers/blaxel/provider_checks.go index bfb2c3cb..1f5aeb1f 100644 --- a/sdks/go/providers/blaxel/provider_checks.go +++ b/sdks/go/providers/blaxel/provider_checks.go @@ -2,6 +2,7 @@ package blaxel import ( "regexp" + "strings" "github.com/sandbox-kit/kit/sdks/go/sandbox" ) @@ -11,6 +12,9 @@ var providerEnvNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) // Blaxel Metadata.Name: lowercase alphanumeric with single hyphens, max 49. var providerSandboxNamePattern = regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$`) +// Matches Daytona/Modal outbound domain checks. +var providerDomainPattern = regexp.MustCompile(`(?i)^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.?$`) + const providerSandboxNameMaxLen = 49 // validateProviderCreate rejects creation settings this provider cannot honor. @@ -26,7 +30,7 @@ func validateProviderCreate(request *sandbox.CreateOptions) error { if len(name) > providerSandboxNameMaxLen || !providerSandboxNamePattern.MatchString(name) { return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldName, "name must be lowercase alphanumeric with single hyphens, at most 49 characters") } - if request.GetSource().GetImage() == nil || request.GetSource().GetImage().Reference == "" { + if request.GetSource().GetImage() == nil || strings.TrimSpace(request.GetSource().GetImage().Reference) == "" { return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldSourceImage, "an explicit image source is required") } if request.GetSource().GetSnapshot() != nil || request.GetSource().GetWarmPool() != nil { @@ -43,6 +47,26 @@ func validateProviderCreate(request *sandbox.CreateOptions) error { return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldEnvironment, "environment is outside supported values") } } + if err := rejectInvalidOutboundDomains(request.Network); err != nil { + return err + } + return nil +} + +func rejectInvalidOutboundDomains(network *sandbox.NetworkConfig) error { + if network == nil || network.OutboundDomains == nil { + return nil + } + for _, value := range network.OutboundDomains.Entries { + domain := strings.TrimPrefix(value, "*.") + if !providerDomainPattern.MatchString(domain) || len(domain) > 253 { + return providerCheckError( + sandbox.ErrorKindInvalidArgument, + sandbox.CreateFieldNetworkOutboundDomainsEntries, + "network.outbound_domains.entries is outside supported values", + ) + } + } return nil } diff --git a/sdks/go/providers/blaxel/rejection_test.go b/sdks/go/providers/blaxel/rejection_test.go index 04a48b72..71e8f9bc 100644 --- a/sdks/go/providers/blaxel/rejection_test.go +++ b/sdks/go/providers/blaxel/rejection_test.go @@ -36,6 +36,27 @@ func TestRejectedCreateSettings(t *testing.T) { kind: sandbox.ErrorKindInvalidArgument, field: sandbox.CreateFieldSourceImage, }, + { + name: "blank image reference", + request: &sandbox.CreateOptions{ + Name: sandbox.Value("sandbox"), + Source: &sandbox.SandboxSource{Kind: &sandbox.Image{ + Image: &sandbox.ImageSource{Reference: " "}, + }}, + }, + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldSourceImage, + }, + { + name: "invalid outbound domain", + request: namedImage(&sandbox.CreateOptions{ + Network: &sandbox.NetworkConfig{ + OutboundDomains: &sandbox.StringAllowlist{Entries: []string{"not a domain!!!"}}, + }, + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldNetworkOutboundDomainsEntries, + }, { name: "cpu cores", request: namedImage(&sandbox.CreateOptions{ diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 42356d06..6e276d19 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -37,6 +37,7 @@ import { } from "#mappings/provider_mappings.js"; import { captureProviderState } from "#config/provider_config.js"; import { mapBlaxelError } from "#errors.js"; +import { remainingWaitBudgetMs } from "./process.js"; import { ResponseError } from "@blaxel/core"; const provider = { scope: { workspace: "ws" }, region: "us-pdx-1" }; @@ -904,6 +905,13 @@ describe("Blaxel provider orchestration", () => { expect(result.stderr).toEqual(new TextEncoder().encode("stopped")); }); + it("subtracts elapsed time from the process wait budget", () => { + expect(remainingWaitBudgetMs(undefined, 0, 10_000)).toBe(-1); + expect(remainingWaitBudgetMs(90, 1_000, 31_000)).toBe(60_000); + expect(remainingWaitBudgetMs(5, 1_000, 6_000)).toBe(0); + expect(remainingWaitBudgetMs(5, 1_000, 7_000)).toBeLessThan(0); + }); + it("polls when a completion-hold start still returns running", async () => { const waits: string[] = []; const backend = runBackend({ diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index de204673..881d6a4e 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -289,6 +289,7 @@ class BlaxelBackend extends BackendBase implements Backend { options, body: async (sandbox) => { const timeout = execTimeoutSeconds(request.timeout); + const startedAt = Date.now(); const started = await sandbox.process.exec( processRequest(request, timeout), ); @@ -296,6 +297,7 @@ class BlaxelBackend extends BackendBase implements Backend { process: sandbox.process, started, timeout, + startedAt, signal: options.signal, }); }, diff --git a/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts b/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts index 4ba152ac..1f85bc96 100644 --- a/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts +++ b/sdks/typescript/providers/blaxel/src/checks/provider_checks.ts @@ -2,6 +2,7 @@ import { ErrorKind, CreateFieldEnvironment, CreateFieldName, + CreateFieldNetworkOutboundDomainsEntries, CreateFieldPlacement, CreateFieldResourcesCpuCores, CreateFieldResourcesCpuLimitCores, @@ -9,6 +10,7 @@ import { CreateFieldResourcesMemoryLimitMib, CreateFieldSource, CreateFieldSourceImage, + validDomain, validEnvName, type ErrorInfo, } from "@sandbox-kit/sandbox"; @@ -35,7 +37,10 @@ export function validateProviderCreate(request: CreateOptions): void { "name must be lowercase alphanumeric with single hyphens, at most 49 characters", ); } - if (request.source?.image === undefined) { + if ( + request.source?.image === undefined || + !request.source.image.reference?.trim() + ) { reject( ErrorKind.InvalidArgument, CreateFieldSourceImage, @@ -69,6 +74,15 @@ export function validateProviderCreate(request: CreateOptions): void { ); } } + for (const value of request.network?.outboundDomains ?? []) { + if (!validDomain(value)) { + reject( + ErrorKind.InvalidArgument, + CreateFieldNetworkOutboundDomainsEntries, + "network.outbound_domains.entries is outside supported values", + ); + } + } } function rejectUnsupportedResources(resources: Resources | undefined): void { diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index 71c287e5..5a234434 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -185,7 +185,7 @@ function mapSecurity(r: CreateOptions): void { rejectIfSet( r.security !== undefined, CreateFieldSecurity, - "Blaxel security settings are not mapped", + "Blaxel security fields are not mapped", ); } @@ -195,7 +195,7 @@ function mapLifetime(r: CreateOptions, p: SandboxCreateConfiguration): void { if (hasUnsupportedLifetimeFields(v)) unsupported( CreateFieldLifetime, - "requested lifecycle policy is unsupported by Blaxel", + "one or more Blaxel lifetime fields are not mapped", ); if (v.maximumLifetime !== undefined) { @@ -251,7 +251,7 @@ function mapObservability(r: CreateOptions): void { rejectIfSet( r.observability !== undefined, CreateFieldObservability, - "Blaxel observability settings are not mapped", + "Blaxel observability fields are not mapped", ); } diff --git a/sdks/typescript/providers/blaxel/src/process.ts b/sdks/typescript/providers/blaxel/src/process.ts index c98a2330..8f2a2a6c 100644 --- a/sdks/typescript/providers/blaxel/src/process.ts +++ b/sdks/typescript/providers/blaxel/src/process.ts @@ -46,7 +46,10 @@ export function processRequest( export interface FinishProcessArgs { process: NativeSandboxHandle["process"]; started: BlaxelProcessResult; + /** Kit process timeout in whole seconds; undefined means unlimited. */ timeout: number | undefined; + /** Date.now() taken immediately before Process.exec. */ + startedAt: number; signal: AbortSignal | undefined; } @@ -64,18 +67,37 @@ export async function finishProcess( message: "sandbox-kit blaxel: SDK returned no process identifier", }); } + const maxWait = remainingWaitBudgetMs(args.timeout, args.startedAt); + if (args.timeout !== undefined && maxWait <= 0) { + providerError({ + kind: ErrorKind.Timeout, + operation: "run", + field: ExecFieldTimeout, + message: "process did not finish before the timeout", + }); + } const waitOptions: { maxWait: number; interval: number; signal?: AbortSignal; } = { - maxWait: args.timeout === undefined ? -1 : args.timeout * 1_000, + maxWait, interval: 1_000, }; if (args.signal !== undefined) waitOptions.signal = args.signal; return execResult(await args.process.wait(identifier, waitOptions)); } +/** Poll window after Process.exec, subtracting time spent on completion hold. */ +export function remainingWaitBudgetMs( + timeoutSeconds: number | undefined, + startedAt: number, + now: number = Date.now(), +): number { + if (timeoutSeconds === undefined) return -1; + return timeoutSeconds * 1_000 - (now - startedAt); +} + /** * Maps Kit ExecOptions.timeout (ms) to Blaxel seconds. * Omitted and explicit zero become undefined so the caller sends 0 (no limit). diff --git a/sdks/typescript/providers/blaxel/src/rejection.test.ts b/sdks/typescript/providers/blaxel/src/rejection.test.ts index 5fecf9cc..744fa75b 100644 --- a/sdks/typescript/providers/blaxel/src/rejection.test.ts +++ b/sdks/typescript/providers/blaxel/src/rejection.test.ts @@ -8,6 +8,7 @@ import { CreateFieldLifetime, CreateFieldName, CreateFieldNetwork, + CreateFieldNetworkOutboundDomainsEntries, CreateFieldObservability, CreateFieldPlacement, CreateFieldProvisioningQueueTimeout, @@ -153,6 +154,34 @@ describe("rejected Blaxel create settings", () => { ); }); + it("blank image reference", () => { + expect(() => + mapCreateRequest( + { name: "box", source: { image: { reference: " " } } }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldSourceImage, + }), + ); + }); + + it("invalid outbound domain", () => { + expect(() => + mapCreateRequest( + namedImage({ network: { outboundDomains: ["not a domain!!!"] } }), + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldNetworkOutboundDomainsEntries, + }), + ); + }); + it("missing region", () => { expect(() => mapCreateRequest(namedImage({}), { scope: { workspace: "ws" } }), diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml index c0595404..06d529fa 100644 --- a/specs/providers/blaxel.yaml +++ b/specs/providers/blaxel.yaml @@ -30,8 +30,9 @@ checks: notes: "Default omits protocol (Blaxel HTTP); Plain→TCP; TLS→TLS; Http2Tls rejected in create mapping", } - { - path: network.outbound_domains, - notes: "mapped to allowedDomains; no Kit-side domain format check", + path: network.outbound_domains.entries, + format: domain, + notes: "mapped to allowedDomains; wildcard prefix *. is stripped for format check", } - { path: network.outbound_cidrs, From 5262ab008f9f6b6c7c39cb6fa1b8f99fe465550b Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 00:12:14 +0530 Subject: [PATCH 31/60] Bring core SDK docs and comments in line with the Blaxel provider. Core READMEs, website Concepts/Scope, and public API comments still described a Daytona/Modal-only surface after Blaxel landed. Co-authored-by: Cursor Entire-Checkpoint: 01M73C95E45N2TYVQ6Q8CP1TVQ --- docs/code-generation.md | 6 +-- sdks/go/internal/contracts/README.md | 2 + sdks/go/internal/sdk/error.go | 2 +- sdks/go/sandbox/README.md | 20 +++++---- sdks/go/sandbox/capabilities.go | 20 +++++---- sdks/go/sandbox/client.go | 13 +++--- sdks/go/sandbox/client_contracts.go | 5 ++- sdks/go/sandbox/create_validation.go | 3 +- sdks/go/sandbox/doc.go | 7 +-- sdks/go/sandbox/filesystem.go | 2 +- sdks/go/sandbox/lifecycle.go | 18 ++++---- sdks/go/sandbox/sandbox.go | 2 +- sdks/go/sandbox/sandbox_data_contracts.go | 2 +- sdks/typescript/README.md | 3 ++ sdks/typescript/contracts/README.md | 2 + sdks/typescript/sandbox/README.md | 24 +++++----- sdks/typescript/sandbox/src/client/client.ts | 10 +++-- .../sandbox/src/client/filesystem.ts | 2 +- sdks/typescript/sandbox/src/client/sandbox.ts | 23 ++++++---- tooling/README.md | 11 ++--- website/content/docs/concepts.mdx | 45 +++++++++++++++++++ website/content/docs/configuration.mdx | 2 +- website/content/docs/sdk/configuration.mdx | 30 +++++++------ 23 files changed, 165 insertions(+), 89 deletions(-) diff --git a/docs/code-generation.md b/docs/code-generation.md index 3e02852a..a7084b46 100644 --- a/docs/code-generation.md +++ b/docs/code-generation.md @@ -94,9 +94,9 @@ Portable messages come from standard protobuf generation into contract packages. SDK runtimes alias them and keep handwritten clone helpers (for example Go `clone.go` and TypeScript metadata helpers). -Provider error classification is not generated: Modal and Daytona mappers live -in handwritten `errors.go` / `errors.ts`. Unavailable fields receive no -fabricated values. +Provider error classification is not generated: Modal, Daytona, and Blaxel +mappers live in handwritten `errors.go` / `errors.ts`. Unavailable fields +receive no fabricated values. See [responses and errors](responses-and-errors.md) for the public contract, native cause preservation, and copy restrictions. diff --git a/sdks/go/internal/contracts/README.md b/sdks/go/internal/contracts/README.md index 1b6ca420..4d9b0cc8 100644 --- a/sdks/go/internal/contracts/README.md +++ b/sdks/go/internal/contracts/README.md @@ -12,6 +12,8 @@ go get github.com/sandbox-kit/kit/sdks/go/sandbox@latest go get github.com/sandbox-kit/kit/sdks/go/providers/daytona@latest # or go get github.com/sandbox-kit/kit/sdks/go/providers/modal@latest +# or +go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest ``` ## License diff --git a/sdks/go/internal/sdk/error.go b/sdks/go/internal/sdk/error.go index 0ec01f49..756f0121 100644 --- a/sdks/go/internal/sdk/error.go +++ b/sdks/go/internal/sdk/error.go @@ -12,7 +12,7 @@ import ( type Error struct { // Kind is the category, such as ErrorKindUnsupported or ErrorKindAuthentication. Kind sandboxv1.ErrorKind - // Provider is the provider name, such as "daytona" or "modal". It is empty when the failure happened before a provider was selected. + // Provider is the provider name, such as "daytona", "modal", or "blaxel". It is empty when the failure happened before a provider was selected. Provider string // Operation is the call that failed, such as "create", "get", or "initialize". Operation string diff --git a/sdks/go/sandbox/README.md b/sdks/go/sandbox/README.md index 8ccdc61d..c32041c1 100644 --- a/sdks/go/sandbox/README.md +++ b/sdks/go/sandbox/README.md @@ -1,7 +1,7 @@ # Sandbox client Create and manage cloud sandboxes with one Go client. Install this module and -either the Daytona provider or the Modal provider. +one provider (Daytona, Modal, or Blaxel). Requires Go 1.26.0 or later. @@ -12,8 +12,10 @@ go get github.com/sandbox-kit/kit/sdks/go/sandbox@latest go get github.com/sandbox-kit/kit/sdks/go/providers/daytona@latest ``` -For Modal, install `github.com/sandbox-kit/kit/sdks/go/providers/modal` instead -of the Daytona provider. +For Modal or Blaxel, install +`github.com/sandbox-kit/kit/sdks/go/providers/modal` or +`github.com/sandbox-kit/kit/sdks/go/providers/blaxel` instead of the Daytona +provider. ## Create a sandbox @@ -37,12 +39,13 @@ fmt.Println(instance.ID()) ``` `Create` with a nil request uses the provider default when that default is -valid. Daytona uses its default snapshot. Modal requires an image. +valid. Daytona uses its default snapshot. Modal requires an image. Blaxel +requires a name and an image, plus client `Region`. -`client.Close` closes your connection to Daytona or Modal. `instance.Close` -does not stop, terminate, or delete the sandbox, and you can keep using the -handle. The sandbox keeps running until you stop, terminate, archive, or -delete it. +`client.Close` closes your connection to Daytona, Modal, or Blaxel. +`instance.Close` does not stop, terminate, or delete the sandbox, and you can +keep using the handle. The sandbox keeps running until you stop, terminate, +archive, or delete it. ## Run a command @@ -88,6 +91,7 @@ Paths inside the sandbox are absolute. The same handle also supports | ---------------------------------------------------------------------------------- | --------------- | ------------------------ | | [Daytona](https://pkg.go.dev/github.com/sandbox-kit/kit/sdks/go/providers/daytona) | `daytona.New()` | `client.Get` | | [Modal](https://pkg.go.dev/github.com/sandbox-kit/kit/sdks/go/providers/modal) | `modal.New()` | `client.Attach` | +| [Blaxel](https://pkg.go.dev/github.com/sandbox-kit/kit/sdks/go/providers/blaxel) | `blaxel.New()` | `client.Get` (by name) | `Supports` reports whether the provider implements that operation. An unimplemented method returns an error and does not call the provider. A diff --git a/sdks/go/sandbox/capabilities.go b/sdks/go/sandbox/capabilities.go index 1e0d1391..86f71922 100644 --- a/sdks/go/sandbox/capabilities.go +++ b/sdks/go/sandbox/capabilities.go @@ -2,11 +2,12 @@ package sandbox import "context" -// SandboxCreator creates sandboxes. Daytona and Modal implement it. +// SandboxCreator creates sandboxes. Daytona, Modal, and Blaxel implement it. // Call Client.Create. type SandboxCreator interface { // Create creates one sandbox. - // A nil request is Daytona's default snapshot. Modal requires an image in the request. + // A nil request is Daytona's default snapshot. Modal requires an image in the + // request. Blaxel requires a name and an image. Create(context.Context, *CreateOptions) (*CreateResult, error) } @@ -18,7 +19,7 @@ type SandboxAttacher interface { } // SandboxRefresher loads current metadata for a sandbox ID. -// Daytona implements it. Call Client.Get or Sandbox.Refresh. +// Daytona and Blaxel implement it. Call Client.Get or Sandbox.Refresh. type SandboxRefresher interface { // RefreshSandbox loads current metadata for id. RefreshSandbox(context.Context, string) (*SandboxInfo, error) @@ -39,7 +40,7 @@ type SandboxStopper interface { } // SandboxPauser pauses a sandbox. -// Daytona implements it and accepts or rejects the sandbox. Modal does not. +// Daytona implements it and accepts or rejects the sandbox. Modal and Blaxel do not. type SandboxPauser interface { // PauseSandbox pauses the sandbox identified by id. PauseSandbox(context.Context, string, *PauseOptions) (*SandboxInfo, error) @@ -53,7 +54,7 @@ type SandboxTerminator interface { } // SandboxArchiver archives a sandbox. -// Daytona implements it and accepts or rejects the sandbox. Modal does not. +// Daytona implements it and accepts or rejects the sandbox. Modal and Blaxel do not. type SandboxArchiver interface { // ArchiveSandbox archives the sandbox identified by id. ArchiveSandbox(context.Context, string, *ArchiveOptions) (*SandboxInfo, error) @@ -61,7 +62,7 @@ type SandboxArchiver interface { // SandboxFiles performs file operations in a sandbox: read, write, copy to or // from the caller, make directory, list directory, stat, and remove. -// Daytona and Modal implement it. Use Sandbox.Filesystem. +// Daytona, Modal, and Blaxel implement it. Use Sandbox.Filesystem. type SandboxFiles interface { // ReadFile returns the bytes of one file. ReadFile(context.Context, string, string, *FileReadOptions) ([]byte, error) @@ -71,7 +72,8 @@ type SandboxFiles interface { CopyFromLocal(context.Context, string, string, string, *FileCopyOptions) error // CopyToLocal copies a sandbox file onto the caller's disk. CopyToLocal(context.Context, string, string, string, *FileCopyOptions) error - // MakeDirectory creates a directory. A nil Parents flag is true. Daytona rejects false. + // MakeDirectory creates a directory. A nil Parents flag is true. Daytona and + // Blaxel reject false. MakeDirectory(context.Context, string, string, *MakeDirectoryOptions) error // ListDirectory lists the entries in one directory. ListDirectory(context.Context, string, string, *ListDirectoryOptions) ([]*FileInfo, error) @@ -82,7 +84,7 @@ type SandboxFiles interface { } // SandboxProcess runs a command in a sandbox and waits for it to finish. -// Daytona and Modal implement it. Use Sandbox.Process. +// Daytona, Modal, and Blaxel implement it. Use Sandbox.Process. type SandboxProcess interface { // RunCommand runs one command and returns its exit code and output. // A non-zero exit code is not an error. @@ -90,7 +92,7 @@ type SandboxProcess interface { } // SandboxDeleter deletes a sandbox. -// Daytona implements it. Call Sandbox.Delete. Close does not delete the cloud sandbox. +// Daytona and Blaxel implement it. Call Sandbox.Delete. Close does not delete the cloud sandbox. type SandboxDeleter interface { // DeleteSandbox deletes the sandbox identified by id. DeleteSandbox(context.Context, string, *DeleteOptions) (*SandboxInfo, error) diff --git a/sdks/go/sandbox/client.go b/sdks/go/sandbox/client.go index 51a34a89..55ba53cc 100644 --- a/sdks/go/sandbox/client.go +++ b/sdks/go/sandbox/client.go @@ -18,7 +18,7 @@ import ( // Close releases that connection. It does not stop or delete a cloud sandbox. // CloseSandbox runs when the caller closes a sandbox handle. It does not stop or delete the cloud sandbox. type Backend interface { - // Name returns the provider name, "daytona" or "modal". + // Name returns the provider name, "daytona", "modal", or "blaxel". Name() string // Close releases the provider connection. It does not stop or delete a cloud sandbox. Close(context.Context) error @@ -28,9 +28,9 @@ type Backend interface { } // Provider is the value passed to NewClient. -// Use daytona.New or modal.New. +// Use daytona.New, modal.New, or blaxel.New. type Provider interface { - // Name returns the provider name, "daytona" or "modal". + // Name returns the provider name, "daytona", "modal", or "blaxel". Name() string // NewClient opens the provider from config. // Call the package NewClient. That checks config and owns cleanup. @@ -65,7 +65,7 @@ func missingClientBinding(binding any) bool { // NewClient checks config and opens a client for the selected provider. // Closing the client releases the provider SDK. It does not stop or delete a sandbox. // -// Pass daytona.New or modal.New as providerFactory. +// Pass daytona.New, modal.New, or blaxel.New as providerFactory. // // Example: // @@ -142,7 +142,7 @@ func secureEndpoint(value string) bool { return u.Hostname() == "localhost" || (err == nil && ip.IsLoopback()) } -// ProviderName returns "daytona" or "modal". +// ProviderName returns "daytona", "modal", or "blaxel". // A nil client returns an empty string. func (c *Client) ProviderName() string { if c == nil || c.backend == nil { @@ -173,7 +173,8 @@ func (c *Client) Close(ctx context.Context) (result error) { } // Create creates one sandbox and returns its handle. -// A nil request uses the provider default. Modal requires an image in CreateOptions.Source. +// A nil request uses the provider default. Modal requires an image in +// CreateOptions.Source. Blaxel requires a name and an image. // A nil client or a nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. // Close the client when finished. That does not delete the sandbox. // diff --git a/sdks/go/sandbox/client_contracts.go b/sdks/go/sandbox/client_contracts.go index 3a5fb75b..88449422 100644 --- a/sdks/go/sandbox/client_contracts.go +++ b/sdks/go/sandbox/client_contracts.go @@ -11,7 +11,8 @@ import ( type ( // Config is the configuration passed to NewClient. // Set Auth for the provider account. Modal also requires Scope.AppName. - // An omitted Endpoint uses the provider default. Modal rejects Endpoint. + // Blaxel create also requires Region. An omitted Endpoint uses the provider + // default. Modal and Blaxel reject Endpoint. Config = sandboxv1.Config // AuthConfig is the credential set on Config.Auth. @@ -19,7 +20,7 @@ type ( AuthConfig = sandboxv1.AuthConfig // APIKey is the API-key form of AuthConfig.Credentials. - // Daytona accepts an API key. + // Daytona and Blaxel accept an API key. APIKey = sandboxv1.AuthConfig_ApiKey // TokenPair is the id-and-secret form of AuthConfig.Credentials. diff --git a/sdks/go/sandbox/create_validation.go b/sdks/go/sandbox/create_validation.go index eb889bab..82f1b02b 100644 --- a/sdks/go/sandbox/create_validation.go +++ b/sdks/go/sandbox/create_validation.go @@ -27,7 +27,8 @@ var stableRuleIDs = map[string]string{ // ValidateCreateOptions checks a create request before a provider is contacted. // A nil request passes this check. Daytona treats that as its default snapshot. -// Modal still requires an image. Quotas and capacity remain the provider's decision. +// Modal still requires an image. Blaxel still requires a name and an image. +// Quotas and capacity remain the provider's decision. func ValidateCreateOptions(request *CreateOptions) error { if request == nil { return nil diff --git a/sdks/go/sandbox/doc.go b/sdks/go/sandbox/doc.go index 73707752..364169e2 100644 --- a/sdks/go/sandbox/doc.go +++ b/sdks/go/sandbox/doc.go @@ -1,7 +1,7 @@ // Package sandbox creates and manages cloud sandboxes through one client. // -// Install this module and one provider. Pass daytona.New or modal.New to -// [NewClient]. [Client.Create] returns a [Sandbox] handle. +// Install this module and one provider. Pass daytona.New, modal.New, or +// blaxel.New to [NewClient]. [Client.Create] returns a [Sandbox] handle. // // [Client.Close] and [Sandbox.Close] release local resources. They leave the // cloud sandbox running. Stop, terminate, archive, or delete it with a @@ -13,5 +13,6 @@ // Compare it with [CreateFieldResourcesCpuCores], [ConfigFieldEndpoint], // [FileFieldPath], and the other field constants. // -// [Client.Create] with a nil request uses Daytona's default snapshot. Modal requires an image. +// [Client.Create] with a nil request uses Daytona's default snapshot. Modal +// requires an image. Blaxel requires a name and an image. package sandbox diff --git a/sdks/go/sandbox/filesystem.go b/sdks/go/sandbox/filesystem.go index 367c6870..d18ec279 100644 --- a/sdks/go/sandbox/filesystem.go +++ b/sdks/go/sandbox/filesystem.go @@ -81,7 +81,7 @@ func (f *Filesystem) CopyToLocal(ctx context.Context, remotePath, localPath stri // MakeDirectory creates the directory at path. // An omitted Parents flag is true: missing parents are created. Set false with Value. -// Daytona rejects false. +// Daytona and Blaxel reject false. // path is absolute. A nil context returns ErrorKindInvalidArgument. func (f *Filesystem) MakeDirectory(ctx context.Context, path string, options *MakeDirectoryOptions) error { return f.call(ctx, "make_directory", options.GetTimeout(), path, nil, func(operationContext context.Context, id string) error { diff --git a/sdks/go/sandbox/lifecycle.go b/sdks/go/sandbox/lifecycle.go index 87b2504c..7f1c0e38 100644 --- a/sdks/go/sandbox/lifecycle.go +++ b/sdks/go/sandbox/lifecycle.go @@ -48,7 +48,7 @@ func (s *Sandbox) runInfoOp(ctx context.Context, operation string, timeout *time } // Refresh loads the sandbox's current metadata from the provider and stores it on this handle. -// Daytona supports Refresh. Modal returns ErrorKindUnsupported. +// Daytona and Blaxel support Refresh. Modal returns ErrorKindUnsupported. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (s *Sandbox) Refresh(ctx context.Context) error { return s.runInfoOp(ctx, "refresh", nil, func(operationContext context.Context, id string) (*SandboxInfo, error) { @@ -62,7 +62,7 @@ func (s *Sandbox) Refresh(ctx context.Context) error { // Start starts a stopped sandbox and stores the updated metadata on this handle. // A nil timeout uses the client timeout as this call's deadline. The provider receives a timeout only when one is set. -// Daytona supports Start. Modal returns ErrorKindUnsupported. +// Daytona supports Start. Modal and Blaxel return ErrorKindUnsupported. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (s *Sandbox) Start(ctx context.Context, options *StartOptions) error { return s.runInfoOp(ctx, "start", sdk.OptionalDuration(options.GetTimeout()), func(operationContext context.Context, id string) (*SandboxInfo, error) { @@ -77,7 +77,7 @@ func (s *Sandbox) Start(ctx context.Context, options *StartOptions) error { // Stop stops a sandbox and stores the updated metadata on this handle. // Set StopOptions.Force with Value(true) to request an immediate stop. // A nil timeout uses the client timeout as this call's deadline. The provider receives a timeout only when one is set. -// Daytona supports Stop. Modal returns ErrorKindUnsupported. +// Daytona supports Stop. Modal and Blaxel return ErrorKindUnsupported. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (s *Sandbox) Stop(ctx context.Context, options *StopOptions) error { return s.runInfoOp(ctx, "stop", sdk.OptionalDuration(options.GetTimeout()), func(operationContext context.Context, id string) (*SandboxInfo, error) { @@ -91,7 +91,7 @@ func (s *Sandbox) Stop(ctx context.Context, options *StopOptions) error { // Pause pauses a sandbox and stores the updated metadata on this handle. // A nil timeout uses the client timeout as this call's deadline. The provider receives a timeout only when one is set. -// Daytona exposes Pause and accepts or rejects the sandbox. Modal returns ErrorKindUnsupported. +// Daytona exposes Pause and accepts or rejects the sandbox. Modal and Blaxel return ErrorKindUnsupported. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (s *Sandbox) Pause(ctx context.Context, options *PauseOptions) error { return s.runInfoOp(ctx, "pause", sdk.OptionalDuration(options.GetTimeout()), func(operationContext context.Context, id string) (*SandboxInfo, error) { @@ -106,7 +106,7 @@ func (s *Sandbox) Pause(ctx context.Context, options *PauseOptions) error { // Terminate ends a sandbox. The returned ExitResult includes the exit code when Wait is set. // Set TerminateOptions.Wait with Value(true) to wait until the sandbox exits. // A nil timeout uses the client timeout as this call's deadline. Modal waits on that deadline and does not take a separate terminate duration. -// Modal supports Terminate. Daytona returns ErrorKindUnsupported. Use Delete or Stop for Daytona. +// Modal supports Terminate. Daytona and Blaxel return ErrorKindUnsupported. Use Delete or Stop for Daytona; Delete for Blaxel. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (s *Sandbox) Terminate(ctx context.Context, options *TerminateOptions) (*ExitResult, error) { operationContext, cancel, id, err := s.operationContext(ctx, "terminate", sdk.OptionalDuration(options.GetTimeout())) @@ -127,7 +127,7 @@ func (s *Sandbox) Terminate(ctx context.Context, options *TerminateOptions) (*Ex // Archive archives a sandbox and stores the updated metadata on this handle. // A nil timeout uses the client timeout as this call's deadline. Daytona archive does not take a separate duration. -// Daytona exposes Archive and accepts or rejects the sandbox. Modal returns ErrorKindUnsupported. +// Daytona exposes Archive and accepts or rejects the sandbox. Modal and Blaxel return ErrorKindUnsupported. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (s *Sandbox) Archive(ctx context.Context, options *ArchiveOptions) error { return s.runInfoOp(ctx, "archive", sdk.OptionalDuration(options.GetTimeout()), func(operationContext context.Context, id string) (*SandboxInfo, error) { @@ -142,7 +142,7 @@ func (s *Sandbox) Archive(ctx context.Context, options *ArchiveOptions) error { // Delete deletes the cloud sandbox and stores the metadata the provider returns. // Set DeleteOptions.Wait with Value(true) to wait until deletion finishes. Value(false) returns after the provider accepts the request. // A nil timeout uses the client timeout as this call's deadline. The provider receives a timeout only when one is set. -// Close leaves the cloud sandbox in place. Daytona supports Delete. Modal returns ErrorKindUnsupported. Use Terminate for Modal. +// Close leaves the cloud sandbox in place. Daytona and Blaxel support Delete. Modal returns ErrorKindUnsupported. Use Terminate for Modal. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (s *Sandbox) Delete(ctx context.Context, options *DeleteOptions) error { operationContext, cancel, id, err := s.operationContext(ctx, "delete", sdk.OptionalDuration(options.GetTimeout())) @@ -175,7 +175,7 @@ func callOptions[T interface { // Get looks up a sandbox by ID and returns a handle the provider has verified. // An empty ID returns ErrorKindInvalidArgument with Field InfoFieldId. -// Daytona supports Get. Modal returns ErrorKindUnsupported. Use Attach for Modal. +// Daytona and Blaxel support Get. Modal returns ErrorKindUnsupported. Use Attach for Modal. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (c *Client) Get(ctx context.Context, id string) (*Sandbox, error) { return c.resolveSandbox(ctx, id, "get", func(operationContext context.Context) (*SandboxInfo, error) { @@ -189,7 +189,7 @@ func (c *Client) Get(ctx context.Context, id string) (*Sandbox, error) { // Attach returns a handle for an existing sandbox ID without asking the provider to look it up. // An empty ID returns ErrorKindInvalidArgument with Field InfoFieldId. -// Modal supports Attach. Daytona returns ErrorKindUnsupported. Use Get for Daytona. +// Modal supports Attach. Daytona and Blaxel return ErrorKindUnsupported. Use Get for Daytona or Blaxel. // A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (c *Client) Attach(ctx context.Context, id string) (*Sandbox, error) { return c.resolveSandbox(ctx, id, "attach", func(operationContext context.Context) (*SandboxInfo, error) { diff --git a/sdks/go/sandbox/sandbox.go b/sdks/go/sandbox/sandbox.go index 776440b9..d687173e 100644 --- a/sdks/go/sandbox/sandbox.go +++ b/sdks/go/sandbox/sandbox.go @@ -58,7 +58,7 @@ func (s *Sandbox) ID() string { return s.currentID() } -// ProviderName returns "daytona" or "modal". +// ProviderName returns "daytona", "modal", or "blaxel". // A nil handle returns an empty string. func (s *Sandbox) ProviderName() string { if s == nil { diff --git a/sdks/go/sandbox/sandbox_data_contracts.go b/sdks/go/sandbox/sandbox_data_contracts.go index 8c5c15c6..b984d192 100644 --- a/sdks/go/sandbox/sandbox_data_contracts.go +++ b/sdks/go/sandbox/sandbox_data_contracts.go @@ -136,7 +136,7 @@ type ( FileCopyOptions = sandboxv1.FileCopyOptions // MakeDirectoryOptions sets the timeout and Parents flag for Filesystem.MakeDirectory. - // An omitted Parents flag is true. Set false with Value. Daytona rejects false. + // An omitted Parents flag is true. Set false with Value. Daytona and Blaxel reject false. MakeDirectoryOptions = sandboxv1.MakeDirectoryOptions // ListDirectoryOptions sets the timeout for Filesystem.ListDirectory. diff --git a/sdks/typescript/README.md b/sdks/typescript/README.md index 97b4777b..0815dbbf 100644 --- a/sdks/typescript/README.md +++ b/sdks/typescript/README.md @@ -8,6 +8,7 @@ Workspace for the published TypeScript packages: | `@sandbox-kit/sandbox` | Core client | | `@sandbox-kit/daytona` | Daytona provider | | `@sandbox-kit/modal` | Modal provider | +| `@sandbox-kit/blaxel` | Blaxel provider | ## Local development @@ -42,6 +43,7 @@ cd contracts && npm publish cd ../sandbox && npm publish cd ../providers/daytona && npm publish cd ../providers/modal && npm publish +cd ../providers/blaxel && npm publish ``` Before a real publish, dry-run each package: @@ -51,6 +53,7 @@ cd sdks/typescript/contracts && npm pack --dry-run cd ../sandbox && npm pack --dry-run cd ../providers/daytona && npm pack --dry-run cd ../providers/modal && npm pack --dry-run +cd ../providers/blaxel && npm pack --dry-run ``` `prepublishOnly` deletes `dist/` then rebuilds so stale outputs are not packed. diff --git a/sdks/typescript/contracts/README.md b/sdks/typescript/contracts/README.md index 7b57de79..a64bb23c 100644 --- a/sdks/typescript/contracts/README.md +++ b/sdks/typescript/contracts/README.md @@ -9,6 +9,8 @@ Install the public client and a provider instead: npm install @sandbox-kit/sandbox @sandbox-kit/daytona # or npm install @sandbox-kit/sandbox @sandbox-kit/modal +# or +npm install @sandbox-kit/sandbox @sandbox-kit/blaxel ``` See [`@sandbox-kit/sandbox`](https://www.npmjs.com/package/@sandbox-kit/sandbox) diff --git a/sdks/typescript/sandbox/README.md b/sdks/typescript/sandbox/README.md index 2eac31c1..ec7c0b5c 100644 --- a/sdks/typescript/sandbox/README.md +++ b/sdks/typescript/sandbox/README.md @@ -1,8 +1,9 @@ # `@sandbox-kit/sandbox` Create and manage cloud sandboxes with a consistent TypeScript client interface. -Pick a provider package (`@sandbox-kit/daytona` or `@sandbox-kit/modal`), -construct a client, then create or open sandboxes through that client. +Pick a provider package (`@sandbox-kit/daytona`, `@sandbox-kit/modal`, or +`@sandbox-kit/blaxel`), construct a client, then create or open sandboxes +through that client. Closing a client or handle releases **local** SDK resources only. It does not stop, terminate, or delete the remote sandbox. @@ -13,6 +14,8 @@ stop, terminate, or delete the remote sandbox. npm install @sandbox-kit/sandbox @sandbox-kit/daytona # or npm install @sandbox-kit/sandbox @sandbox-kit/modal +# or +npm install @sandbox-kit/sandbox @sandbox-kit/blaxel ``` Requires Node.js 20, 22, or 24.15+. @@ -40,14 +43,14 @@ Prefer `await using` when available. Otherwise call `await client.close()` in a Pass the provider on `ClientConfig`, then shared settings: -| Field | Purpose | -| ---------- | --------------------------------------------------------------- | -| `provider` | Provider factory (`daytona()`, `modal()`, …) | -| `auth` | One credential shape (`apiKey`, `bearerToken`, `tokenPair`, …) | -| `scope` | App, environment, organization, or project (provider-dependent) | -| `endpoint` | API URL when the provider supports it | -| `region` | Target or placement preference | -| `timeout` | Default operation deadline in **milliseconds** | +| Field | Purpose | +| ---------- | -------------------------------------------------------------------------- | +| `provider` | Provider factory (`daytona()`, `modal()`, …) | +| `auth` | One credential shape (`apiKey`, `bearerToken`, `tokenPair`, …) | +| `scope` | App, environment, organization, project, or workspace (provider-dependent) | +| `endpoint` | API URL when the provider supports it | +| `region` | Target or placement preference | +| `timeout` | Default operation deadline in **milliseconds** | Omit `auth` when the provider SDK should resolve credentials from its own environment. Provider-specific auth and scope rules live in each provider @@ -153,6 +156,7 @@ try { | ---------------------- | ----------------- | | `@sandbox-kit/daytona` | Daytona sandboxes | | `@sandbox-kit/modal` | Modal sandboxes | +| `@sandbox-kit/blaxel` | Blaxel sandboxes | ## Examples diff --git a/sdks/typescript/sandbox/src/client/client.ts b/sdks/typescript/sandbox/src/client/client.ts index cf9560c5..4127035a 100644 --- a/sdks/typescript/sandbox/src/client/client.ts +++ b/sdks/typescript/sandbox/src/client/client.ts @@ -93,13 +93,14 @@ export class Client implements AsyncDisposable { }); } } - /** `"daytona"` or `"modal"`. */ + /** `"daytona"`, `"modal"`, or `"blaxel"`. */ get providerName(): string { return this.#providerName; } /** * Returns a handle for an existing sandbox ID without looking it up. - * Modal supports attach. Daytona throws `ErrorKind.Unsupported`. Use `get`. + * Modal supports attach. Daytona and Blaxel throw `ErrorKind.Unsupported`. + * Use `get`. */ async attach(id: string, options: OperationOptions = {}): Promise { return this.#openById("attach", id, options, (fetchOptions) => { @@ -109,7 +110,8 @@ export class Client implements AsyncDisposable { } /** * Looks up a sandbox by ID and returns a handle the provider has verified. - * Daytona supports get. Modal throws `ErrorKind.Unsupported`. Use `attach`. + * Daytona and Blaxel support get. Modal throws `ErrorKind.Unsupported`. Use + * `attach`. */ async get(id: string, options: OperationOptions = {}): Promise { return this.#openById("get", id, options, (fetchOptions) => { @@ -152,7 +154,7 @@ export class Client implements AsyncDisposable { /** * Creates one sandbox and returns its handle. * An empty request uses the provider default. Daytona uses its default - * snapshot. Modal requires an image. + * snapshot. Modal requires an image. Blaxel requires a name and an image. */ async create( request: CreateOptions = {}, diff --git a/sdks/typescript/sandbox/src/client/filesystem.ts b/sdks/typescript/sandbox/src/client/filesystem.ts index 7f1bed70..075dc83b 100644 --- a/sdks/typescript/sandbox/src/client/filesystem.ts +++ b/sdks/typescript/sandbox/src/client/filesystem.ts @@ -135,7 +135,7 @@ export class Filesystem { /** * Creates the directory at `path`. * An omitted `parents` flag is true: missing parents are created. - * Daytona rejects `false`. + * Daytona and Blaxel reject `false`. */ makeDirectory( path: string, diff --git a/sdks/typescript/sandbox/src/client/sandbox.ts b/sdks/typescript/sandbox/src/client/sandbox.ts index 76ce87cc..02a074eb 100644 --- a/sdks/typescript/sandbox/src/client/sandbox.ts +++ b/sdks/typescript/sandbox/src/client/sandbox.ts @@ -98,7 +98,7 @@ export class Sandbox implements AsyncDisposable { get id(): string { return this.#info.id; } - /** `"daytona"` or `"modal"`. */ + /** `"daytona"`, `"modal"`, or `"blaxel"`. */ get providerName(): string { return this.#info.provider; } @@ -124,7 +124,7 @@ export class Sandbox implements AsyncDisposable { } /** * Loads current metadata from the provider and stores it on this handle. - * Daytona supports refresh. Modal throws `ErrorKind.Unsupported`. + * Daytona and Blaxel support refresh. Modal throws `ErrorKind.Unsupported`. */ async refresh(options: OperationOptions = {}): Promise { return this.#serialized( @@ -147,7 +147,7 @@ export class Sandbox implements AsyncDisposable { /** * Starts a stopped sandbox and stores the updated metadata on this handle. * An omitted `timeout` uses the client timeout as this call's deadline. - * Daytona supports start. Modal throws `ErrorKind.Unsupported`. + * Daytona supports start. Modal and Blaxel throw `ErrorKind.Unsupported`. */ async start(options: OperationOptions = {}): Promise { return this.#update( @@ -160,7 +160,7 @@ export class Sandbox implements AsyncDisposable { /** * Stops a sandbox and stores the updated metadata on this handle. * Set `force` to request an immediate stop. - * Daytona supports stop. Modal throws `ErrorKind.Unsupported`. + * Daytona supports stop. Modal and Blaxel throw `ErrorKind.Unsupported`. */ async stop(options: StopCallOptions = {}): Promise { const { force, ...call } = options; @@ -176,7 +176,8 @@ export class Sandbox implements AsyncDisposable { } /** * Pauses a sandbox and stores the updated metadata on this handle. - * Daytona accepts or rejects the sandbox. Modal throws `ErrorKind.Unsupported`. + * Daytona accepts or rejects the sandbox. Modal and Blaxel throw + * `ErrorKind.Unsupported`. */ async pause(options: OperationOptions = {}): Promise { return this.#update( @@ -190,7 +191,8 @@ export class Sandbox implements AsyncDisposable { * Ends a sandbox. The result includes `exitCode` when `wait` is set. * An omitted `timeout` uses the client timeout as this call's deadline. * Modal waits on that deadline and does not take a separate terminate duration. - * Modal supports terminate. Daytona throws `ErrorKind.Unsupported`. Use `delete` or `stop`. + * Modal supports terminate. Daytona and Blaxel throw `ErrorKind.Unsupported`. + * Use `delete` or `stop` on Daytona; `delete` on Blaxel. */ async terminate(options: TerminateCallOptions = {}): Promise { const { wait, ...call } = options; @@ -218,7 +220,8 @@ export class Sandbox implements AsyncDisposable { * Archives a sandbox and stores the updated metadata on this handle. * Daytona archive does not take a separate duration. The call still uses * `timeout` as its deadline. - * Daytona accepts or rejects the sandbox. Modal throws `ErrorKind.Unsupported`. + * Daytona accepts or rejects the sandbox. Modal and Blaxel throw + * `ErrorKind.Unsupported`. */ async archive(options: OperationOptions = {}): Promise { return this.#update( @@ -231,7 +234,8 @@ export class Sandbox implements AsyncDisposable { /** * Deletes the cloud sandbox and stores the metadata the provider returns. * Set `wait` to wait until deletion finishes. `close` leaves the sandbox in place. - * Daytona supports delete. Modal throws `ErrorKind.Unsupported`. Use `terminate`. + * Daytona and Blaxel support delete. Modal throws `ErrorKind.Unsupported`. Use + * `terminate`. */ async delete(options: DeleteCallOptions = {}): Promise { const { wait, ...call } = options; @@ -247,7 +251,8 @@ export class Sandbox implements AsyncDisposable { } /** * Closes this handle. The sandbox stays as it is, and you can keep using the handle. - * Closing a Daytona handle does not contact Daytona. Closing a Modal handle does not terminate the sandbox. + * Closing a Daytona or Blaxel handle does not contact the provider. Closing a + * Modal handle does not terminate the sandbox. */ async close(options: OperationOptions = {}): Promise { await this.#serialized("close", options, this.#timeout, (signal) => diff --git a/tooling/README.md b/tooling/README.md index d22d9489..046da515 100644 --- a/tooling/README.md +++ b/tooling/README.md @@ -54,13 +54,14 @@ descriptors; SDKs evaluate create and client-config rules at runtime exports compiled descriptors from `dist/` via `@sandbox-kit/contracts/sandbox` and `@sandbox-kit/contracts/client`. -`test go` tests the public SDK, tooling, both providers, and every independent -Go example module registered by the command. It also checks that `go.work.sum` -matches the checksums written by loading those modules' packages. Example tests -use dummy credentials and local servers; they create no cloud resources. +`test go` tests the public SDK, tooling, all providers (Daytona, Modal, Blaxel), +and every independent Go example module registered by the command. It also +checks that `go.work.sum` matches the checksums written by loading those +modules' packages. Example tests use dummy credentials and local servers; they +create no cloud resources. `test typescript` builds contracts first, then builds and tests the shared -package and both providers, discovers every TypeScript example containing a +package and all providers, discovers every TypeScript example containing a `package.json`, and builds each example without making provider calls. Install TypeScript workspace deps once from `sdks/typescript/` (`npm install`) before running it. Manual npm publish steps live in diff --git a/website/content/docs/concepts.mdx b/website/content/docs/concepts.mdx index 9cfd15cb..01e66bd5 100644 --- a/website/content/docs/concepts.mdx +++ b/website/content/docs/concepts.mdx @@ -51,6 +51,23 @@ await using client = new Client({ }); ``` + + + + +```ts +import { blaxel } from "@sandbox-kit/blaxel"; +import { Client } from "@sandbox-kit/sandbox"; + +await using client = new Client({ + provider: blaxel(), + auth: { apiKey: { key: process.env.BL_API_KEY! } }, + scope: { workspace: process.env.BL_WORKSPACE! }, + region: process.env.BL_REGION || "us-pdx-1", + timeout: 120_000, // default client deadline (ms) +}); +``` + @@ -111,6 +128,34 @@ if err != nil { } ``` + + + + +```go +import ( + "os" + "time" + + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ + ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, + }}, + Scope: &sandbox.Scope{ + Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), + }, + Region: sandbox.Value("us-pdx-1"), + Timeout: sandbox.Duration(2 * time.Minute), +}) +if err != nil { + return err +} +``` + diff --git a/website/content/docs/configuration.mdx b/website/content/docs/configuration.mdx index 47d1fc92..b4e96410 100644 --- a/website/content/docs/configuration.mdx +++ b/website/content/docs/configuration.mdx @@ -42,7 +42,7 @@ if err != nil { | ---------- | ------------------------------------------------------------------------------------------- | | `provider` | Provider constructor | | `auth` | One of `apiKey`, `tokenPair`, `bearerToken`, `oauthRefresh`. Omit to use provider env creds | -| `scope` | App, environment, organization, or workspace | +| `scope` | App, environment, organization, project, or workspace | | `endpoint` | Provider endpoint URL override | | `region` | Target / default placement | | `timeout` | Default deadline for calls that omit their own | diff --git a/website/content/docs/sdk/configuration.mdx b/website/content/docs/sdk/configuration.mdx index 30afbf65..318813ca 100644 --- a/website/content/docs/sdk/configuration.mdx +++ b/website/content/docs/sdk/configuration.mdx @@ -28,7 +28,7 @@ type Config = { | `auth?` | [`AuthInput`](#auth) | Credential set. Omit to use env/profile credentials from the provider. | | `endpoint?` | `string` | Provider endpoint URL override. Remote URLs must be HTTPS. | | `region?` | `string` | Default region or target preference | -| `scope?` | [`Scope`](#scope) | App, environment, organization, or project | +| `scope?` | [`Scope`](#scope) | App, environment, organization, project, or workspace | | `timeout?` | `number` | Default deadline (ms) for calls that omit their own timeout | **Example** @@ -74,7 +74,7 @@ Pass `config` to `NewClient(provider, config)`. | `Auth` | Credential set. Nil uses env/profile credentials from the provider. | | `Endpoint` | Provider endpoint URL override | | `Region` | Default region or target preference | -| `Scope` | App, environment, organization, or project | +| `Scope` | App, environment, organization, project, or workspace | | `Timeout` | Default client deadline via `sandbox.Duration` | **Example** @@ -160,22 +160,24 @@ type AuthInput = -| Field | Description | -| ---------------- | ------------------------------- | -| `appName` | Application name | -| `environment` | Environment name (often `main`) | -| `organizationId` | Organization id | -| `projectId` | Project id | +| Field | Description | +| ---------------- | -------------------------------------------------- | +| `appName` | Application name | +| `environment` | Environment name (often `main`) | +| `organizationId` | Organization id | +| `projectId` | Project id | +| `workspace` | Blaxel workspace name (required for Blaxel create) | -| Field | Description | -| ---------------- | ------------------------------- | -| `AppName` | Application name | -| `Environment` | Environment name (often `main`) | -| `OrganizationId` | Organization id | -| `ProjectId` | Project id | +| Field | Description | +| ---------------- | -------------------------------------------------- | +| `AppName` | Application name | +| `Environment` | Environment name (often `main`) | +| `OrganizationId` | Organization id | +| `ProjectId` | Project id | +| `Workspace` | Blaxel workspace name (required for Blaxel create) | From 5a202dca802f0f2a680507591c662d2c83dfc5a2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 10 Nov 2026 19:01:21 +0000 Subject: [PATCH 32/60] Polish Blaxel soft debt: create options, standby, 409, Stat docs. Drop unused create-plan options, project TS runtime state at the native boundary, map bare HTTP 409 to AlreadyExists, clarify Go Stat ownership, and update provider/user docs to match. Co-authored-by: Nabhag Motivaras --- docs/configuration.md | 6 +- docs/filesystem.md | 7 +- docs/responses-and-errors.md | 4 + docs/sandbox-creation.md | 2 +- docs/sandbox-lifecycle.md | 6 +- sdks/go/providers/blaxel/README.md | 18 +++-- sdks/go/providers/blaxel/errors.go | 4 +- sdks/go/providers/blaxel/errors_test.go | 33 +++++++++ sdks/go/providers/blaxel/filesystem.go | 6 +- sdks/typescript/providers/blaxel/README.md | 18 +++-- .../providers/blaxel/src/blaxel.test.ts | 73 +++++++++++++++++-- .../typescript/providers/blaxel/src/blaxel.ts | 2 +- .../typescript/providers/blaxel/src/errors.ts | 4 +- .../providers/blaxel/src/mappings/mappings.ts | 2 - .../blaxel/src/mappings/provider_mappings.ts | 8 +- .../typescript/providers/blaxel/src/native.ts | 26 ++++++- website/content/docs/blaxel.mdx | 11 ++- website/content/docs/errors.mdx | 5 +- website/content/docs/filesystem.mdx | 8 ++ website/content/docs/lifecycle.mdx | 2 +- 20 files changed, 204 insertions(+), 41 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index c2a56f5d..ace7442a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -91,9 +91,9 @@ deadline or abort signal that arrives earlier still applies. Daytona create keeps its 60-second default when the timeout is omitted and when it is explicit zero. Daytona start, stop, pause, and delete use that 60-second default when the timeout is omitted, and treat explicit zero as no -operation timeout. Blaxel uses the Kit deadline for create. `@blaxel/core` -also accepts a native creation timeout (1–50 whole seconds); this provider -does not forward it. The Go Blaxel SDK has no equivalent argument. +operation timeout. Blaxel uses the Kit deadline for create and does not pass +`@blaxel/core` `SandboxCreateOptions` (including the native 1–50s creation +timeout). The Go Blaxel SDK has no equivalent argument. ## Credentials diff --git a/docs/filesystem.md b/docs/filesystem.md index 52f63c26..50e206ae 100644 --- a/docs/filesystem.md +++ b/docs/filesystem.md @@ -215,7 +215,12 @@ entries, err := instance.Filesystem().ListDirectory(ctx, "/tmp/kit-files", nil) ## Read metadata -`Stat` returns one path. A symlink result describes the symlink. Modal can +`Stat` returns one path. A symlink result describes the symlink. Blaxel has no +dedicated stat API: TypeScript uses `fs.ls` (directory listing, or parent +listing after Blaxel's file-path "Directory not found" response); Go calls the +sandbox filesystem GET through a rebuilt lower client because +`SandboxInstanceFSService` has no public Get/Stat and `LS` always treats the +response as a directory. Modal can report a symlink. Daytona reports a file or a directory. `mode` is an octal string when the provider supplies one, such as `0644`. `permissions`, `owner`, and `group` are set when the provider supplies them. `modifiedAt` is set when diff --git a/docs/responses-and-errors.md b/docs/responses-and-errors.md index 07a9ee2b..e02c952f 100644 --- a/docs/responses-and-errors.md +++ b/docs/responses-and-errors.md @@ -109,6 +109,10 @@ An error that Kit could not classify keeps an unknown kind and the original cause. A kind is a category. It is not a signal that retrying is safe, and Kit does not retry for you. +Blaxel name collisions map to `AlreadyExists` when the provider sends +`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409. Provider codes still win when +present, so `SANDBOX_DELETION_IN_PROGRESS` stays `Conflict`. + ## After a failure An error does not prove that no sandbox exists. Readiness or metadata handling diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index 5ae71d3d..1ceb4f39 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -21,7 +21,7 @@ Blaxel (`modal@0.11.1`, `@daytona/sdk@0.225.0`, `@blaxel/core@0.3.36` / | Security | Named secrets and workload identity | Egress-placeholder secrets and public access | Unsupported | | Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | Ports (not 80/443/8080; Default → Blaxel HTTP); outbound domains | | Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m); explicit idle disable rejected | -| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Kit client/create timeout; native create timeout not forwarded; waitFor Default/Scheduled only | +| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Kit client/create timeout only (native create timeout / SandboxCreateOptions not forwarded); waitFor Default/Scheduled only | | Observability | Verbose output | Telemetry endpoint | Unsupported | Kit has no portable volume fields; Blaxel volumes stay out of create the same diff --git a/docs/sandbox-lifecycle.md b/docs/sandbox-lifecycle.md index aa5102e6..388637a0 100644 --- a/docs/sandbox-lifecycle.md +++ b/docs/sandbox-lifecycle.md @@ -110,7 +110,11 @@ exposes it as `Client.Attach`; Modal does not advertise `Get` or `Refresh`. Blaxel looks up sandboxes by name for `Get` and `Refresh`. `Delete` removes the Blaxel resource. `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and `Terminate` -are unsupported. Closing a Blaxel handle does not contact Blaxel. +are unsupported. Closing a Blaxel handle does not contact Blaxel. On +`Get` / `Refresh`, Blaxel deployment `DEPLOYED` with runtime `STANDBY` maps to +Kit paused, and `RUNNING` maps to running. Go uses the SDK's public +`Sandbox.State`. TypeScript projects runtime state from `@blaxel/core`'s +private wire model onto a Kit-facing `state` field (no public `state` getter). File calls on the same handle are documented in [filesystem](filesystem.md). Daytona, Modal, and Blaxel implement the same eight methods; each handle diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index cb045c82..cf70346e 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -77,15 +77,23 @@ the native create path (not create-if-not-exists). On the handle: `Get`, `Refresh`, `Delete`, and `Close`. `Get` and `Refresh` look up the sandbox by name (`ID()` equals the create name). `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and `Terminate` are not supported. `Close` does not -contact Blaxel and does not change the sandbox. +contact Blaxel and does not change the sandbox. `Refresh` / `Info` map Blaxel +deployment `DEPLOYED` plus runtime `STANDBY` to Kit `Paused` (and `RUNNING` to +`Running`) via the Go SDK's public `Sandbox.State`. A create that collides on +name maps to `AlreadyExists` (`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409). +Coded conflicts such as `SANDBOX_DELETION_IN_PROGRESS` stay `Conflict`. `Filesystem` supports the shared file calls: `ReadFile`, `WriteFile`, `CopyFromLocal`, `CopyToLocal`, `MakeDirectory`, `ListDirectory`, `Stat`, and `Remove`. Paths in the sandbox are absolute. A file timeout is the context -deadline. `Parents` defaults to true. `Parents` set to false is rejected. A -missing file passed to `CopyFromLocal` is not found, and the error field is -`local_path`. A local directory is an invalid argument with the same field. -`CopyToLocal` creates a missing local parent directory. +deadline. `Parents` defaults to true. `Parents` set to false is rejected. +`Stat` uses the sandbox filesystem GET (the same underlying call as `LS`). +`SandboxInstanceFSService` has no public Get/Stat, and `LS` always treats the +response as a directory, so Kit rebuilds the lower filesystem client against +the instance URL for metadata. A missing file passed to `CopyFromLocal` is not +found, and the error field is `local_path`. A local directory is an invalid +argument with the same field. `CopyToLocal` creates a missing local parent +directory. `Process().Run` runs one command and waits for the result. Set exactly one of `Argv` or `Shell`. `Argv` is sent as a POSIX-quoted shell string. A non-zero diff --git a/sdks/go/providers/blaxel/errors.go b/sdks/go/providers/blaxel/errors.go index 5df67a40..7e489ccb 100644 --- a/sdks/go/providers/blaxel/errors.go +++ b/sdks/go/providers/blaxel/errors.go @@ -78,7 +78,9 @@ var providerStatusKinds = map[int]sandbox.ErrorKind{ 404: sandbox.ErrorKindNotFound, 410: sandbox.ErrorKindNotFound, 408: sandbox.ErrorKindTimeout, - 409: sandbox.ErrorKindConflict, + // Bare 409 is usually name collision; coded conflicts (e.g. deletion in + // progress) still win through providerCodeKinds above. + 409: sandbox.ErrorKindAlreadyExists, 412: sandbox.ErrorKindFailedPrecondition, 429: sandbox.ErrorKindRateLimited, 500: sandbox.ErrorKindInternal, diff --git a/sdks/go/providers/blaxel/errors_test.go b/sdks/go/providers/blaxel/errors_test.go index 89272f2e..cf8aafa0 100644 --- a/sdks/go/providers/blaxel/errors_test.go +++ b/sdks/go/providers/blaxel/errors_test.go @@ -53,6 +53,39 @@ func TestMapProviderErrorSandboxAlreadyExists(t *testing.T) { } } +func TestMapProviderErrorBareConflictIsAlreadyExists(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes/demo") + if err != nil { + t.Fatal(err) + } + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusConflict, + Request: &http.Request{Method: http.MethodPost, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusConflict}, + }, "create") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindAlreadyExists { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } +} + +func TestMapProviderErrorDeletionInProgressStaysConflict(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes/demo") + if err != nil { + t.Fatal(err) + } + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusConflict, + ErrorCode: "SANDBOX_DELETION_IN_PROGRESS", + Request: &http.Request{Method: http.MethodDelete, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusConflict}, + }, "delete") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindConflict { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } +} + func TestMapProviderErrorGatewayTimeout(t *testing.T) { parsed, err := url.Parse("https://example.invalid/sandboxes/demo/process") if err != nil { diff --git a/sdks/go/providers/blaxel/filesystem.go b/sdks/go/providers/blaxel/filesystem.go index f3275cde..6c8a4ee6 100644 --- a/sdks/go/providers/blaxel/filesystem.go +++ b/sdks/go/providers/blaxel/filesystem.go @@ -104,8 +104,10 @@ func (b *backend) Stat(ctx context.Context, id, path string, _ *sandbox.StatOpti if err != nil { return nil, err } - // No public Stat on SandboxInstanceFSService; rebuild the lower FS client - // against the instance URL so metadata uses the same sandbox endpoint. + // SandboxInstanceFSService has Read/LS/… but no public Get/Stat. LS always + // calls AsDirectory(), so a file path would look like an empty directory. + // Rebuild the lower SandboxFilesystemService with the instance base URL + // (same construction NewInstance uses for instance.FS) and call Get. fs := b.filesystemService(instance) res, err := fs.Get(ctx, path, blaxel.SandboxFilesystemGetParams{}) if err != nil { diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index ef16bbfe..3207a3a0 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -69,15 +69,23 @@ create. **Lifecycle** on the handle: `get` / `refresh` (lookup by name), `delete`, and `close`. `close` does not delete the sandbox. Start, stop, pause, archive, -terminate, and attach are unsupported. +terminate, and attach are unsupported. `refresh` / `info` map Blaxel +deployment `DEPLOYED` plus runtime `STANDBY` to Kit `paused` (and `RUNNING` to +`running`). `@blaxel/core` types `SandboxInstance.sandbox` private and has no +`state` getter; the provider projects wire runtime state onto a Kit-facing +`state` field at the native boundary. A create that collides on name maps to +`AlreadyExists` (`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409). Coded conflicts +such as `SANDBOX_DELETION_IN_PROGRESS` stay `Conflict`. **Files** through `sandbox.filesystem`: `readFile`, `writeFile`, `copyFromLocal`, `copyToLocal`, `makeDirectory`, `listDirectory`, `stat`, and `remove`. Paths in the sandbox are absolute. `parents` defaults to true; -`parents: false` is rejected. A missing file passed to `copyFromLocal` is not -found before Blaxel is called, and the error field is `local_path`. A local -directory is an invalid argument with the same field. `copyToLocal` creates a -missing local parent directory. +`parents: false` is rejected. `stat` uses Blaxel `fs.ls` (GET-by-path): a +directory listing succeeds; a file path returns Blaxel's "Directory not found" +response, then Kit lists the parent. A missing file passed to `copyFromLocal` +is not found before Blaxel is called, and the error field is `local_path`. A +local directory is an invalid argument with the same field. `copyToLocal` +creates a missing local parent directory. **Commands** through `sandbox.process.run`. Set exactly one of `argv` or `shell`. `argv` is sent as a POSIX-quoted shell string. `run` waits until the diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 6e276d19..106fbfa9 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -38,7 +38,8 @@ import { import { captureProviderState } from "#config/provider_config.js"; import { mapBlaxelError } from "#errors.js"; import { remainingWaitBudgetMs } from "./process.js"; -import { ResponseError } from "@blaxel/core"; +import { projectHandle } from "./native.js"; +import { ResponseError, SandboxInstance } from "@blaxel/core"; const provider = { scope: { workspace: "ws" }, region: "us-pdx-1" }; @@ -163,7 +164,6 @@ describe("Blaxel mappings", () => { }, provider, ); - expect(plan.options).toBeUndefined(); expect(plan).toMatchObject({ params: { name: "example", @@ -321,6 +321,41 @@ describe("Blaxel mappings", () => { }); }); + it("classifies bare HTTP 409 as already exists", () => { + const native = new ResponseError( + new Response(null, { status: 409, statusText: "Conflict" }), + { error: { message: "conflict" } }, + new Error("conflict"), + ); + expect(mapBlaxelError(native, "create")).toMatchObject({ + kind: ErrorKind.AlreadyExists, + provider: "blaxel", + operation: "create", + statusCode: 409, + cause: native, + }); + }); + + it("keeps SANDBOX_DELETION_IN_PROGRESS as conflict", () => { + const native = new ResponseError( + new Response(null, { status: 409, statusText: "Conflict" }), + { + error: { + code: "SANDBOX_DELETION_IN_PROGRESS", + message: "deleting", + status: 409, + }, + }, + new Error("deleting"), + ); + expect(mapBlaxelError(native, "delete")).toMatchObject({ + kind: ErrorKind.Conflict, + providerCode: "SANDBOX_DELETION_IN_PROGRESS", + statusCode: 409, + cause: native, + }); + }); + it("classifies edge gateway 504 as timeout", () => { const native = new ResponseError( new Response(null, { status: 504, statusText: "Gateway Timeout" }), @@ -498,13 +533,40 @@ describe("Blaxel mappings", () => { }); }); - it("reads runtime state from SandboxInstance wire model", () => { + it("maps projected runtime state from the native adapter", () => { expect( mapSandboxIdentity({ metadata: { name: "demo" }, status: "DEPLOYED", - // SandboxInstance exposes runtime state only on its private wire model. - ...({ sandbox: { state: "STANDBY" } } as object), + // defaultNativeFactory.projectHandle copies wire sandbox.state here. + state: "STANDBY", + }).state, + ).toBe(SandboxState.Paused); + }); + + it("projectHandle copies private wire sandbox.state onto state", () => { + const instance = new SandboxInstance({ + metadata: { name: "demo" }, + status: "DEPLOYED", + spec: { region: "us-pdx-1" }, + } as never); + // @blaxel/core types sandbox private; seed the wire model for the adapter. + Object.assign(instance as object, { + sandbox: { + metadata: { name: "demo" }, + status: "DEPLOYED", + state: "STANDBY", + spec: { region: "us-pdx-1" }, + }, + }); + const handle = projectHandle(instance); + expect(handle.state).toBe("STANDBY"); + expect( + mapSandboxIdentity({ + metadata: { name: "demo" }, + status: "DEPLOYED", + state: handle.state, + spec: { region: "us-pdx-1" }, }).state, ).toBe(SandboxState.Paused); }); @@ -561,6 +623,7 @@ describe("Blaxel provider orchestration", () => { memory: 2048, region: "us-pdx-1", }); + // Kit never forwards @blaxel/core SandboxCreateOptions (native create timeout). expect(created?.options).toBeUndefined(); expect(sandbox.id).toBe("example"); expect(sandbox.info().name).toBe("example"); diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index 881d6a4e..a1fd90f6 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -95,7 +95,7 @@ class BlaxelBackend extends BackendBase implements Backend { throwIfAborted(options.signal); const plan = mapCreateRequest(request, this.config); throwIfAborted(options.signal); - const sandbox = await this.native.create(plan.params, plan.options); + const sandbox = await this.native.create(plan.params); return { sandbox: mapSandboxInfo(sandbox, request) }; } catch (error) { throw mapBlaxelError(error, "create"); diff --git a/sdks/typescript/providers/blaxel/src/errors.ts b/sdks/typescript/providers/blaxel/src/errors.ts index 2884b2af..692f797d 100644 --- a/sdks/typescript/providers/blaxel/src/errors.ts +++ b/sdks/typescript/providers/blaxel/src/errors.ts @@ -97,7 +97,9 @@ const STATUS_KINDS: Record = { 404: ErrorKind.NotFound, 410: ErrorKind.NotFound, 408: ErrorKind.Timeout, - 409: ErrorKind.Conflict, + // Bare 409 is usually name collision; coded conflicts (e.g. deletion in + // progress) still win through CODE_KINDS above. + 409: ErrorKind.AlreadyExists, 412: ErrorKind.FailedPrecondition, 429: ErrorKind.RateLimited, 500: ErrorKind.Internal, diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index 5a234434..39ae36a3 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -3,7 +3,6 @@ import type { Config as NativeClientConfig, Port, SandboxCreateConfiguration, - SandboxCreateOptions, SandboxLifecycle, } from "@blaxel/core"; import { @@ -51,7 +50,6 @@ import { providerError } from "#errors.js"; export interface BlaxelCreatePlan { readonly params: SandboxCreateConfiguration; - readonly options?: SandboxCreateOptions; } export function mapClientConfig(config: Config): NativeClientConfig { diff --git a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts index da4b9315..6b9daa9e 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts @@ -30,7 +30,10 @@ export interface BlaxelSandboxIdentity { }; /** Deployment status (DEPLOYED, FAILED, …). */ readonly status?: unknown; - /** Runtime state (RUNNING / STANDBY). */ + /** + * Runtime state (RUNNING / STANDBY). `@blaxel/core` types `sandbox` private + * and has no `state` getter; the native adapter projects wire state here. + */ readonly state?: unknown; readonly spec?: { readonly region?: string; @@ -146,9 +149,6 @@ export function mapAllocatedResources( function runtimeState(source: BlaxelSandboxIdentity): string | undefined { if (source.state !== undefined && String(source.state) !== "") return String(source.state); - // SandboxInstance keeps the wire model on a private field; read it when present. - const nested = (source as { sandbox?: { state?: unknown } }).sandbox?.state; - if (nested !== undefined && String(nested) !== "") return String(nested); return undefined; } diff --git a/sdks/typescript/providers/blaxel/src/native.ts b/sdks/typescript/providers/blaxel/src/native.ts index fbcadfeb..5ae6c129 100644 --- a/sdks/typescript/providers/blaxel/src/native.ts +++ b/sdks/typescript/providers/blaxel/src/native.ts @@ -66,14 +66,32 @@ export function defaultNativeFactory(config: NativeClientConfig): NativeBlaxel { configure(next: NativeClientConfig): void { settings.setConfig(next); }, - create(params, options) { - return SandboxInstance.create(params, options); + async create(params, options) { + return projectHandle(await SandboxInstance.create(params, options)); }, - get(name) { - return SandboxInstance.get(name); + async get(name) { + return projectHandle(await SandboxInstance.get(name)); }, delete(name) { return SandboxInstance.delete(name); }, }; } + +/** + * `@blaxel/core` types `SandboxInstance.sandbox` private and has no `state` + * getter. Project runtime STANDBY/RUNNING onto a Kit-facing `state` field so + * identity mapping never reaches into the private wire model. + */ +export function projectHandle(instance: SandboxInstance): NativeSandboxHandle { + const state = wireRuntimeState(instance); + if (state === undefined) return instance as unknown as NativeSandboxHandle; + return Object.assign(instance, { state }) as unknown as NativeSandboxHandle; +} + +function wireRuntimeState(instance: SandboxInstance): string | undefined { + const wire = instance as unknown as { sandbox?: { state?: unknown } }; + const value = wire.sandbox?.state; + if (value === undefined || String(value) === "") return undefined; + return String(value); +} diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index cae3bb3e..b2e480ff 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -167,6 +167,8 @@ Token-pair, bearer, and OAuth client modes are rejected. Scope fields other than | Volumes | Not in Kit create; no portable volume fields | | Open | `get` / `refresh` by sandbox name | | Lifecycle | `delete`, `close` — no start/stop/pause/archive/terminate/attach | +| State | `DEPLOYED` + runtime `STANDBY` → Kit paused; `RUNNING` → running | +| Errors | Name collision → AlreadyExists; coded deletion-in-progress → Conflict | | Files | Filesystem methods — [Filesystem](/docs/filesystem) | | Process | Blocking `run` — [Run a command](/docs/process) | @@ -176,9 +178,12 @@ memory, so CPU and disk overrides are rejected. Default port transport leaves the protocol to Blaxel (HTTP); plain maps to TCP; TLS maps to TLS; HTTP/2 over TLS is rejected. Client `timeout` is the Kit create/operation deadline. `@blaxel/core`'s native creation timeout (1–50s) is not forwarded; the Go SDK -has no equivalent. `provisioning.waitFor` accepts only `Default` and -`Scheduled`; `Ready` (and `Started` / `Submitted`) are rejected. Readiness -probes are unsupported. Explicit idle-terminate disable is rejected. +has no equivalent. Create never passes `@blaxel/core` `SandboxCreateOptions`. +`provisioning.waitFor` accepts only `Default` and `Scheduled`; `Ready` (and +`Started` / `Submitted`) are rejected. Readiness probes are unsupported. +Explicit idle-terminate disable is rejected. TypeScript projects runtime +standby from `@blaxel/core`'s private wire model onto a Kit-facing `state` +field (no public `state` getter). Go reads `Sandbox.State` on the instance. ## Related diff --git a/website/content/docs/errors.mdx b/website/content/docs/errors.mdx index 242c019d..d44c32ac 100644 --- a/website/content/docs/errors.mdx +++ b/website/content/docs/errors.mdx @@ -300,7 +300,10 @@ See [Examples](/docs/examples). -`get` before retry so you know whether the sandbox exists (lookup by name). +A create that collides on name returns `AlreadyExists` +(`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409). Coded conflicts such as +`SANDBOX_DELETION_IN_PROGRESS` stay `Conflict`. Use `get` before retry so you +know whether the sandbox exists (lookup by name). diff --git a/website/content/docs/filesystem.mdx b/website/content/docs/filesystem.mdx index f3ff5d19..54f9fc18 100644 --- a/website/content/docs/filesystem.mdx +++ b/website/content/docs/filesystem.mdx @@ -347,6 +347,14 @@ fmt.Println(entries) A symlink result describes the symlink when Modal reports one. + + Blaxel has no dedicated stat API. TypeScript uses `fs.ls` (GET-by-path): a + directory listing succeeds; a file path returns Blaxel's "Directory not + found" response, then Kit lists the parent. Go calls the sandbox filesystem + GET through a rebuilt lower client because `SandboxInstanceFSService` has no + public Get/Stat and `LS` always treats the response as a directory. + + diff --git a/website/content/docs/lifecycle.mdx b/website/content/docs/lifecycle.mdx index efe98c57..a2a6d796 100644 --- a/website/content/docs/lifecycle.mdx +++ b/website/content/docs/lifecycle.mdx @@ -127,7 +127,7 @@ if err != nil { -`get` loads a sandbox that already exists by **name** (Blaxel id is the name). `refresh` loads it again. `attach` is not available. +`get` loads a sandbox that already exists by **name** (Blaxel id is the name). `refresh` loads it again. `attach` is not available. `DEPLOYED` with runtime `STANDBY` maps to Kit paused; `RUNNING` maps to running. From 950cecb51a5760c59f37c0c36876a84ee1449cd0 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 12:11:22 +0530 Subject: [PATCH 33/60] Align Blaxel with the unified client for portable create/delete/errors. Scope bare 409 AlreadyExists to create, return real delete state with wait polling, and close Go/TS gaps that broke kind- and field-based app code. Co-authored-by: Cursor --- docs/responses-and-errors.md | 6 +- docs/sandbox-lifecycle.md | 34 ++++---- sdks/go/providers/blaxel/README.md | 13 ++-- sdks/go/providers/blaxel/creation.go | 2 +- sdks/go/providers/blaxel/errors.go | 20 ++++- sdks/go/providers/blaxel/errors_test.go | 16 ++++ sdks/go/providers/blaxel/filesystem.go | 39 +++++++--- sdks/go/providers/blaxel/lifecycle.go | 53 ++++++++++--- sdks/typescript/providers/blaxel/README.md | 19 +++-- .../providers/blaxel/src/blaxel.test.ts | 61 +++++++++------ .../typescript/providers/blaxel/src/blaxel.ts | 77 +++++++++++++++---- .../typescript/providers/blaxel/src/errors.ts | 28 +++++-- .../providers/blaxel/src/mappings/mappings.ts | 13 ++-- .../blaxel/src/mappings/provider_mappings.ts | 12 ++- .../typescript/providers/blaxel/src/native.ts | 10 ++- website/content/docs/blaxel.mdx | 20 ++--- website/content/docs/errors.mdx | 3 +- website/content/docs/lifecycle.mdx | 2 +- 18 files changed, 301 insertions(+), 127 deletions(-) diff --git a/docs/responses-and-errors.md b/docs/responses-and-errors.md index e02c952f..268f41db 100644 --- a/docs/responses-and-errors.md +++ b/docs/responses-and-errors.md @@ -110,8 +110,10 @@ cause. A kind is a category. It is not a signal that retrying is safe, and Kit does not retry for you. Blaxel name collisions map to `AlreadyExists` when the provider sends -`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409. Provider codes still win when -present, so `SANDBOX_DELETION_IN_PROGRESS` stays `Conflict`. +`SANDBOX_ALREADY_EXISTS`, or a bare HTTP 409 on **create**. A bare 409 on +other operations maps to `Conflict` (same status fallback Daytona uses). +Provider codes still win when present, so `SANDBOX_DELETION_IN_PROGRESS` +stays `Conflict`. ## After a failure diff --git a/docs/sandbox-lifecycle.md b/docs/sandbox-lifecycle.md index 388637a0..cbe6db59 100644 --- a/docs/sandbox-lifecycle.md +++ b/docs/sandbox-lifecycle.md @@ -86,16 +86,16 @@ canceled before it starts never reaches the provider. ## Provider support -| Operation | Modal | Daytona | Blaxel | -| ----------- | -------------------------------------- | ----------- | ---------------------------------------- | -| Attach | Supported; ID only, no provider lookup | Unsupported | Unsupported | -| Refresh/Get | Unsupported | Supported | Supported (lookup by sandbox name) | -| Start | Unsupported | Supported | Unsupported | -| Stop | Unsupported | Supported | Unsupported | -| Pause | Unsupported | Supported | Unsupported | -| Terminate | Supported | Unsupported | Unsupported | -| Archive | Unsupported | Supported | Unsupported | -| Delete | Unsupported | Supported | Supported | +| Operation | Modal | Daytona | Blaxel | +| ----------- | -------------------------------------- | ----------- | ---------------------------------- | +| Attach | Supported; ID only, no provider lookup | Unsupported | Unsupported | +| Refresh/Get | Unsupported | Supported | Supported (lookup by sandbox name) | +| Start | Unsupported | Supported | Unsupported | +| Stop | Unsupported | Supported | Unsupported | +| Pause | Unsupported | Supported | Unsupported | +| Terminate | Supported | Unsupported | Unsupported | +| Archive | Unsupported | Supported | Unsupported | +| Delete | Unsupported | Supported | Supported | Daytona reports its resolved sandbox class after creation. Kit maps container and Linux VM to shared isolation kinds and preserves Windows as a provider-specific @@ -109,12 +109,14 @@ Modal gVisor and Linux VM sandboxes share Modal's terminal execution lifecycle. exposes it as `Client.Attach`; Modal does not advertise `Get` or `Refresh`. Blaxel looks up sandboxes by name for `Get` and `Refresh`. `Delete` removes the -Blaxel resource. `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and `Terminate` -are unsupported. Closing a Blaxel handle does not contact Blaxel. On -`Get` / `Refresh`, Blaxel deployment `DEPLOYED` with runtime `STANDBY` maps to -Kit paused, and `RUNNING` maps to running. Go uses the SDK's public -`Sandbox.State`. TypeScript projects runtime state from `@blaxel/core`'s -private wire model onto a Kit-facing `state` field (no public `state` getter). +Blaxel resource and returns the DELETE response state (often `Deleting`). Set +`wait: true` to poll until the name is gone or reports `Deleted`. `Attach`, +`Start`, `Stop`, `Pause`, `Archive`, and `Terminate` are unsupported. Closing a +Blaxel handle does not contact Blaxel. On `Get` / `Refresh`, Blaxel deployment +`DEPLOYED` with runtime `STANDBY` maps to Kit paused, and `RUNNING` maps to +running. Go uses the SDK's public `Sandbox.State`. TypeScript projects runtime +state from `@blaxel/core`'s private wire model onto a Kit-facing `state` field +(no public `state` getter). File calls on the same handle are documented in [filesystem](filesystem.md). Daytona, Modal, and Blaxel implement the same eight methods; each handle diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index cf70346e..faf5453b 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -77,11 +77,14 @@ the native create path (not create-if-not-exists). On the handle: `Get`, `Refresh`, `Delete`, and `Close`. `Get` and `Refresh` look up the sandbox by name (`ID()` equals the create name). `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and `Terminate` are not supported. `Close` does not -contact Blaxel and does not change the sandbox. `Refresh` / `Info` map Blaxel -deployment `DEPLOYED` plus runtime `STANDBY` to Kit `Paused` (and `RUNNING` to -`Running`) via the Go SDK's public `Sandbox.State`. A create that collides on -name maps to `AlreadyExists` (`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409). -Coded conflicts such as `SANDBOX_DELETION_IN_PROGRESS` stay `Conflict`. +contact Blaxel and does not change the sandbox. `Delete` returns Blaxel's DELETE +response state (often `Deleting`); `wait: true` polls until the name is gone or +`Deleted`. `Refresh` / `Info` map Blaxel deployment `DEPLOYED` plus runtime +`STANDBY` to Kit `Paused` (and `RUNNING` to `Running`) via the Go SDK's public +`Sandbox.State`. A create that collides on name maps to `AlreadyExists` +(`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409 on create). A bare 409 on other +operations is `Conflict`. Coded conflicts such as +`SANDBOX_DELETION_IN_PROGRESS` stay `Conflict`. `Filesystem` supports the shared file calls: `ReadFile`, `WriteFile`, `CopyFromLocal`, `CopyToLocal`, `MakeDirectory`, `ListDirectory`, `Stat`, and diff --git a/sdks/go/providers/blaxel/creation.go b/sdks/go/providers/blaxel/creation.go index e9036cee..3fb5de27 100644 --- a/sdks/go/providers/blaxel/creation.go +++ b/sdks/go/providers/blaxel/creation.go @@ -153,7 +153,7 @@ func mapIdleTerminate(action *sandbox.AutomaticAction) (*blaxel.ExpirationPolicy Value: blaxel.String(formatted), }, nil default: - return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminate, "unknown policy mode") + return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminateMode, "unknown policy mode") } } diff --git a/sdks/go/providers/blaxel/errors.go b/sdks/go/providers/blaxel/errors.go index 7e489ccb..15e43778 100644 --- a/sdks/go/providers/blaxel/errors.go +++ b/sdks/go/providers/blaxel/errors.go @@ -3,6 +3,7 @@ package blaxel import ( "context" "errors" + "net/http" sdkblaxel "github.com/blaxel-ai/sdk-go" "github.com/sandbox-kit/kit/sdks/go/internal/sdk" @@ -78,9 +79,9 @@ var providerStatusKinds = map[int]sandbox.ErrorKind{ 404: sandbox.ErrorKindNotFound, 410: sandbox.ErrorKindNotFound, 408: sandbox.ErrorKindTimeout, - // Bare 409 is usually name collision; coded conflicts (e.g. deletion in - // progress) still win through providerCodeKinds above. - 409: sandbox.ErrorKindAlreadyExists, + // Bare 409 is Conflict for non-create ops. Create maps it to AlreadyExists + // in statusKindFor (name collision). Coded conflicts still win above. + 409: sandbox.ErrorKindConflict, 412: sandbox.ErrorKindFailedPrecondition, 429: sandbox.ErrorKindRateLimited, 500: sandbox.ErrorKindInternal, @@ -89,6 +90,17 @@ var providerStatusKinds = map[int]sandbox.ErrorKind{ 504: sandbox.ErrorKindTimeout, } +// statusKindFor maps an HTTP status. Bare 409 on create is treated as a name +// collision (AlreadyExists); other operations keep Conflict so portable code +// that branches on kind matches Daytona's conflict semantics outside create. +func statusKindFor(status int, operation string) (sandbox.ErrorKind, bool) { + if status == http.StatusConflict && operation == "create" { + return sandbox.ErrorKindAlreadyExists, true + } + kind, ok := providerStatusKinds[status] + return kind, ok +} + func mapProviderError(err error, operation string) error { if err == nil { return nil @@ -125,7 +137,7 @@ func mapProviderError(err error, operation string) error { if kind == sandbox.ErrorKindUnknown { if mapped, ok := providerCodeKinds[native.ErrorCode]; ok { kind = mapped - } else if mapped, ok := providerStatusKinds[native.StatusCode]; ok { + } else if mapped, ok := statusKindFor(native.StatusCode, operation); ok { kind = mapped } } diff --git a/sdks/go/providers/blaxel/errors_test.go b/sdks/go/providers/blaxel/errors_test.go index cf8aafa0..f06cbc90 100644 --- a/sdks/go/providers/blaxel/errors_test.go +++ b/sdks/go/providers/blaxel/errors_test.go @@ -69,6 +69,22 @@ func TestMapProviderErrorBareConflictIsAlreadyExists(t *testing.T) { } } +func TestMapProviderErrorBareConflictOnDeleteIsConflict(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes/demo") + if err != nil { + t.Fatal(err) + } + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusConflict, + Request: &http.Request{Method: http.MethodDelete, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusConflict}, + }, "delete") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindConflict { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } +} + func TestMapProviderErrorDeletionInProgressStaysConflict(t *testing.T) { parsed, err := url.Parse("https://example.invalid/sandboxes/demo") if err != nil { diff --git a/sdks/go/providers/blaxel/filesystem.go b/sdks/go/providers/blaxel/filesystem.go index 6c8a4ee6..76244991 100644 --- a/sdks/go/providers/blaxel/filesystem.go +++ b/sdks/go/providers/blaxel/filesystem.go @@ -81,13 +81,7 @@ func (b *backend) ListDirectory(ctx context.Context, id, path string, _ *sandbox return nil, err } result := make([]*sandbox.FileInfo, 0, len(listing.Files)+len(listing.Subdirectories)) - for _, entry := range listing.Files { - info, err := blaxelFileInfo(entry, sandbox.FileTypeFile, "list_directory") - if err != nil { - return nil, err - } - result = append(result, info) - } + // Directories first, then files — matches TypeScript Blaxel listing order. for _, entry := range listing.Subdirectories { result = append(result, &sandbox.FileInfo{ Name: entry.Name, @@ -95,6 +89,13 @@ func (b *backend) ListDirectory(ctx context.Context, id, path string, _ *sandbox Type: sandbox.FileTypeDirectory, }) } + for _, entry := range listing.Files { + info, err := blaxelFileInfo(entry, sandbox.FileTypeFile, "list_directory") + if err != nil { + return nil, err + } + result = append(result, info) + } return result, nil }) } @@ -111,7 +112,7 @@ func (b *backend) Stat(ctx context.Context, id, path string, _ *sandbox.StatOpti fs := b.filesystemService(instance) res, err := fs.Get(ctx, path, blaxel.SandboxFilesystemGetParams{}) if err != nil { - return nil, mapProviderError(err, "stat") + return nil, mapFilesystemError(err, "stat") } if res == nil { return nil, invalidResponse("stat", sandbox.FileFieldPath, "SDK returned no file metadata") @@ -168,11 +169,31 @@ func withFS[T any]( if errors.As(err, &local) { return zero, err } - return zero, mapProviderError(err, call.Operation) + return zero, mapFilesystemError(err, call.Operation) } return result, nil } +// mapFilesystemError attaches Field=path on NotFound so portable callers can +// branch the same way as TypeScript Blaxel Stat/list failures. +func mapFilesystemError(err error, operation string) error { + mapped := mapProviderError(err, operation) + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindNotFound || detail.Field != "" { + return mapped + } + return sandbox.NewError(&sandbox.ErrorInfo{ + Kind: detail.Kind, + Provider: detail.Provider, + Operation: detail.Operation, + Field: sandbox.FileFieldPath, + Message: detail.Message, + StatusCode: detail.StatusCode, + ProviderCode: detail.ProviderCode, + ProviderSource: detail.ProviderSource, + }, err) +} + func (b *backend) fileSandbox(ctx context.Context, id, operation string) (*blaxel.SandboxInstance, error) { instance, err := b.getInstance(ctx, id, operation) if err != nil { diff --git a/sdks/go/providers/blaxel/lifecycle.go b/sdks/go/providers/blaxel/lifecycle.go index 0eb3bc2f..dc3e9d90 100644 --- a/sdks/go/providers/blaxel/lifecycle.go +++ b/sdks/go/providers/blaxel/lifecycle.go @@ -2,6 +2,8 @@ package blaxel import ( "context" + "errors" + "time" blaxel "github.com/blaxel-ai/sdk-go" "github.com/sandbox-kit/kit/sdks/go/sandbox" @@ -9,12 +11,16 @@ import ( var _ sandbox.SandboxDeleter = (*backend)(nil) +const deletePollInterval = time.Second + // DeleteSandbox permanently deletes a Blaxel sandbox by name. // Start, stop, pause, archive, and terminate are not implemented. -func (b *backend) DeleteSandbox(ctx context.Context, id string, _ *sandbox.DeleteOptions) (*sandbox.SandboxInfo, error) { - instance, err := b.getInstance(ctx, id, "delete") +// The returned info mirrors Blaxel's DELETE response (often Deleting). When +// Wait is true, Kit polls Get until the name is gone or the context ends. +func (b *backend) DeleteSandbox(ctx context.Context, id string, options *sandbox.DeleteOptions) (*sandbox.SandboxInfo, error) { + instance, err := b.client.Sandboxes.DeleteInstance(ctx, id) if err != nil { - return nil, err + return nil, mapProviderError(err, "delete") } native, err := requireSandbox(instance, "delete") if err != nil { @@ -24,11 +30,40 @@ func (b *backend) DeleteSandbox(ctx context.Context, id string, _ *sandbox.Delet if err != nil { return nil, err } - if err := instance.Delete(ctx); err != nil { - return nil, mapProviderError(err, "delete") + if options == nil || !options.GetWait() { + return info, nil + } + return b.waitUntilDeleted(ctx, id, info) +} + +func (b *backend) waitUntilDeleted(ctx context.Context, id string, last *sandbox.SandboxInfo) (*sandbox.SandboxInfo, error) { + if last != nil && last.State == sandbox.SandboxStateDeleted { + return last, nil + } + ticker := time.NewTicker(deletePollInterval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return nil, mapProviderError(ctx.Err(), "delete") + case <-ticker.C: + info, err := b.RefreshSandbox(ctx, id) + if err != nil { + var detail *sandbox.Error + if errors.As(err, &detail) && detail.Kind == sandbox.ErrorKindNotFound { + return &sandbox.SandboxInfo{ + Id: id, + Provider: "blaxel", + State: sandbox.SandboxStateDeleted, + Terminal: sandbox.Value(true), + ProviderState: sandbox.Value(string(blaxel.StatusTerminated)), + }, nil + } + return nil, err + } + if info.State == sandbox.SandboxStateDeleted { + return info, nil + } + } } - info.State = sandbox.SandboxStateDeleted - info.Terminal = sandbox.Value(true) - info.ProviderState = sandbox.Value(string(blaxel.StatusTerminated)) - return info, nil } diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index 3207a3a0..6106b139 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -68,14 +68,17 @@ TLS is rejected. Kit has no portable volume fields; Blaxel volumes are outside create. **Lifecycle** on the handle: `get` / `refresh` (lookup by name), `delete`, and -`close`. `close` does not delete the sandbox. Start, stop, pause, archive, -terminate, and attach are unsupported. `refresh` / `info` map Blaxel -deployment `DEPLOYED` plus runtime `STANDBY` to Kit `paused` (and `RUNNING` to -`running`). `@blaxel/core` types `SandboxInstance.sandbox` private and has no -`state` getter; the provider projects wire runtime state onto a Kit-facing -`state` field at the native boundary. A create that collides on name maps to -`AlreadyExists` (`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409). Coded conflicts -such as `SANDBOX_DELETION_IN_PROGRESS` stay `Conflict`. +`close`. `close` does not delete the sandbox. `delete` returns Blaxel's DELETE +response state (often `deleting`); `wait: true` polls until the name is gone or +`deleted`. Start, stop, pause, archive, terminate, and attach are unsupported. +`refresh` / `info` map Blaxel deployment `DEPLOYED` plus runtime `STANDBY` to +Kit `paused` (and `RUNNING` to `running`). `@blaxel/core` types +`SandboxInstance.sandbox` private and has no `state` getter; the provider +projects wire runtime state onto a Kit-facing `state` field at the native +boundary. A create that collides on name maps to `AlreadyExists` +(`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409 on create). A bare 409 on other +operations is `Conflict`. Coded conflicts such as +`SANDBOX_DELETION_IN_PROGRESS` stay `Conflict`. **Files** through `sandbox.filesystem`: `readFile`, `writeFile`, `copyFromLocal`, `copyToLocal`, `makeDirectory`, `listDirectory`, `stat`, and diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 106fbfa9..d1da13d2 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -336,6 +336,21 @@ describe("Blaxel mappings", () => { }); }); + it("classifies bare HTTP 409 outside create as conflict", () => { + const native = new ResponseError( + new Response(null, { status: 409, statusText: "Conflict" }), + { error: { message: "conflict" } }, + new Error("conflict"), + ); + expect(mapBlaxelError(native, "delete")).toMatchObject({ + kind: ErrorKind.Conflict, + provider: "blaxel", + operation: "delete", + statusCode: 409, + cause: native, + }); + }); + it("keeps SANDBOX_DELETION_IN_PROGRESS as conflict", () => { const native = new ResponseError( new Response(null, { status: 409, statusText: "Conflict" }), @@ -586,7 +601,9 @@ describe("Blaxel provider orchestration", () => { async wait() { return this; }, - async delete() {}, + async delete() { + return { metadata: { name: "example" }, status: "DELETED" }; + }, }; const client = new Client({ provider: new BlaxelProvider(() => ({ @@ -647,7 +664,9 @@ describe("Blaxel provider orchestration", () => { async wait() { return this; }, - async delete() {}, + async delete() { + return { metadata: { name: "unused" }, status: "DELETED" }; + }, }; }, async delete() { @@ -663,29 +682,19 @@ describe("Blaxel provider orchestration", () => { expect(info).toMatchObject({ id: "existing", provider: "blaxel" }); }); - it("deletes by name after a provider-verified lookup", async () => { + it("maps delete from the Blaxel DELETE response state", async () => { const deleted: string[] = []; const backend = new BlaxelProvider(() => ({ configure() {}, async create() { throw new Error("unexpected create"); }, - async get(name) { - return { - metadata: { name }, - status: "RUNNING", - fs: {} as never, - process: {} as never, - async wait() { - return this; - }, - async delete() { - deleted.push(name); - }, - }; + async get() { + throw new Error("unexpected get"); }, async delete(name) { - deleted.push(`static:${name}`); + deleted.push(name); + return { metadata: { name }, status: "DELETING" }; }, })).initialize({ auth: { apiKey: { key: "bl_key" } }, @@ -696,10 +705,10 @@ describe("Blaxel provider orchestration", () => { expect(deleted).toEqual(["doomed"]); expect(info).toMatchObject({ id: "doomed", - state: SandboxState.Deleted, - terminal: true, - providerState: "TERMINATED", + state: SandboxState.Deleting, + providerState: "DELETING", }); + expect(info.terminal).toBeUndefined(); }); it("does not create a sandbox after the signal aborts during mapping", async () => { @@ -877,7 +886,9 @@ describe("Blaxel provider orchestration", () => { async wait() { return this; }, - async delete() {}, + async delete() { + return { metadata: { name: "unused" }, status: "DELETED" }; + }, }; }, async delete() { @@ -1178,7 +1189,9 @@ function fsBackend(fs: { async wait() { return this; }, - async delete() {}, + async delete() { + return { metadata: { name: "unused" }, status: "DELETED" }; + }, }; }, async delete() { @@ -1225,7 +1238,9 @@ function runBackend(process: { async wait() { return this; }, - async delete() {}, + async delete() { + return { metadata: { name: "unused" }, status: "DELETED" }; + }, }; }, async delete() { diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index a1fd90f6..f62ba87f 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -113,24 +113,21 @@ class BlaxelBackend extends BackendBase implements Backend { async delete( id: string, - _request: DeleteOptions, + request: DeleteOptions, options: OperationOptions, ): Promise { - return this.#withSandbox({ - operation: "delete", - id, - options, - body: async (sandbox) => { - const info = mapSandboxInfo(sandbox); - await sandbox.delete(); - return { - ...info, - state: SandboxState.Deleted, - terminal: true, - providerState: "TERMINATED", - }; - }, - }); + try { + throwIfAborted(options.signal); + const deleted = await this.native.delete(id); + throwIfAborted(options.signal); + let info = mapSandboxInfo(deleted); + if (request.wait === true) { + info = await this.#waitUntilDeleted(id, info, options.signal); + } + return info; + } catch (error) { + throw mapBlaxelError(error, "delete"); + } } async readFile( @@ -315,6 +312,36 @@ class BlaxelBackend extends BackendBase implements Backend { } } + async #waitUntilDeleted( + id: string, + last: SandboxInfo, + signal: AbortSignal | undefined, + ): Promise { + if (last.state === SandboxState.Deleted) return last; + for (;;) { + throwIfAborted(signal); + await delay(1_000, signal); + try { + const sandbox = await this.native.get(id); + last = mapSandboxInfo(sandbox); + if (last.state === SandboxState.Deleted) return last; + } catch (error) { + const mapped = mapBlaxelError(error, "delete"); + if (mapped.kind === ErrorKind.NotFound) { + return { + id, + provider: "blaxel", + state: SandboxState.Deleted, + terminal: true, + providerState: "TERMINATED", + labels: {}, + }; + } + throw mapped; + } + } + } + async closeSandbox(_id: string, _options: OperationOptions): Promise {} async close(options: OperationOptions): Promise { @@ -326,6 +353,24 @@ function throwIfAborted(signal: AbortSignal | undefined): void { signal?.throwIfAborted(); } +function delay(ms: number, signal: AbortSignal | undefined): Promise { + return new Promise((resolve, reject) => { + if (signal?.aborted) { + reject(signal.reason); + return; + } + const timer = setTimeout(() => { + signal?.removeEventListener("abort", onAbort); + resolve(); + }, ms); + const onAbort = () => { + clearTimeout(timer); + reject(signal?.reason); + }; + signal?.addEventListener("abort", onAbort, { once: true }); + }); +} + /** * Selects Blaxel as the provider for the shared Sandbox Kit client. * diff --git a/sdks/typescript/providers/blaxel/src/errors.ts b/sdks/typescript/providers/blaxel/src/errors.ts index 692f797d..f08cadcc 100644 --- a/sdks/typescript/providers/blaxel/src/errors.ts +++ b/sdks/typescript/providers/blaxel/src/errors.ts @@ -97,9 +97,9 @@ const STATUS_KINDS: Record = { 404: ErrorKind.NotFound, 410: ErrorKind.NotFound, 408: ErrorKind.Timeout, - // Bare 409 is usually name collision; coded conflicts (e.g. deletion in - // progress) still win through CODE_KINDS above. - 409: ErrorKind.AlreadyExists, + // Bare 409 is Conflict for non-create ops. Create maps it in statusKindFor. + // Coded conflicts still win through CODE_KINDS above. + 409: ErrorKind.Conflict, 412: ErrorKind.FailedPrecondition, 429: ErrorKind.RateLimited, 500: ErrorKind.Internal, @@ -124,7 +124,7 @@ export function mapBlaxelError( getBlaxelErrorCode(error) ?? getBlaxelErrorCode(cause), ); return create({ - kind: classifyKind({ error, cause, providerCode, statusCode }), + kind: classifyKind({ error, cause, providerCode, statusCode, operation }), error, operation, ...(statusCode === undefined ? {} : { statusCode }), @@ -137,10 +137,11 @@ interface ClassifyArgs { cause: unknown; providerCode: string | undefined; statusCode: number | undefined; + operation: string; } function classifyKind(args: ClassifyArgs): ErrorInfo["kind"] { - const { error, cause, providerCode, statusCode } = args; + const { error, cause, providerCode, statusCode, operation } = args; if (error instanceof CredentialsError) return ErrorKind.Authentication; if ( error instanceof SandboxCreationTimeoutError || @@ -161,13 +162,24 @@ function classifyKind(args: ClassifyArgs): ErrorInfo["kind"] { // Unknown codes fall through so status can still classify the failure. if (providerCode !== undefined && CODE_KINDS[providerCode] !== undefined) return CODE_KINDS[providerCode]!; - if (statusCode !== undefined && STATUS_KINDS[statusCode] !== undefined) - return STATUS_KINDS[statusCode]!; + if (statusCode !== undefined) { + const mapped = statusKindFor(statusCode, operation); + if (mapped !== undefined) return mapped; + } if (error instanceof ResponseError && error.status !== undefined) - return STATUS_KINDS[error.status] ?? ErrorKind.Unknown; + return statusKindFor(error.status, operation) ?? ErrorKind.Unknown; return ErrorKind.Unknown; } +/** Bare 409 on create → AlreadyExists (name collision); otherwise Conflict. */ +function statusKindFor( + status: number, + operation: string, +): ErrorInfo["kind"] | undefined { + if (status === 409 && operation === "create") return ErrorKind.AlreadyExists; + return STATUS_KINDS[status]; +} + function isProcessWaitTimeout(error: unknown): boolean { return ( error instanceof Error && diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index 39ae36a3..50666400 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -190,12 +190,7 @@ function mapSecurity(r: CreateOptions): void { function mapLifetime(r: CreateOptions, p: SandboxCreateConfiguration): void { const v = r.lifetime; if (!v) return; - if (hasUnsupportedLifetimeFields(v)) - unsupported( - CreateFieldLifetime, - "one or more Blaxel lifetime fields are not mapped", - ); - + // Idle first (matches Go) so idle_terminate errors win over unmapped sibling fields. if (v.maximumLifetime !== undefined) { p.ttl = durationString({ ms: v.maximumLifetime, @@ -208,6 +203,12 @@ function mapLifetime(r: CreateOptions, p: SandboxCreateConfiguration): void { const lifecycle = mapIdleTerminate(v.idleTerminate); if (lifecycle !== undefined) p.lifecycle = lifecycle; } + + if (hasUnsupportedLifetimeFields(v)) + unsupported( + CreateFieldLifetime, + "one or more Blaxel lifetime fields are not mapped", + ); } function mapIdleTerminate( diff --git a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts index 6b9daa9e..e4ac81c3 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts @@ -95,11 +95,15 @@ export function mapCommonCreateFields( const target: Mutable = {}; if (source === undefined) return target; if (source.name !== undefined) target.name = source.name; + // Match Go: omit envs when the map is empty so explicit {} equals omit. if (source.environment !== undefined) { - target.envs = Object.entries(source.environment).map(([name, value]) => ({ - name, - value: String(value), - })); + const entries = Object.entries(source.environment); + if (entries.length > 0) { + target.envs = entries.map(([name, value]) => ({ + name, + value: String(value), + })); + } } if (source.labels !== undefined) target.labels = { ...source.labels }; return target; diff --git a/sdks/typescript/providers/blaxel/src/native.ts b/sdks/typescript/providers/blaxel/src/native.ts index 5ae6c129..462eb0c1 100644 --- a/sdks/typescript/providers/blaxel/src/native.ts +++ b/sdks/typescript/providers/blaxel/src/native.ts @@ -45,7 +45,8 @@ export interface NativeSandboxHandle extends BlaxelSandboxIdentity { }, ): Promise; }; - delete(): Promise; + /** Returns the DELETE response body (often still DELETING). */ + delete(): Promise; } export interface NativeBlaxel { @@ -55,7 +56,8 @@ export interface NativeBlaxel { options?: SandboxCreateOptions, ): Promise; get(name: string): Promise; - delete(name: string): Promise; + /** Returns the DELETE response body (often still DELETING). */ + delete(name: string): Promise; } export type NativeFactory = (config: NativeClientConfig) => NativeBlaxel; @@ -72,8 +74,8 @@ export function defaultNativeFactory(config: NativeClientConfig): NativeBlaxel { async get(name) { return projectHandle(await SandboxInstance.get(name)); }, - delete(name) { - return SandboxInstance.delete(name); + async delete(name) { + return (await SandboxInstance.delete(name)) as BlaxelSandboxIdentity; }, }; } diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index b2e480ff..fed64d1c 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -161,16 +161,16 @@ Token-pair, bearer, and OAuth client modes are rejected. Scope fields other than ## What works -| Area | Notes | -| --------- | ------------------------------------------------------------------------------ | -| Create | Valid name + image; memory 1:1, labels, env, ports, outbound domains, TTL/idle | -| Volumes | Not in Kit create; no portable volume fields | -| Open | `get` / `refresh` by sandbox name | -| Lifecycle | `delete`, `close` — no start/stop/pause/archive/terminate/attach | -| State | `DEPLOYED` + runtime `STANDBY` → Kit paused; `RUNNING` → running | -| Errors | Name collision → AlreadyExists; coded deletion-in-progress → Conflict | -| Files | Filesystem methods — [Filesystem](/docs/filesystem) | -| Process | Blocking `run` — [Run a command](/docs/process) | +| Area | Notes | +| --------- | ------------------------------------------------------------------------------------------------- | +| Create | Valid name + image; memory 1:1, labels, env, ports, outbound domains, TTL/idle | +| Volumes | Not in Kit create; no portable volume fields | +| Open | `get` / `refresh` by sandbox name | +| Lifecycle | `delete`, `close` — no start/stop/pause/archive/terminate/attach | +| State | `DEPLOYED` + runtime `STANDBY` → Kit paused; `RUNNING` → running | +| Errors | Create name collision → AlreadyExists; bare 409 elsewhere / coded deletion-in-progress → Conflict | +| Files | Filesystem methods — [Filesystem](/docs/filesystem) | +| Process | Blocking `run` — [Run a command](/docs/process) | `resources.memoryMib` / `memory_mib` maps 1:1 onto Blaxel's memory field. Blaxel labels that integer MB; Kit does not convert. Blaxel derives CPU from diff --git a/website/content/docs/errors.mdx b/website/content/docs/errors.mdx index d44c32ac..fee5f688 100644 --- a/website/content/docs/errors.mdx +++ b/website/content/docs/errors.mdx @@ -301,7 +301,8 @@ See [Examples](/docs/examples). A create that collides on name returns `AlreadyExists` -(`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409). Coded conflicts such as +(`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409 on create). A bare 409 on other +operations is `Conflict`. Coded conflicts such as `SANDBOX_DELETION_IN_PROGRESS` stay `Conflict`. Use `get` before retry so you know whether the sandbox exists (lookup by name). diff --git a/website/content/docs/lifecycle.mdx b/website/content/docs/lifecycle.mdx index a2a6d796..a8ee6758 100644 --- a/website/content/docs/lifecycle.mdx +++ b/website/content/docs/lifecycle.mdx @@ -127,7 +127,7 @@ if err != nil { -`get` loads a sandbox that already exists by **name** (Blaxel id is the name). `refresh` loads it again. `attach` is not available. `DEPLOYED` with runtime `STANDBY` maps to Kit paused; `RUNNING` maps to running. +`get` loads a sandbox that already exists by **name** (Blaxel id is the name). `refresh` loads it again. `attach` is not available. `delete` returns Blaxel's DELETE response state (often deleting); set `wait: true` to poll until gone or deleted. `DEPLOYED` with runtime `STANDBY` maps to Kit paused; `RUNNING` maps to running. From 5a9f2f58b760936c52fd797ffa6d8dca5181b726 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 12:28:33 +0530 Subject: [PATCH 34/60] Simplify Blaxel delete to a unified Deleted outcome. Drop wait polling; a successful DELETE is reported as Deleted even when Blaxel finishes removal asynchronously. Co-authored-by: Cursor --- docs/sandbox-lifecycle.md | 15 +++-- sdks/go/providers/blaxel/README.md | 8 +-- sdks/go/providers/blaxel/lifecycle.go | 50 +++------------ sdks/typescript/providers/blaxel/README.md | 19 +++--- .../providers/blaxel/src/blaxel.test.ts | 9 +-- .../typescript/providers/blaxel/src/blaxel.ts | 62 +++---------------- .../typescript/providers/blaxel/src/native.ts | 2 - website/content/docs/lifecycle.mdx | 2 +- 8 files changed, 41 insertions(+), 126 deletions(-) diff --git a/docs/sandbox-lifecycle.md b/docs/sandbox-lifecycle.md index cbe6db59..0771f172 100644 --- a/docs/sandbox-lifecycle.md +++ b/docs/sandbox-lifecycle.md @@ -109,14 +109,13 @@ Modal gVisor and Linux VM sandboxes share Modal's terminal execution lifecycle. exposes it as `Client.Attach`; Modal does not advertise `Get` or `Refresh`. Blaxel looks up sandboxes by name for `Get` and `Refresh`. `Delete` removes the -Blaxel resource and returns the DELETE response state (often `Deleting`). Set -`wait: true` to poll until the name is gone or reports `Deleted`. `Attach`, -`Start`, `Stop`, `Pause`, `Archive`, and `Terminate` are unsupported. Closing a -Blaxel handle does not contact Blaxel. On `Get` / `Refresh`, Blaxel deployment -`DEPLOYED` with runtime `STANDBY` maps to Kit paused, and `RUNNING` maps to -running. Go uses the SDK's public `Sandbox.State`. TypeScript projects runtime -state from `@blaxel/core`'s private wire model onto a Kit-facing `state` field -(no public `state` getter). +Blaxel resource; a successful call is reported as `Deleted` even if Blaxel +finishes the removal asynchronously. `Attach`, `Start`, `Stop`, `Pause`, +`Archive`, and `Terminate` are unsupported. Closing a Blaxel handle does not +contact Blaxel. On `Get` / `Refresh`, Blaxel deployment `DEPLOYED` with runtime +`STANDBY` maps to Kit paused, and `RUNNING` maps to running. Go uses the SDK's +public `Sandbox.State`. TypeScript projects runtime state from `@blaxel/core`'s +private wire model onto a Kit-facing `state` field (no public `state` getter). File calls on the same handle are documented in [filesystem](filesystem.md). Daytona, Modal, and Blaxel implement the same eight methods; each handle diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index faf5453b..6417dad8 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -77,10 +77,10 @@ the native create path (not create-if-not-exists). On the handle: `Get`, `Refresh`, `Delete`, and `Close`. `Get` and `Refresh` look up the sandbox by name (`ID()` equals the create name). `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and `Terminate` are not supported. `Close` does not -contact Blaxel and does not change the sandbox. `Delete` returns Blaxel's DELETE -response state (often `Deleting`); `wait: true` polls until the name is gone or -`Deleted`. `Refresh` / `Info` map Blaxel deployment `DEPLOYED` plus runtime -`STANDBY` to Kit `Paused` (and `RUNNING` to `Running`) via the Go SDK's public +contact Blaxel and does not change the sandbox. A successful `Delete` is +reported as `Deleted` (Blaxel may still finish removal asynchronously). +`Refresh` / `Info` map Blaxel deployment `DEPLOYED` plus runtime `STANDBY` to +Kit `Paused` (and `RUNNING` to `Running`) via the Go SDK's public `Sandbox.State`. A create that collides on name maps to `AlreadyExists` (`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409 on create). A bare 409 on other operations is `Conflict`. Coded conflicts such as diff --git a/sdks/go/providers/blaxel/lifecycle.go b/sdks/go/providers/blaxel/lifecycle.go index dc3e9d90..eb665fcd 100644 --- a/sdks/go/providers/blaxel/lifecycle.go +++ b/sdks/go/providers/blaxel/lifecycle.go @@ -2,8 +2,6 @@ package blaxel import ( "context" - "errors" - "time" blaxel "github.com/blaxel-ai/sdk-go" "github.com/sandbox-kit/kit/sdks/go/sandbox" @@ -11,13 +9,11 @@ import ( var _ sandbox.SandboxDeleter = (*backend)(nil) -const deletePollInterval = time.Second - // DeleteSandbox permanently deletes a Blaxel sandbox by name. // Start, stop, pause, archive, and terminate are not implemented. -// The returned info mirrors Blaxel's DELETE response (often Deleting). When -// Wait is true, Kit polls Get until the name is gone or the context ends. -func (b *backend) DeleteSandbox(ctx context.Context, id string, options *sandbox.DeleteOptions) (*sandbox.SandboxInfo, error) { +// Blaxel's DELETE may still finish asynchronously; a successful call is +// reported as Deleted so portable callers get the same terminal outcome. +func (b *backend) DeleteSandbox(ctx context.Context, id string, _ *sandbox.DeleteOptions) (*sandbox.SandboxInfo, error) { instance, err := b.client.Sandboxes.DeleteInstance(ctx, id) if err != nil { return nil, mapProviderError(err, "delete") @@ -30,40 +26,8 @@ func (b *backend) DeleteSandbox(ctx context.Context, id string, options *sandbox if err != nil { return nil, err } - if options == nil || !options.GetWait() { - return info, nil - } - return b.waitUntilDeleted(ctx, id, info) -} - -func (b *backend) waitUntilDeleted(ctx context.Context, id string, last *sandbox.SandboxInfo) (*sandbox.SandboxInfo, error) { - if last != nil && last.State == sandbox.SandboxStateDeleted { - return last, nil - } - ticker := time.NewTicker(deletePollInterval) - defer ticker.Stop() - for { - select { - case <-ctx.Done(): - return nil, mapProviderError(ctx.Err(), "delete") - case <-ticker.C: - info, err := b.RefreshSandbox(ctx, id) - if err != nil { - var detail *sandbox.Error - if errors.As(err, &detail) && detail.Kind == sandbox.ErrorKindNotFound { - return &sandbox.SandboxInfo{ - Id: id, - Provider: "blaxel", - State: sandbox.SandboxStateDeleted, - Terminal: sandbox.Value(true), - ProviderState: sandbox.Value(string(blaxel.StatusTerminated)), - }, nil - } - return nil, err - } - if info.State == sandbox.SandboxStateDeleted { - return info, nil - } - } - } + info.State = sandbox.SandboxStateDeleted + info.Terminal = sandbox.Value(true) + info.ProviderState = sandbox.Value(string(blaxel.StatusTerminated)) + return info, nil } diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index 6106b139..d9bcdfed 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -68,16 +68,15 @@ TLS is rejected. Kit has no portable volume fields; Blaxel volumes are outside create. **Lifecycle** on the handle: `get` / `refresh` (lookup by name), `delete`, and -`close`. `close` does not delete the sandbox. `delete` returns Blaxel's DELETE -response state (often `deleting`); `wait: true` polls until the name is gone or -`deleted`. Start, stop, pause, archive, terminate, and attach are unsupported. -`refresh` / `info` map Blaxel deployment `DEPLOYED` plus runtime `STANDBY` to -Kit `paused` (and `RUNNING` to `running`). `@blaxel/core` types -`SandboxInstance.sandbox` private and has no `state` getter; the provider -projects wire runtime state onto a Kit-facing `state` field at the native -boundary. A create that collides on name maps to `AlreadyExists` -(`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409 on create). A bare 409 on other -operations is `Conflict`. Coded conflicts such as +`close`. `close` does not delete the sandbox. A successful `delete` is reported +as `deleted` (Blaxel may still finish removal asynchronously). Start, stop, +pause, archive, terminate, and attach are unsupported. `refresh` / `info` map +Blaxel deployment `DEPLOYED` plus runtime `STANDBY` to Kit `paused` (and +`RUNNING` to `running`). `@blaxel/core` types `SandboxInstance.sandbox` private +and has no `state` getter; the provider projects wire runtime state onto a +Kit-facing `state` field at the native boundary. A create that collides on name +maps to `AlreadyExists` (`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409 on create). +A bare 409 on other operations is `Conflict`. Coded conflicts such as `SANDBOX_DELETION_IN_PROGRESS` stay `Conflict`. **Files** through `sandbox.filesystem`: `readFile`, `writeFile`, diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index d1da13d2..8af24c35 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -682,7 +682,7 @@ describe("Blaxel provider orchestration", () => { expect(info).toMatchObject({ id: "existing", provider: "blaxel" }); }); - it("maps delete from the Blaxel DELETE response state", async () => { + it("unifies a successful delete as Deleted", async () => { const deleted: string[] = []; const backend = new BlaxelProvider(() => ({ configure() {}, @@ -694,6 +694,7 @@ describe("Blaxel provider orchestration", () => { }, async delete(name) { deleted.push(name); + // Wire may still say DELETING; Kit reports Deleted. return { metadata: { name }, status: "DELETING" }; }, })).initialize({ @@ -705,10 +706,10 @@ describe("Blaxel provider orchestration", () => { expect(deleted).toEqual(["doomed"]); expect(info).toMatchObject({ id: "doomed", - state: SandboxState.Deleting, - providerState: "DELETING", + state: SandboxState.Deleted, + terminal: true, + providerState: "TERMINATED", }); - expect(info.terminal).toBeUndefined(); }); it("does not create a sandbox after the signal aborts during mapping", async () => { diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index f62ba87f..b3b34a61 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -113,18 +113,20 @@ class BlaxelBackend extends BackendBase implements Backend { async delete( id: string, - request: DeleteOptions, + _request: DeleteOptions, options: OperationOptions, ): Promise { try { throwIfAborted(options.signal); const deleted = await this.native.delete(id); throwIfAborted(options.signal); - let info = mapSandboxInfo(deleted); - if (request.wait === true) { - info = await this.#waitUntilDeleted(id, info, options.signal); - } - return info; + // Blaxel DELETE may still finish asynchronously; unify as Deleted. + return { + ...mapSandboxInfo(deleted), + state: SandboxState.Deleted, + terminal: true, + providerState: "TERMINATED", + }; } catch (error) { throw mapBlaxelError(error, "delete"); } @@ -312,36 +314,6 @@ class BlaxelBackend extends BackendBase implements Backend { } } - async #waitUntilDeleted( - id: string, - last: SandboxInfo, - signal: AbortSignal | undefined, - ): Promise { - if (last.state === SandboxState.Deleted) return last; - for (;;) { - throwIfAborted(signal); - await delay(1_000, signal); - try { - const sandbox = await this.native.get(id); - last = mapSandboxInfo(sandbox); - if (last.state === SandboxState.Deleted) return last; - } catch (error) { - const mapped = mapBlaxelError(error, "delete"); - if (mapped.kind === ErrorKind.NotFound) { - return { - id, - provider: "blaxel", - state: SandboxState.Deleted, - terminal: true, - providerState: "TERMINATED", - labels: {}, - }; - } - throw mapped; - } - } - } - async closeSandbox(_id: string, _options: OperationOptions): Promise {} async close(options: OperationOptions): Promise { @@ -353,24 +325,6 @@ function throwIfAborted(signal: AbortSignal | undefined): void { signal?.throwIfAborted(); } -function delay(ms: number, signal: AbortSignal | undefined): Promise { - return new Promise((resolve, reject) => { - if (signal?.aborted) { - reject(signal.reason); - return; - } - const timer = setTimeout(() => { - signal?.removeEventListener("abort", onAbort); - resolve(); - }, ms); - const onAbort = () => { - clearTimeout(timer); - reject(signal?.reason); - }; - signal?.addEventListener("abort", onAbort, { once: true }); - }); -} - /** * Selects Blaxel as the provider for the shared Sandbox Kit client. * diff --git a/sdks/typescript/providers/blaxel/src/native.ts b/sdks/typescript/providers/blaxel/src/native.ts index 462eb0c1..9d1c24d7 100644 --- a/sdks/typescript/providers/blaxel/src/native.ts +++ b/sdks/typescript/providers/blaxel/src/native.ts @@ -45,7 +45,6 @@ export interface NativeSandboxHandle extends BlaxelSandboxIdentity { }, ): Promise; }; - /** Returns the DELETE response body (often still DELETING). */ delete(): Promise; } @@ -56,7 +55,6 @@ export interface NativeBlaxel { options?: SandboxCreateOptions, ): Promise; get(name: string): Promise; - /** Returns the DELETE response body (often still DELETING). */ delete(name: string): Promise; } diff --git a/website/content/docs/lifecycle.mdx b/website/content/docs/lifecycle.mdx index a8ee6758..15e56b47 100644 --- a/website/content/docs/lifecycle.mdx +++ b/website/content/docs/lifecycle.mdx @@ -127,7 +127,7 @@ if err != nil { -`get` loads a sandbox that already exists by **name** (Blaxel id is the name). `refresh` loads it again. `attach` is not available. `delete` returns Blaxel's DELETE response state (often deleting); set `wait: true` to poll until gone or deleted. `DEPLOYED` with runtime `STANDBY` maps to Kit paused; `RUNNING` maps to running. +`get` loads a sandbox that already exists by **name** (Blaxel id is the name). `refresh` loads it again. `attach` is not available. A successful `delete` is reported as deleted (Blaxel may still finish asynchronously). `DEPLOYED` with runtime `STANDBY` maps to Kit paused; `RUNNING` maps to running. From 6548fd7c9db47bb8c12bf8c0cb4ac649577a05cb Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 12:37:07 +0530 Subject: [PATCH 35/60] Simplify Blaxel run and stat to lightweight Kit outcomes. Drop the 60s completion-hold policy for one native process wait, and report file Stat from a single ls path without parent listing. Co-authored-by: Cursor --- docs/filesystem.md | 4 +- docs/process.md | 17 ++-- .../process/run-blaxel-with-timeout/README.md | 6 +- .../process/run-blaxel-with-timeout/README.md | 6 +- sdks/go/providers/blaxel/README.md | 10 +- sdks/go/providers/blaxel/process.go | 67 +++---------- sdks/go/providers/blaxel/process_test.go | 62 +----------- sdks/typescript/providers/blaxel/README.md | 22 ++--- .../providers/blaxel/src/blaxel.test.ts | 94 ++++--------------- .../typescript/providers/blaxel/src/blaxel.ts | 2 - .../providers/blaxel/src/filesystem.ts | 29 +++--- .../providers/blaxel/src/process.ts | 42 +-------- website/content/docs/filesystem.mdx | 4 +- website/content/docs/process.mdx | 5 +- website/content/docs/sdk/process.mdx | 24 ++--- 15 files changed, 98 insertions(+), 296 deletions(-) diff --git a/docs/filesystem.md b/docs/filesystem.md index 50e206ae..379c8710 100644 --- a/docs/filesystem.md +++ b/docs/filesystem.md @@ -216,8 +216,8 @@ entries, err := instance.Filesystem().ListDirectory(ctx, "/tmp/kit-files", nil) ## Read metadata `Stat` returns one path. A symlink result describes the symlink. Blaxel has no -dedicated stat API: TypeScript uses `fs.ls` (directory listing, or parent -listing after Blaxel's file-path "Directory not found" response); Go calls the +dedicated stat API: TypeScript uses `fs.ls` (a directory listing, or a file +when Blaxel returns "Directory not found" for a file path); Go calls the sandbox filesystem GET through a rebuilt lower client because `SandboxInstanceFSService` has no public Get/Stat and `LS` always treats the response as a directory. Modal can diff --git a/docs/process.md b/docs/process.md index 2ce968be..7d380d2d 100644 --- a/docs/process.md +++ b/docs/process.md @@ -75,11 +75,11 @@ provider quirks). Provider quirks: - **Modal** — omit/zero adds no Modal command limit. In TypeScript the client omits `timeoutMs` (sending `0` is rejected by Modal). - **Blaxel** — omit or zero adds no Blaxel process auto-kill. Timeouts must - be whole seconds. A positive timeout of at most 60 seconds uses Blaxel's - blocking completion request. Omit, zero, and longer timeouts start the - command and poll until it ends. The Kit deadline and cancel stop that - wait and leave the remote command running. A positive timeout still sets - Blaxel's process auto-kill. An HTTP 504 during the wait is a timeout error. + be whole seconds. Kit starts without Blaxel's completion hold and uses one + native wait. Omit or zero waits without a Kit budget (context/signal can + still cancel). A positive timeout sets auto-kill and the wait budget. The + Kit deadline and cancel stop that wait and leave the remote command + running. An HTTP 504 during the wait is a timeout error. Cancel behavior: @@ -87,10 +87,9 @@ Cancel behavior: error. The handle stays busy until that provider call returns. - **TypeScript** — abort rejects the `await`. The remote command may keep running. Daytona and Modal do not observe the signal inside exec. Blaxel - polls stop when the signal aborts. A Blaxel run whose timeout is 1–60 - seconds uses a completion request that does not observe the signal. The - handle stays busy until the provider call settles, so the next call on - the same handle does not overlap it. + waits stop when the signal aborts. The handle stays busy until the + provider call settles, so the next call on the same handle does not + overlap it. ```ts const result = await sandbox.process.run({ diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/README.md b/examples/go/blaxel/process/run-blaxel-with-timeout/README.md index 10484231..053c126d 100644 --- a/examples/go/blaxel/process/run-blaxel-with-timeout/README.md +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/README.md @@ -5,9 +5,9 @@ Cap a Blaxel Process.Run with a whole-second timeout. ## What this means Command timeouts must be whole seconds. This run uses 5 seconds, so Blaxel -kills the process at 5 seconds and waits on its completion request. Omit or -zero adds no process auto-kill; Kit polls until the command ends or the call -is canceled. A timeout longer than 60 seconds sets the auto-kill and polls. +sets process auto-kill and Kit uses one native wait with that budget. Omit or +zero adds no process auto-kill and waits until the command ends or the call +is canceled. ## Edge cases diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md b/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md index 0c054906..c8d766d4 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md @@ -5,9 +5,9 @@ Cap a Blaxel Process.Run with a whole-second timeout. ## What this means Command timeouts must be whole seconds. This run uses 5 seconds, so Blaxel -kills the process at 5 seconds and waits on its completion request. Omit or -zero adds no process auto-kill; Kit polls until the command ends or the call -is canceled. A timeout longer than 60 seconds sets the auto-kill and polls. +sets process auto-kill and Kit uses one native wait with that budget. Omit or +zero adds no process auto-kill and waits until the command ends or the call +is canceled. ## Edge cases diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 6417dad8..9e4da2a1 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -101,11 +101,11 @@ directory. `Process().Run` runs one command and waits for the result. Set exactly one of `Argv` or `Shell`. `Argv` is sent as a POSIX-quoted shell string. A non-zero exit code is success for the SDK—read `GetExitCode`. Blaxel returns stdout and -stderr. Omit or zero `Timeout` for no Blaxel process auto-kill; Kit then polls -until the command ends or the context ends. A positive timeout of at most 60 -seconds uses Blaxel's blocking completion request. A longer timeout sets the -process auto-kill and polls. Command timeouts must be whole seconds. Cancel -the context to stop waiting; the remote command may keep running. An HTTP 504 +stderr. Kit starts without Blaxel's completion hold and uses one native +`Process.Wait`. Omit or zero `Timeout` for no Blaxel process auto-kill and an +unlimited wait (context can still cancel). A positive timeout sets auto-kill +and the wait budget. Command timeouts must be whole seconds. Cancel the +context to stop waiting; the remote command may keep running. An HTTP 504 during the wait is a timeout error. `resources.memory_mib` maps 1:1 onto Blaxel's memory field. Blaxel labels that diff --git a/sdks/go/providers/blaxel/process.go b/sdks/go/providers/blaxel/process.go index 4300c5c2..3acd5b65 100644 --- a/sdks/go/providers/blaxel/process.go +++ b/sdks/go/providers/blaxel/process.go @@ -12,11 +12,8 @@ import ( ) const ( - // Docs limit waitForCompletion to 60s; longer holds can be closed by the edge. - blaxelCompletionHold = 60 * time.Second // Process.Wait treats 0 as 1m; only -1 is unlimited. blaxelWaitUnlimited = time.Duration(-1) - processPollInterval = time.Second // Blaxel SDK default poll gap ) var _ sandbox.SandboxProcess = (*backend)(nil) @@ -34,21 +31,15 @@ func (b *backend) RunCommand(ctx context.Context, id string, request *sandbox.Ex if err != nil { return nil, err } - startedAt := time.Now() started, err := instance.Process.New(ctx, body) if err != nil { return nil, mapProviderError(err, "run") } - return finishProcess(ctx, finishProcessArgs{ - Instance: instance, - Started: started, - Timeout: timeout, - Elapsed: time.Since(startedAt), - }) + return finishProcess(ctx, instance, started, timeout) } -// processRequest builds Process.New. Timeout 0 disables Blaxel auto-kill; -// WaitForCompletion is only set for 1–60s. +// processRequest starts without waitForCompletion; Kit waits via Process.Wait. +// Timeout 0 disables Blaxel process auto-kill. func processRequest(request *sandbox.ExecOptions, timeout time.Duration) (blaxel.ProcessRequestParam, error) { command, err := blaxelCommand(request) if err != nil { @@ -57,7 +48,7 @@ func processRequest(request *sandbox.ExecOptions, timeout time.Duration) (blaxel body := blaxel.ProcessRequestParam{ Command: command, Timeout: blaxel.Int(int64(timeout / time.Second)), - WaitForCompletion: blaxel.Bool(completionHold(timeout)), + WaitForCompletion: blaxel.Bool(false), } if request.WorkingDirectory != nil { body.WorkingDir = blaxel.String(*request.WorkingDirectory) @@ -68,58 +59,26 @@ func processRequest(request *sandbox.ExecOptions, timeout time.Duration) (blaxel return body, nil } -type finishProcessArgs struct { - Instance *blaxel.SandboxInstance - Started *blaxel.ProcessResponse - Timeout time.Duration - // Elapsed covers Process.New, including any completion-hold wait. - Elapsed time.Duration -} - -// finishProcess returns a terminal start as-is; otherwise polls with Wait -// (also when waitForCompletion left the process running). Wait retries 504. -func finishProcess(ctx context.Context, args finishProcessArgs) (*sandbox.ExecResult, error) { - if args.Started == nil || processFinished(args.Started) { - return execResult(args.Started) +// finishProcess returns a terminal start as-is; otherwise one native Wait. +func finishProcess(ctx context.Context, instance *blaxel.SandboxInstance, started *blaxel.ProcessResponse, timeout time.Duration) (*sandbox.ExecResult, error) { + if started == nil || processFinished(started) { + return execResult(started) } - identifier := processIdentifier(args.Started) + identifier := processIdentifier(started) if identifier == "" { return nil, invalidResponse("run", "", "SDK returned no process identifier") } - budget := remainingWaitBudget(args.Timeout, args.Elapsed) - if args.Timeout > 0 && budget <= 0 { - return nil, kitError(kitFault{ - Kind: sandbox.ErrorKindTimeout, - Operation: "run", - Field: sandbox.ExecFieldTimeout, - Message: "process did not finish before the timeout", - }) + budget := blaxelWaitUnlimited + if timeout > 0 { + budget = timeout } - finished, err := args.Instance.Process.Wait(ctx, identifier, budget, processPollInterval) + finished, err := instance.Process.Wait(ctx, identifier, budget, 0) if err != nil { return nil, mapProviderError(err, "run") } return execResult(finished) } -func completionHold(timeout time.Duration) bool { - return timeout > 0 && timeout <= blaxelCompletionHold -} - -// remainingWaitBudget is the poll window after Process.New. Omit/zero stays -// unlimited. A fully consumed positive timeout yields 0 (caller times out); -// Blaxel Wait treats 0 as 1m, so finishProcess must not pass that through. -func remainingWaitBudget(timeout, elapsed time.Duration) time.Duration { - if timeout <= 0 { - return blaxelWaitUnlimited - } - remaining := timeout - elapsed - if remaining <= 0 { - return 0 - } - return remaining -} - var terminalProcessStatus = map[blaxel.ProcessResponseStatus]struct{}{ blaxel.ProcessResponseStatusCompleted: {}, blaxel.ProcessResponseStatusFailed: {}, diff --git a/sdks/go/providers/blaxel/process_test.go b/sdks/go/providers/blaxel/process_test.go index dfd48907..370b293c 100644 --- a/sdks/go/providers/blaxel/process_test.go +++ b/sdks/go/providers/blaxel/process_test.go @@ -40,25 +40,7 @@ func TestCommandTimeoutWholeSeconds(t *testing.T) { } } -func TestCompletionHoldAndWaitBudget(t *testing.T) { - if completionHold(0) || completionHold(61*time.Second) || !completionHold(60*time.Second) || !completionHold(time.Second) { - t.Fatal("completion hold boundary") - } - if remainingWaitBudget(0, time.Minute) != blaxelWaitUnlimited { - t.Fatal("unlimited wait budget") - } - if remainingWaitBudget(90*time.Second, 0) != 90*time.Second { - t.Fatal("full wait budget") - } - if remainingWaitBudget(90*time.Second, 30*time.Second) != 60*time.Second { - t.Fatal("remaining wait budget") - } - if remainingWaitBudget(5*time.Second, 5*time.Second) != 0 || remainingWaitBudget(5*time.Second, 6*time.Second) != 0 { - t.Fatal("exhausted wait budget") - } -} - -func TestRunCommandUsesCompletionHoldForShortTimeouts(t *testing.T) { +func TestRunCommandStartsWithoutCompletionHold(t *testing.T) { var posts []map[string]any backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { switch { @@ -88,19 +70,19 @@ func TestRunCommandUsesCompletionHoldForShortTimeouts(t *testing.T) { if len(posts) != 2 { t.Fatalf("posts = %#v", posts) } - if posts[0]["waitForCompletion"] != true || posts[0]["timeout"] != float64(5) || posts[0]["command"] != "'echo' 'hi'" || posts[0]["workingDir"] != "/workspace" { + if posts[0]["waitForCompletion"] != false || posts[0]["timeout"] != float64(5) || posts[0]["command"] != "'echo' 'hi'" || posts[0]["workingDir"] != "/workspace" { t.Fatalf("short post = %#v", posts[0]) } env, _ := posts[0]["env"].(map[string]any) if env["MODE"] != "test" { t.Fatalf("env = %#v", posts[0]["env"]) } - if posts[1]["waitForCompletion"] != true || posts[1]["timeout"] != float64(60) { + if posts[1]["waitForCompletion"] != false || posts[1]["timeout"] != float64(60) { t.Fatalf("60s post = %#v", posts[1]) } } -func TestRunCommandPollsWhenTheCompletionHoldIsUnsafe(t *testing.T) { +func TestRunCommandWaitsWhenStartReturnsRunning(t *testing.T) { var gets atomic.Int32 var posts []map[string]any backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { @@ -129,6 +111,7 @@ func TestRunCommandPollsWhenTheCompletionHoldIsUnsafe(t *testing.T) { {"omit", &sandbox.ExecOptions{Shell: sandbox.Value("echo hi")}, 0}, {"zero", &sandbox.ExecOptions{Argv: []string{"echo", "hi"}, Timeout: sandbox.Duration(0)}, 0}, {"long", &sandbox.ExecOptions{Argv: []string{"echo", "hi"}, Timeout: sandbox.Duration(61 * time.Second)}, 61}, + {"short", &sandbox.ExecOptions{Argv: []string{"echo", "hi"}, Timeout: sandbox.Duration(5 * time.Second)}, 5}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -155,41 +138,6 @@ func TestRunCommandPollsWhenTheCompletionHoldIsUnsafe(t *testing.T) { } } -func TestRunCommandPollsWhenCompletionHoldStillReturnsRunning(t *testing.T) { - var gets atomic.Int32 - backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { - switch { - case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/sbx": - writeJSON(t, w, map[string]any{"metadata": map[string]string{"name": "sbx"}, "spec": map[string]any{}}) - case r.Method == http.MethodPost && r.URL.Path == "/process": - body := readJSON(t, r) - if body["waitForCompletion"] != true || body["timeout"] != float64(5) { - t.Fatalf("post = %#v", body) - } - writeProcess(t, w, "running", 0, "", "") - case r.Method == http.MethodGet && r.URL.Path == "/process/123": - gets.Add(1) - writeProcess(t, w, "completed", 0, "late", "") - default: - t.Errorf("unexpected %s %s", r.Method, r.URL.Path) - http.NotFound(w, r) - } - }) - result, err := backend.RunCommand(context.Background(), "sbx", &sandbox.ExecOptions{ - Argv: []string{"echo", "hi"}, - Timeout: sandbox.Duration(5 * time.Second), - }) - if err != nil { - t.Fatal(err) - } - if result.GetExitCode() != 0 || string(result.GetStdout()) != "late" { - t.Fatalf("result = %#v", result) - } - if gets.Load() < 1 { - t.Fatalf("gets = %d", gets.Load()) - } -} - func TestRunCommandReturnsAFinishedStartWithoutPolling(t *testing.T) { backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { switch { diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index d9bcdfed..89a1bf66 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -84,21 +84,21 @@ A bare 409 on other operations is `Conflict`. Coded conflicts such as `remove`. Paths in the sandbox are absolute. `parents` defaults to true; `parents: false` is rejected. `stat` uses Blaxel `fs.ls` (GET-by-path): a directory listing succeeds; a file path returns Blaxel's "Directory not found" -response, then Kit lists the parent. A missing file passed to `copyFromLocal` -is not found before Blaxel is called, and the error field is `local_path`. A -local directory is an invalid argument with the same field. `copyToLocal` -creates a missing local parent directory. +response and Kit reports a file (no parent listing). A missing file passed to +`copyFromLocal` is not found before Blaxel is called, and the error field is +`local_path`. A local directory is an invalid argument with the same field. +`copyToLocal` creates a missing local parent directory. **Commands** through `sandbox.process.run`. Set exactly one of `argv` or `shell`. `argv` is sent as a POSIX-quoted shell string. `run` waits until the command ends. A non-zero exit code is not an SDK error—check `exitCode`. -Blaxel returns stdout and stderr. Omit or zero `timeout` for no process -auto-kill; Kit then polls until the command ends or the signal aborts. A -positive timeout of at most 60 seconds uses Blaxel's blocking completion -request. A longer timeout sets the process auto-kill and polls. Command -timeouts must be whole seconds (`2000` is two seconds; `1500` is rejected). -Aborting the signal rejects the `await`; the remote command may keep running. -An HTTP 504 during the wait is a timeout error. +Blaxel returns stdout and stderr. Kit starts without Blaxel's completion hold +and uses one native `process.wait`. Omit or zero `timeout` for no process +auto-kill and an unlimited wait (signal can still abort). A positive timeout +sets auto-kill and the wait budget. Command timeouts must be whole seconds +(`2000` is two seconds; `1500` is rejected). Aborting the signal rejects the +`await`; the remote command may keep running. An HTTP 504 during the wait is a +timeout error. Omitted memory keeps the Blaxel default. CPU, CPU limit, memory limit, and disk overrides are rejected. diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 8af24c35..04a9fc57 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -22,7 +22,6 @@ import { CreateFieldNetworkPortsTransport, CreateFieldProvisioningWaitFor, FileFieldLocalPath, - FileFieldPath, } from "@sandbox-kit/sandbox"; import { mapClientConfig, @@ -37,7 +36,6 @@ import { } from "#mappings/provider_mappings.js"; import { captureProviderState } from "#config/provider_config.js"; import { mapBlaxelError } from "#errors.js"; -import { remainingWaitBudgetMs } from "./process.js"; import { projectHandle } from "./native.js"; import { ResponseError, SandboxInstance } from "@blaxel/core"; @@ -808,7 +806,7 @@ describe("Blaxel provider orchestration", () => { expect(lookedUp).toBe(false); }); - it("keeps short runs on waitForCompletion and polls the rest", async () => { + it("starts without waitForCompletion and waits when still running", async () => { const seen: Array<{ command: string; waitForCompletion?: boolean; @@ -819,7 +817,6 @@ describe("Blaxel provider orchestration", () => { const waits: Array<{ identifier: string; maxWait?: number; - interval?: number; signal?: AbortSignal; }> = []; const signal = new AbortController().signal; @@ -845,15 +842,6 @@ describe("Blaxel provider orchestration", () => { entry.workingDir = request.workingDir; if (request.env !== undefined) entry.env = request.env; seen.push(entry); - if (request.waitForCompletion) { - return { - exitCode: 0, - stdout: "ok", - stderr: "", - status: "completed", - pid: "short", - }; - } return { exitCode: 0, stdout: "", @@ -869,9 +857,6 @@ describe("Blaxel provider orchestration", () => { ...(options?.maxWait === undefined ? {} : { maxWait: options.maxWait }), - ...(options?.interval === undefined - ? {} - : { interval: options.interval }), ...(options?.signal === undefined ? {} : { signal: options.signal }), @@ -903,7 +888,7 @@ describe("Blaxel provider orchestration", () => { const polled = await backend.run("sandbox-1", { argv: ["echo", "hi"] }, {}); await backend.run("sandbox-1", { argv: ["echo", "hi"], timeout: 0 }, {}); - const held = await backend.run( + await backend.run( "sandbox-1", { argv: ["echo", "hi"], @@ -934,25 +919,25 @@ describe("Blaxel provider orchestration", () => { expect(polled).toMatchObject({ exitCode: 7 }); expect(polled.stdout).toEqual(new TextEncoder().encode("out")); expect(polled.stderr).toEqual(new TextEncoder().encode("err")); - expect(held).toMatchObject({ exitCode: 0 }); - expect(held.stdout).toEqual(new TextEncoder().encode("ok")); expect(seen).toEqual([ { command: "'echo' 'hi'", waitForCompletion: false, timeout: 0 }, { command: "'echo' 'hi'", waitForCompletion: false, timeout: 0 }, { command: "'echo' 'hi'", - waitForCompletion: true, + waitForCompletion: false, timeout: 5, workingDir: "/workspace", env: { MODE: "test" }, }, - { command: "'echo' 'hi'", waitForCompletion: true, timeout: 60 }, + { command: "'echo' 'hi'", waitForCompletion: false, timeout: 60 }, { command: "'echo' 'hi'", waitForCompletion: false, timeout: 61 }, ]); expect(waits).toEqual([ - { identifier: "long", maxWait: -1, interval: 1_000 }, - { identifier: "long", maxWait: -1, interval: 1_000 }, - { identifier: "long", maxWait: 61_000, interval: 1_000, signal }, + { identifier: "long", maxWait: -1 }, + { identifier: "long", maxWait: -1 }, + { identifier: "long", maxWait: 5_000 }, + { identifier: "long", maxWait: 60_000 }, + { identifier: "long", maxWait: 61_000, signal }, ]); }); @@ -980,18 +965,11 @@ describe("Blaxel provider orchestration", () => { expect(result.stderr).toEqual(new TextEncoder().encode("stopped")); }); - it("subtracts elapsed time from the process wait budget", () => { - expect(remainingWaitBudgetMs(undefined, 0, 10_000)).toBe(-1); - expect(remainingWaitBudgetMs(90, 1_000, 31_000)).toBe(60_000); - expect(remainingWaitBudgetMs(5, 1_000, 6_000)).toBe(0); - expect(remainingWaitBudgetMs(5, 1_000, 7_000)).toBeLessThan(0); - }); - - it("polls when a completion-hold start still returns running", async () => { + it("waits when start still returns running", async () => { const waits: string[] = []; const backend = runBackend({ async exec(request) { - expect(request.waitForCompletion).toBe(true); + expect(request.waitForCompletion).toBe(false); expect(request.timeout).toBe(5); return { status: "running", pid: "held", stdout: "", stderr: "" }; }, @@ -1092,29 +1070,11 @@ describe("Blaxel filesystem.stat", () => { }); }); - it("reads file metadata from the parent listing when ls rejects a file path", async () => { + it("reports a file when ls rejects a file path", async () => { const backend = fsBackend({ async ls(path) { - if (path === "/app/readme.txt") { - throw new Error(JSON.stringify({ error: "Directory not found" })); - } - expect(path).toBe("/app"); - return { - name: "app", - path: "/app", - files: [ - { - name: "readme.txt", - path: "/app/readme.txt", - size: 12, - permissions: "0644", - owner: "user", - group: "user", - lastModified: "2024-01-02T03:04:05Z", - }, - ], - subdirectories: [], - }; + expect(path).toBe("/app/readme.txt"); + throw new Error(JSON.stringify({ error: "Directory not found" })); }, }); await expect( @@ -1123,12 +1083,11 @@ describe("Blaxel filesystem.stat", () => { name: "readme.txt", path: "/app/readme.txt", type: FileType.File, - size: 12n, - permissions: "0644", + size: 0n, }); }); - it("propagates non-file ls errors and reports missing files", async () => { + it("propagates non-file ls errors", async () => { const missing = fsBackend({ async ls() { throw new Error("boom"); @@ -1137,27 +1096,6 @@ describe("Blaxel filesystem.stat", () => { await expect( missing.stat!("sandbox-1", "/missing", {}, {}), ).rejects.toMatchObject({ kind: ErrorKind.Unknown, operation: "stat" }); - - const gone = fsBackend({ - async ls(path) { - if (path === "/app/gone.txt") { - throw new Error(JSON.stringify({ error: "Directory not found" })); - } - return { - name: "app", - path: "/app", - files: [], - subdirectories: [], - }; - }, - }); - await expect( - gone.stat!("sandbox-1", "/app/gone.txt", {}, {}), - ).rejects.toMatchObject({ - kind: ErrorKind.NotFound, - operation: "stat", - field: FileFieldPath, - }); }); }); diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index b3b34a61..33ecdb69 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -288,7 +288,6 @@ class BlaxelBackend extends BackendBase implements Backend { options, body: async (sandbox) => { const timeout = execTimeoutSeconds(request.timeout); - const startedAt = Date.now(); const started = await sandbox.process.exec( processRequest(request, timeout), ); @@ -296,7 +295,6 @@ class BlaxelBackend extends BackendBase implements Backend { process: sandbox.process, started, timeout, - startedAt, signal: options.signal, }); }, diff --git a/sdks/typescript/providers/blaxel/src/filesystem.ts b/sdks/typescript/providers/blaxel/src/filesystem.ts index a7cc33b8..d984deb9 100644 --- a/sdks/typescript/providers/blaxel/src/filesystem.ts +++ b/sdks/typescript/providers/blaxel/src/filesystem.ts @@ -1,10 +1,7 @@ -import { basename, dirname } from "node:path"; +import { basename } from "node:path"; import type { Directory, File as BlaxelFile } from "@blaxel/core"; import { - ErrorKind, - FileFieldPath, FileType, - sandboxError, type FileInfo, } from "@sandbox-kit/sandbox"; import type { NativeSandboxHandle } from "./native.js"; @@ -34,7 +31,11 @@ export function directoryInfo(info: BlaxelSubdirectory): FileInfo { }; } -/** Blaxel has no Stat; ls wraps GET and rejects file paths with this error. */ +/** + * Blaxel has no Stat. `fs.ls` GETs the path: a directory listing succeeds; a + * file body makes ls throw "Directory not found". Report that as a File — + * one request, no parent-listing workaround (Go uses the same GET via a lower client). + */ export async function blaxelStat( fs: NativeSandboxHandle["fs"], path: string, @@ -49,19 +50,13 @@ export async function blaxelStat( }; } catch (error) { if (!isBlaxelFilePath(error)) throw error; + return { + name: basename(path) || path, + path, + type: FileType.File, + size: 0n, + }; } - const parent = dirname(path); - const name = basename(path); - const listing = await fs.ls(parent === "." ? "/" : parent); - const file = listing.files.find((entry) => entry.name === name); - if (file) return fileInfo(file); - throw sandboxError({ - kind: ErrorKind.NotFound, - provider: "blaxel", - operation: "stat", - field: FileFieldPath, - message: `sandbox-kit blaxel: path not found: ${path}`, - }); } /** Blaxel `fs.ls` throws this when GET returns a file instead of a directory. */ diff --git a/sdks/typescript/providers/blaxel/src/process.ts b/sdks/typescript/providers/blaxel/src/process.ts index 8f2a2a6c..c3ecb9b3 100644 --- a/sdks/typescript/providers/blaxel/src/process.ts +++ b/sdks/typescript/providers/blaxel/src/process.ts @@ -12,9 +12,6 @@ import type { ProcessExecRequest, } from "./native.js"; -/** Blaxel's documented limit for a single waitForCompletion request. */ -const blaxelCompletionHoldSeconds = 60; - const terminalProcessStatus = new Set([ "completed", "failed", @@ -22,14 +19,14 @@ const terminalProcessStatus = new Set([ "stopped", ]); +/** Starts without waitForCompletion; Kit waits via process.wait. */ export function processRequest( request: ExecOptions, timeout: number | undefined, ): ProcessExecRequest { const body: ProcessExecRequest = { command: blaxelCommand(request), - // 1–60s can use Blaxel's completion hold; omit/0/longer timeouts poll. - waitForCompletion: holdsCompletion(timeout), + waitForCompletion: false, // omit/0 → 0 disables Blaxel's default process auto-kill. timeout: timeout ?? 0, }; @@ -42,14 +39,12 @@ export function processRequest( return body; } -/** Internal wait orchestration after Process.exec. */ +/** Internal wait after Process.exec: return terminal start, else one native wait. */ export interface FinishProcessArgs { process: NativeSandboxHandle["process"]; started: BlaxelProcessResult; /** Kit process timeout in whole seconds; undefined means unlimited. */ timeout: number | undefined; - /** Date.now() taken immediately before Process.exec. */ - startedAt: number; signal: AbortSignal | undefined; } @@ -67,37 +62,16 @@ export async function finishProcess( message: "sandbox-kit blaxel: SDK returned no process identifier", }); } - const maxWait = remainingWaitBudgetMs(args.timeout, args.startedAt); - if (args.timeout !== undefined && maxWait <= 0) { - providerError({ - kind: ErrorKind.Timeout, - operation: "run", - field: ExecFieldTimeout, - message: "process did not finish before the timeout", - }); - } const waitOptions: { maxWait: number; - interval: number; signal?: AbortSignal; } = { - maxWait, - interval: 1_000, + maxWait: args.timeout === undefined ? -1 : args.timeout * 1_000, }; if (args.signal !== undefined) waitOptions.signal = args.signal; return execResult(await args.process.wait(identifier, waitOptions)); } -/** Poll window after Process.exec, subtracting time spent on completion hold. */ -export function remainingWaitBudgetMs( - timeoutSeconds: number | undefined, - startedAt: number, - now: number = Date.now(), -): number { - if (timeoutSeconds === undefined) return -1; - return timeoutSeconds * 1_000 - (now - startedAt); -} - /** * Maps Kit ExecOptions.timeout (ms) to Blaxel seconds. * Omitted and explicit zero become undefined so the caller sends 0 (no limit). @@ -121,14 +95,6 @@ function isWholeSecondTimeout(timeout: number): boolean { return Number.isFinite(timeout) && timeout >= 0 && timeout % 1_000 === 0; } -function holdsCompletion(timeoutSeconds: number | undefined): boolean { - return ( - timeoutSeconds !== undefined && - timeoutSeconds > 0 && - timeoutSeconds <= blaxelCompletionHoldSeconds - ); -} - function processFinished(status: string | undefined): boolean { return status !== undefined && terminalProcessStatus.has(status); } diff --git a/website/content/docs/filesystem.mdx b/website/content/docs/filesystem.mdx index 54f9fc18..e008199e 100644 --- a/website/content/docs/filesystem.mdx +++ b/website/content/docs/filesystem.mdx @@ -350,8 +350,8 @@ fmt.Println(entries) Blaxel has no dedicated stat API. TypeScript uses `fs.ls` (GET-by-path): a directory listing succeeds; a file path returns Blaxel's "Directory not - found" response, then Kit lists the parent. Go calls the sandbox filesystem - GET through a rebuilt lower client because `SandboxInstanceFSService` has no + found" response and Kit reports a file. Go calls the sandbox filesystem GET + through a rebuilt lower client because `SandboxInstanceFSService` has no public Get/Stat and `LS` always treats the response as a directory. diff --git a/website/content/docs/process.mdx b/website/content/docs/process.mdx index 7f99c4fa..3e514643 100644 --- a/website/content/docs/process.mdx +++ b/website/content/docs/process.mdx @@ -159,9 +159,8 @@ fmt.Println(result) | ---------------------- | ------------------------------------------------------------------------------ | | `argv` | POSIX-quoted shell string | | `shell` | shell string as-is | -| omit / zero timeout | no process auto-kill; Kit polls until the command ends or the call is canceled | -| 1–60 seconds | process auto-kill; blocking completion request | -| longer than 60 seconds | process auto-kill; Kit polls until the command ends or the call is canceled | +| omit / zero timeout | no process auto-kill; one native wait until the command ends or cancel | +| positive timeout | process auto-kill; one native wait with that budget | | fractional (`1500` ms) | rejected (`timeout`) | Blaxel returns both `stdout` and `stderr`. `run` waits until the command ends. diff --git a/website/content/docs/sdk/process.mdx b/website/content/docs/sdk/process.mdx index 43d03308..f654bf02 100644 --- a/website/content/docs/sdk/process.mdx +++ b/website/content/docs/sdk/process.mdx @@ -125,12 +125,12 @@ Outcome of a finished command. Blaxel returns both `stdout` and `stderr`. `argv` is sent as a POSIX-quoted - shell string. Omit or zero timeout means no process auto-kill; Kit polls until - the command ends or the call is canceled. A timeout of at most 60 seconds uses - Blaxel's blocking completion request. A longer timeout sets the process - auto-kill and polls. `run` waits until the command ends. Aborting the signal - stops the wait only; the remote command may keep running. An HTTP 504 while - waiting is a timeout error. + shell string. Kit starts without Blaxel's completion hold and uses one native + wait. Omit or zero timeout means no process auto-kill and an unlimited wait + (signal can still abort). A positive timeout sets auto-kill and the wait + budget. `run` waits until the command ends. Aborting the signal stops the wait + only; the remote command may keep running. An HTTP 504 while waiting is a + timeout error. @@ -156,12 +156,12 @@ Outcome of a finished command. Blaxel returns both Stdout and Stderr. Argv is sent as a POSIX-quoted shell - string. Omit or zero Timeout means no process auto-kill; Kit polls until the - command ends or the call is canceled. A Timeout of at most 60 seconds uses - Blaxel's blocking completion request. A longer Timeout sets the process - auto-kill and polls. Run waits until the command ends. Canceling the context - stops the wait only; the remote command may keep running. An HTTP 504 while - waiting is a timeout error. + string. Kit starts without Blaxel's completion hold and uses one native Wait. + Omit or zero Timeout means no process auto-kill and an unlimited wait + (context can still cancel). A positive Timeout sets auto-kill and the wait + budget. Run waits until the command ends. Canceling the context stops the wait + only; the remote command may keep running. An HTTP 504 while waiting is a + timeout error. From 9bb6692e08b9c929c957830d791da4280ec5b643 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 12:55:56 +0530 Subject: [PATCH 36/60] Rewrite Blaxel user docs for SDK callers and fix TS Stat size. Drop builder-facing wording across guides, examples, and provider READMEs, and have TypeScript Stat use filesystem GET so file metadata matches Go. Co-authored-by: Cursor --- README.md | 33 +++-- docs/configuration.md | 14 +- docs/filesystem.md | 17 +-- docs/process.md | 14 +- docs/providers.md | 3 +- docs/responses-and-errors.md | 8 +- docs/sandbox-creation.md | 49 +++--- docs/sandbox-lifecycle.md | 14 +- examples/README.md | 2 +- examples/go/README.md | 2 +- .../create-blaxel-client-timeout/README.md | 3 +- .../create-blaxel-idle/README.md | 2 +- .../create-blaxel-sandbox/README.md | 9 +- .../create-blaxel-with-allowlists/README.md | 2 +- .../create-blaxel-with-labels/README.md | 2 +- .../create-blaxel-with-ports/README.md | 4 +- .../create-blaxel-with-resources/README.md | 2 +- .../create-blaxel-with-ttl/README.md | 2 +- .../errors/handle-blaxel-errors/README.md | 6 +- .../filesystem/stat-blaxel-file/README.md | 3 +- .../lifecycle/delete-blaxel-sandbox/README.md | 6 +- .../lifecycle/get-blaxel-sandbox/README.md | 2 +- .../lifecycle/get-blaxel-sandbox/main.go | 2 +- .../refresh-blaxel-sandbox/README.md | 4 +- .../process/run-blaxel-check-exit/README.md | 2 +- .../process/run-blaxel-command/README.md | 2 +- .../process/run-blaxel-with-timeout/README.md | 9 +- examples/typescript/README.md | 2 +- .../create-blaxel-client-timeout/README.md | 3 +- .../create-blaxel-idle/README.md | 2 +- .../create-blaxel-sandbox/README.md | 9 +- .../create-blaxel-with-allowlists/README.md | 2 +- .../create-blaxel-with-labels/README.md | 2 +- .../create-blaxel-with-ports/README.md | 4 +- .../create-blaxel-with-resources/README.md | 2 +- .../create-blaxel-with-ttl/README.md | 2 +- .../errors/handle-blaxel-errors/README.md | 6 +- .../filesystem/stat-blaxel-file/README.md | 3 +- .../lifecycle/delete-blaxel-sandbox/README.md | 6 +- .../lifecycle/get-blaxel-sandbox/README.md | 2 +- .../lifecycle/get-blaxel-sandbox/src/main.ts | 2 +- .../refresh-blaxel-sandbox/README.md | 4 +- .../process/run-blaxel-check-exit/README.md | 2 +- .../process/run-blaxel-command/README.md | 2 +- .../process/run-blaxel-with-timeout/README.md | 9 +- sdks/go/providers/blaxel/README.md | 118 ++++++++------- sdks/go/providers/blaxel/doc.go | 9 +- sdks/typescript/providers/blaxel/README.md | 139 ++++++++++-------- .../providers/blaxel/src/blaxel.test.ts | 66 ++++++--- .../typescript/providers/blaxel/src/blaxel.ts | 10 +- .../providers/blaxel/src/filesystem.ts | 46 +++--- .../typescript/providers/blaxel/src/native.ts | 68 ++++++++- website/content/docs/blaxel.mdx | 65 ++++---- website/content/docs/configuration.mdx | 14 +- website/content/docs/create.mdx | 16 +- website/content/docs/errors.mdx | 27 ++-- website/content/docs/filesystem.mdx | 8 +- website/content/docs/lifecycle.mdx | 9 +- website/content/docs/process.mdx | 32 ++-- website/content/docs/sdk/configuration.mdx | 12 +- website/content/docs/sdk/create-options.mdx | 19 +-- website/content/docs/sdk/enums.mdx | 3 +- website/content/docs/sdk/filesystem.mdx | 10 ++ website/content/docs/sdk/process.mdx | 28 ++-- website/content/docs/sdk/sandbox-info.mdx | 4 +- 65 files changed, 531 insertions(+), 454 deletions(-) diff --git a/README.md b/README.md index d356bb2e..61171e4b 100644 --- a/README.md +++ b/README.md @@ -117,9 +117,9 @@ func main() { ### What gets installed -| Language | Core SDK | Providers | -| ---------- | -------------------------------------------- | ---------------------------------------------- | -| TypeScript | `@sandbox-kit/sandbox` | `@sandbox-kit/daytona`, `@sandbox-kit/modal`, `@sandbox-kit/blaxel` | +| Language | Core SDK | Providers | +| ---------- | -------------------------------------------- | ---------------------------------------------------------------------- | +| TypeScript | `@sandbox-kit/sandbox` | `@sandbox-kit/daytona`, `@sandbox-kit/modal`, `@sandbox-kit/blaxel` | | Go | `github.com/sandbox-kit/kit/sdks/go/sandbox` | `.../providers/daytona`, `.../providers/modal`, `.../providers/blaxel` | Install only the provider you use. The core SDK exposes the shared client @@ -263,8 +263,9 @@ More detail: [responses and errors](docs/responses-and-errors.md). Go helpers: `Value(v)` for optional scalars, `Duration(d)` for timeouts. - Unsupported settings fail before provider calls; quotas stay provider-enforced. - Memory and disk are MiB. Modal CPU: fractional cores from 0.125 (0.001 steps). - Daytona: whole cores and whole GiB expressed as MiB. Blaxel: `memoryMib` / - `memory_mib` maps 1:1; Blaxel derives CPU from memory. + Daytona: whole cores and whole GiB expressed as MiB. Blaxel: set + `memoryMib` / `memory_mib` (Blaxel documents MB; Kit does not convert); CPU + follows memory. Guides: [configuration](docs/configuration.md) · [creation](docs/sandbox-creation.md) · @@ -285,17 +286,17 @@ Open http://localhost:3000. Docs are organized as Quickstart, Concepts, Provider These pages are the behavior reference for the same API: -| Guide | Covers | -| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -| [Client configuration](docs/configuration.md) | Auth, scope, defaults, deadlines | -| [Sandbox creation](docs/sandbox-creation.md) | Fields, units, policies | -| [Sandbox lifecycle](docs/sandbox-lifecycle.md) | Get, attach, capabilities | -| [Filesystem](docs/filesystem.md) | Read, write, copy, list, stat, mkdir, remove | -| [Process](docs/process.md) | Run a command and wait for exit code + output | -| [Providers](docs/providers.md) | Daytona, Modal, and Blaxel | -| [Responses and errors](docs/responses-and-errors.md) | Error details and provider causes | -| [TS Daytona](sdks/typescript/providers/daytona/README.md) / [Modal](sdks/typescript/providers/modal/README.md) / [Blaxel](sdks/typescript/providers/blaxel/README.md) | TypeScript provider notes | -| [Go Daytona](sdks/go/providers/daytona/README.md) / [Modal](sdks/go/providers/modal/README.md) / [Blaxel](sdks/go/providers/blaxel/README.md) | Go provider notes | +| Guide | Covers | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | +| [Client configuration](docs/configuration.md) | Auth, scope, defaults, deadlines | +| [Sandbox creation](docs/sandbox-creation.md) | Fields, units, policies | +| [Sandbox lifecycle](docs/sandbox-lifecycle.md) | Get, attach, capabilities | +| [Filesystem](docs/filesystem.md) | Read, write, copy, list, stat, mkdir, remove | +| [Process](docs/process.md) | Run a command and wait for exit code + output | +| [Providers](docs/providers.md) | Daytona, Modal, and Blaxel | +| [Responses and errors](docs/responses-and-errors.md) | Error details and provider causes | +| [TS Daytona](sdks/typescript/providers/daytona/README.md) / [Modal](sdks/typescript/providers/modal/README.md) / [Blaxel](sdks/typescript/providers/blaxel/README.md) | TypeScript provider notes | +| [Go Daytona](sdks/go/providers/daytona/README.md) / [Modal](sdks/go/providers/modal/README.md) / [Blaxel](sdks/go/providers/blaxel/README.md) | Go provider notes | ## License diff --git a/docs/configuration.md b/docs/configuration.md index ace7442a..0aab14bd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -46,8 +46,8 @@ does not change the handle. ## Provider support -The bindings use Modal v0.11.1, Daytona v0.225.0, `@blaxel/core@0.3.36`, and -`github.com/blaxel-ai/sdk-go@v0.31.0`. +The bindings target Modal, Daytona, and Blaxel. Pinned dependency versions live +in each provider package. | Setting | Modal | Daytona | Blaxel | | ---------------- | ---------------------------------------------------------- | --------------------------------- | ------------------------------ | @@ -68,9 +68,8 @@ created. Omit `auth` when the provider should use credentials it already finds in the environment. Daytona can take the organization from that environment when you use a bearer token and leave organization unset. -TypeScript Blaxel clients share `@blaxel/core` process-global `settings`. Use -one Blaxel Kit client, or one credential set, per process; a second client -overwrites the first. +In TypeScript, use one Blaxel client (or one API key) per process. A second +client with different credentials replaces the first. A remote endpoint uses HTTPS. HTTP is accepted for `localhost` or a loopback address. A URL with a username, password, or fragment is rejected. Daytona uses @@ -91,9 +90,8 @@ deadline or abort signal that arrives earlier still applies. Daytona create keeps its 60-second default when the timeout is omitted and when it is explicit zero. Daytona start, stop, pause, and delete use that 60-second default when the timeout is omitted, and treat explicit zero as no -operation timeout. Blaxel uses the Kit deadline for create and does not pass -`@blaxel/core` `SandboxCreateOptions` (including the native 1–50s creation -timeout). The Go Blaxel SDK has no equivalent argument. +operation timeout. On Blaxel, your client/create `timeout` is how long create +may take. ## Credentials diff --git a/docs/filesystem.md b/docs/filesystem.md index 379c8710..ec37b243 100644 --- a/docs/filesystem.md +++ b/docs/filesystem.md @@ -215,16 +215,13 @@ entries, err := instance.Filesystem().ListDirectory(ctx, "/tmp/kit-files", nil) ## Read metadata -`Stat` returns one path. A symlink result describes the symlink. Blaxel has no -dedicated stat API: TypeScript uses `fs.ls` (a directory listing, or a file -when Blaxel returns "Directory not found" for a file path); Go calls the -sandbox filesystem GET through a rebuilt lower client because -`SandboxInstanceFSService` has no public Get/Stat and `LS` always treats the -response as a directory. Modal can -report a symlink. Daytona reports a file or a directory. `mode` is an octal -string when the provider supplies one, such as `0644`. `permissions`, `owner`, -and `group` are set when the provider supplies them. `modifiedAt` is set when -the provider supplies a time. +`Stat` returns one path. A symlink result describes the symlink. On Blaxel, +directories return type directory; files return type file, with size and +permission fields when Blaxel provides them. Modal can report a symlink. +Daytona reports a file or a directory. `mode` is an octal string when the +provider supplies one, such as `0644`. `permissions`, `owner`, and `group` are +set when the provider supplies them. `modifiedAt` is set when the provider +supplies a time. ```ts const info = await sandbox.filesystem.stat("/tmp/note.txt"); diff --git a/docs/process.md b/docs/process.md index 7d380d2d..37d91d77 100644 --- a/docs/process.md +++ b/docs/process.md @@ -27,7 +27,7 @@ How providers run the command: | You pass | Daytona | Modal | Blaxel | | -------- | ------------------------- | ------------------------ | ------------------------- | -| `argv` | POSIX-quoted shell string | argument list as-is | POSIX-quoted shell string | +| `argv` | One quoted shell command string | argument list as-is | One quoted shell command string | | `shell` | shell string as-is | `bash -c` with that text | shell string as-is | ## Working directory and environment @@ -66,7 +66,7 @@ code. | `timeout: 0` / `Duration(0)` | Do not add an extra client deadline for this call | | Positive timeout | Cap this call; must be a whole number of seconds (`5000` ms or `5 * time.Second` is fine; `1500` ms is rejected, field `timeout`) | -Omit or zero also means **no separate provider process auto-kill** (see +Omit or zero also means **no separate provider process time limit** (see provider quirks). Provider quirks: - **Daytona** — if you omit a native timeout, Daytona defaults to 10 seconds. @@ -74,12 +74,10 @@ provider quirks). Provider quirks: capped at 10 seconds. - **Modal** — omit/zero adds no Modal command limit. In TypeScript the client omits `timeoutMs` (sending `0` is rejected by Modal). -- **Blaxel** — omit or zero adds no Blaxel process auto-kill. Timeouts must - be whole seconds. Kit starts without Blaxel's completion hold and uses one - native wait. Omit or zero waits without a Kit budget (context/signal can - still cancel). A positive timeout sets auto-kill and the wait budget. The - Kit deadline and cancel stop that wait and leave the remote command - running. An HTTP 504 during the wait is a timeout error. +- **Blaxel** — omit or zero waits until the command ends (or you cancel), with + no Blaxel process time limit. Positive timeouts must be whole seconds and + cap how long the command may run. Canceling stops waiting; the remote + command may keep running. If the wait times out, you get a timeout error. Cancel behavior: diff --git a/docs/providers.md b/docs/providers.md index 07d94fb9..398cd64a 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -78,8 +78,7 @@ volume fields; Blaxel, Daytona, and Modal volumes stay out of create), browsers, streaming process handles, sessions, PTY, and macOS or Windows creation are outside it. -TypeScript Blaxel uses `@blaxel/core` process-global `settings`: one Blaxel -Kit client, or one credential set, per process. +In TypeScript, use one Blaxel client (or one API key) per process. See [configuration](configuration.md), [creation](sandbox-creation.md), [lifecycle](sandbox-lifecycle.md), [filesystem](filesystem.md), and diff --git a/docs/responses-and-errors.md b/docs/responses-and-errors.md index 268f41db..3df7e978 100644 --- a/docs/responses-and-errors.md +++ b/docs/responses-and-errors.md @@ -109,11 +109,9 @@ An error that Kit could not classify keeps an unknown kind and the original cause. A kind is a category. It is not a signal that retrying is safe, and Kit does not retry for you. -Blaxel name collisions map to `AlreadyExists` when the provider sends -`SANDBOX_ALREADY_EXISTS`, or a bare HTTP 409 on **create**. A bare 409 on -other operations maps to `Conflict` (same status fallback Daytona uses). -Provider codes still win when present, so `SANDBOX_DELETION_IN_PROGRESS` -stays `Conflict`. +On Blaxel, creating a sandbox whose name already exists returns +`AlreadyExists`. If a delete is still finishing or another conflict occurs, +you get `Conflict`. ## After a failure diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index 1ceb4f39..89bf0530 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -6,23 +6,22 @@ from lifecycle operations on an existing sandbox. ## Supported creation settings -Go and TypeScript integrations target Modal v0.11.1, Daytona v0.225.0, and -Blaxel (`modal@0.11.1`, `@daytona/sdk@0.225.0`, `@blaxel/core@0.3.36` / -`github.com/blaxel-ai/sdk-go@v0.31.0`). - -| Setting | Modal | Daytona | Blaxel | -| ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- | -| Source | Explicit registry image and an existing app | Default/specified snapshot or registry image | Named create (Blaxel name rules); registry image required | -| Runtime | Entrypoint, absolute working directory, PTY | Toolbox language and user | Unsupported | -| Isolation | Default, gVisor container, Linux VM | Nested KVM flag; class inherited from source | Unsupported | -| Resources | Fractional CPU, CPU limit, memory request/limit | Whole CPU and GiB memory/disk; image source required for overrides | Memory 1:1 (`memory_mib`); CPU/disk overrides rejected | -| Placement | Cloud and region preferences | Client target; request placement unsupported | Client `region` required for create; request placement unsupported | -| Environment and labels | Environment map and tags | Environment map and labels | Environment map and labels | -| Security | Named secrets and workload identity | Egress-placeholder secrets and public access | Unsupported | -| Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | Ports (not 80/443/8080; Default → Blaxel HTTP); outbound domains | -| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m); explicit idle disable rejected | -| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Kit client/create timeout only (native create timeout / SandboxCreateOptions not forwarded); waitFor Default/Scheduled only | -| Observability | Verbose output | Telemetry endpoint | Unsupported | +Go and TypeScript integrations target Modal, Daytona, and Blaxel. Pinned +dependency versions live in each provider package. + +| Setting | Modal | Daytona | Blaxel | +| ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------- | +| Source | Explicit registry image and an existing app | Default/specified snapshot or registry image | Named create (Blaxel name rules); registry image required | +| Runtime | Entrypoint, absolute working directory, PTY | Toolbox language and user | Unsupported | +| Isolation | Default, gVisor container, Linux VM | Nested KVM flag; class inherited from source | Unsupported | +| Resources | Fractional CPU, CPU limit, memory request/limit | Whole CPU and GiB memory/disk; image source required for overrides | Memory (`memory_mib` / `memoryMib`); CPU/disk overrides rejected | +| Placement | Cloud and region preferences | Client target; request placement unsupported | Client `region` required for create; request placement unsupported | +| Environment and labels | Environment map and tags | Environment map and labels | Environment map and labels | +| Security | Named secrets and workload identity | Egress-placeholder secrets and public access | Unsupported | +| Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | Ports (not 80/443/8080; Default → Blaxel HTTP); outbound domains | +| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m); explicit idle disable rejected | +| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Client/create timeout only; waitFor Default/Scheduled only; Ready unsupported | +| Observability | Verbose output | Telemetry endpoint | Unsupported | Kit has no portable volume fields; Blaxel volumes stay out of create the same way Daytona and Modal volumes do. @@ -50,18 +49,18 @@ field was omitted. Invalid combinations fail in the client before the provider call. A field the selected provider does not support fails there too. -| Resource | Modal | Daytona | Blaxel | -| ---------------- | ----------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------- | -| CPU | Physical cores; minimum 0.125, precision 0.001 | Whole CPU cores | Derived by Blaxel from memory; Kit rejects CPU overrides | -| Memory | MiB; minimum request 128 | Whole GiB expressed as MiB | `memory_mib` / `memoryMib` passed 1:1; Blaxel labels the unit MB | -| Disk | Creation override unsupported | Whole GiB expressed as MiB | Creation override unsupported | -| Maximum lifetime | Positive whole seconds, up to 24 hours (TS: ms) | Whole-minute TTL (TS: ms); zero disables TTL | Blaxel TTL string (at least 5m when set) | +| Resource | Modal | Daytona | Blaxel | +| ---------------- | ----------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------- | +| CPU | Physical cores; minimum 0.125, precision 0.001 | Whole CPU cores | Derived by Blaxel from memory; Kit rejects CPU overrides | +| Memory | MiB; minimum request 128 | Whole GiB expressed as MiB | `memory_mib` / `memoryMib` (Blaxel documents MB; Kit does not convert) | +| Disk | Creation override unsupported | Whole GiB expressed as MiB | Creation override unsupported | +| Maximum lifetime | Positive whole seconds, up to 24 hours (TS: ms) | Whole-minute TTL (TS: ms); zero disables TTL | At least 5 minutes when set | Daytona request conversion divides MiB exactly by 1024. Allocated metadata converts GiB back to MiB. Snapshot creation inherits snapshot resources. Positive CPU or memory limits require a corresponding request and cannot be -lower than that request. Blaxel memory is a 1:1 integer pass-through; Kit -does not convert MiB to MB. +lower than that request. On Blaxel, pass memory as an integer; Kit does not +convert units. Local checks cover syntax, known enums, documented bounds, precision, and supported combinations. Account quotas, class-specific permissions, image diff --git a/docs/sandbox-lifecycle.md b/docs/sandbox-lifecycle.md index 0771f172..fd6174fb 100644 --- a/docs/sandbox-lifecycle.md +++ b/docs/sandbox-lifecycle.md @@ -108,14 +108,12 @@ Modal gVisor and Linux VM sandboxes share Modal's terminal execution lifecycle. `Stop` or `Delete`. Modal's native `FromID` performs no remote lookup, so Kit exposes it as `Client.Attach`; Modal does not advertise `Get` or `Refresh`. -Blaxel looks up sandboxes by name for `Get` and `Refresh`. `Delete` removes the -Blaxel resource; a successful call is reported as `Deleted` even if Blaxel -finishes the removal asynchronously. `Attach`, `Start`, `Stop`, `Pause`, -`Archive`, and `Terminate` are unsupported. Closing a Blaxel handle does not -contact Blaxel. On `Get` / `Refresh`, Blaxel deployment `DEPLOYED` with runtime -`STANDBY` maps to Kit paused, and `RUNNING` maps to running. Go uses the SDK's -public `Sandbox.State`. TypeScript projects runtime state from `@blaxel/core`'s -private wire model onto a Kit-facing `state` field (no public `state` getter). +Blaxel looks up sandboxes by name for `Get` and `Refresh`. After a successful +`Delete`, the handle reports `Deleted` and `terminal`. Wait before recreating +the same name — Blaxel may still be cleaning up and return `Conflict`. +`Attach`, `Start`, `Stop`, `Pause`, `Archive`, and `Terminate` are unsupported. +Closing a Blaxel handle does not contact Blaxel. On `Get` / `Refresh`, an idle +sandbox shows as paused and an active one shows as running. File calls on the same handle are documented in [filesystem](filesystem.md). Daytona, Modal, and Blaxel implement the same eight methods; each handle diff --git a/examples/README.md b/examples/README.md index 0833c621..00a7398c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -144,7 +144,7 @@ Indexes: [Go](go/README.md) · [TypeScript](typescript/README.md). | Blaxel remove one path | [go](go/blaxel/filesystem/remove-blaxel-file/README.md) | [typescript](typescript/blaxel/filesystem/remove-blaxel-file/README.md) | | Blaxel remote retrieval and metadata | [go](go/blaxel/lifecycle/get-blaxel-sandbox/README.md) | [typescript](typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md) | | Blaxel metadata refresh | [go](go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md) | [typescript](typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md) | -| Blaxel waited deletion | [go](go/blaxel/lifecycle/delete-blaxel-sandbox/README.md) | [typescript](typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md) | +| Blaxel delete by name | [go](go/blaxel/lifecycle/delete-blaxel-sandbox/README.md) | [typescript](typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md) | | Blaxel close the handle; sandbox keeps running | [go](go/blaxel/lifecycle/close-blaxel-handle/README.md) | [typescript](typescript/blaxel/lifecycle/close-blaxel-handle/README.md) | | Blaxel typed validation errors | [go](go/blaxel/errors/handle-blaxel-errors/README.md) | [typescript](typescript/blaxel/errors/handle-blaxel-errors/README.md) | diff --git a/examples/go/README.md b/examples/go/README.md index e9aed6be..328a59fa 100644 --- a/examples/go/README.md +++ b/examples/go/README.md @@ -143,7 +143,7 @@ closes your connection to Daytona, Modal, or Blaxel. | [remove-blaxel-file](blaxel/filesystem/remove-blaxel-file/README.md) | remove one path | | [get-blaxel-sandbox](blaxel/lifecycle/get-blaxel-sandbox/README.md) | remote retrieval and metadata | | [refresh-blaxel-sandbox](blaxel/lifecycle/refresh-blaxel-sandbox/README.md) | metadata refresh | -| [delete-blaxel-sandbox](blaxel/lifecycle/delete-blaxel-sandbox/README.md) | waited deletion | +| [delete-blaxel-sandbox](blaxel/lifecycle/delete-blaxel-sandbox/README.md) | delete by name | | [close-blaxel-handle](blaxel/lifecycle/close-blaxel-handle/README.md) | close the handle; sandbox keeps running | | [handle-blaxel-errors](blaxel/errors/handle-blaxel-errors/README.md) | typed validation errors | diff --git a/examples/go/blaxel/configuration/create-blaxel-client-timeout/README.md b/examples/go/blaxel/configuration/create-blaxel-client-timeout/README.md index be7ff9bc..05820772 100644 --- a/examples/go/blaxel/configuration/create-blaxel-client-timeout/README.md +++ b/examples/go/blaxel/configuration/create-blaxel-client-timeout/README.md @@ -8,8 +8,7 @@ Sets `Config.Timeout` / `timeout` on the shared client. This bounds local waitin ## Edge cases -- Kit `Timeout` / `provisioning.timeout` is the local create deadline. -- The Go Blaxel SDK has no separate creation-timeout argument. +- Client `Timeout` is how long create may take. ## Run diff --git a/examples/go/blaxel/configuration/create-blaxel-idle/README.md b/examples/go/blaxel/configuration/create-blaxel-idle/README.md index 1ed5e717..115c4f0c 100644 --- a/examples/go/blaxel/configuration/create-blaxel-idle/README.md +++ b/examples/go/blaxel/configuration/create-blaxel-idle/README.md @@ -4,7 +4,7 @@ Create one Blaxel sandbox that deletes after an idle period. ## What this means -Maps `lifetime.idleTerminate` (`PolicyModeAfter`) to a Blaxel `ttl-idle` expiration policy with action delete. +Set `lifetime.idleTerminate` with `after` and a delay (≥1 minute). The sandbox deletes after that idle period. You cannot disable idle terminate. ## Edge cases diff --git a/examples/go/blaxel/configuration/create-blaxel-sandbox/README.md b/examples/go/blaxel/configuration/create-blaxel-sandbox/README.md index a4f71ea7..3ada61c5 100644 --- a/examples/go/blaxel/configuration/create-blaxel-sandbox/README.md +++ b/examples/go/blaxel/configuration/create-blaxel-sandbox/README.md @@ -4,11 +4,10 @@ Create one Blaxel sandbox from a registry image with an explicit name. Prints th ## What this means -Blaxel create requires a Blaxel-valid `name` (lowercase alphanumeric with -single hyphens, at most 49 characters) and an image source. Kit calls native -create (not create-if-not-exists). Region comes from the client config -(`BL_REGION`, default `us-pdx-1`). Closing the Kit client only drops the local -SDK connection. +Create needs a Blaxel-valid `name` (lowercase alphanumeric with single hyphens, +at most 49 characters), an image, and client `region` (`BL_REGION`, default +`us-pdx-1`). If the name already exists, you get `AlreadyExists`. Closing the +client does not delete the sandbox. ## Edge cases diff --git a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/README.md b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/README.md index 38604ccb..f29f6e1c 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/README.md +++ b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/README.md @@ -4,7 +4,7 @@ Create one Blaxel sandbox that allows selected outbound domains. ## What this means -Maps `network.outboundDomains` to Blaxel `allowedDomains`. Other egress controls are unsupported. +Set `network.outboundDomains` to allow those hosts. Other egress controls are unsupported on Blaxel. ## Edge cases diff --git a/examples/go/blaxel/configuration/create-blaxel-with-labels/README.md b/examples/go/blaxel/configuration/create-blaxel-with-labels/README.md index f7580dbb..60bc3cca 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-labels/README.md +++ b/examples/go/blaxel/configuration/create-blaxel-with-labels/README.md @@ -4,7 +4,7 @@ Create one Blaxel sandbox with labels and environment variables. ## What this means -Labels become Blaxel metadata labels. Environment entries are injected into the sandbox runtime. +Set `labels` and create-time environment variables. They appear as sandbox metadata and runtime env. ## Edge cases diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md b/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md index 958c3ba8..8130b787 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/README.md @@ -4,8 +4,8 @@ Create one Blaxel sandbox that publishes selected ports. ## What this means -Maps `network.ports` to Blaxel runtime ports. Default leaves the protocol to -Blaxel (HTTP). Plain → TCP, TLS → TLS. HTTP/2 over TLS is rejected. +Publish ports with `network.ports`. Default protocol is HTTP; `plain` is TCP; +`tls` is TLS. HTTP/2 over TLS is not supported. ## Edge cases diff --git a/examples/go/blaxel/configuration/create-blaxel-with-resources/README.md b/examples/go/blaxel/configuration/create-blaxel-with-resources/README.md index deb277cf..74bc69fe 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-resources/README.md +++ b/examples/go/blaxel/configuration/create-blaxel-with-resources/README.md @@ -4,7 +4,7 @@ Create one Blaxel sandbox with an explicit memory size in MiB. Blaxel derives CP ## What this means -Only `resources.memoryMib` is mapped. CPU cores, limits, and disk MiB overrides are rejected. Omitted memory keeps the Blaxel default. +You can set `resources.memory_mib` only. CPU cores, limits, and disk overrides are rejected. Omit memory to use Blaxel's default. ## Edge cases diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ttl/README.md b/examples/go/blaxel/configuration/create-blaxel-with-ttl/README.md index fd88526b..a5bc4649 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-ttl/README.md +++ b/examples/go/blaxel/configuration/create-blaxel-with-ttl/README.md @@ -4,7 +4,7 @@ Create one Blaxel sandbox that deletes after a maximum lifetime TTL. ## What this means -Maps `lifetime.maximumLifetime` to Blaxel max-age TTL. Durations must be whole seconds and meet Blaxel minima (5 minutes for max-age). +Set `lifetime.maximumLifetime` (whole seconds, at least 5 minutes). The sandbox deletes when that max age elapses. ## Edge cases diff --git a/examples/go/blaxel/errors/handle-blaxel-errors/README.md b/examples/go/blaxel/errors/handle-blaxel-errors/README.md index e61c3202..5837e996 100644 --- a/examples/go/blaxel/errors/handle-blaxel-errors/README.md +++ b/examples/go/blaxel/errors/handle-blaxel-errors/README.md @@ -4,7 +4,7 @@ Demonstrate typed validation errors from the Blaxel provider without creating a ## What this means -Requests an unsupported CPU override so Kit fails before calling Blaxel. Applications inspect `SandboxError` kind and field. +Requests an unsupported CPU override so validation fails before any cloud call. Inspect `SandboxError` `kind` and `field`. ## Edge cases @@ -34,8 +34,8 @@ BL_REGION= ## Warnings -- Acts on **real** Blaxel resources when credentials succeed. -- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. +- Validation fails locally for the unsupported CPU field; no sandbox is created. +- Closing the client closes your connection to the provider. ## Related diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/README.md b/examples/go/blaxel/filesystem/stat-blaxel-file/README.md index d3c34bcf..74c8aff9 100644 --- a/examples/go/blaxel/filesystem/stat-blaxel-file/README.md +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/README.md @@ -4,7 +4,8 @@ Stat one absolute path in a Blaxel sandbox. ## What this means -Returns type and size for the path. +Returns whether the path is a file or a directory, plus size and mode when +Blaxel provides them. Size may be zero when Blaxel does not return it. ## Edge cases diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md index f5c15d06..a24aa306 100644 --- a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md @@ -1,10 +1,12 @@ # Delete a Blaxel sandbox -Delete an existing Blaxel sandbox by name and wait for completion. +Delete an existing Blaxel sandbox by name. ## What this means -`Delete` permanently removes the Blaxel sandbox. Start, stop, pause, archive, and terminate are unsupported. +`Delete` permanently removes the Blaxel sandbox. When the call succeeds you get +`Deleted` with `terminal` set. Wait before recreating the same name, or you may +get `Conflict`. Start, stop, pause, archive, and terminate are unsupported. ## Edge cases diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md index 7dd59b04..9c20ab2c 100644 --- a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/README.md @@ -4,7 +4,7 @@ Look up an existing Blaxel sandbox by name and print metadata. ## What this means -`Get` retrieves the sandbox by name (the Blaxel id). Use the name printed by a create example as `BL_SANDBOX_NAME`. +`client.Get` loads the sandbox by name (the Blaxel id). Use the name printed by a create example as `BL_SANDBOX_NAME`. ## Edge cases diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go index 631c801d..e05df12a 100644 --- a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go @@ -44,5 +44,5 @@ func main() { log.Fatal(err) } info := instance.Info() - fmt.Printf("Blaxel sandbox: id=%s state=%s native=%s capabilities=%v\n", info.Id, info.State, info.GetProviderState(), instance.Capabilities()) + fmt.Printf("Blaxel sandbox: id=%s state=%s terminal=%v capabilities=%v\n", info.Id, info.State, info.GetTerminal(), instance.Capabilities()) } diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md index 7f515ed1..e77aa5ca 100644 --- a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/README.md @@ -4,11 +4,11 @@ Refresh metadata for an existing Blaxel sandbox by name. ## What this means -`Refresh` re-fetches provider metadata onto the handle. +`Refresh` updates `Info()` on this handle (state, labels, and related metadata). ## Edge cases -- Unsupported when the handle does not report the refresh capability. +- If this handle does not support refresh, you get `Unsupported`. ## Run diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/README.md b/examples/go/blaxel/process/run-blaxel-check-exit/README.md index 404dd054..d522867b 100644 --- a/examples/go/blaxel/process/run-blaxel-check-exit/README.md +++ b/examples/go/blaxel/process/run-blaxel-check-exit/README.md @@ -4,7 +4,7 @@ Run a command that exits non-zero and read the exit code from the result. ## What this means -A non-zero exit is not an SDK error. Applications check `exitCode` / `GetExitCode`. +A non-zero exit is not an SDK error. Check `GetExitCode` on the result. ## Edge cases diff --git a/examples/go/blaxel/process/run-blaxel-command/README.md b/examples/go/blaxel/process/run-blaxel-command/README.md index c4752777..b6589b83 100644 --- a/examples/go/blaxel/process/run-blaxel-command/README.md +++ b/examples/go/blaxel/process/run-blaxel-command/README.md @@ -4,7 +4,7 @@ Run one argv command in a Blaxel sandbox and print exit code plus streams. ## What this means -`Process.Run` is blocking. Set exactly one of `argv` or `shell`. Argv is sent as a POSIX-quoted shell string. +`Process().Run` waits until the command finishes. Set exactly one of `Argv` or `Shell`. `Argv` is sent as one quoted shell command string. ## Edge cases diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/README.md b/examples/go/blaxel/process/run-blaxel-with-timeout/README.md index 053c126d..fe03fe1b 100644 --- a/examples/go/blaxel/process/run-blaxel-with-timeout/README.md +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/README.md @@ -1,13 +1,12 @@ # Run a Blaxel command with timeout -Cap a Blaxel Process.Run with a whole-second timeout. +Cap a Blaxel `Process().Run` with a whole-second timeout. ## What this means -Command timeouts must be whole seconds. This run uses 5 seconds, so Blaxel -sets process auto-kill and Kit uses one native wait with that budget. Omit or -zero adds no process auto-kill and waits until the command ends or the call -is canceled. +Command timeouts must be whole seconds. This run uses 5 seconds and caps how +long the command may run. Omit or zero waits until the command ends (or you +cancel), with no Blaxel process time limit. ## Edge cases diff --git a/examples/typescript/README.md b/examples/typescript/README.md index 54a0b9fe..03daac26 100644 --- a/examples/typescript/README.md +++ b/examples/typescript/README.md @@ -146,7 +146,7 @@ Blaxel needs an API key, workspace, and client region: | [remove-blaxel-file](blaxel/filesystem/remove-blaxel-file/README.md) | remove one path | | [get-blaxel-sandbox](blaxel/lifecycle/get-blaxel-sandbox/README.md) | remote retrieval and metadata | | [refresh-blaxel-sandbox](blaxel/lifecycle/refresh-blaxel-sandbox/README.md) | metadata refresh | -| [delete-blaxel-sandbox](blaxel/lifecycle/delete-blaxel-sandbox/README.md) | waited deletion | +| [delete-blaxel-sandbox](blaxel/lifecycle/delete-blaxel-sandbox/README.md) | delete by name | | [close-blaxel-handle](blaxel/lifecycle/close-blaxel-handle/README.md) | close the handle; sandbox keeps running | | [handle-blaxel-errors](blaxel/errors/handle-blaxel-errors/README.md) | typed validation errors | diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/README.md b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/README.md index ec9a7b1f..97cc74ce 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/README.md @@ -8,8 +8,7 @@ Sets `Config.Timeout` / `timeout` on the shared client. This bounds local waitin ## Edge cases -- Kit `timeout` / `provisioning.timeout` is the local create deadline. -- Blaxel's native creation timeout (1–50s on `@blaxel/core`) is not forwarded. +- Client `timeout` is how long create may take. ## Run diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/README.md b/examples/typescript/blaxel/configuration/create-blaxel-idle/README.md index 80a76660..ff9b7d40 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-idle/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/README.md @@ -4,7 +4,7 @@ Create one Blaxel sandbox that deletes after an idle period. ## What this means -Maps `lifetime.idleTerminate` (`PolicyModeAfter`) to a Blaxel `ttl-idle` expiration policy with action delete. +Set `lifetime.idleTerminate` with `after` and a delay (≥1 minute). The sandbox deletes after that idle period. You cannot disable idle terminate. ## Edge cases diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md index 34e61c14..f0fdc4d4 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/README.md @@ -4,11 +4,10 @@ Create one Blaxel sandbox from a registry image with an explicit name. Prints th ## What this means -Blaxel create requires a Blaxel-valid `name` (lowercase alphanumeric with -single hyphens, at most 49 characters), an image source, and client `region` -(examples read `BL_REGION`, default `us-pdx-1`). Kit calls native create (not -create-if-not-exists). Closing the Kit client only drops the local SDK -connection. +Create needs a Blaxel-valid `name` (lowercase alphanumeric with single hyphens, +at most 49 characters), an image, and client `region` (examples read +`BL_REGION`, default `us-pdx-1`). If the name already exists, you get +`AlreadyExists`. Closing the client does not delete the sandbox. ## Edge cases diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/README.md index ed063530..58aacdef 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/README.md @@ -4,7 +4,7 @@ Create one Blaxel sandbox that allows selected outbound domains. ## What this means -Maps `network.outboundDomains` to Blaxel `allowedDomains`. Other egress controls are unsupported. +Set `network.outboundDomains` to allow those hosts. Other egress controls are unsupported on Blaxel. ## Edge cases diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/README.md index 3b78ed50..4ca86597 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/README.md @@ -4,7 +4,7 @@ Create one Blaxel sandbox with labels and environment variables. ## What this means -Labels become Blaxel metadata labels. Environment entries are injected into the sandbox runtime. +Set `labels` and create-time environment variables. They appear as sandbox metadata and runtime env. ## Edge cases diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md index 20a57e3f..b03a2b12 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/README.md @@ -4,8 +4,8 @@ Create one Blaxel sandbox that publishes selected ports. ## What this means -Maps `network.ports` to Blaxel runtime ports. Default leaves the protocol to -Blaxel (HTTP). Plain → TCP, TLS → TLS. HTTP/2 over TLS is rejected. +Publish ports with `network.ports`. Default protocol is HTTP; `plain` is TCP; +`tls` is TLS. HTTP/2 over TLS is not supported. ## Edge cases diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/README.md index 20d973a8..6cf1ed43 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/README.md @@ -4,7 +4,7 @@ Create one Blaxel sandbox with an explicit memory size in MiB. Blaxel derives CP ## What this means -Only `resources.memoryMib` is mapped. CPU cores, limits, and disk MiB overrides are rejected. Omitted memory keeps the Blaxel default. +You can set `resources.memoryMib` only. CPU cores, limits, and disk overrides are rejected. Omit memory to use Blaxel's default. ## Edge cases diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/README.md index 6dd0a4e8..681637f8 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/README.md @@ -4,7 +4,7 @@ Create one Blaxel sandbox that deletes after a maximum lifetime TTL. ## What this means -Maps `lifetime.maximumLifetime` to Blaxel max-age TTL. Durations must be whole seconds and meet Blaxel minima (5 minutes for max-age). +Set `lifetime.maximumLifetime` (whole seconds, at least 5 minutes). The sandbox deletes when that max age elapses. ## Edge cases diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/README.md b/examples/typescript/blaxel/errors/handle-blaxel-errors/README.md index 50d0d5e6..0bc9b30c 100644 --- a/examples/typescript/blaxel/errors/handle-blaxel-errors/README.md +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/README.md @@ -4,7 +4,7 @@ Demonstrate typed validation errors from the Blaxel provider without creating a ## What this means -Requests an unsupported CPU override so Kit fails before calling Blaxel. Applications inspect `SandboxError` kind and field. +Requests an unsupported CPU override so validation fails before any cloud call. Inspect `SandboxError` `kind` and `field`. ## Edge cases @@ -36,8 +36,8 @@ BL_REGION= ## Warnings -- Acts on **real** Blaxel resources when credentials succeed. -- Closing the handle leaves the sandbox as it is. Closing the client closes your connection to the provider. +- Validation fails locally for the unsupported CPU field; no sandbox is created. +- Closing the client closes your connection to the provider. ## Related diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md index f09f82c0..f14f2a09 100644 --- a/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md @@ -4,7 +4,8 @@ Stat one absolute path in a Blaxel sandbox. ## What this means -Returns type and size for the path. +Returns whether the path is a file or a directory, plus size and mode when +Blaxel provides them. ## Edge cases diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md index 4fdaa8a5..20219121 100644 --- a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md @@ -1,10 +1,12 @@ # Delete a Blaxel sandbox -Delete an existing Blaxel sandbox by name and wait for completion. +Delete an existing Blaxel sandbox by name. ## What this means -`Delete` permanently removes the Blaxel sandbox. Start, stop, pause, archive, and terminate are unsupported. +`delete` permanently removes the Blaxel sandbox. When the call succeeds you get +`deleted` / `terminal: true`. Wait before recreating the same name, or you may +get `Conflict`. Start, stop, pause, archive, and terminate are unsupported. ## Edge cases diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md index f1992552..c5dadaa5 100644 --- a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/README.md @@ -4,7 +4,7 @@ Look up an existing Blaxel sandbox by name and print metadata. ## What this means -`Get` retrieves the sandbox by name (the Blaxel id). Use the name printed by a create example as `BL_SANDBOX_NAME`. +`client.get` loads the sandbox by name (the Blaxel id). Use the name printed by a create example as `BL_SANDBOX_NAME`. ## Edge cases diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts index 716b0a2c..781cda82 100644 --- a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts @@ -14,5 +14,5 @@ await using client = new Client({ const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); const info = sandbox.info(); console.log( - `Blaxel sandbox: id=${info.id} state=${info.state} native=${info.providerState ?? ""} capabilities=${sandbox.capabilities().join(",")}`, + `Blaxel sandbox: id=${info.id} state=${info.state} terminal=${info.terminal ?? false} capabilities=${sandbox.capabilities().join(",")}`, ); diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md index 081c167b..fc8fcfac 100644 --- a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/README.md @@ -4,11 +4,11 @@ Refresh metadata for an existing Blaxel sandbox by name. ## What this means -`Refresh` re-fetches provider metadata onto the handle. +`sandbox.refresh` updates `info()` on this handle (state, labels, and related metadata). ## Edge cases -- Unsupported when the handle does not report the refresh capability. +- If this handle does not support refresh, you get `Unsupported`. ## Run diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/README.md b/examples/typescript/blaxel/process/run-blaxel-check-exit/README.md index b7a3bc69..d6519273 100644 --- a/examples/typescript/blaxel/process/run-blaxel-check-exit/README.md +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/README.md @@ -4,7 +4,7 @@ Run a command that exits non-zero and read the exit code from the result. ## What this means -A non-zero exit is not an SDK error. Applications check `exitCode` / `GetExitCode`. +A non-zero exit is not an SDK error. Check `exitCode` on the result. ## Edge cases diff --git a/examples/typescript/blaxel/process/run-blaxel-command/README.md b/examples/typescript/blaxel/process/run-blaxel-command/README.md index 919567a5..bd77ba12 100644 --- a/examples/typescript/blaxel/process/run-blaxel-command/README.md +++ b/examples/typescript/blaxel/process/run-blaxel-command/README.md @@ -4,7 +4,7 @@ Run one argv command in a Blaxel sandbox and print exit code plus streams. ## What this means -`Process.Run` is blocking. Set exactly one of `argv` or `shell`. Argv is sent as a POSIX-quoted shell string. +`sandbox.process.run` waits until the command finishes. Set exactly one of `argv` or `shell`. `argv` is sent as one quoted shell command string. ## Edge cases diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md b/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md index c8d766d4..440b2ea1 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/README.md @@ -1,13 +1,12 @@ # Run a Blaxel command with timeout -Cap a Blaxel Process.Run with a whole-second timeout. +Cap a Blaxel `sandbox.process.run` with a whole-second timeout. ## What this means -Command timeouts must be whole seconds. This run uses 5 seconds, so Blaxel -sets process auto-kill and Kit uses one native wait with that budget. Omit or -zero adds no process auto-kill and waits until the command ends or the call -is canceled. +Command timeouts must be whole seconds. This run uses 5 seconds and caps how +long the command may run. Omit or zero waits until the command ends (or you +cancel), with no Blaxel process time limit. ## Edge cases diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 9e4da2a1..7e4c9dc1 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -3,8 +3,8 @@ Create and manage Blaxel sandboxes with the shared sandbox client. Requires Go 1.26.0 or later and a Blaxel API key. Set `Auth` to an API key and -`Scope.Workspace` to the workspace name. If either is omitted, the Blaxel SDK -uses `BL_API_KEY` and `BL_WORKSPACE` from the environment when present. +`Scope.Workspace` to the workspace name. If either is omitted, the client uses +`BL_API_KEY` and `BL_WORKSPACE` from the environment when present. `Config.Region` is required for create. ## Install @@ -60,58 +60,68 @@ sandbox. Call `Delete`, or set a lifetime policy. `Config.Endpoint` is not supported. Token pair, OAuth, and bearer auth are rejected. `Scope` fields other than `Workspace` are rejected. -## Operations - -Create from a registry image with an explicit name. The name must be lowercase -alphanumeric with single hyphens and at most 49 characters. You can set labels, -environment variables, memory, ports, outbound domains, maximum lifetime, and -idle terminate. Max-age TTL must be at least 5m; idle TTL must be at least 1m. -`PolicyModeDisabled` on idle terminate is rejected. Ports 80, 443, and 8080 are -rejected (Blaxel-reserved). Default port transport leaves the protocol to -Blaxel (HTTP); plain maps to TCP; TLS maps to TLS. HTTP/2 over TLS is rejected. -Kit `Config.Timeout` / create call timeout is the create deadline. The Go -Blaxel SDK has no separate creation-timeout argument. `WaitFor` accepts only -Default and Scheduled; `WaitConditionReady` is rejected. Create always uses -the native create path (not create-if-not-exists). - -On the handle: `Get`, `Refresh`, `Delete`, and `Close`. `Get` and `Refresh` look -up the sandbox by name (`ID()` equals the create name). `Attach`, `Start`, -`Stop`, `Pause`, `Archive`, and `Terminate` are not supported. `Close` does not -contact Blaxel and does not change the sandbox. A successful `Delete` is -reported as `Deleted` (Blaxel may still finish removal asynchronously). -`Refresh` / `Info` map Blaxel deployment `DEPLOYED` plus runtime `STANDBY` to -Kit `Paused` (and `RUNNING` to `Running`) via the Go SDK's public -`Sandbox.State`. A create that collides on name maps to `AlreadyExists` -(`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409 on create). A bare 409 on other -operations is `Conflict`. Coded conflicts such as -`SANDBOX_DELETION_IN_PROGRESS` stay `Conflict`. - -`Filesystem` supports the shared file calls: `ReadFile`, `WriteFile`, -`CopyFromLocal`, `CopyToLocal`, `MakeDirectory`, `ListDirectory`, `Stat`, and -`Remove`. Paths in the sandbox are absolute. A file timeout is the context -deadline. `Parents` defaults to true. `Parents` set to false is rejected. -`Stat` uses the sandbox filesystem GET (the same underlying call as `LS`). -`SandboxInstanceFSService` has no public Get/Stat, and `LS` always treats the -response as a directory, so Kit rebuilds the lower filesystem client against -the instance URL for metadata. A missing file passed to `CopyFromLocal` is not -found, and the error field is `local_path`. A local directory is an invalid -argument with the same field. `CopyToLocal` creates a missing local parent -directory. - -`Process().Run` runs one command and waits for the result. Set exactly one of -`Argv` or `Shell`. `Argv` is sent as a POSIX-quoted shell string. A non-zero -exit code is success for the SDK—read `GetExitCode`. Blaxel returns stdout and -stderr. Kit starts without Blaxel's completion hold and uses one native -`Process.Wait`. Omit or zero `Timeout` for no Blaxel process auto-kill and an -unlimited wait (context can still cancel). A positive timeout sets auto-kill -and the wait budget. Command timeouts must be whole seconds. Cancel the -context to stop waiting; the remote command may keep running. An HTTP 504 -during the wait is a timeout error. - -`resources.memory_mib` maps 1:1 onto Blaxel's memory field. Blaxel labels that -integer MB; Kit does not convert. Blaxel derives CPU from that value, so -`cpu_cores` and disk overrides are rejected. Kit has no portable volume -fields; Blaxel volumes are outside create. +## What you can do + +### Create + +You need a name and an image. Names are lowercase alphanumeric with single +hyphens (max 49 characters). You can also set labels, environment variables, +memory, ports, outbound domains, maximum lifetime (≥5 minutes), and idle +terminate (≥1 minute). You cannot disable idle terminate. Ports 80, 443, and +8080 are reserved. Default port transport is HTTP; plain is TCP; TLS is TLS; +HTTP/2 over TLS is not supported. + +Your client/create timeout is how long create may take. Ready wait and +readiness probes are not supported (`WaitFor` only Default or Scheduled). + +Set `Resources.MemoryMib` for memory size. Blaxel documents that value as MB; +Kit does not convert units. CPU follows memory on Blaxel — do not set CPU or +disk overrides. If you omit memory, Blaxel applies its default. Volumes are not +part of create. + +If the name already exists, you get `AlreadyExists`. + +### Lifecycle + +You can `Get`, `Refresh`, `Delete`, and `Close`. Lookup is by name +(`ID()` equals the create name). Attach, start, stop, pause, archive, and +terminate are not available. `Close` only drops the local handle. + +After a successful `Delete`, the handle reports `Deleted` and `terminal`. +Blaxel can still be cleaning up — recreating the same name right away may +return `Conflict`. + +On `Get` / `Refresh` / `Info`, an idle sandbox shows as `Paused`; an active +one shows as `Running`. + +### Filesystem + +You get the usual file APIs: read, write, copy in/out, mkdir, list, stat, and +remove. Paths must be absolute. `Parents` defaults to true; `Parents: false` +is rejected. + +`Stat` returns whether the path is a file or a directory, plus size and mode +when Blaxel provides them. Missing local files for `CopyFromLocal` return not +found with field `local_path`. + +### Process + +`Process().Run` runs one command and waits for it to finish. Set exactly one of +`Argv` or `Shell`. `Argv` is sent as one quoted shell command string. A +non-zero exit code is not an error — read `GetExitCode`. You get both stdout +and stderr. + +Omit or zero `Timeout` to wait without a Blaxel process time limit (your +context can still cancel). A positive timeout must be whole seconds and caps +how long the command may run. Canceling the context stops waiting; the remote +command may keep running. If the wait times out, you get a timeout error. + +### Errors you will see + +| Situation | Kind | +| ------------------------------------------ | --------------- | +| Create when the name already exists | `AlreadyExists` | +| Conflict while a delete is still finishing | `Conflict` | ## Examples diff --git a/sdks/go/providers/blaxel/doc.go b/sdks/go/providers/blaxel/doc.go index 41906517..46ecc05c 100644 --- a/sdks/go/providers/blaxel/doc.go +++ b/sdks/go/providers/blaxel/doc.go @@ -3,12 +3,13 @@ // Pass [New] to sandbox.NewClient. Create requires a Blaxel-valid sandbox name // (lowercase alphanumeric with single hyphens, at most 49 characters), an // image source, and [sandbox.Config.Region]. Set Scope.Workspace and an API -// key, or omit them so the Blaxel SDK can use BL_API_KEY and BL_WORKSPACE from -// the environment. The backend implements shared filesystem operations through +// key, or omit them to use BL_API_KEY and BL_WORKSPACE from the environment. +// The backend implements shared filesystem operations through // [sandbox.SandboxFiles] and blocking command execution through // [sandbox.SandboxProcess]. Get and Refresh look up a sandbox by name. Delete // removes the Blaxel resource. Closing the client releases local provider // state and leaves the Blaxel sandbox running. Start, Stop, Pause, Archive, -// Attach, and Terminate are unsupported. resources.memory_mib maps 1:1 onto -// Blaxel's memory field; Kit has no portable volume fields. +// Attach, and Terminate are unsupported. Set resources.memory_mib for memory +// size; Blaxel documents that value as MB and Kit does not convert units. +// Volumes are not part of create. package blaxel diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index 89a1bf66..72cbcdf7 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -34,78 +34,86 @@ const sandbox = await client.create({ console.log(sandbox.id); // same as create name; use with get / BL_SANDBOX_NAME ``` -Closing the client releases local SDK resources only. Reclaim sandboxes with -TTL / idle policies or `delete`. +Closing the client releases local resources only. Reclaim sandboxes with TTL / +idle policies or `delete`. ## Configuration -| Setting | Notes | -| -------------- | ----------------------------------------- | -| API key | `auth: { apiKey: { key } }` | -| Omitted `auth` | Blaxel SDK environment / CLI credentials | -| Workspace | `scope.workspace` — Blaxel workspace name | -| Region | `region` — required for create | -| Endpoint | Not supported | +| Setting | Notes | +| -------------- | ------------------------------------------------------- | +| API key | `auth: { apiKey: { key } }` | +| Omitted `auth` | Uses `BL_API_KEY` / `BL_WORKSPACE` from the environment | +| Workspace | `scope.workspace` — Blaxel workspace name | +| Region | `region` — required for create | +| Endpoint | Not supported | -Token-pair, bearer, and OAuth client modes are rejected. Scope fields other -than `workspace` are rejected. +Token-pair, bearer, and OAuth are rejected. Only `scope.workspace` is accepted +on scope. + +In TypeScript, use one Blaxel client (or one API key) per process. A second +client with different credentials replaces the first. ## What you can do -**Create** requires a Blaxel-valid `name` (lowercase alphanumeric with single -hyphens, at most 49 characters), an image source, and client `region`. -Optional `memoryMib` maps 1:1 onto Blaxel's memory field (Blaxel labels that -integer MB; Kit does not convert; Blaxel CPU follows memory), labels, -environment variables, ports, outbound domains, max-age TTL (at least 5m), and -idle-delete policy (at least 1m; explicit disable is rejected). Client / -create `timeout` is the Kit deadline. `@blaxel/core`'s native creation timeout -(1–50s) is not forwarded. `provisioning.waitFor` accepts only `Default` and -`Scheduled`; Ready (and Started / Submitted) are unsupported. Creation calls -native `SandboxInstance.create`, not `createIfNotExists`. Ports -80, 443, and 8080 are rejected (Blaxel-reserved). Default port transport leaves -the protocol to Blaxel (HTTP); plain maps to TCP; TLS maps to TLS. HTTP/2 over -TLS is rejected. Kit has no portable volume fields; Blaxel volumes are outside -create. - -**Lifecycle** on the handle: `get` / `refresh` (lookup by name), `delete`, and -`close`. `close` does not delete the sandbox. A successful `delete` is reported -as `deleted` (Blaxel may still finish removal asynchronously). Start, stop, -pause, archive, terminate, and attach are unsupported. `refresh` / `info` map -Blaxel deployment `DEPLOYED` plus runtime `STANDBY` to Kit `paused` (and -`RUNNING` to `running`). `@blaxel/core` types `SandboxInstance.sandbox` private -and has no `state` getter; the provider projects wire runtime state onto a -Kit-facing `state` field at the native boundary. A create that collides on name -maps to `AlreadyExists` (`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409 on create). -A bare 409 on other operations is `Conflict`. Coded conflicts such as -`SANDBOX_DELETION_IN_PROGRESS` stay `Conflict`. - -**Files** through `sandbox.filesystem`: `readFile`, `writeFile`, -`copyFromLocal`, `copyToLocal`, `makeDirectory`, `listDirectory`, `stat`, and -`remove`. Paths in the sandbox are absolute. `parents` defaults to true; -`parents: false` is rejected. `stat` uses Blaxel `fs.ls` (GET-by-path): a -directory listing succeeds; a file path returns Blaxel's "Directory not found" -response and Kit reports a file (no parent listing). A missing file passed to -`copyFromLocal` is not found before Blaxel is called, and the error field is -`local_path`. A local directory is an invalid argument with the same field. -`copyToLocal` creates a missing local parent directory. - -**Commands** through `sandbox.process.run`. Set exactly one of `argv` or -`shell`. `argv` is sent as a POSIX-quoted shell string. `run` waits until the -command ends. A non-zero exit code is not an SDK error—check `exitCode`. -Blaxel returns stdout and stderr. Kit starts without Blaxel's completion hold -and uses one native `process.wait`. Omit or zero `timeout` for no process -auto-kill and an unlimited wait (signal can still abort). A positive timeout -sets auto-kill and the wait budget. Command timeouts must be whole seconds -(`2000` is two seconds; `1500` is rejected). Aborting the signal rejects the -`await`; the remote command may keep running. An HTTP 504 during the wait is a -timeout error. - -Omitted memory keeps the Blaxel default. CPU, CPU limit, memory limit, and -disk overrides are rejected. - -`@blaxel/core` applies credentials through process-global `settings`. Use one -Blaxel Kit client, or one credential set, per process; a second client -overwrites the first. +### Create + +You need a valid `name` (lowercase alphanumeric with single hyphens, max 49 +characters), an image, and `region`. You can also set `memoryMib`, labels, +environment variables, ports, outbound domains, max-age TTL (≥5 minutes), and +idle-delete (≥1 minute). You cannot disable idle terminate. If you omit +memory, Blaxel applies its default. CPU and disk overrides are rejected — +Blaxel derives CPU from memory. Set `memoryMib` for memory size; Blaxel +documents that value as MB and Kit does not convert units. + +Your client/create `timeout` is how long create may take. Ready wait is not +supported (`waitFor` only Default or Scheduled). Ports 80, 443, and 8080 are +reserved. Default port transport is HTTP; plain is TCP; TLS is TLS; HTTP/2 +over TLS is not supported. Volumes are not part of create. + +If the name already exists, you get `AlreadyExists`. + +### Lifecycle + +You can `get` / `refresh` (by name), `delete`, and `close`. `close` does not +delete the sandbox. Start, stop, pause, archive, terminate, and attach are not +available. + +After a successful `delete`, you get `state: "deleted"` and `terminal: true`. +Blaxel can still be cleaning up — recreating the same name right away may +return `Conflict`. + +On `get` / `refresh` / `info`, an idle sandbox shows as `paused`; an active one +shows as `running`. + +### Filesystem + +You get the usual file APIs: `readFile`, `writeFile`, `copyFromLocal`, +`copyToLocal`, `makeDirectory`, `listDirectory`, `stat`, and `remove`. Paths +must be absolute. `parents` defaults to true; `parents: false` is rejected. + +For directories, `stat` returns type directory. For files, `stat` returns type +file with size and permission fields when Blaxel provides them. +Missing local files for `copyFromLocal` return not found with field +`local_path`. + +### Process + +`sandbox.process.run` waits until the command finishes. Set exactly one of +`argv` or `shell`. `argv` is sent as one quoted shell command string. A non-zero exit code is not +an error — check `exitCode`. You get both stdout and stderr. + +Omit or zero `timeout` to wait without a Blaxel process time limit (your +signal can still abort). A positive timeout must be whole seconds (`2000` is +two seconds; `1500` is rejected) and caps how long the command may run. +Aborting the signal stops waiting; the remote command may keep running. If the +wait times out, you get a timeout error. + +### Errors you will see + +| Situation | Kind | +| ------------------------------------------ | --------------- | +| Create when the name already exists | `AlreadyExists` | +| Conflict while a delete is still finishing | `Conflict` | ## Examples @@ -117,6 +125,7 @@ overwrites the first. | Read a file | [read-blaxel-file](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem/read-blaxel-file) | | Filesystem | [filesystem examples](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/filesystem) | | Retrieval | [get-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox) | +| Delete | [delete-blaxel-sandbox](https://github.com/sandbox-kit/kit/tree/main/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox) | | Full index | [TypeScript examples](https://github.com/sandbox-kit/kit/tree/main/examples/typescript) | ## License diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 04a9fc57..c00cb8a6 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -1050,9 +1050,9 @@ describe("Blaxel provider orchestration", () => { }); describe("Blaxel filesystem.stat", () => { - it("returns directory metadata from ls on the path", async () => { + it("returns directory metadata from get on the path", async () => { const backend = fsBackend({ - async ls(path) { + async get(path) { expect(path).toBe("/app"); return { name: "app", @@ -1070,11 +1070,19 @@ describe("Blaxel filesystem.stat", () => { }); }); - it("reports a file when ls rejects a file path", async () => { + it("returns file metadata including size from get", async () => { const backend = fsBackend({ - async ls(path) { + async get(path) { expect(path).toBe("/app/readme.txt"); - throw new Error(JSON.stringify({ error: "Directory not found" })); + return { + name: "readme.txt", + path: "/app/readme.txt", + size: 12, + permissions: "0644", + owner: "user", + group: "user", + lastModified: "2024-01-02T03:04:05Z", + }; }, }); await expect( @@ -1083,13 +1091,16 @@ describe("Blaxel filesystem.stat", () => { name: "readme.txt", path: "/app/readme.txt", type: FileType.File, - size: 0n, + size: 12n, + permissions: "0644", + owner: "user", + group: "user", }); }); - it("propagates non-file ls errors", async () => { + it("propagates get errors", async () => { const missing = fsBackend({ - async ls() { + async get() { throw new Error("boom"); }, }); @@ -1100,20 +1111,31 @@ describe("Blaxel filesystem.stat", () => { }); function fsBackend(fs: { - ls(path: string): Promise<{ - name: string; - path: string; - files: Array<{ - name: string; - path: string; - size: number; - permissions?: string; - owner?: string; - group?: string; - lastModified?: string; - }>; - subdirectories: Array<{ name: string; path: string }>; - }>; + get(path: string): Promise< + | { + name: string; + path: string; + files: Array<{ + name: string; + path: string; + size: number; + permissions?: string; + owner?: string; + group?: string; + lastModified?: string; + }>; + subdirectories: Array<{ name: string; path: string }>; + } + | { + name: string; + path: string; + size: number; + permissions?: string; + owner?: string; + group?: string; + lastModified?: string; + } + >; }) { return new BlaxelProvider(() => ({ configure() {}, diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index 33ecdb69..f1a95f0d 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -327,11 +327,11 @@ function throwIfAborted(signal: AbortSignal | undefined): void { * Selects Blaxel as the provider for the shared Sandbox Kit client. * * Create needs `name`, an image source, and client `region`. Set - * `auth.apiKey` and `scope.workspace`, or omit them so `@blaxel/core` can use - * `BL_API_KEY` / `BL_WORKSPACE`. `memoryMib` maps 1:1 onto Blaxel's memory - * field; Kit has no portable volume fields. `@blaxel/core` stores credentials - * in process-global `settings` — use one Blaxel Kit client, or one credential - * set, per process. + * `auth.apiKey` and `scope.workspace`, or omit them to use `BL_API_KEY` / + * `BL_WORKSPACE` from the environment. Set `memoryMib` for memory size; + * Blaxel documents that value as MB and Kit does not convert units. Volumes + * are not part of create. In TypeScript, use one Blaxel client (or one API + * key) per process. */ export function blaxel(): Provider { return new BlaxelProvider(); diff --git a/sdks/typescript/providers/blaxel/src/filesystem.ts b/sdks/typescript/providers/blaxel/src/filesystem.ts index d984deb9..53a46728 100644 --- a/sdks/typescript/providers/blaxel/src/filesystem.ts +++ b/sdks/typescript/providers/blaxel/src/filesystem.ts @@ -1,9 +1,6 @@ import { basename } from "node:path"; import type { Directory, File as BlaxelFile } from "@blaxel/core"; -import { - FileType, - type FileInfo, -} from "@sandbox-kit/sandbox"; +import { FileType, type FileInfo } from "@sandbox-kit/sandbox"; import type { NativeSandboxHandle } from "./native.js"; type BlaxelSubdirectory = Directory["subdirectories"][number]; @@ -32,40 +29,33 @@ export function directoryInfo(info: BlaxelSubdirectory): FileInfo { } /** - * Blaxel has no Stat. `fs.ls` GETs the path: a directory listing succeeds; a - * file body makes ls throw "Directory not found". Report that as a File — - * one request, no parent-listing workaround (Go uses the same GET via a lower client). + * Stat one path. Uses the same filesystem GET as Go: a directory listing or + * file metadata (size, permissions when Blaxel supplies them). */ export async function blaxelStat( fs: NativeSandboxHandle["fs"], path: string, ): Promise { - try { - const listing = await fs.ls(path); + const data = await fs.get(path); + if (isDirectoryListing(data)) { return { - name: listing.name || basename(path) || "/", - path: listing.path || path, + name: data.name || basename(path) || "/", + path: data.path || path, type: FileType.Directory, size: 0n, }; - } catch (error) { - if (!isBlaxelFilePath(error)) throw error; - return { - name: basename(path) || path, - path, - type: FileType.File, - size: 0n, - }; } + return fileInfo({ + name: data.name || basename(path) || path, + path: data.path || path, + size: data.size, + permissions: data.permissions ?? "", + owner: data.owner ?? "", + group: data.group ?? "", + lastModified: data.lastModified ?? "", + }); } -/** Blaxel `fs.ls` throws this when GET returns a file instead of a directory. */ -function isBlaxelFilePath(error: unknown): boolean { - if (!(error instanceof Error)) return false; - try { - const body = JSON.parse(error.message) as { error?: unknown }; - return body.error === "Directory not found"; - } catch { - return false; - } +function isDirectoryListing(data: Directory | BlaxelFile): data is Directory { + return "files" in data || "subdirectories" in data; } diff --git a/sdks/typescript/providers/blaxel/src/native.ts b/sdks/typescript/providers/blaxel/src/native.ts index 9d1c24d7..f7f0f507 100644 --- a/sdks/typescript/providers/blaxel/src/native.ts +++ b/sdks/typescript/providers/blaxel/src/native.ts @@ -1,10 +1,14 @@ import { + getFilesystemByPath, + ResponseError, SandboxInstance, settings, type Config as NativeClientConfig, type Directory, + type File as BlaxelFile, type SandboxCreateConfiguration, type SandboxCreateOptions, + type SandboxFileSystem, } from "@blaxel/core"; import type { BlaxelSandboxIdentity } from "#mappings/provider_mappings.js"; @@ -31,6 +35,8 @@ export interface NativeSandboxHandle extends BlaxelSandboxIdentity { writeBinary(path: string, content: Uint8Array): Promise; mkdir(path: string, permissions?: string): Promise; ls(path: string): Promise; + /** GET path: directory listing or file metadata (size, permissions, …). */ + get(path: string): Promise; rm(path: string, recursive?: boolean): Promise; download(src: string, destinationPath: string): Promise; }; @@ -81,12 +87,68 @@ export function defaultNativeFactory(config: NativeClientConfig): NativeBlaxel { /** * `@blaxel/core` types `SandboxInstance.sandbox` private and has no `state` * getter. Project runtime STANDBY/RUNNING onto a Kit-facing `state` field so - * identity mapping never reaches into the private wire model. + * identity mapping never reaches into the private wire model. Wrap `fs` with + * `get` so Stat can use the same filesystem GET as Go. */ export function projectHandle(instance: SandboxInstance): NativeSandboxHandle { const state = wireRuntimeState(instance); - if (state === undefined) return instance as unknown as NativeSandboxHandle; - return Object.assign(instance, { state }) as unknown as NativeSandboxHandle; + const base = + state === undefined ? instance : Object.assign(instance, { state }); + return Object.assign(base, { + fs: wrapFilesystem(instance.fs), + }) as unknown as NativeSandboxHandle; +} + +function wrapFilesystem(fs: SandboxFileSystem): NativeSandboxHandle["fs"] { + return { + readBinary: (path) => fs.readBinary(path), + writeBinary: (path, content) => fs.writeBinary(path, content), + mkdir: (path, permissions) => fs.mkdir(path, permissions), + ls: (path) => fs.ls(path), + rm: (path, recursive) => fs.rm(path, recursive), + download: (src, destinationPath) => fs.download(src, destinationPath), + get: (path) => filesystemGet(fs, path), + }; +} + +async function filesystemGet( + fs: SandboxFileSystem, + path: string, +): Promise { + const { response, data, error } = await getFilesystemByPath({ + path: { path }, + baseUrl: fs.url, + client: fs.client, + }); + if (!response.ok || data == null) { + throw new ResponseError(response, data, error); + } + if (isDirectoryListing(data)) return data; + if (isFileMetadata(data)) { + const { content: _content, ...meta } = data as BlaxelFile & { + content?: string; + }; + return meta; + } + throw new Error("Unsupported filesystem path type"); +} + +function isDirectoryListing(data: unknown): data is Directory { + return ( + !!data && + typeof data === "object" && + ("files" in data || "subdirectories" in data) + ); +} + +function isFileMetadata(data: unknown): data is BlaxelFile { + return ( + !!data && + typeof data === "object" && + "size" in data && + !("files" in data) && + !("subdirectories" in data) + ); } function wireRuntimeState(instance: SandboxInstance): string | undefined { diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index fed64d1c..183e2283 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -53,7 +53,7 @@ const sandbox = await client.create({ console.log(sandbox.id); // same as create name; use with get / BL_SANDBOX_NAME ``` -`timeout` is milliseconds. Prefer `await using`; otherwise call `await client.close()` when finished. +`timeout` is milliseconds. Prefer `await using`; otherwise call `await client.close()` when finished. Use one Blaxel client (or one API key) per process — a second client with different credentials replaces the first. @@ -105,20 +105,22 @@ Optional strings use `sandbox.Value`. -Omit `auth` and `scope.workspace` to let the Blaxel SDK pick credentials from its environment (`BL_API_KEY`, `BL_WORKSPACE`). `region` is still required for create. `endpoint` is rejected. Closing the client does not delete the sandbox. +Omit `auth` and `scope.workspace` to use `BL_API_KEY` and `BL_WORKSPACE` from +the environment. `region` is still required for create. `endpoint` is rejected. +Closing the client does not delete the sandbox. ## Configuration -| Setting | Notes | -| -------------- | -------------------------------------------------- | -| API key | `auth.apiKey` | -| Omitted `auth` | Blaxel SDK environment / CLI credentials | -| Workspace | `scope.workspace` — or `BL_WORKSPACE` when omitted | -| Region | `region` — required for create | -| Endpoint | Unsupported | +| Setting | Notes | +| -------------- | ------------------------------------------------------- | +| API key | `auth.apiKey` | +| Omitted `auth` | Uses `BL_API_KEY` / `BL_WORKSPACE` from the environment | +| Workspace | `scope.workspace` — or `BL_WORKSPACE` when omitted | +| Region | `region` — required for create | +| Endpoint | Unsupported | ```ts await using client = new Client({ @@ -157,33 +159,30 @@ client, err := sandbox.NewClient(blaxel.New(), config) -Token-pair, bearer, and OAuth client modes are rejected. Scope fields other than workspace are rejected. TypeScript: `@blaxel/core` stores credentials in process-global `settings` — use one Blaxel Kit client, or one credential set, per process. Full rules: [Configuration](/docs/configuration). +Token-pair, bearer, and OAuth are rejected. Only workspace scope is accepted. +In TypeScript, use one Blaxel client (or one API key) per process. Full rules: +[Configuration](/docs/configuration). ## What works -| Area | Notes | -| --------- | ------------------------------------------------------------------------------------------------- | -| Create | Valid name + image; memory 1:1, labels, env, ports, outbound domains, TTL/idle | -| Volumes | Not in Kit create; no portable volume fields | -| Open | `get` / `refresh` by sandbox name | -| Lifecycle | `delete`, `close` — no start/stop/pause/archive/terminate/attach | -| State | `DEPLOYED` + runtime `STANDBY` → Kit paused; `RUNNING` → running | -| Errors | Create name collision → AlreadyExists; bare 409 elsewhere / coded deletion-in-progress → Conflict | -| Files | Filesystem methods — [Filesystem](/docs/filesystem) | -| Process | Blocking `run` — [Run a command](/docs/process) | - -`resources.memoryMib` / `memory_mib` maps 1:1 onto Blaxel's memory field. -Blaxel labels that integer MB; Kit does not convert. Blaxel derives CPU from -memory, so CPU and disk overrides are rejected. Default port transport leaves -the protocol to Blaxel (HTTP); plain maps to TCP; TLS maps to TLS; HTTP/2 over -TLS is rejected. Client `timeout` is the Kit create/operation deadline. -`@blaxel/core`'s native creation timeout (1–50s) is not forwarded; the Go SDK -has no equivalent. Create never passes `@blaxel/core` `SandboxCreateOptions`. -`provisioning.waitFor` accepts only `Default` and `Scheduled`; `Ready` (and -`Started` / `Submitted`) are rejected. Readiness probes are unsupported. -Explicit idle-terminate disable is rejected. TypeScript projects runtime -standby from `@blaxel/core`'s private wire model onto a Kit-facing `state` -field (no public `state` getter). Go reads `Sandbox.State` on the instance. +| Area | Notes | +| --------- | -------------------------------------------------------------------------------- | +| Create | Name + image; memory, labels, env, ports, outbound domains, TTL/idle | +| Volumes | Not available on create | +| Open | `get` / `refresh` by sandbox name | +| Lifecycle | `delete`, `close` — no start/stop/pause/archive/terminate/attach | +| Delete | Succeeds as `deleted` / `terminal`; wait briefly before recreating the same name | +| State | Idle → paused; active → running | +| Errors | Name taken on create → AlreadyExists; delete still in progress → Conflict | +| Files | Shared filesystem methods — [Filesystem](/docs/filesystem) | +| Process | `run` waits for the command — [Run a command](/docs/process) | + +Set `memoryMib` / `memory_mib` for memory size. Blaxel documents that value as +MB; Kit does not convert units. CPU follows memory — CPU and disk overrides are +rejected. Omit memory to use Blaxel's default. Default ports are HTTP; plain is +TCP; TLS is TLS; HTTP/2 over TLS is not supported. Your client/create `timeout` +is how long create may take. Ready wait and readiness probes are not supported. +You cannot disable idle terminate. ## Related diff --git a/website/content/docs/configuration.mdx b/website/content/docs/configuration.mdx index b4e96410..8abe69df 100644 --- a/website/content/docs/configuration.mdx +++ b/website/content/docs/configuration.mdx @@ -342,13 +342,13 @@ client, err := sandbox.NewClient(modal.New(), config) | Setting | What to pass | | -------------- | ---------------------------------------- | | API key | `auth.apiKey.key` | -| Omitted `auth` | Blaxel SDK environment / CLI credentials | +| Omitted `auth` | Uses `BL_API_KEY` / `BL_WORKSPACE` from the environment | | Workspace | `scope.workspace` | | Region | `region` — required for create | | Endpoint | Unsupported | -`@blaxel/core` stores credentials in process-global `settings`. Use one Blaxel -Kit client, or one credential set, per process. +In TypeScript, use one Blaxel client (or one API key) per process. A second +client with different credentials replaces the first. ```ts await using client = new Client({ @@ -531,11 +531,9 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ - Blaxel uses the Kit deadline for create. `@blaxel/core` also accepts a native - creation timeout (1–50 whole seconds); this provider does not forward it. The - Go Blaxel SDK has no equivalent argument. Ready wait and readiness probes are - unsupported. TypeScript Blaxel clients share `@blaxel/core` process-global - `settings`: use one Blaxel Kit client, or one credential set, per process. + Your client/create `timeout` is how long create may take. Ready wait and + readiness probes are unsupported. In TypeScript, use one Blaxel client (or one + API key) per process. ## Credentials diff --git a/website/content/docs/create.mdx b/website/content/docs/create.mdx index 0adb21c0..8483ee92 100644 --- a/website/content/docs/create.mdx +++ b/website/content/docs/create.mdx @@ -273,7 +273,7 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ | --------- | ------------------------------------------------------------------------------------------------- | | Source | Registry image and Blaxel-valid `name` required (lowercase alphanumeric with single hyphens, ≤49) | | CPU | Derived by Blaxel from memory; Kit rejects CPU overrides | -| Memory | `memoryMib` / `memory_mib` passed 1:1; Blaxel labels the unit MB | +| Memory | `memoryMib` / `memory_mib` (Blaxel documents MB; Kit does not convert) | | Disk | Cannot be set | | Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m); explicit idle disable rejected | | Placement | Client `region` required. A placement block on this request is rejected | @@ -612,13 +612,13 @@ See [Examples](/docs/examples). Create needs a Blaxel-valid `name` (lowercase alphanumeric with single hyphens, at most 49 characters), an image, and client `region`. The returned id is that -name. Memory maps 1:1; CPU, disk, placement, readiness, runtime, and volumes -are rejected. Ports 80, 443, and 8080 are reserved. Default port transport -leaves the protocol to Blaxel (HTTP); plain maps to TCP; TLS maps to TLS; -HTTP/2 over TLS is rejected. Max-age TTL ≥5m; idle terminate ≥1m; explicit -idle disable is rejected. `waitFor` only `Default` / `Scheduled`; `Ready` is -rejected. Kit create timeout is the local deadline; Blaxel's native creation -timeout is not forwarded. See [Examples](/docs/examples). +name. Pass memory as `memoryMib` / `memory_mib`; CPU, disk, placement, +readiness, runtime, and volumes are rejected. Ports 80, 443, and 8080 are +reserved. Default ports are HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is +not supported. Max-age TTL ≥5m; idle terminate ≥1m; you cannot disable idle +terminate. `waitFor` only `Default` / `Scheduled`; Ready wait is not +supported. Your client/create `timeout` is how long create may take. See +[Examples](/docs/examples). diff --git a/website/content/docs/errors.mdx b/website/content/docs/errors.mdx index fee5f688..dccfaf44 100644 --- a/website/content/docs/errors.mdx +++ b/website/content/docs/errors.mdx @@ -300,11 +300,9 @@ See [Examples](/docs/examples). -A create that collides on name returns `AlreadyExists` -(`SANDBOX_ALREADY_EXISTS` or a bare HTTP 409 on create). A bare 409 on other -operations is `Conflict`. Coded conflicts such as -`SANDBOX_DELETION_IN_PROGRESS` stay `Conflict`. Use `get` before retry so you -know whether the sandbox exists (lookup by name). +If the name is already taken on create, you get `AlreadyExists`. If a delete is +still finishing, you get `Conflict` — wait, then `get` before you recreate that +name. @@ -313,9 +311,11 @@ know whether the sandbox exists (lookup by name). try { await client.create(request); } catch (error) { - if (knownName) { - const sandbox = await client.get(knownName); - console.log(sandbox.info()); + if (!(error instanceof SandboxError)) throw error; + if (error.kind === ErrorKind.AlreadyExists) { + console.error("Name already taken:", error.message); + } else if (error.kind === ErrorKind.Conflict) { + console.error("Name still conflicting; wait and get before recreate."); } throw error; } @@ -327,10 +327,13 @@ try { ```go instance, err := client.Create(ctx, opts) if err != nil { - if knownName != "" { - existing, getErr := client.Get(ctx, knownName) - if getErr == nil { - fmt.Println(existing.Info()) + var detail *sandbox.Error + if errors.As(err, &detail) { + switch detail.Kind { + case sandbox.ErrorKindAlreadyExists: + fmt.Println("Name already taken:", detail.Message) + case sandbox.ErrorKindConflict: + fmt.Println("Name still conflicting; wait and Get before recreate.") } } return err diff --git a/website/content/docs/filesystem.mdx b/website/content/docs/filesystem.mdx index e008199e..ac48ff06 100644 --- a/website/content/docs/filesystem.mdx +++ b/website/content/docs/filesystem.mdx @@ -139,7 +139,6 @@ data, err := instance.Filesystem().ReadFile(ctx, "/tmp/note.txt", &sandbox.FileR - ## Read a file Returns the bytes of one file. A missing file is not found. A directory path fails at the provider. @@ -348,11 +347,8 @@ fmt.Println(entries) - Blaxel has no dedicated stat API. TypeScript uses `fs.ls` (GET-by-path): a - directory listing succeeds; a file path returns Blaxel's "Directory not - found" response and Kit reports a file. Go calls the sandbox filesystem GET - through a rebuilt lower client because `SandboxInstanceFSService` has no - public Get/Stat and `LS` always treats the response as a directory. + Directories return type directory. Files return type file, with size and + permission fields when Blaxel provides them. diff --git a/website/content/docs/lifecycle.mdx b/website/content/docs/lifecycle.mdx index 15e56b47..1a6ed956 100644 --- a/website/content/docs/lifecycle.mdx +++ b/website/content/docs/lifecycle.mdx @@ -127,7 +127,7 @@ if err != nil { -`get` loads a sandbox that already exists by **name** (Blaxel id is the name). `refresh` loads it again. `attach` is not available. A successful `delete` is reported as deleted (Blaxel may still finish asynchronously). `DEPLOYED` with runtime `STANDBY` maps to Kit paused; `RUNNING` maps to running. +`get` loads a sandbox that already exists by **name** (Blaxel id is the name). `refresh` loads it again. `attach` is not available. After `delete` succeeds you see `deleted` / `terminal: true` — wait before recreating the same name, or you may get `Conflict`. An idle sandbox shows as paused; an active one shows as running. @@ -268,7 +268,7 @@ if err := instance.Delete(ctx, &sandbox.DeleteOptions{ ## Delete -`delete` removes a Blaxel sandbox. +`delete` removes a Blaxel sandbox. When the call succeeds you get `deleted` with `terminal` set. Wait before recreating the same name. @@ -413,8 +413,9 @@ state. See [Errors](/docs/errors). - After a lifecycle call, call `refresh` (or `get` again) before assuming the - sandbox is gone. + After a successful `delete`, trust `info.state` / `terminal` on the handle. + Use `refresh` or `get` when a call failed or you need fresh metadata while + the sandbox still exists. Prefer portable `state` over `providerState`. diff --git a/website/content/docs/process.mdx b/website/content/docs/process.mdx index 3e514643..bc09043b 100644 --- a/website/content/docs/process.mdx +++ b/website/content/docs/process.mdx @@ -78,7 +78,7 @@ fmt.Println(result) | You pass | On Daytona | | ---------------------- | --------------------------------------------------------------- | -| `argv` | POSIX-quoted shell string | +| `argv` | One quoted shell command string | | `shell` | shell string as-is | | omit / zero timeout | no command limit (`0`, so Daytona does not use its 10s default) | | fractional (`1500` ms) | rejected (`timeout`) | @@ -90,7 +90,7 @@ Daytona returns stdout only; `stderr` is empty. stdout may include merged stream ```ts const result = await sandbox.process.run({ - argv: ["echo", "hello"], // sent as a POSIX-quoted shell string + argv: ["echo", "hello"], // sent as one quoted shell command string }); // result: { exitCode, stdout, stderr } — stderr often empty on Daytona console.log(result); @@ -101,7 +101,7 @@ console.log(result); ```go result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ - Argv: []string{"echo", "hello"}, // sent as a POSIX-quoted shell string + Argv: []string{"echo", "hello"}, // sent as one quoted shell command string }) if err != nil { return err @@ -155,24 +155,24 @@ fmt.Println(result) -| You pass | On Blaxel | -| ---------------------- | ------------------------------------------------------------------------------ | -| `argv` | POSIX-quoted shell string | -| `shell` | shell string as-is | -| omit / zero timeout | no process auto-kill; one native wait until the command ends or cancel | -| positive timeout | process auto-kill; one native wait with that budget | -| fractional (`1500` ms) | rejected (`timeout`) | +| You pass | What happens | +| ---------------------- | ----------------------------------------------------------------- | +| `argv` | Sent as one quoted shell command string | +| `shell` | Run as-is | +| omit / zero timeout | Wait until the command ends (or you cancel); no Blaxel time limit | +| positive timeout | Cap how long the command may run (whole seconds only) | +| fractional (`1500` ms) | Rejected | -Blaxel returns both `stdout` and `stderr`. `run` waits until the command ends. -Canceling the context or aborting the signal stops the wait only; the remote -command may keep running. An HTTP 504 while waiting is a timeout error. +You get both `stdout` and `stderr`. `run` waits until the command ends. +Canceling stops waiting only — the remote command may keep running. If the wait +times out, you get a timeout error. ```ts const result = await sandbox.process.run({ - argv: ["echo", "hello"], // sent as a POSIX-quoted shell string + argv: ["echo", "hello"], // sent as one quoted shell command string }); // result: { exitCode, stdout, stderr } console.log(result); @@ -183,7 +183,7 @@ console.log(result); ```go result, err := instance.Process().Run(ctx, &sandbox.ExecOptions{ - Argv: []string{"echo", "hello"}, // sent as a POSIX-quoted shell string + Argv: []string{"echo", "hello"}, // sent as one quoted shell command string }) // result: ExitCode, Stdout, Stderr fmt.Println(result) @@ -204,7 +204,7 @@ A non-zero exit code is **not** an SDK error. | `0` | No extra client deadline for this call | | Positive value | Cap this call; must be whole seconds (`5000` ok, `1500` rejected) | -Omit or zero also means no separate provider process auto-kill (see the +Omit or zero also means no separate provider process time limit (see the provider tables above). diff --git a/website/content/docs/sdk/configuration.mdx b/website/content/docs/sdk/configuration.mdx index 318813ca..667b92b4 100644 --- a/website/content/docs/sdk/configuration.mdx +++ b/website/content/docs/sdk/configuration.mdx @@ -48,10 +48,9 @@ await using client = new Client({ - Blaxel uses the Kit deadline for create. `@blaxel/core`'s native creation - timeout (1–50s) is not forwarded. `region` is required for create. `endpoint` - is unsupported. TypeScript credentials go through process-global - `@blaxel/core` `settings`. + Your client/create `timeout` is how long create may take. `region` is required + for create. `endpoint` is unsupported. Use one Blaxel client (or one API key) + per process. @@ -95,9 +94,8 @@ client, err := sandbox.NewClient(provider, &sandbox.Config{ - Blaxel uses the Kit deadline for create. The Go Blaxel SDK has no separate - creation-timeout argument. `Region` is required for create. `Endpoint` is - unsupported. + Your client/create `Timeout` is how long create may take. `Region` is required + for create. `Endpoint` is unsupported. diff --git a/website/content/docs/sdk/create-options.mdx b/website/content/docs/sdk/create-options.mdx index 22f8d26c..65d5fec1 100644 --- a/website/content/docs/sdk/create-options.mdx +++ b/website/content/docs/sdk/create-options.mdx @@ -222,10 +222,9 @@ Set exactly one variant. Unsupported variants fail before the provider call. Blaxel requires an `image` source and a Blaxel-valid `name` (lowercase alphanumeric with single hyphens, at most 49 characters). Client `region` is - required. Default port transport leaves the protocol to Blaxel (HTTP); plain - maps to TCP; TLS maps to TLS; HTTP/2 over TLS is rejected. Ports 80, 443, and - 8080 are reserved. Kit create timeout is the local deadline; `@blaxel/core`'s - native creation timeout is not forwarded. + required. Default ports are HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is + not supported. Ports 80, 443, and 8080 are reserved. Your client/create + `timeout` is how long create may take. ## RuntimeConfig @@ -424,10 +423,8 @@ Set exactly one variant. Unsupported variants fail before the provider call. - Kit `timeout` / client timeout is the create deadline. `queueTimeout` is - unsupported. `waitFor` accepts only `Default` and `Scheduled`; `Ready`, - `Started`, and `Submitted` are rejected. `@blaxel/core`'s native creation - timeout (1–50s) is not forwarded. + Client `timeout` is how long create may take. `queueTimeout` is unsupported. + `waitFor` accepts only `Default` and `Scheduled`; Ready wait is not supported. @@ -451,10 +448,8 @@ Set exactly one variant. Unsupported variants fail before the provider call. - Kit `Timeout` / client timeout is the create deadline. `QueueTimeout` is - unsupported. `WaitFor` accepts only `Default` and `Scheduled`; `Ready`, - `Started`, and `Submitted` are rejected. The Go Blaxel SDK has no separate - creation-timeout argument. + Client `Timeout` is how long create may take. `QueueTimeout` is unsupported. + `WaitFor` accepts only `Default` and `Scheduled`; Ready wait is not supported. diff --git a/website/content/docs/sdk/enums.mdx b/website/content/docs/sdk/enums.mdx index 962459bb..d73b63ac 100644 --- a/website/content/docs/sdk/enums.mdx +++ b/website/content/docs/sdk/enums.mdx @@ -93,8 +93,7 @@ Transport for a published port. Modal requires an explicit transport (`Default` is rejected). - Blaxel: `Default` omits protocol (Blaxel defaults to HTTP). `Plain` → TCP, - `Tls` → TLS. `Http2Tls` is rejected. + `Default` is HTTP. `Plain` is TCP. `Tls` is TLS. `Http2Tls` is not supported. ## IsolationKind diff --git a/website/content/docs/sdk/filesystem.mdx b/website/content/docs/sdk/filesystem.mdx index cf6d66f5..4a44f4a8 100644 --- a/website/content/docs/sdk/filesystem.mdx +++ b/website/content/docs/sdk/filesystem.mdx @@ -374,6 +374,11 @@ const info = await sandbox.filesystem.stat("/tmp/note.txt"); console.log(info); ``` + + Directories return type directory. Files return type file, with size and + permission fields when Blaxel provides them. + + @@ -395,6 +400,11 @@ info, err := instance.Filesystem().Stat(ctx, "/tmp/note.txt", nil) fmt.Println(info) ``` + + Directories return type directory. Files return type file, with size and mode + when Blaxel provides them. + + diff --git a/website/content/docs/sdk/process.mdx b/website/content/docs/sdk/process.mdx index f654bf02..02fc27bb 100644 --- a/website/content/docs/sdk/process.mdx +++ b/website/content/docs/sdk/process.mdx @@ -113,7 +113,7 @@ Outcome of a finished command. Daytona returns stdout only; `stderr` is empty (stdout may include merged - streams). `argv` is sent as a POSIX-quoted shell string. Omit/zero timeout + streams). `argv` is sent as one quoted shell command string. Omit/zero timeout sends `0` so Daytona does not apply its 10s command default. @@ -124,13 +124,11 @@ Outcome of a finished command. - Blaxel returns both `stdout` and `stderr`. `argv` is sent as a POSIX-quoted - shell string. Kit starts without Blaxel's completion hold and uses one native - wait. Omit or zero timeout means no process auto-kill and an unlimited wait - (signal can still abort). A positive timeout sets auto-kill and the wait - budget. `run` waits until the command ends. Aborting the signal stops the wait - only; the remote command may keep running. An HTTP 504 while waiting is a - timeout error. + You get both `stdout` and `stderr`. `argv` is one quoted shell command string. + Omit or zero timeout waits until the command ends (or you abort), with no + Blaxel time limit. A positive timeout must be whole seconds and caps how long + the command may run. Aborting stops waiting only; the remote command may keep + running. If the wait times out, you get a timeout error. @@ -144,7 +142,7 @@ Outcome of a finished command. Daytona returns Stdout only; Stderr is empty (Stdout may include merged - streams). Argv is sent as a POSIX-quoted shell string. Omit/zero Timeout sends + streams). Argv is sent as one quoted shell command string. Omit/zero Timeout sends 0 so Daytona does not apply its 10s command default. @@ -155,13 +153,11 @@ Outcome of a finished command. - Blaxel returns both Stdout and Stderr. Argv is sent as a POSIX-quoted shell - string. Kit starts without Blaxel's completion hold and uses one native Wait. - Omit or zero Timeout means no process auto-kill and an unlimited wait - (context can still cancel). A positive Timeout sets auto-kill and the wait - budget. Run waits until the command ends. Canceling the context stops the wait - only; the remote command may keep running. An HTTP 504 while waiting is a - timeout error. + You get both Stdout and Stderr. Argv is one quoted shell command string. Omit or + zero Timeout waits until the command ends (or you cancel), with no Blaxel time + limit. A positive Timeout must be whole seconds and caps how long the command + may run. Canceling stops waiting only; the remote command may keep running. An + If the wait times out, you get a timeout error. diff --git a/website/content/docs/sdk/sandbox-info.mdx b/website/content/docs/sdk/sandbox-info.mdx index 47fbe0de..979fbc12 100644 --- a/website/content/docs/sdk/sandbox-info.mdx +++ b/website/content/docs/sdk/sandbox-info.mdx @@ -24,7 +24,7 @@ Returned by info and many lifecycle methods. Values are a **copy**; mutating the | `id` | Sandbox identifier | | `provider` | Provider identifier | | `name` | Display name when reported | -| `providerState` | Raw state string from the cloud | +| `providerState` | Provider-specific status string; prefer portable `state` | | `region` | Region when reported | | `isolation` | Reported isolation config | | `resources` | Reported size (`cpuCores`, `memoryMib`, …) | @@ -51,7 +51,7 @@ console.log(info); | `Id` | Sandbox identifier | | `Provider` | Provider identifier | | `Name` | Display name when reported | -| `ProviderState` | Raw state string from the cloud | +| `ProviderState` | Provider-specific status string; prefer portable `State` | | `Region` | Region when reported | | `Isolation` | Reported isolation config | | `Resources` | Reported size (`CpuCores`, `MemoryMib`, …) | From 117c13f50b480b8f2df0fc82fa41649cfb89eff4 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 13:28:04 +0530 Subject: [PATCH 37/60] Honor Blaxel delete wait in Kit and document for SDK callers. Restore a Kit-owned delete poll when wait is true, drop obsolete Blaxel native/create leftovers, and rewrite product docs plus agent rules for end-user voice. Co-authored-by: Cursor --- .agents/skills/docs-current-state/SKILL.md | 58 ++++-- .cursor/rules/docs-current-state.mdc | 10 +- .cursor/rules/docs-sdk-user-audience.mdc | 51 +++++ AGENTS.md | 10 +- README.md | 33 ++- docs/filesystem.md | 35 ++-- docs/responses-and-errors.md | 14 +- docs/sandbox-lifecycle.md | 5 +- .../filesystem/stat-blaxel-file/README.md | 5 +- .../lifecycle/delete-blaxel-sandbox/README.md | 8 +- .../lifecycle/delete-blaxel-sandbox/main.go | 3 +- .../filesystem/stat-blaxel-file/README.md | 4 +- .../lifecycle/delete-blaxel-sandbox/README.md | 9 +- .../delete-blaxel-sandbox/src/main.ts | 4 +- sdks/go/providers/blaxel/README.md | 17 +- sdks/go/providers/blaxel/example_test.go | 20 +- sdks/go/providers/blaxel/lifecycle.go | 46 ++++- sdks/go/providers/blaxel/lifecycle_test.go | 114 +++++++++++ sdks/go/providers/blaxel/process_test.go | 4 +- sdks/go/sandbox/README.md | 13 +- sdks/go/sandbox/lifecycle.go | 11 +- sdks/go/sandbox/sandbox_data_contracts.go | 2 +- sdks/typescript/providers/blaxel/README.md | 17 +- .../providers/blaxel/src/blaxel.test.ts | 192 +++++++++++++----- .../typescript/providers/blaxel/src/blaxel.ts | 62 +++++- .../blaxel/src/config/provider_config.ts | 10 +- .../typescript/providers/blaxel/src/errors.ts | 2 +- .../providers/blaxel/src/mappings/mappings.ts | 3 +- .../typescript/providers/blaxel/src/native.ts | 12 +- .../providers/blaxel/src/rejection.test.ts | 6 +- sdks/typescript/sandbox/README.md | 20 +- sdks/typescript/sandbox/src/client/sandbox.ts | 7 +- sdks/typescript/sandbox/src/client/types.ts | 2 +- website/content/docs/blaxel.mdx | 22 +- website/content/docs/concepts.mdx | 75 ++++++- website/content/docs/errors.mdx | 43 ++-- website/content/docs/examples.mdx | 4 +- website/content/docs/lifecycle.mdx | 24 ++- website/content/docs/sdk/client.mdx | 55 +++-- website/content/docs/sdk/create-options.mdx | 74 +++++-- website/content/docs/sdk/filesystem.mdx | 16 +- website/content/docs/sdk/process.mdx | 4 +- website/content/docs/sdk/sandbox.mdx | 76 ++++++- 43 files changed, 914 insertions(+), 288 deletions(-) create mode 100644 .cursor/rules/docs-sdk-user-audience.mdc create mode 100644 sdks/go/providers/blaxel/lifecycle_test.go diff --git a/.agents/skills/docs-current-state/SKILL.md b/.agents/skills/docs-current-state/SKILL.md index d4bcbf19..b1bb63ff 100644 --- a/.agents/skills/docs-current-state/SKILL.md +++ b/.agents/skills/docs-current-state/SKILL.md @@ -1,21 +1,43 @@ --- name: docs-current-state description: >- - Update project READMEs and Markdown guides as current-state truth only. - Use when writing or revising README.md, docs/, examples/**/README.md, or any - project guide after API, layout, architecture, or dependency changes; when - removing stale docs; or when the user asks to refine documentation. + Update project READMEs and Markdown/MDX guides as current-state truth for + end users and SDK callers (not SDK builders). Use when writing or revising + README.md, docs/, examples/**/README.md, sdks/**/README.md, + website/content/docs/**, or any product guide after API, layout, + architecture, or dependency changes; when removing stale docs; or when the + user asks to refine documentation. --- -# Docs: current state only +# Docs: current state, SDK-user audience -Write documentation that matches the repository **as it exists now**. Pair with -`crafting-effective-readmes` for README structure, `writing-clearly-and-concisely` -for prose, and `golang-documentation` for Go/doc-comment work. Use -`baoyu-format-markdown` only when the user asks to format or beautify markdown -layout without rewriting meaning. +Write documentation that matches the repository **as it exists now**, for +**people who call the SDK**. Pair with `crafting-effective-readmes` for README +structure, `writing-clearly-and-concisely` for prose, and +`golang-documentation` for Go/doc-comment work. Use `baoyu-format-markdown` +only when the user asks to format or beautify markdown layout without +rewriting meaning. -This skill aligns with `.cursor/rules/docs-current-state.mdc` and `AGENTS.md`. +This skill aligns with `.cursor/rules/docs-current-state.mdc`, +`.cursor/rules/docs-sdk-user-audience.mdc`, and `AGENTS.md`. + +## Audience + +Primary readers are application developers integrating `@sandbox-kit/*` or the +Go sandbox modules. Write like a technical documentation engineer for that +audience. + +| For SDK users | Not for SDK builders | +| ----------------------------------- | --------------------------------------------------------- | +| What to call, pass, and expect back | Mapping layers, wire shapes, adapter internals | +| Provider-specific usage differences | Inventory YAML, proto regen, contributor workflows | +| Outcomes on the handle (`state`, …) | “Unified metadata”, ignored-option plumbing | +| Unsupported → say so + alternative | Negated field catalogs unless they prevent a real mistake | + +Omit options the provider does not use from examples. If you must mention an +unused option, say the provider does not use it—do not say it is “accepted and +ignored.” Prefer listing fields the caller receives over listing fields that +stay empty. ## Rules @@ -26,27 +48,31 @@ This skill aligns with `.cursor/rules/docs-current-state.mdc` and `AGENTS.md`. 3. **Replace stale content.** Delete outdated sections, examples, commands, and claims. Do not keep deprecated paths "for context." 4. **Update with the code change.** When APIs, layout, architecture, examples, - or dependencies change, edit the relevant Markdown in the same task. + or dependencies change, edit the relevant Markdown/MDX in the same task. 5. **Stay silent in chat.** Do not announce "I also updated the README" unless the user asked about docs. PR/commit summaries may mention docs when that is part of the deliverable. 6. **Verify.** Check links and commands against the repo. Match examples to the implemented API. Prefer one catalog of truth (`examples/README.md`) over duplicated incomplete tables. +7. **Provider- and language-specific.** When behavior differs, say so under the + right provider/language section—do not paper over with generic wording. ## Scope | Include | Exclude | | --------------------------------------------- | --------------------------------------------- | | Root/`docs/`/`examples/**` READMEs and guides | CHANGELOG, commit/PR history | -| SDK and tooling READMEs that describe usage | Skill packages under `.agents/skills/` | +| SDK package READMEs that describe usage | Skill packages under `.agents/skills/` | +| `website/content/docs/**` product + SDK pages | Contributor-only architecture unless asked | | Out-of-scope notes stated as present facts | Roadmap or "planned / future work" narratives | ## Workflow -1. Identify which Markdown files describe the changed behavior. -2. Read those files and the matching code/examples. -3. Rewrite so each file is a clean current description; remove contradictions. +1. Identify which Markdown/MDX files describe the changed behavior. +2. Read those files and the matching public API / examples. +3. Rewrite so each file is a clean current description for an SDK caller; + remove contradictions and builder-only detail. 4. For README structure or audience fit, follow `crafting-effective-readmes`. 5. For sentence-level clarity, follow `writing-clearly-and-concisely`. 6. Do not prompt about incidental README edits after feature work unless diff --git a/.cursor/rules/docs-current-state.mdc b/.cursor/rules/docs-current-state.mdc index e0ebf8b3..51d6b673 100644 --- a/.cursor/rules/docs-current-state.mdc +++ b/.cursor/rules/docs-current-state.mdc @@ -1,5 +1,5 @@ --- -description: Keep docs and READMEs as current-state truth; update silently; remove outdated content +description: Keep docs and READMEs as current-state truth for SDK users; update silently; remove outdated content alwaysApply: true --- @@ -8,6 +8,9 @@ alwaysApply: true When APIs, layout, architecture, examples, or dependencies change, update the relevant README and project Markdown as part of the same work. +Audience for product and SDK docs is **end users and SDK callers**, not SDK +builders. Follow `.cursor/rules/docs-sdk-user-audience.mdc` for voice. + ## Write what is true now - Describe the project as it exists **today**. Present tense. @@ -30,6 +33,7 @@ relevant README and project Markdown as part of the same work. ## Scope -- Applies to `README.md`, `docs/`, `examples/**/README.md`, and other project - Markdown that describes current behavior. +- Applies to `README.md`, `docs/`, `examples/**/README.md`, + `website/content/docs/**`, SDK package READMEs, and other project Markdown + that describes current behavior. - Does not replace CHANGELOG or commit/PR history; keep those elsewhere. diff --git a/.cursor/rules/docs-sdk-user-audience.mdc b/.cursor/rules/docs-sdk-user-audience.mdc new file mode 100644 index 00000000..0ed94cb8 --- /dev/null +++ b/.cursor/rules/docs-sdk-user-audience.mdc @@ -0,0 +1,51 @@ +--- +description: Write product and SDK docs for end users and SDK callers, not SDK builders +alwaysApply: true +--- + +# Docs audience: SDK users, not builders + +Write README, guides, website docs, and SDK reference as a technical +documentation engineer for **people who call the SDK** (application +developers). Not for people who implement providers or maintain this repo. + +## Voice + +- Lead with what to call, what to pass, and what you get back. +- Prefer short how-to prose and working snippets over internal design notes. +- Keep provider- and language-specific behavior explicit when it differs. +- Present tense. Current behavior only (see `docs-current-state`). + +## Do + +- Name the public method and show a realistic call. +- Describe outcomes the caller can use (`state`, `terminal`, `exitCode`, …). +- Say what the caller must supply (credentials, name, image, region). +- Note unsupported operations as “not available” / “not supported” for that + provider, with the alternative when there is one. + +## Do not + +- Explain Kit internals, wire formats, mapping layers, or “unified” plumbing. +- Document ignored options as acceptance theater (“`wait` is accepted and + ignored”). Omit unused options from examples; if needed, say the provider + does not wait / does not use that option. +- Surface native `providerState` strings unless the caller must read them. +- Negate field catalogs (“does not fill `mode`”) unless that prevents a + real mistake; prefer listing what the caller actually receives. +- Write for SDK maintainers (inventory YAML, proto regeneration, adapter + shapes) in user-facing docs. Keep that in `AGENTS.md` / contributor docs. + +## Examples + +```md + + +Deletes and returns unified Deleted metadata (`providerState: "TERMINATED"`). +`wait` is accepted and ignored. + + + +Deletes the sandbox. On success, `info.state` is `deleted` and `terminal` is +true. Pass `wait: true` to wait until removal finishes. +``` diff --git a/AGENTS.md b/AGENTS.md index 7ec86a4c..4344d7d5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,9 +82,13 @@ Keep examples aligned with the implemented API. Label test doubles and local verification limits clearly. Verify changed examples before handoff and update `examples/README.md` when adding a language. -Update project Markdown whenever APIs, layout, architecture, or dependencies -change. Write docs as current-state truth: present tense, remove outdated -sections, and verify links and commands against the repository. +Update project Markdown and `website/content/docs/` whenever APIs, layout, +architecture, or dependencies change. Write docs as current-state truth for +**end users and SDK callers** (not SDK builders): present tense, what to call +and what you get back, provider- and language-specific when behavior differs. +Remove outdated sections, and verify links and commands against the +repository. See `.cursor/rules/docs-sdk-user-audience.mdc` and +`.cursor/skills/docs-current-state/`. ## Development tooling diff --git a/README.md b/README.md index 61171e4b..08998058 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ npm install @sandbox-kit/sandbox @sandbox-kit/daytona # or: npm install @sandbox-kit/sandbox @sandbox-kit/blaxel ``` -Set `DAYTONA_API_KEY`, then create a sandbox: +Set `DAYTONA_API_KEY`, then create a Daytona sandbox: ```ts import { daytona } from "@sandbox-kit/daytona"; @@ -54,8 +54,10 @@ await sandbox.filesystem.writeFile( ); ``` -Use `await client.close()` in a `finally` block when `await using` is not -available. Timeouts are milliseconds in TypeScript. +Empty `create()` uses Daytona's default snapshot. Modal needs an image. Blaxel +needs `name`, an image, and client `region`. Use `await client.close()` in a +`finally` block when `await using` is not available. Timeouts are milliseconds +in TypeScript. ### Go @@ -68,7 +70,7 @@ go get github.com/sandbox-kit/kit/sdks/go/providers/daytona@latest # or: go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest ``` -Set `DAYTONA_API_KEY`, then create a sandbox: +Set `DAYTONA_API_KEY`, then create a Daytona sandbox: ```go package main @@ -115,6 +117,9 @@ func main() { } ``` +`Create` with a nil request uses Daytona's default snapshot. Modal needs an +image. Blaxel needs `Name`, an image, and client `Region`. + ### What gets installed | Language | Core SDK | Providers | @@ -122,8 +127,7 @@ func main() { | TypeScript | `@sandbox-kit/sandbox` | `@sandbox-kit/daytona`, `@sandbox-kit/modal`, `@sandbox-kit/blaxel` | | Go | `github.com/sandbox-kit/kit/sdks/go/sandbox` | `.../providers/daytona`, `.../providers/modal`, `.../providers/blaxel` | -Install only the provider you use. The core SDK exposes the shared client -interface; the provider package connects it to the provider's native SDK. +Install only the provider you use. Closing a client or sandbox handle releases local SDK resources. It does not stop, terminate, archive, or delete the cloud sandbox. @@ -172,7 +176,7 @@ Full catalog: [examples/README.md](examples/README.md) · ## Handle errors -Same portable kinds in both languages. Native causes stay on the TypeScript +The same error kinds in both languages. Native causes stay on the TypeScript `cause` property and on Go `Unwrap` / `errors.As`. Kinds do not imply safe retries. ### TypeScript @@ -199,6 +203,15 @@ function handleError(error: unknown): void { "Your account does not have permission for this operation.", ); break; + case ErrorKind.NotFound: + console.error("Sandbox or file path not found."); + break; + case ErrorKind.AlreadyExists: + console.error("Named resource already exists."); + break; + case ErrorKind.Conflict: + console.error("Current state rejects this operation."); + break; case ErrorKind.RateLimited: case ErrorKind.ResourceExhausted: console.error("The provider's rate or resource limit was reached."); @@ -240,6 +253,12 @@ func handleError(err error) { fmt.Fprintln(os.Stderr, "Check your provider credentials.") case sandbox.ErrorKindPermissionDenied: fmt.Fprintln(os.Stderr, "Your account does not have permission for this operation.") + case sandbox.ErrorKindNotFound: + fmt.Fprintln(os.Stderr, "Sandbox or file path not found.") + case sandbox.ErrorKindAlreadyExists: + fmt.Fprintln(os.Stderr, "Named resource already exists.") + case sandbox.ErrorKindConflict: + fmt.Fprintln(os.Stderr, "Current state rejects this operation.") case sandbox.ErrorKindRateLimited, sandbox.ErrorKindResourceExhausted: fmt.Fprintln(os.Stderr, "The provider's rate or resource limit was reached.") case sandbox.ErrorKindCanceled: diff --git a/docs/filesystem.md b/docs/filesystem.md index ec37b243..181b558c 100644 --- a/docs/filesystem.md +++ b/docs/filesystem.md @@ -23,8 +23,8 @@ provider call settles so the next call does not overlap it. ## Paths A path inside the sandbox is absolute: `/tmp/note.txt`. A relative path, a -blank path, or a path that contains a NUL byte is rejected before the provider -is called. The error field is `path`. +blank path, or a path that contains a NUL byte is rejected. The error field +is `path`. A path on your machine, used by copy, may be relative to the process, such as `./note.txt`. It must be non-blank and must not contain a NUL byte. A rejected @@ -57,8 +57,8 @@ omitted, the abort signal carries the client deadline. `1500` ends the call at 1.5 seconds. Every Go file call uses the context as the deadline, so `1500 * time.Millisecond` ends the call at 1.5 seconds. Modal and Blaxel file calls use the context or the abort signal. `1500` milliseconds -ends the call at 1.5 seconds. Blaxel rejects `parents: false` before the -provider call (field `parents`). +ends the call at 1.5 seconds. Blaxel does not support `parents: false` +(field `parents`). ```ts await sandbox.filesystem.readFile("/tmp/note.txt", { timeout: 2_000 }); @@ -166,18 +166,26 @@ true. Set `parents` to false when the parent directory must already exist. Modal then requires that parent, and returns an error if the new directory is -already there. Daytona and Blaxel reject `false` before the call. The error -field is `parents`. +already there. Daytona and Blaxel do not support `false`. The error field is +`parents`. ```ts await sandbox.filesystem.makeDirectory("/tmp/kit-files"); +``` + +```go +err := instance.Filesystem().MakeDirectory(ctx, "/tmp/kit-files", nil) +``` + +Modal accepts `parents: false`: + +```ts await sandbox.filesystem.makeDirectory("/tmp/kit-files/nested", { parents: false, }); ``` ```go -err := instance.Filesystem().MakeDirectory(ctx, "/tmp/kit-files", nil) err = instance.Filesystem().MakeDirectory(ctx, "/tmp/kit-files/nested", &sandbox.MakeDirectoryOptions{ Parents: sandbox.Value(false), }) @@ -215,13 +223,12 @@ entries, err := instance.Filesystem().ListDirectory(ctx, "/tmp/kit-files", nil) ## Read metadata -`Stat` returns one path. A symlink result describes the symlink. On Blaxel, -directories return type directory; files return type file, with size and -permission fields when Blaxel provides them. Modal can report a symlink. -Daytona reports a file or a directory. `mode` is an octal string when the -provider supplies one, such as `0644`. `permissions`, `owner`, and `group` are -set when the provider supplies them. `modifiedAt` is set when the provider -supplies a time. +`Stat` returns one path. A symlink result describes the symlink. + +On Blaxel, you get `type` (file or directory), `size`, and usually +`permissions`, `owner`, `group`, and `modifiedAt`. Modal can report a +symlink. Daytona reports a file or a directory. `mode` is an octal string +when present, such as `0644`. ```ts const info = await sandbox.filesystem.stat("/tmp/note.txt"); diff --git a/docs/responses-and-errors.md b/docs/responses-and-errors.md index 3df7e978..8c30d7b7 100644 --- a/docs/responses-and-errors.md +++ b/docs/responses-and-errors.md @@ -19,9 +19,7 @@ if info.Resources != nil { ``` A present optional value keeps explicit zero, false, and empty strings. A -present map or slice keeps an empty collection. A create or lookup fails in -the client when the provider returns no sandbox ID. When the provider sets -`provider`, it must match the provider you selected. +present map or slice keeps an empty collection. ## Read an error @@ -39,6 +37,10 @@ if errors.As(err, &detail) { fmt.Println("Check configuration:", detail.Field) case sandbox.ErrorKindAuthentication: fmt.Println("Check provider credentials") + case sandbox.ErrorKindAlreadyExists: + fmt.Println("Named resource already exists") + case sandbox.ErrorKindConflict: + fmt.Println("Current state rejects this operation") } } ``` @@ -105,9 +107,9 @@ branch on `Kind` without importing that package. TypeScript keeps the provider error on the standard `cause` property of `SandboxError`. -An error that Kit could not classify keeps an unknown kind and the original -cause. A kind is a category. It is not a signal that retrying is safe, and -Kit does not retry for you. +An unclassified error keeps an unknown kind and the original cause. A kind is +a category. It is not a signal that retrying is safe, and Kit does not retry +for you. On Blaxel, creating a sandbox whose name already exists returns `AlreadyExists`. If a delete is still finishing or another conflict occurs, diff --git a/docs/sandbox-lifecycle.md b/docs/sandbox-lifecycle.md index fd6174fb..b0e8eefa 100644 --- a/docs/sandbox-lifecycle.md +++ b/docs/sandbox-lifecycle.md @@ -109,8 +109,9 @@ Modal gVisor and Linux VM sandboxes share Modal's terminal execution lifecycle. exposes it as `Client.Attach`; Modal does not advertise `Get` or `Refresh`. Blaxel looks up sandboxes by name for `Get` and `Refresh`. After a successful -`Delete`, the handle reports `Deleted` and `terminal`. Wait before recreating -the same name — Blaxel may still be cleaning up and return `Conflict`. +`Delete`, the handle reports `Deleted` and `terminal`. Omit `Wait` to return +when Blaxel accepts the delete; recreating the same name too soon may return +`Conflict`. Pass `Wait: true` to wait until removal finishes. `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and `Terminate` are unsupported. Closing a Blaxel handle does not contact Blaxel. On `Get` / `Refresh`, an idle sandbox shows as paused and an active one shows as running. diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/README.md b/examples/go/blaxel/filesystem/stat-blaxel-file/README.md index 74c8aff9..540f5d09 100644 --- a/examples/go/blaxel/filesystem/stat-blaxel-file/README.md +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/README.md @@ -4,8 +4,9 @@ Stat one absolute path in a Blaxel sandbox. ## What this means -Returns whether the path is a file or a directory, plus size and mode when -Blaxel provides them. Size may be zero when Blaxel does not return it. +`Stat` returns `Type` (file or directory), `Size`, and usually `Permissions`, +`Owner`, `Group`, and `ModifiedAt`. Size may be zero when Blaxel does not +return it. ## Edge cases diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md index a24aa306..2b1b1865 100644 --- a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/README.md @@ -4,9 +4,11 @@ Delete an existing Blaxel sandbox by name. ## What this means -`Delete` permanently removes the Blaxel sandbox. When the call succeeds you get -`Deleted` with `terminal` set. Wait before recreating the same name, or you may -get `Conflict`. Start, stop, pause, archive, and terminate are unsupported. +`Delete` permanently removes the Blaxel sandbox. After success, `Info().State` +is `Deleted` and `Terminal` is true. This example uses `Wait: true` so removal +finishes before the call returns. Omit `Wait` to return when Blaxel accepts +the delete — recreating the same name too soon may return `Conflict`. Start, +stop, pause, archive, and terminate are not available. ## Edge cases diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go index 6e0150c0..2e58be91 100644 --- a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go @@ -49,5 +49,6 @@ func main() { if err := instance.Delete(context.Background(), &sandbox.DeleteOptions{Wait: sandbox.Value(true)}); err != nil { log.Fatal(err) } - fmt.Printf("Blaxel sandbox deleted: %s (terminal=%t)\n", instance.ID(), instance.Info().GetTerminal()) + info := instance.Info() + fmt.Printf("Blaxel sandbox deleted: %s (state=%s, terminal=%t)\n", instance.ID(), info.State, info.GetTerminal()) } diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md index f14f2a09..3a5afe9a 100644 --- a/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md @@ -4,8 +4,8 @@ Stat one absolute path in a Blaxel sandbox. ## What this means -Returns whether the path is a file or a directory, plus size and mode when -Blaxel provides them. +`stat` returns `type` (file or directory), `size`, and usually `permissions`, +`owner`, `group`, and `modifiedAt`. ## Edge cases diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md index 20219121..c9762a32 100644 --- a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/README.md @@ -4,9 +4,12 @@ Delete an existing Blaxel sandbox by name. ## What this means -`delete` permanently removes the Blaxel sandbox. When the call succeeds you get -`deleted` / `terminal: true`. Wait before recreating the same name, or you may -get `Conflict`. Start, stop, pause, archive, and terminate are unsupported. +`delete` permanently removes the Blaxel sandbox and returns `SandboxInfo`. +On success, `state` is `deleted` and `terminal` is true. This example uses +`wait: true` so removal finishes before the call returns. Omit `wait` to +return when Blaxel accepts the delete — recreating the same name too soon may +return `Conflict`. Start, stop, pause, archive, and terminate are not +available. ## Edge cases diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts index 6339e7e2..ba33e4d8 100644 --- a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts @@ -26,7 +26,7 @@ await using client = new Client({ const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); assertCapability(sandbox, LifecycleCapability.Delete); -await sandbox.delete({ wait: true }); +const info = await sandbox.delete({ wait: true }); console.log( - `Blaxel sandbox deleted: ${sandbox.id} (terminal=${sandbox.info().terminal ?? false})`, + `Blaxel sandbox deleted: ${sandbox.id} (state=${info.state}, terminal=${info.terminal ?? false})`, ); diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 7e4c9dc1..19b01d35 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -87,9 +87,10 @@ You can `Get`, `Refresh`, `Delete`, and `Close`. Lookup is by name (`ID()` equals the create name). Attach, start, stop, pause, archive, and terminate are not available. `Close` only drops the local handle. -After a successful `Delete`, the handle reports `Deleted` and `terminal`. -Blaxel can still be cleaning up — recreating the same name right away may -return `Conflict`. +After a successful `Delete`, `Info().State` is `Deleted` and `Terminal` is +true. Omit `Wait` to return as soon as Blaxel accepts the delete — recreating +the same name too soon may return `Conflict`. Pass `Wait: true` to wait until +removal finishes before the call returns. On `Get` / `Refresh` / `Info`, an idle sandbox shows as `Paused`; an active one shows as `Running`. @@ -97,12 +98,12 @@ one shows as `Running`. ### Filesystem You get the usual file APIs: read, write, copy in/out, mkdir, list, stat, and -remove. Paths must be absolute. `Parents` defaults to true; `Parents: false` -is rejected. +remove. Paths must be absolute. `Parents` defaults to true; Blaxel does not +support `Parents: false`. -`Stat` returns whether the path is a file or a directory, plus size and mode -when Blaxel provides them. Missing local files for `CopyFromLocal` return not -found with field `local_path`. +`Stat` returns `Type` (file or directory), `Size`, and usually `Permissions`, +`Owner`, `Group`, and `ModifiedAt`. Missing local files for `CopyFromLocal` +return not found with field `local_path`. ### Process diff --git a/sdks/go/providers/blaxel/example_test.go b/sdks/go/providers/blaxel/example_test.go index a331b0d4..42e47f30 100644 --- a/sdks/go/providers/blaxel/example_test.go +++ b/sdks/go/providers/blaxel/example_test.go @@ -90,7 +90,7 @@ func ExampleNew_get() { } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { fmt.Println(err) return @@ -112,12 +112,12 @@ func ExampleNew_delete() { } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { fmt.Println(err) return } - err = instance.Delete(context.Background(), &sandbox.DeleteOptions{}) + err = instance.Delete(context.Background(), &sandbox.DeleteOptions{Wait: sandbox.Value(true)}) if err != nil { fmt.Println(err) } @@ -181,7 +181,7 @@ func ExampleNew_process() { } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { fmt.Println(err) return @@ -208,7 +208,7 @@ func ExampleNew_processShell() { } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { fmt.Println(err) return @@ -235,7 +235,7 @@ func ExampleNew_processEnv() { } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { fmt.Println(err) return @@ -263,7 +263,7 @@ func ExampleNew_filesystem() { } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { fmt.Println(err) return @@ -293,7 +293,7 @@ func ExampleNew_makeDirectory() { } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { fmt.Println(err) return @@ -316,7 +316,7 @@ func ExampleNew_listDirectory() { } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { fmt.Println(err) return @@ -341,7 +341,7 @@ func ExampleNew_stat() { } defer client.Close(context.Background()) - instance, err := client.Get(context.Background(), os.Getenv("SANDBOX_ID")) + instance, err := client.Get(context.Background(), os.Getenv("BL_SANDBOX_NAME")) if err != nil { fmt.Println(err) return diff --git a/sdks/go/providers/blaxel/lifecycle.go b/sdks/go/providers/blaxel/lifecycle.go index eb665fcd..c4546e17 100644 --- a/sdks/go/providers/blaxel/lifecycle.go +++ b/sdks/go/providers/blaxel/lifecycle.go @@ -2,6 +2,8 @@ package blaxel import ( "context" + "errors" + "time" blaxel "github.com/blaxel-ai/sdk-go" "github.com/sandbox-kit/kit/sdks/go/sandbox" @@ -9,11 +11,14 @@ import ( var _ sandbox.SandboxDeleter = (*backend)(nil) +const deletePollInterval = time.Second + // DeleteSandbox permanently deletes a Blaxel sandbox by name. // Start, stop, pause, archive, and terminate are not implemented. -// Blaxel's DELETE may still finish asynchronously; a successful call is -// reported as Deleted so portable callers get the same terminal outcome. -func (b *backend) DeleteSandbox(ctx context.Context, id string, _ *sandbox.DeleteOptions) (*sandbox.SandboxInfo, error) { +// When Wait is omitted or false, a successful DELETE returns Deleted with +// Terminal true (Blaxel may still finish asynchronously). When Wait is true, +// Kit polls Get until the name is gone or Deleted. +func (b *backend) DeleteSandbox(ctx context.Context, id string, options *sandbox.DeleteOptions) (*sandbox.SandboxInfo, error) { instance, err := b.client.Sandboxes.DeleteInstance(ctx, id) if err != nil { return nil, mapProviderError(err, "delete") @@ -26,8 +31,43 @@ func (b *backend) DeleteSandbox(ctx context.Context, id string, _ *sandbox.Delet if err != nil { return nil, err } + if options != nil && options.GetWait() { + // Blaxel has no native wait-for-delete; Kit polls until gone. + return b.waitUntilDeleted(ctx, id) + } info.State = sandbox.SandboxStateDeleted info.Terminal = sandbox.Value(true) info.ProviderState = sandbox.Value(string(blaxel.StatusTerminated)) return info, nil } + +func (b *backend) waitUntilDeleted(ctx context.Context, id string) (*sandbox.SandboxInfo, error) { + ticker := time.NewTicker(deletePollInterval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return nil, mapProviderError(ctx.Err(), "delete") + case <-ticker.C: + info, err := b.RefreshSandbox(ctx, id) + if err != nil { + var detail *sandbox.Error + if errors.As(err, &detail) && detail.Kind == sandbox.ErrorKindNotFound { + return &sandbox.SandboxInfo{ + Id: id, + Provider: "blaxel", + State: sandbox.SandboxStateDeleted, + Terminal: sandbox.Value(true), + ProviderState: sandbox.Value(string(blaxel.StatusTerminated)), + }, nil + } + return nil, err + } + if info.State == sandbox.SandboxStateDeleted { + info.Terminal = sandbox.Value(true) + info.ProviderState = sandbox.Value(string(blaxel.StatusTerminated)) + return info, nil + } + } + } +} diff --git a/sdks/go/providers/blaxel/lifecycle_test.go b/sdks/go/providers/blaxel/lifecycle_test.go new file mode 100644 index 00000000..86b26fdc --- /dev/null +++ b/sdks/go/providers/blaxel/lifecycle_test.go @@ -0,0 +1,114 @@ +package blaxel + +import ( + "context" + "errors" + "net/http" + "sync/atomic" + "testing" + "time" + + "github.com/sandbox-kit/kit/sdks/go/sandbox" +) + +func TestDeleteSandboxReportsDeletedWithoutWait(t *testing.T) { + var gets atomic.Int32 + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodDelete && r.URL.Path == "/sandboxes/doomed": + writeJSON(t, w, map[string]any{ + "metadata": map[string]string{"name": "doomed"}, + "status": "DELETING", + "spec": map[string]any{}, + }) + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/doomed": + gets.Add(1) + http.NotFound(w, r) + default: + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + } + }) + info, err := backend.DeleteSandbox(context.Background(), "doomed", &sandbox.DeleteOptions{}) + if err != nil { + t.Fatal(err) + } + if info.State != sandbox.SandboxStateDeleted || info.GetTerminal() != true || info.GetProviderState() != "TERMINATED" { + t.Fatalf("info = %#v", info) + } + if gets.Load() != 0 { + t.Fatalf("gets = %d", gets.Load()) + } +} + +func TestDeleteSandboxWaitsUntilNotFound(t *testing.T) { + var gets atomic.Int32 + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodDelete && r.URL.Path == "/sandboxes/doomed": + writeJSON(t, w, map[string]any{ + "metadata": map[string]string{"name": "doomed"}, + "status": "DELETING", + "spec": map[string]any{}, + }) + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/doomed": + n := gets.Add(1) + if n < 2 { + writeJSON(t, w, map[string]any{ + "metadata": map[string]string{"name": "doomed"}, + "status": "DELETING", + "spec": map[string]any{}, + }) + return + } + http.NotFound(w, r) + default: + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + } + }) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + info, err := backend.DeleteSandbox(ctx, "doomed", &sandbox.DeleteOptions{Wait: sandbox.Value(true)}) + if err != nil { + t.Fatal(err) + } + if info.State != sandbox.SandboxStateDeleted || info.GetTerminal() != true { + t.Fatalf("info = %#v", info) + } + if gets.Load() < 2 { + t.Fatalf("gets = %d", gets.Load()) + } +} + +func TestDeleteSandboxWaitHonorsCancel(t *testing.T) { + backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodDelete && r.URL.Path == "/sandboxes/doomed": + writeJSON(t, w, map[string]any{ + "metadata": map[string]string{"name": "doomed"}, + "status": "DELETING", + "spec": map[string]any{}, + }) + case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/doomed": + writeJSON(t, w, map[string]any{ + "metadata": map[string]string{"name": "doomed"}, + "status": "DELETING", + "spec": map[string]any{}, + }) + default: + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + http.NotFound(w, r) + } + }) + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(200 * time.Millisecond) + cancel() + }() + _, err := backend.DeleteSandbox(ctx, "doomed", &sandbox.DeleteOptions{Wait: sandbox.Value(true)}) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindCanceled { + t.Fatalf("error = %v detail = %#v", err, detail) + } +} diff --git a/sdks/go/providers/blaxel/process_test.go b/sdks/go/providers/blaxel/process_test.go index 370b293c..eebb2a39 100644 --- a/sdks/go/providers/blaxel/process_test.go +++ b/sdks/go/providers/blaxel/process_test.go @@ -40,7 +40,7 @@ func TestCommandTimeoutWholeSeconds(t *testing.T) { } } -func TestRunCommandStartsWithoutCompletionHold(t *testing.T) { +func TestRunCommandStartsWithoutWaitForCompletion(t *testing.T) { var posts []map[string]any backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { switch { @@ -138,7 +138,7 @@ func TestRunCommandWaitsWhenStartReturnsRunning(t *testing.T) { } } -func TestRunCommandReturnsAFinishedStartWithoutPolling(t *testing.T) { +func TestRunCommandReturnsAFinishedStartWithoutWaiting(t *testing.T) { backend := processBackend(t, func(w http.ResponseWriter, r *http.Request) { switch { case r.Method == http.MethodGet && r.URL.Path == "/sandboxes/sbx": diff --git a/sdks/go/sandbox/README.md b/sdks/go/sandbox/README.md index c32041c1..12d097dc 100644 --- a/sdks/go/sandbox/README.md +++ b/sdks/go/sandbox/README.md @@ -44,8 +44,9 @@ requires a name and an image, plus client `Region`. `client.Close` closes your connection to Daytona, Modal, or Blaxel. `instance.Close` does not stop, terminate, or delete the sandbox, and you can -keep using the handle. The sandbox keeps running until you stop, terminate, -archive, or delete it. +keep using the handle. Reclaim a Daytona sandbox with Stop, Archive, or +Delete; a Modal sandbox with Terminate or lifetime; a Blaxel sandbox with +Delete or TTL/idle. Blaxel has no Stop, Terminate, or Archive. ## Run a command @@ -93,10 +94,10 @@ Paths inside the sandbox are absolute. The same handle also supports | [Modal](https://pkg.go.dev/github.com/sandbox-kit/kit/sdks/go/providers/modal) | `modal.New()` | `client.Attach` | | [Blaxel](https://pkg.go.dev/github.com/sandbox-kit/kit/sdks/go/providers/blaxel) | `blaxel.New()` | `client.Get` (by name) | -`Supports` reports whether the provider implements that operation. An -unimplemented method returns an error and does not call the provider. A -provider rejection, including one from a closed SDK, comes back as that -provider's error. +`Supports` tells you whether this handle can attempt that operation. On +Blaxel, you can `Get`, `Refresh`, `Delete`, and `Close`. Other lifecycle +methods are not available. If the provider rejects a supported call, you get +that provider's error. ## Read an error diff --git a/sdks/go/sandbox/lifecycle.go b/sdks/go/sandbox/lifecycle.go index 7f1c0e38..7f9ac4db 100644 --- a/sdks/go/sandbox/lifecycle.go +++ b/sdks/go/sandbox/lifecycle.go @@ -140,10 +140,13 @@ func (s *Sandbox) Archive(ctx context.Context, options *ArchiveOptions) error { } // Delete deletes the cloud sandbox and stores the metadata the provider returns. -// Set DeleteOptions.Wait with Value(true) to wait until deletion finishes. Value(false) returns after the provider accepts the request. -// A nil timeout uses the client timeout as this call's deadline. The provider receives a timeout only when one is set. -// Close leaves the cloud sandbox in place. Daytona and Blaxel support Delete. Modal returns ErrorKindUnsupported. Use Terminate for Modal. -// A nil context returns ErrorKindInvalidArgument. Read a failure with errors.As and *Error. +// Daytona and Blaxel honor DeleteOptions.Wait: Value(true) blocks until deletion +// finishes. Without Wait, Blaxel returns Deleted with Terminal true as soon as +// delete is accepted. A nil timeout uses the client timeout as this call's +// deadline. The provider receives a timeout only when one is set. Close leaves +// the cloud sandbox in place. Daytona and Blaxel support Delete. Modal returns +// ErrorKindUnsupported; use Terminate for Modal. A nil context returns +// ErrorKindInvalidArgument. Read a failure with errors.As and *Error. func (s *Sandbox) Delete(ctx context.Context, options *DeleteOptions) error { operationContext, cancel, id, err := s.operationContext(ctx, "delete", sdk.OptionalDuration(options.GetTimeout())) if err != nil { diff --git a/sdks/go/sandbox/sandbox_data_contracts.go b/sdks/go/sandbox/sandbox_data_contracts.go index b984d192..aa62a9e8 100644 --- a/sdks/go/sandbox/sandbox_data_contracts.go +++ b/sdks/go/sandbox/sandbox_data_contracts.go @@ -154,7 +154,7 @@ type ( // zero adds no extra deadline for this call. Both omit and zero also mean // no provider process auto-kill (Daytona uses 0 so its 10s native default // does not apply; Modal adds no command limit; Blaxel adds no auto-kill - // and polls until the command ends). Command + // and waits once via Process.Wait). Command // timeouts must be whole seconds. ExecOptions = sandboxv1.ExecOptions diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index 72cbcdf7..71157bf3 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -78,9 +78,10 @@ You can `get` / `refresh` (by name), `delete`, and `close`. `close` does not delete the sandbox. Start, stop, pause, archive, terminate, and attach are not available. -After a successful `delete`, you get `state: "deleted"` and `terminal: true`. -Blaxel can still be cleaning up — recreating the same name right away may -return `Conflict`. +After a successful `delete`, `state` is `deleted` and `terminal` is true. +Omit `wait` to return as soon as Blaxel accepts the delete — recreating the +same name too soon may return `Conflict`. Pass `wait: true` to wait until +removal finishes before the call returns. On `get` / `refresh` / `info`, an idle sandbox shows as `paused`; an active one shows as `running`. @@ -89,12 +90,12 @@ shows as `running`. You get the usual file APIs: `readFile`, `writeFile`, `copyFromLocal`, `copyToLocal`, `makeDirectory`, `listDirectory`, `stat`, and `remove`. Paths -must be absolute. `parents` defaults to true; `parents: false` is rejected. +must be absolute. `parents` defaults to true; Blaxel does not support +`parents: false`. -For directories, `stat` returns type directory. For files, `stat` returns type -file with size and permission fields when Blaxel provides them. -Missing local files for `copyFromLocal` return not found with field -`local_path`. +`stat` returns `type` (file or directory), `size`, and usually `permissions`, +`owner`, `group`, and `modifiedAt`. Missing local files for `copyFromLocal` +return not found with field `local_path`. ### Process diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index c00cb8a6..76cdcc05 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -1,6 +1,6 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { Client, EgressMode, @@ -39,7 +39,7 @@ import { mapBlaxelError } from "#errors.js"; import { projectHandle } from "./native.js"; import { ResponseError, SandboxInstance } from "@blaxel/core"; -const provider = { scope: { workspace: "ws" }, region: "us-pdx-1" }; +const provider = { region: "us-pdx-1" }; describe("Blaxel mappings", () => { it("maps API key authentication and workspace scope", () => { @@ -97,7 +97,7 @@ describe("Blaxel mappings", () => { name: "example", source: { image: { reference: "blaxel/base-image:latest" } }, }, - { scope: { workspace: "ws" } }, + {}, ), ).toThrowError( expect.objectContaining({ @@ -126,18 +126,14 @@ describe("Blaxel mappings", () => { ); }); - it("captures retained configuration without aliasing caller state", () => { - const config = { - scope: { workspace: "original" }, - region: "us-pdx-1", - }; - const state = captureProviderState(config); - config.scope.workspace = "changed"; - - expect(state).toMatchObject({ - scope: { workspace: "original" }, - region: "us-pdx-1", - }); + it("captures create region from client config", () => { + expect( + captureProviderState({ + scope: { workspace: "original" }, + region: "us-pdx-1", + }), + ).toEqual({ region: "us-pdx-1" }); + expect(captureProviderState({ scope: { workspace: "ws" } })).toEqual({}); }); it("maps the creation surface into the native Blaxel plan", () => { @@ -587,7 +583,7 @@ describe("Blaxel mappings", () => { describe("Blaxel provider orchestration", () => { it("creates with SandboxInstance.create and never createIfNotExists", async () => { - let created: { params: unknown; options: unknown } | undefined; + let created: unknown; let configured: unknown; const nativeSandbox = { metadata: { name: "example" }, @@ -596,20 +592,14 @@ describe("Blaxel provider orchestration", () => { spec: { region: "us-pdx-1" }, fs: {} as never, process: {} as never, - async wait() { - return this; - }, - async delete() { - return { metadata: { name: "example" }, status: "DELETED" }; - }, }; const client = new Client({ provider: new BlaxelProvider(() => ({ configure(config) { configured = config; }, - async create(params, options) { - created = { params, options }; + async create(params) { + created = params; return nativeSandbox; }, async get() { @@ -632,14 +622,12 @@ describe("Blaxel provider orchestration", () => { await client.close(); expect(configured).toEqual({ apiKey: "bl_key", workspace: "ws" }); - expect(created?.params).toMatchObject({ + expect(created).toMatchObject({ name: "example", image: "blaxel/base-image:latest", memory: 2048, region: "us-pdx-1", }); - // Kit never forwards @blaxel/core SandboxCreateOptions (native create timeout). - expect(created?.options).toBeUndefined(); expect(sandbox.id).toBe("example"); expect(sandbox.info().name).toBe("example"); }); @@ -659,12 +647,6 @@ describe("Blaxel provider orchestration", () => { state: "RUNNING", fs: {} as never, process: {} as never, - async wait() { - return this; - }, - async delete() { - return { metadata: { name: "unused" }, status: "DELETED" }; - }, }; }, async delete() { @@ -710,6 +692,118 @@ describe("Blaxel provider orchestration", () => { }); }); + it("does not poll get when wait is omitted", async () => { + let gets = 0; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + gets += 1; + throw new Error("unexpected get"); + }, + async delete(name) { + return { metadata: { name }, status: "DELETING" }; + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + if (backend.delete === undefined) throw new Error("delete is missing"); + await backend.delete("doomed", { wait: false }, {}); + expect(gets).toBe(0); + }); + + it("waits until get returns NotFound when wait is true", async () => { + vi.useFakeTimers(); + try { + let gets = 0; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get(name) { + gets += 1; + if (gets < 2) { + return { + metadata: { name }, + status: "DELETING", + fs: {} as never, + process: {} as never, + }; + } + throw new ResponseError( + new Response(null, { status: 404, statusText: "Not Found" }), + undefined, + undefined, + ); + }, + async delete(name) { + return { metadata: { name }, status: "DELETING" }; + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + if (backend.delete === undefined) throw new Error("delete is missing"); + const pending = backend.delete("doomed", { wait: true }, {}); + await vi.advanceTimersByTimeAsync(1_000); + await vi.advanceTimersByTimeAsync(1_000); + const info = await pending; + expect(gets).toBe(2); + expect(info).toMatchObject({ + id: "doomed", + state: SandboxState.Deleted, + terminal: true, + providerState: "TERMINATED", + }); + } finally { + vi.useRealTimers(); + } + }); + + it("stops waiting for delete when the signal aborts", async () => { + vi.useFakeTimers(); + try { + const controller = new AbortController(); + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get(name) { + return { + metadata: { name }, + status: "DELETING", + fs: {} as never, + process: {} as never, + }; + }, + async delete(name) { + return { metadata: { name }, status: "DELETING" }; + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + if (backend.delete === undefined) throw new Error("delete is missing"); + const pending = backend.delete("doomed", { wait: true }, { + signal: controller.signal, + }); + const expectation = expect(pending).rejects.toMatchObject({ + kind: ErrorKind.Canceled, + operation: "delete", + }); + await vi.advanceTimersByTimeAsync(500); + controller.abort(); + await expectation; + } finally { + vi.useRealTimers(); + } + }); + it("does not create a sandbox after the signal aborts during mapping", async () => { const controller = new AbortController(); let created = false; @@ -869,12 +963,6 @@ describe("Blaxel provider orchestration", () => { }; }, }, - async wait() { - return this; - }, - async delete() { - return { metadata: { name: "unused" }, status: "DELETED" }; - }, }; }, async delete() { @@ -886,7 +974,11 @@ describe("Blaxel provider orchestration", () => { }); if (backend.run === undefined) throw new Error("run is missing"); - const polled = await backend.run("sandbox-1", { argv: ["echo", "hi"] }, {}); + const finished = await backend.run( + "sandbox-1", + { argv: ["echo", "hi"] }, + {}, + ); await backend.run("sandbox-1", { argv: ["echo", "hi"], timeout: 0 }, {}); await backend.run( "sandbox-1", @@ -916,9 +1008,9 @@ describe("Blaxel provider orchestration", () => { operation: "run", }); - expect(polled).toMatchObject({ exitCode: 7 }); - expect(polled.stdout).toEqual(new TextEncoder().encode("out")); - expect(polled.stderr).toEqual(new TextEncoder().encode("err")); + expect(finished).toMatchObject({ exitCode: 7 }); + expect(finished.stdout).toEqual(new TextEncoder().encode("out")); + expect(finished.stderr).toEqual(new TextEncoder().encode("err")); expect(seen).toEqual([ { command: "'echo' 'hi'", waitForCompletion: false, timeout: 0 }, { command: "'echo' 'hi'", waitForCompletion: false, timeout: 0 }, @@ -941,7 +1033,7 @@ describe("Blaxel provider orchestration", () => { ]); }); - it("returns a finished process without polling", async () => { + it("returns a finished process without waiting", async () => { const backend = runBackend({ async exec() { return { @@ -1147,12 +1239,6 @@ function fsBackend(fs: { metadata: { name: "sandbox-1" }, fs: fs as never, process: {} as never, - async wait() { - return this; - }, - async delete() { - return { metadata: { name: "unused" }, status: "DELETED" }; - }, }; }, async delete() { @@ -1196,12 +1282,6 @@ function runBackend(process: { metadata: { name: "sandbox-1" }, fs: {} as never, process, - async wait() { - return this; - }, - async delete() { - return { metadata: { name: "unused" }, status: "DELETED" }; - }, }; }, async delete() { diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index f1a95f0d..2d82756f 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -113,14 +113,18 @@ class BlaxelBackend extends BackendBase implements Backend { async delete( id: string, - _request: DeleteOptions, + request: DeleteOptions, options: OperationOptions, ): Promise { try { throwIfAborted(options.signal); const deleted = await this.native.delete(id); throwIfAborted(options.signal); - // Blaxel DELETE may still finish asynchronously; unify as Deleted. + if (request.wait === true) { + // Blaxel has no native wait-for-delete; Kit polls until gone. + return await this.#waitUntilDeleted(id, options.signal); + } + // DELETE accepted; Blaxel may still finish asynchronously. return { ...mapSandboxInfo(deleted), state: SandboxState.Deleted, @@ -312,6 +316,36 @@ class BlaxelBackend extends BackendBase implements Backend { } } + /** Poll get until NotFound or Deleted. Do not use the DELETE body as done. */ + async #waitUntilDeleted( + id: string, + signal: AbortSignal | undefined, + ): Promise { + for (;;) { + throwIfAborted(signal); + await delay(1_000, signal); + try { + const info = mapSandboxInfo(await this.native.get(id)); + if (info.state === SandboxState.Deleted) { + return { ...info, terminal: true, providerState: "TERMINATED" }; + } + } catch (error) { + const mapped = mapBlaxelError(error, "delete"); + if (mapped.kind === ErrorKind.NotFound) { + return { + id, + provider: "blaxel", + state: SandboxState.Deleted, + terminal: true, + providerState: "TERMINATED", + labels: {}, + }; + } + throw mapped; + } + } + } + async closeSandbox(_id: string, _options: OperationOptions): Promise {} async close(options: OperationOptions): Promise { @@ -323,6 +357,30 @@ function throwIfAborted(signal: AbortSignal | undefined): void { signal?.throwIfAborted(); } +function delay(ms: number, signal: AbortSignal | undefined): Promise { + return new Promise((resolve, reject) => { + if (signal?.aborted) { + reject( + signal.reason ?? + new DOMException("The operation was aborted", "AbortError"), + ); + return; + } + const timer = setTimeout(() => { + signal?.removeEventListener("abort", onAbort); + resolve(); + }, ms); + const onAbort = () => { + clearTimeout(timer); + reject( + signal?.reason ?? + new DOMException("The operation was aborted", "AbortError"), + ); + }; + signal?.addEventListener("abort", onAbort, { once: true }); + }); +} + /** * Selects Blaxel as the provider for the shared Sandbox Kit client. * diff --git a/sdks/typescript/providers/blaxel/src/config/provider_config.ts b/sdks/typescript/providers/blaxel/src/config/provider_config.ts index ac176b8d..55907a70 100644 --- a/sdks/typescript/providers/blaxel/src/config/provider_config.ts +++ b/sdks/typescript/providers/blaxel/src/config/provider_config.ts @@ -5,7 +5,6 @@ import type { Config as NativeClientConfig } from "@blaxel/core"; import { ErrorKind, type Config, - type Scope, ConfigFieldAuth, ConfigFieldEndpoint, ConfigFieldScopeAppName, @@ -52,18 +51,13 @@ export function mapGeneratedClientConfig(config: Config): NativeClientConfig { return target; } +/** Retained create inputs. Scope is applied at initialize; create only needs region. */ export interface ProviderState { - readonly scope?: Scope; readonly region?: string; } export function captureProviderState(config: Config): ProviderState { - return { - ...(config.scope === undefined - ? {} - : { scope: structuredClone(config.scope) }), - ...(config.region === undefined ? {} : { region: config.region }), - }; + return config.region === undefined ? {} : { region: config.region }; } function unsupported(field: string, message: string): never { diff --git a/sdks/typescript/providers/blaxel/src/errors.ts b/sdks/typescript/providers/blaxel/src/errors.ts index f08cadcc..d038b004 100644 --- a/sdks/typescript/providers/blaxel/src/errors.ts +++ b/sdks/typescript/providers/blaxel/src/errors.ts @@ -148,7 +148,7 @@ function classifyKind(args: ClassifyArgs): ErrorInfo["kind"] { isCreationTimeoutError(error) ) return ErrorKind.Timeout; - // process.wait deadline is a plain Error; a 504 on one poll may be its cause. + // process.wait deadline is a plain Error; a 504 on that wait may be its cause. // A bare edge 504 is a timeout too, matching Go. if ( isProcessWaitTimeout(error) || diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index 50666400..d7af666f 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -255,8 +255,7 @@ function mapObservability(r: CreateOptions): void { } // Kit Client.create applies provisioning.timeout as the abort deadline. -// @blaxel/core also accepts SandboxCreateOptions.timeout (1–50s); this -// provider does not forward it — use the Kit deadline instead. +// This provider does not forward a native create timeout — use the Kit deadline. function mapProvisioning(r: CreateOptions): void { const v = r.provisioning; if (!v) return; diff --git a/sdks/typescript/providers/blaxel/src/native.ts b/sdks/typescript/providers/blaxel/src/native.ts index f7f0f507..b926abca 100644 --- a/sdks/typescript/providers/blaxel/src/native.ts +++ b/sdks/typescript/providers/blaxel/src/native.ts @@ -7,7 +7,6 @@ import { type Directory, type File as BlaxelFile, type SandboxCreateConfiguration, - type SandboxCreateOptions, type SandboxFileSystem, } from "@blaxel/core"; import type { BlaxelSandboxIdentity } from "#mappings/provider_mappings.js"; @@ -46,20 +45,15 @@ export interface NativeSandboxHandle extends BlaxelSandboxIdentity { identifier: string, options?: { maxWait?: number; - interval?: number; signal?: AbortSignal; }, ): Promise; }; - delete(): Promise; } export interface NativeBlaxel { configure(config: NativeClientConfig): void; - create( - params: SandboxCreateConfiguration, - options?: SandboxCreateOptions, - ): Promise; + create(params: SandboxCreateConfiguration): Promise; get(name: string): Promise; delete(name: string): Promise; } @@ -72,8 +66,8 @@ export function defaultNativeFactory(config: NativeClientConfig): NativeBlaxel { configure(next: NativeClientConfig): void { settings.setConfig(next); }, - async create(params, options) { - return projectHandle(await SandboxInstance.create(params, options)); + async create(params) { + return projectHandle(await SandboxInstance.create(params)); }, async get(name) { return projectHandle(await SandboxInstance.get(name)); diff --git a/sdks/typescript/providers/blaxel/src/rejection.test.ts b/sdks/typescript/providers/blaxel/src/rejection.test.ts index 744fa75b..ff488e0d 100644 --- a/sdks/typescript/providers/blaxel/src/rejection.test.ts +++ b/sdks/typescript/providers/blaxel/src/rejection.test.ts @@ -29,7 +29,7 @@ import { describe, expect, it } from "vitest"; import { mapClientConfig, mapCreateRequest } from "#mappings/mappings.js"; -const provider = { scope: { workspace: "ws" }, region: "us-pdx-1" }; +const provider = { region: "us-pdx-1" }; describe("rejected Blaxel create settings", () => { const cases: { @@ -183,9 +183,7 @@ describe("rejected Blaxel create settings", () => { }); it("missing region", () => { - expect(() => - mapCreateRequest(namedImage({}), { scope: { workspace: "ws" } }), - ).toThrowError( + expect(() => mapCreateRequest(namedImage({}), {})).toThrowError( expect.objectContaining({ kind: ErrorKind.InvalidArgument, field: ConfigFieldRegion, diff --git a/sdks/typescript/sandbox/README.md b/sdks/typescript/sandbox/README.md index ec7c0b5c..286a84c9 100644 --- a/sdks/typescript/sandbox/README.md +++ b/sdks/typescript/sandbox/README.md @@ -22,6 +22,8 @@ Requires Node.js 20, 22, or 24.15+. ## Quick start +Daytona sample (`create()` with no body uses Daytona's default snapshot): + ```ts import { daytona } from "@sandbox-kit/daytona"; import { Client } from "@sandbox-kit/sandbox"; @@ -45,7 +47,7 @@ Pass the provider on `ClientConfig`, then shared settings: | Field | Purpose | | ---------- | -------------------------------------------------------------------------- | -| `provider` | Provider factory (`daytona()`, `modal()`, …) | +| `provider` | Provider factory (`daytona()`, `modal()`, `blaxel()`) | | `auth` | One credential shape (`apiKey`, `bearerToken`, `tokenPair`, …) | | `scope` | App, environment, organization, project, or workspace (provider-dependent) | | `endpoint` | API URL when the provider supports it | @@ -58,6 +60,8 @@ package README. ## Create a sandbox +Daytona example: + ```ts const sandbox = await client.create({ source: { image: { reference: "daytonaio/playground:latest" } }, @@ -66,15 +70,16 @@ const sandbox = await client.create({ ``` `create()` with no options uses the provider default when that is valid (for -example Daytona's default snapshot). Timeouts and delays are millisecond +example Daytona's default snapshot). Modal requires an image. Blaxel requires +`name`, an image, and client `region`. Timeouts and delays are millisecond numbers. Nested exclusive fields use shapes like `source: { image: … }` or `auth: { apiKey: … }`. ## Open an existing sandbox ```ts -const attached = await client.attach(id); // provider must support attach -const fetched = await client.get(id); // provider-verified lookup +const attached = await client.attach(id); // Modal: id only, no existence check +const fetched = await client.get(id); // Daytona: sandbox id; Blaxel: create name ``` ## Run a command @@ -127,9 +132,10 @@ Sandbox paths are absolute. The same handle also supports `copyFromLocal`, | `filesystem` | Read, write, copy, list, mkdir, stat, remove | | `process` | Run a command and wait for exit code + output | -`supports` reports whether the provider implements that operation. An -unimplemented method throws before the provider call. A provider rejection, -including one from a closed SDK, comes back as that provider's error. +Check `supports` before a lifecycle call that might be unavailable. On Blaxel, +you can `get`, `refresh`, `delete`, and `close`. Other lifecycle methods are +not available. If the provider rejects a supported call, you get that +provider's error. ## Errors diff --git a/sdks/typescript/sandbox/src/client/sandbox.ts b/sdks/typescript/sandbox/src/client/sandbox.ts index 02a074eb..a185f081 100644 --- a/sdks/typescript/sandbox/src/client/sandbox.ts +++ b/sdks/typescript/sandbox/src/client/sandbox.ts @@ -233,9 +233,10 @@ export class Sandbox implements AsyncDisposable { } /** * Deletes the cloud sandbox and stores the metadata the provider returns. - * Set `wait` to wait until deletion finishes. `close` leaves the sandbox in place. - * Daytona and Blaxel support delete. Modal throws `ErrorKind.Unsupported`. Use - * `terminate`. + * Daytona and Blaxel honor `wait` to block until deletion finishes. Blaxel + * without `wait` returns `deleted` / `terminal: true` as soon as delete is + * accepted. `close` leaves the sandbox in place. Modal throws + * `ErrorKind.Unsupported` — use `terminate`. */ async delete(options: DeleteCallOptions = {}): Promise { const { wait, ...call } = options; diff --git a/sdks/typescript/sandbox/src/client/types.ts b/sdks/typescript/sandbox/src/client/types.ts index 6298a7b0..8b1d4bd7 100644 --- a/sdks/typescript/sandbox/src/client/types.ts +++ b/sdks/typescript/sandbox/src/client/types.ts @@ -177,7 +177,7 @@ export interface Provider { initialize(config: Config): Backend; } -/** Settings for `new Client`, including the provider (`daytona()` or `modal()`). */ +/** Settings for `new Client`, including the provider (`daytona()`, `modal()`, or `blaxel()`). */ export type ClientConfig = Config & { readonly provider: Provider; }; diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index 183e2283..5c002cae 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -165,17 +165,17 @@ In TypeScript, use one Blaxel client (or one API key) per process. Full rules: ## What works -| Area | Notes | -| --------- | -------------------------------------------------------------------------------- | -| Create | Name + image; memory, labels, env, ports, outbound domains, TTL/idle | -| Volumes | Not available on create | -| Open | `get` / `refresh` by sandbox name | -| Lifecycle | `delete`, `close` — no start/stop/pause/archive/terminate/attach | -| Delete | Succeeds as `deleted` / `terminal`; wait briefly before recreating the same name | -| State | Idle → paused; active → running | -| Errors | Name taken on create → AlreadyExists; delete still in progress → Conflict | -| Files | Shared filesystem methods — [Filesystem](/docs/filesystem) | -| Process | `run` waits for the command — [Run a command](/docs/process) | +| Area | Notes | +| --------- | --------------------------------------------------------------------------------------------------------------- | +| Create | Name + image; memory, labels, env, ports, outbound domains, TTL/idle | +| Volumes | Not available on create | +| Open | `get` / `refresh` by sandbox name | +| Lifecycle | `delete`, `close` — no start/stop/pause/archive/terminate/attach | +| Delete | Succeeds as `deleted` / `terminal`; omit `wait` may `Conflict` on quick recreate; `wait: true` waits until gone | +| State | Idle → paused; active → running | +| Errors | Name taken on create → AlreadyExists; delete still in progress → Conflict | +| Files | Shared filesystem methods — [Filesystem](/docs/filesystem) | +| Process | `run` waits for the command — [Run a command](/docs/process) | Set `memoryMib` / `memory_mib` for memory size. Blaxel documents that value as MB; Kit does not convert units. CPU follows memory — CPU and disk overrides are diff --git a/website/content/docs/concepts.mdx b/website/content/docs/concepts.mdx index 01e66bd5..0bb89763 100644 --- a/website/content/docs/concepts.mdx +++ b/website/content/docs/concepts.mdx @@ -3,7 +3,7 @@ title: Concepts description: Client, handle, filesystem, process, providers, concurrency, and close. --- -Sandbox Kit is an SDK. You construct a client with a provider and config; the client calls that provider for create, lifecycle, filesystem, and process work. Application code stays on the shared client surface. +Sandbox Kit is an SDK. Construct a client with a provider and config, then create, look up, and operate on sandboxes through that client. ## Client @@ -333,7 +333,7 @@ fmt.Println(result) Each provider is its own package. Pass it into the client. Auth, create defaults, and which lifecycle calls exist depend on the provider; the client methods stay the same. -The client talks to Daytona, Modal, or Blaxel directly. There is no separate Kit cloud in the middle. +The client calls Daytona, Modal, or Blaxel directly. @@ -470,7 +470,7 @@ Setup: [Blaxel](/docs/blaxel). ## Capabilities -Unsupported calls fail before anything reaches the cloud. The cloud can still refuse a supported call for a particular sandbox; that comes back as a provider error. +Unsupported calls return an error. Check `supports` before a lifecycle method that might be unavailable. A supported call can still fail if the provider rejects that sandbox. @@ -497,6 +497,21 @@ if (!sandbox.supports(LifecycleCapability.Terminate)) { } ``` + + + + +```ts +import { LifecycleCapability } from "@sandbox-kit/sandbox"; + +if (!sandbox.supports(LifecycleCapability.Delete)) { + throw new Error(`sandbox ${sandbox.id} cannot be deleted`); +} +if (!sandbox.supports(LifecycleCapability.Refresh)) { + throw new Error(`sandbox ${sandbox.id} cannot be refreshed`); +} +``` + @@ -524,6 +539,21 @@ if !instance.Supports(sandbox.LifecycleCapabilityTerminate) { } ``` + + + + +```go +import "github.com/sandbox-kit/kit/sdks/go/sandbox" + +if !instance.Supports(sandbox.LifecycleCapabilityDelete) { + return fmt.Errorf("sandbox %s cannot be deleted", instance.ID()) +} +if !instance.Supports(sandbox.LifecycleCapabilityRefresh) { + return fmt.Errorf("sandbox %s cannot be refreshed", instance.ID()) +} +``` + @@ -577,6 +607,19 @@ await Promise.all([ ]); ``` + + + + +```ts +// Parallel work: another handle via get(name) has its own queue. +const other = await client.get(sandbox.id); +await Promise.all([ + sandbox.process.run({ argv: ["echo", "a"] }), + other.process.run({ argv: ["echo", "b"] }), +]); +``` + @@ -665,6 +708,32 @@ for range 2 { } ``` + + + + +```go +// Parallel work: another handle via Get(name) has its own queue. +other, err := client.Get(ctx, instance.ID()) +if err != nil { + return err +} +errCh = make(chan error, 2) +go func() { + _, err := instance.Process().Run(ctx, &sandbox.ExecOptions{Argv: []string{"echo", "a"}}) + errCh <- err +}() +go func() { + _, err := other.Process().Run(ctx, &sandbox.ExecOptions{Argv: []string{"echo", "b"}}) + errCh <- err +}() +for range 2 { + if err := <-errCh; err != nil { + return err + } +} +``` + diff --git a/website/content/docs/errors.mdx b/website/content/docs/errors.mdx index dccfaf44..05891671 100644 --- a/website/content/docs/errors.mdx +++ b/website/content/docs/errors.mdx @@ -7,17 +7,24 @@ Failed calls surface a typed error with `kind`, `provider`, `operation`, `messag Kinds are categories, not retry advice. Kit does not retry. -| Kind | What happened | -| ------------------- | ------------------------------------------------------ | -| `InvalidArgument` | The request shape is wrong | -| `Unsupported` | This provider does not implement the call or the field | -| `Authentication` | Credentials were rejected | -| `PermissionDenied` | The account cannot perform this operation | -| `NotFound` | The sandbox or file path was not found | -| `RateLimited` | Provider rate limit reached | -| `ResourceExhausted` | Provider quota or capacity reached | -| `Canceled` | The caller canceled the operation | -| `Timeout` | The deadline fired | +| Kind | What happened | +| -------------------- | ------------------------------------------------------ | +| `InvalidArgument` | A request field is invalid | +| `Unsupported` | This provider does not support the call or the field | +| `Authentication` | Credentials were rejected | +| `PermissionDenied` | The account cannot perform this operation | +| `NotFound` | The sandbox or file path was not found | +| `AlreadyExists` | The named resource already exists | +| `Conflict` | Current state rejects this operation | +| `RateLimited` | Provider rate limit reached | +| `ResourceExhausted` | Provider quota or capacity reached | +| `Timeout` | The deadline fired | +| `Canceled` | The caller canceled the operation | +| `Unavailable` | The provider could not be reached | +| `InvalidResponse` | The provider returned an unusable response | +| `Internal` | Unclassified failure | +| `FailedPrecondition` | Client or sandbox is not in a usable state | +| `Unknown` | Does not match a known category | ## Invalid config @@ -100,6 +107,12 @@ try { case ErrorKind.NotFound: console.error("Sandbox or file path not found."); break; + case ErrorKind.AlreadyExists: + console.error("Named resource already exists."); + break; + case ErrorKind.Conflict: + console.error("Current state rejects this operation."); + break; case ErrorKind.RateLimited: case ErrorKind.ResourceExhausted: console.error("Provider rate or resource limit reached."); @@ -142,6 +155,10 @@ if errors.As(err, &detail) { fmt.Println("Your account cannot perform this operation.") case sandbox.ErrorKindNotFound: fmt.Println("Sandbox or file path not found.") + case sandbox.ErrorKindAlreadyExists: + fmt.Println("Named resource already exists.") + case sandbox.ErrorKindConflict: + fmt.Println("Current state rejects this operation.") case sandbox.ErrorKindRateLimited, sandbox.ErrorKindResourceExhausted: fmt.Println("Provider rate or resource limit reached.") case sandbox.ErrorKindCanceled: @@ -218,8 +235,6 @@ try { // create may have succeeded before a later step failed — no id on the error if (knownId) { const sandbox = await client.get(knownId); - // info: { id, provider, name?, providerState?, region?, isolation?, - // resources?, labels, createdAt?, providerMetadata?, state, terminal? } console.log(sandbox.info()); } throw error; @@ -236,8 +251,6 @@ if err != nil { if knownID != "" { existing, getErr := client.Get(ctx, knownID) if getErr == nil { - // Info(): Id, Provider, Name, ProviderState, Region, Isolation, - // Resources, Labels, CreatedAt, ProviderMetadata, State, Terminal fmt.Println(existing.Info()) } } diff --git a/website/content/docs/examples.mdx b/website/content/docs/examples.mdx index a16e5b95..8cb927bb 100644 --- a/website/content/docs/examples.mdx +++ b/website/content/docs/examples.mdx @@ -43,7 +43,7 @@ npm install && npm run build && npm start ```sh git clone https://github.com/sandbox-kit/kit.git cd kit/examples/typescript/blaxel/configuration/create-blaxel-sandbox -cp .env.example .env # set BL_API_KEY, BL_WORKSPACE +cp .env.example .env # set BL_API_KEY, BL_WORKSPACE, BL_REGION (or us-pdx-1) npm install && npm run build && npm start ``` @@ -81,7 +81,7 @@ go run . ```sh git clone https://github.com/sandbox-kit/kit.git cd kit/examples/go/blaxel/configuration/create-blaxel-sandbox -cp .env.example .env # set BL_API_KEY, BL_WORKSPACE +cp .env.example .env # set BL_API_KEY, BL_WORKSPACE, BL_REGION (or us-pdx-1) go run . ``` diff --git a/website/content/docs/lifecycle.mdx b/website/content/docs/lifecycle.mdx index 1a6ed956..9403ddd9 100644 --- a/website/content/docs/lifecycle.mdx +++ b/website/content/docs/lifecycle.mdx @@ -3,7 +3,7 @@ title: Lifecycle description: get, attach, start, stop, pause, archive, delete, terminate, and close. --- -Operate on a handle from `create`, or reopen one with `get` / `attach`. Unsupported calls fail before the provider runs. +Operate on a handle from `create`, or reopen one with `get` / `attach`. Unsupported calls return an error. @@ -127,7 +127,7 @@ if err != nil { -`get` loads a sandbox that already exists by **name** (Blaxel id is the name). `refresh` loads it again. `attach` is not available. After `delete` succeeds you see `deleted` / `terminal: true` — wait before recreating the same name, or you may get `Conflict`. An idle sandbox shows as paused; an active one shows as running. +`get` loads a sandbox that already exists by **name** (Blaxel id is the name). `refresh` loads it again. `attach` is not available. After `delete` succeeds you see `deleted` / `terminal: true`. Omit `wait` to return when Blaxel accepts the delete; recreating the same name too soon may return `Conflict`. Pass `wait: true` to wait until removal finishes. An idle sandbox shows as paused; an active one shows as running. @@ -268,14 +268,15 @@ if err := instance.Delete(ctx, &sandbox.DeleteOptions{ ## Delete -`delete` removes a Blaxel sandbox. When the call succeeds you get `deleted` with `terminal` set. Wait before recreating the same name. +`delete` removes a Blaxel sandbox. On success, `state` is `deleted` and `terminal` is true. Omit `wait` to return when Blaxel accepts the delete; recreating the same name too soon may return `Conflict`. Pass `wait: true` to wait until removal finishes. ```ts const sandbox = await client.get(process.env.BL_SANDBOX_NAME!); -await sandbox.delete({ timeout: 60_000 }); +const info = await sandbox.delete({ wait: true, timeout: 60_000 }); +console.log(info.state, info.terminal); ``` @@ -294,10 +295,13 @@ if err != nil { return err } if err := instance.Delete(ctx, &sandbox.DeleteOptions{ + Wait: sandbox.Value(true), Timeout: sandbox.Duration(time.Minute), }); err != nil { return err } +info := instance.Info() +fmt.Println(info.State, info.GetTerminal()) ``` @@ -398,9 +402,8 @@ defer client.Close(context.Background()) ## Read the state -`info.state` is the portable lifecycle state. `providerState` is the raw string from -the cloud when one is set. A failed call can still mean the cloud already changed -state. See [Errors](/docs/errors). +`info.state` is the lifecycle state on the handle. A failed call can still +mean the cloud already changed state. See [Errors](/docs/errors). After a lifecycle call, call `refresh` (or `get` again) before assuming the @@ -413,9 +416,10 @@ state. See [Errors](/docs/errors). - After a successful `delete`, trust `info.state` / `terminal` on the handle. - Use `refresh` or `get` when a call failed or you need fresh metadata while - the sandbox still exists. Prefer portable `state` over `providerState`. + After a successful `delete`, `info.state` is `deleted` and `terminal` is true. + Use `wait: true` when you need removal to finish before recreate. Use + `refresh` or `get` when a call failed or you need fresh metadata while the + sandbox still exists. diff --git a/website/content/docs/sdk/client.mdx b/website/content/docs/sdk/client.mdx index 9c562ee4..ed0d7843 100644 --- a/website/content/docs/sdk/client.mdx +++ b/website/content/docs/sdk/client.mdx @@ -3,7 +3,19 @@ title: Client description: Construct a client; create, get or attach, and close. --- -Creates sandboxes and reopens them by id (`get` or `attach`). Closing the client only drops the local connection—cloud sandboxes keep running. +Creates sandboxes and reopens them. + + + Reopen with [`get`](#get) by sandbox id. + + + Reopen with [`attach`](#attach) by sandbox id. Attach does not check that the sandbox exists. + + + Reopen with [`get`](#get) by create name. Blaxel id is the name. + + +Closing the client only drops the local connection—cloud sandboxes keep running. ## Construction @@ -14,7 +26,7 @@ Creates sandboxes and reopens them by id (`get` or `attach`). Closing the client new Client(config: ClientConfig) ``` -Validates config and initializes the selected provider. Throws on invalid config. +Creates a client for the selected provider. Throws if config is invalid. **Parameters** @@ -46,7 +58,7 @@ await using client = new Client({ func NewClient(provider Provider, config *Config) (*Client, error) ``` -Validates config and initializes the provider. Invalid config returns a nil client and an error. +Creates a client for the selected provider. Invalid config returns a nil client and an error. **Parameters** @@ -104,7 +116,17 @@ defer client.Close(context.Background()) create(request?: CreateOptions, options?: OperationOptions): Promise ``` -Creates one sandbox and returns its handle. Omit the request (or pass `{}`) to use provider defaults. +Creates one sandbox and returns its handle. + + + Omit the request (or pass `{}`) to use Daytona's default snapshot. + + + Pass an `image` source. Empty options are rejected. + + + Pass a Blaxel-valid `name`, an `image` source, and client `region`. Empty options are rejected. + **Parameters** @@ -165,7 +187,17 @@ console.log(sandbox.id); func (c *Client) Create(ctx context.Context, request *CreateOptions) (*Sandbox, error) ``` -Creates one sandbox and returns its handle. A nil request uses provider defaults. +Creates one sandbox and returns its handle. + + + A nil request uses Daytona's default snapshot. + + + Pass an image source. A nil request is rejected. + + + Pass a Blaxel-valid `Name`, an image source, and client `Region`. A nil request is rejected. + **Parameters** @@ -233,7 +265,7 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ ### get -Looks up a sandbox by id and returns a handle the provider has verified. +Looks up a sandbox by id and returns a handle. The provider confirms that sandbox exists. @@ -257,10 +289,7 @@ get(id: string, options?: OperationOptions): Promise ```ts const sandbox = await client.get(knownId); -const info = sandbox.info(); -// info: { id, provider, name?, providerState?, region?, isolation?, -// resources?, labels, createdAt?, providerMetadata?, state, terminal? } -console.log(info); +console.log(sandbox.info()); ``` @@ -285,8 +314,6 @@ func (c *Client) Get(ctx context.Context, id string) (*Sandbox, error) ```go instance, err := client.Get(ctx, knownID) -// Info(): Id, Provider, Name, ProviderState, Region, Isolation, -// Resources, Labels, CreatedAt, ProviderMetadata, State, Terminal fmt.Println(instance.Info()) ``` @@ -299,7 +326,7 @@ fmt.Println(instance.Info()) ### get -Looks up a sandbox by **name** (Blaxel id is the name) and returns a verified handle. +Looks up a sandbox by **name**. On Blaxel, the id is the create name. @@ -338,7 +365,7 @@ fmt.Println(instance.Info()) ### attach -Builds a handle for an existing sandbox id without a lookup round trip. It does not ask the provider whether that sandbox exists. +Builds a handle from a sandbox id. It does not check that the sandbox exists. diff --git a/website/content/docs/sdk/create-options.mdx b/website/content/docs/sdk/create-options.mdx index 65d5fec1..81ab5256 100644 --- a/website/content/docs/sdk/create-options.mdx +++ b/website/content/docs/sdk/create-options.mdx @@ -3,7 +3,7 @@ title: CreateOptions description: Create request body and nested option types. --- -Request body for [`Client.create`](/docs/sdk/client#create). Omitted fields keep provider defaults. Unsupported fields fail before the provider call. +Request body for [`Client.create`](/docs/sdk/client#create). Omitted fields keep provider defaults. Unsupported fields are rejected. ## CreateOptions @@ -33,7 +33,7 @@ Durations on this type are **milliseconds**. Allowlists under `network` are plai | Field | Description | | --------------- | ------------------------------------------------------- | -| `name` | Display name. Explicit empty string asks for unnamed. | +| `name` | Sandbox name. See provider notes. | | `source` | Image, snapshot, warm pool, or provider default | | `runtime` | Language hint, user, working directory, entrypoint, PTY | | `isolation` | Isolation class | @@ -48,6 +48,18 @@ Durations on this type are **milliseconds**. Allowlists under `network` are plai | `readiness` | Probe when waiting until ready | | `observability` | Verbose logging / telemetry | + + Daytona treats `name` as a display name. Omit it to keep the provider default. An explicit empty string asks for unnamed. + + + + Modal treats `name` as a display name. Omit it to keep the provider default. An explicit empty string asks for unnamed. + + + + On Blaxel, `name` is required and **is** the sandbox id. Use lowercase letters, digits, and single hyphens (`[a-z0-9]+(?:-[a-z0-9]+)*`), at most 49 characters. Look the sandbox up with `get(name)`. + + **Example** @@ -111,7 +123,7 @@ Durations use `sandbox.Duration`. Optional numbers use `sandbox.Value`. | Field | Description | | --------------- | ------------------------------------------------------- | -| `Name` | Display name. Explicit empty string asks for unnamed. | +| `Name` | Sandbox name. See provider notes. | | `Source` | Image, snapshot, warm pool, or provider default | | `Runtime` | Language hint, user, working directory, entrypoint, PTY | | `Isolation` | Isolation class | @@ -126,6 +138,18 @@ Durations use `sandbox.Duration`. Optional numbers use `sandbox.Value`. | `Readiness` | Probe when waiting until ready | | `Observability` | Verbose logging / telemetry | + + Daytona treats `Name` as a display name. Omit it to keep the provider default. An explicit empty string asks for unnamed. + + + + Modal treats `Name` as a display name. Omit it to keep the provider default. An explicit empty string asks for unnamed. + + + + On Blaxel, `Name` is required and **is** the sandbox id. Use lowercase letters, digits, and single hyphens (`[a-z0-9]+(?:-[a-z0-9]+)*`), at most 49 characters. Look the sandbox up with `Get(name)`. + + **Example** @@ -181,7 +205,7 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ ## Source -Set exactly one variant. Unsupported variants fail before the provider call. +Set exactly one variant. Unsupported variants are rejected. @@ -220,11 +244,11 @@ Set exactly one variant. Unsupported variants fail before the provider call. - Blaxel requires an `image` source and a Blaxel-valid `name` (lowercase - alphanumeric with single hyphens, at most 49 characters). Client `region` is - required. Default ports are HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is - not supported. Ports 80, 443, and 8080 are reserved. Your client/create - `timeout` is how long create may take. + Blaxel requires an `image` source and a `name` (lowercase alphanumeric with + single hyphens, at most 49 characters). Client `region` is required. Default + ports are HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is not supported. + Ports 80, 443, and 8080 are reserved. Create `timeout` is how long create may + take. ## RuntimeConfig @@ -242,7 +266,7 @@ Set exactly one variant. Unsupported variants fail before the provider call. | `pty` | Request a PTY when supported | - Blaxel rejects create-time runtime fields before the call. + Blaxel does not support runtime fields on create. @@ -258,7 +282,7 @@ Set exactly one variant. Unsupported variants fail before the provider call. | `Pty` | Request a PTY when supported | - Blaxel rejects create-time runtime fields before the call. + Blaxel does not support runtime fields on create. @@ -277,6 +301,10 @@ Set exactly one variant. Unsupported variants fail before the provider call. | `memoryLimitMib` | Optional memory ceiling | | `diskMib` | Requested disk in MiB | + + Blaxel accepts `memoryMib` only. CPU and disk overrides are rejected; Blaxel derives CPU from memory. + + @@ -288,6 +316,10 @@ Set exactly one variant. Unsupported variants fail before the provider call. | `MemoryLimitMib` | Optional memory ceiling | | `DiskMib` | Requested disk in MiB | + + Blaxel accepts `MemoryMib` only. CPU and disk overrides are rejected; Blaxel derives CPU from memory. + + @@ -468,7 +500,7 @@ Set either a TCP port or a command. | `command.argv` | Command argv that must succeed | - Blaxel rejects readiness probes before the call. + Blaxel does not support readiness probes. @@ -480,7 +512,7 @@ Set either a TCP port or a command. | `Command` with `Argv` | Command argv that must succeed | - Blaxel rejects readiness probes before the call. + Blaxel does not support readiness probes. @@ -504,6 +536,10 @@ Set either a TCP port or a command. | `customDomain` | Custom domain when supported | | `ports` | Published ports (`port`, optional `transport`) | + + Blaxel accepts ports (not 80, 443, or 8080) and outbound domains. Default transport is HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is rejected. + + @@ -520,6 +556,10 @@ Set either a TCP port or a command. | `CustomDomain` | Custom domain when supported | | `Ports` | Published ports (`Port`, optional `Transport`) | + + Blaxel accepts ports (not 80, 443, or 8080) and outbound domains. Default transport is HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is rejected. + + @@ -534,6 +574,10 @@ Set either a TCP port or a command. | `workloadIdentity` | Request a workload identity when supported | | `secrets` | Provider-managed secret references (`reference`, injection, env var name) | + + Blaxel does not support security fields on create. + + @@ -543,6 +587,10 @@ Set either a TCP port or a command. | `WorkloadIdentity` | Request a workload identity when supported | | `Secrets` | Provider-managed secret references (`Reference`, injection, env var name) | + + Blaxel does not support security fields on create. + + diff --git a/website/content/docs/sdk/filesystem.mdx b/website/content/docs/sdk/filesystem.mdx index 4a44f4a8..1ab6c97e 100644 --- a/website/content/docs/sdk/filesystem.mdx +++ b/website/content/docs/sdk/filesystem.mdx @@ -261,8 +261,8 @@ Creates the directory at `path`. An omitted `parents` flag is `true` (create mis - Blaxel rejects `parents: false` before the call (field `parents`). Omitted or - `true` creates missing parents. + Blaxel creates missing parents when `parents` is omitted or `true`. It does + not support `parents: false` (field `parents`). @@ -295,8 +295,8 @@ Creates the directory at `path`. An omitted `Parents` flag is true. - Blaxel rejects `Parents: false` before the call (field `parents`). Omitted or - true creates missing parents. + Blaxel creates missing parents when `Parents` is omitted or true. It does + not support `Parents: false` (field `parents`). @@ -375,8 +375,8 @@ console.log(info); ``` - Directories return type directory. Files return type file, with size and - permission fields when Blaxel provides them. + You get `type` (file or directory), `size`, and usually `permissions`, + `owner`, `group`, and `modifiedAt`. @@ -401,8 +401,8 @@ fmt.Println(info) ``` - Directories return type directory. Files return type file, with size and mode - when Blaxel provides them. + You get `Type` (file or directory), `Size`, and usually `Permissions`, + `Owner`, `Group`, and `ModifiedAt`. diff --git a/website/content/docs/sdk/process.mdx b/website/content/docs/sdk/process.mdx index 02fc27bb..6573b48f 100644 --- a/website/content/docs/sdk/process.mdx +++ b/website/content/docs/sdk/process.mdx @@ -156,8 +156,8 @@ Outcome of a finished command. You get both Stdout and Stderr. Argv is one quoted shell command string. Omit or zero Timeout waits until the command ends (or you cancel), with no Blaxel time limit. A positive Timeout must be whole seconds and caps how long the command - may run. Canceling stops waiting only; the remote command may keep running. An - If the wait times out, you get a timeout error. + may run. Canceling stops waiting only; the remote command may keep running. If + the wait times out, you get a timeout error. diff --git a/website/content/docs/sdk/sandbox.mdx b/website/content/docs/sdk/sandbox.mdx index b22102c6..8dab97fe 100644 --- a/website/content/docs/sdk/sandbox.mdx +++ b/website/content/docs/sdk/sandbox.mdx @@ -477,13 +477,24 @@ func (s *Sandbox) Refresh(ctx context.Context) error ```ts -delete(options?: DeleteOptions): Promise +delete(options?: DeleteCallOptions): Promise ``` -Removes the Blaxel sandbox. +Deletes the Blaxel sandbox. On success, `state` is `deleted` and `terminal` is true. Omit `wait` to return when Blaxel accepts the delete. Pass `wait: true` to wait until removal finishes. + +**Parameters** + +| Name | Type | Description | +| ---------- | ------------------- | ------------------------------------------------- | +| `options?` | `DeleteCallOptions` | `OperationOptions` plus optional `wait?: boolean` | + +**Returns** + +`Promise` — also stored on the handle ```ts -await sandbox.delete({ timeout: 60_000 }); +const info = await sandbox.delete({ wait: true, timeout: 60_000 }); +console.log(info.state, info.terminal); ``` @@ -493,10 +504,26 @@ await sandbox.delete({ timeout: 60_000 }); func (s *Sandbox) Delete(ctx context.Context, options *DeleteOptions) error ``` +Deletes the Blaxel sandbox. On success, `Info().State` is `Deleted` and `Terminal` is true. Omit `Wait` to return when Blaxel accepts the delete. Pass `Wait: true` to wait until removal finishes. Read the outcome with `Info()`. + +**Parameters** + +| Name | Type | Description | +| --------- | ----------------- | -------------------------- | +| `ctx` | `context.Context` | Cancellation and deadline | +| `options` | `*DeleteOptions` | `Timeout`, optional `Wait` | + +**Returns** + +`error` + ```go err := instance.Delete(ctx, &sandbox.DeleteOptions{ + Wait: sandbox.Value(true), Timeout: sandbox.Duration(time.Minute), }) +info := instance.Info() +fmt.Println(info.State, info.GetTerminal()) ``` @@ -504,7 +531,6 @@ err := instance.Delete(ctx, &sandbox.DeleteOptions{ - ### Terminate @@ -623,17 +649,31 @@ type TerminateCallOptions = OperationOptions & { wait?: boolean }; type DeleteCallOptions = OperationOptions & { wait?: boolean }; ``` -| Field | Description | -| --------- | ---------------------------------------------------------------------------- | -| `signal` | Abort the wait. The handle stays busy until the in-flight call finishes. | -| `timeout` | Deadline for this call (ms). Omitted → client default. `0` → no deadline. | -| `force` | On stop: request an immediate stop. | -| `wait` | On terminate/delete: wait until the operation finishes. | +| Field | Description | +| --------- | ------------------------------------------------------------------------- | +| `signal` | Abort the wait. The handle stays busy until the in-flight call finishes. | +| `timeout` | Deadline for this call (ms). Omitted → client default. `0` → no deadline. | +| `force` | On stop: request an immediate stop. | +| `wait` | On terminate/delete: wait until the operation finishes. | + + + On Daytona, `wait: true` waits until deletion finishes. + + + + On Modal, `wait: true` waits until terminate finishes. + + + + On Blaxel, omit `wait` to return when delete is accepted. Pass `wait: true` to + wait until removal finishes. On success, `state` is `deleted` and `terminal` + is true. + -Lifecycle option messages carry `Timeout *durationpb.Duration` (via `sandbox.Duration`). `StopOptions` adds `Force *bool`. `TerminateOptions` and `DeleteOptions` add `Wait *bool`. +Use `sandbox.Duration` for timeouts. `StopOptions` adds `Force`. `TerminateOptions` and `DeleteOptions` add `Wait`. | Field | Description | | --------- | -------------------------------------------------------------------------- | @@ -641,5 +681,19 @@ Lifecycle option messages carry `Timeout *durationpb.Duration` (via `sandbox.Dur | `Force` | On stop: `Value(true)` requests an immediate stop. | | `Wait` | On terminate/delete: wait until finished. | + + On Daytona, `Wait` waits until deletion finishes. + + + + On Modal, `Wait` waits until terminate finishes. + + + + On Blaxel, omit `Wait` to return when delete is accepted. Pass `Wait: true` to + wait until removal finishes. On success, `State` is `Deleted` and `Terminal` + is true. Read it with `Info()`. + + From 71aeae6a75f53753ff12b19a8609e9c505246cf6 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 13:52:29 +0530 Subject: [PATCH 38/60] Harden Blaxel handle projection and Http2Tls rejection. Project a plain TS sandbox handle instead of mutating the native instance, and classify Http2Tls as Unsupported to match Blaxel's HTTP|TCP|UDP|TLS port protocols. Co-authored-by: Cursor --- sdks/go/providers/blaxel/creation.go | 3 ++- sdks/go/providers/blaxel/rejection_test.go | 2 +- .../providers/blaxel/src/blaxel.test.ts | 17 +++++++----- .../providers/blaxel/src/mappings/mappings.ts | 6 ++--- .../typescript/providers/blaxel/src/native.ts | 26 ++++++++++++++----- specs/providers/blaxel.yaml | 2 +- 6 files changed, 37 insertions(+), 19 deletions(-) diff --git a/sdks/go/providers/blaxel/creation.go b/sdks/go/providers/blaxel/creation.go index 3fb5de27..5aaf39ca 100644 --- a/sdks/go/providers/blaxel/creation.go +++ b/sdks/go/providers/blaxel/creation.go @@ -252,7 +252,8 @@ func blaxelPortProtocol(transport sandbox.PortTransport) (blaxel.PortProtocol, e case sandbox.PortTransportTLS: return blaxel.PortProtocolTls, nil default: - return "", providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldNetworkPortsTransport, "requested port transport is unsupported") + // Blaxel PortProtocol is HTTP|TCP|UDP|TLS only (Kit Http2Tls included). + return "", providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldNetworkPortsTransport, "Http2Tls is not a Blaxel port protocol (HTTP|TCP|UDP|TLS)") } } diff --git a/sdks/go/providers/blaxel/rejection_test.go b/sdks/go/providers/blaxel/rejection_test.go index 71e8f9bc..89e69a64 100644 --- a/sdks/go/providers/blaxel/rejection_test.go +++ b/sdks/go/providers/blaxel/rejection_test.go @@ -345,7 +345,7 @@ func TestRejectsHttp2TlsPortTransport(t *testing.T) { }), "us-pdx-1") var detail *sandbox.Error if !errors.As(err, &detail) || - detail.Kind != sandbox.ErrorKindInvalidArgument || + detail.Kind != sandbox.ErrorKindUnsupported || detail.Field != sandbox.CreateFieldNetworkPortsTransport { t.Fatalf("error = %v detail = %#v", err, detail) } diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 76cdcc05..f59e7200 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -489,7 +489,7 @@ describe("Blaxel mappings", () => { ), ).toThrowError( expect.objectContaining({ - kind: ErrorKind.InvalidArgument, + kind: ErrorKind.Unsupported, field: CreateFieldNetworkPortsTransport, }), ); @@ -547,13 +547,13 @@ describe("Blaxel mappings", () => { mapSandboxIdentity({ metadata: { name: "demo" }, status: "DEPLOYED", - // defaultNativeFactory.projectHandle copies wire sandbox.state here. + // projectHandle puts wire sandbox.state on the plain handle. state: "STANDBY", }).state, ).toBe(SandboxState.Paused); }); - it("projectHandle copies private wire sandbox.state onto state", () => { + it("projectHandle projects private wire sandbox.state without mutating", () => { const instance = new SandboxInstance({ metadata: { name: "demo" }, status: "DEPLOYED", @@ -570,6 +570,7 @@ describe("Blaxel mappings", () => { }); const handle = projectHandle(instance); expect(handle.state).toBe("STANDBY"); + expect((instance as { state?: string }).state).toBeUndefined(); expect( mapSandboxIdentity({ metadata: { name: "demo" }, @@ -789,9 +790,13 @@ describe("Blaxel provider orchestration", () => { scope: { workspace: "ws" }, }); if (backend.delete === undefined) throw new Error("delete is missing"); - const pending = backend.delete("doomed", { wait: true }, { - signal: controller.signal, - }); + const pending = backend.delete( + "doomed", + { wait: true }, + { + signal: controller.signal, + }, + ); const expectation = expect(pending).rejects.toMatchObject({ kind: ErrorKind.Canceled, operation: "delete", diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index d7af666f..c4027c7f 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -172,10 +172,10 @@ function blaxelPortProtocol( if (transport === PortTransport.Default) return undefined; if (transport === PortTransport.Plain) return "TCP"; if (transport === PortTransport.Tls) return "TLS"; - // Blaxel has no HTTP/2 port protocol (Kit Http2Tls included). - invalid( + // Blaxel Port.protocol is HTTP|TCP|UDP|TLS only (Kit Http2Tls included). + unsupported( CreateFieldNetworkPortsTransport, - "requested port transport is unsupported", + "Http2Tls is not a Blaxel port protocol (HTTP|TCP|UDP|TLS)", ); } diff --git a/sdks/typescript/providers/blaxel/src/native.ts b/sdks/typescript/providers/blaxel/src/native.ts index b926abca..c67086f5 100644 --- a/sdks/typescript/providers/blaxel/src/native.ts +++ b/sdks/typescript/providers/blaxel/src/native.ts @@ -80,17 +80,29 @@ export function defaultNativeFactory(config: NativeClientConfig): NativeBlaxel { /** * `@blaxel/core` types `SandboxInstance.sandbox` private and has no `state` - * getter. Project runtime STANDBY/RUNNING onto a Kit-facing `state` field so - * identity mapping never reaches into the private wire model. Wrap `fs` with - * `get` so Stat can use the same filesystem GET as Go. + * getter. Project a plain Kit handle (identity + wrapped fs/process) so + * mapping never mutates the native instance or reaches into its private wire + * model after projection. Wrap `fs` with `get` so Stat uses the same + * filesystem GET as Go. */ export function projectHandle(instance: SandboxInstance): NativeSandboxHandle { const state = wireRuntimeState(instance); - const base = - state === undefined ? instance : Object.assign(instance, { state }); - return Object.assign(base, { + return { + metadata: instance.metadata, + status: instance.status, + ...(state !== undefined ? { state } : {}), + spec: instance.spec, fs: wrapFilesystem(instance.fs), - }) as unknown as NativeSandboxHandle; + process: { + exec: (request) => + instance.process.exec(request) as Promise, + wait: (identifier, options) => + instance.process.wait( + identifier, + options, + ) as Promise, + }, + }; } function wrapFilesystem(fs: SandboxFileSystem): NativeSandboxHandle["fs"] { diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml index 06d529fa..98c7ce64 100644 --- a/specs/providers/blaxel.yaml +++ b/specs/providers/blaxel.yaml @@ -27,7 +27,7 @@ checks: - { path: network.ports.transport, allowed: [default, plain, tls], - notes: "Default omits protocol (Blaxel HTTP); Plain→TCP; TLS→TLS; Http2Tls rejected in create mapping", + notes: "Default omits protocol (Blaxel HTTP); Plain→TCP; TLS→TLS; Http2Tls → Unsupported in create mapping", } - { path: network.outbound_domains.entries, From 34c558e93818d80525498ed7b209f645870bc52f Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 14:23:18 +0530 Subject: [PATCH 39/60] Forward Blaxel create timeouts and tighten caller-facing docs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Map 1–50s create timeouts to Blaxel's native timeout, fix FS NotFound field tagging and empty labels, and rewrite Blaxel website/docs for SDK callers. Co-authored-by: Cursor --- docs/sandbox-creation.md | 26 +++---- sdks/go/providers/blaxel/README.md | 5 +- sdks/go/providers/blaxel/client.go | 4 +- sdks/go/providers/blaxel/client_test.go | 14 ++-- sdks/go/providers/blaxel/creation.go | 4 +- sdks/go/providers/blaxel/sandbox.go | 33 ++++++++- sdks/go/providers/blaxel/sandbox_test.go | 29 ++++++++ sdks/typescript/providers/blaxel/README.md | 9 +-- .../providers/blaxel/src/blaxel.test.ts | 37 +++++++++- .../typescript/providers/blaxel/src/blaxel.ts | 10 ++- .../typescript/providers/blaxel/src/errors.ts | 18 ++++- .../providers/blaxel/src/mappings/mappings.ts | 22 +++++- .../typescript/providers/blaxel/src/native.ts | 21 +++++- specs/providers/blaxel.yaml | 4 ++ website/content/docs/blaxel.mdx | 32 +++++---- website/content/docs/configuration.mdx | 19 +++-- website/content/docs/create.mdx | 32 ++++----- website/content/docs/filesystem.mdx | 8 +-- website/content/docs/lifecycle.mdx | 20 +++--- website/content/docs/process.mdx | 21 +++--- website/content/docs/sdk/client.mdx | 13 ++-- website/content/docs/sdk/configuration.mdx | 10 +-- website/content/docs/sdk/create-options.mdx | 71 ++++++++++--------- website/content/docs/sdk/enums.mdx | 3 +- website/content/docs/sdk/filesystem.mdx | 8 +-- website/content/docs/sdk/process.mdx | 20 +++--- website/content/docs/sdk/sandbox.mdx | 11 ++- 27 files changed, 332 insertions(+), 172 deletions(-) diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index 89bf0530..c1ee8ce4 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -9,19 +9,19 @@ from lifecycle operations on an existing sandbox. Go and TypeScript integrations target Modal, Daytona, and Blaxel. Pinned dependency versions live in each provider package. -| Setting | Modal | Daytona | Blaxel | -| ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------- | -| Source | Explicit registry image and an existing app | Default/specified snapshot or registry image | Named create (Blaxel name rules); registry image required | -| Runtime | Entrypoint, absolute working directory, PTY | Toolbox language and user | Unsupported | -| Isolation | Default, gVisor container, Linux VM | Nested KVM flag; class inherited from source | Unsupported | -| Resources | Fractional CPU, CPU limit, memory request/limit | Whole CPU and GiB memory/disk; image source required for overrides | Memory (`memory_mib` / `memoryMib`); CPU/disk overrides rejected | -| Placement | Cloud and region preferences | Client target; request placement unsupported | Client `region` required for create; request placement unsupported | -| Environment and labels | Environment map and tags | Environment map and labels | Environment map and labels | -| Security | Named secrets and workload identity | Egress-placeholder secrets and public access | Unsupported | -| Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | Ports (not 80/443/8080; Default → Blaxel HTTP); outbound domains | -| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m); explicit idle disable rejected | -| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Client/create timeout only; waitFor Default/Scheduled only; Ready unsupported | -| Observability | Verbose output | Telemetry endpoint | Unsupported | +| Setting | Modal | Daytona | Blaxel | +| ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | +| Source | Explicit registry image and an existing app | Default/specified snapshot or registry image | Named create (Blaxel name rules); registry image required | +| Runtime | Entrypoint, absolute working directory, PTY | Toolbox language and user | Unsupported | +| Isolation | Default, gVisor container, Linux VM | Nested KVM flag; class inherited from source | Unsupported | +| Resources | Fractional CPU, CPU limit, memory request/limit | Whole CPU and GiB memory/disk; image source required for overrides | Memory (`memory_mib` / `memoryMib`); CPU/disk overrides rejected | +| Placement | Cloud and region preferences | Client target; request placement unsupported | Client `region` required for create; request placement unsupported | +| Environment and labels | Environment map and tags | Environment map and labels | Environment map and labels | +| Security | Named secrets and workload identity | Egress-placeholder secrets and public access | Unsupported | +| Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | Ports (not 80/443/8080; Default → Blaxel HTTP); outbound domains | +| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m); explicit idle disable rejected | +| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Create `timeout`; 1–50s frees the name on timeout; waitFor Default/Scheduled only; Ready unsupported | +| Observability | Verbose output | Telemetry endpoint | Unsupported | Kit has no portable volume fields; Blaxel volumes stay out of create the same way Daytona and Modal volumes do. diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 19b01d35..93bd1e58 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -71,8 +71,9 @@ terminate (≥1 minute). You cannot disable idle terminate. Ports 80, 443, and 8080 are reserved. Default port transport is HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is not supported. -Your client/create timeout is how long create may take. Ready wait and -readiness probes are not supported (`WaitFor` only Default or Scheduled). +Set client or create timeout for how long create may wait. With a 1–50s +timeout, a timed-out create frees the name. Ready wait and readiness probes +are not supported (`WaitFor` only Default or Scheduled). Set `Resources.MemoryMib` for memory size. Blaxel documents that value as MB; Kit does not convert units. CPU follows memory on Blaxel — do not set CPU or diff --git a/sdks/go/providers/blaxel/client.go b/sdks/go/providers/blaxel/client.go index c0dba657..1be1080c 100644 --- a/sdks/go/providers/blaxel/client.go +++ b/sdks/go/providers/blaxel/client.go @@ -1,6 +1,8 @@ package blaxel import ( + "strings" + "github.com/blaxel-ai/sdk-go/option" "github.com/sandbox-kit/kit/sdks/go/sandbox" ) @@ -61,7 +63,7 @@ func (s *clientSettings) applyScope() error { return nil } workspace := scope.GetWorkspace() - if workspace == "" { + if strings.TrimSpace(workspace) == "" { return invalidClientField(sandbox.ConfigFieldScopeWorkspace, "workspace requires a non-blank value") } s.opts = append(s.opts, option.WithWorkspace(workspace)) diff --git a/sdks/go/providers/blaxel/client_test.go b/sdks/go/providers/blaxel/client_test.go index b591764a..664be4aa 100644 --- a/sdks/go/providers/blaxel/client_test.go +++ b/sdks/go/providers/blaxel/client_test.go @@ -102,11 +102,13 @@ func TestAuthReportsTheMissingCredential(t *testing.T) { } func TestBlankWorkspaceRejected(t *testing.T) { - _, err := mapClientConfig(&sandbox.Config{ - Scope: &sandbox.Scope{Workspace: sandbox.Value("")}, - }) - var detail *sandbox.Error - if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidArgument || detail.Field != sandbox.ConfigFieldScopeWorkspace { - t.Fatalf("error = %v detail = %#v", err, detail) + for _, workspace := range []string{"", " "} { + _, err := mapClientConfig(&sandbox.Config{ + Scope: &sandbox.Scope{Workspace: sandbox.Value(workspace)}, + }) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidArgument || detail.Field != sandbox.ConfigFieldScopeWorkspace { + t.Fatalf("workspace %q: error = %v detail = %#v", workspace, err, detail) + } } } diff --git a/sdks/go/providers/blaxel/creation.go b/sdks/go/providers/blaxel/creation.go index 5aaf39ca..95145425 100644 --- a/sdks/go/providers/blaxel/creation.go +++ b/sdks/go/providers/blaxel/creation.go @@ -262,7 +262,9 @@ func (c *creation) provisioning() error { return nil } config := cloneMessage(c.request.Provisioning) - // Client.Create already applies Timeout as the context deadline. + // Client.Create applies Timeout as the context deadline. When that value + // is a whole number of seconds in 1..50, create also sends + // X-Blaxel-Creation-Timeout so Blaxel frees the name on CREATION_TIMEOUT. config.Timeout = nil if config.QueueTimeout != nil { return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldProvisioningQueueTimeout, "Blaxel queue timeout is unsupported") diff --git a/sdks/go/providers/blaxel/sandbox.go b/sdks/go/providers/blaxel/sandbox.go index a6a8ce70..1bf230b0 100644 --- a/sdks/go/providers/blaxel/sandbox.go +++ b/sdks/go/providers/blaxel/sandbox.go @@ -2,7 +2,10 @@ package blaxel import ( "context" + "strconv" + "time" + "github.com/blaxel-ai/sdk-go/option" "github.com/sandbox-kit/kit/sdks/go/sandbox" ) @@ -21,7 +24,7 @@ func (b *backend) create(ctx context.Context, request *sandbox.CreateOptions) (* if err != nil { return nil, err } - instance, err := b.client.Sandboxes.NewInstance(ctx, plan.params) + instance, err := b.client.Sandboxes.NewInstance(ctx, plan.params, blaxelCreateOptions(request)...) if err != nil { return nil, err } @@ -36,6 +39,34 @@ func (b *backend) create(ctx context.Context, request *sandbox.CreateOptions) (* return &sandbox.CreateResult{Sandbox: info}, nil } +// blaxelCreateOptions forwards Blaxel's create timeout when Kit's create +// deadline is a whole number of seconds in 1..50. Outside that range Kit keeps +// the context deadline only (same bounds as @blaxel/core). +func blaxelCreateOptions(request *sandbox.CreateOptions) []option.RequestOption { + seconds := blaxelCreateTimeoutSeconds(request) + if seconds == 0 { + return nil + } + return []option.RequestOption{ + option.WithHeader("X-Blaxel-Creation-Timeout", strconv.Itoa(seconds)), + } +} + +func blaxelCreateTimeoutSeconds(request *sandbox.CreateOptions) int { + if request == nil || request.Provisioning == nil || request.Provisioning.Timeout == nil { + return 0 + } + timeout := request.Provisioning.Timeout.AsDuration() + if timeout <= 0 || timeout%time.Second != 0 { + return 0 + } + seconds := int(timeout / time.Second) + if seconds < 1 || seconds > 50 { + return 0 + } + return seconds +} + // RefreshSandbox retrieves current metadata for an existing Blaxel sandbox by name. func (b *backend) RefreshSandbox(ctx context.Context, id string) (*sandbox.SandboxInfo, error) { instance, err := b.getInstance(ctx, id, "refresh") diff --git a/sdks/go/providers/blaxel/sandbox_test.go b/sdks/go/providers/blaxel/sandbox_test.go index 58e13177..f1a7cb9f 100644 --- a/sdks/go/providers/blaxel/sandbox_test.go +++ b/sdks/go/providers/blaxel/sandbox_test.go @@ -108,6 +108,35 @@ func TestMapSandboxInfoUsesNameAsID(t *testing.T) { } } +func TestBlaxelCreateTimeoutSeconds(t *testing.T) { + for _, tc := range []struct { + timeout *time.Duration + want int + }{ + {nil, 0}, + {durationPtr(0), 0}, + {durationPtr(30 * time.Second), 30}, + {durationPtr(time.Second), 1}, + {durationPtr(50 * time.Second), 50}, + {durationPtr(1500 * time.Millisecond), 0}, + {durationPtr(120 * time.Second), 0}, + } { + request := &sandbox.CreateOptions{} + if tc.timeout != nil { + request.Provisioning = &sandbox.ProvisioningOptions{ + Timeout: sandbox.Duration(*tc.timeout), + } + } + if got := blaxelCreateTimeoutSeconds(request); got != tc.want { + t.Fatalf("timeout %#v: got %d want %d", tc.timeout, got, tc.want) + } + } +} + +func durationPtr(value time.Duration) *time.Duration { + return &value +} + func TestFormatBlaxelDuration(t *testing.T) { for _, tc := range []struct { in time.Duration diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index 71157bf3..3136030a 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -65,10 +65,11 @@ memory, Blaxel applies its default. CPU and disk overrides are rejected — Blaxel derives CPU from memory. Set `memoryMib` for memory size; Blaxel documents that value as MB and Kit does not convert units. -Your client/create `timeout` is how long create may take. Ready wait is not -supported (`waitFor` only Default or Scheduled). Ports 80, 443, and 8080 are -reserved. Default port transport is HTTP; plain is TCP; TLS is TLS; HTTP/2 -over TLS is not supported. Volumes are not part of create. +Set client or create `timeout` for how long create may wait. With a 1–50s +timeout, a timed-out create frees the name. Ready wait is not supported +(`waitFor` only Default or Scheduled). Ports 80, 443, and 8080 are reserved. +Default port transport is HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is +not supported. Volumes are not part of create. If the name already exists, you get `AlreadyExists`. diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index f59e7200..ff0c1a9f 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -22,8 +22,10 @@ import { CreateFieldNetworkPortsTransport, CreateFieldProvisioningWaitFor, FileFieldLocalPath, + FileFieldPath, } from "@sandbox-kit/sandbox"; import { + blaxelCreateTimeoutSeconds, mapClientConfig, mapCreateRequest, mapSandboxInfo, @@ -205,6 +207,16 @@ describe("Blaxel mappings", () => { ).not.toThrow(); }); + it("forwards Blaxel create timeout only for whole seconds 1..50", () => { + expect(blaxelCreateTimeoutSeconds(30_000)).toBe(30); + expect(blaxelCreateTimeoutSeconds(1_000)).toBe(1); + expect(blaxelCreateTimeoutSeconds(50_000)).toBe(50); + expect(blaxelCreateTimeoutSeconds(undefined)).toBeUndefined(); + expect(blaxelCreateTimeoutSeconds(0)).toBeUndefined(); + expect(blaxelCreateTimeoutSeconds(1_500)).toBeUndefined(); + expect(blaxelCreateTimeoutSeconds(120_000)).toBeUndefined(); + }); + it("rejects WaitCondition.Ready", () => { expect(() => mapCreateRequest( @@ -289,6 +301,13 @@ describe("Blaxel mappings", () => { kind: ErrorKind.NotFound, provider: "blaxel", operation: "refresh", + field: "", + cause: native, + }); + expect(mapBlaxelError(native, "stat")).toMatchObject({ + kind: ErrorKind.NotFound, + operation: "stat", + field: FileFieldPath, cause: native, }); }); @@ -447,6 +466,18 @@ describe("Blaxel mappings", () => { }); }); + it("omits labels when Blaxel does not return them", () => { + const info = mapSandboxInfo({ + metadata: { name: "demo" }, + status: "DEPLOYED", + state: "RUNNING", + spec: { region: "us-pdx-1" }, + }); + expect(info.labels).toBeUndefined(); + expect(info.createdAt).toBeUndefined(); + expect(info.providerMetadata).toBeUndefined(); + }); + it("marks DELETED and TERMINATED refresh as terminal", () => { for (const status of ["DELETED", "TERMINATED", "DEACTIVATED"] as const) { expect( @@ -585,6 +616,7 @@ describe("Blaxel mappings", () => { describe("Blaxel provider orchestration", () => { it("creates with SandboxInstance.create and never createIfNotExists", async () => { let created: unknown; + let createOptions: unknown; let configured: unknown; const nativeSandbox = { metadata: { name: "example" }, @@ -599,8 +631,9 @@ describe("Blaxel provider orchestration", () => { configure(config) { configured = config; }, - async create(params) { + async create(params, options) { created = params; + createOptions = options; return nativeSandbox; }, async get() { @@ -619,6 +652,7 @@ describe("Blaxel provider orchestration", () => { name: "example", source: { image: { reference: "blaxel/base-image:latest" } }, resources: { memoryMib: 2048n }, + provisioning: { timeout: 30_000 }, }); await client.close(); @@ -629,6 +663,7 @@ describe("Blaxel provider orchestration", () => { memory: 2048, region: "us-pdx-1", }); + expect(createOptions).toEqual({ timeout: 30 }); expect(sandbox.id).toBe("example"); expect(sandbox.info().name).toBe("example"); }); diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index 2d82756f..c4d7cd6d 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -24,6 +24,7 @@ import type { StatOptions, } from "@sandbox-kit/sandbox"; import { + blaxelCreateTimeoutSeconds, mapClientConfig, mapCreateRequest, mapSandboxInfo, @@ -95,7 +96,13 @@ class BlaxelBackend extends BackendBase implements Backend { throwIfAborted(options.signal); const plan = mapCreateRequest(request, this.config); throwIfAborted(options.signal); - const sandbox = await this.native.create(plan.params); + const timeoutSeconds = blaxelCreateTimeoutSeconds( + request.provisioning?.timeout, + ); + const sandbox = await this.native.create( + plan.params, + timeoutSeconds === undefined ? undefined : { timeout: timeoutSeconds }, + ); return { sandbox: mapSandboxInfo(sandbox, request) }; } catch (error) { throw mapBlaxelError(error, "create"); @@ -338,7 +345,6 @@ class BlaxelBackend extends BackendBase implements Backend { state: SandboxState.Deleted, terminal: true, providerState: "TERMINATED", - labels: {}, }; } throw mapped; diff --git a/sdks/typescript/providers/blaxel/src/errors.ts b/sdks/typescript/providers/blaxel/src/errors.ts index d038b004..c8219bbc 100644 --- a/sdks/typescript/providers/blaxel/src/errors.ts +++ b/sdks/typescript/providers/blaxel/src/errors.ts @@ -16,11 +16,23 @@ import { } from "@blaxel/core"; import { ErrorKind, + FileFieldPath, SandboxError, sandboxError, type ErrorInfo, } from "@sandbox-kit/sandbox"; +const FILESYSTEM_OPERATIONS = new Set([ + "read_file", + "write_file", + "copy_from_local", + "copy_to_local", + "make_directory", + "list_directory", + "stat", + "remove", +]); + /** Inputs for translating a native/provider failure into {@link SandboxError}. */ interface MappedErrorInput { kind: ErrorInfo["kind"]; @@ -199,12 +211,16 @@ function stringifyCode(code: unknown): string | undefined { function create(input: MappedErrorInput): SandboxError { const { kind, error, operation, statusCode, providerCode } = input; + const field = + kind === ErrorKind.NotFound && FILESYSTEM_OPERATIONS.has(operation) + ? FileFieldPath + : ""; return sandboxError( { kind, provider: "blaxel", operation, - field: "", + field, message: error instanceof Error ? error.message diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index c4027c7f..3f0d7c87 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -255,7 +255,8 @@ function mapObservability(r: CreateOptions): void { } // Kit Client.create applies provisioning.timeout as the abort deadline. -// This provider does not forward a native create timeout — use the Kit deadline. +// When that deadline is a whole number of seconds in 1..50, create also +// forwards Blaxel's native create timeout (name freed on CREATION_TIMEOUT). function mapProvisioning(r: CreateOptions): void { const v = r.provisioning; if (!v) return; @@ -274,6 +275,21 @@ function mapProvisioning(r: CreateOptions): void { ); } +/** + * Blaxel `SandboxInstance.create` timeout is whole seconds 1..50. + * Outside that range Kit keeps the abort deadline only. + */ +export function blaxelCreateTimeoutSeconds( + timeoutMs: number | undefined, +): number | undefined { + if (timeoutMs === undefined || timeoutMs === 0) return undefined; + if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs % 1_000 !== 0) + return undefined; + const seconds = timeoutMs / 1_000; + if (seconds < 1 || seconds > 50) return undefined; + return seconds; +} + export function mapSandboxInfo( value: BlaxelSandboxIdentity, request?: CreateOptions, @@ -284,11 +300,11 @@ export function mapSandboxInfo( id, provider: "blaxel", state: mapped.state ?? SandboxState.Unknown, - labels: mapped.labels ?? {}, }; if (mapped.name !== undefined) mutable(result).name = mapped.name; else if (request?.name !== undefined) mutable(result).name = request.name; - if (request?.labels !== undefined && mapped.labels === undefined) + if (mapped.labels !== undefined) mutable(result).labels = mapped.labels; + else if (request?.labels !== undefined) mutable(result).labels = { ...request.labels }; if (mapped.region !== undefined) mutable(result).region = mapped.region; if (mapped.providerState !== undefined) diff --git a/sdks/typescript/providers/blaxel/src/native.ts b/sdks/typescript/providers/blaxel/src/native.ts index c67086f5..0483647f 100644 --- a/sdks/typescript/providers/blaxel/src/native.ts +++ b/sdks/typescript/providers/blaxel/src/native.ts @@ -51,9 +51,17 @@ export interface NativeSandboxHandle extends BlaxelSandboxIdentity { }; } +export interface NativeCreateOptions { + /** Blaxel create timeout in whole seconds (1..50). */ + readonly timeout?: number; +} + export interface NativeBlaxel { configure(config: NativeClientConfig): void; - create(params: SandboxCreateConfiguration): Promise; + create( + params: SandboxCreateConfiguration, + options?: NativeCreateOptions, + ): Promise; get(name: string): Promise; delete(name: string): Promise; } @@ -66,8 +74,15 @@ export function defaultNativeFactory(config: NativeClientConfig): NativeBlaxel { configure(next: NativeClientConfig): void { settings.setConfig(next); }, - async create(params) { - return projectHandle(await SandboxInstance.create(params)); + async create(params, options) { + return projectHandle( + await SandboxInstance.create( + params, + options?.timeout === undefined + ? undefined + : { timeout: options.timeout }, + ), + ); }, async get(name) { return projectHandle(await SandboxInstance.get(name)); diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml index 98c7ce64..14fde26e 100644 --- a/specs/providers/blaxel.yaml +++ b/specs/providers/blaxel.yaml @@ -43,6 +43,10 @@ checks: - { path: security, unsupported: true } - { path: observability, unsupported: true } - { path: provisioning.queue_timeout, unsupported: true } + - { + path: provisioning.timeout, + notes: "Kit create deadline; when whole seconds 1..50 also X-Blaxel-Creation-Timeout / SandboxInstance.create timeout so Blaxel frees the name", + } - { path: provisioning.wait_for, allowed: [default, scheduled], diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index 5c002cae..9e2fca69 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -27,9 +27,9 @@ go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest ## Create -Set `BL_API_KEY` and `BL_WORKSPACE`. Create requires a Blaxel-valid name -(lowercase alphanumeric with single hyphens, at most 49 characters) and an -image. Set `region` on the client (default examples use `us-pdx-1`). +Set `BL_API_KEY` and `BL_WORKSPACE`. Create needs a Blaxel-valid `name` +(lowercase alphanumeric with single hyphens, ≤49 characters), an image, and +client `region`. @@ -53,7 +53,9 @@ const sandbox = await client.create({ console.log(sandbox.id); // same as create name; use with get / BL_SANDBOX_NAME ``` -`timeout` is milliseconds. Prefer `await using`; otherwise call `await client.close()` when finished. Use one Blaxel client (or one API key) per process — a second client with different credentials replaces the first. +`timeout` is milliseconds. Prefer `await using`; otherwise call +`await client.close()` when finished. Use one Blaxel client (or one API key) +per process. @@ -106,8 +108,8 @@ Optional strings use `sandbox.Value`. Omit `auth` and `scope.workspace` to use `BL_API_KEY` and `BL_WORKSPACE` from -the environment. `region` is still required for create. `endpoint` is rejected. -Closing the client does not delete the sandbox. +the environment. `region` is still required for create. `endpoint` is not +supported. Closing the client does not delete the sandbox. ## Configuration @@ -159,8 +161,8 @@ client, err := sandbox.NewClient(blaxel.New(), config) -Token-pair, bearer, and OAuth are rejected. Only workspace scope is accepted. -In TypeScript, use one Blaxel client (or one API key) per process. Full rules: +Only API key auth and workspace scope are supported. In TypeScript, use one +Blaxel client (or one API key) per process. Full rules: [Configuration](/docs/configuration). ## What works @@ -177,12 +179,14 @@ In TypeScript, use one Blaxel client (or one API key) per process. Full rules: | Files | Shared filesystem methods — [Filesystem](/docs/filesystem) | | Process | `run` waits for the command — [Run a command](/docs/process) | -Set `memoryMib` / `memory_mib` for memory size. Blaxel documents that value as -MB; Kit does not convert units. CPU follows memory — CPU and disk overrides are -rejected. Omit memory to use Blaxel's default. Default ports are HTTP; plain is -TCP; TLS is TLS; HTTP/2 over TLS is not supported. Your client/create `timeout` -is how long create may take. Ready wait and readiness probes are not supported. -You cannot disable idle terminate. +Pass `memoryMib` / `memory_mib` for memory (Blaxel treats it as MB). Omit it for +Blaxel’s default. CPU and disk overrides are not supported. + +Default ports are HTTP; plain is TCP; TLS is TLS. HTTP/2 over TLS is not +supported. Ready wait and idle disable are not supported. + +Set client or create `timeout` for how long create may wait. With a 1–50s +timeout, a timed-out create frees the name. ## Related diff --git a/website/content/docs/configuration.mdx b/website/content/docs/configuration.mdx index 8abe69df..2ca23961 100644 --- a/website/content/docs/configuration.mdx +++ b/website/content/docs/configuration.mdx @@ -339,13 +339,13 @@ client, err := sandbox.NewClient(modal.New(), config) -| Setting | What to pass | -| -------------- | ---------------------------------------- | -| API key | `auth.apiKey.key` | +| Setting | What to pass | +| -------------- | ------------------------------------------------------- | +| API key | `auth.apiKey.key` | | Omitted `auth` | Uses `BL_API_KEY` / `BL_WORKSPACE` from the environment | -| Workspace | `scope.workspace` | -| Region | `region` — required for create | -| Endpoint | Unsupported | +| Workspace | `scope.workspace` | +| Region | `region` — required for create | +| Endpoint | Unsupported | In TypeScript, use one Blaxel client (or one API key) per process. A second client with different credentials replaces the first. @@ -387,7 +387,7 @@ client, err := sandbox.NewClient(blaxel.New(), config) -Token-pair, bearer, and OAuth modes are rejected. Scope fields other than workspace are rejected. +Only API key auth and workspace scope are supported. @@ -531,9 +531,8 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ - Your client/create `timeout` is how long create may take. Ready wait and - readiness probes are unsupported. In TypeScript, use one Blaxel client (or one - API key) per process. + Set client or create `timeout` for how long create may wait. Ready wait is not + supported. In TypeScript, use one Blaxel client (or one API key) per process. ## Credentials diff --git a/website/content/docs/create.mdx b/website/content/docs/create.mdx index 8483ee92..7ceb3af5 100644 --- a/website/content/docs/create.mdx +++ b/website/content/docs/create.mdx @@ -272,16 +272,16 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ | | Blaxel | | --------- | ------------------------------------------------------------------------------------------------- | | Source | Registry image and Blaxel-valid `name` required (lowercase alphanumeric with single hyphens, ≤49) | -| CPU | Derived by Blaxel from memory; Kit rejects CPU overrides | -| Memory | `memoryMib` / `memory_mib` (Blaxel documents MB; Kit does not convert) | +| CPU | Derived from memory; CPU overrides are not supported | +| Memory | `memoryMib` / `memory_mib` (Blaxel treats the value as MB) | | Disk | Cannot be set | -| Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m); explicit idle disable rejected | -| Placement | Client `region` required. A placement block on this request is rejected | -| Ports | Default → Blaxel HTTP; plain → TCP; TLS → TLS; Http2Tls rejected; 80/443/8080 reserved | -| Wait | `waitFor` only `Default` / `Scheduled`; `Ready` rejected | -| Runtime | Unsupported | +| Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m); idle disable not supported | +| Placement | Client `region` required; placement on the request is not supported | +| Ports | Default → HTTP; plain → TCP; TLS → TLS; Http2Tls not supported; 80/443/8080 reserved | +| Wait | `waitFor` only `Default` / `Scheduled`; create `timeout` 1–50s frees the name on timeout | +| Runtime | Not supported | -Kit has no portable volume fields. +Volumes are not available on create. @@ -610,15 +610,13 @@ See [Examples](/docs/examples). -Create needs a Blaxel-valid `name` (lowercase alphanumeric with single hyphens, -at most 49 characters), an image, and client `region`. The returned id is that -name. Pass memory as `memoryMib` / `memory_mib`; CPU, disk, placement, -readiness, runtime, and volumes are rejected. Ports 80, 443, and 8080 are -reserved. Default ports are HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is -not supported. Max-age TTL ≥5m; idle terminate ≥1m; you cannot disable idle -terminate. `waitFor` only `Default` / `Scheduled`; Ready wait is not -supported. Your client/create `timeout` is how long create may take. See -[Examples](/docs/examples). +Create needs a Blaxel-valid `name`, an image, and client `region`. The returned +id is that name. + +Set create `timeout` for how long create may wait. With 1–50 seconds, a +timed-out create frees the name so you can retry. + +See [Examples](/docs/examples). diff --git a/website/content/docs/filesystem.mdx b/website/content/docs/filesystem.mdx index ac48ff06..93f42693 100644 --- a/website/content/docs/filesystem.mdx +++ b/website/content/docs/filesystem.mdx @@ -117,7 +117,7 @@ data, err := instance.Filesystem().ReadFile(ctx, "/tmp/note.txt", &sandbox.FileR -Timeouts are milliseconds. File calls use the abort signal / Kit deadline. `1500` ms ends at 1.5s. +Timeouts are milliseconds. `1500` ends the call at 1.5s. ```ts await sandbox.filesystem.readFile("/tmp/note.txt", { timeout: 1_500 }); @@ -247,8 +247,8 @@ err := instance.Filesystem().CopyToLocal(ctx, "/tmp/note.txt", "note.txt", nil) - Blaxel creates missing parents when `parents` is omitted or true, and rejects - `parents: false` before the call (field `parents`). + Missing parents are created when `parents` is omitted or true. `parents: + false` is not supported. @@ -348,7 +348,7 @@ fmt.Println(entries) Directories return type directory. Files return type file, with size and - permission fields when Blaxel provides them. + permissions when available. diff --git a/website/content/docs/lifecycle.mdx b/website/content/docs/lifecycle.mdx index 9403ddd9..c41c5dea 100644 --- a/website/content/docs/lifecycle.mdx +++ b/website/content/docs/lifecycle.mdx @@ -127,7 +127,10 @@ if err != nil { -`get` loads a sandbox that already exists by **name** (Blaxel id is the name). `refresh` loads it again. `attach` is not available. After `delete` succeeds you see `deleted` / `terminal: true`. Omit `wait` to return when Blaxel accepts the delete; recreating the same name too soon may return `Conflict`. Pass `wait: true` to wait until removal finishes. An idle sandbox shows as paused; an active one shows as running. +`get` loads a sandbox that already exists by **name** (the id is the create +name). `refresh` loads it again. `attach` is not available. + +Idle sandboxes show as paused; active ones as running. @@ -268,7 +271,9 @@ if err := instance.Delete(ctx, &sandbox.DeleteOptions{ ## Delete -`delete` removes a Blaxel sandbox. On success, `state` is `deleted` and `terminal` is true. Omit `wait` to return when Blaxel accepts the delete; recreating the same name too soon may return `Conflict`. Pass `wait: true` to wait until removal finishes. +`delete` removes a Blaxel sandbox. On success, `state` is `deleted` and +`terminal` is true. Omit `wait` to return when delete is accepted. Pass +`wait: true` to wait until removal finishes before recreate. @@ -392,9 +397,8 @@ defer client.Close(context.Background()) `Sandbox.Close` does not stop or delete the sandbox. Later calls on that handle still reach the provider. - On Blaxel, handle `close` releases local state only and does not contact - Blaxel. The sandbox keeps running until you `delete` it or a lifetime policy - ends it. + Handle `close` does not delete the sandbox. It keeps running until you + `delete` it or a lifetime policy ends it. @@ -416,10 +420,8 @@ mean the cloud already changed state. See [Errors](/docs/errors). - After a successful `delete`, `info.state` is `deleted` and `terminal` is true. - Use `wait: true` when you need removal to finish before recreate. Use - `refresh` or `get` when a call failed or you need fresh metadata while the - sandbox still exists. + After `delete`, use `wait: true` when you need removal to finish before + recreate. Use `refresh` or `get` for fresh metadata while the sandbox exists. diff --git a/website/content/docs/process.mdx b/website/content/docs/process.mdx index bc09043b..34a991f2 100644 --- a/website/content/docs/process.mdx +++ b/website/content/docs/process.mdx @@ -155,17 +155,16 @@ fmt.Println(result) -| You pass | What happens | -| ---------------------- | ----------------------------------------------------------------- | -| `argv` | Sent as one quoted shell command string | -| `shell` | Run as-is | -| omit / zero timeout | Wait until the command ends (or you cancel); no Blaxel time limit | -| positive timeout | Cap how long the command may run (whole seconds only) | -| fractional (`1500` ms) | Rejected | - -You get both `stdout` and `stderr`. `run` waits until the command ends. -Canceling stops waiting only — the remote command may keep running. If the wait -times out, you get a timeout error. +| You pass | What happens | +| ---------------------- | ----------------------------------------------------- | +| `argv` | Sent as one shell command string | +| `shell` | Run as-is | +| omit / zero timeout | Wait until the command ends | +| positive timeout | Cap how long the command may run (whole seconds only) | +| fractional (`1500` ms) | Not supported | + +You get `stdout` and `stderr`. `run` waits until the command ends. Abort stops +the wait only — the remote command may keep running. diff --git a/website/content/docs/sdk/client.mdx b/website/content/docs/sdk/client.mdx index ed0d7843..ecc808d5 100644 --- a/website/content/docs/sdk/client.mdx +++ b/website/content/docs/sdk/client.mdx @@ -5,11 +5,10 @@ description: Construct a client; create, get or attach, and close. Creates sandboxes and reopens them. - - Reopen with [`get`](#get) by sandbox id. - +Reopen with [`get`](#get) by sandbox id. - Reopen with [`attach`](#attach) by sandbox id. Attach does not check that the sandbox exists. + Reopen with [`attach`](#attach) by sandbox id. Attach does not check that the + sandbox exists. Reopen with [`get`](#get) by create name. Blaxel id is the name. @@ -125,7 +124,7 @@ Creates one sandbox and returns its handle. Pass an `image` source. Empty options are rejected. - Pass a Blaxel-valid `name`, an `image` source, and client `region`. Empty options are rejected. + Pass a Blaxel-valid `name`, an `image` source, and client `region`. **Parameters** @@ -196,7 +195,7 @@ Creates one sandbox and returns its handle. Pass an image source. A nil request is rejected. - Pass a Blaxel-valid `Name`, an image source, and client `Region`. A nil request is rejected. + Pass a Blaxel-valid `Name`, an image source, and client `Region`. **Parameters** @@ -326,7 +325,7 @@ fmt.Println(instance.Info()) ### get -Looks up a sandbox by **name**. On Blaxel, the id is the create name. +Looks up a sandbox by **name**. The id is the create name. diff --git a/website/content/docs/sdk/configuration.mdx b/website/content/docs/sdk/configuration.mdx index 667b92b4..cb280930 100644 --- a/website/content/docs/sdk/configuration.mdx +++ b/website/content/docs/sdk/configuration.mdx @@ -48,9 +48,9 @@ await using client = new Client({ - Your client/create `timeout` is how long create may take. `region` is required - for create. `endpoint` is unsupported. Use one Blaxel client (or one API key) - per process. + Set client or create `timeout` for how long create may wait. `region` is + required for create. `endpoint` is not supported. Use one Blaxel client (or + one API key) per process. @@ -94,8 +94,8 @@ client, err := sandbox.NewClient(provider, &sandbox.Config{ - Your client/create `Timeout` is how long create may take. `Region` is required - for create. `Endpoint` is unsupported. + Set client or create `Timeout` for how long create may wait. `Region` is + required for create. `Endpoint` is not supported. diff --git a/website/content/docs/sdk/create-options.mdx b/website/content/docs/sdk/create-options.mdx index 81ab5256..6bc3b814 100644 --- a/website/content/docs/sdk/create-options.mdx +++ b/website/content/docs/sdk/create-options.mdx @@ -49,15 +49,18 @@ Durations on this type are **milliseconds**. Allowlists under `network` are plai | `observability` | Verbose logging / telemetry | - Daytona treats `name` as a display name. Omit it to keep the provider default. An explicit empty string asks for unnamed. + Daytona treats `name` as a display name. Omit it to keep the provider default. + An explicit empty string asks for unnamed. - Modal treats `name` as a display name. Omit it to keep the provider default. An explicit empty string asks for unnamed. + Modal treats `name` as a display name. Omit it to keep the provider default. + An explicit empty string asks for unnamed. - On Blaxel, `name` is required and **is** the sandbox id. Use lowercase letters, digits, and single hyphens (`[a-z0-9]+(?:-[a-z0-9]+)*`), at most 49 characters. Look the sandbox up with `get(name)`. + `name` is required and is the sandbox id. Use lowercase letters, digits, and + single hyphens, at most 49 characters. Look it up with `get(name)`. **Example** @@ -139,15 +142,18 @@ Durations use `sandbox.Duration`. Optional numbers use `sandbox.Value`. | `Observability` | Verbose logging / telemetry | - Daytona treats `Name` as a display name. Omit it to keep the provider default. An explicit empty string asks for unnamed. + Daytona treats `Name` as a display name. Omit it to keep the provider default. + An explicit empty string asks for unnamed. - Modal treats `Name` as a display name. Omit it to keep the provider default. An explicit empty string asks for unnamed. + Modal treats `Name` as a display name. Omit it to keep the provider default. + An explicit empty string asks for unnamed. - On Blaxel, `Name` is required and **is** the sandbox id. Use lowercase letters, digits, and single hyphens (`[a-z0-9]+(?:-[a-z0-9]+)*`), at most 49 characters. Look the sandbox up with `Get(name)`. + `Name` is required and is the sandbox id. Use lowercase letters, digits, and + single hyphens, at most 49 characters. Look it up with `Get(name)`. **Example** @@ -244,11 +250,10 @@ Set exactly one variant. Unsupported variants are rejected. - Blaxel requires an `image` source and a `name` (lowercase alphanumeric with - single hyphens, at most 49 characters). Client `region` is required. Default - ports are HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is not supported. - Ports 80, 443, and 8080 are reserved. Create `timeout` is how long create may - take. + Requires `name`, an `image` source, and client `region`. Ports 80, 443, and + 8080 are reserved. Default port transport is HTTP; plain is TCP; TLS is TLS. + HTTP/2 over TLS is not supported. Set create `timeout` for how long create may + wait (1–50s frees the name on timeout). ## RuntimeConfig @@ -266,7 +271,7 @@ Set exactly one variant. Unsupported variants are rejected. | `pty` | Request a PTY when supported | - Blaxel does not support runtime fields on create. + Runtime fields are not supported on create. @@ -282,7 +287,7 @@ Set exactly one variant. Unsupported variants are rejected. | `Pty` | Request a PTY when supported | - Blaxel does not support runtime fields on create. + Runtime fields are not supported on create. @@ -302,7 +307,7 @@ Set exactly one variant. Unsupported variants are rejected. | `diskMib` | Requested disk in MiB | - Blaxel accepts `memoryMib` only. CPU and disk overrides are rejected; Blaxel derives CPU from memory. + Pass `memoryMib` only. CPU and disk overrides are not supported. @@ -317,7 +322,7 @@ Set exactly one variant. Unsupported variants are rejected. | `DiskMib` | Requested disk in MiB | - Blaxel accepts `MemoryMib` only. CPU and disk overrides are rejected; Blaxel derives CPU from memory. + Pass `MemoryMib` only. CPU and disk overrides are not supported. @@ -373,9 +378,8 @@ Set exactly one variant. Unsupported variants are rejected. - Blaxel supports `maximumLifetime` (≥5m when set) and `idleTerminate` (≥1m when - `PolicyMode.After`). `idleTerminate` with `PolicyMode.Disabled` is rejected. - Ephemeral, idle stop/pause, and stopped archive/delete are rejected. + Use `maximumLifetime` (≥5m) and `idleTerminate` (≥1m with `PolicyMode.After`). + Disabling idle terminate is not supported. @@ -403,9 +407,8 @@ Set exactly one variant. Unsupported variants are rejected. - Blaxel supports `MaximumLifetime` (≥5m when set) and `IdleTerminate` (≥1m when - `PolicyModeAfter`). `IdleTerminate` with `PolicyModeDisabled` is rejected. - Ephemeral, idle stop/pause, and stopped archive/delete are rejected. + Use `MaximumLifetime` (≥5m) and `IdleTerminate` (≥1m with `PolicyModeAfter`). + Disabling idle terminate is not supported. @@ -455,8 +458,8 @@ Set exactly one variant. Unsupported variants are rejected. - Client `timeout` is how long create may take. `queueTimeout` is unsupported. - `waitFor` accepts only `Default` and `Scheduled`; Ready wait is not supported. + Set `timeout` for how long create may wait (1–50s frees the name on timeout). + `queueTimeout` is not supported. `waitFor` only `Default` or `Scheduled`. @@ -480,8 +483,8 @@ Set exactly one variant. Unsupported variants are rejected. - Client `Timeout` is how long create may take. `QueueTimeout` is unsupported. - `WaitFor` accepts only `Default` and `Scheduled`; Ready wait is not supported. + Set `Timeout` for how long create may wait (1–50s frees the name on timeout). + `QueueTimeout` is not supported. `WaitFor` only `Default` or `Scheduled`. @@ -499,9 +502,7 @@ Set either a TCP port or a command. | `tcpPort` | Port `1`–`65535` | | `command.argv` | Command argv that must succeed | - - Blaxel does not support readiness probes. - +Readiness probes are not supported. @@ -511,9 +512,7 @@ Set either a TCP port or a command. | `TcpPort` | Port `1`–`65535` | | `Command` with `Argv` | Command argv that must succeed | - - Blaxel does not support readiness probes. - +Readiness probes are not supported. @@ -537,7 +536,8 @@ Set either a TCP port or a command. | `ports` | Published ports (`port`, optional `transport`) | - Blaxel accepts ports (not 80, 443, or 8080) and outbound domains. Default transport is HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is rejected. + Use ports (not 80, 443, or 8080) and outbound domains. Default transport is + HTTP; plain is TCP; TLS is TLS. HTTP/2 over TLS is not supported. @@ -557,7 +557,8 @@ Set either a TCP port or a command. | `Ports` | Published ports (`Port`, optional `Transport`) | - Blaxel accepts ports (not 80, 443, or 8080) and outbound domains. Default transport is HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is rejected. + Use ports (not 80, 443, or 8080) and outbound domains. Default transport is + HTTP; plain is TCP; TLS is TLS. HTTP/2 over TLS is not supported. @@ -575,7 +576,7 @@ Set either a TCP port or a command. | `secrets` | Provider-managed secret references (`reference`, injection, env var name) | - Blaxel does not support security fields on create. + Security fields are not supported on create. @@ -588,7 +589,7 @@ Set either a TCP port or a command. | `Secrets` | Provider-managed secret references (`Reference`, injection, env var name) | - Blaxel does not support security fields on create. + Security fields are not supported on create. diff --git a/website/content/docs/sdk/enums.mdx b/website/content/docs/sdk/enums.mdx index d73b63ac..79af3a26 100644 --- a/website/content/docs/sdk/enums.mdx +++ b/website/content/docs/sdk/enums.mdx @@ -63,8 +63,7 @@ How far provisioning wait-for waits before create returns. | `Ready` | Return once readiness succeeds (probe + positive timeout when required) | - Blaxel accepts only `Default` and `Scheduled`. `Ready`, `Started`, and - `Submitted` are rejected. Readiness probes are unsupported. + Use only `Default` or `Scheduled`. Ready wait is not supported. ## EgressMode diff --git a/website/content/docs/sdk/filesystem.mdx b/website/content/docs/sdk/filesystem.mdx index 1ab6c97e..a85150c2 100644 --- a/website/content/docs/sdk/filesystem.mdx +++ b/website/content/docs/sdk/filesystem.mdx @@ -261,8 +261,8 @@ Creates the directory at `path`. An omitted `parents` flag is `true` (create mis - Blaxel creates missing parents when `parents` is omitted or `true`. It does - not support `parents: false` (field `parents`). + Missing parents are created when `parents` is omitted or `true`. `parents: + false` is not supported. @@ -295,8 +295,8 @@ Creates the directory at `path`. An omitted `Parents` flag is true. - Blaxel creates missing parents when `Parents` is omitted or true. It does - not support `Parents: false` (field `parents`). + Missing parents are created when `Parents` is omitted or true. `Parents: + false` is not supported. diff --git a/website/content/docs/sdk/process.mdx b/website/content/docs/sdk/process.mdx index 6573b48f..7b5dd05f 100644 --- a/website/content/docs/sdk/process.mdx +++ b/website/content/docs/sdk/process.mdx @@ -124,11 +124,11 @@ Outcome of a finished command. - You get both `stdout` and `stderr`. `argv` is one quoted shell command string. - Omit or zero timeout waits until the command ends (or you abort), with no - Blaxel time limit. A positive timeout must be whole seconds and caps how long - the command may run. Aborting stops waiting only; the remote command may keep - running. If the wait times out, you get a timeout error. +You get `stdout` and `stderr`. `argv` becomes one shell command string. + +Omit or zero `timeout` to wait until the command ends. A positive timeout must +be whole seconds and caps how long the command may run. Abort stops the wait +only — the remote command may keep running. @@ -153,11 +153,11 @@ Outcome of a finished command. - You get both Stdout and Stderr. Argv is one quoted shell command string. Omit or - zero Timeout waits until the command ends (or you cancel), with no Blaxel time - limit. A positive Timeout must be whole seconds and caps how long the command - may run. Canceling stops waiting only; the remote command may keep running. If - the wait times out, you get a timeout error. +You get Stdout and Stderr. Argv becomes one shell command string. + +Omit or zero Timeout to wait until the command ends. A positive Timeout must be +whole seconds and caps how long the command may run. Cancel stops the wait +only — the remote command may keep running. diff --git a/website/content/docs/sdk/sandbox.mdx b/website/content/docs/sdk/sandbox.mdx index 8dab97fe..2ebb7aae 100644 --- a/website/content/docs/sdk/sandbox.mdx +++ b/website/content/docs/sdk/sandbox.mdx @@ -665,9 +665,8 @@ type DeleteCallOptions = OperationOptions & { wait?: boolean }; - On Blaxel, omit `wait` to return when delete is accepted. Pass `wait: true` to - wait until removal finishes. On success, `state` is `deleted` and `terminal` - is true. + Omit `wait` to return when delete is accepted. Pass `wait: true` to wait until + removal finishes. On success, `state` is `deleted` and `terminal` is true. @@ -690,9 +689,9 @@ Use `sandbox.Duration` for timeouts. `StopOptions` adds `Force`. `TerminateOptio - On Blaxel, omit `Wait` to return when delete is accepted. Pass `Wait: true` to - wait until removal finishes. On success, `State` is `Deleted` and `Terminal` - is true. Read it with `Info()`. + Omit `Wait` to return when delete is accepted. Pass `Wait: true` to wait until + removal finishes. On success, `State` is `Deleted` and `Terminal` is true — + read it with `Info()`. From c81e7a0dc76d7c5b86729cc1d244ec2611ac53a8 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 20:21:03 +0530 Subject: [PATCH 40/60] Align Blaxel Go/TS error kinds, duration text, and create info. Stop inventing labels on TypeScript create, map remaining Blaxel control-plane codes, prefer provider codes over bare gateway status, and keep the YAML inventory and caller docs in sync. Co-authored-by: Cursor Entire-Checkpoint: 01M75HEJSYDFTDW4X6CH4Q3WAW --- docs/responses-and-errors.md | 5 +- docs/sandbox-creation.md | 6 +- .../create-blaxel-with-labels/README.md | 4 +- .../create-blaxel-with-labels/README.md | 4 +- sdks/go/providers/blaxel/README.md | 19 +-- sdks/go/providers/blaxel/errors.go | 32 +++++- sdks/go/providers/blaxel/errors_test.go | 48 ++++++++ sdks/go/providers/blaxel/provider_mappings.go | 15 ++- sdks/go/providers/blaxel/sandbox_test.go | 24 ++-- sdks/typescript/providers/blaxel/README.md | 21 ++-- .../providers/blaxel/src/blaxel.test.ts | 108 ++++++++++++++++++ .../typescript/providers/blaxel/src/blaxel.ts | 2 +- .../typescript/providers/blaxel/src/errors.ts | 39 +++++-- .../providers/blaxel/src/mappings/mappings.ts | 24 ++-- specs/providers/blaxel.yaml | 21 +++- website/content/docs/blaxel.mdx | 6 +- website/content/docs/create.mdx | 3 +- website/content/docs/errors.mdx | 41 ++++--- website/content/docs/sdk/create-options.mdx | 11 +- website/content/docs/sdk/sandbox-info.mdx | 62 +++++----- 20 files changed, 378 insertions(+), 117 deletions(-) diff --git a/docs/responses-and-errors.md b/docs/responses-and-errors.md index 8c30d7b7..f388f7c6 100644 --- a/docs/responses-and-errors.md +++ b/docs/responses-and-errors.md @@ -113,7 +113,10 @@ for you. On Blaxel, creating a sandbox whose name already exists returns `AlreadyExists`. If a delete is still finishing or another conflict occurs, -you get `Conflict`. +you get `Conflict`. Lifetime below Blaxel’s floors (≥5m max lifetime, ≥1m idle) +or a non-whole-second duration is `InvalidArgument`. Unsupported create fields +are `Unsupported`. When Blaxel returns a control-plane code, it is on +`ProviderCode` / `providerCode`. ## After a failure diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index c1ee8ce4..e826393c 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -65,7 +65,8 @@ convert units. Local checks cover syntax, known enums, documented bounds, precision, and supported combinations. Account quotas, class-specific permissions, image existence, regions, and capacity remain provider-enforced. Metadata availability -varies; resource metadata represents SDK-reported allocation, not echoed requests. +varies; resource and label fields on the handle are what the provider reported, +not a copy of the create request. ## Policy semantics @@ -100,7 +101,8 @@ Its maximum lifetime and idle delay remain separate settings. Blaxel idle terminate supports only `PolicyModeAfter` with a delay of at least 1m, or provider default. `PolicyModeDisabled` on idle terminate is rejected. -Maximum lifetime, when set, must be at least 5m. +Maximum lifetime, when set, must be at least 5m. Ephemeral, idle stop/pause, +stopped archive/delete, runtime, and isolation are rejected. Image references support lowercase repository paths, optional registry hostnames and ports, tags up to 128 characters, and SHA-256 digests, within 512 characters. diff --git a/examples/go/blaxel/configuration/create-blaxel-with-labels/README.md b/examples/go/blaxel/configuration/create-blaxel-with-labels/README.md index 60bc3cca..ef147bab 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-labels/README.md +++ b/examples/go/blaxel/configuration/create-blaxel-with-labels/README.md @@ -4,7 +4,9 @@ Create one Blaxel sandbox with labels and environment variables. ## What this means -Set `labels` and create-time environment variables. They appear as sandbox metadata and runtime env. +Set `labels` and create-time environment variables. Environment variables are +available inside the sandbox. `Info().Labels` is set only when Blaxel reports +labels back — Kit does not copy them from the create request. ## Edge cases diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/README.md b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/README.md index 4ca86597..f52e161b 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/README.md +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/README.md @@ -4,7 +4,9 @@ Create one Blaxel sandbox with labels and environment variables. ## What this means -Set `labels` and create-time environment variables. They appear as sandbox metadata and runtime env. +Set `labels` and create-time environment variables. Environment variables are +available inside the sandbox. `info().labels` is set only when Blaxel reports +labels back — Kit does not copy them from the create request. ## Edge cases diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 93bd1e58..05b6d4ec 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -67,9 +67,10 @@ rejected. `Scope` fields other than `Workspace` are rejected. You need a name and an image. Names are lowercase alphanumeric with single hyphens (max 49 characters). You can also set labels, environment variables, memory, ports, outbound domains, maximum lifetime (≥5 minutes), and idle -terminate (≥1 minute). You cannot disable idle terminate. Ports 80, 443, and -8080 are reserved. Default port transport is HTTP; plain is TCP; TLS is TLS; -HTTP/2 over TLS is not supported. +terminate (≥1 minute). You cannot disable idle terminate. Runtime and +isolation are not supported. `Info().Labels` is set only when Blaxel reports +labels. Ports 80, 443, and 8080 are reserved. Default port transport is HTTP; +plain is TCP; TLS is TLS; HTTP/2 over TLS is not supported. Set client or create timeout for how long create may wait. With a 1–50s timeout, a timed-out create frees the name. Ready wait and readiness probes @@ -120,10 +121,14 @@ command may keep running. If the wait times out, you get a timeout error. ### Errors you will see -| Situation | Kind | -| ------------------------------------------ | --------------- | -| Create when the name already exists | `AlreadyExists` | -| Conflict while a delete is still finishing | `Conflict` | +| Situation | Kind | +| ------------------------------------------ | ----------------- | +| Create when the name already exists | `AlreadyExists` | +| Conflict while a delete is still finishing | `Conflict` | +| Lifetime below floor or not whole seconds | `InvalidArgument` | +| Unsupported create field (runtime, …) | `Unsupported` | + +Blaxel control-plane codes appear on `ProviderCode` when present. ## Examples diff --git a/sdks/go/providers/blaxel/errors.go b/sdks/go/providers/blaxel/errors.go index 15e43778..20b48110 100644 --- a/sdks/go/providers/blaxel/errors.go +++ b/sdks/go/providers/blaxel/errors.go @@ -43,6 +43,15 @@ var providerCodeKinds = map[string]sandbox.ErrorKind{ "VALIDATION_FAILED": sandbox.ErrorKindInvalidArgument, "VALIDATION_ERROR": sandbox.ErrorKindInvalidArgument, "WORKSPACE_REQUIRED": sandbox.ErrorKindInvalidArgument, + "PARSE_FAILED": sandbox.ErrorKindInvalidArgument, + "REGION_MISMATCH": sandbox.ErrorKindInvalidArgument, + "VOLUME_PROVIDER_MISMATCH": sandbox.ErrorKindInvalidArgument, + "KERNEL_GENERATION_MISMATCH": sandbox.ErrorKindInvalidArgument, + "VALIDATED_DATA_ERROR": sandbox.ErrorKindInvalidArgument, + "WORKSPACE_DATA_MISSING": sandbox.ErrorKindInvalidArgument, + + "UPLOAD_NOT_SUPPORTED": sandbox.ErrorKindUnsupported, + "UNSUPPORTED_GENERATION": sandbox.ErrorKindUnsupported, "CREATION_TIMEOUT": sandbox.ErrorKindTimeout, "UPSTREAM_CONNECT_TIMEOUT": sandbox.ErrorKindTimeout, @@ -55,6 +64,7 @@ var providerCodeKinds = map[string]sandbox.ErrorKind{ "WORKSPACE_RATE_LIMIT_EXCEEDED": sandbox.ErrorKindRateLimited, "SANDBOX_DELETION_IN_PROGRESS": sandbox.ErrorKindConflict, + "LOCKDOWN_ERROR": sandbox.ErrorKindFailedPrecondition, sdkblaxel.ErrWorkloadUnavailable: sandbox.ErrorKindUnavailable, "ROUTING_UNAVAILABLE": sandbox.ErrorKindUnavailable, @@ -63,11 +73,23 @@ var providerCodeKinds = map[string]sandbox.ErrorKind{ "UPSTREAM_CONNECT_FAILED": sandbox.ErrorKindUnavailable, "UPSTREAM_ERROR": sandbox.ErrorKindUnavailable, "GATEWAY_INTERNAL_ERROR": sandbox.ErrorKindUnavailable, - - "WORKLOAD_FAILED": sandbox.ErrorKindInternal, - "INTERNAL_ERROR": sandbox.ErrorKindInternal, - "HANDLER_ERROR": sandbox.ErrorKindInternal, - "DATABASE_ERROR": sandbox.ErrorKindInternal, + "UPLOAD_UNAVAILABLE": sandbox.ErrorKindUnavailable, + "LOCK_ACQUISITION_FAILED": sandbox.ErrorKindUnavailable, + "VOLUME_LOOKUP_FAILED": sandbox.ErrorKindUnavailable, + "CLUSTER_GATEWAY_ERROR": sandbox.ErrorKindUnavailable, + + "WORKLOAD_FAILED": sandbox.ErrorKindInternal, + "INTERNAL_ERROR": sandbox.ErrorKindInternal, + "HANDLER_ERROR": sandbox.ErrorKindInternal, + "DATABASE_ERROR": sandbox.ErrorKindInternal, + "READ_FAILED": sandbox.ErrorKindInternal, + "BUILD_TRIGGER_ERROR": sandbox.ErrorKindInternal, + "PRESIGNED_URL_ERROR": sandbox.ErrorKindInternal, + "DEPLOYMENT_FAILED": sandbox.ErrorKindInternal, + "PROXY_CONFIG_ERROR": sandbox.ErrorKindInternal, + "CALLBACK_SEND_FAILED": sandbox.ErrorKindInternal, + "FORK_FAILED": sandbox.ErrorKindInternal, + "UNKNOWN_ERROR": sandbox.ErrorKindInternal, } var providerStatusKinds = map[int]sandbox.ErrorKind{ diff --git a/sdks/go/providers/blaxel/errors_test.go b/sdks/go/providers/blaxel/errors_test.go index f06cbc90..9fbb8e4b 100644 --- a/sdks/go/providers/blaxel/errors_test.go +++ b/sdks/go/providers/blaxel/errors_test.go @@ -117,3 +117,51 @@ func TestMapProviderErrorGatewayTimeout(t *testing.T) { t.Fatalf("error = %v detail = %#v", mapped, detail) } } + +func TestMapProviderErrorWorkloadFailedOverGatewayStatus(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes/demo") + if err != nil { + t.Fatal(err) + } + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusServiceUnavailable, + ErrorCode: "WORKLOAD_FAILED", + Request: &http.Request{Method: http.MethodGet, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusServiceUnavailable}, + }, "refresh") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != sandbox.ErrorKindInternal { + t.Fatalf("error = %v detail = %#v", mapped, detail) + } + if detail.GetProviderCode() != "WORKLOAD_FAILED" { + t.Fatalf("provider code = %q", detail.GetProviderCode()) + } +} + +func TestMapProviderErrorCreatePlaneCodes(t *testing.T) { + parsed, err := url.Parse("https://example.invalid/sandboxes") + if err != nil { + t.Fatal(err) + } + for _, tc := range []struct { + code string + kind sandbox.ErrorKind + }{ + {"DEPLOYMENT_FAILED", sandbox.ErrorKindInternal}, + {"REGION_MISMATCH", sandbox.ErrorKindInvalidArgument}, + {"UPLOAD_NOT_SUPPORTED", sandbox.ErrorKindUnsupported}, + {"UPLOAD_UNAVAILABLE", sandbox.ErrorKindUnavailable}, + {"LOCKDOWN_ERROR", sandbox.ErrorKindFailedPrecondition}, + } { + mapped := mapProviderError(&sdkblaxel.Error{ + StatusCode: http.StatusBadRequest, + ErrorCode: tc.code, + Request: &http.Request{Method: http.MethodPost, URL: parsed}, + Response: &http.Response{StatusCode: http.StatusBadRequest}, + }, "create") + var detail *sandbox.Error + if !errors.As(mapped, &detail) || detail.Kind != tc.kind { + t.Fatalf("%s: error = %v detail = %#v", tc.code, mapped, detail) + } + } +} diff --git a/sdks/go/providers/blaxel/provider_mappings.go b/sdks/go/providers/blaxel/provider_mappings.go index ac37d173..2bab58a6 100644 --- a/sdks/go/providers/blaxel/provider_mappings.go +++ b/sdks/go/providers/blaxel/provider_mappings.go @@ -101,22 +101,27 @@ func mapAllocatedResources(source *blaxel.Sandbox) (*sandbox.Resources, error) { // formatBlaxelDuration formats a whole-second duration as a Blaxel TTL string. // minimum is the Blaxel floor for that policy (max-age TTL: 5m, idle TTL: 1m). +// Rejection text matches TypeScript durationString. func formatBlaxelDuration(value, minimum time.Duration) (string, error) { if value <= 0 || value%time.Second != 0 { return "", fmt.Errorf("duration must be a positive whole number of seconds") } if value < minimum { - return "", fmt.Errorf("duration must be at least %s", minimum) + return "", fmt.Errorf("duration must be at least %s", blaxelDurationUnits(minimum)) } + return blaxelDurationUnits(value), nil +} + +func blaxelDurationUnits(value time.Duration) string { seconds := int64(value / time.Second) switch { case seconds%86400 == 0: - return fmt.Sprintf("%dd", seconds/86400), nil + return fmt.Sprintf("%dd", seconds/86400) case seconds%3600 == 0: - return fmt.Sprintf("%dh", seconds/3600), nil + return fmt.Sprintf("%dh", seconds/3600) case seconds%60 == 0: - return fmt.Sprintf("%dm", seconds/60), nil + return fmt.Sprintf("%dm", seconds/60) default: - return fmt.Sprintf("%ds", seconds), nil + return fmt.Sprintf("%ds", seconds) } } diff --git a/sdks/go/providers/blaxel/sandbox_test.go b/sdks/go/providers/blaxel/sandbox_test.go index f1a7cb9f..f242de90 100644 --- a/sdks/go/providers/blaxel/sandbox_test.go +++ b/sdks/go/providers/blaxel/sandbox_test.go @@ -142,26 +142,26 @@ func TestFormatBlaxelDuration(t *testing.T) { in time.Duration minimum time.Duration want string - ok bool + err string }{ - {30 * time.Second, time.Second, "30s", true}, - {5 * time.Minute, 5 * time.Minute, "5m", true}, - {2 * time.Hour, 5 * time.Minute, "2h", true}, - {48 * time.Hour, 5 * time.Minute, "2d", true}, - {30 * time.Second, time.Minute, "", false}, - {4 * time.Minute, 5 * time.Minute, "", false}, - {1500 * time.Millisecond, time.Second, "", false}, - {0, time.Second, "", false}, + {30 * time.Second, time.Second, "30s", ""}, + {5 * time.Minute, 5 * time.Minute, "5m", ""}, + {2 * time.Hour, 5 * time.Minute, "2h", ""}, + {48 * time.Hour, 5 * time.Minute, "2d", ""}, + {30 * time.Second, time.Minute, "", "duration must be at least 1m"}, + {4 * time.Minute, 5 * time.Minute, "", "duration must be at least 5m"}, + {1500 * time.Millisecond, time.Second, "", "duration must be a positive whole number of seconds"}, + {0, time.Second, "", "duration must be a positive whole number of seconds"}, } { got, err := formatBlaxelDuration(tc.in, tc.minimum) - if tc.ok { + if tc.err == "" { if err != nil || got != tc.want { t.Fatalf("%v (min %v): got %q err %v", tc.in, tc.minimum, got, err) } continue } - if err == nil { - t.Fatalf("%v (min %v): expected error", tc.in, tc.minimum) + if err == nil || err.Error() != tc.err { + t.Fatalf("%v (min %v): got err %v want %q", tc.in, tc.minimum, err, tc.err) } } } diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index 3136030a..9599ee97 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -60,10 +60,11 @@ client with different credentials replaces the first. You need a valid `name` (lowercase alphanumeric with single hyphens, max 49 characters), an image, and `region`. You can also set `memoryMib`, labels, environment variables, ports, outbound domains, max-age TTL (≥5 minutes), and -idle-delete (≥1 minute). You cannot disable idle terminate. If you omit -memory, Blaxel applies its default. CPU and disk overrides are rejected — -Blaxel derives CPU from memory. Set `memoryMib` for memory size; Blaxel -documents that value as MB and Kit does not convert units. +idle-delete (≥1 minute). You cannot disable idle terminate. Runtime and +isolation are not supported. `info().labels` is set only when Blaxel reports +labels. If you omit memory, Blaxel applies its default. CPU and disk overrides +are rejected — Blaxel derives CPU from memory. Set `memoryMib` for memory size; +Blaxel documents that value as MB and Kit does not convert units. Set client or create `timeout` for how long create may wait. With a 1–50s timeout, a timed-out create frees the name. Ready wait is not supported @@ -112,10 +113,14 @@ wait times out, you get a timeout error. ### Errors you will see -| Situation | Kind | -| ------------------------------------------ | --------------- | -| Create when the name already exists | `AlreadyExists` | -| Conflict while a delete is still finishing | `Conflict` | +| Situation | Kind | +| ------------------------------------------ | ----------------- | +| Create when the name already exists | `AlreadyExists` | +| Conflict while a delete is still finishing | `Conflict` | +| Lifetime below floor or not whole seconds | `InvalidArgument` | +| Unsupported create field (runtime, …) | `Unsupported` | + +Blaxel control-plane codes appear on `providerCode` when present. ## Examples diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index ff0c1a9f..b139a772 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -269,6 +269,27 @@ describe("Blaxel mappings", () => { expect.objectContaining({ kind: ErrorKind.InvalidArgument, field: CreateFieldLifetimeMaximumLifetime, + message: + "sandbox-kit blaxel: duration must be a positive whole number of seconds", + }), + ); + }); + + it("rejects lifetime durations below the Blaxel floor", () => { + expect(() => + mapCreateRequest( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + lifetime: { maximumLifetime: 4 * 60_000 }, + }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldLifetimeMaximumLifetime, + message: "sandbox-kit blaxel: duration must be at least 5m", }), ); }); @@ -408,6 +429,74 @@ describe("Blaxel mappings", () => { }); }); + it("prefers WORKLOAD_FAILED over bare gateway 503 unavailable", () => { + const native = new ResponseError( + new Response(null, { status: 503, statusText: "Service Unavailable" }), + { + error: { + code: "WORKLOAD_FAILED", + message: "failed", + status: 503, + }, + }, + new Error("failed"), + ); + expect(mapBlaxelError(native, "refresh")).toMatchObject({ + kind: ErrorKind.Internal, + providerCode: "WORKLOAD_FAILED", + statusCode: 503, + cause: native, + }); + }); + + it("classifies create-plane codes from @blaxel/core BlaxelErrorCode", () => { + const deployment = new ResponseError( + new Response(null, { status: 500, statusText: "Internal Server Error" }), + { + error: { + code: "DEPLOYMENT_FAILED", + message: "deploy", + status_code: 500, + }, + }, + new Error("deploy"), + ); + expect(mapBlaxelError(deployment, "create")).toMatchObject({ + kind: ErrorKind.Internal, + providerCode: "DEPLOYMENT_FAILED", + }); + const mismatch = new ResponseError( + new Response(null, { status: 400, statusText: "Bad Request" }), + { + error: { + code: "REGION_MISMATCH", + message: "region", + status_code: 400, + }, + }, + new Error("region"), + ); + expect(mapBlaxelError(mismatch, "create")).toMatchObject({ + kind: ErrorKind.InvalidArgument, + providerCode: "REGION_MISMATCH", + }); + const unsupported = new ResponseError( + new Response(null, { status: 400, statusText: "Bad Request" }), + { + error: { + code: "UPLOAD_NOT_SUPPORTED", + message: "upload", + status_code: 400, + }, + }, + new Error("upload"), + ); + expect(mapBlaxelError(unsupported, "create")).toMatchObject({ + kind: ErrorKind.Unsupported, + providerCode: "UPLOAD_NOT_SUPPORTED", + }); + }); + it("reads provider codes from error.cause", () => { const native = new ResponseError( new Response(null, { status: 403, statusText: "Forbidden" }), @@ -478,6 +567,25 @@ describe("Blaxel mappings", () => { expect(info.providerMetadata).toBeUndefined(); }); + it("keeps native labels and does not invent from create request", () => { + expect( + mapSandboxInfo({ + metadata: { name: "demo", labels: { team: "kit" } }, + status: "DEPLOYED", + state: "RUNNING", + spec: { region: "us-pdx-1" }, + }).labels, + ).toEqual({ team: "kit" }); + expect( + mapSandboxInfo({ + metadata: { name: "demo" }, + status: "DEPLOYED", + state: "RUNNING", + spec: { region: "us-pdx-1" }, + }).labels, + ).toBeUndefined(); + }); + it("marks DELETED and TERMINATED refresh as terminal", () => { for (const status of ["DELETED", "TERMINATED", "DEACTIVATED"] as const) { expect( diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index c4d7cd6d..e6c1fa8e 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -103,7 +103,7 @@ class BlaxelBackend extends BackendBase implements Backend { plan.params, timeoutSeconds === undefined ? undefined : { timeout: timeoutSeconds }, ); - return { sandbox: mapSandboxInfo(sandbox, request) }; + return { sandbox: mapSandboxInfo(sandbox) }; } catch (error) { throw mapBlaxelError(error, "create"); } diff --git a/sdks/typescript/providers/blaxel/src/errors.ts b/sdks/typescript/providers/blaxel/src/errors.ts index c8219bbc..80b9f655 100644 --- a/sdks/typescript/providers/blaxel/src/errors.ts +++ b/sdks/typescript/providers/blaxel/src/errors.ts @@ -79,6 +79,14 @@ const CODE_KINDS: Record = { VALIDATION_FAILED: ErrorKind.InvalidArgument, VALIDATION_ERROR: ErrorKind.InvalidArgument, WORKSPACE_REQUIRED: ErrorKind.InvalidArgument, + PARSE_FAILED: ErrorKind.InvalidArgument, + REGION_MISMATCH: ErrorKind.InvalidArgument, + VOLUME_PROVIDER_MISMATCH: ErrorKind.InvalidArgument, + KERNEL_GENERATION_MISMATCH: ErrorKind.InvalidArgument, + VALIDATED_DATA_ERROR: ErrorKind.InvalidArgument, + WORKSPACE_DATA_MISSING: ErrorKind.InvalidArgument, + UPLOAD_NOT_SUPPORTED: ErrorKind.Unsupported, + UNSUPPORTED_GENERATION: ErrorKind.Unsupported, CREATION_TIMEOUT: ErrorKind.Timeout, UPSTREAM_CONNECT_TIMEOUT: ErrorKind.Timeout, UPSTREAM_TIMEOUT: ErrorKind.Timeout, @@ -87,6 +95,7 @@ const CODE_KINDS: Record = { RATE_LIMIT_EXCEEDED: ErrorKind.RateLimited, WORKSPACE_RATE_LIMIT_EXCEEDED: ErrorKind.RateLimited, SANDBOX_DELETION_IN_PROGRESS: ErrorKind.Conflict, + LOCKDOWN_ERROR: ErrorKind.FailedPrecondition, WORKLOAD_UNAVAILABLE: ErrorKind.Unavailable, ROUTING_UNAVAILABLE: ErrorKind.Unavailable, SERVICE_UNAVAILABLE: ErrorKind.Unavailable, @@ -94,10 +103,22 @@ const CODE_KINDS: Record = { UPSTREAM_CONNECT_FAILED: ErrorKind.Unavailable, UPSTREAM_ERROR: ErrorKind.Unavailable, GATEWAY_INTERNAL_ERROR: ErrorKind.Unavailable, + UPLOAD_UNAVAILABLE: ErrorKind.Unavailable, + LOCK_ACQUISITION_FAILED: ErrorKind.Unavailable, + VOLUME_LOOKUP_FAILED: ErrorKind.Unavailable, + CLUSTER_GATEWAY_ERROR: ErrorKind.Unavailable, WORKLOAD_FAILED: ErrorKind.Internal, INTERNAL_ERROR: ErrorKind.Internal, HANDLER_ERROR: ErrorKind.Internal, DATABASE_ERROR: ErrorKind.Internal, + READ_FAILED: ErrorKind.Internal, + BUILD_TRIGGER_ERROR: ErrorKind.Internal, + PRESIGNED_URL_ERROR: ErrorKind.Internal, + DEPLOYMENT_FAILED: ErrorKind.Internal, + PROXY_CONFIG_ERROR: ErrorKind.Internal, + CALLBACK_SEND_FAILED: ErrorKind.Internal, + FORK_FAILED: ErrorKind.Internal, + UNKNOWN_ERROR: ErrorKind.Internal, }; const STATUS_KINDS: Record = { @@ -161,19 +182,19 @@ function classifyKind(args: ClassifyArgs): ErrorInfo["kind"] { ) return ErrorKind.Timeout; // process.wait deadline is a plain Error; a 504 on that wait may be its cause. - // A bare edge 504 is a timeout too, matching Go. - if ( - isProcessWaitTimeout(error) || - isGatewayTimeout(error) || - isGatewayTimeout(cause) - ) + if (isProcessWaitTimeout(error)) return ErrorKind.Timeout; + + // Provider codes win over bare gateway status so WORKLOAD_FAILED on 503 is + // Internal in both languages (Go maps ErrorCode before StatusCode). + if (providerCode !== undefined && CODE_KINDS[providerCode] !== undefined) + return CODE_KINDS[providerCode]!; + + // Bare edge 504 is a timeout; other gateway statuses are Unavailable. + if (isGatewayTimeout(error) || isGatewayTimeout(cause) || statusCode === 504) return ErrorKind.Timeout; if (error instanceof SandboxGatewayError || isGatewayError(error)) return ErrorKind.Unavailable; - // Unknown codes fall through so status can still classify the failure. - if (providerCode !== undefined && CODE_KINDS[providerCode] !== undefined) - return CODE_KINDS[providerCode]!; if (statusCode !== undefined) { const mapped = statusKindFor(statusCode, operation); if (mapped !== undefined) return mapped; diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index 3f0d7c87..f4ec6f15 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -290,22 +290,17 @@ export function blaxelCreateTimeoutSeconds( return seconds; } -export function mapSandboxInfo( - value: BlaxelSandboxIdentity, - request?: CreateOptions, -): SandboxInfo { +/** Maps native fields into shared metadata. Does not invent values Blaxel omitted. */ +export function mapSandboxInfo(value: BlaxelSandboxIdentity): SandboxInfo { const mapped = generatedMapSandboxIdentity(value); - const id = mapped.id ?? request?.name ?? value.metadata?.name ?? ""; + const id = mapped.id ?? value.metadata?.name ?? ""; const result: SandboxInfo = { id, provider: "blaxel", state: mapped.state ?? SandboxState.Unknown, }; if (mapped.name !== undefined) mutable(result).name = mapped.name; - else if (request?.name !== undefined) mutable(result).name = request.name; if (mapped.labels !== undefined) mutable(result).labels = mapped.labels; - else if (request?.labels !== undefined) - mutable(result).labels = { ...request.labels }; if (mapped.region !== undefined) mutable(result).region = mapped.region; if (mapped.providerState !== undefined) mutable(result).providerState = mapped.providerState; @@ -335,9 +330,18 @@ interface DurationStringArgs { /** Converts Kit millisecond durations into Blaxel duration strings (s/m/h/d). */ function durationString(args: DurationStringArgs): string { const { ms, field, minimumMs } = args; + // Rejection text matches Go formatBlaxelDuration. if (!Number.isFinite(ms) || ms <= 0 || ms % 1_000 !== 0) - invalid(field, "must be a whole number of seconds"); - if (ms < minimumMs) invalid(field, "is below the Blaxel minimum duration"); + invalid(field, "duration must be a positive whole number of seconds"); + if (ms < minimumMs) + invalid( + field, + `duration must be at least ${blaxelDurationUnits(minimumMs)}`, + ); + return blaxelDurationUnits(ms); +} + +function blaxelDurationUnits(ms: number): string { const seconds = ms / 1_000; if (seconds % 86_400 === 0) return `${seconds / 86_400}d`; if (seconds % 3_600 === 0) return `${seconds / 3_600}h`; diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml index 14fde26e..dc9f6f8e 100644 --- a/specs/providers/blaxel.yaml +++ b/specs/providers/blaxel.yaml @@ -1,9 +1,9 @@ version: 1 provider: blaxel # Create checks are handwritten. Name/image/placement/resources/env live in -# provider_checks; readiness/security/observability/provisioning/ports/network -# live in create mapping (Go creation.go, TS mappings/mappings.ts). Keep this -# list in sync; it is not emitted. +# provider_checks; readiness/security/observability/provisioning/ports/network/ +# runtime/isolation/lifetime live in create mapping (Go creation.go, TS +# mappings/mappings.ts). Keep this list in sync; it is not emitted. checks: - { path: name, @@ -20,6 +20,21 @@ checks: - { path: resources.cpu_limit_cores, unsupported: true } - { path: resources.memory_limit_mib, unsupported: true } - { path: resources.disk_mib, unsupported: true } + - { + path: runtime, + unsupported: true, + notes: "working_directory, user, language, version, entrypoint, and PTY; enforced in create mapping", + } + - { path: isolation, unsupported: true } + - { + path: lifetime.idle_terminate, + notes: "PolicyModeDisabled rejected; After maps to ttl-idle ≥60s; enforced in create mapping", + } + - { + path: lifetime.ephemeral, + unsupported: true, + notes: "with idle_stop, idle_pause, stopped_archive, and stopped_delete", + } - { path: network.ports.port, notes: "reject reserved 80, 443, and 8080; enforced in create mapping", diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index 9e2fca69..a3f6e9cd 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -175,12 +175,14 @@ Blaxel client (or one API key) per process. Full rules: | Lifecycle | `delete`, `close` — no start/stop/pause/archive/terminate/attach | | Delete | Succeeds as `deleted` / `terminal`; omit `wait` may `Conflict` on quick recreate; `wait: true` waits until gone | | State | Idle → paused; active → running | -| Errors | Name taken on create → AlreadyExists; delete still in progress → Conflict | +| Errors | Name taken on create → AlreadyExists; delete still in progress → Conflict; bad TTL/idle → InvalidArgument | | Files | Shared filesystem methods — [Filesystem](/docs/filesystem) | | Process | `run` waits for the command — [Run a command](/docs/process) | Pass `memoryMib` / `memory_mib` for memory (Blaxel treats it as MB). Omit it for -Blaxel’s default. CPU and disk overrides are not supported. +Blaxel’s default. CPU and disk overrides are not supported. Runtime and +isolation are not supported. Labels on `info()` appear only when Blaxel reports +them. Default ports are HTTP; plain is TCP; TLS is TLS. HTTP/2 over TLS is not supported. Ready wait and idle disable are not supported. diff --git a/website/content/docs/create.mdx b/website/content/docs/create.mdx index 7ceb3af5..7e696ba6 100644 --- a/website/content/docs/create.mdx +++ b/website/content/docs/create.mdx @@ -275,11 +275,12 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ | CPU | Derived from memory; CPU overrides are not supported | | Memory | `memoryMib` / `memory_mib` (Blaxel treats the value as MB) | | Disk | Cannot be set | -| Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m); idle disable not supported | +| Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m); idle disable / ephemeral siblings not supported | | Placement | Client `region` required; placement on the request is not supported | | Ports | Default → HTTP; plain → TCP; TLS → TLS; Http2Tls not supported; 80/443/8080 reserved | | Wait | `waitFor` only `Default` / `Scheduled`; create `timeout` 1–50s frees the name on timeout | | Runtime | Not supported | +| Isolation | Not supported | Volumes are not available on create. diff --git a/website/content/docs/errors.mdx b/website/content/docs/errors.mdx index 05891671..0596c170 100644 --- a/website/content/docs/errors.mdx +++ b/website/content/docs/errors.mdx @@ -7,24 +7,24 @@ Failed calls surface a typed error with `kind`, `provider`, `operation`, `messag Kinds are categories, not retry advice. Kit does not retry. -| Kind | What happened | -| -------------------- | ------------------------------------------------------ | -| `InvalidArgument` | A request field is invalid | -| `Unsupported` | This provider does not support the call or the field | -| `Authentication` | Credentials were rejected | -| `PermissionDenied` | The account cannot perform this operation | -| `NotFound` | The sandbox or file path was not found | -| `AlreadyExists` | The named resource already exists | -| `Conflict` | Current state rejects this operation | -| `RateLimited` | Provider rate limit reached | -| `ResourceExhausted` | Provider quota or capacity reached | -| `Timeout` | The deadline fired | -| `Canceled` | The caller canceled the operation | -| `Unavailable` | The provider could not be reached | -| `InvalidResponse` | The provider returned an unusable response | -| `Internal` | Unclassified failure | -| `FailedPrecondition` | Client or sandbox is not in a usable state | -| `Unknown` | Does not match a known category | +| Kind | What happened | +| -------------------- | ---------------------------------------------------- | +| `InvalidArgument` | A request field is invalid | +| `Unsupported` | This provider does not support the call or the field | +| `Authentication` | Credentials were rejected | +| `PermissionDenied` | The account cannot perform this operation | +| `NotFound` | The sandbox or file path was not found | +| `AlreadyExists` | The named resource already exists | +| `Conflict` | Current state rejects this operation | +| `RateLimited` | Provider rate limit reached | +| `ResourceExhausted` | Provider quota or capacity reached | +| `Timeout` | The deadline fired | +| `Canceled` | The caller canceled the operation | +| `Unavailable` | The provider could not be reached | +| `InvalidResponse` | The provider returned an unusable response | +| `Internal` | Unclassified failure | +| `FailedPrecondition` | Client or sandbox is not in a usable state | +| `Unknown` | Does not match a known category | ## Invalid config @@ -315,7 +315,10 @@ See [Examples](/docs/examples). If the name is already taken on create, you get `AlreadyExists`. If a delete is still finishing, you get `Conflict` — wait, then `get` before you recreate that -name. +name. Lifetime below Blaxel’s floors (≥5m max lifetime, ≥1m idle) or a +non-whole-second duration is `InvalidArgument`. Unsupported create fields +(runtime, isolation, idle disable, …) are `Unsupported`. When Blaxel returns a +control-plane code, read it on `providerCode` / `ProviderCode`. diff --git a/website/content/docs/sdk/create-options.mdx b/website/content/docs/sdk/create-options.mdx index 6bc3b814..7b812df4 100644 --- a/website/content/docs/sdk/create-options.mdx +++ b/website/content/docs/sdk/create-options.mdx @@ -252,8 +252,9 @@ Set exactly one variant. Unsupported variants are rejected. Requires `name`, an `image` source, and client `region`. Ports 80, 443, and 8080 are reserved. Default port transport is HTTP; plain is TCP; TLS is TLS. - HTTP/2 over TLS is not supported. Set create `timeout` for how long create may - wait (1–50s frees the name on timeout). + HTTP/2 over TLS is not supported. Runtime and isolation are not supported. Set + create `timeout` for how long create may wait (1–50s frees the name on + timeout). Labels on `info()` appear only when Blaxel reports them. ## RuntimeConfig @@ -379,7 +380,8 @@ Set exactly one variant. Unsupported variants are rejected. Use `maximumLifetime` (≥5m) and `idleTerminate` (≥1m with `PolicyMode.After`). - Disabling idle terminate is not supported. + Disabling idle terminate is not supported. Ephemeral, idle stop/pause, and + stopped archive/delete are rejected. @@ -408,7 +410,8 @@ Set exactly one variant. Unsupported variants are rejected. Use `MaximumLifetime` (≥5m) and `IdleTerminate` (≥1m with `PolicyModeAfter`). - Disabling idle terminate is not supported. + Disabling idle terminate is not supported. Ephemeral, idle stop/pause, and + stopped archive/delete are rejected. diff --git a/website/content/docs/sdk/sandbox-info.mdx b/website/content/docs/sdk/sandbox-info.mdx index 979fbc12..f2ad73e7 100644 --- a/website/content/docs/sdk/sandbox-info.mdx +++ b/website/content/docs/sdk/sandbox-info.mdx @@ -19,20 +19,25 @@ Returned by info and many lifecycle methods. Values are a **copy**; mutating the -| Field | Description | -| ------------------ | ------------------------------------------------------- | -| `id` | Sandbox identifier | -| `provider` | Provider identifier | -| `name` | Display name when reported | +| Field | Description | +| ------------------ | -------------------------------------------------------- | +| `id` | Sandbox identifier | +| `provider` | Provider identifier | +| `name` | Display name when reported | | `providerState` | Provider-specific status string; prefer portable `state` | -| `region` | Region when reported | -| `isolation` | Reported isolation config | -| `resources` | Reported size (`cpuCores`, `memoryMib`, …) | -| `labels` | Labels when reported | -| `createdAt` | Creation time when reported | -| `providerMetadata` | Extra provider metadata object | -| `state` | Portable [`SandboxState`](/docs/sdk/enums#sandboxstate) | -| `terminal` | Whether the lifecycle is permanently complete | +| `region` | Region when reported | +| `isolation` | Reported isolation config | +| `resources` | Reported size (`cpuCores`, `memoryMib`, …) | +| `labels` | Labels when reported | +| `createdAt` | Creation time when reported | +| `providerMetadata` | Extra provider metadata object | +| `state` | Portable [`SandboxState`](/docs/sdk/enums#sandboxstate) | +| `terminal` | Whether the lifecycle is permanently complete | + + + `labels` is set only when Blaxel returns them — not copied from the create + request. + **Example** @@ -46,20 +51,25 @@ console.log(info); -| Field | Description | -| ------------------ | ------------------------------------------------------- | -| `Id` | Sandbox identifier | -| `Provider` | Provider identifier | -| `Name` | Display name when reported | +| Field | Description | +| ------------------ | -------------------------------------------------------- | +| `Id` | Sandbox identifier | +| `Provider` | Provider identifier | +| `Name` | Display name when reported | | `ProviderState` | Provider-specific status string; prefer portable `State` | -| `Region` | Region when reported | -| `Isolation` | Reported isolation config | -| `Resources` | Reported size (`CpuCores`, `MemoryMib`, …) | -| `Labels` | Labels when reported | -| `CreatedAt` | Creation time when reported | -| `ProviderMetadata` | Extra provider metadata object | -| `State` | Portable [`SandboxState`](/docs/sdk/enums#sandboxstate) | -| `Terminal` | Whether the lifecycle is permanently complete | +| `Region` | Region when reported | +| `Isolation` | Reported isolation config | +| `Resources` | Reported size (`CpuCores`, `MemoryMib`, …) | +| `Labels` | Labels when reported | +| `CreatedAt` | Creation time when reported | +| `ProviderMetadata` | Extra provider metadata object | +| `State` | Portable [`SandboxState`](/docs/sdk/enums#sandboxstate) | +| `Terminal` | Whether the lifecycle is permanently complete | + + + `Labels` is set only when Blaxel returns them — not copied from the create + request. + **Example** From df70f75e3f1d33d452be74f1408f43a29ccb33e7 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 20:34:05 +0530 Subject: [PATCH 41/60] Map Blaxel LOCKDOWN_ERROR to Internal and sync wait/lifetime inventory. Treat lockdown as an internal control-plane failure like sibling create codes, and record the 5m TTL floor plus Ready/Started/Submitted wait rejects in YAML and caller docs. Co-authored-by: Cursor Entire-Checkpoint: 01M75J6E122PN6ST5N68VPHMXS --- docs/sandbox-creation.md | 26 +++++++++---------- sdks/go/providers/blaxel/README.md | 4 +-- sdks/go/providers/blaxel/errors.go | 2 +- sdks/go/providers/blaxel/errors_test.go | 2 +- sdks/typescript/providers/blaxel/README.md | 5 ++-- .../typescript/providers/blaxel/src/errors.ts | 2 +- specs/providers/blaxel.yaml | 16 ++++++++++-- website/content/docs/blaxel.mdx | 2 +- website/content/docs/configuration.mdx | 5 ++-- website/content/docs/sdk/enums.mdx | 3 ++- 10 files changed, 41 insertions(+), 26 deletions(-) diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index e826393c..e79a93e2 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -9,19 +9,19 @@ from lifecycle operations on an existing sandbox. Go and TypeScript integrations target Modal, Daytona, and Blaxel. Pinned dependency versions live in each provider package. -| Setting | Modal | Daytona | Blaxel | -| ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | -| Source | Explicit registry image and an existing app | Default/specified snapshot or registry image | Named create (Blaxel name rules); registry image required | -| Runtime | Entrypoint, absolute working directory, PTY | Toolbox language and user | Unsupported | -| Isolation | Default, gVisor container, Linux VM | Nested KVM flag; class inherited from source | Unsupported | -| Resources | Fractional CPU, CPU limit, memory request/limit | Whole CPU and GiB memory/disk; image source required for overrides | Memory (`memory_mib` / `memoryMib`); CPU/disk overrides rejected | -| Placement | Cloud and region preferences | Client target; request placement unsupported | Client `region` required for create; request placement unsupported | -| Environment and labels | Environment map and tags | Environment map and labels | Environment map and labels | -| Security | Named secrets and workload identity | Egress-placeholder secrets and public access | Unsupported | -| Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | Ports (not 80/443/8080; Default → Blaxel HTTP); outbound domains | -| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m); explicit idle disable rejected | -| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Create `timeout`; 1–50s frees the name on timeout; waitFor Default/Scheduled only; Ready unsupported | -| Observability | Verbose output | Telemetry endpoint | Unsupported | +| Setting | Modal | Daytona | Blaxel | +| ---------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | +| Source | Explicit registry image and an existing app | Default/specified snapshot or registry image | Named create (Blaxel name rules); registry image required | +| Runtime | Entrypoint, absolute working directory, PTY | Toolbox language and user | Unsupported | +| Isolation | Default, gVisor container, Linux VM | Nested KVM flag; class inherited from source | Unsupported | +| Resources | Fractional CPU, CPU limit, memory request/limit | Whole CPU and GiB memory/disk; image source required for overrides | Memory (`memory_mib` / `memoryMib`); CPU/disk overrides rejected | +| Placement | Cloud and region preferences | Client target; request placement unsupported | Client `region` required for create; request placement unsupported | +| Environment and labels | Environment map and tags | Environment map and labels | Environment map and labels | +| Security | Named secrets and workload identity | Egress-placeholder secrets and public access | Unsupported | +| Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | Ports (not 80/443/8080; Default → Blaxel HTTP); outbound domains | +| Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m); explicit idle disable rejected | +| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Create `timeout`; 1–50s frees the name on timeout; waitFor Default/Scheduled only; Ready/Started/Submitted unsupported | +| Observability | Verbose output | Telemetry endpoint | Unsupported | Kit has no portable volume fields; Blaxel volumes stay out of create the same way Daytona and Modal volumes do. diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 05b6d4ec..11a392c2 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -73,8 +73,8 @@ labels. Ports 80, 443, and 8080 are reserved. Default port transport is HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is not supported. Set client or create timeout for how long create may wait. With a 1–50s -timeout, a timed-out create frees the name. Ready wait and readiness probes -are not supported (`WaitFor` only Default or Scheduled). +timeout, a timed-out create frees the name. Ready/Started/Submitted wait and +readiness probes are not supported (`WaitFor` only Default or Scheduled). Set `Resources.MemoryMib` for memory size. Blaxel documents that value as MB; Kit does not convert units. CPU follows memory on Blaxel — do not set CPU or diff --git a/sdks/go/providers/blaxel/errors.go b/sdks/go/providers/blaxel/errors.go index 20b48110..fa302412 100644 --- a/sdks/go/providers/blaxel/errors.go +++ b/sdks/go/providers/blaxel/errors.go @@ -64,7 +64,6 @@ var providerCodeKinds = map[string]sandbox.ErrorKind{ "WORKSPACE_RATE_LIMIT_EXCEEDED": sandbox.ErrorKindRateLimited, "SANDBOX_DELETION_IN_PROGRESS": sandbox.ErrorKindConflict, - "LOCKDOWN_ERROR": sandbox.ErrorKindFailedPrecondition, sdkblaxel.ErrWorkloadUnavailable: sandbox.ErrorKindUnavailable, "ROUTING_UNAVAILABLE": sandbox.ErrorKindUnavailable, @@ -89,6 +88,7 @@ var providerCodeKinds = map[string]sandbox.ErrorKind{ "PROXY_CONFIG_ERROR": sandbox.ErrorKindInternal, "CALLBACK_SEND_FAILED": sandbox.ErrorKindInternal, "FORK_FAILED": sandbox.ErrorKindInternal, + "LOCKDOWN_ERROR": sandbox.ErrorKindInternal, "UNKNOWN_ERROR": sandbox.ErrorKindInternal, } diff --git a/sdks/go/providers/blaxel/errors_test.go b/sdks/go/providers/blaxel/errors_test.go index 9fbb8e4b..c68aca7a 100644 --- a/sdks/go/providers/blaxel/errors_test.go +++ b/sdks/go/providers/blaxel/errors_test.go @@ -151,7 +151,7 @@ func TestMapProviderErrorCreatePlaneCodes(t *testing.T) { {"REGION_MISMATCH", sandbox.ErrorKindInvalidArgument}, {"UPLOAD_NOT_SUPPORTED", sandbox.ErrorKindUnsupported}, {"UPLOAD_UNAVAILABLE", sandbox.ErrorKindUnavailable}, - {"LOCKDOWN_ERROR", sandbox.ErrorKindFailedPrecondition}, + {"LOCKDOWN_ERROR", sandbox.ErrorKindInternal}, } { mapped := mapProviderError(&sdkblaxel.Error{ StatusCode: http.StatusBadRequest, diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index 9599ee97..a01ed925 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -67,8 +67,9 @@ are rejected — Blaxel derives CPU from memory. Set `memoryMib` for memory size Blaxel documents that value as MB and Kit does not convert units. Set client or create `timeout` for how long create may wait. With a 1–50s -timeout, a timed-out create frees the name. Ready wait is not supported -(`waitFor` only Default or Scheduled). Ports 80, 443, and 8080 are reserved. +timeout, a timed-out create frees the name. Ready/Started/Submitted wait is not +supported (`waitFor` only Default or Scheduled). Ports 80, 443, and 8080 are +reserved. Default port transport is HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is not supported. Volumes are not part of create. diff --git a/sdks/typescript/providers/blaxel/src/errors.ts b/sdks/typescript/providers/blaxel/src/errors.ts index 80b9f655..5b86de8f 100644 --- a/sdks/typescript/providers/blaxel/src/errors.ts +++ b/sdks/typescript/providers/blaxel/src/errors.ts @@ -95,7 +95,6 @@ const CODE_KINDS: Record = { RATE_LIMIT_EXCEEDED: ErrorKind.RateLimited, WORKSPACE_RATE_LIMIT_EXCEEDED: ErrorKind.RateLimited, SANDBOX_DELETION_IN_PROGRESS: ErrorKind.Conflict, - LOCKDOWN_ERROR: ErrorKind.FailedPrecondition, WORKLOAD_UNAVAILABLE: ErrorKind.Unavailable, ROUTING_UNAVAILABLE: ErrorKind.Unavailable, SERVICE_UNAVAILABLE: ErrorKind.Unavailable, @@ -118,6 +117,7 @@ const CODE_KINDS: Record = { PROXY_CONFIG_ERROR: ErrorKind.Internal, CALLBACK_SEND_FAILED: ErrorKind.Internal, FORK_FAILED: ErrorKind.Internal, + LOCKDOWN_ERROR: ErrorKind.Internal, UNKNOWN_ERROR: ErrorKind.Internal, }; diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml index dc9f6f8e..01ff9a81 100644 --- a/specs/providers/blaxel.yaml +++ b/specs/providers/blaxel.yaml @@ -25,7 +25,14 @@ checks: unsupported: true, notes: "working_directory, user, language, version, entrypoint, and PTY; enforced in create mapping", } - - { path: isolation, unsupported: true } + - { + path: isolation, + notes: "Default-only / empty is a no-op; non-default kind, providerKind, and nestedVirtualization rejected in create mapping", + } + - { + path: lifetime.maximum_lifetime, + notes: "whole seconds ≥5m (Blaxel ttl-max-age floor); enforced in create mapping", + } - { path: lifetime.idle_terminate, notes: "PolicyModeDisabled rejected; After maps to ttl-idle ≥60s; enforced in create mapping", @@ -240,7 +247,12 @@ groups: name: SandboxCreateConfiguration fields: { ttl: ttl } fields: - - { from: maximum_lifetime, to: ttl, transform: duration_blaxel } + - { + from: maximum_lifetime, + to: ttl, + transform: duration_blaxel, + notes: "whole seconds ≥5m", + } - name: mapIdleDuration error_path: lifetime.idle_terminate direction: request diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index a3f6e9cd..e3b65644 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -185,7 +185,7 @@ isolation are not supported. Labels on `info()` appear only when Blaxel reports them. Default ports are HTTP; plain is TCP; TLS is TLS. HTTP/2 over TLS is not -supported. Ready wait and idle disable are not supported. +supported. Ready/Started/Submitted wait and idle disable are not supported. Set client or create `timeout` for how long create may wait. With a 1–50s timeout, a timed-out create frees the name. diff --git a/website/content/docs/configuration.mdx b/website/content/docs/configuration.mdx index 2ca23961..b17304e8 100644 --- a/website/content/docs/configuration.mdx +++ b/website/content/docs/configuration.mdx @@ -531,8 +531,9 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ - Set client or create `timeout` for how long create may wait. Ready wait is not - supported. In TypeScript, use one Blaxel client (or one API key) per process. + Set client or create `timeout` for how long create may wait. Ready, Started, + and Submitted wait are not supported. In TypeScript, use one Blaxel client (or + one API key) per process. ## Credentials diff --git a/website/content/docs/sdk/enums.mdx b/website/content/docs/sdk/enums.mdx index 79af3a26..cbd89ea0 100644 --- a/website/content/docs/sdk/enums.mdx +++ b/website/content/docs/sdk/enums.mdx @@ -63,7 +63,8 @@ How far provisioning wait-for waits before create returns. | `Ready` | Return once readiness succeeds (probe + positive timeout when required) | - Use only `Default` or `Scheduled`. Ready wait is not supported. + Use only `Default` or `Scheduled`. Ready, Started, and Submitted are not + supported. ## EgressMode From 3cbe6c584dace4043b54fde9b076b3bfd794b084 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 20:41:17 +0530 Subject: [PATCH 42/60] Remove dead Blaxel helpers and tighten wire-state projection. Drop the redundant resource unmapped path and unused TS exports so create validation and handle mapping stay on one clear path. Co-authored-by: Cursor Entire-Checkpoint: 01M75JKM1T3F2H1A383X6YBW7Z --- sdks/go/providers/blaxel/creation.go | 7 ----- sdks/typescript/providers/blaxel/src/local.ts | 2 +- .../typescript/providers/blaxel/src/native.ts | 29 +++++++++++-------- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/sdks/go/providers/blaxel/creation.go b/sdks/go/providers/blaxel/creation.go index 95145425..699766f2 100644 --- a/sdks/go/providers/blaxel/creation.go +++ b/sdks/go/providers/blaxel/creation.go @@ -81,9 +81,6 @@ func (c *creation) resources() error { if err != nil { return err } - if hasUnmappedResourceFields(remaining) { - return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResources, "one or more Blaxel resource fields are not mapped") - } if err := sdk.RejectUnmapped("blaxel", sandbox.CreateFieldResources, remaining); err != nil { return err } @@ -303,10 +300,6 @@ func isReservedPort(port uint32) bool { return ok } -func hasUnmappedResourceFields(r *sandbox.Resources) bool { - return r.CpuCores != nil || r.CpuLimitCores != nil || r.MemoryLimitMib != nil || r.DiskMib != nil -} - func hasUnsupportedLifetimeFields(c *sandbox.LifetimePolicy) bool { return c.Ephemeral != nil || c.IdleStop != nil || c.IdlePause != nil || c.StoppedArchive != nil || c.StoppedDelete != nil diff --git a/sdks/typescript/providers/blaxel/src/local.ts b/sdks/typescript/providers/blaxel/src/local.ts index 610df783..a9fcedfd 100644 --- a/sdks/typescript/providers/blaxel/src/local.ts +++ b/sdks/typescript/providers/blaxel/src/local.ts @@ -10,7 +10,7 @@ import { import { providerError } from "./errors.js"; /** Rejects a missing local file or a directory before Blaxel reads it. */ -export async function requireLocalFile( +async function requireLocalFile( localPath: string, operation: string, ): Promise { diff --git a/sdks/typescript/providers/blaxel/src/native.ts b/sdks/typescript/providers/blaxel/src/native.ts index 0483647f..dc1bab50 100644 --- a/sdks/typescript/providers/blaxel/src/native.ts +++ b/sdks/typescript/providers/blaxel/src/native.ts @@ -51,16 +51,12 @@ export interface NativeSandboxHandle extends BlaxelSandboxIdentity { }; } -export interface NativeCreateOptions { - /** Blaxel create timeout in whole seconds (1..50). */ - readonly timeout?: number; -} - export interface NativeBlaxel { configure(config: NativeClientConfig): void; create( params: SandboxCreateConfiguration, - options?: NativeCreateOptions, + /** Blaxel create timeout in whole seconds (1..50). */ + options?: { readonly timeout?: number }, ): Promise; get(name: string): Promise; delete(name: string): Promise; @@ -101,7 +97,7 @@ export function defaultNativeFactory(config: NativeClientConfig): NativeBlaxel { * filesystem GET as Go. */ export function projectHandle(instance: SandboxInstance): NativeSandboxHandle { - const state = wireRuntimeState(instance); + const state = readWireRuntimeState(instance); return { metadata: instance.metadata, status: instance.status, @@ -172,9 +168,18 @@ function isFileMetadata(data: unknown): data is BlaxelFile { ); } -function wireRuntimeState(instance: SandboxInstance): string | undefined { - const wire = instance as unknown as { sandbox?: { state?: unknown } }; - const value = wire.sandbox?.state; - if (value === undefined || String(value) === "") return undefined; - return String(value); +/** + * Runtime state lives on Blaxel's private wire model (`sandbox.state`). The + * published `SandboxInstance` typings expose status/spec getters but not state, + * so this is the single boundary that reads it for Kit's handle projection. + */ +type BlaxelWireRuntime = { + readonly sandbox?: { readonly state?: unknown }; +}; + +function readWireRuntimeState(instance: SandboxInstance): string | undefined { + const value = (instance as unknown as BlaxelWireRuntime).sandbox?.state; + if (value === undefined || value === null || value === "") return undefined; + const text = String(value); + return text === "" ? undefined : text; } From 4fc52f8ce8a250dce23ec3597c01d0ef4704eb47 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 20:57:36 +0530 Subject: [PATCH 43/60] Align Blaxel name and Stat parity across Go, TypeScript, and docs. Treat whitespace-only create names as required, pass Blaxel Stat name/path through without inventing, and document both for SDK callers. Co-authored-by: Cursor Entire-Checkpoint: 01M75KHG44HR3FXP1K7P3HHWMS --- docs/filesystem.md | 6 ++-- .../filesystem/stat-blaxel-file/README.md | 4 +-- .../filesystem/stat-blaxel-file/README.md | 3 +- sdks/go/providers/blaxel/README.md | 10 +++--- sdks/go/providers/blaxel/provider_checks.go | 2 +- sdks/go/providers/blaxel/rejection_test.go | 23 ++++++++++++++ sdks/typescript/providers/blaxel/README.md | 10 +++--- .../providers/blaxel/src/blaxel.test.ts | 19 ++++++++++++ .../providers/blaxel/src/filesystem.ts | 31 ++++++++++++++----- .../providers/blaxel/src/rejection.test.ts | 18 +++++++++++ website/content/docs/blaxel.mdx | 4 +-- website/content/docs/create.mdx | 28 ++++++++--------- website/content/docs/filesystem.mdx | 2 +- website/content/docs/sdk/create-options.mdx | 10 +++--- website/content/docs/sdk/filesystem.mdx | 6 ++-- 15 files changed, 131 insertions(+), 45 deletions(-) diff --git a/docs/filesystem.md b/docs/filesystem.md index 181b558c..80ae8d57 100644 --- a/docs/filesystem.md +++ b/docs/filesystem.md @@ -226,9 +226,9 @@ entries, err := instance.Filesystem().ListDirectory(ctx, "/tmp/kit-files", nil) `Stat` returns one path. A symlink result describes the symlink. On Blaxel, you get `type` (file or directory), `size`, and usually -`permissions`, `owner`, `group`, and `modifiedAt`. Modal can report a -symlink. Daytona reports a file or a directory. `mode` is an octal string -when present, such as `0644`. +`permissions`, `owner`, `group`, and `modifiedAt`. `name` and `path` are +whatever Blaxel reports. Modal can report a symlink. Daytona reports a file +or a directory. `mode` is an octal string when present, such as `0644`. ```ts const info = await sandbox.filesystem.stat("/tmp/note.txt"); diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/README.md b/examples/go/blaxel/filesystem/stat-blaxel-file/README.md index 540f5d09..2e8853d2 100644 --- a/examples/go/blaxel/filesystem/stat-blaxel-file/README.md +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/README.md @@ -5,8 +5,8 @@ Stat one absolute path in a Blaxel sandbox. ## What this means `Stat` returns `Type` (file or directory), `Size`, and usually `Permissions`, -`Owner`, `Group`, and `ModifiedAt`. Size may be zero when Blaxel does not -return it. +`Owner`, `Group`, and `ModifiedAt`. `Name` and `Path` are whatever Blaxel +reports. Size may be zero when Blaxel does not return it. ## Edge cases diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md index 3a5afe9a..e0c97de0 100644 --- a/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md @@ -5,7 +5,8 @@ Stat one absolute path in a Blaxel sandbox. ## What this means `stat` returns `type` (file or directory), `size`, and usually `permissions`, -`owner`, `group`, and `modifiedAt`. +`owner`, `group`, and `modifiedAt`. `name` and `path` are whatever Blaxel +reports. ## Edge cases diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 11a392c2..6b3c70ea 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -64,8 +64,9 @@ rejected. `Scope` fields other than `Workspace` are rejected. ### Create -You need a name and an image. Names are lowercase alphanumeric with single -hyphens (max 49 characters). You can also set labels, environment variables, +You need a name and an image. Name is required (blank or whitespace-only is +rejected). Names are lowercase alphanumeric with single hyphens (max 49 +characters). You can also set labels, environment variables, memory, ports, outbound domains, maximum lifetime (≥5 minutes), and idle terminate (≥1 minute). You cannot disable idle terminate. Runtime and isolation are not supported. `Info().Labels` is set only when Blaxel reports @@ -104,8 +105,9 @@ remove. Paths must be absolute. `Parents` defaults to true; Blaxel does not support `Parents: false`. `Stat` returns `Type` (file or directory), `Size`, and usually `Permissions`, -`Owner`, `Group`, and `ModifiedAt`. Missing local files for `CopyFromLocal` -return not found with field `local_path`. +`Owner`, `Group`, and `ModifiedAt`. `Name` and `Path` are whatever Blaxel +reports. Missing local files for `CopyFromLocal` return not found with field +`local_path`. ### Process diff --git a/sdks/go/providers/blaxel/provider_checks.go b/sdks/go/providers/blaxel/provider_checks.go index 1f5aeb1f..3b8cc596 100644 --- a/sdks/go/providers/blaxel/provider_checks.go +++ b/sdks/go/providers/blaxel/provider_checks.go @@ -24,7 +24,7 @@ func validateProviderCreate(request *sandbox.CreateOptions) error { return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldName, "name is required") } name := request.GetName() - if name == "" { + if strings.TrimSpace(name) == "" { return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldName, "name is required") } if len(name) > providerSandboxNameMaxLen || !providerSandboxNamePattern.MatchString(name) { diff --git a/sdks/go/providers/blaxel/rejection_test.go b/sdks/go/providers/blaxel/rejection_test.go index 89e69a64..3beff94b 100644 --- a/sdks/go/providers/blaxel/rejection_test.go +++ b/sdks/go/providers/blaxel/rejection_test.go @@ -2,6 +2,7 @@ package blaxel import ( "errors" + "strings" "testing" "time" @@ -9,6 +10,20 @@ import ( "github.com/sandbox-kit/kit/sdks/go/sandbox" ) +func TestWhitespaceNameIsRequired(t *testing.T) { + _, err := planCreate(imaged(&sandbox.CreateOptions{Name: sandbox.Value(" ")}), "us-pdx-1") + var detail *sandbox.Error + if !errors.As(err, &detail) { + t.Fatalf("error = %v", err) + } + if detail.Kind != sandbox.ErrorKindInvalidArgument || detail.Field != sandbox.CreateFieldName { + t.Fatalf("detail = %#v", detail) + } + if !strings.Contains(detail.Message, "name is required") { + t.Fatalf("message = %q", detail.Message) + } +} + func TestRejectedCreateSettings(t *testing.T) { for _, tc := range []struct { name string @@ -22,6 +37,14 @@ func TestRejectedCreateSettings(t *testing.T) { kind: sandbox.ErrorKindInvalidArgument, field: sandbox.CreateFieldName, }, + { + name: "whitespace name", + request: imaged(&sandbox.CreateOptions{ + Name: sandbox.Value(" "), + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldName, + }, { name: "invalid name", request: imaged(&sandbox.CreateOptions{ diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index a01ed925..629129cb 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -57,8 +57,9 @@ client with different credentials replaces the first. ### Create -You need a valid `name` (lowercase alphanumeric with single hyphens, max 49 -characters), an image, and `region`. You can also set `memoryMib`, labels, +You need a valid `name` (required; blank or whitespace-only is rejected; +lowercase alphanumeric with single hyphens, max 49 characters), an image, and +`region`. You can also set `memoryMib`, labels, environment variables, ports, outbound domains, max-age TTL (≥5 minutes), and idle-delete (≥1 minute). You cannot disable idle terminate. Runtime and isolation are not supported. `info().labels` is set only when Blaxel reports @@ -97,8 +98,9 @@ must be absolute. `parents` defaults to true; Blaxel does not support `parents: false`. `stat` returns `type` (file or directory), `size`, and usually `permissions`, -`owner`, `group`, and `modifiedAt`. Missing local files for `copyFromLocal` -return not found with field `local_path`. +`owner`, `group`, and `modifiedAt`. `name` and `path` are whatever Blaxel +reports. Missing local files for `copyFromLocal` return not found with field +`local_path`. ### Process diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index b139a772..485fc0a0 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -1310,6 +1310,25 @@ describe("Blaxel filesystem.stat", () => { }); }); + it("does not invent directory name or path when Blaxel omits them", async () => { + const backend = fsBackend({ + async get() { + return { + name: "", + path: "", + files: [], + subdirectories: [], + }; + }, + }); + await expect(backend.stat!("sandbox-1", "/app", {}, {})).resolves.toEqual({ + name: "", + path: "", + type: FileType.Directory, + size: 0n, + }); + }); + it("returns file metadata including size from get", async () => { const backend = fsBackend({ async get(path) { diff --git a/sdks/typescript/providers/blaxel/src/filesystem.ts b/sdks/typescript/providers/blaxel/src/filesystem.ts index 53a46728..2179868a 100644 --- a/sdks/typescript/providers/blaxel/src/filesystem.ts +++ b/sdks/typescript/providers/blaxel/src/filesystem.ts @@ -1,6 +1,11 @@ -import { basename } from "node:path"; import type { Directory, File as BlaxelFile } from "@blaxel/core"; -import { FileType, type FileInfo } from "@sandbox-kit/sandbox"; +import { + ErrorKind, + FileFieldPath, + FileType, + type FileInfo, +} from "@sandbox-kit/sandbox"; +import { providerError } from "./errors.js"; import type { NativeSandboxHandle } from "./native.js"; type BlaxelSubdirectory = Directory["subdirectories"][number]; @@ -30,7 +35,8 @@ export function directoryInfo(info: BlaxelSubdirectory): FileInfo { /** * Stat one path. Uses the same filesystem GET as Go: a directory listing or - * file metadata (size, permissions when Blaxel supplies them). + * file metadata (size, permissions when Blaxel supplies them). Does not invent + * name or path when Blaxel omits them. */ export async function blaxelStat( fs: NativeSandboxHandle["fs"], @@ -39,15 +45,26 @@ export async function blaxelStat( const data = await fs.get(path); if (isDirectoryListing(data)) { return { - name: data.name || basename(path) || "/", - path: data.path || path, + name: data.name ?? "", + path: data.path ?? "", type: FileType.Directory, size: 0n, }; } + const name = data.name ?? ""; + const filePath = data.path ?? ""; + // Match Go blaxelFileInfo: empty name and path is not usable file metadata. + if (!name && !filePath) { + providerError({ + kind: ErrorKind.InvalidResponse, + operation: "stat", + field: FileFieldPath, + message: "SDK returned no file metadata", + }); + } return fileInfo({ - name: data.name || basename(path) || path, - path: data.path || path, + name, + path: filePath, size: data.size, permissions: data.permissions ?? "", owner: data.owner ?? "", diff --git a/sdks/typescript/providers/blaxel/src/rejection.test.ts b/sdks/typescript/providers/blaxel/src/rejection.test.ts index ff488e0d..a7325df4 100644 --- a/sdks/typescript/providers/blaxel/src/rejection.test.ts +++ b/sdks/typescript/providers/blaxel/src/rejection.test.ts @@ -128,6 +128,24 @@ describe("rejected Blaxel create settings", () => { ); }); + it("whitespace name", () => { + expect(() => + mapCreateRequest( + { + name: " ", + source: { image: { reference: "blaxel/base-image:latest" } }, + }, + provider, + ), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldName, + message: expect.stringContaining("name is required"), + }), + ); + }); + it("invalid name", () => { expect(() => mapCreateRequest( diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index e3b65644..82a10430 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -28,8 +28,8 @@ go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest ## Create Set `BL_API_KEY` and `BL_WORKSPACE`. Create needs a Blaxel-valid `name` -(lowercase alphanumeric with single hyphens, ≤49 characters), an image, and -client `region`. +(required; blank or whitespace-only is rejected; lowercase alphanumeric with +single hyphens, ≤49 characters), an image, and client `region`. diff --git a/website/content/docs/create.mdx b/website/content/docs/create.mdx index 7e696ba6..4be807d5 100644 --- a/website/content/docs/create.mdx +++ b/website/content/docs/create.mdx @@ -269,18 +269,18 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ -| | Blaxel | -| --------- | ------------------------------------------------------------------------------------------------- | -| Source | Registry image and Blaxel-valid `name` required (lowercase alphanumeric with single hyphens, ≤49) | -| CPU | Derived from memory; CPU overrides are not supported | -| Memory | `memoryMib` / `memory_mib` (Blaxel treats the value as MB) | -| Disk | Cannot be set | -| Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m); idle disable / ephemeral siblings not supported | -| Placement | Client `region` required; placement on the request is not supported | -| Ports | Default → HTTP; plain → TCP; TLS → TLS; Http2Tls not supported; 80/443/8080 reserved | -| Wait | `waitFor` only `Default` / `Scheduled`; create `timeout` 1–50s frees the name on timeout | -| Runtime | Not supported | -| Isolation | Not supported | +| | Blaxel | +| --------- | ---------------------------------------------------------------------------------------------------------------------------- | +| Source | Registry image and Blaxel-valid `name` required (blank/whitespace rejected; lowercase alphanumeric with single hyphens, ≤49) | +| CPU | Derived from memory; CPU overrides are not supported | +| Memory | `memoryMib` / `memory_mib` (Blaxel treats the value as MB) | +| Disk | Cannot be set | +| Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m); idle disable / ephemeral siblings not supported | +| Placement | Client `region` required; placement on the request is not supported | +| Ports | Default → HTTP; plain → TCP; TLS → TLS; Http2Tls not supported; 80/443/8080 reserved | +| Wait | `waitFor` only `Default` / `Scheduled`; create `timeout` 1–50s frees the name on timeout | +| Runtime | Not supported | +| Isolation | Not supported | Volumes are not available on create. @@ -611,8 +611,8 @@ See [Examples](/docs/examples). -Create needs a Blaxel-valid `name`, an image, and client `region`. The returned -id is that name. +Create needs a Blaxel-valid `name` (blank or whitespace-only is rejected), an +image, and client `region`. The returned id is that name. Set create `timeout` for how long create may wait. With 1–50 seconds, a timed-out create frees the name so you can retry. diff --git a/website/content/docs/filesystem.mdx b/website/content/docs/filesystem.mdx index 93f42693..7fccdb75 100644 --- a/website/content/docs/filesystem.mdx +++ b/website/content/docs/filesystem.mdx @@ -348,7 +348,7 @@ fmt.Println(entries) Directories return type directory. Files return type file, with size and - permissions when available. + permissions when available. `name` and `path` are whatever Blaxel reports. diff --git a/website/content/docs/sdk/create-options.mdx b/website/content/docs/sdk/create-options.mdx index 7b812df4..e5aec639 100644 --- a/website/content/docs/sdk/create-options.mdx +++ b/website/content/docs/sdk/create-options.mdx @@ -59,8 +59,9 @@ Durations on this type are **milliseconds**. Allowlists under `network` are plai - `name` is required and is the sandbox id. Use lowercase letters, digits, and - single hyphens, at most 49 characters. Look it up with `get(name)`. + `name` is required (blank or whitespace-only is rejected) and is the sandbox + id. Use lowercase letters, digits, and single hyphens, at most 49 characters. + Look it up with `get(name)`. **Example** @@ -152,8 +153,9 @@ Durations use `sandbox.Duration`. Optional numbers use `sandbox.Value`. - `Name` is required and is the sandbox id. Use lowercase letters, digits, and - single hyphens, at most 49 characters. Look it up with `Get(name)`. + `Name` is required (blank or whitespace-only is rejected) and is the sandbox + id. Use lowercase letters, digits, and single hyphens, at most 49 characters. + Look it up with `Get(name)`. **Example** diff --git a/website/content/docs/sdk/filesystem.mdx b/website/content/docs/sdk/filesystem.mdx index a85150c2..dc0062e6 100644 --- a/website/content/docs/sdk/filesystem.mdx +++ b/website/content/docs/sdk/filesystem.mdx @@ -376,7 +376,8 @@ console.log(info); You get `type` (file or directory), `size`, and usually `permissions`, - `owner`, `group`, and `modifiedAt`. + `owner`, `group`, and `modifiedAt`. `name` and `path` are whatever Blaxel + reports. @@ -402,7 +403,8 @@ fmt.Println(info) You get `Type` (file or directory), `Size`, and usually `Permissions`, - `Owner`, `Group`, and `ModifiedAt`. + `Owner`, `Group`, and `ModifiedAt`. `Name` and `Path` are whatever Blaxel + reports. From 8fe949779f54f6e0b6ea5ce5d8be7a681b9094fc Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 21:11:19 +0530 Subject: [PATCH 44/60] Align Blaxel TypeScript filesystem error kinds with Go. Map junk Stat bodies and empty list identities to InvalidResponse, and classify copyToLocal Node path failures as local_path so callers see the same kinds in both languages. Co-authored-by: Cursor Entire-Checkpoint: 01M75MAKHPB275KCG9MWHZ2DNP --- sdks/go/providers/blaxel/README.md | 4 +- sdks/typescript/providers/blaxel/README.md | 4 +- .../providers/blaxel/src/blaxel.test.ts | 89 +++++++++++++++++++ .../typescript/providers/blaxel/src/blaxel.ts | 9 +- .../providers/blaxel/src/filesystem.ts | 38 ++++---- sdks/typescript/providers/blaxel/src/local.ts | 22 ++++- .../typescript/providers/blaxel/src/native.ts | 11 ++- 7 files changed, 151 insertions(+), 26 deletions(-) diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 6b3c70ea..5c5726c2 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -106,8 +106,8 @@ support `Parents: false`. `Stat` returns `Type` (file or directory), `Size`, and usually `Permissions`, `Owner`, `Group`, and `ModifiedAt`. `Name` and `Path` are whatever Blaxel -reports. Missing local files for `CopyFromLocal` return not found with field -`local_path`. +reports. Missing local files for `CopyFromLocal`, and local write failures from +`CopyToLocal`, use field `local_path`. ### Process diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index 629129cb..e199a867 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -99,8 +99,8 @@ must be absolute. `parents` defaults to true; Blaxel does not support `stat` returns `type` (file or directory), `size`, and usually `permissions`, `owner`, `group`, and `modifiedAt`. `name` and `path` are whatever Blaxel -reports. Missing local files for `copyFromLocal` return not found with field -`local_path`. +reports. Missing local files for `copyFromLocal`, and local write failures from +`copyToLocal`, use field `local_path`. ### Process diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 485fc0a0..3ac3bf17 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -1367,6 +1367,95 @@ describe("Blaxel filesystem.stat", () => { missing.stat!("sandbox-1", "/missing", {}, {}), ).rejects.toMatchObject({ kind: ErrorKind.Unknown, operation: "stat" }); }); + + it("maps an unrecognized get body to InvalidResponse", async () => { + const backend = fsBackend({ + async get() { + return { unexpected: true } as never; + }, + }); + await expect( + backend.stat!("sandbox-1", "/app/readme.txt", {}, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.InvalidResponse, + operation: "stat", + field: FileFieldPath, + }); + }); + + it("rejects list entries with empty name and path", async () => { + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + return { + metadata: { name: "sandbox-1" }, + fs: { + async ls() { + return { + files: [{ name: "", path: "", size: 0 }], + subdirectories: [], + }; + }, + } as never, + process: {} as never, + }; + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + await expect( + backend.listDirectory!("sandbox-1", "/app", {}, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.InvalidResponse, + operation: "list_directory", + field: FileFieldPath, + }); + }); + + it("maps Node path failures from copyToLocal to local_path", async () => { + const local = join(tmpdir(), "kit-blaxel-copy-fail.txt"); + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + return { + metadata: { name: "sandbox-1" }, + fs: { + async download() { + const err = new Error( + `ENOENT: no such file or directory, open '${local}'`, + ) as NodeJS.ErrnoException; + err.code = "ENOENT"; + throw err; + }, + } as never, + process: {} as never, + }; + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + await expect( + backend.copyToLocal!("sandbox-1", "/tmp/note.txt", local, {}, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.NotFound, + field: FileFieldLocalPath, + operation: "copy_to_local", + }); + }); }); function fsBackend(fs: { diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index e6c1fa8e..49fad413 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -38,6 +38,7 @@ import { import { blaxelStat, directoryInfo, fileInfo } from "./filesystem.js"; import { ensureLocalParent, + mapCallerPathError, readLocalFile, requireLocalDestination, } from "./local.js"; @@ -209,7 +210,11 @@ class BlaxelBackend extends BackendBase implements Backend { id, options, body: async (sandbox) => { - await sandbox.fs.download(remotePath, localPath); + try { + await sandbox.fs.download(remotePath, localPath); + } catch (error) { + mapCallerPathError(error, "copy_to_local"); + } }, }); } @@ -252,7 +257,7 @@ class BlaxelBackend extends BackendBase implements Backend { const listing = await sandbox.fs.ls(path); return [ ...listing.subdirectories.map((entry) => directoryInfo(entry)), - ...listing.files.map((entry) => fileInfo(entry)), + ...listing.files.map((entry) => fileInfo(entry, "list_directory")), ]; }, }); diff --git a/sdks/typescript/providers/blaxel/src/filesystem.ts b/sdks/typescript/providers/blaxel/src/filesystem.ts index 2179868a..c436bd6a 100644 --- a/sdks/typescript/providers/blaxel/src/filesystem.ts +++ b/sdks/typescript/providers/blaxel/src/filesystem.ts @@ -10,7 +10,8 @@ import type { NativeSandboxHandle } from "./native.js"; type BlaxelSubdirectory = Directory["subdirectories"][number]; -export function fileInfo(info: BlaxelFile): FileInfo { +export function fileInfo(info: BlaxelFile, operation = "stat"): FileInfo { + requireFileIdentity(info, operation); const modified = Date.parse(info.lastModified); return { name: info.name, @@ -51,26 +52,33 @@ export async function blaxelStat( size: 0n, }; } - const name = data.name ?? ""; - const filePath = data.path ?? ""; - // Match Go blaxelFileInfo: empty name and path is not usable file metadata. - if (!name && !filePath) { + return fileInfo( + { + name: data.name ?? "", + path: data.path ?? "", + size: data.size, + permissions: data.permissions ?? "", + owner: data.owner ?? "", + group: data.group ?? "", + lastModified: data.lastModified ?? "", + }, + "stat", + ); +} + +/** Match Go blaxelFileInfo: empty name and path is not usable file metadata. */ +function requireFileIdentity( + info: Pick, + operation: string, +): void { + if (!info.name && !info.path) { providerError({ kind: ErrorKind.InvalidResponse, - operation: "stat", + operation, field: FileFieldPath, message: "SDK returned no file metadata", }); } - return fileInfo({ - name, - path: filePath, - size: data.size, - permissions: data.permissions ?? "", - owner: data.owner ?? "", - group: data.group ?? "", - lastModified: data.lastModified ?? "", - }); } function isDirectoryListing(data: Directory | BlaxelFile): data is Directory { diff --git a/sdks/typescript/providers/blaxel/src/local.ts b/sdks/typescript/providers/blaxel/src/local.ts index a9fcedfd..30dc396c 100644 --- a/sdks/typescript/providers/blaxel/src/local.ts +++ b/sdks/typescript/providers/blaxel/src/local.ts @@ -64,6 +64,22 @@ export async function readLocalFile( } } +/** + * Classifies a Node path failure from Blaxel download as `local_path`. + * Other errors are rethrown for `mapBlaxelError` (matches Go mapCallerPathError). + */ +export function mapCallerPathError(error: unknown, operation: string): never { + if (error instanceof SandboxError) throw error; + if (nodeCode(error) || nodeCode(causeOf(error))) { + throw localFileError(error, operation); + } + throw error; +} + +function causeOf(error: unknown): unknown { + return error instanceof Error ? error.cause : undefined; +} + function localDirectory(operation: string): never { providerError({ kind: ErrorKind.InvalidArgument, @@ -74,12 +90,10 @@ function localDirectory(operation: string): never { } function localFileError(error: unknown, operation: string): SandboxError { + const code = nodeCode(error) ?? nodeCode(causeOf(error)); return sandboxError( { - kind: - nodeCode(error) === "ENOENT" - ? ErrorKind.NotFound - : ErrorKind.InvalidArgument, + kind: code === "ENOENT" ? ErrorKind.NotFound : ErrorKind.InvalidArgument, provider: "blaxel", operation, field: FileFieldLocalPath, diff --git a/sdks/typescript/providers/blaxel/src/native.ts b/sdks/typescript/providers/blaxel/src/native.ts index dc1bab50..9a7bd8c8 100644 --- a/sdks/typescript/providers/blaxel/src/native.ts +++ b/sdks/typescript/providers/blaxel/src/native.ts @@ -9,7 +9,9 @@ import { type SandboxCreateConfiguration, type SandboxFileSystem, } from "@blaxel/core"; +import { ErrorKind, FileFieldPath } from "@sandbox-kit/sandbox"; import type { BlaxelSandboxIdentity } from "#mappings/provider_mappings.js"; +import { providerError } from "./errors.js"; export interface BlaxelProcessResult { readonly pid?: string; @@ -147,7 +149,14 @@ async function filesystemGet( }; return meta; } - throw new Error("Unsupported filesystem path type"); + // Match Go Stat: a non-listing GET body is treated as file metadata; when it + // is not usable Kit reports InvalidResponse (not Unknown from a plain Error). + providerError({ + kind: ErrorKind.InvalidResponse, + operation: "stat", + field: FileFieldPath, + message: "SDK returned no file metadata", + }); } function isDirectoryListing(data: unknown): data is Directory { From 5999b9152e9da3f5a1d15a103627d14d474bde24 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 21:15:16 +0530 Subject: [PATCH 45/60] Tighten Blaxel copyToLocal path errors and share listing detection. Remap local_path only when the Node error path matches the destination, matching Go, and reuse one isDirectoryListing helper for Stat. Co-authored-by: Cursor Entire-Checkpoint: 01M75MHTVFCPY90FSC7B0H7EY6 --- .../providers/blaxel/src/blaxel.test.ts | 39 +++++++++++++++++++ .../typescript/providers/blaxel/src/blaxel.ts | 2 +- .../providers/blaxel/src/filesystem.ts | 6 +-- sdks/typescript/providers/blaxel/src/local.ts | 24 +++++++++--- .../typescript/providers/blaxel/src/native.ts | 3 +- 5 files changed, 62 insertions(+), 12 deletions(-) diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 3ac3bf17..2e6a221e 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -1435,6 +1435,7 @@ describe("Blaxel filesystem.stat", () => { `ENOENT: no such file or directory, open '${local}'`, ) as NodeJS.ErrnoException; err.code = "ENOENT"; + err.path = local; throw err; }, } as never, @@ -1456,6 +1457,44 @@ describe("Blaxel filesystem.stat", () => { operation: "copy_to_local", }); }); + + it("does not tag non-destination Node errors as local_path on copyToLocal", async () => { + const local = join(tmpdir(), "kit-blaxel-copy-ok-dest.txt"); + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + return { + metadata: { name: "sandbox-1" }, + fs: { + async download() { + const err = new Error( + "ENOENT: no such file or directory, open '/other/path'", + ) as NodeJS.ErrnoException; + err.code = "ENOENT"; + err.path = "/other/path"; + throw err; + }, + } as never, + process: {} as never, + }; + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + await expect( + backend.copyToLocal!("sandbox-1", "/tmp/note.txt", local, {}, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.Unknown, + operation: "copy_to_local", + }); + }); }); function fsBackend(fs: { diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index 49fad413..1fd9dcb7 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -213,7 +213,7 @@ class BlaxelBackend extends BackendBase implements Backend { try { await sandbox.fs.download(remotePath, localPath); } catch (error) { - mapCallerPathError(error, "copy_to_local"); + mapCallerPathError(error, "copy_to_local", localPath); } }, }); diff --git a/sdks/typescript/providers/blaxel/src/filesystem.ts b/sdks/typescript/providers/blaxel/src/filesystem.ts index c436bd6a..10c9c42b 100644 --- a/sdks/typescript/providers/blaxel/src/filesystem.ts +++ b/sdks/typescript/providers/blaxel/src/filesystem.ts @@ -6,7 +6,7 @@ import { type FileInfo, } from "@sandbox-kit/sandbox"; import { providerError } from "./errors.js"; -import type { NativeSandboxHandle } from "./native.js"; +import { isDirectoryListing, type NativeSandboxHandle } from "./native.js"; type BlaxelSubdirectory = Directory["subdirectories"][number]; @@ -80,7 +80,3 @@ function requireFileIdentity( }); } } - -function isDirectoryListing(data: Directory | BlaxelFile): data is Directory { - return "files" in data || "subdirectories" in data; -} diff --git a/sdks/typescript/providers/blaxel/src/local.ts b/sdks/typescript/providers/blaxel/src/local.ts index 30dc396c..20250d64 100644 --- a/sdks/typescript/providers/blaxel/src/local.ts +++ b/sdks/typescript/providers/blaxel/src/local.ts @@ -1,5 +1,5 @@ import { mkdir, readFile, stat } from "node:fs/promises"; -import { dirname } from "node:path"; +import { dirname, resolve } from "node:path"; import { ErrorKind, @@ -65,17 +65,31 @@ export async function readLocalFile( } /** - * Classifies a Node path failure from Blaxel download as `local_path`. - * Other errors are rethrown for `mapBlaxelError` (matches Go mapCallerPathError). + * Classifies a local destination path failure from Blaxel download as + * `local_path`. Matches Go: only when the error path equals `localPath`. + * Other errors are rethrown for `mapBlaxelError`. */ -export function mapCallerPathError(error: unknown, operation: string): never { +export function mapCallerPathError( + error: unknown, + operation: string, + localPath: string, +): never { if (error instanceof SandboxError) throw error; - if (nodeCode(error) || nodeCode(causeOf(error))) { + const pathErr = nodePathError(error) ?? nodePathError(causeOf(error)); + if (pathErr !== undefined && resolve(pathErr.path) === resolve(localPath)) { throw localFileError(error, operation); } throw error; } +/** Node `ErrnoException`-style path error (Go `os.PathError` counterpart). */ +function nodePathError(error: unknown): { path: string } | undefined { + if (typeof error !== "object" || !error || !("path" in error)) + return undefined; + const path = error.path; + return typeof path === "string" && path !== "" ? { path } : undefined; +} + function causeOf(error: unknown): unknown { return error instanceof Error ? error.cause : undefined; } diff --git a/sdks/typescript/providers/blaxel/src/native.ts b/sdks/typescript/providers/blaxel/src/native.ts index 9a7bd8c8..59ddc0f5 100644 --- a/sdks/typescript/providers/blaxel/src/native.ts +++ b/sdks/typescript/providers/blaxel/src/native.ts @@ -159,7 +159,8 @@ async function filesystemGet( }); } -function isDirectoryListing(data: unknown): data is Directory { +/** True when a filesystem GET body is a directory listing (shared with Stat). */ +export function isDirectoryListing(data: unknown): data is Directory { return ( !!data && typeof data === "object" && From f007d7b66be2eb71af64b747dacf228d0dbe496c Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 21:35:07 +0530 Subject: [PATCH 46/60] Map Blaxel TypeScript providerSource and tighten caller notes. Prefer X-Blaxel-Source then body origin like Go, fix the lifetime mapping comment, and note that Stat size may be zero. Co-authored-by: Cursor Entire-Checkpoint: 01M75NP65A11TKDYVKV5MMG3ME --- .../filesystem/stat-blaxel-file/README.md | 2 +- .../providers/blaxel/src/blaxel.test.ts | 32 ++++++++++++ .../typescript/providers/blaxel/src/errors.ts | 52 ++++++++++++++++++- .../providers/blaxel/src/mappings/mappings.ts | 2 +- 4 files changed, 85 insertions(+), 3 deletions(-) diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md b/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md index e0c97de0..e0ac96e9 100644 --- a/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/README.md @@ -6,7 +6,7 @@ Stat one absolute path in a Blaxel sandbox. `stat` returns `type` (file or directory), `size`, and usually `permissions`, `owner`, `group`, and `modifiedAt`. `name` and `path` are whatever Blaxel -reports. +reports. Size may be zero when Blaxel does not return it. ## Edge cases diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 2e6a221e..9db16858 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -518,6 +518,38 @@ describe("Blaxel mappings", () => { }); }); + it("maps X-Blaxel-Source and body origin onto providerSource", () => { + const fromHeader = new ResponseError( + new Response(null, { + status: 500, + statusText: "Internal Server Error", + headers: { "X-Blaxel-Source": "platform" }, + }), + { error: { code: "INTERNAL_ERROR", message: "boom", origin: "ignored" } }, + new Error("boom"), + ); + expect(mapBlaxelError(fromHeader, "refresh")).toMatchObject({ + providerSource: "platform", + providerCode: "INTERNAL_ERROR", + }); + const fromOrigin = new ResponseError( + new Response(null, { status: 403, statusText: "Forbidden" }), + { + error: { + code: "POLICY_VIOLATION", + message: "policy", + origin: "gateway", + status: 403, + }, + }, + new Error("policy"), + ); + expect(mapBlaxelError(fromOrigin, "create")).toMatchObject({ + providerSource: "gateway", + providerCode: "POLICY_VIOLATION", + }); + }); + it("accepts default egress with outbound domains", () => { const plan = mapCreateRequest( { diff --git a/sdks/typescript/providers/blaxel/src/errors.ts b/sdks/typescript/providers/blaxel/src/errors.ts index 5b86de8f..5ade66d1 100644 --- a/sdks/typescript/providers/blaxel/src/errors.ts +++ b/sdks/typescript/providers/blaxel/src/errors.ts @@ -40,6 +40,7 @@ interface MappedErrorInput { operation: string; statusCode?: number; providerCode?: string; + providerSource?: string; } /** Inputs for throwing a Blaxel-scoped validation or mapping error. */ @@ -156,12 +157,16 @@ export function mapBlaxelError( const providerCode = stringifyCode( getBlaxelErrorCode(error) ?? getBlaxelErrorCode(cause), ); + // Match Go: X-Blaxel-Source first, then body origin (BlaxelSource / Origin). + const providerSource = + blaxelProviderSource(error) ?? blaxelProviderSource(cause); return create({ kind: classifyKind({ error, cause, providerCode, statusCode, operation }), error, operation, ...(statusCode === undefined ? {} : { statusCode }), ...(providerCode === undefined ? {} : { providerCode }), + ...(providerSource === undefined ? {} : { providerSource }), }); } @@ -230,8 +235,52 @@ function stringifyCode(code: unknown): string | undefined { return undefined; } +function readProperty(value: unknown, key: string): unknown { + try { + return typeof value === "object" && value !== null + ? Reflect.get(value, key) + : undefined; + } catch { + return undefined; + } +} + +/** Same candidate walk as @blaxel/core getBlaxelErrorCode (error / data / nested). */ +function errorBodies(error: unknown): unknown[] { + const bodies = [ + error, + readProperty(error, "error"), + readProperty(error, "data"), + ]; + return bodies.flatMap((body) => [body, readProperty(body, "error")]); +} + +function headerValue(error: unknown, name: string): string | undefined { + try { + const headers = readProperty(readProperty(error, "response"), "headers"); + const get = readProperty(headers, "get"); + if (typeof get !== "function") return undefined; + const value = Reflect.apply(get, headers, [name]); + return typeof value === "string" && value !== "" ? value : undefined; + } catch { + return undefined; + } +} + +/** Prefer X-Blaxel-Source (Go BlaxelSource), else body origin (Go Origin). */ +function blaxelProviderSource(error: unknown): string | undefined { + const fromHeader = headerValue(error, "x-blaxel-source"); + if (fromHeader !== undefined) return fromHeader; + for (const body of errorBodies(error)) { + const origin = readProperty(body, "origin"); + if (typeof origin === "string" && origin !== "") return origin; + } + return undefined; +} + function create(input: MappedErrorInput): SandboxError { - const { kind, error, operation, statusCode, providerCode } = input; + const { kind, error, operation, statusCode, providerCode, providerSource } = + input; const field = kind === ErrorKind.NotFound && FILESYSTEM_OPERATIONS.has(operation) ? FileFieldPath @@ -248,6 +297,7 @@ function create(input: MappedErrorInput): SandboxError { : "sandbox-kit blaxel: provider operation failed", ...(statusCode === undefined ? {} : { statusCode }), ...(providerCode === undefined ? {} : { providerCode }), + ...(providerSource === undefined ? {} : { providerSource }), }, error, ); diff --git a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts index f4ec6f15..46b893bf 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/mappings.ts @@ -190,7 +190,7 @@ function mapSecurity(r: CreateOptions): void { function mapLifetime(r: CreateOptions, p: SandboxCreateConfiguration): void { const v = r.lifetime; if (!v) return; - // Idle first (matches Go) so idle_terminate errors win over unmapped sibling fields. + // Maximum lifetime first, then idle (matches Go creation.go). if (v.maximumLifetime !== undefined) { p.ttl = durationString({ ms: v.maximumLifetime, From 41b1767d7254788dbbf659deb41d4990fb77ece7 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 21:39:22 +0530 Subject: [PATCH 47/60] Drop Blaxel Go providerMetadata and createdAt from SandboxInfo. Keep Blaxel info on the YAML mapSandboxInfo surface so Go matches TypeScript. Co-authored-by: Cursor Entire-Checkpoint: 01M75NXZCP7T0BYKYAR18Z4R3A --- sdks/go/providers/blaxel/metadata.go | 41 ------------------------ sdks/go/providers/blaxel/sandbox_test.go | 19 ++++++++--- 2 files changed, 15 insertions(+), 45 deletions(-) diff --git a/sdks/go/providers/blaxel/metadata.go b/sdks/go/providers/blaxel/metadata.go index 3e6b95a3..aabc4821 100644 --- a/sdks/go/providers/blaxel/metadata.go +++ b/sdks/go/providers/blaxel/metadata.go @@ -3,7 +3,6 @@ package blaxel import ( blaxel "github.com/blaxel-ai/sdk-go" "github.com/sandbox-kit/kit/sdks/go/sandbox" - "google.golang.org/protobuf/types/known/timestamppb" ) type observedSandbox struct { @@ -21,7 +20,6 @@ func sandboxInfo(native *blaxel.Sandbox) (*sandbox.SandboxInfo, error) { return info, err } observed.state() - observed.metadata() return info, nil } @@ -81,42 +79,3 @@ func runtimeOnlyState(runtime blaxel.SandboxState) (sandbox.SandboxState, bool) return 0, false } } - -func (s *observedSandbox) metadata() { - native := s.native - values := make(map[string]*sandbox.MetadataValue, 12) - putText(values, "display_name", native.Metadata.DisplayName) - putText(values, "created_at", native.Metadata.CreatedAt) - putText(values, "updated_at", native.Metadata.UpdatedAt) - putText(values, "last_used_at", native.LastUsedAt) - putText(values, "node_generation", native.NodeGeneration) - if native.ExpiresIn > 0 { - values["expires_in_seconds"] = metadataInt(native.ExpiresIn) - } - if native.State != "" { - values["runtime_state"] = metadataText(string(native.State)) - } - if len(values) > 0 { - s.info.ProviderMetadata = &sandbox.MetadataObject{Values: values} - } - if native.Metadata.CreatedAt != "" { - if parsed, err := parseBlaxelTime(native.Metadata.CreatedAt); err == nil { - s.info.CreatedAt = timestamppb.New(parsed) - } - } -} - -func putText(values map[string]*sandbox.MetadataValue, key, value string) { - if value == "" { - return - } - values[key] = metadataText(value) -} - -func metadataInt(value int64) *sandbox.MetadataValue { - return &sandbox.MetadataValue{Value: &sandbox.MetadataSignedInteger{SignedInteger: value}} -} - -func metadataText(value string) *sandbox.MetadataValue { - return &sandbox.MetadataValue{Value: &sandbox.MetadataText{Text: value}} -} diff --git a/sdks/go/providers/blaxel/sandbox_test.go b/sdks/go/providers/blaxel/sandbox_test.go index f242de90..85ebeb5d 100644 --- a/sdks/go/providers/blaxel/sandbox_test.go +++ b/sdks/go/providers/blaxel/sandbox_test.go @@ -72,10 +72,17 @@ func TestMapSandboxInfoUsesNameAsID(t *testing.T) { t.Fatalf("info = %#v", info) } observed, err := sandboxInfo(&blaxel.Sandbox{ - Metadata: blaxel.Metadata{Name: "demo"}, - Spec: blaxel.SandboxSpec{Runtime: blaxel.SandboxRuntime{Memory: 2048}}, - Status: blaxel.StatusDeployed, - State: blaxel.SandboxStateRunning, + Metadata: blaxel.Metadata{ + Name: "demo", + DisplayName: "Demo", + CreatedAt: "2026-01-02T03:04:05Z", + UpdatedAt: "2026-01-02T04:05:06Z", + }, + Spec: blaxel.SandboxSpec{Runtime: blaxel.SandboxRuntime{Memory: 2048}}, + Status: blaxel.StatusDeployed, + State: blaxel.SandboxStateRunning, + LastUsedAt: "2026-01-02T05:06:07Z", + ExpiresIn: 3600, }) if err != nil { t.Fatal(err) @@ -83,6 +90,10 @@ func TestMapSandboxInfoUsesNameAsID(t *testing.T) { if observed.State != sandbox.SandboxStateRunning || observed.Resources.GetMemoryMib() != 2048 { t.Fatalf("observed = %#v", observed) } + // Blaxel keeps SandboxInfo to YAML mapSandboxInfo fields; no metadata bag or createdAt. + if observed.ProviderMetadata != nil || observed.CreatedAt != nil { + t.Fatalf("unexpected metadata = %#v createdAt = %#v", observed.ProviderMetadata, observed.CreatedAt) + } standby, err := sandboxInfo(&blaxel.Sandbox{ Metadata: blaxel.Metadata{Name: "demo"}, From c169bd770439581e717380bc4e359459d1372692 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 21:48:15 +0530 Subject: [PATCH 48/60] Register Blaxel in sandbox-kit test go and typescript. Wire the Go provider, all Go Blaxel examples, and the TypeScript provider into the harness so CI matches the tooling README. Co-authored-by: Cursor Entire-Checkpoint: 01M75PE7TRNR7FJ5MVCPQYZ6HZ --- go.work.sum | 1 - tooling/internal/commands/commands_test.go | 5 ++-- tooling/internal/commands/test.go | 30 +++++++++++++++++++++- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/go.work.sum b/go.work.sum index 967c9e08..018e4e34 100644 --- a/go.work.sum +++ b/go.work.sum @@ -40,7 +40,6 @@ golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= diff --git a/tooling/internal/commands/commands_test.go b/tooling/internal/commands/commands_test.go index 258a635a..4db7b464 100644 --- a/tooling/internal/commands/commands_test.go +++ b/tooling/internal/commands/commands_test.go @@ -210,7 +210,7 @@ func TestTypeScriptVerificationBuildsBeforeTests(t *testing.T) { if err := command.ExecuteContext(context.Background()); err != nil { t.Fatal(err) } - if len(runner.steps) != 9 { + if len(runner.steps) != 11 { t.Fatalf("unexpected TypeScript verification steps: %#v", runner.steps) } for _, task := range runner.steps { @@ -218,7 +218,8 @@ func TestTypeScriptVerificationBuildsBeforeTests(t *testing.T) { t.Fatalf("TypeScript verification did not use configured npm: %#v", runner.steps) } } - if strings.Join(runner.steps[0].args, " ") != "run build" || strings.Join(runner.steps[1].args, " ") != "run build" || strings.Join(runner.steps[2].args, " ") != "test" || strings.Join(runner.steps[8].args, " ") != "run build" { + last := len(runner.steps) - 1 + if strings.Join(runner.steps[0].args, " ") != "run build" || strings.Join(runner.steps[1].args, " ") != "run build" || strings.Join(runner.steps[2].args, " ") != "test" || strings.Join(runner.steps[last].args, " ") != "run build" { t.Fatalf("TypeScript verification order is incorrect: %#v", runner.steps) } } diff --git a/tooling/internal/commands/test.go b/tooling/internal/commands/test.go index 6c67b81a..b67a4264 100644 --- a/tooling/internal/commands/test.go +++ b/tooling/internal/commands/test.go @@ -10,7 +10,7 @@ import ( ) var goTestModules = []string{ - "sdks/go/sandbox", "tooling", "sdks/go/providers/modal", "sdks/go/providers/daytona", + "sdks/go/sandbox", "tooling", "sdks/go/providers/modal", "sdks/go/providers/daytona", "sdks/go/providers/blaxel", "examples/go/modal/configuration/create-modal-sandbox", "examples/go/daytona/configuration/create-daytona-sandbox", "examples/go/modal/configuration/create-modal-with-resources", "examples/go/daytona/configuration/create-daytona-with-policies", "examples/go/daytona/configuration/create-daytona-from-snapshot", "examples/go/daytona/configuration/create-daytona-from-image", @@ -64,6 +64,33 @@ var goTestModules = []string{ "examples/go/modal/process/run-modal-with-cwd", "examples/go/modal/process/run-modal-with-env", "examples/go/modal/process/run-modal-with-timeout", + "examples/go/blaxel/configuration/create-blaxel-sandbox", + "examples/go/blaxel/configuration/create-blaxel-with-resources", + "examples/go/blaxel/configuration/create-blaxel-with-labels", + "examples/go/blaxel/configuration/create-blaxel-with-ttl", + "examples/go/blaxel/configuration/create-blaxel-idle", + "examples/go/blaxel/configuration/create-blaxel-client-timeout", + "examples/go/blaxel/configuration/create-blaxel-with-ports", + "examples/go/blaxel/configuration/create-blaxel-with-allowlists", + "examples/go/blaxel/errors/handle-blaxel-errors", + "examples/go/blaxel/lifecycle/get-blaxel-sandbox", + "examples/go/blaxel/lifecycle/refresh-blaxel-sandbox", + "examples/go/blaxel/lifecycle/delete-blaxel-sandbox", + "examples/go/blaxel/lifecycle/close-blaxel-handle", + "examples/go/blaxel/filesystem/copy-file-from-blaxel", + "examples/go/blaxel/filesystem/copy-file-into-blaxel", + "examples/go/blaxel/filesystem/list-blaxel-directory", + "examples/go/blaxel/filesystem/make-blaxel-directory", + "examples/go/blaxel/filesystem/read-blaxel-file", + "examples/go/blaxel/filesystem/remove-blaxel-file", + "examples/go/blaxel/filesystem/stat-blaxel-file", + "examples/go/blaxel/filesystem/write-blaxel-file", + "examples/go/blaxel/process/run-blaxel-check-exit", + "examples/go/blaxel/process/run-blaxel-command", + "examples/go/blaxel/process/run-blaxel-shell-command", + "examples/go/blaxel/process/run-blaxel-with-cwd", + "examples/go/blaxel/process/run-blaxel-with-env", + "examples/go/blaxel/process/run-blaxel-with-timeout", } var typescriptTestModules = []struct { @@ -74,6 +101,7 @@ var typescriptTestModules = []struct { {"sdks/typescript/sandbox", true}, {"sdks/typescript/providers/daytona", true}, {"sdks/typescript/providers/modal", true}, + {"sdks/typescript/providers/blaxel", true}, } func typescriptModules(root string) ([]struct { From f327db5009fd60ac2f39110209017ac2f9ade1be Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 21:58:05 +0530 Subject: [PATCH 49/60] Set @sandbox-kit/blaxel to 0.1.0 and clarify workspace config docs. First Blaxel npm cut should start at 0.1.0; document that workspace may come from BL_WORKSPACE when omitted. Co-authored-by: Cursor Entire-Checkpoint: 01M75Q081EAWTAMYPAHHYVHQHR --- docs/configuration.md | 2 +- sdks/typescript/package-lock.json | 2 +- sdks/typescript/providers/blaxel/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 0aab14bd..4a243b64 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -61,7 +61,7 @@ in each provider package. | Environment | Client, app, and secret lookup environment | Unsupported | Unsupported | | Organization ID | Unsupported | Organization | Unsupported | | Project ID | Unsupported | Unsupported | Unsupported | -| Workspace | Unsupported | Unsupported | Required Blaxel workspace name | +| Workspace | Unsupported | Unsupported | Blaxel workspace (`BL_WORKSPACE` when omitted) | A setting the provider does not support fails before the provider client is created. Omit `auth` when the provider should use credentials it already finds diff --git a/sdks/typescript/package-lock.json b/sdks/typescript/package-lock.json index 9ee871c9..bbad7eb0 100644 --- a/sdks/typescript/package-lock.json +++ b/sdks/typescript/package-lock.json @@ -6559,7 +6559,7 @@ }, "providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/sdks/typescript/providers/blaxel/package.json b/sdks/typescript/providers/blaxel/package.json index ab11352c..685e1517 100644 --- a/sdks/typescript/providers/blaxel/package.json +++ b/sdks/typescript/providers/blaxel/package.json @@ -1,6 +1,6 @@ { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "description": "blaxel provider for sandbox kit", "keywords": [ "sandbox-kit", From 0bee2666f99453831e6afb8d33bd9a4bf33078ff Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 22:07:53 +0530 Subject: [PATCH 50/60] Align Blaxel example deps and README with the 0.1.0 cut. Point Go/TS examples at Blaxel 0.1.0 and stop describing every npm package as 0.1.7. Co-authored-by: Cursor Entire-Checkpoint: 01M75QJ61YKYC189A1YFWH6KZZ --- README.md | 5 +++-- .../blaxel/configuration/create-blaxel-client-timeout/go.mod | 2 +- examples/go/blaxel/configuration/create-blaxel-idle/go.mod | 2 +- .../go/blaxel/configuration/create-blaxel-sandbox/go.mod | 2 +- .../configuration/create-blaxel-with-allowlists/go.mod | 2 +- .../go/blaxel/configuration/create-blaxel-with-labels/go.mod | 2 +- .../go/blaxel/configuration/create-blaxel-with-ports/go.mod | 2 +- .../blaxel/configuration/create-blaxel-with-resources/go.mod | 2 +- .../go/blaxel/configuration/create-blaxel-with-ttl/go.mod | 2 +- examples/go/blaxel/errors/handle-blaxel-errors/go.mod | 2 +- examples/go/blaxel/filesystem/copy-file-from-blaxel/go.mod | 2 +- examples/go/blaxel/filesystem/copy-file-into-blaxel/go.mod | 2 +- examples/go/blaxel/filesystem/list-blaxel-directory/go.mod | 2 +- examples/go/blaxel/filesystem/make-blaxel-directory/go.mod | 2 +- examples/go/blaxel/filesystem/read-blaxel-file/go.mod | 2 +- examples/go/blaxel/filesystem/remove-blaxel-file/go.mod | 2 +- examples/go/blaxel/filesystem/stat-blaxel-file/go.mod | 2 +- examples/go/blaxel/filesystem/write-blaxel-file/go.mod | 2 +- examples/go/blaxel/lifecycle/close-blaxel-handle/go.mod | 2 +- examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.mod | 2 +- examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.mod | 2 +- examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.mod | 2 +- examples/go/blaxel/process/run-blaxel-check-exit/go.mod | 2 +- examples/go/blaxel/process/run-blaxel-command/go.mod | 2 +- examples/go/blaxel/process/run-blaxel-shell-command/go.mod | 2 +- examples/go/blaxel/process/run-blaxel-with-cwd/go.mod | 2 +- examples/go/blaxel/process/run-blaxel-with-env/go.mod | 2 +- examples/go/blaxel/process/run-blaxel-with-timeout/go.mod | 2 +- .../create-blaxel-client-timeout/package-lock.json | 2 +- .../configuration/create-blaxel-idle/package-lock.json | 2 +- .../configuration/create-blaxel-sandbox/package-lock.json | 2 +- .../create-blaxel-with-allowlists/package-lock.json | 2 +- .../create-blaxel-with-labels/package-lock.json | 2 +- .../configuration/create-blaxel-with-ports/package-lock.json | 2 +- .../create-blaxel-with-resources/package-lock.json | 2 +- .../configuration/create-blaxel-with-ttl/package-lock.json | 2 +- .../blaxel/errors/handle-blaxel-errors/package-lock.json | 2 +- .../filesystem/copy-file-from-blaxel/package-lock.json | 2 +- .../filesystem/copy-file-into-blaxel/package-lock.json | 2 +- .../filesystem/list-blaxel-directory/package-lock.json | 2 +- .../filesystem/make-blaxel-directory/package-lock.json | 2 +- .../blaxel/filesystem/read-blaxel-file/package-lock.json | 2 +- .../blaxel/filesystem/remove-blaxel-file/package-lock.json | 2 +- .../blaxel/filesystem/stat-blaxel-file/package-lock.json | 2 +- .../blaxel/filesystem/write-blaxel-file/package-lock.json | 2 +- .../blaxel/lifecycle/close-blaxel-handle/package-lock.json | 2 +- .../blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json | 2 +- .../blaxel/lifecycle/get-blaxel-sandbox/package-lock.json | 2 +- .../lifecycle/refresh-blaxel-sandbox/package-lock.json | 2 +- .../blaxel/process/run-blaxel-check-exit/package-lock.json | 2 +- .../blaxel/process/run-blaxel-command/package-lock.json | 2 +- .../process/run-blaxel-shell-command/package-lock.json | 2 +- .../blaxel/process/run-blaxel-with-cwd/package-lock.json | 2 +- .../blaxel/process/run-blaxel-with-env/package-lock.json | 2 +- .../blaxel/process/run-blaxel-with-timeout/package-lock.json | 2 +- 55 files changed, 57 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 08998058..aaf32889 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,9 @@ sandboxes through a consistent client interface. Daytona, Modal, and Blaxel are ## npm -The TypeScript SDK is published on npm at version 0.1.7. Each badge counts every -download npm has recorded for that package. +TypeScript packages on npm: `@sandbox-kit/sandbox`, `@sandbox-kit/daytona`, and +`@sandbox-kit/modal` are 0.1.7; `@sandbox-kit/blaxel` is 0.1.0. Each badge shows +that package's registry version and download count. | Package | Version | Downloads | | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.mod b/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.mod index 6860e9e3..ed86310a 100644 --- a/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.mod +++ b/examples/go/blaxel/configuration/create-blaxel-client-timeout/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/configuration/create-blaxel-idle/go.mod b/examples/go/blaxel/configuration/create-blaxel-idle/go.mod index 013a16c9..7d86cb10 100644 --- a/examples/go/blaxel/configuration/create-blaxel-idle/go.mod +++ b/examples/go/blaxel/configuration/create-blaxel-idle/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/configuration/create-blaxel-sandbox/go.mod b/examples/go/blaxel/configuration/create-blaxel-sandbox/go.mod index 2d5238e3..1788fee2 100644 --- a/examples/go/blaxel/configuration/create-blaxel-sandbox/go.mod +++ b/examples/go/blaxel/configuration/create-blaxel-sandbox/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.mod b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.mod index 3eeb6902..167e3790 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.mod +++ b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-labels/go.mod b/examples/go/blaxel/configuration/create-blaxel-with-labels/go.mod index 99f66c27..85c807e9 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-labels/go.mod +++ b/examples/go/blaxel/configuration/create-blaxel-with-labels/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/go.mod b/examples/go/blaxel/configuration/create-blaxel-with-ports/go.mod index b1f0076f..b10eb25c 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-ports/go.mod +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-resources/go.mod b/examples/go/blaxel/configuration/create-blaxel-with-resources/go.mod index f025d36c..16e6d5b8 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-resources/go.mod +++ b/examples/go/blaxel/configuration/create-blaxel-with-resources/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.mod b/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.mod index 9d34f283..43bb177e 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.mod +++ b/examples/go/blaxel/configuration/create-blaxel-with-ttl/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/errors/handle-blaxel-errors/go.mod b/examples/go/blaxel/errors/handle-blaxel-errors/go.mod index 60a40bdb..674480a3 100644 --- a/examples/go/blaxel/errors/handle-blaxel-errors/go.mod +++ b/examples/go/blaxel/errors/handle-blaxel-errors/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.mod b/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.mod index 740728bd..33faa96c 100644 --- a/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.mod +++ b/examples/go/blaxel/filesystem/copy-file-from-blaxel/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.mod b/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.mod index 825811f3..8a61bf3d 100644 --- a/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.mod +++ b/examples/go/blaxel/filesystem/copy-file-into-blaxel/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/filesystem/list-blaxel-directory/go.mod b/examples/go/blaxel/filesystem/list-blaxel-directory/go.mod index 7902cb79..9ce05095 100644 --- a/examples/go/blaxel/filesystem/list-blaxel-directory/go.mod +++ b/examples/go/blaxel/filesystem/list-blaxel-directory/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/filesystem/make-blaxel-directory/go.mod b/examples/go/blaxel/filesystem/make-blaxel-directory/go.mod index 22b1ef42..72221bf4 100644 --- a/examples/go/blaxel/filesystem/make-blaxel-directory/go.mod +++ b/examples/go/blaxel/filesystem/make-blaxel-directory/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/filesystem/read-blaxel-file/go.mod b/examples/go/blaxel/filesystem/read-blaxel-file/go.mod index 1cf0c077..2ef1eebe 100644 --- a/examples/go/blaxel/filesystem/read-blaxel-file/go.mod +++ b/examples/go/blaxel/filesystem/read-blaxel-file/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/filesystem/remove-blaxel-file/go.mod b/examples/go/blaxel/filesystem/remove-blaxel-file/go.mod index 59fac4e7..b207b60a 100644 --- a/examples/go/blaxel/filesystem/remove-blaxel-file/go.mod +++ b/examples/go/blaxel/filesystem/remove-blaxel-file/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/go.mod b/examples/go/blaxel/filesystem/stat-blaxel-file/go.mod index 86140b67..e99814d8 100644 --- a/examples/go/blaxel/filesystem/stat-blaxel-file/go.mod +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/filesystem/write-blaxel-file/go.mod b/examples/go/blaxel/filesystem/write-blaxel-file/go.mod index eedfe36e..138ea09f 100644 --- a/examples/go/blaxel/filesystem/write-blaxel-file/go.mod +++ b/examples/go/blaxel/filesystem/write-blaxel-file/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/lifecycle/close-blaxel-handle/go.mod b/examples/go/blaxel/lifecycle/close-blaxel-handle/go.mod index e672480a..c32880b5 100644 --- a/examples/go/blaxel/lifecycle/close-blaxel-handle/go.mod +++ b/examples/go/blaxel/lifecycle/close-blaxel-handle/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.mod b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.mod index e0fa5cd2..b221a716 100644 --- a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.mod +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.mod b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.mod index 49ad536b..b8c6d9bc 100644 --- a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.mod +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.mod b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.mod index c58da959..813266ea 100644 --- a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.mod +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/go.mod b/examples/go/blaxel/process/run-blaxel-check-exit/go.mod index 7a818f73..0e4377fa 100644 --- a/examples/go/blaxel/process/run-blaxel-check-exit/go.mod +++ b/examples/go/blaxel/process/run-blaxel-check-exit/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/process/run-blaxel-command/go.mod b/examples/go/blaxel/process/run-blaxel-command/go.mod index 6ea1b384..b0f30281 100644 --- a/examples/go/blaxel/process/run-blaxel-command/go.mod +++ b/examples/go/blaxel/process/run-blaxel-command/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/process/run-blaxel-shell-command/go.mod b/examples/go/blaxel/process/run-blaxel-shell-command/go.mod index e55c0e3b..03c4b15d 100644 --- a/examples/go/blaxel/process/run-blaxel-shell-command/go.mod +++ b/examples/go/blaxel/process/run-blaxel-shell-command/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/process/run-blaxel-with-cwd/go.mod b/examples/go/blaxel/process/run-blaxel-with-cwd/go.mod index d4b67f0f..b7cd2e64 100644 --- a/examples/go/blaxel/process/run-blaxel-with-cwd/go.mod +++ b/examples/go/blaxel/process/run-blaxel-with-cwd/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/process/run-blaxel-with-env/go.mod b/examples/go/blaxel/process/run-blaxel-with-env/go.mod index 1d06483e..45d02ac6 100644 --- a/examples/go/blaxel/process/run-blaxel-with-env/go.mod +++ b/examples/go/blaxel/process/run-blaxel-with-env/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/go.mod b/examples/go/blaxel/process/run-blaxel-with-timeout/go.mod index 23b8b637..b5a90420 100644 --- a/examples/go/blaxel/process/run-blaxel-with-timeout/go.mod +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/go.mod @@ -4,7 +4,7 @@ go 1.26.0 require ( github.com/joho/godotenv v1.5.1 - github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.5 + github.com/sandbox-kit/kit/sdks/go/providers/blaxel v0.1.0 github.com/sandbox-kit/kit/sdks/go/sandbox v0.1.5 ) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package-lock.json index c55880f8..f3bf6417 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-idle/package-lock.json index e7ed325c..d1f6872c 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-idle/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package-lock.json index 14bab028..86f24529 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package-lock.json index f964f393..588555c2 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package-lock.json index 2fe39f31..69288462 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package-lock.json index 19d718f5..8a6653ba 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package-lock.json index 6e67c188..879556fc 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package-lock.json b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package-lock.json index 41dd6004..085c15c9 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package-lock.json +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/package-lock.json b/examples/typescript/blaxel/errors/handle-blaxel-errors/package-lock.json index 46edf3de..1cece427 100644 --- a/examples/typescript/blaxel/errors/handle-blaxel-errors/package-lock.json +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package-lock.json b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package-lock.json index 1598b682..7e8566d0 100644 --- a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package-lock.json +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package-lock.json b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package-lock.json index 268fc7cb..2b440fea 100644 --- a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package-lock.json +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/package-lock.json b/examples/typescript/blaxel/filesystem/list-blaxel-directory/package-lock.json index afcb97eb..51e4a86b 100644 --- a/examples/typescript/blaxel/filesystem/list-blaxel-directory/package-lock.json +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/package-lock.json b/examples/typescript/blaxel/filesystem/make-blaxel-directory/package-lock.json index 1b3043b7..8eb08d74 100644 --- a/examples/typescript/blaxel/filesystem/make-blaxel-directory/package-lock.json +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/read-blaxel-file/package-lock.json index 75ac891e..202ad343 100644 --- a/examples/typescript/blaxel/filesystem/read-blaxel-file/package-lock.json +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/remove-blaxel-file/package-lock.json index 19bf527f..e49aa9c1 100644 --- a/examples/typescript/blaxel/filesystem/remove-blaxel-file/package-lock.json +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/stat-blaxel-file/package-lock.json index 49ce3e5a..eaad3cfb 100644 --- a/examples/typescript/blaxel/filesystem/stat-blaxel-file/package-lock.json +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/package-lock.json b/examples/typescript/blaxel/filesystem/write-blaxel-file/package-lock.json index 765ff996..1d5836e7 100644 --- a/examples/typescript/blaxel/filesystem/write-blaxel-file/package-lock.json +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package-lock.json b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package-lock.json index 53f310af..61dfdf22 100644 --- a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package-lock.json +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json index 6136eaaa..10d8a487 100644 --- a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package-lock.json index 033c9404..7e7beaf1 100644 --- a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package-lock.json +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json index 0eb80f4a..e6ca5b65 100644 --- a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-check-exit/package-lock.json index d25cf0d2..419ce498 100644 --- a/examples/typescript/blaxel/process/run-blaxel-check-exit/package-lock.json +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/process/run-blaxel-command/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-command/package-lock.json index b5c2a53b..14fb2063 100644 --- a/examples/typescript/blaxel/process/run-blaxel-command/package-lock.json +++ b/examples/typescript/blaxel/process/run-blaxel-command/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-shell-command/package-lock.json index df701949..ac26f8bd 100644 --- a/examples/typescript/blaxel/process/run-blaxel-shell-command/package-lock.json +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-with-cwd/package-lock.json index 9aeec1a8..6626b93d 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-cwd/package-lock.json +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-with-env/package-lock.json index a7470d4c..2ffcb55d 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-env/package-lock.json +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/package-lock.json b/examples/typescript/blaxel/process/run-blaxel-with-timeout/package-lock.json index cf7a9021..bd73fb1f 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-timeout/package-lock.json +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/package-lock.json @@ -21,7 +21,7 @@ }, "../../../../../sdks/typescript/providers/blaxel": { "name": "@sandbox-kit/blaxel", - "version": "0.1.7", + "version": "0.1.0", "license": "MIT", "dependencies": { "@blaxel/core": "0.3.36", From 8620a7791ad260ba3015dc112bed46cbfbe9a5b5 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 22:27:18 +0530 Subject: [PATCH 51/60] Document Blaxel create 504 drift and omit blank workspace. Keep examples and docs from setting an empty workspace; note Go vs TS edge-504 create behavior. Co-authored-by: Cursor Entire-Checkpoint: 01M75RNQWQZ3DA1G8HBQ7PKK8X --- docs/configuration.md | 4 +- docs/sandbox-creation.md | 6 +- .../create-blaxel-client-timeout/main.go | 7 +- .../configuration/create-blaxel-idle/main.go | 7 +- .../create-blaxel-sandbox/main.go | 7 +- .../create-blaxel-with-allowlists/main.go | 7 +- .../create-blaxel-with-labels/main.go | 7 +- .../create-blaxel-with-ports/main.go | 7 +- .../create-blaxel-with-resources/main.go | 7 +- .../create-blaxel-with-ttl/main.go | 7 +- .../errors/handle-blaxel-errors/main.go | 7 +- .../filesystem/copy-file-from-blaxel/main.go | 7 +- .../filesystem/copy-file-into-blaxel/main.go | 7 +- .../filesystem/list-blaxel-directory/main.go | 7 +- .../filesystem/make-blaxel-directory/main.go | 7 +- .../filesystem/read-blaxel-file/main.go | 7 +- .../filesystem/remove-blaxel-file/main.go | 7 +- .../filesystem/stat-blaxel-file/main.go | 7 +- .../filesystem/write-blaxel-file/main.go | 7 +- .../lifecycle/close-blaxel-handle/main.go | 7 +- .../lifecycle/delete-blaxel-sandbox/main.go | 7 +- .../lifecycle/get-blaxel-sandbox/main.go | 7 +- .../lifecycle/refresh-blaxel-sandbox/main.go | 7 +- .../process/run-blaxel-check-exit/main.go | 7 +- .../blaxel/process/run-blaxel-command/main.go | 7 +- .../process/run-blaxel-shell-command/main.go | 7 +- .../process/run-blaxel-with-cwd/main.go | 7 +- .../process/run-blaxel-with-env/main.go | 7 +- .../process/run-blaxel-with-timeout/main.go | 7 +- .../create-blaxel-client-timeout/src/main.ts | 3 +- .../create-blaxel-idle/src/main.ts | 3 +- .../create-blaxel-sandbox/src/main.ts | 3 +- .../create-blaxel-with-allowlists/src/main.ts | 3 +- .../create-blaxel-with-labels/src/main.ts | 3 +- .../create-blaxel-with-ports/src/main.ts | 3 +- .../create-blaxel-with-resources/src/main.ts | 3 +- .../create-blaxel-with-ttl/src/main.ts | 3 +- .../errors/handle-blaxel-errors/src/main.ts | 3 +- .../copy-file-from-blaxel/src/main.ts | 3 +- .../copy-file-into-blaxel/src/main.ts | 3 +- .../list-blaxel-directory/src/main.ts | 3 +- .../make-blaxel-directory/src/main.ts | 3 +- .../filesystem/read-blaxel-file/src/main.ts | 3 +- .../filesystem/remove-blaxel-file/src/main.ts | 3 +- .../filesystem/stat-blaxel-file/src/main.ts | 3 +- .../filesystem/write-blaxel-file/src/main.ts | 3 +- .../lifecycle/close-blaxel-handle/src/main.ts | 3 +- .../delete-blaxel-sandbox/src/main.ts | 3 +- .../lifecycle/get-blaxel-sandbox/src/main.ts | 3 +- .../refresh-blaxel-sandbox/src/main.ts | 3 +- .../process/run-blaxel-check-exit/src/main.ts | 3 +- .../process/run-blaxel-command/src/main.ts | 3 +- .../run-blaxel-shell-command/src/main.ts | 3 +- .../process/run-blaxel-with-cwd/src/main.ts | 3 +- .../process/run-blaxel-with-env/src/main.ts | 3 +- .../run-blaxel-with-timeout/src/main.ts | 3 +- sdks/go/providers/blaxel/README.md | 18 ++- sdks/go/providers/blaxel/example_test.go | 124 +++++++++--------- sdks/go/providers/blaxel/provider.go | 11 +- sdks/typescript/providers/blaxel/README.md | 11 +- website/content/docs/blaxel.mdx | 31 +++-- website/content/docs/concepts.mdx | 34 +++-- website/content/docs/configuration.mdx | 45 ++++--- website/content/docs/create.mdx | 29 ++-- website/content/docs/index.mdx | 16 ++- 65 files changed, 357 insertions(+), 242 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 4a243b64..0c472d84 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -91,7 +91,9 @@ Daytona create keeps its 60-second default when the timeout is omitted and when it is explicit zero. Daytona start, stop, pause, and delete use that 60-second default when the timeout is omitted, and treat explicit zero as no operation timeout. On Blaxel, your client/create `timeout` is how long create -may take. +may take. An edge HTTP 504 during create can leave TypeScript waiting until +the sandbox is ready or that timeout ends; Go returns a timeout when the 504 +arrives. ## Credentials diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index e79a93e2..88d1764d 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -20,7 +20,7 @@ dependency versions live in each provider package. | Security | Named secrets and workload identity | Egress-placeholder secrets and public access | Unsupported | | Network | Egress allowlists, inbound CIDRs, private network, managed proxy, ports, custom domain | Egress allowlists, outbound proxy, linked sandbox | Ports (not 80/443/8080; Default → Blaxel HTTP); outbound domains | | Lifetime | Maximum lifetime and idle termination | TTL, ephemeral behavior, stop/pause/archive/delete policies | Maximum lifetime (≥5m) and idle terminate (≥1m); explicit idle disable rejected | -| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Create `timeout`; 1–50s frees the name on timeout; waitFor Default/Scheduled only; Ready/Started/Submitted unsupported | +| Wait | Default/scheduled; ready requires a probe and positive timeout | Go: default, submitted, or started. TypeScript: default or started | Create `timeout`; 1–50s frees the name on timeout; waitFor Default/Scheduled only; Ready/Started/Submitted unsupported; edge HTTP 504: TypeScript may keep waiting, Go returns timeout (get by name before recreate) | | Observability | Verbose output | Telemetry endpoint | Unsupported | Kit has no portable volume fields; Blaxel volumes stay out of create the same @@ -125,6 +125,10 @@ deadline. A caller-context deadline still applies. A Daytona creation timeout must be a whole number of seconds. An omitted Daytona creation timeout and an explicit zero both leave Daytona's 60-second create default in place. Modal ready waiting also requires a readiness probe and a positive creation timeout. +On Blaxel, an edge HTTP 504 during create can leave TypeScript waiting until +the sandbox is ready or the create timeout ends, while Go returns a timeout as +soon as that response arrives. A Go timeout does not always mean the sandbox +was never created — call `Get` by name before recreating. ## Results and cleanup diff --git a/examples/go/blaxel/configuration/create-blaxel-client-timeout/main.go b/examples/go/blaxel/configuration/create-blaxel-client-timeout/main.go index df9546dc..10daf333 100644 --- a/examples/go/blaxel/configuration/create-blaxel-client-timeout/main.go +++ b/examples/go/blaxel/configuration/create-blaxel-client-timeout/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(90 * time.Second), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/configuration/create-blaxel-idle/main.go b/examples/go/blaxel/configuration/create-blaxel-idle/main.go index b7f37190..351c0f55 100644 --- a/examples/go/blaxel/configuration/create-blaxel-idle/main.go +++ b/examples/go/blaxel/configuration/create-blaxel-idle/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/configuration/create-blaxel-sandbox/main.go b/examples/go/blaxel/configuration/create-blaxel-sandbox/main.go index c46e8e40..e5722875 100644 --- a/examples/go/blaxel/configuration/create-blaxel-sandbox/main.go +++ b/examples/go/blaxel/configuration/create-blaxel-sandbox/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/main.go b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/main.go index 4406056d..46c23633 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-allowlists/main.go +++ b/examples/go/blaxel/configuration/create-blaxel-with-allowlists/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-labels/main.go b/examples/go/blaxel/configuration/create-blaxel-with-labels/main.go index 11eee991..5963603e 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-labels/main.go +++ b/examples/go/blaxel/configuration/create-blaxel-with-labels/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ports/main.go b/examples/go/blaxel/configuration/create-blaxel-with-ports/main.go index d0dbd73c..bd8c7d51 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-ports/main.go +++ b/examples/go/blaxel/configuration/create-blaxel-with-ports/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-resources/main.go b/examples/go/blaxel/configuration/create-blaxel-with-resources/main.go index 39984fbc..f06738af 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-resources/main.go +++ b/examples/go/blaxel/configuration/create-blaxel-with-resources/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/configuration/create-blaxel-with-ttl/main.go b/examples/go/blaxel/configuration/create-blaxel-with-ttl/main.go index d987159c..98f535df 100644 --- a/examples/go/blaxel/configuration/create-blaxel-with-ttl/main.go +++ b/examples/go/blaxel/configuration/create-blaxel-with-ttl/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/errors/handle-blaxel-errors/main.go b/examples/go/blaxel/errors/handle-blaxel-errors/main.go index 0d659378..4dda7b6a 100644 --- a/examples/go/blaxel/errors/handle-blaxel-errors/main.go +++ b/examples/go/blaxel/errors/handle-blaxel-errors/main.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -29,12 +30,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go b/examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go index fee28a06..795eb6a2 100644 --- a/examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go +++ b/examples/go/blaxel/filesystem/copy-file-from-blaxel/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go b/examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go index 2907e00c..433f599c 100644 --- a/examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go +++ b/examples/go/blaxel/filesystem/copy-file-into-blaxel/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/filesystem/list-blaxel-directory/main.go b/examples/go/blaxel/filesystem/list-blaxel-directory/main.go index 3b82c83a..6b9ba0fd 100644 --- a/examples/go/blaxel/filesystem/list-blaxel-directory/main.go +++ b/examples/go/blaxel/filesystem/list-blaxel-directory/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/filesystem/make-blaxel-directory/main.go b/examples/go/blaxel/filesystem/make-blaxel-directory/main.go index 6978669c..07d54c63 100644 --- a/examples/go/blaxel/filesystem/make-blaxel-directory/main.go +++ b/examples/go/blaxel/filesystem/make-blaxel-directory/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/filesystem/read-blaxel-file/main.go b/examples/go/blaxel/filesystem/read-blaxel-file/main.go index f05784b3..e5ffb068 100644 --- a/examples/go/blaxel/filesystem/read-blaxel-file/main.go +++ b/examples/go/blaxel/filesystem/read-blaxel-file/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/filesystem/remove-blaxel-file/main.go b/examples/go/blaxel/filesystem/remove-blaxel-file/main.go index 083b7d49..4644ee30 100644 --- a/examples/go/blaxel/filesystem/remove-blaxel-file/main.go +++ b/examples/go/blaxel/filesystem/remove-blaxel-file/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/filesystem/stat-blaxel-file/main.go b/examples/go/blaxel/filesystem/stat-blaxel-file/main.go index 4e23cbb2..1b103a20 100644 --- a/examples/go/blaxel/filesystem/stat-blaxel-file/main.go +++ b/examples/go/blaxel/filesystem/stat-blaxel-file/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/filesystem/write-blaxel-file/main.go b/examples/go/blaxel/filesystem/write-blaxel-file/main.go index 04a6e752..d4e9cf67 100644 --- a/examples/go/blaxel/filesystem/write-blaxel-file/main.go +++ b/examples/go/blaxel/filesystem/write-blaxel-file/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/lifecycle/close-blaxel-handle/main.go b/examples/go/blaxel/lifecycle/close-blaxel-handle/main.go index d7662e63..8750b650 100644 --- a/examples/go/blaxel/lifecycle/close-blaxel-handle/main.go +++ b/examples/go/blaxel/lifecycle/close-blaxel-handle/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go index 2e58be91..bb0c04b0 100644 --- a/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go +++ b/examples/go/blaxel/lifecycle/delete-blaxel-sandbox/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go index e05df12a..1afe0a65 100644 --- a/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go +++ b/examples/go/blaxel/lifecycle/get-blaxel-sandbox/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go index 02d2225f..1a64936d 100644 --- a/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go +++ b/examples/go/blaxel/lifecycle/refresh-blaxel-sandbox/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/process/run-blaxel-check-exit/main.go b/examples/go/blaxel/process/run-blaxel-check-exit/main.go index ea7bab76..0ff4bc7f 100644 --- a/examples/go/blaxel/process/run-blaxel-check-exit/main.go +++ b/examples/go/blaxel/process/run-blaxel-check-exit/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/process/run-blaxel-command/main.go b/examples/go/blaxel/process/run-blaxel-command/main.go index 600015e7..1744518b 100644 --- a/examples/go/blaxel/process/run-blaxel-command/main.go +++ b/examples/go/blaxel/process/run-blaxel-command/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/process/run-blaxel-shell-command/main.go b/examples/go/blaxel/process/run-blaxel-shell-command/main.go index 251ff3af..66d543b1 100644 --- a/examples/go/blaxel/process/run-blaxel-shell-command/main.go +++ b/examples/go/blaxel/process/run-blaxel-shell-command/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/process/run-blaxel-with-cwd/main.go b/examples/go/blaxel/process/run-blaxel-with-cwd/main.go index fec0ef56..7ffe6c29 100644 --- a/examples/go/blaxel/process/run-blaxel-with-cwd/main.go +++ b/examples/go/blaxel/process/run-blaxel-with-cwd/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/process/run-blaxel-with-env/main.go b/examples/go/blaxel/process/run-blaxel-with-env/main.go index 9af6c599..c0c12d75 100644 --- a/examples/go/blaxel/process/run-blaxel-with-env/main.go +++ b/examples/go/blaxel/process/run-blaxel-with-env/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/go/blaxel/process/run-blaxel-with-timeout/main.go b/examples/go/blaxel/process/run-blaxel-with-timeout/main.go index 5e8e42f5..b6bd9ca8 100644 --- a/examples/go/blaxel/process/run-blaxel-with-timeout/main.go +++ b/examples/go/blaxel/process/run-blaxel-with-timeout/main.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/joho/godotenv" @@ -28,12 +29,12 @@ func main() { }, }, }, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) diff --git a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/src/main.ts index 7dc77d67..2d2852c7 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/src/main.ts +++ b/examples/typescript/blaxel/configuration/create-blaxel-client-timeout/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 90000, }); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-idle/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-idle/src/main.ts index cac21075..4a008396 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-idle/src/main.ts +++ b/examples/typescript/blaxel/configuration/create-blaxel-idle/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client, PolicyMode } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/src/main.ts index 5990aa71..9fc61cf8 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-sandbox/src/main.ts +++ b/examples/typescript/blaxel/configuration/create-blaxel-sandbox/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/src/main.ts index 61b7f9e2..3bb81fef 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/src/main.ts +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-allowlists/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/src/main.ts index d0b43514..dc5f6ba4 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-labels/src/main.ts +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-labels/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/src/main.ts index dfcca774..7ddf0d00 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-ports/src/main.ts +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ports/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client, PortTransport } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/src/main.ts index 43844790..8f26b1f8 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-resources/src/main.ts +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-resources/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/src/main.ts b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/src/main.ts index 712eb45b..4b21c1af 100644 --- a/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/src/main.ts +++ b/examples/typescript/blaxel/configuration/create-blaxel-with-ttl/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/errors/handle-blaxel-errors/src/main.ts b/examples/typescript/blaxel/errors/handle-blaxel-errors/src/main.ts index 58f6f1d1..ef5f693a 100644 --- a/examples/typescript/blaxel/errors/handle-blaxel-errors/src/main.ts +++ b/examples/typescript/blaxel/errors/handle-blaxel-errors/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client, SandboxError } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/src/main.ts b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/src/main.ts index ab903f27..8697f796 100644 --- a/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/src/main.ts +++ b/examples/typescript/blaxel/filesystem/copy-file-from-blaxel/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/src/main.ts b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/src/main.ts index 60f8c71e..2be83ebc 100644 --- a/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/src/main.ts +++ b/examples/typescript/blaxel/filesystem/copy-file-into-blaxel/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/filesystem/list-blaxel-directory/src/main.ts b/examples/typescript/blaxel/filesystem/list-blaxel-directory/src/main.ts index e741e9f7..295c5c53 100644 --- a/examples/typescript/blaxel/filesystem/list-blaxel-directory/src/main.ts +++ b/examples/typescript/blaxel/filesystem/list-blaxel-directory/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/filesystem/make-blaxel-directory/src/main.ts b/examples/typescript/blaxel/filesystem/make-blaxel-directory/src/main.ts index 695fda6d..ecf95dce 100644 --- a/examples/typescript/blaxel/filesystem/make-blaxel-directory/src/main.ts +++ b/examples/typescript/blaxel/filesystem/make-blaxel-directory/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/filesystem/read-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/read-blaxel-file/src/main.ts index 61f2afc2..814e4550 100644 --- a/examples/typescript/blaxel/filesystem/read-blaxel-file/src/main.ts +++ b/examples/typescript/blaxel/filesystem/read-blaxel-file/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/filesystem/remove-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/remove-blaxel-file/src/main.ts index 09464ff4..de743181 100644 --- a/examples/typescript/blaxel/filesystem/remove-blaxel-file/src/main.ts +++ b/examples/typescript/blaxel/filesystem/remove-blaxel-file/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120_000, }); diff --git a/examples/typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts index 9ce75411..afe0d2d8 100644 --- a/examples/typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts +++ b/examples/typescript/blaxel/filesystem/stat-blaxel-file/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/filesystem/write-blaxel-file/src/main.ts b/examples/typescript/blaxel/filesystem/write-blaxel-file/src/main.ts index 18ea66e8..175905cd 100644 --- a/examples/typescript/blaxel/filesystem/write-blaxel-file/src/main.ts +++ b/examples/typescript/blaxel/filesystem/write-blaxel-file/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/src/main.ts b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/src/main.ts index 2841d90e..49a03bb3 100644 --- a/examples/typescript/blaxel/lifecycle/close-blaxel-handle/src/main.ts +++ b/examples/typescript/blaxel/lifecycle/close-blaxel-handle/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts index ba33e4d8..0fee1d1d 100644 --- a/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts +++ b/examples/typescript/blaxel/lifecycle/delete-blaxel-sandbox/src/main.ts @@ -15,11 +15,12 @@ function assertCapability( } const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120_000, }); diff --git a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts index 781cda82..da1c533b 100644 --- a/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts +++ b/examples/typescript/blaxel/lifecycle/get-blaxel-sandbox/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120_000, }); diff --git a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts index 6e9afd26..ae956e10 100644 --- a/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts +++ b/examples/typescript/blaxel/lifecycle/refresh-blaxel-sandbox/src/main.ts @@ -15,11 +15,12 @@ function assertCapability( } const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120_000, }); diff --git a/examples/typescript/blaxel/process/run-blaxel-check-exit/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-check-exit/src/main.ts index e10f077e..74fd4914 100644 --- a/examples/typescript/blaxel/process/run-blaxel-check-exit/src/main.ts +++ b/examples/typescript/blaxel/process/run-blaxel-check-exit/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/process/run-blaxel-command/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-command/src/main.ts index c040996c..524c167c 100644 --- a/examples/typescript/blaxel/process/run-blaxel-command/src/main.ts +++ b/examples/typescript/blaxel/process/run-blaxel-command/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/process/run-blaxel-shell-command/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-shell-command/src/main.ts index c5b4d830..d3777166 100644 --- a/examples/typescript/blaxel/process/run-blaxel-shell-command/src/main.ts +++ b/examples/typescript/blaxel/process/run-blaxel-shell-command/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts index d376a5d2..c01a8d88 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts +++ b/examples/typescript/blaxel/process/run-blaxel-with-cwd/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/process/run-blaxel-with-env/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-with-env/src/main.ts index eaef0dc4..5bf5675c 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-env/src/main.ts +++ b/examples/typescript/blaxel/process/run-blaxel-with-env/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/examples/typescript/blaxel/process/run-blaxel-with-timeout/src/main.ts b/examples/typescript/blaxel/process/run-blaxel-with-timeout/src/main.ts index bed95ad4..a08fc7c6 100644 --- a/examples/typescript/blaxel/process/run-blaxel-with-timeout/src/main.ts +++ b/examples/typescript/blaxel/process/run-blaxel-with-timeout/src/main.ts @@ -2,11 +2,12 @@ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; const region = process.env.BL_REGION || "us-pdx-1"; +const workspace = process.env.BL_WORKSPACE?.trim(); await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region, timeout: 120000, }); diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 5c5726c2..5d75396d 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -17,16 +17,17 @@ go get github.com/sandbox-kit/kit/sdks/go/providers/blaxel@latest ## Create a sandbox ```go -client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ +config := &sandbox.Config{ Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, }}, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value("us-pdx-1"), Timeout: sandbox.Duration(2 * time.Minute), -}) +} +if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +} +client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) } @@ -53,7 +54,7 @@ sandbox. Call `Delete`, or set a lifetime policy. | Setting | How to set it | | --------- | ------------------------------------- | | API key | `Auth` with `APIKey` | -| Workspace | `Scope.Workspace` | +| Workspace | `Scope.Workspace` (or `BL_WORKSPACE`) | | Region | `Config.Region` (required for create) | | Timeout | `Config.Timeout` (create/op deadline) | @@ -77,6 +78,11 @@ Set client or create timeout for how long create may wait. With a 1–50s timeout, a timed-out create frees the name. Ready/Started/Submitted wait and readiness probes are not supported (`WaitFor` only Default or Scheduled). +If Blaxel’s edge returns HTTP 504 during create, Go returns a timeout as soon +as that response arrives. TypeScript may keep waiting until the sandbox is +ready or your create timeout ends. A Go timeout does not always mean the +sandbox was never created — call `Get` by name before recreating. + Set `Resources.MemoryMib` for memory size. Blaxel documents that value as MB; Kit does not convert units. CPU follows memory on Blaxel — do not set CPU or disk overrides. If you omit memory, Blaxel applies its default. Volumes are not diff --git a/sdks/go/providers/blaxel/example_test.go b/sdks/go/providers/blaxel/example_test.go index 42e47f30..9375d362 100644 --- a/sdks/go/providers/blaxel/example_test.go +++ b/sdks/go/providers/blaxel/example_test.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "strings" "time" "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" @@ -16,16 +17,17 @@ func ExampleNew() { if region == "" { region = "us-pdx-1" } - client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + config := &sandbox.Config{ Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, }}, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value(region), Timeout: sandbox.Duration(2 * time.Minute), - }) + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { fmt.Println(err) return @@ -50,12 +52,13 @@ func ExampleNew_image() { if region == "" { region = "us-pdx-1" } - client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, + config := &sandbox.Config{ Region: sandbox.Value(region), - }) + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { fmt.Println(err) return @@ -79,11 +82,11 @@ func ExampleNew_image() { } func ExampleNew_get() { - client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, - }) + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { fmt.Println(err) return @@ -101,11 +104,11 @@ func ExampleNew_get() { } func ExampleNew_delete() { - client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, - }) + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { fmt.Println(err) return @@ -128,12 +131,13 @@ func ExampleNew_close() { if region == "" { region = "us-pdx-1" } - client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, + config := &sandbox.Config{ Region: sandbox.Value(region), - }) + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { fmt.Println(err) return @@ -170,11 +174,11 @@ func ExampleNew_errors() { } func ExampleNew_process() { - client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, - }) + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { fmt.Println(err) return @@ -197,11 +201,11 @@ func ExampleNew_process() { } func ExampleNew_processShell() { - client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, - }) + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { fmt.Println(err) return @@ -224,11 +228,11 @@ func ExampleNew_processShell() { } func ExampleNew_processEnv() { - client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, - }) + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { fmt.Println(err) return @@ -252,11 +256,11 @@ func ExampleNew_processEnv() { } func ExampleNew_filesystem() { - client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, - }) + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { fmt.Println(err) return @@ -282,11 +286,11 @@ func ExampleNew_filesystem() { } func ExampleNew_makeDirectory() { - client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, - }) + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { fmt.Println(err) return @@ -305,11 +309,11 @@ func ExampleNew_makeDirectory() { } func ExampleNew_listDirectory() { - client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, - }) + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { fmt.Println(err) return @@ -330,11 +334,11 @@ func ExampleNew_listDirectory() { } func ExampleNew_stat() { - client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, - }) + config := &sandbox.Config{} + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { fmt.Println(err) return diff --git a/sdks/go/providers/blaxel/provider.go b/sdks/go/providers/blaxel/provider.go index 53986a3d..de2e8d43 100644 --- a/sdks/go/providers/blaxel/provider.go +++ b/sdks/go/providers/blaxel/provider.go @@ -21,15 +21,16 @@ type backend struct { // // Example: // -// client, err := sandbox.NewClient(New(), &sandbox.Config{ +// config := &sandbox.Config{ // Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ // ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, // }}, -// Scope: &sandbox.Scope{ -// Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), -// }, // Region: sandbox.Value("us-pdx-1"), -// }) +// } +// if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { +// config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +// } +// client, err := sandbox.NewClient(New(), config) func New() *Provider { return &Provider{} } func (*Provider) Name() string { return "blaxel" } diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index e199a867..9ecb379e 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -17,10 +17,12 @@ Requires Node.js 20, 22, or 24.15+, a Blaxel API key, and a workspace name. import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; +const workspace = process.env.BL_WORKSPACE?.trim(); + await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region: process.env.BL_REGION ?? "us-pdx-1", timeout: 120_000, }); @@ -43,7 +45,7 @@ idle policies or `delete`. | -------------- | ------------------------------------------------------- | | API key | `auth: { apiKey: { key } }` | | Omitted `auth` | Uses `BL_API_KEY` / `BL_WORKSPACE` from the environment | -| Workspace | `scope.workspace` — Blaxel workspace name | +| Workspace | `scope.workspace` — or `BL_WORKSPACE` when omitted | | Region | `region` — required for create | | Endpoint | Not supported | @@ -71,6 +73,11 @@ Set client or create `timeout` for how long create may wait. With a 1–50s timeout, a timed-out create frees the name. Ready/Started/Submitted wait is not supported (`waitFor` only Default or Scheduled). Ports 80, 443, and 8080 are reserved. + +If Blaxel’s edge returns HTTP 504 during create, TypeScript may keep waiting +until the sandbox is ready or your create timeout ends. Go returns a timeout as +soon as that response arrives. A Go timeout does not always mean the sandbox +was never created — call `get` by name before recreating. Default port transport is HTTP; plain is TCP; TLS is TLS; HTTP/2 over TLS is not supported. Volumes are not part of create. diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index 82a10430..d1bc9d43 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -38,10 +38,12 @@ single hyphens, ≤49 characters), an image, and client `region`. import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; +const workspace = process.env.BL_WORKSPACE?.trim(); + await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region: process.env.BL_REGION || "us-pdx-1", timeout: 120_000, }); @@ -67,6 +69,7 @@ import ( "context" "fmt" "os" + "strings" "time" "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" @@ -74,16 +77,17 @@ import ( ) func main() { - client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + config := &sandbox.Config{ Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, }}, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value("us-pdx-1"), Timeout: sandbox.Duration(2 * time.Minute), - }) + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { panic(err) } @@ -125,10 +129,12 @@ supported. Closing the client does not delete the sandbox. | Endpoint | Unsupported | ```ts +const workspace = process.env.BL_WORKSPACE?.trim(); + await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region: process.env.BL_REGION || "us-pdx-1", timeout: 120_000, }); @@ -149,12 +155,12 @@ config := &sandbox.Config{ Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, }}, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value("us-pdx-1"), Timeout: sandbox.Duration(2 * time.Minute), } +if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +} client, err := sandbox.NewClient(blaxel.New(), config) ``` @@ -190,6 +196,11 @@ supported. Ready/Started/Submitted wait and idle disable are not supported. Set client or create `timeout` for how long create may wait. With a 1–50s timeout, a timed-out create frees the name. +If Blaxel’s edge returns HTTP 504 during create, TypeScript may keep waiting +until the sandbox is ready or your create timeout ends. Go returns a timeout as +soon as that response arrives. A Go timeout does not always mean the sandbox +was never created — call `get` / `Get` by name before recreating. + ## Related - Labels, ports, TTL/idle — [Create](/docs/create) diff --git a/website/content/docs/concepts.mdx b/website/content/docs/concepts.mdx index 0bb89763..3be8761c 100644 --- a/website/content/docs/concepts.mdx +++ b/website/content/docs/concepts.mdx @@ -59,10 +59,12 @@ await using client = new Client({ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; +const workspace = process.env.BL_WORKSPACE?.trim(); + await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region: process.env.BL_REGION || "us-pdx-1", timeout: 120_000, // default client deadline (ms) }); @@ -135,22 +137,24 @@ if err != nil { ```go import ( "os" + "strings" "time" "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" "github.com/sandbox-kit/kit/sdks/go/sandbox" ) -client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ +config := &sandbox.Config{ Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, }}, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value("us-pdx-1"), Timeout: sandbox.Duration(2 * time.Minute), -}) +} +if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +} +client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { return err } @@ -382,10 +386,12 @@ Setup: [Modal](/docs/modal). import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; +const workspace = process.env.BL_WORKSPACE?.trim(); + await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region: process.env.BL_REGION || "us-pdx-1", }); ``` @@ -446,19 +452,23 @@ Setup: [Modal](/docs/modal). ```go import ( + "os" + "strings" + "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" "github.com/sandbox-kit/kit/sdks/go/sandbox" ) -client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ +config := &sandbox.Config{ Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, }}, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value("us-pdx-1"), -}) +} +if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +} +client, err := sandbox.NewClient(blaxel.New(), config) ``` Setup: [Blaxel](/docs/blaxel). diff --git a/website/content/docs/configuration.mdx b/website/content/docs/configuration.mdx index b17304e8..59b85e6b 100644 --- a/website/content/docs/configuration.mdx +++ b/website/content/docs/configuration.mdx @@ -105,10 +105,12 @@ await using client = new Client({ import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; +const workspace = process.env.BL_WORKSPACE?.trim(); + await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region: process.env.BL_REGION || "us-pdx-1", timeout: 120_000, }); @@ -181,6 +183,7 @@ client, err := sandbox.NewClient(modal.New(), config) ```go import ( "os" + "strings" "time" "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" @@ -191,12 +194,12 @@ config := &sandbox.Config{ Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, }}, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value("us-pdx-1"), Timeout: sandbox.Duration(2 * time.Minute), } +if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +} if region := os.Getenv("BL_REGION"); region != "" { config.Region = sandbox.Value(region) } @@ -343,7 +346,7 @@ client, err := sandbox.NewClient(modal.New(), config) | -------------- | ------------------------------------------------------- | | API key | `auth.apiKey.key` | | Omitted `auth` | Uses `BL_API_KEY` / `BL_WORKSPACE` from the environment | -| Workspace | `scope.workspace` | +| Workspace | `scope.workspace` — or `BL_WORKSPACE` when omitted | | Region | `region` — required for create | | Endpoint | Unsupported | @@ -351,10 +354,12 @@ In TypeScript, use one Blaxel client (or one API key) per process. A second client with different credentials replaces the first. ```ts +const workspace = process.env.BL_WORKSPACE?.trim(); + await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region: process.env.BL_REGION || "us-pdx-1", timeout: 120_000, }); @@ -363,24 +368,24 @@ await using client = new Client({ -| Setting | What to pass | -| --------- | ------------------------------------- | -| API key | `APIKey` | -| Workspace | `Scope.Workspace` | -| Region | `Config.Region` — required for create | -| Endpoint | Unsupported | +| Setting | What to pass | +| --------- | -------------------------------------------------- | +| API key | `APIKey` | +| Workspace | `Scope.Workspace` — or `BL_WORKSPACE` when omitted | +| Region | `Config.Region` — required for create | +| Endpoint | Unsupported | ```go config := &sandbox.Config{ Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, }}, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value("us-pdx-1"), Timeout: sandbox.Duration(2 * time.Minute), } +if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +} client, err := sandbox.NewClient(blaxel.New(), config) ``` @@ -580,10 +585,12 @@ await using client = new Client(config); ```ts +const workspace = process.env.BL_WORKSPACE?.trim(); + const config = { provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region: process.env.BL_REGION || "us-pdx-1", timeout: 120_000, }; @@ -638,12 +645,12 @@ config := &sandbox.Config{ Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, }}, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value("us-pdx-1"), Timeout: sandbox.Duration(2 * time.Minute), } +if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} +} client, err := sandbox.NewClient(blaxel.New(), config) ``` diff --git a/website/content/docs/create.mdx b/website/content/docs/create.mdx index 4be807d5..16caf547 100644 --- a/website/content/docs/create.mdx +++ b/website/content/docs/create.mdx @@ -269,18 +269,18 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ -| | Blaxel | -| --------- | ---------------------------------------------------------------------------------------------------------------------------- | -| Source | Registry image and Blaxel-valid `name` required (blank/whitespace rejected; lowercase alphanumeric with single hyphens, ≤49) | -| CPU | Derived from memory; CPU overrides are not supported | -| Memory | `memoryMib` / `memory_mib` (Blaxel treats the value as MB) | -| Disk | Cannot be set | -| Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m); idle disable / ephemeral siblings not supported | -| Placement | Client `region` required; placement on the request is not supported | -| Ports | Default → HTTP; plain → TCP; TLS → TLS; Http2Tls not supported; 80/443/8080 reserved | -| Wait | `waitFor` only `Default` / `Scheduled`; create `timeout` 1–50s frees the name on timeout | -| Runtime | Not supported | -| Isolation | Not supported | +| | Blaxel | +| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| Source | Registry image and Blaxel-valid `name` required (blank/whitespace rejected; lowercase alphanumeric with single hyphens, ≤49) | +| CPU | Derived from memory; CPU overrides are not supported | +| Memory | `memoryMib` / `memory_mib` (Blaxel treats the value as MB) | +| Disk | Cannot be set | +| Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m); idle disable / ephemeral siblings not supported | +| Placement | Client `region` required; placement on the request is not supported | +| Ports | Default → HTTP; plain → TCP; TLS → TLS; Http2Tls not supported; 80/443/8080 reserved | +| Wait | `waitFor` only `Default` / `Scheduled`; create `timeout` 1–50s frees the name on timeout; edge 504: TypeScript may keep waiting, Go returns timeout | +| Runtime | Not supported | +| Isolation | Not supported | Volumes are not available on create. @@ -617,6 +617,11 @@ image, and client `region`. The returned id is that name. Set create `timeout` for how long create may wait. With 1–50 seconds, a timed-out create frees the name so you can retry. +If Blaxel’s edge returns HTTP 504 during create, TypeScript may keep waiting +until the sandbox is ready or your create timeout ends. Go returns a timeout as +soon as that response arrives. A Go timeout does not always mean the sandbox +was never created — call `get` / `Get` by name before recreating. + See [Examples](/docs/examples). diff --git a/website/content/docs/index.mdx b/website/content/docs/index.mdx index 7d7cfeab..ad189c74 100644 --- a/website/content/docs/index.mdx +++ b/website/content/docs/index.mdx @@ -151,10 +151,12 @@ console.log(sandbox.id); import { blaxel } from "@sandbox-kit/blaxel"; import { Client } from "@sandbox-kit/sandbox"; +const workspace = process.env.BL_WORKSPACE?.trim(); + await using client = new Client({ provider: blaxel(), auth: { apiKey: { key: process.env.BL_API_KEY! } }, - scope: { workspace: process.env.BL_WORKSPACE! }, + ...(workspace ? { scope: { workspace } } : {}), region: process.env.BL_REGION || "us-pdx-1", timeout: 120_000, // default client deadline (ms) }); @@ -268,6 +270,7 @@ import ( "fmt" "log" "os" + "strings" "time" "github.com/sandbox-kit/kit/sdks/go/providers/blaxel" @@ -275,16 +278,17 @@ import ( ) func main() { - client, err := sandbox.NewClient(blaxel.New(), &sandbox.Config{ + config := &sandbox.Config{ Auth: &sandbox.AuthConfig{Credentials: &sandbox.APIKey{ ApiKey: &sandbox.APIKeyCredentials{Key: os.Getenv("BL_API_KEY")}, }}, - Scope: &sandbox.Scope{ - Workspace: sandbox.Value(os.Getenv("BL_WORKSPACE")), - }, Region: sandbox.Value("us-pdx-1"), Timeout: sandbox.Duration(2 * time.Minute), - }) + } + if workspace := strings.TrimSpace(os.Getenv("BL_WORKSPACE")); workspace != "" { + config.Scope = &sandbox.Scope{Workspace: sandbox.Value(workspace)} + } + client, err := sandbox.NewClient(blaxel.New(), config) if err != nil { log.Fatal(err) } From c8bc34e49d349a1e67c0adcdfc298b05d707ec7c Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Wed, 11 Nov 2026 22:40:41 +0530 Subject: [PATCH 52/60] docs: separate TypeScript and Go content --- website/content/docs/blaxel.mdx | 44 +++++--- website/content/docs/concepts.mdx | 8 +- website/content/docs/configuration.mdx | 28 +++--- website/content/docs/create.mdx | 22 ++-- website/content/docs/daytona.mdx | 7 +- website/content/docs/errors.mdx | 10 +- website/content/docs/filesystem.mdx | 29 +++++- website/content/docs/index.mdx | 6 +- website/content/docs/lifecycle.mdx | 105 ++++++++++++++------ website/content/docs/modal.mdx | 4 +- website/content/docs/process.mdx | 16 +-- website/content/docs/sdk/client.mdx | 18 +++- website/content/docs/sdk/create-options.mdx | 2 +- website/content/docs/sdk/sandbox.mdx | 10 +- 14 files changed, 219 insertions(+), 90 deletions(-) diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index d1bc9d43..0b554ce3 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -167,39 +167,57 @@ client, err := sandbox.NewClient(blaxel.New(), config) -Only API key auth and workspace scope are supported. In TypeScript, use one -Blaxel client (or one API key) per process. Full rules: +Only API key auth and workspace scope are supported. Full rules: [Configuration](/docs/configuration). + + Use one Blaxel client (or one API key) per process. + + ## What works | Area | Notes | | --------- | --------------------------------------------------------------------------------------------------------------- | | Create | Name + image; memory, labels, env, ports, outbound domains, TTL/idle | | Volumes | Not available on create | -| Open | `get` / `refresh` by sandbox name | -| Lifecycle | `delete`, `close` — no start/stop/pause/archive/terminate/attach | +| Open | Look up or refresh by sandbox name | +| Lifecycle | Delete or close — no start, stop, pause, archive, terminate, or attach | | Delete | Succeeds as `deleted` / `terminal`; omit `wait` may `Conflict` on quick recreate; `wait: true` waits until gone | | State | Idle → paused; active → running | | Errors | Name taken on create → AlreadyExists; delete still in progress → Conflict; bad TTL/idle → InvalidArgument | | Files | Shared filesystem methods — [Filesystem](/docs/filesystem) | | Process | `run` waits for the command — [Run a command](/docs/process) | -Pass `memoryMib` / `memory_mib` for memory (Blaxel treats it as MB). Omit it for -Blaxel’s default. CPU and disk overrides are not supported. Runtime and -isolation are not supported. Labels on `info()` appear only when Blaxel reports -them. +Pass a memory value (Blaxel treats it as MB), or omit it for Blaxel’s default. +CPU and disk overrides are not supported. Runtime and isolation are not +supported. + + + Labels on `info()` appear only when Blaxel reports them. + + + + Labels on `Info()` appear only when Blaxel reports them. + Default ports are HTTP; plain is TCP; TLS is TLS. HTTP/2 over TLS is not supported. Ready/Started/Submitted wait and idle disable are not supported. -Set client or create `timeout` for how long create may wait. With a 1–50s +Set a client or create timeout for how long create may wait. With a 1–50s timeout, a timed-out create frees the name. -If Blaxel’s edge returns HTTP 504 during create, TypeScript may keep waiting -until the sandbox is ready or your create timeout ends. Go returns a timeout as -soon as that response arrives. A Go timeout does not always mean the sandbox -was never created — call `get` / `Get` by name before recreating. + + If Blaxel’s edge returns HTTP 504 during create, the client may keep waiting + until the sandbox is ready or your create timeout ends. A timeout does not + always mean the sandbox was never created—call `get` by name before + recreating. + + + + If Blaxel’s edge returns HTTP 504 during create, the call returns a timeout as + soon as that response arrives. A timeout does not always mean the sandbox was + never created—call `Get` by name before recreating. + ## Related diff --git a/website/content/docs/concepts.mdx b/website/content/docs/concepts.mdx index 3be8761c..dee6d2c5 100644 --- a/website/content/docs/concepts.mdx +++ b/website/content/docs/concepts.mdx @@ -167,7 +167,9 @@ if err != nil { ## Sandbox handle -`create` returns a handle. To reopen an existing sandbox, use `get` or `attach`—see [Lifecycle](/docs/lifecycle). Use the handle for lifecycle, filesystem, and process calls. +Creating a sandbox returns a handle. To reopen an existing sandbox, use the +provider’s open operation—see [Lifecycle](/docs/lifecycle). Use the handle for +lifecycle, filesystem, and process calls. @@ -480,7 +482,9 @@ Setup: [Blaxel](/docs/blaxel). ## Capabilities -Unsupported calls return an error. Check `supports` before a lifecycle method that might be unavailable. A supported call can still fail if the provider rejects that sandbox. +Unsupported calls return an error. Check a handle’s capabilities before a +lifecycle method that might be unavailable. A supported call can still fail if +the provider rejects that sandbox. diff --git a/website/content/docs/configuration.mdx b/website/content/docs/configuration.mdx index 59b85e6b..35109ac4 100644 --- a/website/content/docs/configuration.mdx +++ b/website/content/docs/configuration.mdx @@ -38,14 +38,14 @@ if err != nil { -| Field | Purpose | -| ---------- | ------------------------------------------------------------------------------------------- | -| `provider` | Provider constructor | -| `auth` | One of `apiKey`, `tokenPair`, `bearerToken`, `oauthRefresh`. Omit to use provider env creds | -| `scope` | App, environment, organization, project, or workspace | -| `endpoint` | Provider endpoint URL override | -| `region` | Target / default placement | -| `timeout` | Default deadline for calls that omit their own | +| Setting | Purpose | +| ----------- | -------------------------------------------------------------------------------- | +| Provider | Provider constructor | +| Credentials | API key, token pair, bearer token, or OAuth refresh token; omit for env creds | +| Scope | App, environment, organization, project, or workspace | +| Endpoint | Provider endpoint URL override | +| Region | Target or default placement | +| Timeout | Default deadline for calls that omit their own | Provider setup: [Daytona](/docs/daytona). @@ -398,7 +398,8 @@ Only API key auth and workspace scope are supported. ## Timeouts -`timeout` on the client is the default deadline. Create and lifecycle calls can override it. +The client timeout is the default deadline. Create and lifecycle calls can +override it. Explicit zero means no deadline from the client. A context deadline or abort signal that fires earlier still wins. @@ -536,9 +537,12 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ - Set client or create `timeout` for how long create may wait. Ready, Started, - and Submitted wait are not supported. In TypeScript, use one Blaxel client (or - one API key) per process. + Set a client or create timeout for how long create may wait. Ready, Started, + and Submitted wait are not supported. + + + Use one Blaxel client (or one API key) per process. + ## Credentials diff --git a/website/content/docs/create.mdx b/website/content/docs/create.mdx index 16caf547..9419c83e 100644 --- a/website/content/docs/create.mdx +++ b/website/content/docs/create.mdx @@ -273,12 +273,12 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | Source | Registry image and Blaxel-valid `name` required (blank/whitespace rejected; lowercase alphanumeric with single hyphens, ≤49) | | CPU | Derived from memory; CPU overrides are not supported | -| Memory | `memoryMib` / `memory_mib` (Blaxel treats the value as MB) | +| Memory | Memory value (Blaxel treats the value as MB) | | Disk | Cannot be set | | Lifetime | Maximum lifetime (≥5m) and idle terminate (≥1m); idle disable / ephemeral siblings not supported | | Placement | Client `region` required; placement on the request is not supported | | Ports | Default → HTTP; plain → TCP; TLS → TLS; Http2Tls not supported; 80/443/8080 reserved | -| Wait | `waitFor` only `Default` / `Scheduled`; create `timeout` 1–50s frees the name on timeout; edge 504: TypeScript may keep waiting, Go returns timeout | +| Wait | Default or Scheduled wait only; a 1–50s create timeout frees the name on timeout | | Runtime | Not supported | | Isolation | Not supported | @@ -614,13 +614,21 @@ See [Examples](/docs/examples). Create needs a Blaxel-valid `name` (blank or whitespace-only is rejected), an image, and client `region`. The returned id is that name. -Set create `timeout` for how long create may wait. With 1–50 seconds, a +Set a create timeout for how long create may wait. With 1–50 seconds, a timed-out create frees the name so you can retry. -If Blaxel’s edge returns HTTP 504 during create, TypeScript may keep waiting -until the sandbox is ready or your create timeout ends. Go returns a timeout as -soon as that response arrives. A Go timeout does not always mean the sandbox -was never created — call `get` / `Get` by name before recreating. + + If Blaxel’s edge returns HTTP 504 during create, the client may keep waiting + until the sandbox is ready or your create timeout ends. A timeout does not + always mean the sandbox was never created—call `get` by name before + recreating. + + + + If Blaxel’s edge returns HTTP 504 during create, the call returns a timeout as + soon as that response arrives. A timeout does not always mean the sandbox was + never created—call `Get` by name before recreating. + See [Examples](/docs/examples). diff --git a/website/content/docs/daytona.mdx b/website/content/docs/daytona.mdx index ff85951c..4832d52d 100644 --- a/website/content/docs/daytona.mdx +++ b/website/content/docs/daytona.mdx @@ -3,7 +3,8 @@ title: Daytona description: Install, authenticate, and create a Daytona sandbox. --- -Use Daytona when you want default snapshots, verified `get`, and start / stop / pause / archive. +Use Daytona when you want default snapshots, verified sandbox lookup, and start, +stop, pause, or archive operations. ## Install @@ -175,8 +176,8 @@ Default endpoint: `https://app.daytona.io/api`. Remote URLs need HTTPS; HTTP is | Area | Notes | | --------- | ----------------------------------------------------------- | | Create | Default snapshot, named snapshot, or image (with resources) | -| Open | `get` — verified. `attach` is unsupported | -| Lifecycle | `refresh`, `start`, `stop`, `pause`, `archive`, `delete` | +| Open | Verified lookup. Attach is unsupported | +| Lifecycle | Refresh, start, stop, pause, archive, and delete | | Files | Filesystem methods — [Filesystem](/docs/filesystem) | | Process | `run` — [Run a command](/docs/process) | diff --git a/website/content/docs/errors.mdx b/website/content/docs/errors.mdx index 0596c170..9aea3f38 100644 --- a/website/content/docs/errors.mdx +++ b/website/content/docs/errors.mdx @@ -28,7 +28,10 @@ Kinds are categories, not retry advice. Kit does not retry. ## Invalid config -Bad client config fails before any provider call. TypeScript throws; Go returns `(*Client, error)`. +Bad client config fails before any provider call. + +It throws. +It returns `(*Client, error)`. @@ -318,7 +321,10 @@ still finishing, you get `Conflict` — wait, then `get` before you recreate tha name. Lifetime below Blaxel’s floors (≥5m max lifetime, ≥1m idle) or a non-whole-second duration is `InvalidArgument`. Unsupported create fields (runtime, isolation, idle disable, …) are `Unsupported`. When Blaxel returns a -control-plane code, read it on `providerCode` / `ProviderCode`. +control-plane code, read it on the typed error. + +Use `providerCode`. +Use `ProviderCode`. diff --git a/website/content/docs/filesystem.mdx b/website/content/docs/filesystem.mdx index 7fccdb75..9a6ba6b1 100644 --- a/website/content/docs/filesystem.mdx +++ b/website/content/docs/filesystem.mdx @@ -5,6 +5,8 @@ description: Read, write, copy, mkdir, list, stat, and remove paths inside a san Use the filesystem methods on an open handle. + + | Method | Purpose | | --------------------------------------- | ------------------------ | | [`readFile`](#read-a-file) | Read file bytes | @@ -16,6 +18,23 @@ Use the filesystem methods on an open handle. | [`stat`](#read-metadata) | Path metadata | | [`remove`](#delete-a-file-or-directory) | Delete a path | + + + + +| Method | Purpose | +| --------------------------------------- | ------------------------ | +| [`ReadFile`](#read-a-file) | Read file bytes | +| [`WriteFile`](#write-a-file) | Replace or create a file | +| [`CopyFromLocal`](#copy-a-file-in) | Local → sandbox | +| [`CopyToLocal`](#copy-a-file-out) | Sandbox → local | +| [`MakeDirectory`](#create-a-directory) | Create a directory | +| [`ListDirectory`](#list-a-directory) | List one directory | +| [`Stat`](#read-metadata) | Path metadata | +| [`Remove`](#delete-a-file-or-directory) | Delete a path | + + + You need a sandbox from [create](/docs/create) or [lifecycle](/docs/lifecycle). ## Paths and bytes @@ -25,7 +44,15 @@ You need a sandbox from [create](/docs/create) or [lifecycle](/docs/lifecycle). | Sandbox | Absolute (`/tmp/note.txt`). Relative, blank, or NUL rejected | `path` | | Local (copy) | May be relative (`./note.txt`). Non-blank, no NUL | `local_path` | -`readFile` and `writeFile` transfer bytes—encode and decode text in your app. Copy methods use files on disk. + + `readFile` and `writeFile` transfer bytes—encode and decode text in your app. + Copy methods use files on disk. + + + + `ReadFile` and `WriteFile` transfer bytes—encode and decode text in your app. + Copy methods use files on disk. + diff --git a/website/content/docs/index.mdx b/website/content/docs/index.mdx index ad189c74..22dcadcd 100644 --- a/website/content/docs/index.mdx +++ b/website/content/docs/index.mdx @@ -390,9 +390,9 @@ err = instance.Filesystem().WriteFile( Sandbox paths are absolute. More: [Filesystem](/docs/filesystem). - `client.close()` and handle `close` only release local resources. The cloud - sandbox keeps running until an explicit lifecycle call or a lifetime policy - ends it. See [Concepts](/docs/concepts) and [Lifecycle](/docs/lifecycle). + Closing a client or handle only releases local resources. The cloud sandbox + keeps running until an explicit lifecycle call or a lifetime policy ends it. + See [Concepts](/docs/concepts) and [Lifecycle](/docs/lifecycle). ## Next diff --git a/website/content/docs/lifecycle.mdx b/website/content/docs/lifecycle.mdx index c41c5dea..13646787 100644 --- a/website/content/docs/lifecycle.mdx +++ b/website/content/docs/lifecycle.mdx @@ -3,7 +3,8 @@ title: Lifecycle description: get, attach, start, stop, pause, archive, delete, terminate, and close. --- -Operate on a handle from `create`, or reopen one with `get` / `attach`. Unsupported calls return an error. +Operate on a handle from creation, or reopen an existing sandbox. Unsupported +calls return an error. @@ -22,10 +23,10 @@ Check support with `Supports`. | Operation | Support | | ------------------------------ | ------------------------------------------ | -| Open existing | [`get`](#open-a-sandbox) (provider lookup) | +| Open existing | [Look up](#open-a-sandbox) (provider lookup) | | start / stop / pause / archive | [Supported](#stop-start-pause-and-archive) | -| Destroy | [`delete`](#delete) | -| Local disconnect | [`close`](#close-the-client) | +| Destroy | [Delete](#delete) | +| Local disconnect | [Close](#close-the-client) | Daytona may still reject a supported op for a given sandbox. Snippets assume a constructed client — [Daytona](/docs/daytona). @@ -35,11 +36,12 @@ Daytona may still reject a supported op for a given sandbox. Snippets assume a c | Operation | Support | | ---------------- | ------------------------------------------------- | -| Open existing | [`attach`](#open-a-sandbox) (id only) | -| Destroy | [`terminate`](#terminate) or create-time lifetime | -| Local disconnect | [`close`](#close-the-client) | +| Open existing | [Attach](#open-a-sandbox) (id only) | +| Destroy | [Terminate](#terminate) or create-time lifetime | +| Local disconnect | [Close](#close-the-client) | -`start`, `stop`, `pause`, `archive`, and `delete` are rejected in Kit. Snippets assume a constructed client — [Modal](/docs/modal). +Start, stop, pause, archive, and delete are rejected in Kit. Snippets assume a +constructed client — [Modal](/docs/modal). @@ -47,10 +49,10 @@ Daytona may still reject a supported op for a given sandbox. Snippets assume a c | Operation | Support | | ------------------------------ | ----------------------------------------- | -| Open existing | [`get`](#open-a-sandbox) (lookup by name) | +| Open existing | [Look up](#open-a-sandbox) (by name) | | start / stop / pause / archive | Unsupported | -| Destroy | [`delete`](#delete) | -| Local disconnect | [`close`](#close-the-client) | +| Destroy | [Delete](#delete) | +| Local disconnect | [Close](#close-the-client) | Snippets assume a constructed client — [Blaxel](/docs/blaxel). @@ -60,7 +62,15 @@ Snippets assume a constructed client — [Blaxel](/docs/blaxel). -`get` loads a sandbox that already exists. `refresh` loads it again. `attach` is not available. + + `get` loads a sandbox that already exists. `refresh` loads it again. `attach` + is not available. + + + + `Get` loads a sandbox that already exists. `Refresh` loads it again. `Attach` + is not available. + @@ -98,7 +108,15 @@ fmt.Println(instance.Info()) -`attach` builds a handle from an id. It does not ask Modal whether that sandbox exists. `get` and `refresh` are not available. + + `attach` builds a handle from an id. It does not ask Modal whether that + sandbox exists. `get` and `refresh` are not available. + + + + `Attach` builds a handle from an id. It does not ask Modal whether that + sandbox exists. `Get` and `Refresh` are not available. + @@ -127,8 +145,15 @@ if err != nil { -`get` loads a sandbox that already exists by **name** (the id is the create -name). `refresh` loads it again. `attach` is not available. + + `get` loads a sandbox that already exists by **name** (the id is the create + name). `refresh` loads it again. `attach` is not available. + + + + `Get` loads a sandbox that already exists by **name** (the id is the create + name). `Refresh` loads it again. `Attach` is not available. + Idle sandboxes show as paused; active ones as running. @@ -166,7 +191,9 @@ fmt.Println(instance.Info()) ## Stop, start, pause, and archive -`stop` leaves the sandbox available to start again. `start` brings a stopped or archived sandbox back. `pause` keeps memory. `archive` moves a retained sandbox into archive storage. +Stop leaves the sandbox available to start again. Start brings a stopped or +archived sandbox back. Pause keeps memory. Archive moves a retained sandbox +into archive storage. @@ -230,7 +257,7 @@ Daytona decides whether that sandbox can pause, archive, or stop. An ephemeral s ## Delete -`delete` removes a Daytona sandbox. +Delete removes a Daytona sandbox. @@ -271,9 +298,9 @@ if err := instance.Delete(ctx, &sandbox.DeleteOptions{ ## Delete -`delete` removes a Blaxel sandbox. On success, `state` is `deleted` and -`terminal` is true. Omit `wait` to return when delete is accepted. Pass -`wait: true` to wait until removal finishes before recreate. +Delete removes a Blaxel sandbox. On success, its state is deleted and terminal. +Omit the wait option to return when deletion is accepted; set it to wait until +removal finishes before recreating. @@ -318,7 +345,8 @@ fmt.Println(info.State, info.GetTerminal()) ## Terminate -`terminate` finishes a Modal sandbox. A maximum lifetime or idle timeout set at [create](/docs/create) also ends it. +Terminate finishes a Modal sandbox. A maximum lifetime or idle timeout set at +[create](/docs/create) also ends it. @@ -406,22 +434,41 @@ defer client.Close(context.Background()) ## Read the state -`info.state` is the lifecycle state on the handle. A failed call can still -mean the cloud already changed state. See [Errors](/docs/errors). +The handle stores the lifecycle state. A failed call can still mean the cloud +already changed state. See [Errors](/docs/errors). - After a lifecycle call, call `refresh` (or `get` again) before assuming the - sandbox is stopped or gone. + + After a lifecycle call, call `refresh` (or `get` again) before assuming the + sandbox is stopped or gone. + + + After a lifecycle call, call `Refresh` (or `Get` again) before assuming the + sandbox is stopped or gone. + - Use the info returned by the call, or `info()` on the handle. There is no - `refresh` or `get` on Modal. + + Use the info returned by the call, or `info()` on the handle. There is no + `refresh` or `get` on Modal. + + + Use the info returned by the call, or `Info()` on the handle. There is no + `Refresh` or `Get` on Modal. + - After `delete`, use `wait: true` when you need removal to finish before - recreate. Use `refresh` or `get` for fresh metadata while the sandbox exists. + + After `delete`, use `wait: true` when you need removal to finish before + recreate. Use `refresh` or `get` for fresh metadata while the sandbox exists. + + + After `Delete`, use `Wait: sandbox.Value(true)` when you need removal to + finish before recreate. Use `Refresh` or `Get` for fresh metadata while the + sandbox exists. + diff --git a/website/content/docs/modal.mdx b/website/content/docs/modal.mdx index 1a24beb6..e92c4dba 100644 --- a/website/content/docs/modal.mdx +++ b/website/content/docs/modal.mdx @@ -233,8 +233,8 @@ Full rules: [Configuration](/docs/configuration). | Area | Notes | | --------- | ------------------------------------------------------------------------------ | | Create | Registry image; runtime, resources, network, ports, secrets, readiness, labels | -| Open | `attach` by id. `get` and `refresh` unsupported | -| Lifecycle | `terminate`. Start, stop, pause, archive, and delete are unsupported | +| Open | Attach by id. Lookup and refresh are unsupported | +| Lifecycle | Terminate. Start, stop, pause, archive, and delete are unsupported | | Files | Filesystem methods — [Filesystem](/docs/filesystem) | | Process | `run` — [Run a command](/docs/process) | diff --git a/website/content/docs/process.mdx b/website/content/docs/process.mdx index 34a991f2..10edbe30 100644 --- a/website/content/docs/process.mdx +++ b/website/content/docs/process.mdx @@ -29,13 +29,13 @@ Set **exactly one** of: | `argv` | Program + arguments | `["echo", "hello"]` | | `shell` | One shell command string | `"echo hello"` | -| Field | Rules | -| ------------------ | ------------------------------------------------------------------------- | -| Both or neither | Rejected. Field: `argv` | -| Blank `shell` | Rejected after trim. Field: `shell` | -| `workingDirectory` | Absolute when set. Relative, blank, or NUL → field `working_directory` | -| `environment` | For this call only. Omit → provider default. Empty map → add no variables | -| `timeout` | See [Timeouts](#timeouts-and-cancel) | +| Setting | Rules | +| ----------------- | ------------------------------------------------------------------------- | +| Program or shell | Set exactly one. A request with both or neither is rejected. | +| Shell command | A blank command is rejected after trimming. | +| Working directory | Absolute when set. Relative, blank, or NUL is rejected. | +| Environment | Applies only to this call. Omit for the provider default. | +| Timeout | See [Timeouts](#timeouts-and-cancel) | @@ -199,7 +199,7 @@ A non-zero exit code is **not** an SDK error. | Setting | Meaning | | -------------- | ----------------------------------------------------------------- | -| Omit `timeout` | Use the client default deadline | +| Omit timeout | Use the client default deadline | | `0` | No extra client deadline for this call | | Positive value | Cap this call; must be whole seconds (`5000` ok, `1500` rejected) | diff --git a/website/content/docs/sdk/client.mdx b/website/content/docs/sdk/client.mdx index ecc808d5..b7fd70f3 100644 --- a/website/content/docs/sdk/client.mdx +++ b/website/content/docs/sdk/client.mdx @@ -5,13 +5,23 @@ description: Construct a client; create, get or attach, and close. Creates sandboxes and reopens them. -Reopen with [`get`](#get) by sandbox id. + + Reopen with [`get`](#get) by sandbox id. + Reopen with [`Get`](#get) by sandbox id. + - Reopen with [`attach`](#attach) by sandbox id. Attach does not check that the - sandbox exists. + + Reopen with [`attach`](#attach) by sandbox id. It does not check that the + sandbox exists. + + + Reopen with [`Attach`](#attach) by sandbox id. It does not check that the + sandbox exists. + - Reopen with [`get`](#get) by create name. Blaxel id is the name. + Reopen with [`get`](#get) by create name. Blaxel id is the name. + Reopen with [`Get`](#get) by create name. Blaxel id is the name. Closing the client only drops the local connection—cloud sandboxes keep running. diff --git a/website/content/docs/sdk/create-options.mdx b/website/content/docs/sdk/create-options.mdx index e5aec639..682c2d28 100644 --- a/website/content/docs/sdk/create-options.mdx +++ b/website/content/docs/sdk/create-options.mdx @@ -255,7 +255,7 @@ Set exactly one variant. Unsupported variants are rejected. Requires `name`, an `image` source, and client `region`. Ports 80, 443, and 8080 are reserved. Default port transport is HTTP; plain is TCP; TLS is TLS. HTTP/2 over TLS is not supported. Runtime and isolation are not supported. Set - create `timeout` for how long create may wait (1–50s frees the name on + create timeout for how long create may wait (1–50s frees the name on timeout). Labels on `info()` appear only when Blaxel reports them. diff --git a/website/content/docs/sdk/sandbox.mdx b/website/content/docs/sdk/sandbox.mdx index 2ebb7aae..126af7cc 100644 --- a/website/content/docs/sdk/sandbox.mdx +++ b/website/content/docs/sdk/sandbox.mdx @@ -3,7 +3,10 @@ title: Sandbox description: Handle for one sandbox — lifecycle, info, filesystem, and process. --- -Handle from [`Client.create`](/docs/sdk/client#create), [`get`](/docs/sdk/client#get), or [`attach`](/docs/sdk/client#attach). Lifecycle methods that return info also update the handle. `close` only drops local resources—the cloud sandbox keeps running. Call `supports` before a lifecycle method that might be unavailable. +Handle returned when you create or reopen a sandbox. Lifecycle methods that +return info also update the handle. Closing only drops local resources—the cloud +sandbox keeps running. Check capabilities before a lifecycle method that might +be unavailable. ## Properties @@ -596,7 +599,8 @@ fmt.Println(result) -`timeout` / `Timeout` caps how long this call waits. It is not sent to Modal as a separate terminate setting. +The call timeout caps how long this call waits. It is not sent to Modal as a +separate terminate setting. @@ -633,7 +637,7 @@ Releases local handle resources. The cloud sandbox is unchanged. ## Operation options -Shared call options for TypeScript lifecycle and filesystem/process calls. +Shared call options for lifecycle and filesystem/process calls. From 581f88dc165f723aa7858a1bc665a77d4c1b378c Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Thu, 12 Nov 2026 12:51:08 +0530 Subject: [PATCH 53/60] fix: isolate Blaxel TypeScript configuration --- sdks/typescript/providers/blaxel/README.md | 5 +- .../providers/blaxel/src/blaxel.test.ts | 78 +++++++------------ .../typescript/providers/blaxel/src/blaxel.ts | 4 +- .../blaxel/src/mappings/provider_mappings.ts | 37 ++------- .../typescript/providers/blaxel/src/native.ts | 52 ++++++------- website/content/docs/blaxel.mdx | 7 +- website/content/docs/configuration.mdx | 8 +- website/content/docs/sdk/configuration.mdx | 5 +- 8 files changed, 74 insertions(+), 122 deletions(-) diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index 9ecb379e..3ec4a492 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -52,8 +52,9 @@ idle policies or `delete`. Token-pair, bearer, and OAuth are rejected. Only `scope.workspace` is accepted on scope. -In TypeScript, use one Blaxel client (or one API key) per process. A second -client with different credentials replaces the first. +In TypeScript, use one Blaxel configuration per process. A second client with +different credentials or workspace is rejected before it can replace the +process-wide SDK configuration. ## What you can do diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 9db16858..2cc8b116 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -38,7 +38,7 @@ import { } from "#mappings/provider_mappings.js"; import { captureProviderState } from "#config/provider_config.js"; import { mapBlaxelError } from "#errors.js"; -import { projectHandle } from "./native.js"; +import { defaultNativeFactory, projectHandle } from "./native.js"; import { ResponseError, SandboxInstance } from "@blaxel/core"; const provider = { region: "us-pdx-1" }; @@ -578,7 +578,6 @@ describe("Blaxel mappings", () => { mapSandboxInfo({ metadata: { name: "demo" }, status: "DEPLOYED", - state: "RUNNING", spec: { region: "us-pdx-1", runtime: { memory: 4096 } }, }), ).toMatchObject({ @@ -591,7 +590,6 @@ describe("Blaxel mappings", () => { const info = mapSandboxInfo({ metadata: { name: "demo" }, status: "DEPLOYED", - state: "RUNNING", spec: { region: "us-pdx-1" }, }); expect(info.labels).toBeUndefined(); @@ -604,7 +602,6 @@ describe("Blaxel mappings", () => { mapSandboxInfo({ metadata: { name: "demo", labels: { team: "kit" } }, status: "DEPLOYED", - state: "RUNNING", spec: { region: "us-pdx-1" }, }).labels, ).toEqual({ team: "kit" }); @@ -612,7 +609,6 @@ describe("Blaxel mappings", () => { mapSandboxInfo({ metadata: { name: "demo" }, status: "DEPLOYED", - state: "RUNNING", spec: { region: "us-pdx-1" }, }).labels, ).toBeUndefined(); @@ -684,72 +680,50 @@ describe("Blaxel mappings", () => { ); }); - it("maps DEPLOYED plus runtime STANDBY to paused", () => { + it("maps public DEPLOYED status to running", () => { expect( mapSandboxIdentity({ metadata: { name: "demo" }, status: "DEPLOYED", - state: "STANDBY", spec: { region: "us-pdx-1" }, }), ).toMatchObject({ id: "demo", - providerState: "DEPLOYED", - state: SandboxState.Paused, - region: "us-pdx-1", - }); - }); - - it("maps DEPLOYED plus runtime RUNNING to running", () => { - expect( - mapSandboxIdentity({ - metadata: { name: "demo" }, - status: "DEPLOYED", - state: "RUNNING", - }), - ).toMatchObject({ providerState: "DEPLOYED", state: SandboxState.Running, + region: "us-pdx-1", }); }); - it("maps projected runtime state from the native adapter", () => { - expect( - mapSandboxIdentity({ - metadata: { name: "demo" }, - status: "DEPLOYED", - // projectHandle puts wire sandbox.state on the plain handle. - state: "STANDBY", - }).state, - ).toBe(SandboxState.Paused); - }); - - it("projectHandle projects private wire sandbox.state without mutating", () => { + it("projectHandle uses public SDK properties only", () => { const instance = new SandboxInstance({ metadata: { name: "demo" }, status: "DEPLOYED", spec: { region: "us-pdx-1" }, } as never); - // @blaxel/core types sandbox private; seed the wire model for the adapter. - Object.assign(instance as object, { - sandbox: { - metadata: { name: "demo" }, - status: "DEPLOYED", - state: "STANDBY", - spec: { region: "us-pdx-1" }, - }, - }); const handle = projectHandle(instance); - expect(handle.state).toBe("STANDBY"); - expect((instance as { state?: string }).state).toBeUndefined(); - expect( - mapSandboxIdentity({ - metadata: { name: "demo" }, - status: "DEPLOYED", - state: handle.state, - spec: { region: "us-pdx-1" }, - }).state, - ).toBe(SandboxState.Paused); + expect(handle).toMatchObject({ + metadata: { name: "demo" }, + status: "DEPLOYED", + spec: { region: "us-pdx-1" }, + }); + expect("state" in handle).toBe(false); + }); + + it("rejects a conflicting process-wide Blaxel configuration", () => { + defaultNativeFactory({ apiKey: "first", workspace: "workspace-a" }); + expect(() => + defaultNativeFactory({ workspace: "workspace-a", apiKey: "first" }), + ).not.toThrow(); + + expect(() => + defaultNativeFactory({ apiKey: "second", workspace: "workspace-b" }), + ).toThrowError( + expect.objectContaining({ + kind: ErrorKind.FailedPrecondition, + operation: "initialize", + }), + ); }); }); diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index 1fd9dcb7..32910ce5 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -399,8 +399,8 @@ function delay(ms: number, signal: AbortSignal | undefined): Promise { * `auth.apiKey` and `scope.workspace`, or omit them to use `BL_API_KEY` / * `BL_WORKSPACE` from the environment. Set `memoryMib` for memory size; * Blaxel documents that value as MB and Kit does not convert units. Volumes - * are not part of create. In TypeScript, use one Blaxel client (or one API - * key) per process. + * are not part of create. In TypeScript, one process accepts one Blaxel + * configuration; a conflicting client configuration is rejected. */ export function blaxel(): Provider { return new BlaxelProvider(); diff --git a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts index e4ac81c3..d8e71459 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts @@ -30,11 +30,6 @@ export interface BlaxelSandboxIdentity { }; /** Deployment status (DEPLOYED, FAILED, …). */ readonly status?: unknown; - /** - * Runtime state (RUNNING / STANDBY). `@blaxel/core` types `sandbox` private - * and has no `state` getter; the native adapter projects wire state here. - */ - readonly state?: unknown; readonly spec?: { readonly region?: string; readonly runtime?: { readonly memory?: number }; @@ -52,13 +47,10 @@ export function mapSandboxIdentity( target.id = name; target.name = name; } - const runtime = runtimeState(source); if (source.status !== undefined && String(source.status) !== "") { target.providerState = String(source.status); - } else if (runtime !== undefined) { - target.providerState = runtime; } - const state = mapSandboxState(source.status, runtime); + const state = mapSandboxState(source.status); if (state !== undefined) target.state = state; const region = source.spec?.region; if (region !== undefined && region !== "") target.region = region; @@ -150,22 +142,12 @@ export function mapAllocatedResources( return { memoryMib: BigInt(Math.trunc(memory)) }; } -function runtimeState(source: BlaxelSandboxIdentity): string | undefined { - if (source.state !== undefined && String(source.state) !== "") - return String(source.state); - return undefined; -} - -/** Mirrors Go metadata.go: deployment status, then runtime standby. */ -function mapSandboxState( - status: unknown, - runtime: string | undefined, -): SandboxState | undefined { +/** Maps the public deployment status exposed by @blaxel/core. */ +function mapSandboxState(status: unknown): SandboxState | undefined { const normalized = status === undefined || String(status) === "" ? "" : String(status).toUpperCase(); - const runtimeNormalized = runtime?.toUpperCase(); switch (normalized) { case "UPLOADING": case "BUILDING": @@ -173,9 +155,7 @@ function mapSandboxState( case "BUILT": return SandboxState.Queued; case "DEPLOYED": - return runtimeNormalized === "STANDBY" - ? SandboxState.Paused - : SandboxState.Running; + return SandboxState.Running; case "DELETING": case "DEACTIVATING": return SandboxState.Deleting; @@ -192,13 +172,6 @@ function mapSandboxState( case "FAILED": return SandboxState.Error; default: - switch (runtimeNormalized) { - case "RUNNING": - return SandboxState.Running; - case "STANDBY": - return SandboxState.Paused; - default: - return undefined; - } + return undefined; } } diff --git a/sdks/typescript/providers/blaxel/src/native.ts b/sdks/typescript/providers/blaxel/src/native.ts index 59ddc0f5..23b23bf8 100644 --- a/sdks/typescript/providers/blaxel/src/native.ts +++ b/sdks/typescript/providers/blaxel/src/native.ts @@ -9,10 +9,12 @@ import { type SandboxCreateConfiguration, type SandboxFileSystem, } from "@blaxel/core"; -import { ErrorKind, FileFieldPath } from "@sandbox-kit/sandbox"; +import { ConfigFieldAuth, ErrorKind, FileFieldPath } from "@sandbox-kit/sandbox"; import type { BlaxelSandboxIdentity } from "#mappings/provider_mappings.js"; import { providerError } from "./errors.js"; +let configuredProcessConfig: string | undefined; + export interface BlaxelProcessResult { readonly pid?: string; readonly name?: string; @@ -67,10 +69,10 @@ export interface NativeBlaxel { export type NativeFactory = (config: NativeClientConfig) => NativeBlaxel; export function defaultNativeFactory(config: NativeClientConfig): NativeBlaxel { - settings.setConfig(config); + configureProcessSettings(config); return { configure(next: NativeClientConfig): void { - settings.setConfig(next); + configureProcessSettings(next); }, async create(params, options) { return projectHandle( @@ -91,19 +93,11 @@ export function defaultNativeFactory(config: NativeClientConfig): NativeBlaxel { }; } -/** - * `@blaxel/core` types `SandboxInstance.sandbox` private and has no `state` - * getter. Project a plain Kit handle (identity + wrapped fs/process) so - * mapping never mutates the native instance or reaches into its private wire - * model after projection. Wrap `fs` with `get` so Stat uses the same - * filesystem GET as Go. - */ +/** Projects public SDK properties and wraps filesystem/process helpers. */ export function projectHandle(instance: SandboxInstance): NativeSandboxHandle { - const state = readWireRuntimeState(instance); return { metadata: instance.metadata, status: instance.status, - ...(state !== undefined ? { state } : {}), spec: instance.spec, fs: wrapFilesystem(instance.fs), process: { @@ -178,18 +172,24 @@ function isFileMetadata(data: unknown): data is BlaxelFile { ); } -/** - * Runtime state lives on Blaxel's private wire model (`sandbox.state`). The - * published `SandboxInstance` typings expose status/spec getters but not state, - * so this is the single boundary that reads it for Kit's handle projection. - */ -type BlaxelWireRuntime = { - readonly sandbox?: { readonly state?: unknown }; -}; - -function readWireRuntimeState(instance: SandboxInstance): string | undefined { - const value = (instance as unknown as BlaxelWireRuntime).sandbox?.state; - if (value === undefined || value === null || value === "") return undefined; - const text = String(value); - return text === "" ? undefined : text; +function configureProcessSettings(config: NativeClientConfig): void { + const serialized = JSON.stringify( + Object.entries(config) + .filter(([, value]) => value !== undefined) + .sort(([left], [right]) => left.localeCompare(right)), + ); + if ( + configuredProcessConfig !== undefined && + configuredProcessConfig !== serialized + ) { + providerError({ + kind: ErrorKind.FailedPrecondition, + operation: "initialize", + field: ConfigFieldAuth, + message: + "a Blaxel TypeScript client with a different configuration already exists in this process", + }); + } + settings.setConfig(config); + configuredProcessConfig = serialized; } diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index 0b554ce3..e1f56359 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -56,8 +56,8 @@ console.log(sandbox.id); // same as create name; use with get / BL_SANDBOX_NAME ``` `timeout` is milliseconds. Prefer `await using`; otherwise call -`await client.close()` when finished. Use one Blaxel client (or one API key) -per process. +`await client.close()` when finished. Use one Blaxel configuration per process; +a client with different credentials or workspace is rejected. @@ -171,7 +171,8 @@ Only API key auth and workspace scope are supported. Full rules: [Configuration](/docs/configuration). - Use one Blaxel client (or one API key) per process. + Use one Blaxel configuration per process. A client with different credentials + or workspace is rejected. ## What works diff --git a/website/content/docs/configuration.mdx b/website/content/docs/configuration.mdx index 35109ac4..1a428ac6 100644 --- a/website/content/docs/configuration.mdx +++ b/website/content/docs/configuration.mdx @@ -350,8 +350,9 @@ client, err := sandbox.NewClient(modal.New(), config) | Region | `region` — required for create | | Endpoint | Unsupported | -In TypeScript, use one Blaxel client (or one API key) per process. A second -client with different credentials replaces the first. +In TypeScript, use one Blaxel configuration per process. A second client with +different credentials or workspace is rejected before it can replace the +process-wide SDK configuration. ```ts const workspace = process.env.BL_WORKSPACE?.trim(); @@ -541,7 +542,8 @@ instance, err := client.Create(ctx, &sandbox.CreateOptions{ and Submitted wait are not supported. - Use one Blaxel client (or one API key) per process. + Use one Blaxel configuration per process. A second client with different + credentials or workspace is rejected. diff --git a/website/content/docs/sdk/configuration.mdx b/website/content/docs/sdk/configuration.mdx index cb280930..7f23fb17 100644 --- a/website/content/docs/sdk/configuration.mdx +++ b/website/content/docs/sdk/configuration.mdx @@ -49,8 +49,9 @@ await using client = new Client({ Set client or create `timeout` for how long create may wait. `region` is - required for create. `endpoint` is not supported. Use one Blaxel client (or - one API key) per process. + required for create. `endpoint` is not supported. Use one Blaxel + configuration per process; a client with different credentials or workspace + is rejected. From 8d7377b0d25a53ff3abca4e31f37f2e520722fca Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Thu, 12 Nov 2026 19:46:46 +0530 Subject: [PATCH 54/60] fix: validate sandbox response identity --- sdks/go/sandbox/client.go | 2 +- sdks/go/sandbox/client_lifecycle.go | 2 +- sdks/go/sandbox/operation.go | 8 +- sdks/go/sandbox/sandbox.go | 15 ++- sdks/go/sandbox/tests/lifecycle_test.go | 98 +++++++++++++++++++ .../sandbox/src/client/client.test.ts | 56 +++++++++++ sdks/typescript/sandbox/src/client/client.ts | 20 +++- sdks/typescript/sandbox/src/client/sandbox.ts | 23 ++++- 8 files changed, 214 insertions(+), 10 deletions(-) diff --git a/sdks/go/sandbox/client.go b/sdks/go/sandbox/client.go index 55ba53cc..3d2bf780 100644 --- a/sdks/go/sandbox/client.go +++ b/sdks/go/sandbox/client.go @@ -224,7 +224,7 @@ func (c *Client) Create(ctx context.Context, request *CreateOptions) (instance * if err != nil { return nil, err } - instance, err = sandboxFromResponse(c.backend, response, "create") + instance, err = sandboxFromResponse(c.backend, response, "create", "") if err != nil { return nil, err } diff --git a/sdks/go/sandbox/client_lifecycle.go b/sdks/go/sandbox/client_lifecycle.go index aa3be366..be609c05 100644 --- a/sdks/go/sandbox/client_lifecycle.go +++ b/sdks/go/sandbox/client_lifecycle.go @@ -33,7 +33,7 @@ func (c *Client) resolveSandbox(ctx context.Context, id, operation string, invok if err != nil { return nil, sdk.WithErrorContext(err, provider, operation) } - instance, err := sandboxFromResponse(c.backend, &CreateResult{Sandbox: info}, operation) + instance, err := sandboxFromResponse(c.backend, &CreateResult{Sandbox: info}, operation, id) if err != nil { return nil, err } diff --git a/sdks/go/sandbox/operation.go b/sdks/go/sandbox/operation.go index 1f5e3c8b..0de8b551 100644 --- a/sdks/go/sandbox/operation.go +++ b/sdks/go/sandbox/operation.go @@ -74,12 +74,16 @@ func (s *Sandbox) replaceInfo(operation string, info *SandboxInfo) error { return NewError(&ErrorInfo{Kind: ErrorKindInvalidResponse, Provider: s.ProviderName(), Operation: operation, Field: InfoFieldId, Message: "sandbox-kit: provider returned no sandbox identity"}, nil) } provider := s.ProviderName() + if info.Id != s.currentID() { + return NewError(&ErrorInfo{Kind: ErrorKindInvalidResponse, Provider: provider, Operation: operation, Field: InfoFieldId, Message: "sandbox-kit: provider returned a different sandbox identity"}, nil) + } if info.Provider != "" && info.Provider != provider { return NewError(&ErrorInfo{Kind: ErrorKindInvalidResponse, Provider: provider, Operation: operation, Field: InfoFieldProvider, Message: "sandbox-kit: provider returned inconsistent sandbox metadata"}, nil) } - info.Provider = provider + stored := cloneSandboxInfo(info) + stored.Provider = provider s.mu.Lock() - s.info = cloneSandboxInfo(info) + s.info = stored s.mu.Unlock() return nil } diff --git a/sdks/go/sandbox/sandbox.go b/sdks/go/sandbox/sandbox.go index d687173e..51dcf3ac 100644 --- a/sdks/go/sandbox/sandbox.go +++ b/sdks/go/sandbox/sandbox.go @@ -25,9 +25,9 @@ type Sandbox struct { info *SandboxInfo } -// sandboxFromResponse checks that creation returned an identifier -// and consistent provider metadata. -func sandboxFromResponse(backend Backend, response *CreateResult, operation string) (*Sandbox, error) { +// sandboxFromResponse checks that a provider returned the expected identifier +// and consistent provider metadata. An empty expectedID accepts creation IDs. +func sandboxFromResponse(backend Backend, response *CreateResult, operation, expectedID string) (*Sandbox, error) { if response == nil || response.Sandbox == nil || response.Sandbox.Id == "" { return nil, NewError(&ErrorInfo{ Kind: ErrorKindInvalidResponse, @@ -37,6 +37,15 @@ func sandboxFromResponse(backend Backend, response *CreateResult, operation stri Message: "sandbox-kit: provider returned no sandbox identity", }, nil) } + if expectedID != "" && response.Sandbox.Id != expectedID { + return nil, NewError(&ErrorInfo{ + Kind: ErrorKindInvalidResponse, + Provider: backend.Name(), + Operation: operation, + Field: InfoFieldId, + Message: "sandbox-kit: provider returned a different sandbox identity", + }, nil) + } info := cloneSandboxInfo(response.Sandbox) name := backend.Name() if info.Provider != "" && info.Provider != name { diff --git a/sdks/go/sandbox/tests/lifecycle_test.go b/sdks/go/sandbox/tests/lifecycle_test.go index e946b6dd..fe580499 100644 --- a/sdks/go/sandbox/tests/lifecycle_test.go +++ b/sdks/go/sandbox/tests/lifecycle_test.go @@ -46,6 +46,15 @@ func (p *lifecycleProvider) NewClient(*sandbox.Config) (sandbox.Backend, error) return p.backend, nil } +type attachLifecycleBackend struct { + *lifecycleBackend + attach func(context.Context, string) (*sandbox.SandboxInfo, error) +} + +func (b *attachLifecycleBackend) AttachSandbox(ctx context.Context, id string) (*sandbox.SandboxInfo, error) { + return b.attach(ctx, id) +} + func TestLifecycleDispatchAndUnsupportedOperation(t *testing.T) { called := false backend := &lifecycleBackend{start: func(ctx context.Context, id string, options *sandbox.StartOptions) (*sandbox.SandboxInfo, error) { @@ -170,6 +179,95 @@ func TestGetAndRefreshOwnMetadata(t *testing.T) { } } +func TestGetAndAttachRejectInconsistentProviderResponses(t *testing.T) { + tests := []struct { + name string + operation string + field string + backend sandbox.Backend + invoke func(*sandbox.Client) error + }{ + { + name: "get returns a different identity", + operation: "get", + field: sandbox.InfoFieldId, + backend: &lifecycleBackend{get: func(context.Context, string) (*sandbox.SandboxInfo, error) { + return &sandbox.SandboxInfo{Id: "different"}, nil + }}, + invoke: func(client *sandbox.Client) error { + _, err := client.Get(context.Background(), "requested") + return err + }, + }, + { + name: "attach returns a different provider", + operation: "attach", + field: sandbox.InfoFieldProvider, + backend: &attachLifecycleBackend{ + lifecycleBackend: &lifecycleBackend{}, + attach: func(_ context.Context, id string) (*sandbox.SandboxInfo, error) { + return &sandbox.SandboxInfo{Id: id, Provider: "different"}, nil + }, + }, + invoke: func(client *sandbox.Client) error { + _, err := client.Attach(context.Background(), "requested") + return err + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + client, err := sandbox.NewClient(&lifecycleProvider{backend: test.backend}, &sandbox.Config{}) + if err != nil { + t.Fatal(err) + } + err = test.invoke(client) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidResponse || detail.Operation != test.operation || detail.Field != test.field { + t.Fatalf("unexpected error: %v", err) + } + }) + } +} + +func TestRefreshRejectsInconsistentMetadataWithoutUpdatingTheHandle(t *testing.T) { + tests := []struct { + name string + info *sandbox.SandboxInfo + field string + }{ + {name: "different identity", info: &sandbox.SandboxInfo{Id: "different"}, field: sandbox.InfoFieldId}, + {name: "different provider", info: &sandbox.SandboxInfo{Id: "created", Provider: "different"}, field: sandbox.InfoFieldProvider}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + backend := &lifecycleBackend{ + get: func(context.Context, string) (*sandbox.SandboxInfo, error) { + return test.info, nil + }, + } + client, err := sandbox.NewClient(&lifecycleProvider{backend: backend}, &sandbox.Config{}) + if err != nil { + t.Fatal(err) + } + instance, err := client.Create(context.Background(), nil) + if err != nil { + t.Fatal(err) + } + err = instance.Refresh(context.Background()) + var detail *sandbox.Error + if !errors.As(err, &detail) || detail.Kind != sandbox.ErrorKindInvalidResponse || detail.Operation != "refresh" || detail.Field != test.field { + t.Fatalf("unexpected error: %v", err) + } + if instance.ID() != "created" || instance.ProviderName() != "lifecycle" { + t.Fatalf("refresh changed handle metadata: %#v", instance.Info()) + } + }) + } +} + func TestLifecycleInheritsClientTimeout(t *testing.T) { backend := &lifecycleBackend{start: func(ctx context.Context, _ string, _ *sandbox.StartOptions) (*sandbox.SandboxInfo, error) { <-ctx.Done() diff --git a/sdks/typescript/sandbox/src/client/client.test.ts b/sdks/typescript/sandbox/src/client/client.test.ts index d44febc9..f2c5d6fd 100644 --- a/sdks/typescript/sandbox/src/client/client.test.ts +++ b/sdks/typescript/sandbox/src/client/client.test.ts @@ -5,6 +5,7 @@ import { ExecFieldWorkingDirectory, FileFieldLocalPath, FileFieldPath, + InfoFieldId, CreateFieldLifetimeIdleStopAfter, CreateFieldLifetimeIdleStopMode, CreateFieldNetwork, @@ -190,6 +191,61 @@ describe("Client cleanup", () => { }); }); +describe("Provider response validation", () => { + it("rejects lookup responses with a different identity or provider", async () => { + const attachBackend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + attach: async () => ({ id: "different", provider: "test" }), + }; + const attachClient = new Client({ + provider: { name: attachBackend.name, initialize: () => attachBackend }, + }); + await expect(attachClient.attach("requested")).rejects.toMatchObject({ + kind: ErrorKind.InvalidResponse, + operation: "attach", + field: InfoFieldId, + }); + + const getBackend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + refresh: async (id) => ({ id, provider: "different" }), + }; + const getClient = new Client({ + provider: { name: getBackend.name, initialize: () => getBackend }, + }); + await expect(getClient.get("requested")).rejects.toMatchObject({ + kind: ErrorKind.InvalidResponse, + operation: "get", + field: ConfigFieldProvider, + }); + }); + + it("preserves a handle when a lifecycle response changes its identity", async () => { + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + attach: async (id) => ({ id, provider: "test" }), + refresh: async () => ({ id: "different", provider: "test" }), + }; + const client = new Client({ + provider: { name: backend.name, initialize: () => backend }, + }); + const sandbox = await client.attach("requested"); + + await expect(sandbox.refresh()).rejects.toMatchObject({ + kind: ErrorKind.InvalidResponse, + operation: "refresh", + field: InfoFieldId, + }); + expect(sandbox.id).toBe("requested"); + }); +}); + describe("Shared request validation", () => { it("exposes validateCreateOptions for public CreateOptions", () => { expect(() => validateCreateOptions({})).not.toThrow(); diff --git a/sdks/typescript/sandbox/src/client/client.ts b/sdks/typescript/sandbox/src/client/client.ts index 4127035a..56c6e03b 100644 --- a/sdks/typescript/sandbox/src/client/client.ts +++ b/sdks/typescript/sandbox/src/client/client.ts @@ -140,9 +140,27 @@ export class Client implements AsyncDisposable { () => fetch(operationOptions(options, scoped?.signal)), scoped.signal, ); + if (!info?.id || info.id !== id) + throw sandboxError({ + kind: ErrorKind.InvalidResponse, + provider: this.providerName, + operation, + field: InfoFieldId, + message: + "sandbox-kit: provider returned a different sandbox identity", + }); + if (info.provider && info.provider !== this.providerName) + throw sandboxError({ + kind: ErrorKind.InvalidResponse, + provider: this.providerName, + operation, + field: ConfigFieldProvider, + message: + "sandbox-kit: response provider differs from selected provider", + }); return new Sandbox(sandboxConstructorToken, { backend: this.#backend, - info: { ...info, id, provider: this.providerName }, + info: { ...info, provider: this.providerName }, ...(this.#timeout === undefined ? {} : { timeout: this.#timeout }), }); } catch (error) { diff --git a/sdks/typescript/sandbox/src/client/sandbox.ts b/sdks/typescript/sandbox/src/client/sandbox.ts index a185f081..c6f75a1b 100644 --- a/sdks/typescript/sandbox/src/client/sandbox.ts +++ b/sdks/typescript/sandbox/src/client/sandbox.ts @@ -12,7 +12,8 @@ import { type StopOptions, type TerminateOptions, } from "#contracts/sandbox_data_contracts.js"; -import { unsupported, withContext } from "#client/errors.js"; +import { InfoFieldId, InfoFieldProvider } from "#contracts/field_paths.js"; +import { ErrorKind, sandboxError, unsupported, withContext } from "#client/errors.js"; import { awaitWithSignal, clone, @@ -135,6 +136,7 @@ export class Sandbox implements AsyncDisposable { const method = this.#backend.refresh; if (!method) unsupported(this.providerName, "refresh"); return this.#storeInfo( + "refresh", await method.call( this.#backend, this.id, @@ -292,6 +294,7 @@ export class Sandbox implements AsyncDisposable { async (signal) => { if (!method) unsupported(this.providerName, operation); return this.#storeInfo( + operation, await method.call( this.#backend, this.id, @@ -302,7 +305,23 @@ export class Sandbox implements AsyncDisposable { }, ); } - #storeInfo(info: SandboxInfo): SandboxInfo { + #storeInfo(operation: string, info: SandboxInfo): SandboxInfo { + if (!info?.id || info.id !== this.id) + throw sandboxError({ + kind: ErrorKind.InvalidResponse, + provider: this.providerName, + operation, + field: InfoFieldId, + message: "sandbox-kit: provider returned a different sandbox identity", + }); + if (info.provider && info.provider !== this.providerName) + throw sandboxError({ + kind: ErrorKind.InvalidResponse, + provider: this.providerName, + operation, + field: InfoFieldProvider, + message: "sandbox-kit: provider returned inconsistent sandbox metadata", + }); const stored = clone(info); stored.provider = this.providerName; this.#info = stored; From 4ad2449ee16d6c2a6fc2fa5ae64afcbfea2cb68d Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Fri, 13 Nov 2026 12:57:24 +0530 Subject: [PATCH 55/60] fix: harden client boundaries and credential serialization --- docs/code-generation.md | 7 +- proto/kit/sandbox/v1/client.proto | 14 +- .../contracts/kit/sandbox/v1/client.pb.go | 14 +- .../contracts/kit/sandbox/v1/client_json.go | 32 ++++ .../kit/sandbox/v1/client_json_test.go | 51 ++++++ sdks/go/sandbox/client.go | 2 +- sdks/go/sandbox/tests/client_test.go | 13 ++ .../sandbox/tests/validation_limits_test.go | 4 +- .../contracts/src/kit/sandbox/v1/client_pb.ts | 14 +- .../sandbox/src/client/client.test.ts | 158 ++++++++++++++++++ sdks/typescript/sandbox/src/client/client.ts | 5 +- sdks/typescript/sandbox/src/client/errors.ts | 6 +- .../sandbox/src/client/filesystem.ts | 2 +- sdks/typescript/sandbox/src/client/process.ts | 4 +- .../sandbox/src/client/validation.ts | 21 ++- 15 files changed, 310 insertions(+), 37 deletions(-) create mode 100644 sdks/go/internal/contracts/kit/sandbox/v1/client_json.go create mode 100644 sdks/go/internal/contracts/kit/sandbox/v1/client_json_test.go diff --git a/docs/code-generation.md b/docs/code-generation.md index a7084b46..4439a94b 100644 --- a/docs/code-generation.md +++ b/docs/code-generation.md @@ -121,8 +121,11 @@ field uses that field, such as `resources.cpu_limit_cores`. A constraint that names several fields uses their parent message. List indexes and oneof names are omitted. Serialization policy is separate from validation. Client representation metadata -marks credential fields `sensitive`, which excludes them from Go JSON output -without treating secrecy as a value-validity rule. +marks credential fields `sensitive`. Handwritten Go contract serialization +follows that inventory and excludes credentials from standard-library +`encoding/json` output without treating secrecy as a value-validity rule. Do +not use protobuf JSON serialization for client configuration because protobuf +JSON represents every schema field. | Constraint | Meaning | | ------------------- | --------------------------------------------- | diff --git a/proto/kit/sandbox/v1/client.proto b/proto/kit/sandbox/v1/client.proto index 14cb8bf2..f73722cd 100644 --- a/proto/kit/sandbox/v1/client.proto +++ b/proto/kit/sandbox/v1/client.proto @@ -54,7 +54,7 @@ message AuthConfig { } // APIKeyCredentials is an API-key credential. Daytona uses Key. message APIKeyCredentials { - // The secret API key. It is excluded from generated JSON. + // The secret API key. Go encoding/json output excludes it. string key = 1 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" @@ -63,13 +63,13 @@ message APIKeyCredentials { } // TokenPairCredentials is a token-id and token-secret pair. Modal uses both fields. message TokenPairCredentials { - // The token identifier. + // The token identifier. Go encoding/json output excludes it. string id = 1 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" expression: "this.trim() != ''" }]; - // The token secret. It is excluded from generated JSON. + // The token secret. Go encoding/json output excludes it. string secret = 2 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" @@ -78,7 +78,7 @@ message TokenPairCredentials { } // BearerTokenCredentials is a bearer or JWT credential. Daytona also requires an organization. message BearerTokenCredentials { - // The bearer token. It is excluded from generated JSON. + // The bearer token. Go encoding/json output excludes it. string token = 1 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" @@ -96,7 +96,7 @@ message OAuthCredentials { } ] }; - // The refresh token. It is excluded from generated JSON. + // The refresh token. Go encoding/json output excludes it. string refresh_token = 1 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" @@ -108,13 +108,13 @@ message OAuthCredentials { message: "must not be blank" expression: "this.trim() != ''" }]; - // One client secret. Do not set this together with JWTKey. + // One client secret. Do not set this together with JWTKey. Go encoding/json output excludes it. optional string client_secret = 3 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" expression: "this.trim() != ''" }]; - // One JWT signing key. Do not set this together with ClientSecret. + // One JWT signing key. Do not set this together with ClientSecret. Go encoding/json output excludes it. optional string jwt_key = 4 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" diff --git a/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go b/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go index f675adb2..571a7c1f 100644 --- a/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go +++ b/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go @@ -230,7 +230,7 @@ func (*AuthConfig_OauthRefresh) isAuthConfig_Credentials() {} // APIKeyCredentials is an API-key credential. Daytona uses Key. type APIKeyCredentials struct { state protoimpl.MessageState `protogen:"open.v1"` - // The secret API key. It is excluded from generated JSON. + // The secret API key. Go encoding/json output excludes it. Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -276,9 +276,9 @@ func (x *APIKeyCredentials) GetKey() string { // TokenPairCredentials is a token-id and token-secret pair. Modal uses both fields. type TokenPairCredentials struct { state protoimpl.MessageState `protogen:"open.v1"` - // The token identifier. + // The token identifier. Go encoding/json output excludes it. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - // The token secret. It is excluded from generated JSON. + // The token secret. Go encoding/json output excludes it. Secret string `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -331,7 +331,7 @@ func (x *TokenPairCredentials) GetSecret() string { // BearerTokenCredentials is a bearer or JWT credential. Daytona also requires an organization. type BearerTokenCredentials struct { state protoimpl.MessageState `protogen:"open.v1"` - // The bearer token. It is excluded from generated JSON. + // The bearer token. Go encoding/json output excludes it. Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -377,13 +377,13 @@ func (x *BearerTokenCredentials) GetToken() string { // OAuthCredentials is an OAuth refresh credential. Supply ClientID and exactly one of ClientSecret or JWTKey. type OAuthCredentials struct { state protoimpl.MessageState `protogen:"open.v1"` - // The refresh token. It is excluded from generated JSON. + // The refresh token. Go encoding/json output excludes it. RefreshToken string `protobuf:"bytes,1,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` // The OAuth client identifier. ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` - // One client secret. Do not set this together with JWTKey. + // One client secret. Do not set this together with JWTKey. Go encoding/json output excludes it. ClientSecret *string `protobuf:"bytes,3,opt,name=client_secret,json=clientSecret,proto3,oneof" json:"client_secret,omitempty"` - // One JWT signing key. Do not set this together with ClientSecret. + // One JWT signing key. Do not set this together with ClientSecret. Go encoding/json output excludes it. JwtKey *string `protobuf:"bytes,4,opt,name=jwt_key,json=jwtKey,proto3,oneof" json:"jwt_key,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache diff --git a/sdks/go/internal/contracts/kit/sandbox/v1/client_json.go b/sdks/go/internal/contracts/kit/sandbox/v1/client_json.go new file mode 100644 index 00000000..f04f2264 --- /dev/null +++ b/sdks/go/internal/contracts/kit/sandbox/v1/client_json.go @@ -0,0 +1,32 @@ +package sandboxv1 + +import "encoding/json" + +// MarshalJSON prevents the API key from entering ordinary Go JSON output. +func (*APIKeyCredentials) MarshalJSON() ([]byte, error) { + return []byte("{}"), nil +} + +// MarshalJSON prevents the token identifier and secret from entering ordinary +// Go JSON output. +func (*TokenPairCredentials) MarshalJSON() ([]byte, error) { + return []byte("{}"), nil +} + +// MarshalJSON prevents the bearer token from entering ordinary Go JSON output. +func (*BearerTokenCredentials) MarshalJSON() ([]byte, error) { + return []byte("{}"), nil +} + +// MarshalJSON retains the non-sensitive OAuth client identifier and prevents +// the refresh token, client secret, and JWT key from entering ordinary Go JSON +// output. +func (credentials *OAuthCredentials) MarshalJSON() ([]byte, error) { + type publicOAuthCredentials struct { + ClientID string `json:"client_id,omitempty"` + } + if credentials == nil { + return []byte("null"), nil + } + return json.Marshal(publicOAuthCredentials{ClientID: credentials.ClientId}) +} diff --git a/sdks/go/internal/contracts/kit/sandbox/v1/client_json_test.go b/sdks/go/internal/contracts/kit/sandbox/v1/client_json_test.go new file mode 100644 index 00000000..a5c65e74 --- /dev/null +++ b/sdks/go/internal/contracts/kit/sandbox/v1/client_json_test.go @@ -0,0 +1,51 @@ +package sandboxv1_test + +import ( + "encoding/json" + "strings" + "testing" + + sandboxv1 "github.com/sandbox-kit/kit/sdks/go/internal/contracts/kit/sandbox/v1" +) + +func TestCredentialJSONRedactsSensitiveFields(t *testing.T) { + tests := []struct { + name string + credential any + want string + }{ + {name: "api key", credential: &sandboxv1.APIKeyCredentials{Key: "api-key-secret"}, want: `{}`}, + {name: "token pair", credential: &sandboxv1.TokenPairCredentials{Id: "token-id-secret", Secret: "token-secret"}, want: `{}`}, + {name: "bearer token", credential: &sandboxv1.BearerTokenCredentials{Token: "bearer-secret"}, want: `{}`}, + {name: "OAuth", credential: &sandboxv1.OAuthCredentials{RefreshToken: "refresh-secret", ClientId: "public-client", ClientSecret: sandboxv1String("client-secret"), JwtKey: sandboxv1String("jwt-secret")}, want: `{"client_id":"public-client"}`}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + encoded, err := json.Marshal(test.credential) + if err != nil { + t.Fatal(err) + } + if string(encoded) != test.want { + t.Fatalf("JSON = %s, want %s", encoded, test.want) + } + for _, secret := range []string{"api-key-secret", "token-id-secret", "token-secret", "bearer-secret", "refresh-secret", "client-secret", "jwt-secret"} { + if strings.Contains(string(encoded), secret) { + t.Fatalf("JSON contains sensitive value %q", secret) + } + } + }) + } +} + +func TestConfigJSONUsesCredentialRedaction(t *testing.T) { + config := &sandboxv1.Config{Auth: &sandboxv1.AuthConfig{Credentials: &sandboxv1.AuthConfig_ApiKey{ApiKey: &sandboxv1.APIKeyCredentials{Key: "nested-secret"}}}} + encoded, err := json.Marshal(config) + if err != nil { + t.Fatal(err) + } + if strings.Contains(string(encoded), "nested-secret") { + t.Fatalf("config JSON contains credential: %s", encoded) + } +} + +func sandboxv1String(value string) *string { return &value } diff --git a/sdks/go/sandbox/client.go b/sdks/go/sandbox/client.go index 3d2bf780..09dcdb23 100644 --- a/sdks/go/sandbox/client.go +++ b/sdks/go/sandbox/client.go @@ -139,7 +139,7 @@ func secureEndpoint(value string) bool { return false } ip, err := netip.ParseAddr(u.Hostname()) - return u.Hostname() == "localhost" || (err == nil && ip.IsLoopback()) + return strings.EqualFold(u.Hostname(), "localhost") || (err == nil && ip.IsLoopback()) } // ProviderName returns "daytona", "modal", or "blaxel". diff --git a/sdks/go/sandbox/tests/client_test.go b/sdks/go/sandbox/tests/client_test.go index a81163b1..4f3d6d5c 100644 --- a/sdks/go/sandbox/tests/client_test.go +++ b/sdks/go/sandbox/tests/client_test.go @@ -2,8 +2,10 @@ package sandbox_test import ( "context" + "encoding/json" "errors" "reflect" + "strings" "testing" "time" @@ -133,6 +135,17 @@ func TestConfigContractsAreProtobufMessages(t *testing.T) { } } +func TestConfigJSONRedactsCredentials(t *testing.T) { + config := &sandbox.Config{Auth: &sandbox.AuthConfig{Credentials: &sandbox.TokenPair{TokenPair: &sandbox.TokenPairCredentials{Id: "sensitive-id", Secret: "sensitive-secret"}}}} + encoded, err := json.Marshal(config) + if err != nil { + t.Fatal(err) + } + if strings.Contains(string(encoded), "sensitive-id") || strings.Contains(string(encoded), "sensitive-secret") { + t.Fatalf("config JSON contains credentials: %s", encoded) + } +} + func TestProviderRequiresInitializationContract(t *testing.T) { // A name-only implementation no longer satisfies the initialization contract. typeNameOnly := reflect.TypeOf(nameOnly{}) diff --git a/sdks/go/sandbox/tests/validation_limits_test.go b/sdks/go/sandbox/tests/validation_limits_test.go index e8185f33..aff15d5e 100644 --- a/sdks/go/sandbox/tests/validation_limits_test.go +++ b/sdks/go/sandbox/tests/validation_limits_test.go @@ -13,8 +13,9 @@ func TestSecureEndpoints(t *testing.T) { endpoint string valid bool }{ - {"https://api.example.test/api", true}, {"http://127.0.0.1:1234", true}, {"http://[::1]:1234", true}, {"http://localhost:1234", true}, + {"https://api.example.test/api", true}, {"http://127.0.0.1:1234", true}, {"http://[::1]:1234", true}, {"http://[0:0:0:0:0:0:0:1]:1234", true}, {"http://localhost:1234", true}, {"http://LOCALHOST:1234", true}, {"http://api.example.test", false}, {"http://127.0.0.1.evil.test", false}, {"http://0.0.0.0:1234", false}, {"https://user:secret@api.example.test", false}, {"https://api.example.test/#fragment", false}, + {"http://@localhost", false}, {"http://127.1", false}, } { t.Run(tc.endpoint, func(t *testing.T) { err := sandbox.ValidateConfig(&sandbox.Config{Endpoint: sandbox.Value(tc.endpoint)}) @@ -46,4 +47,3 @@ func TestImageReferenceValidationBeforeBackend(t *testing.T) { }) } } - diff --git a/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts b/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts index 9a2d6094..b494d699 100644 --- a/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts +++ b/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts @@ -121,7 +121,7 @@ export const AuthConfigSchema: GenMessage = /*@__PURE__*/ */ export type APIKeyCredentials = Message<"kit.sandbox.v1.APIKeyCredentials"> & { /** - * The secret API key. It is excluded from generated JSON. + * The secret API key. Go encoding/json output excludes it. * * @generated from field: string key = 1; */ @@ -142,14 +142,14 @@ export const APIKeyCredentialsSchema: GenMessage = /*@__PURE_ */ export type TokenPairCredentials = Message<"kit.sandbox.v1.TokenPairCredentials"> & { /** - * The token identifier. + * The token identifier. Go encoding/json output excludes it. * * @generated from field: string id = 1; */ id: string; /** - * The token secret. It is excluded from generated JSON. + * The token secret. Go encoding/json output excludes it. * * @generated from field: string secret = 2; */ @@ -170,7 +170,7 @@ export const TokenPairCredentialsSchema: GenMessage = /*@_ */ export type BearerTokenCredentials = Message<"kit.sandbox.v1.BearerTokenCredentials"> & { /** - * The bearer token. It is excluded from generated JSON. + * The bearer token. Go encoding/json output excludes it. * * @generated from field: string token = 1; */ @@ -191,7 +191,7 @@ export const BearerTokenCredentialsSchema: GenMessage = */ export type OAuthCredentials = Message<"kit.sandbox.v1.OAuthCredentials"> & { /** - * The refresh token. It is excluded from generated JSON. + * The refresh token. Go encoding/json output excludes it. * * @generated from field: string refresh_token = 1; */ @@ -205,14 +205,14 @@ export type OAuthCredentials = Message<"kit.sandbox.v1.OAuthCredentials"> & { clientId: string; /** - * One client secret. Do not set this together with JWTKey. + * One client secret. Do not set this together with JWTKey. Go encoding/json output excludes it. * * @generated from field: optional string client_secret = 3; */ clientSecret?: string | undefined; /** - * One JWT signing key. Do not set this together with ClientSecret. + * One JWT signing key. Do not set this together with ClientSecret. Go encoding/json output excludes it. * * @generated from field: optional string jwt_key = 4; */ diff --git a/sdks/typescript/sandbox/src/client/client.test.ts b/sdks/typescript/sandbox/src/client/client.test.ts index f2c5d6fd..44f6142e 100644 --- a/sdks/typescript/sandbox/src/client/client.test.ts +++ b/sdks/typescript/sandbox/src/client/client.test.ts @@ -26,6 +26,8 @@ import { Client, EgressMode, ErrorKind, + SandboxError, + sandboxError, PolicyMode, validateCreateOptions, type Backend, @@ -192,6 +194,41 @@ describe("Client cleanup", () => { }); describe("Provider response validation", () => { + it("reports the public get operation and preserves the provider error", async () => { + const native = new Error("native failure"); + const providerError = sandboxError( + { + kind: ErrorKind.Unknown, + provider: "wrong-provider", + operation: "refresh", + field: "", + message: "lookup failed", + }, + native, + ); + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + refresh: async () => { + throw providerError; + }, + }; + const client = new Client({ + provider: { name: backend.name, initialize: () => backend }, + }); + + const error = await client.get("requested").catch((reason: unknown) => reason); + expect(error).toBeInstanceOf(SandboxError); + expect(error).toMatchObject({ + provider: "test", + operation: "get", + message: "lookup failed", + }); + expect((error as Error).cause).toBe(providerError); + expect(providerError.cause).toBe(native); + }); + it("rejects lookup responses with a different identity or provider", async () => { const attachBackend: Backend = { name: "test", @@ -382,6 +419,48 @@ describe("Shared request validation", () => { }); describe("Public create options for providers", () => { + it("keeps nested create input owned by the caller", async () => { + const request: CreateOptions = { + environment: { ORIGINAL: "value" }, + runtime: { entrypoint: ["echo"] }, + }; + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + create: async (providerRequest) => { + providerRequest.environment!.ORIGINAL = "mutated"; + providerRequest.runtime!.entrypoint!.push("mutated"); + return { sandbox: { id: "sandbox-1", provider: "test" } }; + }, + }; + const client = new Client({ + provider: { name: backend.name, initialize: () => backend }, + }); + + await client.create(request); + + expect(request).toEqual({ + environment: { ORIGINAL: "value" }, + runtime: { entrypoint: ["echo"] }, + }); + }); + + it("keeps nested client configuration owned by the caller", () => { + const auth = { apiKey: { key: "secret" } } as const; + const provider: Provider = { + name: "test", + initialize: (config) => { + config.auth!.apiKey!.key = "mutated"; + return new RecordingBackend(); + }, + }; + + new Client({ provider, auth }); + + expect(auth.apiKey.key).toBe("secret"); + }); + it("hands the provider public create options after a create call", async () => { const { client, backend } = fixture(); @@ -408,7 +487,86 @@ describe("Public create options for providers", () => { }); }); +describe("Provider input ownership", () => { + it("keeps process arrays and maps owned by the caller", async () => { + const argv = ["echo", "hello"]; + const environment = { ORIGINAL: "value" }; + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + attach: async (id) => ({ id, provider: "test" }), + run: async (_id, request) => { + request.argv!.push("mutated"); + request.environment!.ORIGINAL = "mutated"; + return { exitCode: 0 }; + }, + }; + const client = new Client({ + provider: { name: backend.name, initialize: () => backend }, + }); + const sandbox = await client.attach("sandbox-1"); + + await sandbox.process.run({ argv, environment }); + + expect(argv).toEqual(["echo", "hello"]); + expect(environment).toEqual({ ORIGINAL: "value" }); + }); + + it("keeps write buffers owned by the caller", async () => { + const data = new Uint8Array([1, 2, 3]); + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + attach: async (id) => ({ id, provider: "test" }), + writeFile: async (_id, _path, providerData) => { + providerData[0] = 9; + }, + }; + const client = new Client({ + provider: { name: backend.name, initialize: () => backend }, + }); + const sandbox = await client.attach("sandbox-1"); + + await sandbox.filesystem.writeFile("/tmp/file", data); + + expect(data).toEqual(new Uint8Array([1, 2, 3])); + }); +}); + describe("Shared client configuration validation", () => { + it.each([ + ["https://api.example.test/api", true], + ["http://127.0.0.1:1234", true], + ["http://[::1]:1234", true], + ["http://[0:0:0:0:0:0:0:1]:1234", true], + ["http://localhost:1234", true], + ["http://LOCALHOST:1234", true], + ["http://api.example.test", false], + ["http://127.0.0.1.evil.test", false], + ["http://0.0.0.0:1234", false], + ["https://user:secret@api.example.test", false], + ["https://api.example.test/#fragment", false], + ["http://@localhost", false], + ["http://127.1", false], + ])("validates secure endpoint %s as %s", (endpoint, valid) => { + const provider: Provider = { + name: "test", + initialize: () => new RecordingBackend(), + }; + const construct = () => new Client({ provider, endpoint }); + + if (valid) expect(construct).not.toThrow(); + else + expect(construct).toThrowError( + expect.objectContaining({ + kind: ErrorKind.InvalidArgument, + field: ConfigFieldEndpoint, + }), + ); + }); + it("rejects insecure remote endpoints before initializing the provider", () => { const backend = new RecordingBackend(); let initialized = false; diff --git a/sdks/typescript/sandbox/src/client/client.ts b/sdks/typescript/sandbox/src/client/client.ts index 56c6e03b..81c2b53b 100644 --- a/sdks/typescript/sandbox/src/client/client.ts +++ b/sdks/typescript/sandbox/src/client/client.ts @@ -19,6 +19,7 @@ import { awaitWithSignal, operationOptions, operationSignal, + clone, } from "#client/operation.js"; import { Sandbox, sandboxConstructorToken } from "#client/sandbox.js"; import type { Backend, ClientConfig, OperationOptions } from "#client/types.js"; @@ -69,7 +70,7 @@ export class Client implements AsyncDisposable { this.#timeout = config.timeout; try { const { provider: _provider, ...shared } = config; - this.#backend = config.provider.initialize(shared); + this.#backend = config.provider.initialize(clone(shared)); } catch (error) { throw withContext(error, config.provider.name, "initialize"); } @@ -188,7 +189,7 @@ export class Client implements AsyncDisposable { const create = this.#backend.create; if (!create) unsupported(this.providerName, "create"); const providerRequest: CreateOptions = { - ...request, + ...clone(request), ...(timeout === undefined ? {} : { diff --git a/sdks/typescript/sandbox/src/client/errors.ts b/sdks/typescript/sandbox/src/client/errors.ts index 6646041e..7a35c003 100644 --- a/sdks/typescript/sandbox/src/client/errors.ts +++ b/sdks/typescript/sandbox/src/client/errors.ts @@ -63,8 +63,8 @@ export function withContext( return sandboxError( { kind: error.kind as ErrorInfo["kind"], - provider: error.provider || provider, - operation: error.operation || operation, + provider: provider || error.provider, + operation: operation || error.operation, field: error.field, message: error.message, ...(error.rule === undefined ? {} : { rule: error.rule }), @@ -78,7 +78,7 @@ export function withContext( ? {} : { providerSource: error.providerSource }), }, - error.cause, + error, ); } if (error instanceof DOMException && error.name === "AbortError") diff --git a/sdks/typescript/sandbox/src/client/filesystem.ts b/sdks/typescript/sandbox/src/client/filesystem.ts index 075dc83b..22427865 100644 --- a/sdks/typescript/sandbox/src/client/filesystem.ts +++ b/sdks/typescript/sandbox/src/client/filesystem.ts @@ -69,7 +69,7 @@ export class Filesystem { this.host.backend, this.host.id, path, - data, + data.slice(), requestFields(options), operationOptions(options, signal), ); diff --git a/sdks/typescript/sandbox/src/client/process.ts b/sdks/typescript/sandbox/src/client/process.ts index 0388b245..62865088 100644 --- a/sdks/typescript/sandbox/src/client/process.ts +++ b/sdks/typescript/sandbox/src/client/process.ts @@ -105,13 +105,13 @@ function validateExecOptions( function requestFields(options: ProcessCall): ExecOptions { const request: ExecOptions = {}; - if (options.argv !== undefined) request.argv = options.argv; + if (options.argv !== undefined) request.argv = [...options.argv]; if (options.shell !== undefined) request.shell = options.shell; if (options.workingDirectory !== undefined) { request.workingDirectory = options.workingDirectory; } if (options.environment !== undefined) { - request.environment = options.environment; + request.environment = { ...options.environment }; } if (options.timeout !== undefined) request.timeout = options.timeout; return request; diff --git a/sdks/typescript/sandbox/src/client/validation.ts b/sdks/typescript/sandbox/src/client/validation.ts index 914b225d..de12616b 100644 --- a/sdks/typescript/sandbox/src/client/validation.ts +++ b/sdks/typescript/sandbox/src/client/validation.ts @@ -87,15 +87,30 @@ function validationError(input: ValidationErrorInput): never { function validSecureEndpoint(value: string): boolean { try { const url = new URL(value); - if (!url.hostname || url.username || url.password || url.hash) return false; + const authorityStart = value.indexOf("://"); + if (authorityStart < 0) return false; + const authority = value + .slice(authorityStart + 3) + .split(/[/?#]/, 1)[0]!; + if ( + !url.hostname || + authority.includes("@") || + url.username || + url.password || + url.hash + ) + return false; if (url.protocol === "https:") return true; if (url.protocol !== "http:") return false; const host = url.hostname.toLowerCase(); + const rawHost = authority.startsWith("[") + ? authority.slice(0, authority.indexOf("]") + 1).toLowerCase() + : authority.split(":", 1)[0]!.toLowerCase(); return ( - host === "localhost" || + (host === "localhost" && rawHost === host) || host === "[::1]" || host === "::1" || - /^127(?:\.[0-9]{1,3}){3}$/.test(host) + (/^127(?:\.[0-9]{1,3}){3}$/.test(host) && rawHost === host) ); } catch { return false; From b920d0c4d0406f4cb213a899f99cf5c01000e062 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Fri, 13 Nov 2026 13:07:54 +0530 Subject: [PATCH 56/60] fix: capture queued operation inputs --- docs/code-generation.md | 7 ++-- .../sandbox/src/client/client.test.ts | 42 ++++++++++++++++++- .../sandbox/src/client/filesystem.ts | 3 +- sdks/typescript/sandbox/src/client/process.ts | 10 +++-- 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/docs/code-generation.md b/docs/code-generation.md index 4439a94b..6d385ef6 100644 --- a/docs/code-generation.md +++ b/docs/code-generation.md @@ -123,9 +123,10 @@ are omitted. Serialization policy is separate from validation. Client representation metadata marks credential fields `sensitive`. Handwritten Go contract serialization follows that inventory and excludes credentials from standard-library -`encoding/json` output without treating secrecy as a value-validity rule. Do -not use protobuf JSON serialization for client configuration because protobuf -JSON represents every schema field. +`encoding/json` output when credentials are passed by pointer, including within +`*sandbox.Config`. Credential values passed directly to `json.Marshal` are not +redacted; do not serialize them by value. Protobuf JSON serialization also +represents every schema field and must not be used for client configuration. | Constraint | Meaning | | ------------------- | --------------------------------------------- | diff --git a/sdks/typescript/sandbox/src/client/client.test.ts b/sdks/typescript/sandbox/src/client/client.test.ts index 44f6142e..e26ac388 100644 --- a/sdks/typescript/sandbox/src/client/client.test.ts +++ b/sdks/typescript/sandbox/src/client/client.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "vitest"; +import { Buffer } from "node:buffer"; import { ExecFieldArgv, ExecFieldShell, @@ -514,7 +515,7 @@ describe("Provider input ownership", () => { }); it("keeps write buffers owned by the caller", async () => { - const data = new Uint8Array([1, 2, 3]); + const data = Buffer.from([1, 2, 3]); const backend: Backend = { name: "test", close: async () => {}, @@ -531,7 +532,44 @@ describe("Provider input ownership", () => { await sandbox.filesystem.writeFile("/tmp/file", data); - expect(data).toEqual(new Uint8Array([1, 2, 3])); + expect([...data]).toEqual([1, 2, 3]); + }); + + it("captures process and file inputs before queued operations run", async () => { + let release!: () => void; + const blocked = new Promise((resolve) => { release = resolve; }); + let observedArgv: string[] | undefined; + let observedEnvironment: Record | undefined; + let observedData: number[] | undefined; + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + attach: async (id) => ({ id, provider: "test" }), + start: async (id) => { await blocked; return { id, provider: "test" }; }, + run: async (_id, request) => { + observedArgv = request.argv; + observedEnvironment = request.environment; + return { exitCode: 0 }; + }, + writeFile: async (_id, _path, data) => { observedData = [...data]; }, + }; + const client = new Client({ provider: { name: "test", initialize: () => backend } }); + const sandbox = await client.attach("sandbox-1"); + const first = sandbox.start(); + const argv = ["echo", "hello"]; + const environment = { KEY: "original" }; + const data = Buffer.from([1, 2, 3]); + const command = sandbox.process.run({ argv, environment }); + const write = sandbox.filesystem.writeFile("/tmp/file", data); + argv.length = 0; + environment.KEY = "changed"; + data[0] = 9; + release(); + await Promise.all([first, command, write]); + expect(observedArgv).toEqual(["echo", "hello"]); + expect(observedEnvironment).toEqual({ KEY: "original" }); + expect(observedData).toEqual([1, 2, 3]); }); }); diff --git a/sdks/typescript/sandbox/src/client/filesystem.ts b/sdks/typescript/sandbox/src/client/filesystem.ts index 22427865..605ff1cc 100644 --- a/sdks/typescript/sandbox/src/client/filesystem.ts +++ b/sdks/typescript/sandbox/src/client/filesystem.ts @@ -62,6 +62,7 @@ export class Filesystem { data: Uint8Array, options: FileCall = {}, ): Promise { + const providerData = new Uint8Array(data); return this.#call("write_file", path, options, (signal) => { const method = this.host.backend.writeFile; if (!method) unsupported(this.host.providerName, "write_file"); @@ -69,7 +70,7 @@ export class Filesystem { this.host.backend, this.host.id, path, - data.slice(), + providerData, requestFields(options), operationOptions(options, signal), ); diff --git a/sdks/typescript/sandbox/src/client/process.ts b/sdks/typescript/sandbox/src/client/process.ts index 62865088..33a01bed 100644 --- a/sdks/typescript/sandbox/src/client/process.ts +++ b/sdks/typescript/sandbox/src/client/process.ts @@ -41,15 +41,17 @@ export class Process { * A non-zero exit code is not an error. */ async run(options: ProcessCall): Promise { - validateExecOptions(this.host.providerName, "run", options); - return this.host.run("run", options, (signal) => { + const request = requestFields(options); + const callOptions = operationOptions(options, options.signal); + validateExecOptions(this.host.providerName, "run", request); + return this.host.run("run", callOptions, (signal) => { const method = this.host.backend.run; if (!method) unsupported(this.host.providerName, "run"); return method.call( this.host.backend, this.host.id, - requestFields(options), - operationOptions(options, signal), + request, + operationOptions(callOptions, signal), ); }); } From 388ada9683186164b327067eb3f4c39079aadaaf Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Fri, 13 Nov 2026 13:16:07 +0530 Subject: [PATCH 57/60] fix: preserve filesystem options and long deadlines --- proto/kit/sandbox/v1/client.proto | 14 ++--- .../contracts/kit/sandbox/v1/client.pb.go | 14 ++--- .../contracts/src/kit/sandbox/v1/client_pb.ts | 14 ++--- .../sandbox/src/client/client.test.ts | 61 ++++++++++++++++++- sdks/typescript/sandbox/src/client/client.ts | 2 +- .../sandbox/src/client/filesystem.ts | 8 +++ .../sandbox/src/client/operation.ts | 28 ++++++--- 7 files changed, 108 insertions(+), 33 deletions(-) diff --git a/proto/kit/sandbox/v1/client.proto b/proto/kit/sandbox/v1/client.proto index f73722cd..3d0db28e 100644 --- a/proto/kit/sandbox/v1/client.proto +++ b/proto/kit/sandbox/v1/client.proto @@ -54,7 +54,7 @@ message AuthConfig { } // APIKeyCredentials is an API-key credential. Daytona uses Key. message APIKeyCredentials { - // The secret API key. Go encoding/json output excludes it. + // The secret API key. Go encoding/json output excludes it when marshaling a pointer. string key = 1 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" @@ -63,13 +63,13 @@ message APIKeyCredentials { } // TokenPairCredentials is a token-id and token-secret pair. Modal uses both fields. message TokenPairCredentials { - // The token identifier. Go encoding/json output excludes it. + // The token identifier. Go encoding/json output excludes it when marshaling a pointer. string id = 1 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" expression: "this.trim() != ''" }]; - // The token secret. Go encoding/json output excludes it. + // The token secret. Go encoding/json output excludes it when marshaling a pointer. string secret = 2 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" @@ -78,7 +78,7 @@ message TokenPairCredentials { } // BearerTokenCredentials is a bearer or JWT credential. Daytona also requires an organization. message BearerTokenCredentials { - // The bearer token. Go encoding/json output excludes it. + // The bearer token. Go encoding/json output excludes it when marshaling a pointer. string token = 1 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" @@ -96,7 +96,7 @@ message OAuthCredentials { } ] }; - // The refresh token. Go encoding/json output excludes it. + // The refresh token. Go encoding/json output excludes it when marshaling a pointer. string refresh_token = 1 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" @@ -108,13 +108,13 @@ message OAuthCredentials { message: "must not be blank" expression: "this.trim() != ''" }]; - // One client secret. Do not set this together with JWTKey. Go encoding/json output excludes it. + // One client secret. Do not set this together with JWTKey. Go encoding/json excludes it when marshaling a pointer. optional string client_secret = 3 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" expression: "this.trim() != ''" }]; - // One JWT signing key. Do not set this together with ClientSecret. Go encoding/json output excludes it. + // One JWT signing key. Do not set this together with ClientSecret. Go encoding/json excludes it when marshaling a pointer. optional string jwt_key = 4 [(buf.validate.field).cel = { id: "nonblank" message: "must not be blank" diff --git a/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go b/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go index 571a7c1f..1c4b9bc9 100644 --- a/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go +++ b/sdks/go/internal/contracts/kit/sandbox/v1/client.pb.go @@ -230,7 +230,7 @@ func (*AuthConfig_OauthRefresh) isAuthConfig_Credentials() {} // APIKeyCredentials is an API-key credential. Daytona uses Key. type APIKeyCredentials struct { state protoimpl.MessageState `protogen:"open.v1"` - // The secret API key. Go encoding/json output excludes it. + // The secret API key. Go encoding/json output excludes it when marshaling a pointer. Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -276,9 +276,9 @@ func (x *APIKeyCredentials) GetKey() string { // TokenPairCredentials is a token-id and token-secret pair. Modal uses both fields. type TokenPairCredentials struct { state protoimpl.MessageState `protogen:"open.v1"` - // The token identifier. Go encoding/json output excludes it. + // The token identifier. Go encoding/json output excludes it when marshaling a pointer. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - // The token secret. Go encoding/json output excludes it. + // The token secret. Go encoding/json output excludes it when marshaling a pointer. Secret string `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -331,7 +331,7 @@ func (x *TokenPairCredentials) GetSecret() string { // BearerTokenCredentials is a bearer or JWT credential. Daytona also requires an organization. type BearerTokenCredentials struct { state protoimpl.MessageState `protogen:"open.v1"` - // The bearer token. Go encoding/json output excludes it. + // The bearer token. Go encoding/json output excludes it when marshaling a pointer. Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -377,13 +377,13 @@ func (x *BearerTokenCredentials) GetToken() string { // OAuthCredentials is an OAuth refresh credential. Supply ClientID and exactly one of ClientSecret or JWTKey. type OAuthCredentials struct { state protoimpl.MessageState `protogen:"open.v1"` - // The refresh token. Go encoding/json output excludes it. + // The refresh token. Go encoding/json output excludes it when marshaling a pointer. RefreshToken string `protobuf:"bytes,1,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` // The OAuth client identifier. ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` - // One client secret. Do not set this together with JWTKey. Go encoding/json output excludes it. + // One client secret. Do not set this together with JWTKey. Go encoding/json excludes it when marshaling a pointer. ClientSecret *string `protobuf:"bytes,3,opt,name=client_secret,json=clientSecret,proto3,oneof" json:"client_secret,omitempty"` - // One JWT signing key. Do not set this together with ClientSecret. Go encoding/json output excludes it. + // One JWT signing key. Do not set this together with ClientSecret. Go encoding/json excludes it when marshaling a pointer. JwtKey *string `protobuf:"bytes,4,opt,name=jwt_key,json=jwtKey,proto3,oneof" json:"jwt_key,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache diff --git a/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts b/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts index b494d699..a0b1739e 100644 --- a/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts +++ b/sdks/typescript/contracts/src/kit/sandbox/v1/client_pb.ts @@ -121,7 +121,7 @@ export const AuthConfigSchema: GenMessage = /*@__PURE__*/ */ export type APIKeyCredentials = Message<"kit.sandbox.v1.APIKeyCredentials"> & { /** - * The secret API key. Go encoding/json output excludes it. + * The secret API key. Go encoding/json output excludes it when marshaling a pointer. * * @generated from field: string key = 1; */ @@ -142,14 +142,14 @@ export const APIKeyCredentialsSchema: GenMessage = /*@__PURE_ */ export type TokenPairCredentials = Message<"kit.sandbox.v1.TokenPairCredentials"> & { /** - * The token identifier. Go encoding/json output excludes it. + * The token identifier. Go encoding/json output excludes it when marshaling a pointer. * * @generated from field: string id = 1; */ id: string; /** - * The token secret. Go encoding/json output excludes it. + * The token secret. Go encoding/json output excludes it when marshaling a pointer. * * @generated from field: string secret = 2; */ @@ -170,7 +170,7 @@ export const TokenPairCredentialsSchema: GenMessage = /*@_ */ export type BearerTokenCredentials = Message<"kit.sandbox.v1.BearerTokenCredentials"> & { /** - * The bearer token. Go encoding/json output excludes it. + * The bearer token. Go encoding/json output excludes it when marshaling a pointer. * * @generated from field: string token = 1; */ @@ -191,7 +191,7 @@ export const BearerTokenCredentialsSchema: GenMessage = */ export type OAuthCredentials = Message<"kit.sandbox.v1.OAuthCredentials"> & { /** - * The refresh token. Go encoding/json output excludes it. + * The refresh token. Go encoding/json output excludes it when marshaling a pointer. * * @generated from field: string refresh_token = 1; */ @@ -205,14 +205,14 @@ export type OAuthCredentials = Message<"kit.sandbox.v1.OAuthCredentials"> & { clientId: string; /** - * One client secret. Do not set this together with JWTKey. Go encoding/json output excludes it. + * One client secret. Do not set this together with JWTKey. Go encoding/json excludes it when marshaling a pointer. * * @generated from field: optional string client_secret = 3; */ clientSecret?: string | undefined; /** - * One JWT signing key. Do not set this together with ClientSecret. Go encoding/json output excludes it. + * One JWT signing key. Do not set this together with ClientSecret. Go encoding/json excludes it when marshaling a pointer. * * @generated from field: optional string jwt_key = 4; */ diff --git a/sdks/typescript/sandbox/src/client/client.test.ts b/sdks/typescript/sandbox/src/client/client.test.ts index e26ac388..62476d01 100644 --- a/sdks/typescript/sandbox/src/client/client.test.ts +++ b/sdks/typescript/sandbox/src/client/client.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { Buffer } from "node:buffer"; import { ExecFieldArgv, @@ -195,6 +195,20 @@ describe("Client cleanup", () => { }); describe("Provider response validation", () => { + it.each([undefined, null])("rejects an absent create result: %s", async (result) => { + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + create: async () => result as unknown as CreateResult, + }; + const client = new Client({ provider: { name: "test", initialize: () => backend } }); + await expect(client.create()).rejects.toMatchObject({ + kind: ErrorKind.InvalidResponse, + operation: "create", + field: InfoFieldId, + }); + }); it("reports the public get operation and preserves the provider error", async () => { const native = new Error("native failure"); const providerError = sandboxError( @@ -489,6 +503,36 @@ describe("Public create options for providers", () => { }); describe("Provider input ownership", () => { + it("captures queued filesystem options before callers can change them", async () => { + let release!: () => void; + const blocked = new Promise((resolve) => { release = resolve; }); + let removed: { recursive?: boolean; timeout?: number } | undefined; + let directory: { parents?: boolean; timeout?: number } | undefined; + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + attach: async (id) => ({ id, provider: "test" }), + start: async (id) => { await blocked; return { id, provider: "test" }; }, + remove: async (_id, _path, request) => { removed = request; }, + makeDirectory: async (_id, _path, request) => { directory = request; }, + }; + const client = new Client({ provider: { name: "test", initialize: () => backend } }); + const sandbox = await client.attach("sandbox-1"); + const first = sandbox.start(); + const removeOptions = { recursive: false, timeout: 60_000 }; + const mkdirOptions = { parents: false, timeout: 60_000 }; + const remove = sandbox.filesystem.remove("/data", removeOptions); + const mkdir = sandbox.filesystem.makeDirectory("/directory", mkdirOptions); + removeOptions.recursive = true; + removeOptions.timeout = 1; + mkdirOptions.parents = true; + mkdirOptions.timeout = 1; + release(); + await Promise.all([first, remove, mkdir]); + expect(removed).toEqual({ recursive: false, timeout: 60_000 }); + expect(directory).toEqual({ parents: false, timeout: 60_000 }); + }); it("keeps process arrays and maps owned by the caller", async () => { const argv = ["echo", "hello"]; const environment = { ORIGINAL: "value" }; @@ -657,6 +701,21 @@ describe("Shared client configuration validation", () => { }); describe("Operation cancellation", () => { + it("keeps long timeouts active until the requested deadline", async () => { + vi.useFakeTimers(); + try { + const { client } = hangingFixture(); + const promise = client.create({}, { timeout: 2_147_483_648 }); + const assertion = expect(promise).rejects.toMatchObject({ kind: ErrorKind.Timeout }); + await vi.advanceTimersByTimeAsync(2_147_483_647); + expect(vi.getTimerCount()).toBe(1); + await vi.advanceTimersByTimeAsync(1); + await assertion; + expect(vi.getTimerCount()).toBe(0); + } finally { + vi.useRealTimers(); + } + }); it("does not invoke the provider for an already-aborted operation", async () => { const { client, backend } = fixture(); const controller = new AbortController(); diff --git a/sdks/typescript/sandbox/src/client/client.ts b/sdks/typescript/sandbox/src/client/client.ts index 81c2b53b..f97a68d4 100644 --- a/sdks/typescript/sandbox/src/client/client.ts +++ b/sdks/typescript/sandbox/src/client/client.ts @@ -211,7 +211,7 @@ export class Client implements AsyncDisposable { ), scoped.signal, ); - if (!result.sandbox?.id) + if (!result?.sandbox?.id) throw sandboxError({ kind: ErrorKind.InvalidResponse, provider: this.providerName, diff --git a/sdks/typescript/sandbox/src/client/filesystem.ts b/sdks/typescript/sandbox/src/client/filesystem.ts index 605ff1cc..fb75e59b 100644 --- a/sdks/typescript/sandbox/src/client/filesystem.ts +++ b/sdks/typescript/sandbox/src/client/filesystem.ts @@ -43,6 +43,7 @@ export class Filesystem { path: string, options: FileCall = {}, ): Promise { + options = { ...options }; return this.#call("read_file", path, options, (signal) => { const method = this.host.backend.readFile; if (!method) unsupported(this.host.providerName, "read_file"); @@ -62,6 +63,7 @@ export class Filesystem { data: Uint8Array, options: FileCall = {}, ): Promise { + options = { ...options }; const providerData = new Uint8Array(data); return this.#call("write_file", path, options, (signal) => { const method = this.host.backend.writeFile; @@ -83,6 +85,7 @@ export class Filesystem { remotePath: string, options: FileCall = {}, ): Promise { + options = { ...options }; return this.#copy( "copy_from_local", localPath, @@ -113,6 +116,7 @@ export class Filesystem { localPath: string, options: FileCall = {}, ): Promise { + options = { ...options }; return this.#copy( "copy_to_local", localPath, @@ -142,6 +146,7 @@ export class Filesystem { path: string, options: FileCall = {}, ): Promise { + options = { ...options }; return this.#call("make_directory", path, options, (signal) => { const method = this.host.backend.makeDirectory; if (!method) unsupported(this.host.providerName, "make_directory"); @@ -160,6 +165,7 @@ export class Filesystem { path: string, options: FileCall = {}, ): Promise { + options = { ...options }; return this.#call("list_directory", path, options, (signal) => { const method = this.host.backend.listDirectory; if (!method) unsupported(this.host.providerName, "list_directory"); @@ -175,6 +181,7 @@ export class Filesystem { /** Returns metadata for one path. A symlink result describes the symlink. */ stat(path: string, options: FileCall = {}): Promise { + options = { ...options }; return this.#call("stat", path, options, (signal) => { const method = this.host.backend.stat; if (!method) unsupported(this.host.providerName, "stat"); @@ -193,6 +200,7 @@ export class Filesystem { * An omitted `recursive` flag removes a file or an empty directory. */ remove(path: string, options: FileCall = {}): Promise { + options = { ...options }; return this.#call("remove", path, options, (signal) => { const method = this.host.backend.remove; if (!method) unsupported(this.host.providerName, "remove"); diff --git a/sdks/typescript/sandbox/src/client/operation.ts b/sdks/typescript/sandbox/src/client/operation.ts index 1185b01b..3d5abe86 100644 --- a/sdks/typescript/sandbox/src/client/operation.ts +++ b/sdks/typescript/sandbox/src/client/operation.ts @@ -28,16 +28,24 @@ export function operationSignal( const abort = () => controller.abort(options.signal?.reason); if (options.signal?.aborted) abort(); else options.signal?.addEventListener("abort", abort, { once: true }); - const timer = - timeout !== undefined && timeout > 0 - ? setTimeout( - () => - controller.abort( - new DOMException("The operation timed out", "TimeoutError"), - ), - timeout, - ) - : undefined; + let timer: ReturnType | undefined; + if (timeout !== undefined && timeout > 0) { + let remaining = timeout; + let started = performance.now(); + const schedule = () => { + timer = setTimeout(() => { + const now = performance.now(); + remaining -= now - started; + if (remaining <= 0) { + controller.abort(new DOMException("The operation timed out", "TimeoutError")); + } else { + started = now; + schedule(); + } + }, Math.min(remaining, 2_147_483_647)); + }; + schedule(); + } return { signal: controller.signal, dispose() { From 2f921d270f70b5fe85c9612d63738c48593e3dc7 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Fri, 13 Nov 2026 13:21:29 +0530 Subject: [PATCH 58/60] fix: handle cancellation and protect write buffers --- sdks/go/sandbox/filesystem.go | 6 ++- sdks/go/sandbox/tests/filesystem_test.go | 27 +++++++++++- .../sandbox/src/client/client.test.ts | 42 +++++++++++++++++++ .../sandbox/src/client/operation.ts | 17 +++++--- 4 files changed, 83 insertions(+), 9 deletions(-) diff --git a/sdks/go/sandbox/filesystem.go b/sdks/go/sandbox/filesystem.go index d18ec279..357bc672 100644 --- a/sdks/go/sandbox/filesystem.go +++ b/sdks/go/sandbox/filesystem.go @@ -45,12 +45,16 @@ func (f *Filesystem) ReadFile(ctx context.Context, path string, options *FileRea // WriteFile replaces the file at path with data and creates missing parent directories. // path is absolute. A nil context returns ErrorKindInvalidArgument. func (f *Filesystem) WriteFile(ctx context.Context, path string, data []byte, options *FileWriteOptions) error { + ownedData := append([]byte(nil), data...) + if data != nil && ownedData == nil { + ownedData = []byte{} + } return f.call(ctx, "write_file", options.GetTimeout(), path, nil, func(operationContext context.Context, id string) error { files, err := f.files("write_file") if err != nil { return err } - return files.WriteFile(operationContext, id, path, data, cloneMessage(options)) + return files.WriteFile(operationContext, id, path, ownedData, cloneMessage(options)) }) } diff --git a/sdks/go/sandbox/tests/filesystem_test.go b/sdks/go/sandbox/tests/filesystem_test.go index 9d7004d3..fdb146ec 100644 --- a/sdks/go/sandbox/tests/filesystem_test.go +++ b/sdks/go/sandbox/tests/filesystem_test.go @@ -9,7 +9,8 @@ import ( ) type fileBackend struct { - read func(context.Context, string, string, *sandbox.FileReadOptions) ([]byte, error) + read func(context.Context, string, string, *sandbox.FileReadOptions) ([]byte, error) + write func([]byte) } func (*fileBackend) Name() string { return "files" } @@ -23,7 +24,10 @@ func (*fileBackend) Create(context.Context, *sandbox.CreateOptions) (*sandbox.Cr func (b *fileBackend) ReadFile(ctx context.Context, id, path string, options *sandbox.FileReadOptions) ([]byte, error) { return b.read(ctx, id, path, options) } -func (*fileBackend) WriteFile(context.Context, string, string, []byte, *sandbox.FileWriteOptions) error { +func (b *fileBackend) WriteFile(_ context.Context, _, _ string, data []byte, _ *sandbox.FileWriteOptions) error { + if b.write != nil { + b.write(data) + } return nil } func (*fileBackend) CopyFromLocal(context.Context, string, string, string, *sandbox.FileCopyOptions) error { @@ -108,6 +112,25 @@ func TestFilesystemReadsAbsolutePath(t *testing.T) { } } +func TestFilesystemWriteOwnsData(t *testing.T) { + backend := &fileBackend{write: func(data []byte) { data[0] = 'X' }} + client, err := sandbox.NewClient(&fileProvider{backend: backend}, nil) + if err != nil { + t.Fatal(err) + } + instance, err := client.Create(context.Background(), nil) + if err != nil { + t.Fatal(err) + } + data := []byte("hello") + if err := instance.Filesystem().WriteFile(context.Background(), "/note", data, nil); err != nil { + t.Fatal(err) + } + if string(data) != "hello" { + t.Fatalf("caller data changed: %q", data) + } +} + func TestFilesystemCopyRequiresLocalPath(t *testing.T) { client, err := sandbox.NewClient(&fileProvider{backend: &fileBackend{}}, &sandbox.Config{}) if err != nil { diff --git a/sdks/typescript/sandbox/src/client/client.test.ts b/sdks/typescript/sandbox/src/client/client.test.ts index 62476d01..3fff8238 100644 --- a/sdks/typescript/sandbox/src/client/client.test.ts +++ b/sdks/typescript/sandbox/src/client/client.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it, vi } from "vitest"; import { Buffer } from "node:buffer"; +import { getEventListeners } from "node:events"; import { ExecFieldArgv, ExecFieldShell, @@ -701,6 +702,47 @@ describe("Shared client configuration validation", () => { }); describe("Operation cancellation", () => { + it("observes a provider rejection after a synchronous abort", async () => { + const controller = new AbortController(); + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + create: () => { + controller.abort(); + return Promise.reject(new Error("provider failure")); + }, + }; + const client = new Client({ provider: { name: "test", initialize: () => backend } }); + await expect(client.create({}, { signal: controller.signal })).rejects.toMatchObject({ + kind: ErrorKind.Canceled, + }); + // Give Node an event-loop turn to detect an unhandled provider rejection. + await new Promise((resolve) => setImmediate(resolve)); + }); + + it("removes its listener from the original lifecycle signal", async () => { + let release!: () => void; + const blocked = new Promise((resolve) => { release = resolve; }); + const backend: Backend = { + name: "test", + close: async () => {}, + closeSandbox: async () => {}, + attach: async (id) => ({ id, provider: "test" }), + refresh: async (id) => { await blocked; return { id, provider: "test" }; }, + }; + const client = new Client({ provider: { name: "test", initialize: () => backend } }); + const sandbox = await client.attach("sandbox-1"); + const first = new AbortController(); + const second = new AbortController(); + const options = { signal: first.signal }; + const pending = sandbox.refresh(options); + options.signal = second.signal; + release(); + await pending; + expect(getEventListeners(first.signal, "abort")).toHaveLength(0); + expect(getEventListeners(second.signal, "abort")).toHaveLength(0); + }); it("keeps long timeouts active until the requested deadline", async () => { vi.useFakeTimers(); try { diff --git a/sdks/typescript/sandbox/src/client/operation.ts b/sdks/typescript/sandbox/src/client/operation.ts index 3d5abe86..2cd5c09d 100644 --- a/sdks/typescript/sandbox/src/client/operation.ts +++ b/sdks/typescript/sandbox/src/client/operation.ts @@ -22,12 +22,13 @@ export function operationSignal( message: "sandbox-kit: timeout must be a finite non-negative number", }); } - if ((timeout === undefined || timeout === 0) && options.signal === undefined) + const parentSignal = options.signal; + if ((timeout === undefined || timeout === 0) && parentSignal === undefined) return { dispose() {} }; const controller = new AbortController(); - const abort = () => controller.abort(options.signal?.reason); - if (options.signal?.aborted) abort(); - else options.signal?.addEventListener("abort", abort, { once: true }); + const abort = () => controller.abort(parentSignal?.reason); + if (parentSignal?.aborted) abort(); + else parentSignal?.addEventListener("abort", abort, { once: true }); let timer: ReturnType | undefined; if (timeout !== undefined && timeout > 0) { let remaining = timeout; @@ -50,7 +51,7 @@ export function operationSignal( signal: controller.signal, dispose() { if (timer !== undefined) clearTimeout(timer); - options.signal?.removeEventListener("abort", abort); + parentSignal?.removeEventListener("abort", abort); }, }; } @@ -91,7 +92,11 @@ export async function awaitWithSignal( } function raceSignal(pending: Promise, signal?: AbortSignal): Promise { - if (signal?.aborted) throw abortReason(signal); + if (signal?.aborted) { + // The operation has already started; observe its eventual rejection. + void pending.catch(() => {}); + throw abortReason(signal); + } if (signal === undefined) return pending; return new Promise((resolve, reject) => { const aborted = () => reject(abortReason(signal)); From af788a5bc0a4e03ab45bd6a1a6b0ca3680a1c216 Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Fri, 13 Nov 2026 18:00:18 +0530 Subject: [PATCH 59/60] fix: correct Blaxel resource and error handling --- README.md | 2 +- docs/provider-verification.md | 2 +- docs/sandbox-creation.md | 2 +- sdks/go/providers/blaxel/README.md | 5 +- sdks/go/providers/blaxel/provider_mappings.go | 36 +++++++- sdks/go/providers/blaxel/sandbox_test.go | 14 ++- sdks/typescript/providers/blaxel/README.md | 4 +- .../providers/blaxel/src/blaxel.test.ts | 89 +++++++++++++++++-- .../typescript/providers/blaxel/src/blaxel.ts | 36 +++++++- .../typescript/providers/blaxel/src/errors.ts | 16 +++- .../blaxel/src/mappings/provider_mappings.ts | 37 +++++++- specs/providers/blaxel.yaml | 7 +- website/content/docs/blaxel.mdx | 3 +- 13 files changed, 226 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index aaf32889..79943467 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,7 @@ More detail: [responses and errors](docs/responses-and-errors.md). - Unsupported settings fail before provider calls; quotas stay provider-enforced. - Memory and disk are MiB. Modal CPU: fractional cores from 0.125 (0.001 steps). Daytona: whole cores and whole GiB expressed as MiB. Blaxel: set - `memoryMib` / `memory_mib` (Blaxel documents MB; Kit does not convert); CPU + `memoryMib` / `memory_mib` (Kit converts MiB to Blaxel's decimal MB); CPU follows memory. Guides: [configuration](docs/configuration.md) · diff --git a/docs/provider-verification.md b/docs/provider-verification.md index e52480f7..2a364d98 100644 --- a/docs/provider-verification.md +++ b/docs/provider-verification.md @@ -57,7 +57,7 @@ uses its asynchronous create/get and lifecycle methods. Blaxel create uses `SandboxInstance.create` (not create-if-not-exists); get/refresh look up by sandbox name; delete removes the resource. Handwritten mappings and error classification preserve the same portable semantics in both languages. -Blaxel memory is a 1:1 integer pass-through onto Blaxel's memory field; Kit +Blaxel memory converts Kit MiB to Blaxel's decimal MB field, rounding up; Kit rejects CPU/disk overrides and has no portable volume fields. TypeScript Blaxel credentials go through process-global `@blaxel/core` `settings`. diff --git a/docs/sandbox-creation.md b/docs/sandbox-creation.md index 88d1764d..7bca837e 100644 --- a/docs/sandbox-creation.md +++ b/docs/sandbox-creation.md @@ -52,7 +52,7 @@ selected provider does not support fails there too. | Resource | Modal | Daytona | Blaxel | | ---------------- | ----------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------- | | CPU | Physical cores; minimum 0.125, precision 0.001 | Whole CPU cores | Derived by Blaxel from memory; Kit rejects CPU overrides | -| Memory | MiB; minimum request 128 | Whole GiB expressed as MiB | `memory_mib` / `memoryMib` (Blaxel documents MB; Kit does not convert) | +| Memory | MiB; minimum request 128 | Whole GiB expressed as MiB | `memory_mib` / `memoryMib` (Kit converts to Blaxel decimal MB) | | Disk | Creation override unsupported | Whole GiB expressed as MiB | Creation override unsupported | | Maximum lifetime | Positive whole seconds, up to 24 hours (TS: ms) | Whole-minute TTL (TS: ms); zero disables TTL | At least 5 minutes when set | diff --git a/sdks/go/providers/blaxel/README.md b/sdks/go/providers/blaxel/README.md index 5d75396d..82d45e22 100644 --- a/sdks/go/providers/blaxel/README.md +++ b/sdks/go/providers/blaxel/README.md @@ -83,8 +83,9 @@ as that response arrives. TypeScript may keep waiting until the sandbox is ready or your create timeout ends. A Go timeout does not always mean the sandbox was never created — call `Get` by name before recreating. -Set `Resources.MemoryMib` for memory size. Blaxel documents that value as MB; -Kit does not convert units. CPU follows memory on Blaxel — do not set CPU or +Set `Resources.MemoryMib` for memory size. Kit converts MiB to Blaxel's decimal +MB value, rounding up so a request is never under-provisioned. CPU follows +memory on Blaxel — do not set CPU or disk overrides. If you omit memory, Blaxel applies its default. Volumes are not part of create. diff --git a/sdks/go/providers/blaxel/provider_mappings.go b/sdks/go/providers/blaxel/provider_mappings.go index 2bab58a6..b9a1d362 100644 --- a/sdks/go/providers/blaxel/provider_mappings.go +++ b/sdks/go/providers/blaxel/provider_mappings.go @@ -10,6 +10,13 @@ import ( "github.com/sandbox-kit/kit/sdks/go/sandbox" ) +const ( + // Blaxel measures memory in decimal MB while Kit exposes binary MiB. + // 1 MiB = 1.048576 MB = 131072 / 125000 MB. + blaxelMegabytesPerMebibyteNumerator uint64 = 131072 + blaxelMegabytesPerMebibyteDenominator uint64 = 125000 +) + // mapCommonCreateFields maps name, environment, and labels into the native // create request and returns the fields that were not mapped. func mapCommonCreateFields(source *sandbox.CreateOptions) (blaxel.SandboxNewParams, *sandbox.CreateOptions, error) { @@ -57,7 +64,15 @@ func mapResources(source *sandbox.Resources) (blaxel.SandboxRuntimeParam, *sandb "memory_mib cannot be represented", ) } - target.Memory = blaxel.Int(int64(value)) + memory, ok := mibToBlaxelMB(value) + if !ok { + return target, remaining, providerCheckError( + sandbox.ErrorKindInvalidArgument, + sandbox.CreateFieldResourcesMemoryMib, + "memory_mib cannot be represented", + ) + } + target.Memory = blaxel.Int(memory) } remaining.MemoryMib = nil return target, remaining, nil @@ -95,10 +110,27 @@ func mapAllocatedResources(source *blaxel.Sandbox) (*sandbox.Resources, error) { if source == nil || source.Spec.Runtime.Memory <= 0 { return target, nil } - target.MemoryMib = sandbox.Value(uint64(source.Spec.Runtime.Memory)) + target.MemoryMib = sandbox.Value(blaxelMBToMib(uint64(source.Spec.Runtime.Memory))) return target, nil } +// mibToBlaxelMB rounds up so a Kit request is never under-provisioned. +func mibToBlaxelMB(mib uint64) (int64, bool) { + if mib == 0 || mib > math.MaxInt64/blaxelMegabytesPerMebibyteNumerator { + return 0, false + } + mb := (mib*blaxelMegabytesPerMebibyteNumerator + blaxelMegabytesPerMebibyteDenominator - 1) / blaxelMegabytesPerMebibyteDenominator + if mb > math.MaxInt64 { + return 0, false + } + return int64(mb), true +} + +// blaxelMBToMib reports the whole MiB guaranteed by a native decimal-MB value. +func blaxelMBToMib(mb uint64) uint64 { + return mb * blaxelMegabytesPerMebibyteDenominator / blaxelMegabytesPerMebibyteNumerator +} + // formatBlaxelDuration formats a whole-second duration as a Blaxel TTL string. // minimum is the Blaxel floor for that policy (max-age TTL: 5m, idle TTL: 1m). // Rejection text matches TypeScript durationString. diff --git a/sdks/go/providers/blaxel/sandbox_test.go b/sdks/go/providers/blaxel/sandbox_test.go index 85ebeb5d..0c35c782 100644 --- a/sdks/go/providers/blaxel/sandbox_test.go +++ b/sdks/go/providers/blaxel/sandbox_test.go @@ -37,7 +37,7 @@ func TestPlanCreateMapsRequiredFields(t *testing.T) { if !plan.params.Sandbox.Spec.Region.Valid() || plan.params.Sandbox.Spec.Region.Value != "us-pdx-1" { t.Fatalf("region = %#v", plan.params.Sandbox.Spec.Region) } - if !plan.params.Sandbox.Spec.Runtime.Memory.Valid() || plan.params.Sandbox.Spec.Runtime.Memory.Value != 4096 { + if !plan.params.Sandbox.Spec.Runtime.Memory.Valid() || plan.params.Sandbox.Spec.Runtime.Memory.Value != 4295 { t.Fatalf("memory = %#v", plan.params.Sandbox.Spec.Runtime.Memory) } if !plan.params.Sandbox.Spec.Runtime.Ttl.Valid() || plan.params.Sandbox.Spec.Runtime.Ttl.Value != "30m" { @@ -55,6 +55,16 @@ func TestPlanCreateMapsRequiredFields(t *testing.T) { } } +func TestBlaxelMemoryUnitConversion(t *testing.T) { + mb, ok := mibToBlaxelMB(4096) + if !ok || mb != 4295 { + t.Fatalf("4096 MiB = (%d, %t) MB, want (4295, true)", mb, ok) + } + if mib := blaxelMBToMib(4295); mib != 4096 { + t.Fatalf("4295 MB = %d MiB, want 4096", mib) + } +} + func TestMapSandboxInfoUsesNameAsID(t *testing.T) { info, err := mapSandboxInfo(&blaxel.Sandbox{ Metadata: blaxel.Metadata{ @@ -87,7 +97,7 @@ func TestMapSandboxInfoUsesNameAsID(t *testing.T) { if err != nil { t.Fatal(err) } - if observed.State != sandbox.SandboxStateRunning || observed.Resources.GetMemoryMib() != 2048 { + if observed.State != sandbox.SandboxStateRunning || observed.Resources.GetMemoryMib() != 1953 { t.Fatalf("observed = %#v", observed) } // Blaxel keeps SandboxInfo to YAML mapSandboxInfo fields; no metadata bag or createdAt. diff --git a/sdks/typescript/providers/blaxel/README.md b/sdks/typescript/providers/blaxel/README.md index 3ec4a492..1768c3d6 100644 --- a/sdks/typescript/providers/blaxel/README.md +++ b/sdks/typescript/providers/blaxel/README.md @@ -68,7 +68,9 @@ idle-delete (≥1 minute). You cannot disable idle terminate. Runtime and isolation are not supported. `info().labels` is set only when Blaxel reports labels. If you omit memory, Blaxel applies its default. CPU and disk overrides are rejected — Blaxel derives CPU from memory. Set `memoryMib` for memory size; -Blaxel documents that value as MB and Kit does not convert units. +Kit converts `memoryMib` to Blaxel's decimal MB value, rounding up so a request +is never under-provisioned. Reported native MB values are converted back to +whole MiB. Set client or create `timeout` for how long create may wait. With a 1–50s timeout, a timed-out create frees the name. Ready/Started/Submitted wait is not diff --git a/sdks/typescript/providers/blaxel/src/blaxel.test.ts b/sdks/typescript/providers/blaxel/src/blaxel.test.ts index 2cc8b116..628d2472 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.test.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.test.ts @@ -164,7 +164,7 @@ describe("Blaxel mappings", () => { params: { name: "example", image: "blaxel/base-image:latest", - memory: 4096, + memory: 4295, region: "us-pdx-1", ttl: "1d", labels: { purpose: "test" }, @@ -190,8 +190,8 @@ describe("Blaxel mappings", () => { }, provider, ).params.memory, - ).toBe(128); - expect(mapResources({ memoryMib: 4096n })).toEqual({ memory: 4096 }); + ).toBe(135); + expect(mapResources({ memoryMib: 4096n })).toEqual({ memory: 4295 }); }); it("accepts kit create timeout as the client deadline", () => { @@ -573,7 +573,7 @@ describe("Blaxel mappings", () => { metadata: { name: "demo" }, spec: { runtime: { memory: 2048 } }, }), - ).toEqual({ memoryMib: 2048n }); + ).toEqual({ memoryMib: 1953n }); expect( mapSandboxInfo({ metadata: { name: "demo" }, @@ -582,7 +582,7 @@ describe("Blaxel mappings", () => { }), ).toMatchObject({ id: "demo", - resources: { memoryMib: 4096n }, + resources: { memoryMib: 3906n }, }); }); @@ -774,7 +774,7 @@ describe("Blaxel provider orchestration", () => { expect(created).toMatchObject({ name: "example", image: "blaxel/base-image:latest", - memory: 2048, + memory: 2148, region: "us-pdx-1", }); expect(createOptions).toEqual({ timeout: 30 }); @@ -805,6 +805,7 @@ describe("Blaxel provider orchestration", () => { })).initialize({ auth: { apiKey: { key: "bl_key" } }, scope: { workspace: "ws" }, + region: "us-pdx-1", }); if (backend.refresh === undefined) throw new Error("refresh is missing"); const info = await backend.refresh("existing", {}); @@ -994,6 +995,57 @@ describe("Blaxel provider orchestration", () => { expect(created).toBe(false); }); + it("deletes a sandbox created after the caller aborts", async () => { + const controller = new AbortController(); + let resolveCreate: ((value: { + metadata: { name: string }; + fs: never; + process: never; + }) => void) | undefined; + const created = new Promise<{ + metadata: { name: string }; + fs: never; + process: never; + }>((resolve) => { + resolveCreate = resolve; + }); + const deleted: string[] = []; + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + return created; + }, + async get() { + throw new Error("unexpected get"); + }, + async delete(name) { + deleted.push(name); + return { metadata: { name } }; + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + region: "us-pdx-1", + }); + if (backend.create === undefined) throw new Error("create is missing"); + + const pending = backend.create( + { + name: "example", + source: { image: { reference: "blaxel/base-image:latest" } }, + }, + { signal: controller.signal }, + ); + controller.abort(); + resolveCreate?.({ metadata: { name: "example" }, fs: {} as never, process: {} as never }); + + await expect(pending).rejects.toMatchObject({ + kind: ErrorKind.Canceled, + operation: "create", + }); + expect(deleted).toEqual(["example"]); + }); + it("rejects a missing or directory local file before Blaxel is called", async () => { let lookedUp = false; const backend = new BlaxelProvider(() => ({ @@ -1296,6 +1348,31 @@ describe("Blaxel provider orchestration", () => { }); describe("Blaxel filesystem.stat", () => { + it("does not label a missing sandbox as a missing filesystem path", async () => { + const backend = new BlaxelProvider(() => ({ + configure() {}, + async create() { + throw new Error("unexpected create"); + }, + async get() { + throw new ResponseError(new Response(null, { status: 404 }), {}, {}); + }, + async delete() { + throw new Error("unexpected delete"); + }, + })).initialize({ + auth: { apiKey: { key: "bl_key" } }, + scope: { workspace: "ws" }, + }); + await expect( + backend.readFile!("deleted-sandbox", "/app/readme.txt", {}, {}), + ).rejects.toMatchObject({ + kind: ErrorKind.NotFound, + operation: "read_file", + field: "", + }); + }); + it("returns directory metadata from get on the path", async () => { const backend = fsBackend({ async get(path) { diff --git a/sdks/typescript/providers/blaxel/src/blaxel.ts b/sdks/typescript/providers/blaxel/src/blaxel.ts index 32910ce5..4372e8f9 100644 --- a/sdks/typescript/providers/blaxel/src/blaxel.ts +++ b/sdks/typescript/providers/blaxel/src/blaxel.ts @@ -104,6 +104,12 @@ class BlaxelBackend extends BackendBase implements Backend { plan.params, timeoutSeconds === undefined ? undefined : { timeout: timeoutSeconds }, ); + await deleteCreatedSandboxAfterAbort( + this.native, + sandbox, + request.name, + options.signal, + ); return { sandbox: mapSandboxInfo(sandbox) }; } catch (error) { throw mapBlaxelError(error, "create"); @@ -318,10 +324,16 @@ class BlaxelBackend extends BackendBase implements Backend { } async #withSandbox(args: WithSandboxArgs): Promise { + let sandbox: NativeSandboxHandle; try { throwIfAborted(args.options.signal); - const sandbox = await this.native.get(args.id); + sandbox = await this.native.get(args.id); throwIfAborted(args.options.signal); + } catch (error) { + // A 404 here identifies the sandbox, not the requested filesystem path. + throw mapBlaxelError(error, args.operation, false); + } + try { return await args.body(sandbox); } catch (error) { throw mapBlaxelError(error, args.operation); @@ -368,6 +380,28 @@ function throwIfAborted(signal: AbortSignal | undefined): void { signal?.throwIfAborted(); } +// @blaxel/core create has no AbortSignal. If Kit's outer race has already +// returned cancellation, delete the eventually-created named sandbox so it +// does not become an orphaned resource. +async function deleteCreatedSandboxAfterAbort( + native: NativeBlaxel, + sandbox: NativeSandboxHandle, + requestedName: string | undefined, + signal: AbortSignal | undefined, +): Promise { + if (!signal?.aborted) return; + const name = sandbox.metadata?.name ?? requestedName; + if (name) { + try { + await native.delete(name); + } catch { + // Preserve the caller's cancellation; a cleanup failure cannot safely + // replace it with a successful create result. + } + } + throwIfAborted(signal); +} + function delay(ms: number, signal: AbortSignal | undefined): Promise { return new Promise((resolve, reject) => { if (signal?.aborted) { diff --git a/sdks/typescript/providers/blaxel/src/errors.ts b/sdks/typescript/providers/blaxel/src/errors.ts index 5ade66d1..230a59b2 100644 --- a/sdks/typescript/providers/blaxel/src/errors.ts +++ b/sdks/typescript/providers/blaxel/src/errors.ts @@ -41,6 +41,7 @@ interface MappedErrorInput { statusCode?: number; providerCode?: string; providerSource?: string; + filesystemNotFound?: boolean; } /** Inputs for throwing a Blaxel-scoped validation or mapping error. */ @@ -145,6 +146,7 @@ const STATUS_KINDS: Record = { export function mapBlaxelError( error: unknown, operation: string, + filesystemNotFound: boolean = FILESYSTEM_OPERATIONS.has(operation), ): SandboxError { if (error instanceof SandboxError) return error; if (error instanceof DOMException && error.name === "AbortError") @@ -164,6 +166,7 @@ export function mapBlaxelError( kind: classifyKind({ error, cause, providerCode, statusCode, operation }), error, operation, + filesystemNotFound, ...(statusCode === undefined ? {} : { statusCode }), ...(providerCode === undefined ? {} : { providerCode }), ...(providerSource === undefined ? {} : { providerSource }), @@ -279,10 +282,17 @@ function blaxelProviderSource(error: unknown): string | undefined { } function create(input: MappedErrorInput): SandboxError { - const { kind, error, operation, statusCode, providerCode, providerSource } = - input; + const { + kind, + error, + operation, + statusCode, + providerCode, + providerSource, + filesystemNotFound, + } = input; const field = - kind === ErrorKind.NotFound && FILESYSTEM_OPERATIONS.has(operation) + kind === ErrorKind.NotFound && filesystemNotFound ? FileFieldPath : ""; return sandboxError( diff --git a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts index d8e71459..1e96fddc 100644 --- a/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts +++ b/sdks/typescript/providers/blaxel/src/mappings/provider_mappings.ts @@ -22,6 +22,11 @@ import { providerError } from "#errors.js"; type Mutable = { -readonly [K in keyof T]: T[K] }; +// Blaxel uses decimal MB; Kit uses binary MiB. Keep this rational to avoid +// float rounding at the public boundary. +const BLAXEL_MB_PER_MIB_NUMERATOR = 131_072n; +const BLAXEL_MB_PER_MIB_DENOMINATOR = 125_000n; + /** Minimal native sandbox shape used for identity mapping. */ export interface BlaxelSandboxIdentity { readonly metadata?: { @@ -116,7 +121,15 @@ export function mapResources( message: "memory_mib cannot be represented", operation, }); - target.memory = Number(value); + const megabytes = mibToBlaxelMb(value); + if (megabytes > BigInt(Number.MAX_SAFE_INTEGER)) + providerError({ + kind: ErrorKind.InvalidArgument, + field: CreateFieldResourcesMemoryMib, + message: "memory_mib cannot be represented", + operation, + }); + target.memory = Number(megabytes); } return target; } @@ -131,7 +144,7 @@ export function mapAPIKeyCredentials( return target; } -/** Maps native Blaxel memory (MB) onto Kit `resources.memoryMib` 1:1. */ +/** Maps native decimal MB onto the whole binary MiB guaranteed by that value. */ export function mapAllocatedResources( source: BlaxelSandboxIdentity | undefined, ): Resources | undefined { @@ -139,7 +152,25 @@ export function mapAllocatedResources( if (memory === undefined || !Number.isFinite(memory) || memory <= 0) { return undefined; } - return { memoryMib: BigInt(Math.trunc(memory)) }; + return { memoryMib: blaxelMbToMib(BigInt(Math.trunc(memory))) }; +} + +/** Rounds up so a Kit request is never under-provisioned by Blaxel. */ +function mibToBlaxelMb(mib: bigint): bigint { + return ( + (mib * BLAXEL_MB_PER_MIB_NUMERATOR + + BLAXEL_MB_PER_MIB_DENOMINATOR - + 1n) / + BLAXEL_MB_PER_MIB_DENOMINATOR + ); +} + +/** Reports the whole MiB guaranteed by a native decimal-MB allocation. */ +function blaxelMbToMib(megabytes: bigint): bigint { + return ( + (megabytes * BLAXEL_MB_PER_MIB_DENOMINATOR) / + BLAXEL_MB_PER_MIB_NUMERATOR + ); } /** Maps the public deployment status exposed by @blaxel/core. */ diff --git a/specs/providers/blaxel.yaml b/specs/providers/blaxel.yaml index 01ff9a81..b4ebe9eb 100644 --- a/specs/providers/blaxel.yaml +++ b/specs/providers/blaxel.yaml @@ -210,7 +210,8 @@ groups: to: region, notes: "required for create; taken from captured client Config.region", } - # memory_mib → Blaxel memory is 1:1; Blaxel labels the unit MB; Kit does not convert. + # memory_mib → Blaxel decimal MB. Request mapping rounds up; response mapping + # reports the whole MiB guaranteed by the returned MB value. - name: mapResources direction: request source: { name: Resources } @@ -230,7 +231,7 @@ groups: from: memory_mib, to: memory, cast: integer, - notes: "reject 0; Go caps at MaxInt64, TypeScript at MAX_SAFE_INTEGER", + notes: "reject 0 and unrepresentable values; converts MiB to decimal MB by rounding up", } - name: mapLifetimeDuration direction: request @@ -363,4 +364,4 @@ groups: fields: { memory: spec.runtime.memory } target: { name: Resources } fields: - - { from: memory, to: memory_mib, cast: number } + - { from: memory, to: memory_mib, cast: number, notes: "decimal MB to whole guaranteed MiB" } diff --git a/website/content/docs/blaxel.mdx b/website/content/docs/blaxel.mdx index e1f56359..589afd96 100644 --- a/website/content/docs/blaxel.mdx +++ b/website/content/docs/blaxel.mdx @@ -189,7 +189,8 @@ Only API key auth and workspace scope are supported. Full rules: | Files | Shared filesystem methods — [Filesystem](/docs/filesystem) | | Process | `run` waits for the command — [Run a command](/docs/process) | -Pass a memory value (Blaxel treats it as MB), or omit it for Blaxel’s default. +Pass memory in MiB, or omit it for Blaxel’s default. Kit converts it to +Blaxel’s decimal MB field and rounds up so the request is not under-provisioned. CPU and disk overrides are not supported. Runtime and isolation are not supported. From 637b0b327b16f68ef79db60bf642b7b0cbb3ec4f Mon Sep 17 00:00:00 2001 From: Nabhag Motivaras <65061890+Nabhag8848@users.noreply.github.com> Date: Fri, 13 Nov 2026 18:10:58 +0530 Subject: [PATCH 60/60] fix: reject nil Blaxel port bindings --- sdks/go/providers/blaxel/creation.go | 10 +++++++--- sdks/go/providers/blaxel/rejection_test.go | 8 ++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/sdks/go/providers/blaxel/creation.go b/sdks/go/providers/blaxel/creation.go index 699766f2..ad17835b 100644 --- a/sdks/go/providers/blaxel/creation.go +++ b/sdks/go/providers/blaxel/creation.go @@ -203,8 +203,8 @@ func (c *creation) network() error { c.params.Sandbox.Spec.Network.AllowedDomains = append([]string(nil), config.OutboundDomains.Entries...) config.OutboundDomains = nil } - if len(config.Ports) > 0 { - ports, err := mapPorts(config.Ports) + if len(c.request.Network.Ports) > 0 { + ports, err := mapPorts(c.request.Network.Ports) if err != nil { return err } @@ -223,7 +223,11 @@ func mapPorts(bindings []*sandbox.PortBinding) ([]blaxel.PortParam, error) { ports := make([]blaxel.PortParam, 0, len(bindings)) for _, binding := range bindings { if binding == nil { - continue + return nil, providerCheckError( + sandbox.ErrorKindInvalidArgument, + sandbox.CreateFieldNetworkPorts, + "port binding is required", + ) } if isReservedPort(binding.Port) { return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldNetworkPortsPort, "ports 80, 443, and 8080 are reserved by Blaxel") diff --git a/sdks/go/providers/blaxel/rejection_test.go b/sdks/go/providers/blaxel/rejection_test.go index 3beff94b..5787501e 100644 --- a/sdks/go/providers/blaxel/rejection_test.go +++ b/sdks/go/providers/blaxel/rejection_test.go @@ -80,6 +80,14 @@ func TestRejectedCreateSettings(t *testing.T) { kind: sandbox.ErrorKindInvalidArgument, field: sandbox.CreateFieldNetworkOutboundDomainsEntries, }, + { + name: "nil port", + request: namedImage(&sandbox.CreateOptions{ + Network: &sandbox.NetworkConfig{Ports: []*sandbox.PortBinding{nil}}, + }), + kind: sandbox.ErrorKindInvalidArgument, + field: sandbox.CreateFieldNetworkPorts, + }, { name: "cpu cores", request: namedImage(&sandbox.CreateOptions{