diff --git a/.changeset/sandbox-environments.md b/.changeset/sandbox-environments.md
new file mode 100644
index 000000000..52f0ac82b
--- /dev/null
+++ b/.changeset/sandbox-environments.md
@@ -0,0 +1,6 @@
+---
+"@truefoundry/trueforge": minor
+"@truefoundry/trueforge-core": minor
+---
+
+Add owned sandbox environments (CRUD + AgentSpec.environment) gated by tenant CREATE permission.
diff --git a/.github/fern/openapi/openapi.json b/.github/fern/openapi/openapi.json
index b40934b3f..502d645cf 100644
--- a/.github/fern/openapi/openapi.json
+++ b/.github/fern/openapi/openapi.json
@@ -1053,6 +1053,27 @@
],
"type": "object"
},
+ "CreateSandboxEnvironmentRequest": {
+ "additionalProperties": false,
+ "properties": {
+ "description": {
+ "description": "Optional human-readable description.",
+ "maxLength": 1024,
+ "type": "string"
+ },
+ "manifest": {
+ "$ref": "#/components/schemas/SandboxEnvironmentManifest"
+ },
+ "name": {
+ "$ref": "#/components/schemas/ResourceName"
+ }
+ },
+ "required": [
+ "name",
+ "manifest"
+ ],
+ "type": "object"
+ },
"CreateScheduleRequest": {
"additionalProperties": false,
"properties": {
@@ -1226,6 +1247,147 @@
],
"type": "object"
},
+ "DaytonaDockerImage": {
+ "additionalProperties": false,
+ "properties": {
+ "ref": {
+ "description": "Container image reference passed to Daytona create-from-image.",
+ "minLength": 1,
+ "type": "string"
+ },
+ "type": {
+ "description": "Build/create from a container image reference.",
+ "enum": [
+ "docker"
+ ],
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "ref"
+ ],
+ "type": "object"
+ },
+ "DaytonaGpuType": {
+ "description": "Preferred Daytona GPU type.",
+ "enum": [
+ "H100",
+ "H200",
+ "RTX-PRO-6000",
+ "RTX-4090",
+ "RTX-5090"
+ ],
+ "type": "string"
+ },
+ "DaytonaSandboxEnvironmentImage": {
+ "discriminator": {
+ "mapping": {
+ "docker": "#/components/schemas/DaytonaDockerImage",
+ "snapshot": "#/components/schemas/DaytonaSnapshotImage",
+ "trueforge-default": "#/components/schemas/DaytonaTrueforgeDefaultImage"
+ },
+ "propertyName": "type"
+ },
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/DaytonaTrueforgeDefaultImage"
+ },
+ {
+ "$ref": "#/components/schemas/DaytonaSnapshotImage"
+ },
+ {
+ "$ref": "#/components/schemas/DaytonaDockerImage"
+ }
+ ]
+ },
+ "DaytonaSandboxEnvironmentLifecycle": {
+ "additionalProperties": false,
+ "properties": {
+ "auto_archive_interval_in_minutes": {
+ "description": "Minutes before Daytona auto-archives the sandbox (0 disables).",
+ "minimum": 0,
+ "type": "integer"
+ },
+ "auto_delete_interval_in_minutes": {
+ "description": "Minutes before Daytona auto-deletes the sandbox (0 disables).",
+ "minimum": 0,
+ "type": "integer"
+ },
+ "auto_stop_interval_in_minutes": {
+ "description": "Minutes of idle time before Daytona auto-stops the sandbox (0 disables).",
+ "minimum": 0,
+ "type": "integer"
+ }
+ },
+ "type": "object"
+ },
+ "DaytonaSandboxEnvironmentNetworking": {
+ "additionalProperties": false,
+ "properties": {
+ "domain_allow_list": {
+ "description": "Comma-separated allowed domains.",
+ "minLength": 1,
+ "type": "string"
+ },
+ "network_allow_list": {
+ "description": "Comma-separated allowed CIDR network addresses.",
+ "minLength": 1,
+ "type": "string"
+ },
+ "network_block_all": {
+ "description": "Block all outbound network access.",
+ "type": "boolean"
+ },
+ "outbound_proxy_url": {
+ "description": "Outbound HTTP(S) proxy URL.",
+ "minLength": 1,
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "DaytonaSandboxEnvironmentResources": {
+ "additionalProperties": false,
+ "properties": {
+ "cpu": {
+ "description": "CPU allocation in cores.",
+ "exclusiveMinimum": 0,
+ "type": "number"
+ },
+ "disk": {
+ "description": "Disk allocation in GiB.",
+ "exclusiveMinimum": 0,
+ "type": "number"
+ },
+ "gpu": {
+ "description": "GPU allocation in Daytona GPU units.",
+ "minimum": 0,
+ "type": "number"
+ },
+ "gpu_type": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/DaytonaGpuType"
+ },
+ {
+ "items": {
+ "$ref": "#/components/schemas/DaytonaGpuType"
+ },
+ "minItems": 1,
+ "type": "array"
+ }
+ ],
+ "description": "Preferred GPU type, or an ordered fallback list."
+ },
+ "memory": {
+ "description": "Memory allocation in GiB.",
+ "exclusiveMinimum": 0,
+ "type": "number"
+ }
+ },
+ "type": "object"
+ },
"DaytonaSandboxProviderAuth": {
"additionalProperties": false,
"description": "Daytona authentication credentials.",
@@ -1241,10 +1403,52 @@
],
"type": "object"
},
+ "DaytonaSnapshotImage": {
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "description": "Daytona snapshot name.",
+ "minLength": 1,
+ "type": "string"
+ },
+ "type": {
+ "description": "Clone an existing Daytona snapshot by name.",
+ "enum": [
+ "snapshot"
+ ],
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "name"
+ ],
+ "type": "object"
+ },
+ "DaytonaTrueforgeDefaultImage": {
+ "additionalProperties": false,
+ "properties": {
+ "type": {
+ "description": "Use the tenant provider TrueForge release snapshot.",
+ "enum": [
+ "trueforge-default"
+ ],
+ "type": "string"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "type": "object"
+ },
"DeleteAgentResponse": {
"properties": {},
"type": "object"
},
+ "DeleteSandboxEnvironmentResponse": {
+ "properties": {},
+ "type": "object"
+ },
"DeleteScheduleResponse": {
"properties": {},
"type": "object"
@@ -1528,6 +1732,17 @@
],
"type": "object"
},
+ "GetSandboxEnvironmentResponse": {
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/SandboxEnvironment"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "type": "object"
+ },
"GetSandboxProviderCatalogResponse": {
"properties": {
"data": {
@@ -2277,7 +2492,7 @@
},
"type": "array"
},
- "description": "For agent/schedule/session: keyed by resource id. For tenant: keyed by entity kind (e.g. `agent` → `CREATE`).",
+ "description": "For agent/schedule/session: keyed by resource id. For tenant: keyed by entity kind (e.g. `agent` or `sandbox-environment` → `CREATE`).",
"type": "object"
},
"type": {
@@ -2322,6 +2537,24 @@
],
"type": "object"
},
+ "ListSandboxEnvironmentsResponse": {
+ "properties": {
+ "data": {
+ "items": {
+ "$ref": "#/components/schemas/SandboxEnvironment"
+ },
+ "type": "array"
+ },
+ "pagination": {
+ "$ref": "#/components/schemas/TokenPagination"
+ }
+ },
+ "required": [
+ "data",
+ "pagination"
+ ],
+ "type": "object"
+ },
"ListScheduleRunsResponse": {
"properties": {
"data": {
@@ -3658,6 +3891,11 @@
"description": "Give the agent a sandbox. Required for skills and Code Mode.",
"type": "boolean"
},
+ "environment": {
+ "description": "Caller-owned sandbox environment name. Omit to use the tenant provider defaults.",
+ "minLength": 1,
+ "type": "string"
+ },
"file_downloads": {
"default": true,
"description": "Allow downloading agent-produced files via the turn download endpoint. Default: true.",
@@ -3707,6 +3945,97 @@
],
"type": "object"
},
+ "SandboxEnvironment": {
+ "additionalProperties": false,
+ "properties": {
+ "created_at": {
+ "description": "ISO-8601 create time.",
+ "format": "date-time",
+ "type": "string"
+ },
+ "created_by_subject": {
+ "$ref": "#/components/schemas/CreatedBySubject"
+ },
+ "description": {
+ "description": "Optional human-readable description.",
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "id": {
+ "description": "Immutable server-generated environment identifier.",
+ "minLength": 1,
+ "type": "string"
+ },
+ "manifest": {
+ "$ref": "#/components/schemas/SandboxEnvironmentManifest"
+ },
+ "name": {
+ "$ref": "#/components/schemas/ResourceName"
+ },
+ "updated_at": {
+ "description": "ISO-8601 last update time.",
+ "format": "date-time",
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "description",
+ "manifest",
+ "created_by_subject",
+ "created_at",
+ "updated_at"
+ ],
+ "type": "object"
+ },
+ "SandboxEnvironmentManifest": {
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "$ref": "#/components/schemas/DaytonaSandboxEnvironmentImage"
+ },
+ "lifecycle": {
+ "$ref": "#/components/schemas/DaytonaSandboxEnvironmentLifecycle"
+ },
+ "networking": {
+ "$ref": "#/components/schemas/DaytonaSandboxEnvironmentNetworking"
+ },
+ "provider": {
+ "description": "Must match the configured sandbox provider name/type.",
+ "enum": [
+ "daytona"
+ ],
+ "type": "string"
+ },
+ "resources": {
+ "$ref": "#/components/schemas/DaytonaSandboxEnvironmentResources"
+ },
+ "secrets": {
+ "additionalProperties": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "description": "Map of sandbox env var name to an existing Daytona organization secret name.",
+ "type": "object"
+ },
+ "type": {
+ "description": "Daytona sandbox environment.",
+ "enum": [
+ "daytona"
+ ],
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "provider",
+ "image"
+ ],
+ "type": "object"
+ },
"SandboxProviderManifest": {
"additionalProperties": false,
"properties": {
@@ -5581,15 +5910,35 @@
],
"type": "object"
},
- "UpdateSandboxProviderRequest": {
+ "UpdateSandboxEnvironmentRequest": {
"additionalProperties": false,
"properties": {
- "manifest": {
- "$ref": "#/components/schemas/SandboxProviderManifest"
- }
- },
- "required": [
- "manifest"
+ "description": {
+ "description": "Optional human-readable description.",
+ "maxLength": 1024,
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "manifest": {
+ "$ref": "#/components/schemas/SandboxEnvironmentManifest"
+ }
+ },
+ "required": [
+ "manifest"
+ ],
+ "type": "object"
+ },
+ "UpdateSandboxProviderRequest": {
+ "additionalProperties": false,
+ "properties": {
+ "manifest": {
+ "$ref": "#/components/schemas/SandboxProviderManifest"
+ }
+ },
+ "required": [
+ "manifest"
],
"type": "object"
},
@@ -7537,6 +7886,367 @@
"x-fern-sdk-method-name": "list"
}
},
+ "/api/v1/sandbox-environments": {
+ "get": {
+ "description": "List sandbox environments created by the authenticated subject, newest first.",
+ "parameters": [
+ {
+ "description": "Page size. Defaults to 25",
+ "in": "query",
+ "name": "limit",
+ "required": false,
+ "schema": {
+ "default": 25,
+ "description": "Page size. Defaults to 25",
+ "maximum": 25,
+ "minimum": 1,
+ "type": "integer"
+ }
+ },
+ {
+ "description": "Opaque token from a previous response `next_page_token`.",
+ "in": "query",
+ "name": "page_token",
+ "required": false,
+ "schema": {
+ "description": "Opaque token from a previous response `next_page_token`.",
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ListSandboxEnvironmentsResponse"
+ }
+ }
+ },
+ "description": "Paginated caller-owned sandbox environments."
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Invalid query parameters or page token."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Unauthenticated."
+ }
+ },
+ "summary": "List sandbox environments",
+ "tags": [
+ "Sandboxes"
+ ],
+ "x-fern-pagination": {
+ "cursor": "$request.page_token",
+ "next_cursor": "$response.pagination.next_page_token",
+ "results": "$response.data"
+ },
+ "x-fern-sdk-group-name": [
+ "sandbox_environments"
+ ],
+ "x-fern-sdk-method-name": "list"
+ },
+ "post": {
+ "description": "Create a Daytona sandbox environment owned by the authenticated subject.",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateSandboxEnvironmentRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GetSandboxEnvironmentResponse"
+ }
+ }
+ },
+ "description": "Created sandbox environment."
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Invalid request body."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Unauthenticated."
+ },
+ "409": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Name already exists in the tenant."
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Provider, snapshot, or secret validation failed."
+ }
+ },
+ "summary": "Create a sandbox environment",
+ "tags": [
+ "Sandboxes"
+ ],
+ "x-fern-sdk-group-name": [
+ "sandbox_environments"
+ ],
+ "x-fern-sdk-method-name": "create"
+ }
+ },
+ "/api/v1/sandbox-environments/{sandbox_environment_id}": {
+ "delete": {
+ "description": "Delete a caller-owned sandbox environment. Fails if any agent references it.",
+ "parameters": [
+ {
+ "description": "Immutable sandbox environment identifier.",
+ "in": "path",
+ "name": "sandbox_environment_id",
+ "required": true,
+ "schema": {
+ "description": "Immutable sandbox environment identifier.",
+ "maxLength": 64,
+ "minLength": 1,
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DeleteSandboxEnvironmentResponse"
+ }
+ }
+ },
+ "description": "Deleted."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Unauthenticated."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Not found or you do not have access."
+ },
+ "409": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Environment is referenced by one or more agents."
+ }
+ },
+ "summary": "Delete a sandbox environment",
+ "tags": [
+ "Sandboxes"
+ ],
+ "x-fern-sdk-group-name": [
+ "sandbox_environments"
+ ],
+ "x-fern-sdk-method-name": "delete"
+ },
+ "get": {
+ "description": "Get a sandbox environment owned by the authenticated subject.",
+ "parameters": [
+ {
+ "description": "Immutable sandbox environment identifier.",
+ "in": "path",
+ "name": "sandbox_environment_id",
+ "required": true,
+ "schema": {
+ "description": "Immutable sandbox environment identifier.",
+ "maxLength": 64,
+ "minLength": 1,
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GetSandboxEnvironmentResponse"
+ }
+ }
+ },
+ "description": "The sandbox environment."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Unauthenticated."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Not found or you do not have access."
+ }
+ },
+ "summary": "Get a sandbox environment",
+ "tags": [
+ "Sandboxes"
+ ],
+ "x-fern-sdk-group-name": [
+ "sandbox_environments"
+ ],
+ "x-fern-sdk-method-name": "get"
+ },
+ "put": {
+ "description": "Replace the manifest (and optionally description). Name is immutable.",
+ "parameters": [
+ {
+ "description": "Immutable sandbox environment identifier.",
+ "in": "path",
+ "name": "sandbox_environment_id",
+ "required": true,
+ "schema": {
+ "description": "Immutable sandbox environment identifier.",
+ "maxLength": 64,
+ "minLength": 1,
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateSandboxEnvironmentRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GetSandboxEnvironmentResponse"
+ }
+ }
+ },
+ "description": "Updated sandbox environment."
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Invalid request body."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Unauthenticated."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Not found or you do not have access."
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Provider, snapshot, or secret validation failed."
+ }
+ },
+ "summary": "Update a sandbox environment",
+ "tags": [
+ "Sandboxes"
+ ],
+ "x-fern-sdk-group-name": [
+ "sandbox_environments"
+ ],
+ "x-fern-sdk-method-name": "update"
+ }
+ },
"/api/v1/schedules": {
"get": {
"description": "List schedules for the tenant, newest first.",
diff --git a/docs/openapi.json b/docs/openapi.json
index b40934b3f..502d645cf 100644
--- a/docs/openapi.json
+++ b/docs/openapi.json
@@ -1053,6 +1053,27 @@
],
"type": "object"
},
+ "CreateSandboxEnvironmentRequest": {
+ "additionalProperties": false,
+ "properties": {
+ "description": {
+ "description": "Optional human-readable description.",
+ "maxLength": 1024,
+ "type": "string"
+ },
+ "manifest": {
+ "$ref": "#/components/schemas/SandboxEnvironmentManifest"
+ },
+ "name": {
+ "$ref": "#/components/schemas/ResourceName"
+ }
+ },
+ "required": [
+ "name",
+ "manifest"
+ ],
+ "type": "object"
+ },
"CreateScheduleRequest": {
"additionalProperties": false,
"properties": {
@@ -1226,6 +1247,147 @@
],
"type": "object"
},
+ "DaytonaDockerImage": {
+ "additionalProperties": false,
+ "properties": {
+ "ref": {
+ "description": "Container image reference passed to Daytona create-from-image.",
+ "minLength": 1,
+ "type": "string"
+ },
+ "type": {
+ "description": "Build/create from a container image reference.",
+ "enum": [
+ "docker"
+ ],
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "ref"
+ ],
+ "type": "object"
+ },
+ "DaytonaGpuType": {
+ "description": "Preferred Daytona GPU type.",
+ "enum": [
+ "H100",
+ "H200",
+ "RTX-PRO-6000",
+ "RTX-4090",
+ "RTX-5090"
+ ],
+ "type": "string"
+ },
+ "DaytonaSandboxEnvironmentImage": {
+ "discriminator": {
+ "mapping": {
+ "docker": "#/components/schemas/DaytonaDockerImage",
+ "snapshot": "#/components/schemas/DaytonaSnapshotImage",
+ "trueforge-default": "#/components/schemas/DaytonaTrueforgeDefaultImage"
+ },
+ "propertyName": "type"
+ },
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/DaytonaTrueforgeDefaultImage"
+ },
+ {
+ "$ref": "#/components/schemas/DaytonaSnapshotImage"
+ },
+ {
+ "$ref": "#/components/schemas/DaytonaDockerImage"
+ }
+ ]
+ },
+ "DaytonaSandboxEnvironmentLifecycle": {
+ "additionalProperties": false,
+ "properties": {
+ "auto_archive_interval_in_minutes": {
+ "description": "Minutes before Daytona auto-archives the sandbox (0 disables).",
+ "minimum": 0,
+ "type": "integer"
+ },
+ "auto_delete_interval_in_minutes": {
+ "description": "Minutes before Daytona auto-deletes the sandbox (0 disables).",
+ "minimum": 0,
+ "type": "integer"
+ },
+ "auto_stop_interval_in_minutes": {
+ "description": "Minutes of idle time before Daytona auto-stops the sandbox (0 disables).",
+ "minimum": 0,
+ "type": "integer"
+ }
+ },
+ "type": "object"
+ },
+ "DaytonaSandboxEnvironmentNetworking": {
+ "additionalProperties": false,
+ "properties": {
+ "domain_allow_list": {
+ "description": "Comma-separated allowed domains.",
+ "minLength": 1,
+ "type": "string"
+ },
+ "network_allow_list": {
+ "description": "Comma-separated allowed CIDR network addresses.",
+ "minLength": 1,
+ "type": "string"
+ },
+ "network_block_all": {
+ "description": "Block all outbound network access.",
+ "type": "boolean"
+ },
+ "outbound_proxy_url": {
+ "description": "Outbound HTTP(S) proxy URL.",
+ "minLength": 1,
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "DaytonaSandboxEnvironmentResources": {
+ "additionalProperties": false,
+ "properties": {
+ "cpu": {
+ "description": "CPU allocation in cores.",
+ "exclusiveMinimum": 0,
+ "type": "number"
+ },
+ "disk": {
+ "description": "Disk allocation in GiB.",
+ "exclusiveMinimum": 0,
+ "type": "number"
+ },
+ "gpu": {
+ "description": "GPU allocation in Daytona GPU units.",
+ "minimum": 0,
+ "type": "number"
+ },
+ "gpu_type": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/DaytonaGpuType"
+ },
+ {
+ "items": {
+ "$ref": "#/components/schemas/DaytonaGpuType"
+ },
+ "minItems": 1,
+ "type": "array"
+ }
+ ],
+ "description": "Preferred GPU type, or an ordered fallback list."
+ },
+ "memory": {
+ "description": "Memory allocation in GiB.",
+ "exclusiveMinimum": 0,
+ "type": "number"
+ }
+ },
+ "type": "object"
+ },
"DaytonaSandboxProviderAuth": {
"additionalProperties": false,
"description": "Daytona authentication credentials.",
@@ -1241,10 +1403,52 @@
],
"type": "object"
},
+ "DaytonaSnapshotImage": {
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "description": "Daytona snapshot name.",
+ "minLength": 1,
+ "type": "string"
+ },
+ "type": {
+ "description": "Clone an existing Daytona snapshot by name.",
+ "enum": [
+ "snapshot"
+ ],
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "name"
+ ],
+ "type": "object"
+ },
+ "DaytonaTrueforgeDefaultImage": {
+ "additionalProperties": false,
+ "properties": {
+ "type": {
+ "description": "Use the tenant provider TrueForge release snapshot.",
+ "enum": [
+ "trueforge-default"
+ ],
+ "type": "string"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "type": "object"
+ },
"DeleteAgentResponse": {
"properties": {},
"type": "object"
},
+ "DeleteSandboxEnvironmentResponse": {
+ "properties": {},
+ "type": "object"
+ },
"DeleteScheduleResponse": {
"properties": {},
"type": "object"
@@ -1528,6 +1732,17 @@
],
"type": "object"
},
+ "GetSandboxEnvironmentResponse": {
+ "properties": {
+ "data": {
+ "$ref": "#/components/schemas/SandboxEnvironment"
+ }
+ },
+ "required": [
+ "data"
+ ],
+ "type": "object"
+ },
"GetSandboxProviderCatalogResponse": {
"properties": {
"data": {
@@ -2277,7 +2492,7 @@
},
"type": "array"
},
- "description": "For agent/schedule/session: keyed by resource id. For tenant: keyed by entity kind (e.g. `agent` → `CREATE`).",
+ "description": "For agent/schedule/session: keyed by resource id. For tenant: keyed by entity kind (e.g. `agent` or `sandbox-environment` → `CREATE`).",
"type": "object"
},
"type": {
@@ -2322,6 +2537,24 @@
],
"type": "object"
},
+ "ListSandboxEnvironmentsResponse": {
+ "properties": {
+ "data": {
+ "items": {
+ "$ref": "#/components/schemas/SandboxEnvironment"
+ },
+ "type": "array"
+ },
+ "pagination": {
+ "$ref": "#/components/schemas/TokenPagination"
+ }
+ },
+ "required": [
+ "data",
+ "pagination"
+ ],
+ "type": "object"
+ },
"ListScheduleRunsResponse": {
"properties": {
"data": {
@@ -3658,6 +3891,11 @@
"description": "Give the agent a sandbox. Required for skills and Code Mode.",
"type": "boolean"
},
+ "environment": {
+ "description": "Caller-owned sandbox environment name. Omit to use the tenant provider defaults.",
+ "minLength": 1,
+ "type": "string"
+ },
"file_downloads": {
"default": true,
"description": "Allow downloading agent-produced files via the turn download endpoint. Default: true.",
@@ -3707,6 +3945,97 @@
],
"type": "object"
},
+ "SandboxEnvironment": {
+ "additionalProperties": false,
+ "properties": {
+ "created_at": {
+ "description": "ISO-8601 create time.",
+ "format": "date-time",
+ "type": "string"
+ },
+ "created_by_subject": {
+ "$ref": "#/components/schemas/CreatedBySubject"
+ },
+ "description": {
+ "description": "Optional human-readable description.",
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "id": {
+ "description": "Immutable server-generated environment identifier.",
+ "minLength": 1,
+ "type": "string"
+ },
+ "manifest": {
+ "$ref": "#/components/schemas/SandboxEnvironmentManifest"
+ },
+ "name": {
+ "$ref": "#/components/schemas/ResourceName"
+ },
+ "updated_at": {
+ "description": "ISO-8601 last update time.",
+ "format": "date-time",
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "description",
+ "manifest",
+ "created_by_subject",
+ "created_at",
+ "updated_at"
+ ],
+ "type": "object"
+ },
+ "SandboxEnvironmentManifest": {
+ "additionalProperties": false,
+ "properties": {
+ "image": {
+ "$ref": "#/components/schemas/DaytonaSandboxEnvironmentImage"
+ },
+ "lifecycle": {
+ "$ref": "#/components/schemas/DaytonaSandboxEnvironmentLifecycle"
+ },
+ "networking": {
+ "$ref": "#/components/schemas/DaytonaSandboxEnvironmentNetworking"
+ },
+ "provider": {
+ "description": "Must match the configured sandbox provider name/type.",
+ "enum": [
+ "daytona"
+ ],
+ "type": "string"
+ },
+ "resources": {
+ "$ref": "#/components/schemas/DaytonaSandboxEnvironmentResources"
+ },
+ "secrets": {
+ "additionalProperties": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "description": "Map of sandbox env var name to an existing Daytona organization secret name.",
+ "type": "object"
+ },
+ "type": {
+ "description": "Daytona sandbox environment.",
+ "enum": [
+ "daytona"
+ ],
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "provider",
+ "image"
+ ],
+ "type": "object"
+ },
"SandboxProviderManifest": {
"additionalProperties": false,
"properties": {
@@ -5581,15 +5910,35 @@
],
"type": "object"
},
- "UpdateSandboxProviderRequest": {
+ "UpdateSandboxEnvironmentRequest": {
"additionalProperties": false,
"properties": {
- "manifest": {
- "$ref": "#/components/schemas/SandboxProviderManifest"
- }
- },
- "required": [
- "manifest"
+ "description": {
+ "description": "Optional human-readable description.",
+ "maxLength": 1024,
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "manifest": {
+ "$ref": "#/components/schemas/SandboxEnvironmentManifest"
+ }
+ },
+ "required": [
+ "manifest"
+ ],
+ "type": "object"
+ },
+ "UpdateSandboxProviderRequest": {
+ "additionalProperties": false,
+ "properties": {
+ "manifest": {
+ "$ref": "#/components/schemas/SandboxProviderManifest"
+ }
+ },
+ "required": [
+ "manifest"
],
"type": "object"
},
@@ -7537,6 +7886,367 @@
"x-fern-sdk-method-name": "list"
}
},
+ "/api/v1/sandbox-environments": {
+ "get": {
+ "description": "List sandbox environments created by the authenticated subject, newest first.",
+ "parameters": [
+ {
+ "description": "Page size. Defaults to 25",
+ "in": "query",
+ "name": "limit",
+ "required": false,
+ "schema": {
+ "default": 25,
+ "description": "Page size. Defaults to 25",
+ "maximum": 25,
+ "minimum": 1,
+ "type": "integer"
+ }
+ },
+ {
+ "description": "Opaque token from a previous response `next_page_token`.",
+ "in": "query",
+ "name": "page_token",
+ "required": false,
+ "schema": {
+ "description": "Opaque token from a previous response `next_page_token`.",
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ListSandboxEnvironmentsResponse"
+ }
+ }
+ },
+ "description": "Paginated caller-owned sandbox environments."
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Invalid query parameters or page token."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Unauthenticated."
+ }
+ },
+ "summary": "List sandbox environments",
+ "tags": [
+ "Sandboxes"
+ ],
+ "x-fern-pagination": {
+ "cursor": "$request.page_token",
+ "next_cursor": "$response.pagination.next_page_token",
+ "results": "$response.data"
+ },
+ "x-fern-sdk-group-name": [
+ "sandbox_environments"
+ ],
+ "x-fern-sdk-method-name": "list"
+ },
+ "post": {
+ "description": "Create a Daytona sandbox environment owned by the authenticated subject.",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateSandboxEnvironmentRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GetSandboxEnvironmentResponse"
+ }
+ }
+ },
+ "description": "Created sandbox environment."
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Invalid request body."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Unauthenticated."
+ },
+ "409": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Name already exists in the tenant."
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Provider, snapshot, or secret validation failed."
+ }
+ },
+ "summary": "Create a sandbox environment",
+ "tags": [
+ "Sandboxes"
+ ],
+ "x-fern-sdk-group-name": [
+ "sandbox_environments"
+ ],
+ "x-fern-sdk-method-name": "create"
+ }
+ },
+ "/api/v1/sandbox-environments/{sandbox_environment_id}": {
+ "delete": {
+ "description": "Delete a caller-owned sandbox environment. Fails if any agent references it.",
+ "parameters": [
+ {
+ "description": "Immutable sandbox environment identifier.",
+ "in": "path",
+ "name": "sandbox_environment_id",
+ "required": true,
+ "schema": {
+ "description": "Immutable sandbox environment identifier.",
+ "maxLength": 64,
+ "minLength": 1,
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DeleteSandboxEnvironmentResponse"
+ }
+ }
+ },
+ "description": "Deleted."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Unauthenticated."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Not found or you do not have access."
+ },
+ "409": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Environment is referenced by one or more agents."
+ }
+ },
+ "summary": "Delete a sandbox environment",
+ "tags": [
+ "Sandboxes"
+ ],
+ "x-fern-sdk-group-name": [
+ "sandbox_environments"
+ ],
+ "x-fern-sdk-method-name": "delete"
+ },
+ "get": {
+ "description": "Get a sandbox environment owned by the authenticated subject.",
+ "parameters": [
+ {
+ "description": "Immutable sandbox environment identifier.",
+ "in": "path",
+ "name": "sandbox_environment_id",
+ "required": true,
+ "schema": {
+ "description": "Immutable sandbox environment identifier.",
+ "maxLength": 64,
+ "minLength": 1,
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GetSandboxEnvironmentResponse"
+ }
+ }
+ },
+ "description": "The sandbox environment."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Unauthenticated."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Not found or you do not have access."
+ }
+ },
+ "summary": "Get a sandbox environment",
+ "tags": [
+ "Sandboxes"
+ ],
+ "x-fern-sdk-group-name": [
+ "sandbox_environments"
+ ],
+ "x-fern-sdk-method-name": "get"
+ },
+ "put": {
+ "description": "Replace the manifest (and optionally description). Name is immutable.",
+ "parameters": [
+ {
+ "description": "Immutable sandbox environment identifier.",
+ "in": "path",
+ "name": "sandbox_environment_id",
+ "required": true,
+ "schema": {
+ "description": "Immutable sandbox environment identifier.",
+ "maxLength": 64,
+ "minLength": 1,
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateSandboxEnvironmentRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GetSandboxEnvironmentResponse"
+ }
+ }
+ },
+ "description": "Updated sandbox environment."
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Invalid request body."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Unauthenticated."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Not found or you do not have access."
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Provider, snapshot, or secret validation failed."
+ }
+ },
+ "summary": "Update a sandbox environment",
+ "tags": [
+ "Sandboxes"
+ ],
+ "x-fern-sdk-group-name": [
+ "sandbox_environments"
+ ],
+ "x-fern-sdk-method-name": "update"
+ }
+ },
"/api/v1/schedules": {
"get": {
"description": "List schedules for the tenant, newest first.",
diff --git a/packages/trueforge-core/src/agent-session/schemas/agentSpec.ts b/packages/trueforge-core/src/agent-session/schemas/agentSpec.ts
index c5134870b..17a501ab1 100644
--- a/packages/trueforge-core/src/agent-session/schemas/agentSpec.ts
+++ b/packages/trueforge-core/src/agent-session/schemas/agentSpec.ts
@@ -166,6 +166,11 @@ const SandboxConfigSchema = z
.boolean()
.default(true)
.describe('Allow downloading agent-produced files via the turn download endpoint. Default: true.'),
+ environment: z
+ .string()
+ .min(1)
+ .optional()
+ .describe('Caller-owned sandbox environment name. Omit to use the tenant provider defaults.'),
})
.openapi('SandboxConfig');
diff --git a/packages/trueforge-sdk/package.json b/packages/trueforge-sdk/package.json
index 8287ffb70..a8ea1ce6e 100644
--- a/packages/trueforge-sdk/package.json
+++ b/packages/trueforge-sdk/package.json
@@ -99,6 +99,17 @@
},
"default": "./dist/cjs/api/resources/models/exports.js"
},
+ "./sandboxEnvironments": {
+ "import": {
+ "types": "./dist/esm/api/resources/sandboxEnvironments/exports.d.mts",
+ "default": "./dist/esm/api/resources/sandboxEnvironments/exports.mjs"
+ },
+ "require": {
+ "types": "./dist/cjs/api/resources/sandboxEnvironments/exports.d.ts",
+ "default": "./dist/cjs/api/resources/sandboxEnvironments/exports.js"
+ },
+ "default": "./dist/cjs/api/resources/sandboxEnvironments/exports.js"
+ },
"./schedules": {
"import": {
"types": "./dist/esm/api/resources/schedules/exports.d.mts",
diff --git a/packages/trueforge-sdk/reference.md b/packages/trueforge-sdk/reference.md
index 0936210ae..23868bf53 100644
--- a/packages/trueforge-sdk/reference.md
+++ b/packages/trueforge-sdk/reference.md
@@ -896,6 +896,361 @@ await client.models.list();
+
+
+
+
+## SandboxEnvironments
+client.sandboxEnvironments.list({ ...params }) -> core.Page<TrueForge.SandboxEnvironment, TrueForge.ListSandboxEnvironmentsResponse>
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+List sandbox environments created by the authenticated subject, newest first.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```typescript
+const pageableResponse = await client.sandboxEnvironments.list();
+for await (const item of pageableResponse) {
+ console.log(item);
+}
+
+// Or you can manually iterate page-by-page
+let page = await client.sandboxEnvironments.list();
+while (page.hasNextPage()) {
+ page = page.getNextPage();
+}
+
+// You can also access the underlying response
+const response = page.response;
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**request:** `TrueForge.ListSandboxEnvironmentsRequest`
+
+
+
+
+
+-
+
+**requestOptions:** `SandboxEnvironmentsClient.RequestOptions`
+
+
+
+
+
+
+
+
+
+
+
+client.sandboxEnvironments.create({ ...params }) -> TrueForge.GetSandboxEnvironmentResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Create a Daytona sandbox environment owned by the authenticated subject.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```typescript
+await client.sandboxEnvironments.create({
+ manifest: {
+ image: {
+ ref: "ref",
+ type: "docker"
+ },
+ provider: "daytona",
+ type: "daytona"
+ },
+ name: "name"
+});
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**request:** `TrueForge.CreateSandboxEnvironmentRequest`
+
+
+
+
+
+-
+
+**requestOptions:** `SandboxEnvironmentsClient.RequestOptions`
+
+
+
+
+
+
+
+
+
+
+
+client.sandboxEnvironments.get(sandbox_environment_id) -> TrueForge.GetSandboxEnvironmentResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Get a sandbox environment owned by the authenticated subject.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```typescript
+await client.sandboxEnvironments.get("sandbox_environment_id");
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**sandbox_environment_id:** `string` — Immutable sandbox environment identifier.
+
+
+
+
+
+-
+
+**requestOptions:** `SandboxEnvironmentsClient.RequestOptions`
+
+
+
+
+
+
+
+
+
+
+
+client.sandboxEnvironments.update(sandbox_environment_id, { ...params }) -> TrueForge.GetSandboxEnvironmentResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Replace the manifest (and optionally description). Name is immutable.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```typescript
+await client.sandboxEnvironments.update("sandbox_environment_id", {
+ manifest: {
+ image: {
+ ref: "ref",
+ type: "docker"
+ },
+ provider: "daytona",
+ type: "daytona"
+ }
+});
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**sandbox_environment_id:** `string` — Immutable sandbox environment identifier.
+
+
+
+
+
+-
+
+**request:** `TrueForge.UpdateSandboxEnvironmentRequest`
+
+
+
+
+
+-
+
+**requestOptions:** `SandboxEnvironmentsClient.RequestOptions`
+
+
+
+
+
+
+
+
+
+
+
+client.sandboxEnvironments.delete(sandbox_environment_id) -> TrueForge.DeleteSandboxEnvironmentResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Delete a caller-owned sandbox environment. Fails if any agent references it.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```typescript
+await client.sandboxEnvironments.delete("sandbox_environment_id");
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**sandbox_environment_id:** `string` — Immutable sandbox environment identifier.
+
+
+
+
+
+-
+
+**requestOptions:** `SandboxEnvironmentsClient.RequestOptions`
+
+
+
+
+
+
+
diff --git a/packages/trueforge-sdk/src/Client.ts b/packages/trueforge-sdk/src/Client.ts
index d8dab2878..757773d88 100644
--- a/packages/trueforge-sdk/src/Client.ts
+++ b/packages/trueforge-sdk/src/Client.ts
@@ -6,6 +6,7 @@ import { CatalogsClient } from "./api/resources/catalogs/client/Client.js";
import { InternalClient } from "./api/resources/internal/client/Client.js";
import { McpServersClient } from "./api/resources/mcpServers/client/Client.js";
import { ModelsClient } from "./api/resources/models/client/Client.js";
+import { SandboxEnvironmentsClient } from "./api/resources/sandboxEnvironments/client/Client.js";
import { SchedulesClient } from "./api/resources/schedules/client/Client.js";
import { ServerClient } from "./api/resources/server/client/Client.js";
import { SessionsClient } from "./api/resources/sessions/client/Client.js";
@@ -29,6 +30,7 @@ export class TrueForge {
protected _server: ServerClient | undefined;
protected _mcpServers: McpServersClient | undefined;
protected _models: ModelsClient | undefined;
+ protected _sandboxEnvironments: SandboxEnvironmentsClient | undefined;
protected _schedules: SchedulesClient | undefined;
protected _sessions: SessionsClient | undefined;
protected _skills: SkillsClient | undefined;
@@ -63,6 +65,10 @@ export class TrueForge {
return (this._models ??= new ModelsClient(this._options));
}
+ public get sandboxEnvironments(): SandboxEnvironmentsClient {
+ return (this._sandboxEnvironments ??= new SandboxEnvironmentsClient(this._options));
+ }
+
public get schedules(): SchedulesClient {
return (this._schedules ??= new SchedulesClient(this._options));
}
diff --git a/packages/trueforge-sdk/src/api/resources/index.ts b/packages/trueforge-sdk/src/api/resources/index.ts
index 000fb4979..bef7cbe19 100644
--- a/packages/trueforge-sdk/src/api/resources/index.ts
+++ b/packages/trueforge-sdk/src/api/resources/index.ts
@@ -7,6 +7,8 @@ export * as internal from "./internal/index.js";
export * from "./mcpServers/client/requests/index.js";
export * as mcpServers from "./mcpServers/index.js";
export * as models from "./models/index.js";
+export * from "./sandboxEnvironments/client/requests/index.js";
+export * as sandboxEnvironments from "./sandboxEnvironments/index.js";
export * from "./schedules/client/requests/index.js";
export * as schedules from "./schedules/index.js";
export * as server from "./server/index.js";
diff --git a/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/Client.ts b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/Client.ts
new file mode 100644
index 000000000..53d01c45b
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/Client.ts
@@ -0,0 +1,642 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type { BaseClientOptions, BaseRequestOptions } from "../../../../BaseClient.js";
+import { type NormalizedClientOptionsWithAuth, normalizeClientOptionsWithAuth } from "../../../../BaseClient.js";
+import { mergeHeaders } from "../../../../core/headers.js";
+import * as core from "../../../../core/index.js";
+import { mergeAdditionalBodyParameters } from "../../../../core/requestBody.js";
+import { handleNonStatusCodeError } from "../../../../errors/handleNonStatusCodeError.js";
+import * as errors from "../../../../errors/index.js";
+import * as serializers from "../../../../serialization/index.js";
+import * as TrueForge from "../../../index.js";
+
+export declare namespace SandboxEnvironmentsClient {
+ export type Options = BaseClientOptions;
+
+ export interface RequestOptions extends BaseRequestOptions {}
+}
+
+export class SandboxEnvironmentsClient {
+ protected readonly _options: NormalizedClientOptionsWithAuth;
+
+ constructor(options: SandboxEnvironmentsClient.Options) {
+ this._options = normalizeClientOptionsWithAuth(options);
+ }
+
+ /**
+ * List sandbox environments created by the authenticated subject, newest first.
+ *
+ * @param {TrueForge.ListSandboxEnvironmentsRequest} request
+ * @param {SandboxEnvironmentsClient.RequestOptions} requestOptions - Request-specific configuration.
+ *
+ * @throws {@link TrueForge.BadRequestError}
+ * @throws {@link TrueForge.UnauthorizedError}
+ * @throws {@link errors.TrueForgeError}
+ * @throws {@link errors.TrueForgeTimeoutError}
+ *
+ * @example
+ * await client.sandboxEnvironments.list()
+ */
+ public async list(
+ request: TrueForge.ListSandboxEnvironmentsRequest = {},
+ requestOptions?: SandboxEnvironmentsClient.RequestOptions,
+ ): Promise> {
+ const list = core.HttpResponsePromise.interceptFunction(
+ async (
+ request: TrueForge.ListSandboxEnvironmentsRequest,
+ ): Promise> => {
+ const { limit = 25, pageToken } = request;
+ const _queryParams: Record = {
+ limit,
+ page_token: pageToken,
+ };
+ const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest();
+ const _headers: core.Fetcher.Args["headers"] = mergeHeaders(
+ _authRequest.headers,
+ this._options?.headers,
+ requestOptions?.headers,
+ );
+ const _response = await (this._options.fetcher ?? core.fetcher)({
+ url: core.url.join(
+ (await core.Supplier.get(this._options.baseUrl)) ??
+ (await core.Supplier.get(this._options.environment)),
+ "api/v1/sandbox-environments",
+ ),
+ method: "GET",
+ headers: _headers,
+ queryString: core.url
+ .queryBuilder()
+ .addMany(_queryParams)
+ .mergeAdditional(requestOptions?.queryParams)
+ .build(),
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000,
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
+ abortSignal: requestOptions?.abortSignal,
+ fetchFn: this._options?.fetch,
+ logging: this._options.logging,
+ });
+ if (_response.ok) {
+ return {
+ data: serializers.ListSandboxEnvironmentsResponse.parseOrThrow(_response.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ rawResponse: _response.rawResponse,
+ };
+ }
+ if (_response.error.reason === "status-code") {
+ switch (_response.error.statusCode) {
+ case 400:
+ throw new TrueForge.BadRequestError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ case 401:
+ throw new TrueForge.UnauthorizedError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ default:
+ throw new errors.TrueForgeError({
+ statusCode: _response.error.statusCode,
+ body: _response.error.body,
+ rawResponse: _response.rawResponse,
+ });
+ }
+ }
+ return handleNonStatusCodeError(
+ _response.error,
+ _response.rawResponse,
+ "GET",
+ "/api/v1/sandbox-environments",
+ );
+ },
+ );
+ const dataWithRawResponse = await list(request).withRawResponse();
+ return new core.Page({
+ response: dataWithRawResponse.data,
+ rawResponse: dataWithRawResponse.rawResponse,
+ hasNextPage: (response) =>
+ response?.pagination.nextPageToken != null &&
+ !(typeof response?.pagination.nextPageToken === "string" && response?.pagination.nextPageToken === ""),
+ getItems: (response) => response?.data ?? [],
+ loadPage: (response) => {
+ return list(core.setObjectProperty(request, "pageToken", response?.pagination.nextPageToken));
+ },
+ });
+ }
+
+ /**
+ * Create a Daytona sandbox environment owned by the authenticated subject.
+ *
+ * @param {TrueForge.CreateSandboxEnvironmentRequest} request
+ * @param {SandboxEnvironmentsClient.RequestOptions} requestOptions - Request-specific configuration.
+ *
+ * @throws {@link TrueForge.BadRequestError}
+ * @throws {@link TrueForge.UnauthorizedError}
+ * @throws {@link TrueForge.ConflictError}
+ * @throws {@link TrueForge.UnprocessableEntityError}
+ * @throws {@link errors.TrueForgeError}
+ * @throws {@link errors.TrueForgeTimeoutError}
+ *
+ * @example
+ * await client.sandboxEnvironments.create({
+ * manifest: {
+ * image: {
+ * ref: "ref",
+ * type: "docker"
+ * },
+ * provider: "daytona",
+ * type: "daytona"
+ * },
+ * name: "name"
+ * })
+ */
+ public create(
+ request: TrueForge.CreateSandboxEnvironmentRequest,
+ requestOptions?: SandboxEnvironmentsClient.RequestOptions,
+ ): core.HttpResponsePromise {
+ return core.HttpResponsePromise.fromPromise(this.__create(request, requestOptions));
+ }
+
+ private async __create(
+ request: TrueForge.CreateSandboxEnvironmentRequest,
+ requestOptions?: SandboxEnvironmentsClient.RequestOptions,
+ ): Promise> {
+ const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest();
+ const _headers: core.Fetcher.Args["headers"] = mergeHeaders(
+ _authRequest.headers,
+ this._options?.headers,
+ requestOptions?.headers,
+ );
+ const _response = await (this._options.fetcher ?? core.fetcher)({
+ url: core.url.join(
+ (await core.Supplier.get(this._options.baseUrl)) ??
+ (await core.Supplier.get(this._options.environment)),
+ "api/v1/sandbox-environments",
+ ),
+ method: "POST",
+ headers: _headers,
+ contentType: "application/json",
+ queryString: core.url.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
+ requestType: "json",
+ body: mergeAdditionalBodyParameters(
+ serializers.CreateSandboxEnvironmentRequest.jsonOrThrow(request, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ omitUndefined: true,
+ }),
+ requestOptions?.additionalBodyParameters,
+ ),
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000,
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
+ abortSignal: requestOptions?.abortSignal,
+ fetchFn: this._options?.fetch,
+ logging: this._options.logging,
+ });
+ if (_response.ok) {
+ return {
+ data: serializers.GetSandboxEnvironmentResponse.parseOrThrow(_response.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ rawResponse: _response.rawResponse,
+ };
+ }
+
+ if (_response.error.reason === "status-code") {
+ switch (_response.error.statusCode) {
+ case 400:
+ throw new TrueForge.BadRequestError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ case 401:
+ throw new TrueForge.UnauthorizedError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ case 409:
+ throw new TrueForge.ConflictError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ case 422:
+ throw new TrueForge.UnprocessableEntityError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ default:
+ throw new errors.TrueForgeError({
+ statusCode: _response.error.statusCode,
+ body: _response.error.body,
+ rawResponse: _response.rawResponse,
+ });
+ }
+ }
+
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/api/v1/sandbox-environments");
+ }
+
+ /**
+ * Get a sandbox environment owned by the authenticated subject.
+ *
+ * @param {string} sandbox_environment_id - Immutable sandbox environment identifier.
+ * @param {SandboxEnvironmentsClient.RequestOptions} requestOptions - Request-specific configuration.
+ *
+ * @throws {@link TrueForge.UnauthorizedError}
+ * @throws {@link TrueForge.NotFoundError}
+ * @throws {@link errors.TrueForgeError}
+ * @throws {@link errors.TrueForgeTimeoutError}
+ *
+ * @example
+ * await client.sandboxEnvironments.get("sandbox_environment_id")
+ */
+ public get(
+ sandbox_environment_id: string,
+ requestOptions?: SandboxEnvironmentsClient.RequestOptions,
+ ): core.HttpResponsePromise {
+ return core.HttpResponsePromise.fromPromise(this.__get(sandbox_environment_id, requestOptions));
+ }
+
+ private async __get(
+ sandbox_environment_id: string,
+ requestOptions?: SandboxEnvironmentsClient.RequestOptions,
+ ): Promise> {
+ const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest();
+ const _headers: core.Fetcher.Args["headers"] = mergeHeaders(
+ _authRequest.headers,
+ this._options?.headers,
+ requestOptions?.headers,
+ );
+ const _response = await (this._options.fetcher ?? core.fetcher)({
+ url: core.url.join(
+ (await core.Supplier.get(this._options.baseUrl)) ??
+ (await core.Supplier.get(this._options.environment)),
+ `api/v1/sandbox-environments/${core.url.encodePathParam(sandbox_environment_id)}`,
+ ),
+ method: "GET",
+ headers: _headers,
+ queryString: core.url.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000,
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
+ abortSignal: requestOptions?.abortSignal,
+ fetchFn: this._options?.fetch,
+ logging: this._options.logging,
+ });
+ if (_response.ok) {
+ return {
+ data: serializers.GetSandboxEnvironmentResponse.parseOrThrow(_response.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ rawResponse: _response.rawResponse,
+ };
+ }
+
+ if (_response.error.reason === "status-code") {
+ switch (_response.error.statusCode) {
+ case 401:
+ throw new TrueForge.UnauthorizedError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ case 404:
+ throw new TrueForge.NotFoundError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ default:
+ throw new errors.TrueForgeError({
+ statusCode: _response.error.statusCode,
+ body: _response.error.body,
+ rawResponse: _response.rawResponse,
+ });
+ }
+ }
+
+ return handleNonStatusCodeError(
+ _response.error,
+ _response.rawResponse,
+ "GET",
+ "/api/v1/sandbox-environments/{sandbox_environment_id}",
+ );
+ }
+
+ /**
+ * Replace the manifest (and optionally description). Name is immutable.
+ *
+ * @param {string} sandbox_environment_id - Immutable sandbox environment identifier.
+ * @param {TrueForge.UpdateSandboxEnvironmentRequest} request
+ * @param {SandboxEnvironmentsClient.RequestOptions} requestOptions - Request-specific configuration.
+ *
+ * @throws {@link TrueForge.BadRequestError}
+ * @throws {@link TrueForge.UnauthorizedError}
+ * @throws {@link TrueForge.NotFoundError}
+ * @throws {@link TrueForge.UnprocessableEntityError}
+ * @throws {@link errors.TrueForgeError}
+ * @throws {@link errors.TrueForgeTimeoutError}
+ *
+ * @example
+ * await client.sandboxEnvironments.update("sandbox_environment_id", {
+ * manifest: {
+ * image: {
+ * ref: "ref",
+ * type: "docker"
+ * },
+ * provider: "daytona",
+ * type: "daytona"
+ * }
+ * })
+ */
+ public update(
+ sandbox_environment_id: string,
+ request: TrueForge.UpdateSandboxEnvironmentRequest,
+ requestOptions?: SandboxEnvironmentsClient.RequestOptions,
+ ): core.HttpResponsePromise {
+ return core.HttpResponsePromise.fromPromise(this.__update(sandbox_environment_id, request, requestOptions));
+ }
+
+ private async __update(
+ sandbox_environment_id: string,
+ request: TrueForge.UpdateSandboxEnvironmentRequest,
+ requestOptions?: SandboxEnvironmentsClient.RequestOptions,
+ ): Promise> {
+ const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest();
+ const _headers: core.Fetcher.Args["headers"] = mergeHeaders(
+ _authRequest.headers,
+ this._options?.headers,
+ requestOptions?.headers,
+ );
+ const _response = await (this._options.fetcher ?? core.fetcher)({
+ url: core.url.join(
+ (await core.Supplier.get(this._options.baseUrl)) ??
+ (await core.Supplier.get(this._options.environment)),
+ `api/v1/sandbox-environments/${core.url.encodePathParam(sandbox_environment_id)}`,
+ ),
+ method: "PUT",
+ headers: _headers,
+ contentType: "application/json",
+ queryString: core.url.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
+ requestType: "json",
+ body: mergeAdditionalBodyParameters(
+ serializers.UpdateSandboxEnvironmentRequest.jsonOrThrow(request, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ omitUndefined: true,
+ }),
+ requestOptions?.additionalBodyParameters,
+ ),
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000,
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
+ abortSignal: requestOptions?.abortSignal,
+ fetchFn: this._options?.fetch,
+ logging: this._options.logging,
+ });
+ if (_response.ok) {
+ return {
+ data: serializers.GetSandboxEnvironmentResponse.parseOrThrow(_response.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ rawResponse: _response.rawResponse,
+ };
+ }
+
+ if (_response.error.reason === "status-code") {
+ switch (_response.error.statusCode) {
+ case 400:
+ throw new TrueForge.BadRequestError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ case 401:
+ throw new TrueForge.UnauthorizedError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ case 404:
+ throw new TrueForge.NotFoundError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ case 422:
+ throw new TrueForge.UnprocessableEntityError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ default:
+ throw new errors.TrueForgeError({
+ statusCode: _response.error.statusCode,
+ body: _response.error.body,
+ rawResponse: _response.rawResponse,
+ });
+ }
+ }
+
+ return handleNonStatusCodeError(
+ _response.error,
+ _response.rawResponse,
+ "PUT",
+ "/api/v1/sandbox-environments/{sandbox_environment_id}",
+ );
+ }
+
+ /**
+ * Delete a caller-owned sandbox environment. Fails if any agent references it.
+ *
+ * @param {string} sandbox_environment_id - Immutable sandbox environment identifier.
+ * @param {SandboxEnvironmentsClient.RequestOptions} requestOptions - Request-specific configuration.
+ *
+ * @throws {@link TrueForge.UnauthorizedError}
+ * @throws {@link TrueForge.NotFoundError}
+ * @throws {@link TrueForge.ConflictError}
+ * @throws {@link errors.TrueForgeError}
+ * @throws {@link errors.TrueForgeTimeoutError}
+ *
+ * @example
+ * await client.sandboxEnvironments.delete("sandbox_environment_id")
+ */
+ public delete(
+ sandbox_environment_id: string,
+ requestOptions?: SandboxEnvironmentsClient.RequestOptions,
+ ): core.HttpResponsePromise {
+ return core.HttpResponsePromise.fromPromise(this.__delete(sandbox_environment_id, requestOptions));
+ }
+
+ private async __delete(
+ sandbox_environment_id: string,
+ requestOptions?: SandboxEnvironmentsClient.RequestOptions,
+ ): Promise> {
+ const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest();
+ const _headers: core.Fetcher.Args["headers"] = mergeHeaders(
+ _authRequest.headers,
+ this._options?.headers,
+ requestOptions?.headers,
+ );
+ const _response = await (this._options.fetcher ?? core.fetcher)({
+ url: core.url.join(
+ (await core.Supplier.get(this._options.baseUrl)) ??
+ (await core.Supplier.get(this._options.environment)),
+ `api/v1/sandbox-environments/${core.url.encodePathParam(sandbox_environment_id)}`,
+ ),
+ method: "DELETE",
+ headers: _headers,
+ queryString: core.url.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000,
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
+ abortSignal: requestOptions?.abortSignal,
+ fetchFn: this._options?.fetch,
+ logging: this._options.logging,
+ });
+ if (_response.ok) {
+ return {
+ data: serializers.DeleteSandboxEnvironmentResponse.parseOrThrow(_response.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ rawResponse: _response.rawResponse,
+ };
+ }
+
+ if (_response.error.reason === "status-code") {
+ switch (_response.error.statusCode) {
+ case 401:
+ throw new TrueForge.UnauthorizedError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ case 404:
+ throw new TrueForge.NotFoundError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ case 409:
+ throw new TrueForge.ConflictError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ default:
+ throw new errors.TrueForgeError({
+ statusCode: _response.error.statusCode,
+ body: _response.error.body,
+ rawResponse: _response.rawResponse,
+ });
+ }
+ }
+
+ return handleNonStatusCodeError(
+ _response.error,
+ _response.rawResponse,
+ "DELETE",
+ "/api/v1/sandbox-environments/{sandbox_environment_id}",
+ );
+ }
+}
diff --git a/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/index.ts b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/index.ts
new file mode 100644
index 000000000..195f9aa8a
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/index.ts
@@ -0,0 +1 @@
+export * from "./requests/index.js";
diff --git a/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/requests/CreateSandboxEnvironmentRequest.ts b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/requests/CreateSandboxEnvironmentRequest.ts
new file mode 100644
index 000000000..af9348dd0
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/requests/CreateSandboxEnvironmentRequest.ts
@@ -0,0 +1,24 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../../../index.js";
+
+/**
+ * @example
+ * {
+ * manifest: {
+ * image: {
+ * ref: "ref",
+ * type: "docker"
+ * },
+ * provider: "daytona",
+ * type: "daytona"
+ * },
+ * name: "name"
+ * }
+ */
+export interface CreateSandboxEnvironmentRequest {
+ /** Optional human-readable description. */
+ description?: string;
+ manifest: TrueForge.SandboxEnvironmentManifest;
+ name: TrueForge.ResourceName;
+}
diff --git a/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/requests/ListSandboxEnvironmentsRequest.ts b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/requests/ListSandboxEnvironmentsRequest.ts
new file mode 100644
index 000000000..2efd1793b
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/requests/ListSandboxEnvironmentsRequest.ts
@@ -0,0 +1,12 @@
+// This file was auto-generated by Fern from our API Definition.
+
+/**
+ * @example
+ * {}
+ */
+export interface ListSandboxEnvironmentsRequest {
+ /** Page size. Defaults to 25 */
+ limit?: number;
+ /** Opaque token from a previous response `next_page_token`. */
+ pageToken?: string;
+}
diff --git a/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/requests/UpdateSandboxEnvironmentRequest.ts b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/requests/UpdateSandboxEnvironmentRequest.ts
new file mode 100644
index 000000000..e44ba8223
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/requests/UpdateSandboxEnvironmentRequest.ts
@@ -0,0 +1,22 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../../../index.js";
+
+/**
+ * @example
+ * {
+ * manifest: {
+ * image: {
+ * ref: "ref",
+ * type: "docker"
+ * },
+ * provider: "daytona",
+ * type: "daytona"
+ * }
+ * }
+ */
+export interface UpdateSandboxEnvironmentRequest {
+ /** Optional human-readable description. */
+ description?: string | null;
+ manifest: TrueForge.SandboxEnvironmentManifest;
+}
diff --git a/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/requests/index.ts b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/requests/index.ts
new file mode 100644
index 000000000..f20f36488
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/client/requests/index.ts
@@ -0,0 +1,3 @@
+export type { CreateSandboxEnvironmentRequest } from "./CreateSandboxEnvironmentRequest.js";
+export type { ListSandboxEnvironmentsRequest } from "./ListSandboxEnvironmentsRequest.js";
+export type { UpdateSandboxEnvironmentRequest } from "./UpdateSandboxEnvironmentRequest.js";
diff --git a/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/exports.ts b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/exports.ts
new file mode 100644
index 000000000..fc58f1b3e
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/exports.ts
@@ -0,0 +1,4 @@
+// This file was auto-generated by Fern from our API Definition.
+
+export { SandboxEnvironmentsClient } from "./client/Client.js";
+export * from "./client/index.js";
diff --git a/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/index.ts b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/index.ts
new file mode 100644
index 000000000..914b8c3c7
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/resources/sandboxEnvironments/index.ts
@@ -0,0 +1 @@
+export * from "./client/index.js";
diff --git a/packages/trueforge-sdk/src/api/types/DaytonaDockerImage.ts b/packages/trueforge-sdk/src/api/types/DaytonaDockerImage.ts
new file mode 100644
index 000000000..aeba33f8c
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/types/DaytonaDockerImage.ts
@@ -0,0 +1,7 @@
+// This file was auto-generated by Fern from our API Definition.
+
+export interface DaytonaDockerImage {
+ /** Container image reference passed to Daytona create-from-image. */
+ ref: string;
+ type: "docker";
+}
diff --git a/packages/trueforge-sdk/src/api/types/DaytonaGpuType.ts b/packages/trueforge-sdk/src/api/types/DaytonaGpuType.ts
new file mode 100644
index 000000000..e9de0407d
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/types/DaytonaGpuType.ts
@@ -0,0 +1,11 @@
+// This file was auto-generated by Fern from our API Definition.
+
+/** Preferred Daytona GPU type. */
+export const DaytonaGpuType = {
+ H100: "H100",
+ H200: "H200",
+ RtxPro6000: "RTX-PRO-6000",
+ Rtx4090: "RTX-4090",
+ Rtx5090: "RTX-5090",
+} as const;
+export type DaytonaGpuType = (typeof DaytonaGpuType)[keyof typeof DaytonaGpuType];
diff --git a/packages/trueforge-sdk/src/api/types/DaytonaSandboxEnvironmentImage.ts b/packages/trueforge-sdk/src/api/types/DaytonaSandboxEnvironmentImage.ts
new file mode 100644
index 000000000..765912cf9
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/types/DaytonaSandboxEnvironmentImage.ts
@@ -0,0 +1,8 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../index.js";
+
+export type DaytonaSandboxEnvironmentImage =
+ | TrueForge.DaytonaDockerImage
+ | TrueForge.DaytonaSnapshotImage
+ | TrueForge.DaytonaTrueforgeDefaultImage;
diff --git a/packages/trueforge-sdk/src/api/types/DaytonaSandboxEnvironmentLifecycle.ts b/packages/trueforge-sdk/src/api/types/DaytonaSandboxEnvironmentLifecycle.ts
new file mode 100644
index 000000000..f1443f40f
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/types/DaytonaSandboxEnvironmentLifecycle.ts
@@ -0,0 +1,10 @@
+// This file was auto-generated by Fern from our API Definition.
+
+export interface DaytonaSandboxEnvironmentLifecycle {
+ /** Minutes before Daytona auto-archives the sandbox (0 disables). */
+ autoArchiveIntervalInMinutes?: number;
+ /** Minutes before Daytona auto-deletes the sandbox (0 disables). */
+ autoDeleteIntervalInMinutes?: number;
+ /** Minutes of idle time before Daytona auto-stops the sandbox (0 disables). */
+ autoStopIntervalInMinutes?: number;
+}
diff --git a/packages/trueforge-sdk/src/api/types/DaytonaSandboxEnvironmentNetworking.ts b/packages/trueforge-sdk/src/api/types/DaytonaSandboxEnvironmentNetworking.ts
new file mode 100644
index 000000000..69d8e9253
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/types/DaytonaSandboxEnvironmentNetworking.ts
@@ -0,0 +1,12 @@
+// This file was auto-generated by Fern from our API Definition.
+
+export interface DaytonaSandboxEnvironmentNetworking {
+ /** Comma-separated allowed domains. */
+ domainAllowList?: string;
+ /** Comma-separated allowed CIDR network addresses. */
+ networkAllowList?: string;
+ /** Block all outbound network access. */
+ networkBlockAll?: boolean;
+ /** Outbound HTTP(S) proxy URL. */
+ outboundProxyUrl?: string;
+}
diff --git a/packages/trueforge-sdk/src/api/types/DaytonaSandboxEnvironmentResources.ts b/packages/trueforge-sdk/src/api/types/DaytonaSandboxEnvironmentResources.ts
new file mode 100644
index 000000000..168bc67a9
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/types/DaytonaSandboxEnvironmentResources.ts
@@ -0,0 +1,16 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../index.js";
+
+export interface DaytonaSandboxEnvironmentResources {
+ /** CPU allocation in cores. */
+ cpu?: number;
+ /** Disk allocation in GiB. */
+ disk?: number;
+ /** GPU allocation in Daytona GPU units. */
+ gpu?: number;
+ /** Preferred GPU type, or an ordered fallback list. */
+ gpuType?: TrueForge.DaytonaSandboxEnvironmentResourcesGpuType;
+ /** Memory allocation in GiB. */
+ memory?: number;
+}
diff --git a/packages/trueforge-sdk/src/api/types/DaytonaSandboxEnvironmentResourcesGpuType.ts b/packages/trueforge-sdk/src/api/types/DaytonaSandboxEnvironmentResourcesGpuType.ts
new file mode 100644
index 000000000..cb55bbd15
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/types/DaytonaSandboxEnvironmentResourcesGpuType.ts
@@ -0,0 +1,8 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../index.js";
+
+/**
+ * Preferred GPU type, or an ordered fallback list.
+ */
+export type DaytonaSandboxEnvironmentResourcesGpuType = TrueForge.DaytonaGpuType | TrueForge.DaytonaGpuType[];
diff --git a/packages/trueforge-sdk/src/api/types/DaytonaSnapshotImage.ts b/packages/trueforge-sdk/src/api/types/DaytonaSnapshotImage.ts
new file mode 100644
index 000000000..2ac83bcdf
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/types/DaytonaSnapshotImage.ts
@@ -0,0 +1,7 @@
+// This file was auto-generated by Fern from our API Definition.
+
+export interface DaytonaSnapshotImage {
+ /** Daytona snapshot name. */
+ name: string;
+ type: "snapshot";
+}
diff --git a/packages/trueforge-sdk/src/api/types/DaytonaTrueforgeDefaultImage.ts b/packages/trueforge-sdk/src/api/types/DaytonaTrueforgeDefaultImage.ts
new file mode 100644
index 000000000..f50ee4bbe
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/types/DaytonaTrueforgeDefaultImage.ts
@@ -0,0 +1,5 @@
+// This file was auto-generated by Fern from our API Definition.
+
+export interface DaytonaTrueforgeDefaultImage {
+ type: "trueforge-default";
+}
diff --git a/packages/trueforge-sdk/src/api/types/DeleteSandboxEnvironmentResponse.ts b/packages/trueforge-sdk/src/api/types/DeleteSandboxEnvironmentResponse.ts
new file mode 100644
index 000000000..f9bb1fc09
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/types/DeleteSandboxEnvironmentResponse.ts
@@ -0,0 +1,3 @@
+// This file was auto-generated by Fern from our API Definition.
+
+export type DeleteSandboxEnvironmentResponse = {};
diff --git a/packages/trueforge-sdk/src/api/types/GetSandboxEnvironmentResponse.ts b/packages/trueforge-sdk/src/api/types/GetSandboxEnvironmentResponse.ts
new file mode 100644
index 000000000..eaaa4d921
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/types/GetSandboxEnvironmentResponse.ts
@@ -0,0 +1,7 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../index.js";
+
+export interface GetSandboxEnvironmentResponse {
+ data: TrueForge.SandboxEnvironment;
+}
diff --git a/packages/trueforge-sdk/src/api/types/ListPermissionsData.ts b/packages/trueforge-sdk/src/api/types/ListPermissionsData.ts
index e66887730..b4f62f10d 100644
--- a/packages/trueforge-sdk/src/api/types/ListPermissionsData.ts
+++ b/packages/trueforge-sdk/src/api/types/ListPermissionsData.ts
@@ -3,7 +3,7 @@
import type * as TrueForge from "../index.js";
export interface ListPermissionsData {
- /** For agent/schedule/session: keyed by resource id. For tenant: keyed by entity kind (e.g. `agent` → `CREATE`). */
+ /** For agent/schedule/session: keyed by resource id. For tenant: keyed by entity kind (e.g. `agent` or `sandbox-environment` → `CREATE`). */
permissions: Record;
type: TrueForge.PermissionResourceType;
}
diff --git a/packages/trueforge-sdk/src/api/types/ListSandboxEnvironmentsResponse.ts b/packages/trueforge-sdk/src/api/types/ListSandboxEnvironmentsResponse.ts
new file mode 100644
index 000000000..93b9b399e
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/types/ListSandboxEnvironmentsResponse.ts
@@ -0,0 +1,8 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../index.js";
+
+export interface ListSandboxEnvironmentsResponse {
+ data: TrueForge.SandboxEnvironment[];
+ pagination: TrueForge.TokenPagination;
+}
diff --git a/packages/trueforge-sdk/src/api/types/SandboxConfig.ts b/packages/trueforge-sdk/src/api/types/SandboxConfig.ts
index ebd8ca110..f7c6bb649 100644
--- a/packages/trueforge-sdk/src/api/types/SandboxConfig.ts
+++ b/packages/trueforge-sdk/src/api/types/SandboxConfig.ts
@@ -3,6 +3,8 @@
export interface SandboxConfig {
/** Give the agent a sandbox. Required for skills and Code Mode. */
enabled: boolean;
+ /** Caller-owned sandbox environment name. Omit to use the tenant provider defaults. */
+ environment?: string;
/** Allow downloading agent-produced files via the turn download endpoint. Default: true. */
fileDownloads?: boolean;
}
diff --git a/packages/trueforge-sdk/src/api/types/SandboxEnvironment.ts b/packages/trueforge-sdk/src/api/types/SandboxEnvironment.ts
new file mode 100644
index 000000000..30ee28f96
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/types/SandboxEnvironment.ts
@@ -0,0 +1,17 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../index.js";
+
+export interface SandboxEnvironment {
+ /** ISO-8601 create time. */
+ createdAt: Date;
+ createdBySubject: TrueForge.CreatedBySubject;
+ /** Optional human-readable description. */
+ description: string | null;
+ /** Immutable server-generated environment identifier. */
+ id: string;
+ manifest: TrueForge.SandboxEnvironmentManifest;
+ name: TrueForge.ResourceName;
+ /** ISO-8601 last update time. */
+ updatedAt: Date;
+}
diff --git a/packages/trueforge-sdk/src/api/types/SandboxEnvironmentManifest.ts b/packages/trueforge-sdk/src/api/types/SandboxEnvironmentManifest.ts
new file mode 100644
index 000000000..c3cab7ace
--- /dev/null
+++ b/packages/trueforge-sdk/src/api/types/SandboxEnvironmentManifest.ts
@@ -0,0 +1,16 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../index.js";
+
+export interface SandboxEnvironmentManifest {
+ image: TrueForge.DaytonaSandboxEnvironmentImage;
+ lifecycle?: TrueForge.DaytonaSandboxEnvironmentLifecycle;
+ networking?: TrueForge.DaytonaSandboxEnvironmentNetworking;
+ /** Must match the configured sandbox provider name/type. */
+ provider: "daytona";
+ resources?: TrueForge.DaytonaSandboxEnvironmentResources;
+ /** Map of sandbox env var name to an existing Daytona organization secret name. */
+ secrets?: Record;
+ /** Daytona sandbox environment. */
+ type: "daytona";
+}
diff --git a/packages/trueforge-sdk/src/api/types/index.ts b/packages/trueforge-sdk/src/api/types/index.ts
index 9b29138b5..2985092b1 100644
--- a/packages/trueforge-sdk/src/api/types/index.ts
+++ b/packages/trueforge-sdk/src/api/types/index.ts
@@ -49,8 +49,18 @@ export * from "./CreateScheduleRunResponse.js";
export * from "./CreateSessionAgent.js";
export * from "./CronExpression.js";
export * from "./CustomModelProvider.js";
+export * from "./DaytonaDockerImage.js";
+export * from "./DaytonaGpuType.js";
+export * from "./DaytonaSandboxEnvironmentImage.js";
+export * from "./DaytonaSandboxEnvironmentLifecycle.js";
+export * from "./DaytonaSandboxEnvironmentNetworking.js";
+export * from "./DaytonaSandboxEnvironmentResources.js";
+export * from "./DaytonaSandboxEnvironmentResourcesGpuType.js";
export * from "./DaytonaSandboxProviderAuth.js";
+export * from "./DaytonaSnapshotImage.js";
+export * from "./DaytonaTrueforgeDefaultImage.js";
export * from "./DeleteAgentResponse.js";
+export * from "./DeleteSandboxEnvironmentResponse.js";
export * from "./DeleteScheduleResponse.js";
export * from "./DynamicSubAgentsConfig.js";
export * from "./ExtendedChunkDeltaToolCall.js";
@@ -68,6 +78,7 @@ export * from "./GetMeResponse.js";
export * from "./GetMeSubject.js";
export * from "./GetModelProviderCatalogResponse.js";
export * from "./GetModelProviderResponse.js";
+export * from "./GetSandboxEnvironmentResponse.js";
export * from "./GetSandboxProviderCatalogResponse.js";
export * from "./GetSandboxProviderResponse.js";
export * from "./GetScheduleResponse.js";
@@ -92,6 +103,7 @@ export * from "./ListMcpServerToolsResponse.js";
export * from "./ListModelProvidersResponse.js";
export * from "./ListPermissionsData.js";
export * from "./ListPermissionsResponse.js";
+export * from "./ListSandboxEnvironmentsResponse.js";
export * from "./ListScheduleRunsResponse.js";
export * from "./ListSchedulesResponse.js";
export * from "./ListSessionEventsResponse.js";
@@ -155,6 +167,8 @@ export * from "./SandboxBuildStatus.js";
export * from "./SandboxCapability.js";
export * from "./SandboxConfig.js";
export * from "./SandboxCreatedEvent.js";
+export * from "./SandboxEnvironment.js";
+export * from "./SandboxEnvironmentManifest.js";
export * from "./SandboxProviderManifest.js";
export * from "./Schedule.js";
export * from "./ScheduleManifest.js";
diff --git a/packages/trueforge-sdk/src/serialization/resources/index.ts b/packages/trueforge-sdk/src/serialization/resources/index.ts
index 4d4bfd3a0..fb0e61aaf 100644
--- a/packages/trueforge-sdk/src/serialization/resources/index.ts
+++ b/packages/trueforge-sdk/src/serialization/resources/index.ts
@@ -2,6 +2,8 @@ export * from "./agents/client/requests/index.js";
export * as agents from "./agents/index.js";
export * from "./internal/client/requests/index.js";
export * as internal from "./internal/index.js";
+export * from "./sandboxEnvironments/client/requests/index.js";
+export * as sandboxEnvironments from "./sandboxEnvironments/index.js";
export * from "./schedules/client/requests/index.js";
export * as schedules from "./schedules/index.js";
export * from "./sessions/client/requests/index.js";
diff --git a/packages/trueforge-sdk/src/serialization/resources/sandboxEnvironments/client/index.ts b/packages/trueforge-sdk/src/serialization/resources/sandboxEnvironments/client/index.ts
new file mode 100644
index 000000000..195f9aa8a
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/resources/sandboxEnvironments/client/index.ts
@@ -0,0 +1 @@
+export * from "./requests/index.js";
diff --git a/packages/trueforge-sdk/src/serialization/resources/sandboxEnvironments/client/requests/CreateSandboxEnvironmentRequest.ts b/packages/trueforge-sdk/src/serialization/resources/sandboxEnvironments/client/requests/CreateSandboxEnvironmentRequest.ts
new file mode 100644
index 000000000..fa8f816a6
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/resources/sandboxEnvironments/client/requests/CreateSandboxEnvironmentRequest.ts
@@ -0,0 +1,24 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../../../../api/index.js";
+import * as core from "../../../../../core/index.js";
+import type * as serializers from "../../../../index.js";
+import { ResourceName } from "../../../../types/ResourceName.js";
+import { SandboxEnvironmentManifest } from "../../../../types/SandboxEnvironmentManifest.js";
+
+export const CreateSandboxEnvironmentRequest: core.serialization.Schema<
+ serializers.CreateSandboxEnvironmentRequest.Raw,
+ TrueForge.CreateSandboxEnvironmentRequest
+> = core.serialization.object({
+ description: core.serialization.string().optional(),
+ manifest: SandboxEnvironmentManifest,
+ name: ResourceName,
+});
+
+export declare namespace CreateSandboxEnvironmentRequest {
+ export interface Raw {
+ description?: string | null;
+ manifest: SandboxEnvironmentManifest.Raw;
+ name: ResourceName.Raw;
+ }
+}
diff --git a/packages/trueforge-sdk/src/serialization/resources/sandboxEnvironments/client/requests/UpdateSandboxEnvironmentRequest.ts b/packages/trueforge-sdk/src/serialization/resources/sandboxEnvironments/client/requests/UpdateSandboxEnvironmentRequest.ts
new file mode 100644
index 000000000..7b733a6ee
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/resources/sandboxEnvironments/client/requests/UpdateSandboxEnvironmentRequest.ts
@@ -0,0 +1,21 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../../../../api/index.js";
+import * as core from "../../../../../core/index.js";
+import type * as serializers from "../../../../index.js";
+import { SandboxEnvironmentManifest } from "../../../../types/SandboxEnvironmentManifest.js";
+
+export const UpdateSandboxEnvironmentRequest: core.serialization.Schema<
+ serializers.UpdateSandboxEnvironmentRequest.Raw,
+ TrueForge.UpdateSandboxEnvironmentRequest
+> = core.serialization.object({
+ description: core.serialization.string().optionalNullable(),
+ manifest: SandboxEnvironmentManifest,
+});
+
+export declare namespace UpdateSandboxEnvironmentRequest {
+ export interface Raw {
+ description?: (string | null | undefined) | null;
+ manifest: SandboxEnvironmentManifest.Raw;
+ }
+}
diff --git a/packages/trueforge-sdk/src/serialization/resources/sandboxEnvironments/client/requests/index.ts b/packages/trueforge-sdk/src/serialization/resources/sandboxEnvironments/client/requests/index.ts
new file mode 100644
index 000000000..77023ee76
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/resources/sandboxEnvironments/client/requests/index.ts
@@ -0,0 +1,2 @@
+export { CreateSandboxEnvironmentRequest } from "./CreateSandboxEnvironmentRequest.js";
+export { UpdateSandboxEnvironmentRequest } from "./UpdateSandboxEnvironmentRequest.js";
diff --git a/packages/trueforge-sdk/src/serialization/resources/sandboxEnvironments/index.ts b/packages/trueforge-sdk/src/serialization/resources/sandboxEnvironments/index.ts
new file mode 100644
index 000000000..914b8c3c7
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/resources/sandboxEnvironments/index.ts
@@ -0,0 +1 @@
+export * from "./client/index.js";
diff --git a/packages/trueforge-sdk/src/serialization/types/DaytonaDockerImage.ts b/packages/trueforge-sdk/src/serialization/types/DaytonaDockerImage.ts
new file mode 100644
index 000000000..5dbc1058c
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/types/DaytonaDockerImage.ts
@@ -0,0 +1,20 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../api/index.js";
+import * as core from "../../core/index.js";
+import type * as serializers from "../index.js";
+
+export const DaytonaDockerImage: core.serialization.ObjectSchema<
+ serializers.DaytonaDockerImage.Raw,
+ TrueForge.DaytonaDockerImage
+> = core.serialization.object({
+ ref: core.serialization.string(),
+ type: core.serialization.stringLiteral("docker"),
+});
+
+export declare namespace DaytonaDockerImage {
+ export interface Raw {
+ ref: string;
+ type: "docker";
+ }
+}
diff --git a/packages/trueforge-sdk/src/serialization/types/DaytonaGpuType.ts b/packages/trueforge-sdk/src/serialization/types/DaytonaGpuType.ts
new file mode 100644
index 000000000..3640aa8ff
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/types/DaytonaGpuType.ts
@@ -0,0 +1,12 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../api/index.js";
+import * as core from "../../core/index.js";
+import type * as serializers from "../index.js";
+
+export const DaytonaGpuType: core.serialization.Schema =
+ core.serialization.enum_(["H100", "H200", "RTX-PRO-6000", "RTX-4090", "RTX-5090"]);
+
+export declare namespace DaytonaGpuType {
+ export type Raw = "H100" | "H200" | "RTX-PRO-6000" | "RTX-4090" | "RTX-5090";
+}
diff --git a/packages/trueforge-sdk/src/serialization/types/DaytonaSandboxEnvironmentImage.ts b/packages/trueforge-sdk/src/serialization/types/DaytonaSandboxEnvironmentImage.ts
new file mode 100644
index 000000000..5e52642cb
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/types/DaytonaSandboxEnvironmentImage.ts
@@ -0,0 +1,17 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../api/index.js";
+import * as core from "../../core/index.js";
+import type * as serializers from "../index.js";
+import { DaytonaDockerImage } from "./DaytonaDockerImage.js";
+import { DaytonaSnapshotImage } from "./DaytonaSnapshotImage.js";
+import { DaytonaTrueforgeDefaultImage } from "./DaytonaTrueforgeDefaultImage.js";
+
+export const DaytonaSandboxEnvironmentImage: core.serialization.Schema<
+ serializers.DaytonaSandboxEnvironmentImage.Raw,
+ TrueForge.DaytonaSandboxEnvironmentImage
+> = core.serialization.undiscriminatedUnion([DaytonaDockerImage, DaytonaSnapshotImage, DaytonaTrueforgeDefaultImage]);
+
+export declare namespace DaytonaSandboxEnvironmentImage {
+ export type Raw = DaytonaDockerImage.Raw | DaytonaSnapshotImage.Raw | DaytonaTrueforgeDefaultImage.Raw;
+}
diff --git a/packages/trueforge-sdk/src/serialization/types/DaytonaSandboxEnvironmentLifecycle.ts b/packages/trueforge-sdk/src/serialization/types/DaytonaSandboxEnvironmentLifecycle.ts
new file mode 100644
index 000000000..83132d770
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/types/DaytonaSandboxEnvironmentLifecycle.ts
@@ -0,0 +1,31 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../api/index.js";
+import * as core from "../../core/index.js";
+import type * as serializers from "../index.js";
+
+export const DaytonaSandboxEnvironmentLifecycle: core.serialization.ObjectSchema<
+ serializers.DaytonaSandboxEnvironmentLifecycle.Raw,
+ TrueForge.DaytonaSandboxEnvironmentLifecycle
+> = core.serialization.object({
+ autoArchiveIntervalInMinutes: core.serialization.property(
+ "auto_archive_interval_in_minutes",
+ core.serialization.number().optional(),
+ ),
+ autoDeleteIntervalInMinutes: core.serialization.property(
+ "auto_delete_interval_in_minutes",
+ core.serialization.number().optional(),
+ ),
+ autoStopIntervalInMinutes: core.serialization.property(
+ "auto_stop_interval_in_minutes",
+ core.serialization.number().optional(),
+ ),
+});
+
+export declare namespace DaytonaSandboxEnvironmentLifecycle {
+ export interface Raw {
+ auto_archive_interval_in_minutes?: number | null;
+ auto_delete_interval_in_minutes?: number | null;
+ auto_stop_interval_in_minutes?: number | null;
+ }
+}
diff --git a/packages/trueforge-sdk/src/serialization/types/DaytonaSandboxEnvironmentNetworking.ts b/packages/trueforge-sdk/src/serialization/types/DaytonaSandboxEnvironmentNetworking.ts
new file mode 100644
index 000000000..599ba6355
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/types/DaytonaSandboxEnvironmentNetworking.ts
@@ -0,0 +1,24 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../api/index.js";
+import * as core from "../../core/index.js";
+import type * as serializers from "../index.js";
+
+export const DaytonaSandboxEnvironmentNetworking: core.serialization.ObjectSchema<
+ serializers.DaytonaSandboxEnvironmentNetworking.Raw,
+ TrueForge.DaytonaSandboxEnvironmentNetworking
+> = core.serialization.object({
+ domainAllowList: core.serialization.property("domain_allow_list", core.serialization.string().optional()),
+ networkAllowList: core.serialization.property("network_allow_list", core.serialization.string().optional()),
+ networkBlockAll: core.serialization.property("network_block_all", core.serialization.boolean().optional()),
+ outboundProxyUrl: core.serialization.property("outbound_proxy_url", core.serialization.string().optional()),
+});
+
+export declare namespace DaytonaSandboxEnvironmentNetworking {
+ export interface Raw {
+ domain_allow_list?: string | null;
+ network_allow_list?: string | null;
+ network_block_all?: boolean | null;
+ outbound_proxy_url?: string | null;
+ }
+}
diff --git a/packages/trueforge-sdk/src/serialization/types/DaytonaSandboxEnvironmentResources.ts b/packages/trueforge-sdk/src/serialization/types/DaytonaSandboxEnvironmentResources.ts
new file mode 100644
index 000000000..fae5eb8ba
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/types/DaytonaSandboxEnvironmentResources.ts
@@ -0,0 +1,27 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../api/index.js";
+import * as core from "../../core/index.js";
+import type * as serializers from "../index.js";
+import { DaytonaSandboxEnvironmentResourcesGpuType } from "./DaytonaSandboxEnvironmentResourcesGpuType.js";
+
+export const DaytonaSandboxEnvironmentResources: core.serialization.ObjectSchema<
+ serializers.DaytonaSandboxEnvironmentResources.Raw,
+ TrueForge.DaytonaSandboxEnvironmentResources
+> = core.serialization.object({
+ cpu: core.serialization.number().optional(),
+ disk: core.serialization.number().optional(),
+ gpu: core.serialization.number().optional(),
+ gpuType: core.serialization.property("gpu_type", DaytonaSandboxEnvironmentResourcesGpuType.optional()),
+ memory: core.serialization.number().optional(),
+});
+
+export declare namespace DaytonaSandboxEnvironmentResources {
+ export interface Raw {
+ cpu?: number | null;
+ disk?: number | null;
+ gpu?: number | null;
+ gpu_type?: DaytonaSandboxEnvironmentResourcesGpuType.Raw | null;
+ memory?: number | null;
+ }
+}
diff --git a/packages/trueforge-sdk/src/serialization/types/DaytonaSandboxEnvironmentResourcesGpuType.ts b/packages/trueforge-sdk/src/serialization/types/DaytonaSandboxEnvironmentResourcesGpuType.ts
new file mode 100644
index 000000000..a19a2c72a
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/types/DaytonaSandboxEnvironmentResourcesGpuType.ts
@@ -0,0 +1,15 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../api/index.js";
+import * as core from "../../core/index.js";
+import type * as serializers from "../index.js";
+import { DaytonaGpuType } from "./DaytonaGpuType.js";
+
+export const DaytonaSandboxEnvironmentResourcesGpuType: core.serialization.Schema<
+ serializers.DaytonaSandboxEnvironmentResourcesGpuType.Raw,
+ TrueForge.DaytonaSandboxEnvironmentResourcesGpuType
+> = core.serialization.undiscriminatedUnion([DaytonaGpuType, core.serialization.list(DaytonaGpuType)]);
+
+export declare namespace DaytonaSandboxEnvironmentResourcesGpuType {
+ export type Raw = DaytonaGpuType.Raw | DaytonaGpuType.Raw[];
+}
diff --git a/packages/trueforge-sdk/src/serialization/types/DaytonaSnapshotImage.ts b/packages/trueforge-sdk/src/serialization/types/DaytonaSnapshotImage.ts
new file mode 100644
index 000000000..00462f552
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/types/DaytonaSnapshotImage.ts
@@ -0,0 +1,20 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../api/index.js";
+import * as core from "../../core/index.js";
+import type * as serializers from "../index.js";
+
+export const DaytonaSnapshotImage: core.serialization.ObjectSchema<
+ serializers.DaytonaSnapshotImage.Raw,
+ TrueForge.DaytonaSnapshotImage
+> = core.serialization.object({
+ name: core.serialization.string(),
+ type: core.serialization.stringLiteral("snapshot"),
+});
+
+export declare namespace DaytonaSnapshotImage {
+ export interface Raw {
+ name: string;
+ type: "snapshot";
+ }
+}
diff --git a/packages/trueforge-sdk/src/serialization/types/DaytonaTrueforgeDefaultImage.ts b/packages/trueforge-sdk/src/serialization/types/DaytonaTrueforgeDefaultImage.ts
new file mode 100644
index 000000000..30cf8f0f1
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/types/DaytonaTrueforgeDefaultImage.ts
@@ -0,0 +1,18 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../api/index.js";
+import * as core from "../../core/index.js";
+import type * as serializers from "../index.js";
+
+export const DaytonaTrueforgeDefaultImage: core.serialization.ObjectSchema<
+ serializers.DaytonaTrueforgeDefaultImage.Raw,
+ TrueForge.DaytonaTrueforgeDefaultImage
+> = core.serialization.object({
+ type: core.serialization.stringLiteral("trueforge-default"),
+});
+
+export declare namespace DaytonaTrueforgeDefaultImage {
+ export interface Raw {
+ type: "trueforge-default";
+ }
+}
diff --git a/packages/trueforge-sdk/src/serialization/types/DeleteSandboxEnvironmentResponse.ts b/packages/trueforge-sdk/src/serialization/types/DeleteSandboxEnvironmentResponse.ts
new file mode 100644
index 000000000..11565de1b
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/types/DeleteSandboxEnvironmentResponse.ts
@@ -0,0 +1,14 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../api/index.js";
+import * as core from "../../core/index.js";
+import type * as serializers from "../index.js";
+
+export const DeleteSandboxEnvironmentResponse: core.serialization.ObjectSchema<
+ serializers.DeleteSandboxEnvironmentResponse.Raw,
+ TrueForge.DeleteSandboxEnvironmentResponse
+> = core.serialization.object({});
+
+export declare namespace DeleteSandboxEnvironmentResponse {
+ export type Raw = {};
+}
diff --git a/packages/trueforge-sdk/src/serialization/types/GetSandboxEnvironmentResponse.ts b/packages/trueforge-sdk/src/serialization/types/GetSandboxEnvironmentResponse.ts
new file mode 100644
index 000000000..400aa98b2
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/types/GetSandboxEnvironmentResponse.ts
@@ -0,0 +1,19 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../api/index.js";
+import * as core from "../../core/index.js";
+import type * as serializers from "../index.js";
+import { SandboxEnvironment } from "./SandboxEnvironment.js";
+
+export const GetSandboxEnvironmentResponse: core.serialization.ObjectSchema<
+ serializers.GetSandboxEnvironmentResponse.Raw,
+ TrueForge.GetSandboxEnvironmentResponse
+> = core.serialization.object({
+ data: SandboxEnvironment,
+});
+
+export declare namespace GetSandboxEnvironmentResponse {
+ export interface Raw {
+ data: SandboxEnvironment.Raw;
+ }
+}
diff --git a/packages/trueforge-sdk/src/serialization/types/ListSandboxEnvironmentsResponse.ts b/packages/trueforge-sdk/src/serialization/types/ListSandboxEnvironmentsResponse.ts
new file mode 100644
index 000000000..c33d25772
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/types/ListSandboxEnvironmentsResponse.ts
@@ -0,0 +1,22 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../api/index.js";
+import * as core from "../../core/index.js";
+import type * as serializers from "../index.js";
+import { SandboxEnvironment } from "./SandboxEnvironment.js";
+import { TokenPagination } from "./TokenPagination.js";
+
+export const ListSandboxEnvironmentsResponse: core.serialization.ObjectSchema<
+ serializers.ListSandboxEnvironmentsResponse.Raw,
+ TrueForge.ListSandboxEnvironmentsResponse
+> = core.serialization.object({
+ data: core.serialization.list(SandboxEnvironment),
+ pagination: TokenPagination,
+});
+
+export declare namespace ListSandboxEnvironmentsResponse {
+ export interface Raw {
+ data: SandboxEnvironment.Raw[];
+ pagination: TokenPagination.Raw;
+ }
+}
diff --git a/packages/trueforge-sdk/src/serialization/types/SandboxConfig.ts b/packages/trueforge-sdk/src/serialization/types/SandboxConfig.ts
index 6476134b7..e274591f1 100644
--- a/packages/trueforge-sdk/src/serialization/types/SandboxConfig.ts
+++ b/packages/trueforge-sdk/src/serialization/types/SandboxConfig.ts
@@ -7,12 +7,14 @@ import type * as serializers from "../index.js";
export const SandboxConfig: core.serialization.ObjectSchema =
core.serialization.object({
enabled: core.serialization.boolean(),
+ environment: core.serialization.string().optional(),
fileDownloads: core.serialization.property("file_downloads", core.serialization.boolean().optional()),
});
export declare namespace SandboxConfig {
export interface Raw {
enabled: boolean;
+ environment?: string | null;
file_downloads?: boolean | null;
}
}
diff --git a/packages/trueforge-sdk/src/serialization/types/SandboxEnvironment.ts b/packages/trueforge-sdk/src/serialization/types/SandboxEnvironment.ts
new file mode 100644
index 000000000..129422971
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/types/SandboxEnvironment.ts
@@ -0,0 +1,33 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../api/index.js";
+import * as core from "../../core/index.js";
+import type * as serializers from "../index.js";
+import { CreatedBySubject } from "./CreatedBySubject.js";
+import { ResourceName } from "./ResourceName.js";
+import { SandboxEnvironmentManifest } from "./SandboxEnvironmentManifest.js";
+
+export const SandboxEnvironment: core.serialization.ObjectSchema<
+ serializers.SandboxEnvironment.Raw,
+ TrueForge.SandboxEnvironment
+> = core.serialization.object({
+ createdAt: core.serialization.property("created_at", core.serialization.date()),
+ createdBySubject: core.serialization.property("created_by_subject", CreatedBySubject),
+ description: core.serialization.string().nullable(),
+ id: core.serialization.string(),
+ manifest: SandboxEnvironmentManifest,
+ name: ResourceName,
+ updatedAt: core.serialization.property("updated_at", core.serialization.date()),
+});
+
+export declare namespace SandboxEnvironment {
+ export interface Raw {
+ created_at: string;
+ created_by_subject: CreatedBySubject.Raw;
+ description?: string | null;
+ id: string;
+ manifest: SandboxEnvironmentManifest.Raw;
+ name: ResourceName.Raw;
+ updated_at: string;
+ }
+}
diff --git a/packages/trueforge-sdk/src/serialization/types/SandboxEnvironmentManifest.ts b/packages/trueforge-sdk/src/serialization/types/SandboxEnvironmentManifest.ts
new file mode 100644
index 000000000..a6ebc45ba
--- /dev/null
+++ b/packages/trueforge-sdk/src/serialization/types/SandboxEnvironmentManifest.ts
@@ -0,0 +1,34 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import type * as TrueForge from "../../api/index.js";
+import * as core from "../../core/index.js";
+import type * as serializers from "../index.js";
+import { DaytonaSandboxEnvironmentImage } from "./DaytonaSandboxEnvironmentImage.js";
+import { DaytonaSandboxEnvironmentLifecycle } from "./DaytonaSandboxEnvironmentLifecycle.js";
+import { DaytonaSandboxEnvironmentNetworking } from "./DaytonaSandboxEnvironmentNetworking.js";
+import { DaytonaSandboxEnvironmentResources } from "./DaytonaSandboxEnvironmentResources.js";
+
+export const SandboxEnvironmentManifest: core.serialization.ObjectSchema<
+ serializers.SandboxEnvironmentManifest.Raw,
+ TrueForge.SandboxEnvironmentManifest
+> = core.serialization.object({
+ image: DaytonaSandboxEnvironmentImage,
+ lifecycle: DaytonaSandboxEnvironmentLifecycle.optional(),
+ networking: DaytonaSandboxEnvironmentNetworking.optional(),
+ provider: core.serialization.stringLiteral("daytona"),
+ resources: DaytonaSandboxEnvironmentResources.optional(),
+ secrets: core.serialization.record(core.serialization.string(), core.serialization.string()).optional(),
+ type: core.serialization.stringLiteral("daytona"),
+});
+
+export declare namespace SandboxEnvironmentManifest {
+ export interface Raw {
+ image: DaytonaSandboxEnvironmentImage.Raw;
+ lifecycle?: DaytonaSandboxEnvironmentLifecycle.Raw | null;
+ networking?: DaytonaSandboxEnvironmentNetworking.Raw | null;
+ provider: "daytona";
+ resources?: DaytonaSandboxEnvironmentResources.Raw | null;
+ secrets?: Record | null;
+ type: "daytona";
+ }
+}
diff --git a/packages/trueforge-sdk/src/serialization/types/index.ts b/packages/trueforge-sdk/src/serialization/types/index.ts
index 9b29138b5..2985092b1 100644
--- a/packages/trueforge-sdk/src/serialization/types/index.ts
+++ b/packages/trueforge-sdk/src/serialization/types/index.ts
@@ -49,8 +49,18 @@ export * from "./CreateScheduleRunResponse.js";
export * from "./CreateSessionAgent.js";
export * from "./CronExpression.js";
export * from "./CustomModelProvider.js";
+export * from "./DaytonaDockerImage.js";
+export * from "./DaytonaGpuType.js";
+export * from "./DaytonaSandboxEnvironmentImage.js";
+export * from "./DaytonaSandboxEnvironmentLifecycle.js";
+export * from "./DaytonaSandboxEnvironmentNetworking.js";
+export * from "./DaytonaSandboxEnvironmentResources.js";
+export * from "./DaytonaSandboxEnvironmentResourcesGpuType.js";
export * from "./DaytonaSandboxProviderAuth.js";
+export * from "./DaytonaSnapshotImage.js";
+export * from "./DaytonaTrueforgeDefaultImage.js";
export * from "./DeleteAgentResponse.js";
+export * from "./DeleteSandboxEnvironmentResponse.js";
export * from "./DeleteScheduleResponse.js";
export * from "./DynamicSubAgentsConfig.js";
export * from "./ExtendedChunkDeltaToolCall.js";
@@ -68,6 +78,7 @@ export * from "./GetMeResponse.js";
export * from "./GetMeSubject.js";
export * from "./GetModelProviderCatalogResponse.js";
export * from "./GetModelProviderResponse.js";
+export * from "./GetSandboxEnvironmentResponse.js";
export * from "./GetSandboxProviderCatalogResponse.js";
export * from "./GetSandboxProviderResponse.js";
export * from "./GetScheduleResponse.js";
@@ -92,6 +103,7 @@ export * from "./ListMcpServerToolsResponse.js";
export * from "./ListModelProvidersResponse.js";
export * from "./ListPermissionsData.js";
export * from "./ListPermissionsResponse.js";
+export * from "./ListSandboxEnvironmentsResponse.js";
export * from "./ListScheduleRunsResponse.js";
export * from "./ListSchedulesResponse.js";
export * from "./ListSessionEventsResponse.js";
@@ -155,6 +167,8 @@ export * from "./SandboxBuildStatus.js";
export * from "./SandboxCapability.js";
export * from "./SandboxConfig.js";
export * from "./SandboxCreatedEvent.js";
+export * from "./SandboxEnvironment.js";
+export * from "./SandboxEnvironmentManifest.js";
export * from "./SandboxProviderManifest.js";
export * from "./Schedule.js";
export * from "./ScheduleManifest.js";
diff --git a/packages/trueforge-sdk/tests/wire/sandboxEnvironments.test.ts b/packages/trueforge-sdk/tests/wire/sandboxEnvironments.test.ts
new file mode 100644
index 000000000..5324dae9a
--- /dev/null
+++ b/packages/trueforge-sdk/tests/wire/sandboxEnvironments.test.ts
@@ -0,0 +1,691 @@
+// This file was auto-generated by Fern from our API Definition.
+
+import * as TrueForgeTypes from "../../src/api/index";
+import { TrueForge } from "../../src/Client";
+import { mockServerPool } from "../mock-server/MockServerPool";
+
+describe("SandboxEnvironmentsClient", () => {
+ test("list (1)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+
+ const rawResponseBody = {
+ data: [
+ {
+ created_at: "2024-01-15T09:30:00Z",
+ created_by_subject: {
+ subject_display_name: "subject_display_name",
+ subject_id: "subject_id",
+ subject_type: "subject_type",
+ },
+ description: "description",
+ id: "id",
+ manifest: { image: { ref: "ref", type: "docker" }, provider: "daytona", type: "daytona" },
+ name: "name",
+ updated_at: "2024-01-15T09:30:00Z",
+ },
+ ],
+ pagination: { limit: 1, next_page_token: "next_page_token", previous_page_token: "previous_page_token" },
+ };
+
+ server
+ .mockEndpoint({ once: false })
+ .get("/api/v1/sandbox-environments")
+ .respondWith()
+ .statusCode(200)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ const expected = {
+ data: [
+ {
+ createdAt: new Date("2024-01-15T09:30:00.000Z"),
+ createdBySubject: {
+ subjectDisplayName: "subject_display_name",
+ subjectId: "subject_id",
+ subjectType: "subject_type",
+ },
+ description: "description",
+ id: "id",
+ manifest: {
+ image: {
+ ref: "ref",
+ type: "docker",
+ },
+ provider: "daytona",
+ type: "daytona",
+ },
+ name: "name",
+ updatedAt: new Date("2024-01-15T09:30:00.000Z"),
+ },
+ ],
+ pagination: {
+ limit: 1,
+ nextPageToken: "next_page_token",
+ previousPageToken: "previous_page_token",
+ },
+ };
+ const page = await client.sandboxEnvironments.list();
+
+ expect(expected.data).toEqual(page.data);
+ expect(page.hasNextPage()).toBe(true);
+ const nextPage = await page.getNextPage();
+ expect(expected.data).toEqual(nextPage.data);
+ });
+
+ test("list (2)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .get("/api/v1/sandbox-environments")
+ .respondWith()
+ .statusCode(400)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.sandboxEnvironments.list();
+ }).rejects.toThrow(TrueForgeTypes.BadRequestError);
+ });
+
+ test("list (3)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .get("/api/v1/sandbox-environments")
+ .respondWith()
+ .statusCode(401)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.sandboxEnvironments.list();
+ }).rejects.toThrow(TrueForgeTypes.UnauthorizedError);
+ });
+
+ test("create (1)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+ const rawRequestBody = {
+ manifest: { image: { ref: "ref", type: "docker" }, provider: "daytona", type: "daytona" },
+ name: "name",
+ };
+ const rawResponseBody = {
+ data: {
+ created_at: "2024-01-15T09:30:00Z",
+ created_by_subject: {
+ subject_display_name: "subject_display_name",
+ subject_id: "subject_id",
+ subject_type: "subject_type",
+ },
+ description: "description",
+ id: "id",
+ manifest: {
+ image: { ref: "ref", type: "docker" },
+ provider: "daytona",
+ secrets: { key: "value" },
+ type: "daytona",
+ },
+ name: "name",
+ updated_at: "2024-01-15T09:30:00Z",
+ },
+ };
+
+ server
+ .mockEndpoint()
+ .post("/api/v1/sandbox-environments")
+ .jsonBody(rawRequestBody)
+ .respondWith()
+ .statusCode(200)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ const response = await client.sandboxEnvironments.create({
+ manifest: {
+ image: {
+ ref: "ref",
+ type: "docker",
+ },
+ provider: "daytona",
+ type: "daytona",
+ },
+ name: "name",
+ });
+ expect(response).toEqual({
+ data: {
+ createdAt: new Date("2024-01-15T09:30:00.000Z"),
+ createdBySubject: {
+ subjectDisplayName: "subject_display_name",
+ subjectId: "subject_id",
+ subjectType: "subject_type",
+ },
+ description: "description",
+ id: "id",
+ manifest: {
+ image: {
+ ref: "ref",
+ type: "docker",
+ },
+ provider: "daytona",
+ secrets: {
+ key: "value",
+ },
+ type: "daytona",
+ },
+ name: "name",
+ updatedAt: new Date("2024-01-15T09:30:00.000Z"),
+ },
+ });
+ });
+
+ test("create (2)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+ const rawRequestBody = {
+ manifest: { image: { ref: "x", type: "docker" }, provider: "daytona", type: "daytona" },
+ name: "xy",
+ };
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .post("/api/v1/sandbox-environments")
+ .jsonBody(rawRequestBody)
+ .respondWith()
+ .statusCode(400)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.sandboxEnvironments.create({
+ manifest: {
+ image: {
+ ref: "x",
+ type: "docker",
+ },
+ provider: "daytona",
+ type: "daytona",
+ },
+ name: "xy",
+ });
+ }).rejects.toThrow(TrueForgeTypes.BadRequestError);
+ });
+
+ test("create (3)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+ const rawRequestBody = {
+ manifest: { image: { ref: "x", type: "docker" }, provider: "daytona", type: "daytona" },
+ name: "xy",
+ };
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .post("/api/v1/sandbox-environments")
+ .jsonBody(rawRequestBody)
+ .respondWith()
+ .statusCode(401)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.sandboxEnvironments.create({
+ manifest: {
+ image: {
+ ref: "x",
+ type: "docker",
+ },
+ provider: "daytona",
+ type: "daytona",
+ },
+ name: "xy",
+ });
+ }).rejects.toThrow(TrueForgeTypes.UnauthorizedError);
+ });
+
+ test("create (4)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+ const rawRequestBody = {
+ manifest: { image: { ref: "x", type: "docker" }, provider: "daytona", type: "daytona" },
+ name: "xy",
+ };
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .post("/api/v1/sandbox-environments")
+ .jsonBody(rawRequestBody)
+ .respondWith()
+ .statusCode(409)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.sandboxEnvironments.create({
+ manifest: {
+ image: {
+ ref: "x",
+ type: "docker",
+ },
+ provider: "daytona",
+ type: "daytona",
+ },
+ name: "xy",
+ });
+ }).rejects.toThrow(TrueForgeTypes.ConflictError);
+ });
+
+ test("create (5)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+ const rawRequestBody = {
+ manifest: { image: { ref: "x", type: "docker" }, provider: "daytona", type: "daytona" },
+ name: "xy",
+ };
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .post("/api/v1/sandbox-environments")
+ .jsonBody(rawRequestBody)
+ .respondWith()
+ .statusCode(422)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.sandboxEnvironments.create({
+ manifest: {
+ image: {
+ ref: "x",
+ type: "docker",
+ },
+ provider: "daytona",
+ type: "daytona",
+ },
+ name: "xy",
+ });
+ }).rejects.toThrow(TrueForgeTypes.UnprocessableEntityError);
+ });
+
+ test("get (1)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+
+ const rawResponseBody = {
+ data: {
+ created_at: "2024-01-15T09:30:00Z",
+ created_by_subject: {
+ subject_display_name: "subject_display_name",
+ subject_id: "subject_id",
+ subject_type: "subject_type",
+ },
+ description: "description",
+ id: "id",
+ manifest: {
+ image: { ref: "ref", type: "docker" },
+ provider: "daytona",
+ secrets: { key: "value" },
+ type: "daytona",
+ },
+ name: "name",
+ updated_at: "2024-01-15T09:30:00Z",
+ },
+ };
+
+ server
+ .mockEndpoint()
+ .get("/api/v1/sandbox-environments/sandbox_environment_id")
+ .respondWith()
+ .statusCode(200)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ const response = await client.sandboxEnvironments.get("sandbox_environment_id");
+ expect(response).toEqual({
+ data: {
+ createdAt: new Date("2024-01-15T09:30:00.000Z"),
+ createdBySubject: {
+ subjectDisplayName: "subject_display_name",
+ subjectId: "subject_id",
+ subjectType: "subject_type",
+ },
+ description: "description",
+ id: "id",
+ manifest: {
+ image: {
+ ref: "ref",
+ type: "docker",
+ },
+ provider: "daytona",
+ secrets: {
+ key: "value",
+ },
+ type: "daytona",
+ },
+ name: "name",
+ updatedAt: new Date("2024-01-15T09:30:00.000Z"),
+ },
+ });
+ });
+
+ test("get (2)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .get("/api/v1/sandbox-environments/sandbox_environment_id")
+ .respondWith()
+ .statusCode(401)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.sandboxEnvironments.get("sandbox_environment_id");
+ }).rejects.toThrow(TrueForgeTypes.UnauthorizedError);
+ });
+
+ test("get (3)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .get("/api/v1/sandbox-environments/sandbox_environment_id")
+ .respondWith()
+ .statusCode(404)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.sandboxEnvironments.get("sandbox_environment_id");
+ }).rejects.toThrow(TrueForgeTypes.NotFoundError);
+ });
+
+ test("update (1)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+ const rawRequestBody = {
+ manifest: { image: { ref: "ref", type: "docker" }, provider: "daytona", type: "daytona" },
+ };
+ const rawResponseBody = {
+ data: {
+ created_at: "2024-01-15T09:30:00Z",
+ created_by_subject: {
+ subject_display_name: "subject_display_name",
+ subject_id: "subject_id",
+ subject_type: "subject_type",
+ },
+ description: "description",
+ id: "id",
+ manifest: {
+ image: { ref: "ref", type: "docker" },
+ provider: "daytona",
+ secrets: { key: "value" },
+ type: "daytona",
+ },
+ name: "name",
+ updated_at: "2024-01-15T09:30:00Z",
+ },
+ };
+
+ server
+ .mockEndpoint()
+ .put("/api/v1/sandbox-environments/sandbox_environment_id")
+ .jsonBody(rawRequestBody)
+ .respondWith()
+ .statusCode(200)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ const response = await client.sandboxEnvironments.update("sandbox_environment_id", {
+ manifest: {
+ image: {
+ ref: "ref",
+ type: "docker",
+ },
+ provider: "daytona",
+ type: "daytona",
+ },
+ });
+ expect(response).toEqual({
+ data: {
+ createdAt: new Date("2024-01-15T09:30:00.000Z"),
+ createdBySubject: {
+ subjectDisplayName: "subject_display_name",
+ subjectId: "subject_id",
+ subjectType: "subject_type",
+ },
+ description: "description",
+ id: "id",
+ manifest: {
+ image: {
+ ref: "ref",
+ type: "docker",
+ },
+ provider: "daytona",
+ secrets: {
+ key: "value",
+ },
+ type: "daytona",
+ },
+ name: "name",
+ updatedAt: new Date("2024-01-15T09:30:00.000Z"),
+ },
+ });
+ });
+
+ test("update (2)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+ const rawRequestBody = {
+ manifest: { image: { ref: "x", type: "docker" }, provider: "daytona", type: "daytona" },
+ };
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .put("/api/v1/sandbox-environments/sandbox_environment_id")
+ .jsonBody(rawRequestBody)
+ .respondWith()
+ .statusCode(400)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.sandboxEnvironments.update("sandbox_environment_id", {
+ manifest: {
+ image: {
+ ref: "x",
+ type: "docker",
+ },
+ provider: "daytona",
+ type: "daytona",
+ },
+ });
+ }).rejects.toThrow(TrueForgeTypes.BadRequestError);
+ });
+
+ test("update (3)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+ const rawRequestBody = {
+ manifest: { image: { ref: "x", type: "docker" }, provider: "daytona", type: "daytona" },
+ };
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .put("/api/v1/sandbox-environments/sandbox_environment_id")
+ .jsonBody(rawRequestBody)
+ .respondWith()
+ .statusCode(401)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.sandboxEnvironments.update("sandbox_environment_id", {
+ manifest: {
+ image: {
+ ref: "x",
+ type: "docker",
+ },
+ provider: "daytona",
+ type: "daytona",
+ },
+ });
+ }).rejects.toThrow(TrueForgeTypes.UnauthorizedError);
+ });
+
+ test("update (4)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+ const rawRequestBody = {
+ manifest: { image: { ref: "x", type: "docker" }, provider: "daytona", type: "daytona" },
+ };
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .put("/api/v1/sandbox-environments/sandbox_environment_id")
+ .jsonBody(rawRequestBody)
+ .respondWith()
+ .statusCode(404)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.sandboxEnvironments.update("sandbox_environment_id", {
+ manifest: {
+ image: {
+ ref: "x",
+ type: "docker",
+ },
+ provider: "daytona",
+ type: "daytona",
+ },
+ });
+ }).rejects.toThrow(TrueForgeTypes.NotFoundError);
+ });
+
+ test("update (5)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+ const rawRequestBody = {
+ manifest: { image: { ref: "x", type: "docker" }, provider: "daytona", type: "daytona" },
+ };
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .put("/api/v1/sandbox-environments/sandbox_environment_id")
+ .jsonBody(rawRequestBody)
+ .respondWith()
+ .statusCode(422)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.sandboxEnvironments.update("sandbox_environment_id", {
+ manifest: {
+ image: {
+ ref: "x",
+ type: "docker",
+ },
+ provider: "daytona",
+ type: "daytona",
+ },
+ });
+ }).rejects.toThrow(TrueForgeTypes.UnprocessableEntityError);
+ });
+
+ test("delete (1)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+
+ const rawResponseBody = {};
+
+ server
+ .mockEndpoint()
+ .delete("/api/v1/sandbox-environments/sandbox_environment_id")
+ .respondWith()
+ .statusCode(200)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ const response = await client.sandboxEnvironments.delete("sandbox_environment_id");
+ expect(response).toEqual({});
+ });
+
+ test("delete (2)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .delete("/api/v1/sandbox-environments/sandbox_environment_id")
+ .respondWith()
+ .statusCode(401)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.sandboxEnvironments.delete("sandbox_environment_id");
+ }).rejects.toThrow(TrueForgeTypes.UnauthorizedError);
+ });
+
+ test("delete (3)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .delete("/api/v1/sandbox-environments/sandbox_environment_id")
+ .respondWith()
+ .statusCode(404)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.sandboxEnvironments.delete("sandbox_environment_id");
+ }).rejects.toThrow(TrueForgeTypes.NotFoundError);
+ });
+
+ test("delete (4)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .delete("/api/v1/sandbox-environments/sandbox_environment_id")
+ .respondWith()
+ .statusCode(409)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.sandboxEnvironments.delete("sandbox_environment_id");
+ }).rejects.toThrow(TrueForgeTypes.ConflictError);
+ });
+});
diff --git a/packages/trueforge/scripts/write-openapi.ts b/packages/trueforge/scripts/write-openapi.ts
index 69c19657d..7aa7878ac 100644
--- a/packages/trueforge/scripts/write-openapi.ts
+++ b/packages/trueforge/scripts/write-openapi.ts
@@ -26,6 +26,7 @@ import { SqliteAgentStore } from '../src/db/sqlite/agent-store/SqliteAgentStore'
import { createSqliteDb } from '../src/db/sqlite/client';
import { SqliteMcpServerStore } from '../src/db/sqlite/mcp-server-store/SqliteMcpServerStore';
import { SqliteModelProviderStore } from '../src/db/sqlite/model-provider-store/SqliteModelProviderStore';
+import { SqliteSandboxEnvironmentStore } from '../src/db/sqlite/sandbox-environment-store/SqliteSandboxEnvironmentStore';
import { SqliteSandboxProviderStore } from '../src/db/sqlite/sandbox-provider-store/SqliteSandboxProviderStore';
import { SqliteScheduleStore } from '../src/db/sqlite/schedule-store/SqliteScheduleStore';
import { SqliteSessionMetricsStore } from '../src/db/sqlite/session-metrics/SqliteSessionMetricsStore';
@@ -64,6 +65,7 @@ const tokenStore = new SqliteOAuthTokenStore(db);
const agentStore = new SqliteAgentStore(db);
const skillStore = new SqliteSkillStore(db);
const sandboxProviderStore = new SqliteSandboxProviderStore(db);
+const sandboxEnvironmentStore = new SqliteSandboxEnvironmentStore(db);
const app = createServerApp({
modelCatalog: ModelCatalog.load(),
mcpCatalog: McpCatalog.load(),
@@ -78,6 +80,7 @@ const app = createServerApp({
}),
resolveSkillStore: () => skillStore,
resolveSandboxProviderStore: () => sandboxProviderStore,
+ resolveSandboxEnvironmentStore: () => sandboxEnvironmentStore,
resolveAgentStore: () => agentStore,
resolveImportAgentStore: () => agentStore,
agentStore,
diff --git a/packages/trueforge/src/apis/agents.ts b/packages/trueforge/src/apis/agents.ts
index fafe32daf..ec0bcf44c 100644
--- a/packages/trueforge/src/apis/agents.ts
+++ b/packages/trueforge/src/apis/agents.ts
@@ -15,6 +15,7 @@ import {
} from '../db/agentStore';
import type { IMcpServerStore } from '../db/mcpServerStore';
import type { IModelProviderStore } from '../db/modelProviderStore';
+import type { ISandboxEnvironmentStore } from '../db/sandboxEnvironmentStore';
import type { ISandboxProviderStore } from '../db/sandboxProviderStore';
import type { ISkillStore } from '../db/skillStore';
import type { WithTransaction } from '../db/transaction';
@@ -38,6 +39,7 @@ export interface AgentsRouterDeps {
resolveMcpServerStore: (c: Context) => IMcpServerStore;
resolveSkillStore: ResolveSkillStore;
resolveSandboxProviderStore: (c: Context) => ISandboxProviderStore;
+ resolveSandboxEnvironmentStore: (c: Context) => ISandboxEnvironmentStore;
withTransaction: WithTransaction;
resolveRequestContext: ResolveRequestContext;
authorizer: Authorizer;
@@ -60,22 +62,28 @@ async function validateManifest({
mcpServerStore,
skillStore,
sandboxProviderStore,
+ sandboxEnvironmentStore,
tenant_id,
+ subject_id,
}: {
spec: AgentSpec;
modelProviderStore: IModelProviderStore;
mcpServerStore: IMcpServerStore;
skillStore: ISkillStore;
sandboxProviderStore: ISandboxProviderStore;
+ sandboxEnvironmentStore: ISandboxEnvironmentStore;
tenant_id: string;
+ subject_id: string;
}): Promise {
await validateAgentSpec({
spec,
tenant_id,
+ subject_id,
modelProviderStore,
mcpServerStore,
skillStore,
sandboxProviderStore,
+ sandboxEnvironmentStore,
});
return spec;
}
@@ -112,7 +120,9 @@ export function createAgentsRouter(deps: AgentsRouterDeps(deps: AgentsRouterDeps {
+ resolveSandboxEnvironmentStore: (c: Context) => ISandboxEnvironmentStore;
+ resolveSandboxProviderStore: (c: Context) => ISandboxProviderStore;
+ agentStore: IAgentStore;
+ withTransaction: WithTransaction;
+ resolveRequestContext: ResolveRequestContext;
+}
+
+function toWire(record: SandboxEnvironmentRecord): SandboxEnvironment {
+ return {
+ id: record.id,
+ name: record.name,
+ description: record.description,
+ manifest: record.manifest,
+ created_by_subject: record.created_by_subject,
+ created_at: record.created_at,
+ updated_at: record.updated_at,
+ };
+}
+
+function isOwner(subjectId: string, record: SandboxEnvironmentRecord): boolean {
+ return record.created_by_subject.subject_id === subjectId;
+}
+
+async function validateManifestAgainstProvider({
+ manifest,
+ tenant_id,
+ sandboxProviderStore,
+}: {
+ manifest: SandboxEnvironmentManifest;
+ tenant_id: string;
+ sandboxProviderStore: ISandboxProviderStore;
+}): Promise {
+ const provider = await sandboxProviderStore.getSandboxProvider(tenant_id);
+ if (provider?.manifest.type !== 'daytona') {
+ throw new HTTPException(422, {
+ message: 'sandbox environments require a configured Daytona sandbox provider',
+ });
+ }
+ if (manifest.provider !== provider.name) {
+ throw new HTTPException(422, {
+ message: `manifest.provider must match the configured sandbox provider ("${provider.name}")`,
+ });
+ }
+
+ if (manifest.image.type === 'trueforge-default' && provider.status !== 'ready') {
+ throw new HTTPException(422, {
+ message: 'trueforge-default image requires the sandbox provider status to be ready',
+ });
+ }
+
+ const daytona = new Daytona({ apiKey: provider.manifest.auth.api_key });
+
+ if (manifest.image.type === 'snapshot') {
+ try {
+ await daytona.snapshot.get(manifest.image.name);
+ } catch (error) {
+ if (error instanceof DaytonaError && error.statusCode === SANDBOX_NOT_FOUND_STATUS) {
+ throw new HTTPException(422, {
+ message: `Daytona snapshot "${manifest.image.name}" was not found`,
+ cause: error,
+ });
+ }
+ if (isDaytonaAuthError(error)) {
+ throw new HTTPException(422, {
+ message: 'Daytona rejected the API key — check the credentials',
+ cause: error,
+ });
+ }
+ if (isDaytonaPermissionError(error)) {
+ throw new HTTPException(422, {
+ message: 'Daytona denied access while verifying the snapshot',
+ cause: error,
+ });
+ }
+ throw error;
+ }
+ }
+
+ if (manifest.secrets) {
+ const secretNames = [...new Set(Object.values(manifest.secrets))];
+ try {
+ for (const secretName of secretNames) {
+ // Prefix match on Daytona today; require an exact name among results.
+ const { items } = await daytona.secret.list({ name: secretName, limit: 200 });
+ if (!items.some(secret => secret.name === secretName)) {
+ throw new HTTPException(422, {
+ message: `Daytona organization secret "${secretName}" was not found`,
+ });
+ }
+ }
+ } catch (error) {
+ if (error instanceof HTTPException) {
+ throw error;
+ }
+ if (isDaytonaAuthError(error)) {
+ throw new HTTPException(422, {
+ message: 'Daytona rejected the API key — check the credentials',
+ cause: error,
+ });
+ }
+ if (isDaytonaPermissionError(error)) {
+ throw new HTTPException(422, {
+ message: 'Daytona denied access while verifying organization secrets',
+ cause: error,
+ });
+ }
+ throw error;
+ }
+ }
+}
+
+export function createSandboxEnvironmentsRouter(deps: SandboxEnvironmentsRouterDeps) {
+ const listHandler: RouteHandler = async c => {
+ const { limit, page_token: pageToken } = c.req.valid('query');
+ const requestContext = deps.resolveRequestContext(c);
+ try {
+ const { data, pagination } = await deps.resolveSandboxEnvironmentStore(c).listSandboxEnvironments({
+ tenant_id: requestContext.tenant_id,
+ created_by_subject_id: requestContext.subject.id,
+ limit,
+ page_token: pageToken,
+ });
+ return c.json({ data: data.map(toWire), pagination }, 200);
+ } catch (error) {
+ if (error instanceof InvalidPageTokenError) {
+ return c.json({ error: { message: error.message } }, 400);
+ }
+ throw error;
+ }
+ };
+
+ const createHandler: RouteHandler = async c => {
+ const body: CreateSandboxEnvironmentRequest = c.req.valid('json');
+ const requestContext = deps.resolveRequestContext(c);
+ try {
+ await validateManifestAgainstProvider({
+ manifest: body.manifest,
+ tenant_id: requestContext.tenant_id,
+ sandboxProviderStore: deps.resolveSandboxProviderStore(c),
+ });
+ const record = await deps.resolveSandboxEnvironmentStore(c).createSandboxEnvironment({
+ tenant_id: requestContext.tenant_id,
+ name: body.name,
+ description: body.description ?? null,
+ manifest: body.manifest,
+ created_by_subject: createdBySubjectFromRequestContext(requestContext),
+ });
+ return c.json({ data: toWire(record) }, 201);
+ } catch (error) {
+ if (error instanceof SandboxEnvironmentNameConflictError) {
+ return c.json({ error: { message: error.message } }, 409);
+ }
+ throw error;
+ }
+ };
+
+ const getHandler: RouteHandler = async c => {
+ const { sandbox_environment_id: id } = c.req.valid('param');
+ const requestContext = deps.resolveRequestContext(c);
+ const record = await deps.resolveSandboxEnvironmentStore(c).getSandboxEnvironment({
+ tenant_id: requestContext.tenant_id,
+ id,
+ });
+ if (!record || !isOwner(requestContext.subject.id, record)) {
+ return c.json({ error: { message: `Sandbox environment not found or you don't have access to it: ${id}` } }, 404);
+ }
+ return c.json({ data: toWire(record) }, 200);
+ };
+
+ const putHandler: RouteHandler = async c => {
+ const { sandbox_environment_id: id } = c.req.valid('param');
+ const body: UpdateSandboxEnvironmentRequest = c.req.valid('json');
+ const requestContext = deps.resolveRequestContext(c);
+ const existing = await deps.resolveSandboxEnvironmentStore(c).getSandboxEnvironment({
+ tenant_id: requestContext.tenant_id,
+ id,
+ });
+ if (!existing || !isOwner(requestContext.subject.id, existing)) {
+ return c.json({ error: { message: `Sandbox environment not found or you don't have access to it: ${id}` } }, 404);
+ }
+ await validateManifestAgainstProvider({
+ manifest: body.manifest,
+ tenant_id: requestContext.tenant_id,
+ sandboxProviderStore: deps.resolveSandboxProviderStore(c),
+ });
+ const record = await deps.resolveSandboxEnvironmentStore(c).updateSandboxEnvironment({
+ tenant_id: requestContext.tenant_id,
+ id,
+ description: body.description,
+ manifest: body.manifest,
+ });
+ if (!record) {
+ return c.json({ error: { message: `Sandbox environment not found or you don't have access to it: ${id}` } }, 404);
+ }
+ return c.json({ data: toWire(record) }, 200);
+ };
+
+ const deleteHandler: RouteHandler = async c => {
+ const { sandbox_environment_id: id } = c.req.valid('param');
+ const requestContext = deps.resolveRequestContext(c);
+ const existing = await deps.resolveSandboxEnvironmentStore(c).getSandboxEnvironment({
+ tenant_id: requestContext.tenant_id,
+ id,
+ });
+ if (!existing || !isOwner(requestContext.subject.id, existing)) {
+ return c.json({ error: { message: `Sandbox environment not found or you don't have access to it: ${id}` } }, 404);
+ }
+ const agentIds = await deps.agentStore.listAgentIdsUsingSandboxEnvironment({
+ tenant_id: requestContext.tenant_id,
+ environment_name: existing.name,
+ });
+ if (agentIds.length) {
+ return c.json(
+ {
+ error: {
+ message: `Sandbox environment "${existing.name}" is referenced by ${String(agentIds.length)} agent(s)`,
+ },
+ },
+ 409,
+ );
+ }
+ await deps.resolveSandboxEnvironmentStore(c).deleteSandboxEnvironment({
+ tenant_id: requestContext.tenant_id,
+ id,
+ });
+ return c.json({}, 200);
+ };
+
+ const router = new OpenAPIHono();
+ router.openapi(listSandboxEnvironmentsRoute, listHandler);
+ router.openapi(createSandboxEnvironmentRoute, createHandler);
+ router.openapi(getSandboxEnvironmentRoute, getHandler);
+ router.openapi(putSandboxEnvironmentRoute, putHandler);
+ router.openapi(deleteSandboxEnvironmentRoute, deleteHandler);
+ return router;
+}
diff --git a/packages/trueforge/src/apis/sessions.ts b/packages/trueforge/src/apis/sessions.ts
index 37083e58b..4f9fa5534 100644
--- a/packages/trueforge/src/apis/sessions.ts
+++ b/packages/trueforge/src/apis/sessions.ts
@@ -27,6 +27,7 @@ import configuration from '../config';
import type { IAgentStore } from '../db/agentStore';
import type { IMcpServerStore } from '../db/mcpServerStore';
import type { IModelProviderStore } from '../db/modelProviderStore';
+import type { ISandboxEnvironmentStore } from '../db/sandboxEnvironmentStore';
import type { ISandboxProviderStore } from '../db/sandboxProviderStore';
import {
cancelSessionRoute,
@@ -80,6 +81,7 @@ export interface SessionsRouterDeps {
resolveSkillStore: ResolveSkillStore;
resolveAgentStore: (c: Context) => IAgentStore;
resolveSandboxProviderStore: (c: Context) => ISandboxProviderStore;
+ resolveSandboxEnvironmentStore: (c: Context) => ISandboxEnvironmentStore;
redis?: RedisClientType | undefined;
requestReplyRouter: RequestReplyRouter;
resolveRequestContext: ResolveRequestContext;
@@ -236,6 +238,7 @@ type InternalSessionsRouterDeps = Pick<
| 'resolveSkillStore'
| 'resolveAgentStore'
| 'resolveSandboxProviderStore'
+ | 'resolveSandboxEnvironmentStore'
| 'resolveRequestContext'
| 'authorizer'
>;
@@ -285,10 +288,12 @@ function createGetOrCreateSessionByExternalIdHandler(
await validateAgentSpec({
spec: body.agent.spec,
tenant_id: requestContext.tenant_id,
+ subject_id: requestContext.subject.id,
modelProviderStore: deps.resolveModelProviderStore(c),
mcpServerStore: deps.resolveMcpServerStore(c),
skillStore: deps.resolveSkillStore(c),
sandboxProviderStore: deps.resolveSandboxProviderStore(c),
+ sandboxEnvironmentStore: deps.resolveSandboxEnvironmentStore(c),
});
agent = { type: 'inline', spec: body.agent.spec };
}
@@ -357,10 +362,12 @@ export function createSessionsRouter(deps: SessionsRouterDeps) {
await validateAgentSpec({
spec: body.agent.spec,
tenant_id: requestContext.tenant_id,
+ subject_id: requestContext.subject.id,
modelProviderStore: deps.resolveModelProviderStore(c),
mcpServerStore: deps.resolveMcpServerStore(c),
skillStore: deps.resolveSkillStore(c),
sandboxProviderStore: deps.resolveSandboxProviderStore(c),
+ sandboxEnvironmentStore: deps.resolveSandboxEnvironmentStore(c),
});
const session = await deps.sessions.create({
tenant_id: requestContext.tenant_id,
@@ -448,10 +455,12 @@ export function createSessionsRouter(deps: SessionsRouterDeps) {
await validateAgentSpec({
spec: body.agent.spec,
tenant_id: requestContext.tenant_id,
+ subject_id: requestContext.subject.id,
modelProviderStore: deps.resolveModelProviderStore(c),
mcpServerStore: deps.resolveMcpServerStore(c),
skillStore: deps.resolveSkillStore(c),
sandboxProviderStore: deps.resolveSandboxProviderStore(c),
+ sandboxEnvironmentStore: deps.resolveSandboxEnvironmentStore(c),
});
}
try {
diff --git a/packages/trueforge/src/app.ts b/packages/trueforge/src/app.ts
index a8c9ef36e..080353fef 100644
--- a/packages/trueforge/src/app.ts
+++ b/packages/trueforge/src/app.ts
@@ -19,6 +19,7 @@ import { createMcpOAuthRouter } from './apis/mcpOAuth';
import { createMcpServersRouter } from './apis/mcpServers';
import { createModelsRouter } from './apis/models';
import { createPermissionsRouter } from './apis/permissions';
+import { createSandboxEnvironmentsRouter } from './apis/sandboxEnvironments';
import { createScheduleExecutionRouter, createSchedulesRouter } from './apis/schedules';
import { createInternalMetricsRouter } from './apis/sessionMetrics';
import { createInternalSessionsRouter, createSessionsRouter } from './apis/sessions';
@@ -42,6 +43,7 @@ import configuration, { getPublicUiBasePath, getTrueForgeAuthMode, TrueForgeAuth
import type { AgentRecord, IAgentStore } from './db/agentStore';
import type { IMcpServerWithAuthStore } from './db/mcpServerStore';
import type { IModelProviderStore } from './db/modelProviderStore';
+import type { ISandboxEnvironmentStore } from './db/sandboxEnvironmentStore';
import type { ISandboxProviderStore } from './db/sandboxProviderStore';
import type { IScheduleStore } from './db/scheduleStore';
import type { ISessionMetricsStore } from './db/sessionMetricsStore';
@@ -193,6 +195,8 @@ export interface ServerDeps {
* (`TRUEFOUNDRY_SANDBOX_*` + static SETTINGS JSON).
*/
resolveSandboxProviderStore: (c: Context) => ISandboxProviderStore;
+ /** Per-request store: DB sandbox environments, or TrueFoundry empty stub. */
+ resolveSandboxEnvironmentStore: (c: Context) => ISandboxEnvironmentStore;
/** Per-request store: DB git skills, or TrueFoundry registry catalog in TrueFoundry mode. */
resolveSkillStore: ResolveSkillStore;
withTransaction: WithTransaction;
@@ -336,6 +340,7 @@ export function createServerApp(deps: ServerDeps) {
resolveMcpServerStore: deps.resolveMcpServerStore,
resolveSkillStore: deps.resolveSkillStore,
resolveSandboxProviderStore: deps.resolveSandboxProviderStore,
+ resolveSandboxEnvironmentStore: deps.resolveSandboxEnvironmentStore,
withTransaction: deps.withTransaction,
resolveRequestContext,
authorizer: deps.authorizer,
@@ -343,6 +348,19 @@ export function createServerApp(deps: ServerDeps) {
authMiddleware,
),
);
+ app.route(
+ '/api/v1/sandbox-environments',
+ withAuth(
+ createSandboxEnvironmentsRouter({
+ resolveSandboxEnvironmentStore: deps.resolveSandboxEnvironmentStore,
+ resolveSandboxProviderStore: deps.resolveSandboxProviderStore,
+ agentStore: deps.agentStore,
+ withTransaction: deps.withTransaction,
+ resolveRequestContext,
+ }),
+ authMiddleware,
+ ),
+ );
app.route(
'/api/internal/schedules',
withAuth(createScheduleExecutionRouter(scheduleTurnDeps), scheduleExecutionAuthMiddleware),
@@ -396,6 +414,7 @@ export function createServerApp(deps: ServerDeps) {
resolveSkillStore: deps.resolveSkillStore,
resolveAgentStore: deps.resolveAgentStore,
resolveSandboxProviderStore: deps.resolveSandboxProviderStore,
+ resolveSandboxEnvironmentStore: deps.resolveSandboxEnvironmentStore,
resolveRequestContext,
authorizer: deps.authorizer,
}),
@@ -439,6 +458,7 @@ export function createServerApp(deps: ServerDeps) {
resolveSkillStore: deps.resolveSkillStore,
resolveAgentStore: deps.resolveAgentStore,
resolveSandboxProviderStore: deps.resolveSandboxProviderStore,
+ resolveSandboxEnvironmentStore: deps.resolveSandboxEnvironmentStore,
redis: deps.redis,
requestReplyRouter: deps.requestReplyRouter,
resolveRequestContext,
diff --git a/packages/trueforge/src/auth/authorizer.ts b/packages/trueforge/src/auth/authorizer.ts
index c7724baf9..6d43e60a8 100644
--- a/packages/trueforge/src/auth/authorizer.ts
+++ b/packages/trueforge/src/auth/authorizer.ts
@@ -9,6 +9,7 @@ import {
SCHEDULE_OWNER_PERMISSIONS,
SESSION_OWNER_PERMISSIONS,
TENANT_CREATE_AGENT_PERMISSIONS,
+ TENANT_CREATE_SANDBOX_ENVIRONMENT_PERMISSIONS,
type ListPermissionsData,
} from '../schemas/permissions';
import type { RequestContext } from './identity';
@@ -49,7 +50,10 @@ export class TrueForgeAuthorizer implements Authorizer {
async getPermissions(input: GetPermissionsInput): Promise {
if (input.resourceType === 'tenant') {
- return listPermissionsData('tenant', { agent: [...TENANT_CREATE_AGENT_PERMISSIONS] });
+ return listPermissionsData('tenant', {
+ agent: [...TENANT_CREATE_AGENT_PERMISSIONS],
+ 'sandbox-environment': [...TENANT_CREATE_SANDBOX_ENVIRONMENT_PERMISSIONS],
+ });
}
const data = emptyPermissionsByResourceId(input.resourceIds);
diff --git a/packages/trueforge/src/db/agentStore.ts b/packages/trueforge/src/db/agentStore.ts
index 8430680d8..8e61a5cc8 100644
--- a/packages/trueforge/src/db/agentStore.ts
+++ b/packages/trueforge/src/db/agentStore.ts
@@ -91,6 +91,11 @@ export interface DeleteAgentInput {
id: string;
}
+export interface ListAgentIdsUsingSandboxEnvironmentInput {
+ tenant_id: string;
+ environment_name: string;
+}
+
/** Unique `(tenant_id, name)` violation on create. */
export class AgentNameConflictError extends Error {
readonly tenant_id: string;
@@ -136,4 +141,9 @@ export interface IAgentStore {
updateAgent(input: UpdateAgentInput, transaction?: TTransaction): Promise;
/** Deletes by immutable id. Idempotent if already missing. */
deleteAgent(input: DeleteAgentInput, transaction?: TTransaction): Promise;
+ /** Agent ids whose manifest references `environment_name` under config.sandbox.environment. */
+ listAgentIdsUsingSandboxEnvironment(
+ input: ListAgentIdsUsingSandboxEnvironmentInput,
+ transaction?: TTransaction,
+ ): Promise;
}
diff --git a/packages/trueforge/src/db/indexes.ts b/packages/trueforge/src/db/indexes.ts
index 22ab4c7b5..5511220b1 100644
--- a/packages/trueforge/src/db/indexes.ts
+++ b/packages/trueforge/src/db/indexes.ts
@@ -30,3 +30,9 @@ export const SCHEDULE_RUN_CREATED_BY_SUBJECT_ID_IDX = 'schedule_run_created_by_s
/** `(tenant_id, name)` unique on sandbox_provider. */
export const SANDBOX_PROVIDER_TENANT_NAME_UQ = 'sandbox_provider_tenant_name_uq';
+
+/** `(tenant_id, name)` unique on sandbox_environment. */
+export const SANDBOX_ENVIRONMENT_TENANT_NAME_UQ = 'sandbox_environment_tenant_name_uq';
+
+/** `(tenant_id, created_by_subject.subject_id)` on sandbox_environment. */
+export const SANDBOX_ENVIRONMENT_CREATED_BY_SUBJECT_ID_IDX = 'sandbox_environment_created_by_subject_id_idx';
diff --git a/packages/trueforge/src/db/postgres/agent-store/PostgresAgentStore.ts b/packages/trueforge/src/db/postgres/agent-store/PostgresAgentStore.ts
index 5a647a839..53fc022c6 100644
--- a/packages/trueforge/src/db/postgres/agent-store/PostgresAgentStore.ts
+++ b/packages/trueforge/src/db/postgres/agent-store/PostgresAgentStore.ts
@@ -17,6 +17,7 @@ import {
type GetExternalIdsByIdsInput,
type GetOwnedIdsInput,
type IAgentStore,
+ type ListAgentIdsUsingSandboxEnvironmentInput,
type ListAgentsInput,
type UpdateAgentInput,
} from '../../agentStore';
@@ -205,4 +206,18 @@ export class PostgresAgentStore implements IAgentStore> {
const db = transaction ?? this.#db;
await db.deleteFrom('agent').where('tenant_id', '=', input.tenant_id).where('id', '=', input.id).execute();
}
+
+ async listAgentIdsUsingSandboxEnvironment(
+ input: ListAgentIdsUsingSandboxEnvironmentInput,
+ transaction?: Transaction,
+ ): Promise {
+ const db = transaction ?? this.#db;
+ const rows = await db
+ .selectFrom('agent')
+ .select('id')
+ .where('tenant_id', '=', input.tenant_id)
+ .where(sql`manifest->'config'->'sandbox'->>'environment'`, '=', input.environment_name)
+ .execute();
+ return rows.map(row => row.id);
+ }
}
diff --git a/packages/trueforge/src/db/postgres/migrations/20260918_000003_sandbox_environment.ts b/packages/trueforge/src/db/postgres/migrations/20260918_000003_sandbox_environment.ts
new file mode 100644
index 000000000..e13f9699f
--- /dev/null
+++ b/packages/trueforge/src/db/postgres/migrations/20260918_000003_sandbox_environment.ts
@@ -0,0 +1,35 @@
+import { sql, type Kysely } from 'kysely';
+import { SANDBOX_ENVIRONMENT_CREATED_BY_SUBJECT_ID_IDX, SANDBOX_ENVIRONMENT_TENANT_NAME_UQ } from '../../indexes';
+
+/**
+ * Owned sandbox environments: immutable ULID `id` PK, unique `(tenant_id, name)`,
+ * Zod-validated `manifest` jsonb, creator in `created_by_subject`.
+ */
+export async function up(db: Kysely): Promise {
+ await sql`SET LOCAL lock_timeout = '5s'`.execute(db);
+
+ await db.schema
+ .createTable('sandbox_environment')
+ .addColumn('id', 'text', col => col.notNull())
+ .addColumn('tenant_id', 'text', col => col.notNull())
+ .addColumn('name', 'text', col => col.notNull())
+ .addColumn('description', 'text')
+ .addColumn('manifest', 'jsonb', col => col.notNull())
+ .addColumn('created_by_subject', 'jsonb', col => col.notNull())
+ .addColumn('created_at', 'timestamptz', col => col.notNull())
+ .addColumn('updated_at', 'timestamptz', col => col.notNull())
+ .addPrimaryKeyConstraint('sandbox_environment_pkey', ['id'])
+ .addUniqueConstraint(SANDBOX_ENVIRONMENT_TENANT_NAME_UQ, ['tenant_id', 'name'])
+ .execute();
+
+ await sql`
+ CREATE INDEX ${sql.raw(SANDBOX_ENVIRONMENT_CREATED_BY_SUBJECT_ID_IDX)}
+ ON sandbox_environment (tenant_id, (created_by_subject->>'subject_id'))
+ `.execute(db);
+}
+
+export async function down(db: Kysely): Promise {
+ await sql`SET LOCAL lock_timeout = '5s'`.execute(db);
+ await sql`DROP INDEX IF EXISTS ${sql.raw(SANDBOX_ENVIRONMENT_CREATED_BY_SUBJECT_ID_IDX)}`.execute(db);
+ await db.schema.dropTable('sandbox_environment').ifExists().execute();
+}
diff --git a/packages/trueforge/src/db/postgres/sandbox-environment-store/PostgresSandboxEnvironmentStore.ts b/packages/trueforge/src/db/postgres/sandbox-environment-store/PostgresSandboxEnvironmentStore.ts
new file mode 100644
index 000000000..980db65ea
--- /dev/null
+++ b/packages/trueforge/src/db/postgres/sandbox-environment-store/PostgresSandboxEnvironmentStore.ts
@@ -0,0 +1,157 @@
+import { CreatedBySubjectSchema, type TokenPagination } from '@truefoundry/trueforge-core/agent-session';
+import {
+ decodeOffsetPageToken,
+ paginateOffsetRows,
+} from '@truefoundry/trueforge-core/agent-session/store/OffsetPageToken';
+import { sql, type Kysely, type Selectable, type Transaction } from 'kysely';
+import { newId } from '../../../utils/id';
+import {
+ parseStoredSandboxEnvironmentManifest,
+ SandboxEnvironmentNameConflictError,
+ type CreateSandboxEnvironmentInput,
+ type DeleteSandboxEnvironmentInput,
+ type GetOwnedIdsInput,
+ type GetSandboxEnvironmentInput,
+ type ISandboxEnvironmentStore,
+ type ListSandboxEnvironmentsInput,
+ type SandboxEnvironmentRecord,
+ type UpdateSandboxEnvironmentInput,
+} from '../../sandboxEnvironmentStore';
+import { isUniqueViolation } from '../client';
+import { json, now } from '../sqlExpressions';
+import type { Database, SandboxEnvironmentTable } from '../types';
+
+function toRecord(row: Selectable): SandboxEnvironmentRecord {
+ return {
+ id: row.id,
+ tenant_id: row.tenant_id,
+ name: row.name,
+ description: row.description,
+ manifest: parseStoredSandboxEnvironmentManifest(row.manifest),
+ created_by_subject: CreatedBySubjectSchema.parse(row.created_by_subject),
+ created_at: row.created_at.toISOString(),
+ updated_at: row.updated_at.toISOString(),
+ };
+}
+
+export class PostgresSandboxEnvironmentStore implements ISandboxEnvironmentStore> {
+ readonly #db: Kysely;
+
+ constructor(db: Kysely) {
+ this.#db = db;
+ }
+
+ async createSandboxEnvironment(
+ input: CreateSandboxEnvironmentInput,
+ transaction?: Transaction,
+ ): Promise {
+ const db = transaction ?? this.#db;
+ try {
+ const row = await db
+ .insertInto('sandbox_environment')
+ .values({
+ id: newId(),
+ tenant_id: input.tenant_id,
+ name: input.name,
+ description: input.description,
+ manifest: json(input.manifest),
+ created_by_subject: json(input.created_by_subject),
+ created_at: now(),
+ updated_at: now(),
+ })
+ .returningAll()
+ .executeTakeFirstOrThrow();
+ return toRecord(row);
+ } catch (error) {
+ if (isUniqueViolation(error)) {
+ throw new SandboxEnvironmentNameConflictError(
+ { tenant_id: input.tenant_id, name: input.name },
+ { cause: error },
+ );
+ }
+ throw error;
+ }
+ }
+
+ async getSandboxEnvironment(
+ input: GetSandboxEnvironmentInput,
+ transaction?: Transaction,
+ ): Promise {
+ const db = transaction ?? this.#db;
+ let query = db.selectFrom('sandbox_environment').selectAll().where('tenant_id', '=', input.tenant_id);
+ if ('id' in input) {
+ query = query.where('id', '=', input.id);
+ } else {
+ query = query.where('name', '=', input.name);
+ }
+ const row = await query.executeTakeFirst();
+ return row === undefined ? undefined : toRecord(row);
+ }
+
+ async listSandboxEnvironments(
+ input: ListSandboxEnvironmentsInput,
+ transaction?: Transaction,
+ ): Promise<{ data: SandboxEnvironmentRecord[]; pagination: TokenPagination }> {
+ const offset = decodeOffsetPageToken(input.page_token);
+ const db = transaction ?? this.#db;
+ const rows = await db
+ .selectFrom('sandbox_environment')
+ .selectAll()
+ .where('tenant_id', '=', input.tenant_id)
+ .where(sql`created_by_subject->>'subject_id'`, '=', input.created_by_subject_id)
+ .orderBy('created_at', 'desc')
+ .orderBy('id')
+ .limit(input.limit + 1)
+ .offset(offset)
+ .execute();
+ const { data, pagination } = paginateOffsetRows(rows, input.limit, offset);
+ return { data: data.map(toRecord), pagination };
+ }
+
+ async updateSandboxEnvironment(
+ input: UpdateSandboxEnvironmentInput,
+ transaction?: Transaction,
+ ): Promise {
+ const db = transaction ?? this.#db;
+ const row = await db
+ .updateTable('sandbox_environment')
+ .set({
+ ...(input.description === undefined ? {} : { description: input.description }),
+ manifest: json(input.manifest),
+ updated_at: now(),
+ })
+ .where('tenant_id', '=', input.tenant_id)
+ .where('id', '=', input.id)
+ .returningAll()
+ .executeTakeFirst();
+ return row === undefined ? undefined : toRecord(row);
+ }
+
+ async deleteSandboxEnvironment(
+ input: DeleteSandboxEnvironmentInput,
+ transaction?: Transaction,
+ ): Promise {
+ const db = transaction ?? this.#db;
+ const result = await db
+ .deleteFrom('sandbox_environment')
+ .where('tenant_id', '=', input.tenant_id)
+ .where('id', '=', input.id)
+ .executeTakeFirst();
+ return Number(result.numDeletedRows) > 0;
+ }
+
+ async getOwnedIds(input: GetOwnedIdsInput, transaction?: Transaction): Promise {
+ if (input.ids.length === 0) {
+ return [];
+ }
+ const db = transaction ?? this.#db;
+ const rows = await db
+ .selectFrom('sandbox_environment')
+ .select('id')
+ .where('tenant_id', '=', input.tenant_id)
+ .where('id', 'in', [...input.ids])
+ .where(sql`created_by_subject->>'subject_id'`, '=', input.subject_id)
+ .execute();
+ return rows.map(row => row.id);
+ }
+}
diff --git a/packages/trueforge/src/db/postgres/types.ts b/packages/trueforge/src/db/postgres/types.ts
index 7723013e9..edd5c5c61 100644
--- a/packages/trueforge/src/db/postgres/types.ts
+++ b/packages/trueforge/src/db/postgres/types.ts
@@ -25,6 +25,7 @@ import type { CurrentContextUsage } from '@truefoundry/trueforge-core/core/runti
import type { ColumnType, Generated, JSONColumnType } from 'kysely';
import type { McpServerManifest } from '../../schemas/mcpServer';
import type { ModelProviderManifest } from '../../schemas/modelProvider';
+import type { StoredSandboxEnvironmentManifest } from '../../schemas/sandboxEnvironment';
import type {
SandboxBuildMetadata,
SandboxBuildStatus,
@@ -377,6 +378,25 @@ export interface SandboxProviderTable {
updated_at: Date;
}
+/**
+ * Configured sandbox environments — immutable ULID `id` PK; UNIQUE (tenant_id, name).
+ * PRIMARY KEY (id)
+ */
+export interface SandboxEnvironmentTable {
+ id: string;
+ tenant_id: string;
+ name: string;
+ description: string | null;
+ manifest: JSONColumnType<
+ StoredSandboxEnvironmentManifest,
+ StoredSandboxEnvironmentManifest,
+ StoredSandboxEnvironmentManifest
+ >;
+ created_by_subject: JSONColumnType;
+ created_at: Date;
+ updated_at: Date;
+}
+
/**
* Configured agents — immutable ULID `id` PK; UNIQUE immutable (tenant_id, name).
* PRIMARY KEY (id)
@@ -527,6 +547,7 @@ export interface Database {
model_provider: ModelProviderTable;
skill: SkillTable;
sandbox_provider: SandboxProviderTable;
+ sandbox_environment: SandboxEnvironmentTable;
agent: AgentTable;
schedule: ScheduleTable;
schedule_run: ScheduleRunTable;
diff --git a/packages/trueforge/src/db/sandboxEnvironmentStore.ts b/packages/trueforge/src/db/sandboxEnvironmentStore.ts
new file mode 100644
index 000000000..05a58c5e0
--- /dev/null
+++ b/packages/trueforge/src/db/sandboxEnvironmentStore.ts
@@ -0,0 +1,97 @@
+/**
+ * DB-backed sandbox environments: owned per creator within a tenant.
+ * Immutable ULID `id`, unique `name` within a tenant, Zod-validated manifest jsonb.
+ * Implementations: PostgresSandboxEnvironmentStore and SqliteSandboxEnvironmentStore.
+ */
+import type { CreatedBySubject, TokenPagination } from '@truefoundry/trueforge-core/agent-session';
+import type { ResourceName } from '../schemas/common';
+import {
+ StoredSandboxEnvironmentManifestSchema,
+ type StoredSandboxEnvironmentManifest,
+} from '../schemas/sandboxEnvironment';
+
+/** Re-parse persisted manifest JSON so schema defaults materialize for older rows. */
+export function parseStoredSandboxEnvironmentManifest(manifest: unknown): StoredSandboxEnvironmentManifest {
+ return StoredSandboxEnvironmentManifestSchema.parse(manifest);
+}
+
+export interface SandboxEnvironmentRecord {
+ id: string;
+ tenant_id: string;
+ name: ResourceName;
+ description: string | null;
+ manifest: StoredSandboxEnvironmentManifest;
+ created_by_subject: CreatedBySubject;
+ /** ISO-8601 UTC instant. */
+ created_at: string;
+ /** ISO-8601 UTC instant. */
+ updated_at: string;
+}
+
+export type GetSandboxEnvironmentInput = { tenant_id: string } & ({ id: string } | { name: string });
+
+export interface ListSandboxEnvironmentsInput {
+ tenant_id: string;
+ created_by_subject_id: string;
+ limit: number;
+ page_token: string | undefined;
+}
+
+export interface GetOwnedIdsInput {
+ tenant_id: string;
+ ids: readonly string[];
+ subject_id: string;
+}
+
+export interface CreateSandboxEnvironmentInput {
+ tenant_id: string;
+ name: ResourceName;
+ description: string | null;
+ manifest: StoredSandboxEnvironmentManifest;
+ created_by_subject: CreatedBySubject;
+}
+
+export interface UpdateSandboxEnvironmentInput {
+ tenant_id: string;
+ id: string;
+ description: string | null | undefined;
+ manifest: StoredSandboxEnvironmentManifest;
+}
+
+export interface DeleteSandboxEnvironmentInput {
+ tenant_id: string;
+ id: string;
+}
+
+export class SandboxEnvironmentNameConflictError extends Error {
+ readonly tenant_id: string;
+ readonly conflict_name: string;
+
+ constructor(input: { tenant_id: string; name: string }, options?: ErrorOptions) {
+ super(`Sandbox environment name "${input.name}" already exists`, options);
+ this.name = 'SandboxEnvironmentNameConflictError';
+ this.tenant_id = input.tenant_id;
+ this.conflict_name = input.name;
+ }
+}
+
+export interface ISandboxEnvironmentStore {
+ createSandboxEnvironment(
+ input: CreateSandboxEnvironmentInput,
+ transaction?: TTransaction,
+ ): Promise;
+ getSandboxEnvironment(
+ input: GetSandboxEnvironmentInput,
+ transaction?: TTransaction,
+ ): Promise;
+ listSandboxEnvironments(
+ input: ListSandboxEnvironmentsInput,
+ transaction?: TTransaction,
+ ): Promise<{ data: SandboxEnvironmentRecord[]; pagination: TokenPagination }>;
+ updateSandboxEnvironment(
+ input: UpdateSandboxEnvironmentInput,
+ transaction?: TTransaction,
+ ): Promise;
+ deleteSandboxEnvironment(input: DeleteSandboxEnvironmentInput, transaction?: TTransaction): Promise;
+ getOwnedIds(input: GetOwnedIdsInput, transaction?: TTransaction): Promise;
+}
diff --git a/packages/trueforge/src/db/sqlite/agent-store/SqliteAgentStore.ts b/packages/trueforge/src/db/sqlite/agent-store/SqliteAgentStore.ts
index 27b1daa6a..117c4b8fd 100644
--- a/packages/trueforge/src/db/sqlite/agent-store/SqliteAgentStore.ts
+++ b/packages/trueforge/src/db/sqlite/agent-store/SqliteAgentStore.ts
@@ -22,6 +22,7 @@ import {
type GetExternalIdsByIdsInput,
type GetOwnedIdsInput,
type IAgentStore,
+ type ListAgentIdsUsingSandboxEnvironmentInput,
type ListAgentsInput,
type UpdateAgentInput,
} from '../../agentStore';
@@ -213,6 +214,20 @@ export class SqliteAgentStore implements IAgentStore> {
await db.deleteFrom('agent').where('tenant_id', '=', input.tenant_id).where('id', '=', input.id).execute();
}
+ async listAgentIdsUsingSandboxEnvironment(
+ input: ListAgentIdsUsingSandboxEnvironmentInput,
+ transaction?: Transaction,
+ ): Promise {
+ const db = transaction ?? this.#db;
+ const rows = await db
+ .selectFrom('agent')
+ .select('id')
+ .where('tenant_id', '=', input.tenant_id)
+ .where(sql`json_extract(manifest, '$.config.sandbox.environment')`, '=', input.environment_name)
+ .execute();
+ return rows.map(row => row.id);
+ }
+
/** Map unique violations to external_id vs name conflicts. */
private async throwCreateUnique({
error,
diff --git a/packages/trueforge/src/db/sqlite/migrations/20260918_000003_sandbox_environment.ts b/packages/trueforge/src/db/sqlite/migrations/20260918_000003_sandbox_environment.ts
new file mode 100644
index 000000000..45e71fe2a
--- /dev/null
+++ b/packages/trueforge/src/db/sqlite/migrations/20260918_000003_sandbox_environment.ts
@@ -0,0 +1,45 @@
+import { sql, type Kysely } from 'kysely';
+import { SANDBOX_ENVIRONMENT_CREATED_BY_SUBJECT_ID_IDX, SANDBOX_ENVIRONMENT_TENANT_NAME_UQ } from '../../indexes';
+
+/**
+ * Owned sandbox environments for SQLite — mirrors
+ * db/postgres/migrations/20260918_000002_sandbox_environment.ts.
+ *
+ * SQLite differences: `manifest` / `created_by_subject` are BLOB JSONB, timestamps
+ * are ISO TEXT, table is STRICT.
+ */
+export async function up(db: Kysely): Promise {
+ await db.transaction().execute(async trx => {
+ await sql`
+ CREATE TABLE sandbox_environment (
+ id TEXT NOT NULL,
+ tenant_id TEXT NOT NULL,
+ name TEXT NOT NULL,
+ description TEXT,
+ manifest BLOB NOT NULL,
+ created_by_subject BLOB NOT NULL,
+ created_at TEXT NOT NULL,
+ updated_at TEXT NOT NULL,
+ PRIMARY KEY (id)
+ ) STRICT
+ `.execute(trx);
+
+ await sql`
+ CREATE UNIQUE INDEX ${sql.raw(SANDBOX_ENVIRONMENT_TENANT_NAME_UQ)}
+ ON sandbox_environment (tenant_id, name)
+ `.execute(trx);
+
+ await sql`
+ CREATE INDEX ${sql.raw(SANDBOX_ENVIRONMENT_CREATED_BY_SUBJECT_ID_IDX)}
+ ON sandbox_environment (tenant_id, json_extract(created_by_subject, '$.subject_id'))
+ `.execute(trx);
+ });
+}
+
+export async function down(db: Kysely): Promise {
+ await db.transaction().execute(async trx => {
+ await sql`DROP INDEX IF EXISTS ${sql.raw(SANDBOX_ENVIRONMENT_CREATED_BY_SUBJECT_ID_IDX)}`.execute(trx);
+ await sql`DROP INDEX IF EXISTS ${sql.raw(SANDBOX_ENVIRONMENT_TENANT_NAME_UQ)}`.execute(trx);
+ await sql`DROP TABLE IF EXISTS sandbox_environment`.execute(trx);
+ });
+}
diff --git a/packages/trueforge/src/db/sqlite/sandbox-environment-store/SqliteSandboxEnvironmentStore.ts b/packages/trueforge/src/db/sqlite/sandbox-environment-store/SqliteSandboxEnvironmentStore.ts
new file mode 100644
index 000000000..379704fee
--- /dev/null
+++ b/packages/trueforge/src/db/sqlite/sandbox-environment-store/SqliteSandboxEnvironmentStore.ts
@@ -0,0 +1,185 @@
+import {
+ CreatedBySubjectSchema,
+ type CreatedBySubject,
+ type TokenPagination,
+} from '@truefoundry/trueforge-core/agent-session';
+import {
+ decodeOffsetPageToken,
+ paginateOffsetRows,
+} from '@truefoundry/trueforge-core/agent-session/store/OffsetPageToken';
+import { sql, type ExpressionBuilder, type Kysely, type Transaction } from 'kysely';
+import type { StoredSandboxEnvironmentManifest } from '../../../schemas/sandboxEnvironment';
+import { newId } from '../../../utils/id';
+import {
+ parseStoredSandboxEnvironmentManifest,
+ SandboxEnvironmentNameConflictError,
+ type CreateSandboxEnvironmentInput,
+ type DeleteSandboxEnvironmentInput,
+ type GetOwnedIdsInput,
+ type GetSandboxEnvironmentInput,
+ type ISandboxEnvironmentStore,
+ type ListSandboxEnvironmentsInput,
+ type SandboxEnvironmentRecord,
+ type UpdateSandboxEnvironmentInput,
+} from '../../sandboxEnvironmentStore';
+import { isUniqueViolation } from '../client';
+import { jsonbBind, jsonText, nowIso } from '../sqlExpressions';
+import type { Database } from '../types';
+
+function environmentColumns(eb: ExpressionBuilder) {
+ return [
+ 'id' as const,
+ 'tenant_id' as const,
+ 'name' as const,
+ 'description' as const,
+ jsonText(eb.ref('manifest')).as('manifest'),
+ jsonText(eb.ref('created_by_subject')).as('created_by_subject'),
+ 'created_at' as const,
+ 'updated_at' as const,
+ ];
+}
+
+interface SandboxEnvironmentRow {
+ id: string;
+ tenant_id: string;
+ name: string;
+ description: string | null;
+ manifest: StoredSandboxEnvironmentManifest;
+ created_by_subject: CreatedBySubject;
+ created_at: string;
+ updated_at: string;
+}
+
+function toRecord(row: SandboxEnvironmentRow): SandboxEnvironmentRecord {
+ return {
+ ...row,
+ manifest: parseStoredSandboxEnvironmentManifest(row.manifest),
+ created_by_subject: CreatedBySubjectSchema.parse(row.created_by_subject),
+ };
+}
+
+export class SqliteSandboxEnvironmentStore implements ISandboxEnvironmentStore> {
+ readonly #db: Kysely;
+
+ constructor(db: Kysely) {
+ this.#db = db;
+ }
+
+ async createSandboxEnvironment(
+ input: CreateSandboxEnvironmentInput,
+ transaction?: Transaction,
+ ): Promise {
+ const db = transaction ?? this.#db;
+ const timestamp = nowIso();
+ try {
+ const row = await db
+ .insertInto('sandbox_environment')
+ .values({
+ id: newId(),
+ tenant_id: input.tenant_id,
+ name: input.name,
+ description: input.description,
+ manifest: jsonbBind(input.manifest),
+ created_by_subject: jsonbBind(input.created_by_subject),
+ created_at: timestamp,
+ updated_at: timestamp,
+ })
+ .returning(environmentColumns)
+ .executeTakeFirstOrThrow();
+ return toRecord(row);
+ } catch (error) {
+ if (isUniqueViolation(error)) {
+ throw new SandboxEnvironmentNameConflictError(
+ { tenant_id: input.tenant_id, name: input.name },
+ { cause: error },
+ );
+ }
+ throw error;
+ }
+ }
+
+ async getSandboxEnvironment(
+ input: GetSandboxEnvironmentInput,
+ transaction?: Transaction,
+ ): Promise {
+ const db = transaction ?? this.#db;
+ let query = db
+ .selectFrom('sandbox_environment')
+ .select(environmentColumns)
+ .where('tenant_id', '=', input.tenant_id);
+ if ('id' in input) {
+ query = query.where('id', '=', input.id);
+ } else {
+ query = query.where('name', '=', input.name);
+ }
+ const row = await query.executeTakeFirst();
+ return row === undefined ? undefined : toRecord(row);
+ }
+
+ async listSandboxEnvironments(
+ input: ListSandboxEnvironmentsInput,
+ transaction?: Transaction,
+ ): Promise<{ data: SandboxEnvironmentRecord[]; pagination: TokenPagination }> {
+ const offset = decodeOffsetPageToken(input.page_token);
+ const db = transaction ?? this.#db;
+ const rows = await db
+ .selectFrom('sandbox_environment')
+ .select(environmentColumns)
+ .where('tenant_id', '=', input.tenant_id)
+ .where(sql`json_extract(created_by_subject, '$.subject_id')`, '=', input.created_by_subject_id)
+ .orderBy('created_at', 'desc')
+ .orderBy('id')
+ .limit(input.limit + 1)
+ .offset(offset)
+ .execute();
+ const { data, pagination } = paginateOffsetRows(rows, input.limit, offset);
+ return { data: data.map(toRecord), pagination };
+ }
+
+ async updateSandboxEnvironment(
+ input: UpdateSandboxEnvironmentInput,
+ transaction?: Transaction,
+ ): Promise {
+ const db = transaction ?? this.#db;
+ const row = await db
+ .updateTable('sandbox_environment')
+ .set({
+ ...(input.description === undefined ? {} : { description: input.description }),
+ manifest: jsonbBind(input.manifest),
+ updated_at: nowIso(),
+ })
+ .where('tenant_id', '=', input.tenant_id)
+ .where('id', '=', input.id)
+ .returning(environmentColumns)
+ .executeTakeFirst();
+ return row === undefined ? undefined : toRecord(row);
+ }
+
+ async deleteSandboxEnvironment(
+ input: DeleteSandboxEnvironmentInput,
+ transaction?: Transaction,
+ ): Promise {
+ const db = transaction ?? this.#db;
+ const result = await db
+ .deleteFrom('sandbox_environment')
+ .where('tenant_id', '=', input.tenant_id)
+ .where('id', '=', input.id)
+ .executeTakeFirst();
+ return Number(result.numDeletedRows) > 0;
+ }
+
+ async getOwnedIds(input: GetOwnedIdsInput, transaction?: Transaction): Promise {
+ if (input.ids.length === 0) {
+ return [];
+ }
+ const db = transaction ?? this.#db;
+ const rows = await db
+ .selectFrom('sandbox_environment')
+ .select('id')
+ .where('tenant_id', '=', input.tenant_id)
+ .where('id', 'in', [...input.ids])
+ .where(sql`json_extract(created_by_subject, '$.subject_id')`, '=', input.subject_id)
+ .execute();
+ return rows.map(row => row.id);
+ }
+}
diff --git a/packages/trueforge/src/db/sqlite/types.ts b/packages/trueforge/src/db/sqlite/types.ts
index e19c679d4..2a4ad4fa5 100644
--- a/packages/trueforge/src/db/sqlite/types.ts
+++ b/packages/trueforge/src/db/sqlite/types.ts
@@ -28,6 +28,7 @@ import type { CurrentContextUsage } from '@truefoundry/trueforge-core/core/runti
import type { ColumnType, Generated, JSONColumnType } from 'kysely';
import type { McpServerManifest } from '../../schemas/mcpServer';
import type { ModelProviderManifest } from '../../schemas/modelProvider';
+import type { StoredSandboxEnvironmentManifest } from '../../schemas/sandboxEnvironment';
import type {
SandboxBuildMetadata,
SandboxBuildStatus,
@@ -221,6 +222,22 @@ export interface SandboxProviderTable {
updated_at: string;
}
+/**
+ * Configured sandbox environments — mirrors the Postgres `sandbox_environment` table.
+ * PRIMARY KEY (id); UNIQUE (tenant_id, name).
+ */
+export interface SandboxEnvironmentTable {
+ id: string;
+ tenant_id: string;
+ name: string;
+ description: string | null;
+ /** StoredSandboxEnvironmentManifest document; replaced whole on every update */
+ manifest: JsonbColumn;
+ created_by_subject: JsonbColumn;
+ created_at: string;
+ updated_at: string;
+}
+
/**
* Configured agents — mirrors the Postgres `agent` table.
* PRIMARY KEY (id); UNIQUE (tenant_id, name).
@@ -347,6 +364,7 @@ export interface Database {
model_provider: ModelProviderTable;
skill: SkillTable;
sandbox_provider: SandboxProviderTable;
+ sandbox_environment: SandboxEnvironmentTable;
agent: AgentTable;
schedule: ScheduleTable;
schedule_run: ScheduleRunTable;
diff --git a/packages/trueforge/src/main.ts b/packages/trueforge/src/main.ts
index 944500498..d1a405ef3 100644
--- a/packages/trueforge/src/main.ts
+++ b/packages/trueforge/src/main.ts
@@ -73,6 +73,7 @@ import { McpServerWithAuthStore } from './db/McpServerWithAuthStore';
import type { IModelProviderStore } from './db/modelProviderStore';
import type { PostgresAgentStore } from './db/postgres/agent-store/PostgresAgentStore';
import type { Database as PostgresDatabase } from './db/postgres/types';
+import type { ISandboxEnvironmentStore } from './db/sandboxEnvironmentStore';
import type { ISandboxProviderStore } from './db/sandboxProviderStore';
import type { IScheduleStore } from './db/scheduleStore';
import type { ISessionMetricsStore } from './db/sessionMetricsStore';
@@ -118,6 +119,7 @@ interface ServerPersistence {
perServerHeaders?: PerServerMcpHeaders,
) => IMcpServerWithAuthStore;
resolveSandboxProviderStore: (rc: RequestContext) => ISandboxProviderStore;
+ resolveSandboxEnvironmentStore: (rc: RequestContext) => ISandboxEnvironmentStore;
/** Per-request store: DB git skills, or TrueFoundry registry catalog in TrueFoundry mode. */
resolveSkillStore: (rc: RequestContext) => ISkillStore;
resolveAgentStore: (rc: RequestContext) => IAgentStore;
@@ -292,6 +294,7 @@ async function createStandalonePersistence(options: {
import('./db/sqlite/token-store/SqliteOAuthTokenStore'),
import('./db/sqlite/skill-store/SqliteSkillStore'),
import('./db/sqlite/sandbox-provider-store/SqliteSandboxProviderStore'),
+ import('./db/sqlite/sandbox-environment-store/SqliteSandboxEnvironmentStore'),
import('./db/sqlite/agent-store/SqliteAgentStore'),
import('./db/sqlite/schedule-store/SqliteScheduleStore'),
]),
@@ -304,6 +307,7 @@ async function createStandalonePersistence(options: {
{ SqliteOAuthTokenStore },
{ SqliteSkillStore },
{ SqliteSandboxProviderStore },
+ { SqliteSandboxEnvironmentStore },
{ SqliteAgentStore },
{ SqliteScheduleStore },
] = sqliteStores;
@@ -322,6 +326,7 @@ async function createStandalonePersistence(options: {
clientName: configuration.MCP_DCR_OAUTH_CLIENT_NAME,
});
const sandboxProviderStore = new SqliteSandboxProviderStore(db);
+ const sandboxEnvironmentStore = new SqliteSandboxEnvironmentStore(db);
const skillStore = new SqliteSkillStore(db);
return {
withTransaction: callback => db.transaction().execute(callback),
@@ -333,6 +338,7 @@ async function createStandalonePersistence(options: {
resolveModelProviderStore: () => modelProviderStore,
resolveMcpServerStore: () => mcpServerStore,
resolveSandboxProviderStore: () => sandboxProviderStore,
+ resolveSandboxEnvironmentStore: () => sandboxEnvironmentStore,
resolveSkillStore: () => skillStore,
resolveAgentStore: () => agentStore,
resolveImportAgentStore: () => agentStore,
@@ -372,6 +378,7 @@ async function createDistributedPersistence(options: {
import('./db/postgres/token-store/PostgresOAuthTokenStore'),
import('./db/postgres/skill-store/PostgresSkillStore'),
import('./db/postgres/sandbox-provider-store/PostgresSandboxProviderStore'),
+ import('./db/postgres/sandbox-environment-store/PostgresSandboxEnvironmentStore'),
import('./db/postgres/agent-store/PostgresAgentStore'),
import('./db/postgres/schedule-store/PostgresScheduleStore'),
]),
@@ -384,6 +391,7 @@ async function createDistributedPersistence(options: {
{ PostgresOAuthTokenStore },
{ PostgresSkillStore },
{ PostgresSandboxProviderStore },
+ { PostgresSandboxEnvironmentStore },
{ PostgresAgentStore },
{ PostgresScheduleStore },
] = postgresStores;
@@ -408,6 +416,7 @@ async function createDistributedPersistence(options: {
clientName: configuration.MCP_DCR_OAUTH_CLIENT_NAME,
});
const sandboxProviderStore = new PostgresSandboxProviderStore(db);
+ const sandboxEnvironmentStore = new PostgresSandboxEnvironmentStore(db);
const skillStore = new PostgresSkillStore(db);
const agentStore = new PostgresAgentStore(db);
const turnSkillsResolverStore = buildTurnSkillsResolverStore({
@@ -452,6 +461,7 @@ async function createDistributedPersistence(options: {
const resolveSandboxProviderStore = buildResolveSandboxProviderStore({
persistenceStore: sandboxProviderStore,
});
+ const resolveSandboxEnvironmentStore = () => sandboxEnvironmentStore;
const resolveSkillStore = buildResolveSkillStore({
persistenceStore: skillStore,
client: serviceFoundryClient,
@@ -466,6 +476,7 @@ async function createDistributedPersistence(options: {
resolveModelProviderStore,
resolveMcpServerStore,
resolveSandboxProviderStore,
+ resolveSandboxEnvironmentStore,
resolveSkillStore,
resolveAgentStore,
resolveImportAgentStore,
@@ -561,6 +572,8 @@ async function createServerRuntime(persistence: ServerPersistence<
};
const resolveAgentStore = (c: Context) => persistence.resolveAgentStore(resolveRequestContext(c));
const resolveSandboxProviderStore = (c: Context) => persistence.resolveSandboxProviderStore(resolveRequestContext(c));
+ const resolveSandboxEnvironmentStore = (c: Context) =>
+ persistence.resolveSandboxEnvironmentStore(resolveRequestContext(c));
const resolveSkillStore = (c: Context) => {
const store = persistence.resolveSkillStore(resolveRequestContext(c));
if (!isTrueFoundryModeEnabled(configuration)) {
@@ -582,6 +595,7 @@ async function createServerRuntime(persistence: ServerPersistence<
resolveAgentStore,
resolveImportAgentStore,
resolveSandboxProviderStore,
+ resolveSandboxEnvironmentStore,
resolveSkillStore,
withTransaction,
tokenStore,
diff --git a/packages/trueforge/src/routes/sandboxEnvironmentRoutes.ts b/packages/trueforge/src/routes/sandboxEnvironmentRoutes.ts
new file mode 100644
index 000000000..63b2fb099
--- /dev/null
+++ b/packages/trueforge/src/routes/sandboxEnvironmentRoutes.ts
@@ -0,0 +1,197 @@
+/**
+ * Sandbox environment route definitions (mounted at /api/v1/sandbox-environments).
+ * Handlers are registered in apis/sandboxEnvironments.ts.
+ */
+import { createRoute, z } from '@hono/zod-openapi';
+import { PAGE_LIMIT } from '../schemas/common';
+import { RequestErrorResponseSchema } from '../schemas/errors';
+import {
+ CreateSandboxEnvironmentRequestSchema,
+ DeleteSandboxEnvironmentResponseSchema,
+ GetSandboxEnvironmentResponseSchema,
+ ListSandboxEnvironmentsResponseSchema,
+ UpdateSandboxEnvironmentRequestSchema,
+} from '../schemas/sandboxEnvironment';
+import { TOKEN_PAGINATION } from './fernExtensions';
+import { OpenApiTag } from './openapiTags';
+
+export const SandboxEnvironmentIdParamsSchema = z.object({
+ sandbox_environment_id: z.string().min(1).max(64).describe('Immutable sandbox environment identifier.'),
+});
+
+export const ListSandboxEnvironmentsQuerySchema = z
+ .object({
+ limit: z.coerce
+ .number()
+ .int()
+ .min(1)
+ .max(PAGE_LIMIT)
+ .optional()
+ .default(PAGE_LIMIT)
+ .describe(`Page size. Defaults to ${String(PAGE_LIMIT)}`),
+ page_token: z.string().optional().describe('Opaque token from a previous response `next_page_token`.'),
+ })
+ .openapi('ListSandboxEnvironmentsQuery');
+
+export const listSandboxEnvironmentsRoute = createRoute({
+ method: 'get',
+ path: '/',
+ tags: [OpenApiTag.SANDBOXES],
+ summary: 'List sandbox environments',
+ description: 'List sandbox environments created by the authenticated subject, newest first.',
+ 'x-fern-sdk-group-name': ['sandbox_environments'],
+ 'x-fern-sdk-method-name': 'list',
+ 'x-fern-pagination': TOKEN_PAGINATION,
+ request: {
+ query: ListSandboxEnvironmentsQuerySchema,
+ },
+ responses: {
+ 200: {
+ content: { 'application/json': { schema: ListSandboxEnvironmentsResponseSchema } },
+ description: 'Paginated caller-owned sandbox environments.',
+ },
+ 400: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Invalid query parameters or page token.',
+ },
+ 401: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Unauthenticated.',
+ },
+ },
+});
+
+export const createSandboxEnvironmentRoute = createRoute({
+ method: 'post',
+ path: '/',
+ tags: [OpenApiTag.SANDBOXES],
+ summary: 'Create a sandbox environment',
+ description: 'Create a Daytona sandbox environment owned by the authenticated subject.',
+ 'x-fern-sdk-group-name': ['sandbox_environments'],
+ 'x-fern-sdk-method-name': 'create',
+ request: {
+ body: {
+ content: { 'application/json': { schema: CreateSandboxEnvironmentRequestSchema } },
+ required: true,
+ },
+ },
+ responses: {
+ 201: {
+ content: { 'application/json': { schema: GetSandboxEnvironmentResponseSchema } },
+ description: 'Created sandbox environment.',
+ },
+ 400: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Invalid request body.',
+ },
+ 401: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Unauthenticated.',
+ },
+ 409: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Name already exists in the tenant.',
+ },
+ 422: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Provider, snapshot, or secret validation failed.',
+ },
+ },
+});
+
+export const getSandboxEnvironmentRoute = createRoute({
+ method: 'get',
+ path: '/{sandbox_environment_id}',
+ tags: [OpenApiTag.SANDBOXES],
+ summary: 'Get a sandbox environment',
+ description: 'Get a sandbox environment owned by the authenticated subject.',
+ 'x-fern-sdk-group-name': ['sandbox_environments'],
+ 'x-fern-sdk-method-name': 'get',
+ request: {
+ params: SandboxEnvironmentIdParamsSchema,
+ },
+ responses: {
+ 200: {
+ content: { 'application/json': { schema: GetSandboxEnvironmentResponseSchema } },
+ description: 'The sandbox environment.',
+ },
+ 401: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Unauthenticated.',
+ },
+ 404: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Not found or you do not have access.',
+ },
+ },
+});
+
+export const putSandboxEnvironmentRoute = createRoute({
+ method: 'put',
+ path: '/{sandbox_environment_id}',
+ tags: [OpenApiTag.SANDBOXES],
+ summary: 'Update a sandbox environment',
+ description: 'Replace the manifest (and optionally description). Name is immutable.',
+ 'x-fern-sdk-group-name': ['sandbox_environments'],
+ 'x-fern-sdk-method-name': 'update',
+ request: {
+ params: SandboxEnvironmentIdParamsSchema,
+ body: {
+ content: { 'application/json': { schema: UpdateSandboxEnvironmentRequestSchema } },
+ required: true,
+ },
+ },
+ responses: {
+ 200: {
+ content: { 'application/json': { schema: GetSandboxEnvironmentResponseSchema } },
+ description: 'Updated sandbox environment.',
+ },
+ 400: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Invalid request body.',
+ },
+ 401: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Unauthenticated.',
+ },
+ 404: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Not found or you do not have access.',
+ },
+ 422: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Provider, snapshot, or secret validation failed.',
+ },
+ },
+});
+
+export const deleteSandboxEnvironmentRoute = createRoute({
+ method: 'delete',
+ path: '/{sandbox_environment_id}',
+ tags: [OpenApiTag.SANDBOXES],
+ summary: 'Delete a sandbox environment',
+ description: 'Delete a caller-owned sandbox environment. Fails if any agent references it.',
+ 'x-fern-sdk-group-name': ['sandbox_environments'],
+ 'x-fern-sdk-method-name': 'delete',
+ request: {
+ params: SandboxEnvironmentIdParamsSchema,
+ },
+ responses: {
+ 200: {
+ content: { 'application/json': { schema: DeleteSandboxEnvironmentResponseSchema } },
+ description: 'Deleted.',
+ },
+ 401: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Unauthenticated.',
+ },
+ 404: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Not found or you do not have access.',
+ },
+ 409: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Environment is referenced by one or more agents.',
+ },
+ },
+});
diff --git a/packages/trueforge/src/runtime/sessionResources.ts b/packages/trueforge/src/runtime/sessionResources.ts
index c1287be3e..d34df8b6f 100644
--- a/packages/trueforge/src/runtime/sessionResources.ts
+++ b/packages/trueforge/src/runtime/sessionResources.ts
@@ -14,9 +14,10 @@ import { HTTPException } from 'hono/http-exception';
import { join } from 'node:path';
import type { Logger } from 'winston';
import { z } from 'zod';
-import configuration from '../config';
+import configuration, { isTrueFoundryModeEnabled } from '../config';
import type { IMcpServerStore, IMcpServerWithAuthStore } from '../db/mcpServerStore';
import type { IModelProviderStore } from '../db/modelProviderStore';
+import type { ISandboxEnvironmentStore } from '../db/sandboxEnvironmentStore';
import type { ISandboxProviderStore } from '../db/sandboxProviderStore';
import type { ISkillStore } from '../db/skillStore';
import { LocalSandboxProvider } from '../sandbox/local/provider/LocalSandboxProvider';
@@ -290,17 +291,21 @@ export function buildTurnSandbox(input: {
export async function validateAgentSpec({
spec,
tenant_id,
+ subject_id,
modelProviderStore,
mcpServerStore,
skillStore,
sandboxProviderStore,
+ sandboxEnvironmentStore,
}: {
spec: AgentSpec;
tenant_id: string;
+ subject_id: string;
modelProviderStore: IModelProviderStore;
mcpServerStore: IMcpServerStore;
skillStore: ISkillStore;
sandboxProviderStore: ISandboxProviderStore;
+ sandboxEnvironmentStore: ISandboxEnvironmentStore;
}): Promise {
const resolved = await getModelDetails({
tenant_id,
@@ -345,6 +350,20 @@ export async function validateAgentSpec({
const wantsSandbox = spec.config.sandbox.enabled;
const hasSkills = requestedSkills.length > 0;
+ const environmentName = spec.config.sandbox.environment;
+ if (environmentName) {
+ if (!wantsSandbox) {
+ throw new HTTPException(422, {
+ message: 'sandbox.environment requires sandbox.enabled to be true',
+ });
+ }
+ if (isTrueFoundryModeEnabled(configuration)) {
+ throw new HTTPException(422, {
+ message: 'sandbox.environment is not supported in TrueFoundry mode',
+ });
+ }
+ }
+
if (wantsSandbox || hasSkills) {
const record = await sandboxProviderStore.getSandboxProvider(tenant_id);
if (record === undefined && !isLocalSandboxFallbackEnabled()) {
@@ -354,9 +373,30 @@ export async function validateAgentSpec({
: 'sandbox is enabled but no sandbox provider is configured — PUT /settings/sandbox-providers',
});
}
+ if (environmentName) {
+ if (record?.manifest.type !== 'daytona') {
+ throw new HTTPException(422, {
+ message: 'sandbox.environment requires a configured Daytona sandbox provider',
+ });
+ }
+ const environment = await sandboxEnvironmentStore.getSandboxEnvironment({
+ tenant_id,
+ name: environmentName,
+ });
+ if (environment?.created_by_subject.subject_id !== subject_id) {
+ throw new HTTPException(422, {
+ message: `Unknown sandbox environment "${environmentName}" — not found or not owned by the caller`,
+ });
+ }
+ if (environment.manifest.provider !== record.name) {
+ throw new HTTPException(422, {
+ message: `Sandbox environment "${environmentName}" provider does not match the configured sandbox provider`,
+ });
+ }
+ }
}
- if (spec.config.web_search.enabled && resolveWebSearchProvider() === undefined) {
+ if (spec.config.web_search.enabled && !resolveWebSearchProvider()) {
throw new HTTPException(422, {
message: 'web_search is enabled but no web-search provider is configured',
});
diff --git a/packages/trueforge/src/schemas/permissions.ts b/packages/trueforge/src/schemas/permissions.ts
index 68e91cd04..3825ebece 100644
--- a/packages/trueforge/src/schemas/permissions.ts
+++ b/packages/trueforge/src/schemas/permissions.ts
@@ -46,7 +46,7 @@ export const ListPermissionsDataSchema = z
permissions: z
.record(z.string(), z.array(ResourcePermissionSchema))
.describe(
- 'For agent/schedule/session: keyed by resource id. For tenant: keyed by entity kind (e.g. `agent` → `CREATE`).',
+ 'For agent/schedule/session: keyed by resource id. For tenant: keyed by entity kind (e.g. `agent` or `sandbox-environment` → `CREATE`).',
),
})
.openapi('ListPermissionsData');
@@ -72,6 +72,9 @@ export const AGENT_OWNER_PERMISSIONS = AgentResourcePermissionSchema.options;
export const SCHEDULE_OWNER_PERMISSIONS = ScheduleResourcePermissionSchema.options;
export const SESSION_OWNER_PERMISSIONS = SessionResourcePermissionSchema.options;
export const TENANT_CREATE_AGENT_PERMISSIONS = [TenantScopedResourcePermissionSchema.enum.CREATE] as const;
+export const TENANT_CREATE_SANDBOX_ENVIRONMENT_PERMISSIONS = [
+ TenantScopedResourcePermissionSchema.enum.CREATE,
+] as const;
/** Response scaffold: every requested id starts with no grants. */
export function emptyPermissionsByResourceId(resourceIds: readonly string[]): Record {
diff --git a/packages/trueforge/src/schemas/sandboxEnvironment.ts b/packages/trueforge/src/schemas/sandboxEnvironment.ts
new file mode 100644
index 000000000..a73ec0502
--- /dev/null
+++ b/packages/trueforge/src/schemas/sandboxEnvironment.ts
@@ -0,0 +1,194 @@
+/**
+ * Sandbox environment domain + wire schemas: identity columns plus a nested
+ * DaytonaSandboxEnvironmentManifest document (JSON key `manifest`).
+ *
+ * Wire `SandboxEnvironmentManifest` is Daytona-only for now (avoid one-member oneOf).
+ * Store jsonb may widen to a type-discriminated union when another provider ships.
+ */
+import { GpuType } from '@daytona/sdk';
+import { z } from '@hono/zod-openapi';
+import { CreatedBySubjectSchema, TokenPaginationSchema } from '@truefoundry/trueforge-core/agent-session';
+import { NameSchema } from './common';
+
+// random things in sdk:)
+const DAYTONA_GPU_TYPE_VALUES = Object.values(GpuType).filter(value => value !== GpuType.UNKNOWN_DEFAULT_OPEN_API) as [
+ string,
+ ...string[],
+];
+
+export const DaytonaGpuTypeSchema = z
+ .enum(DAYTONA_GPU_TYPE_VALUES)
+ .describe('Preferred Daytona GPU type.')
+ .openapi('DaytonaGpuType');
+
+const DaytonaTrueforgeDefaultImageSchema = z
+ .object({
+ type: z.literal('trueforge-default').describe('Use the tenant provider TrueForge release snapshot.'),
+ })
+ .strict()
+ .openapi('DaytonaTrueforgeDefaultImage');
+
+const DaytonaSnapshotImageSchema = z
+ .object({
+ type: z.literal('snapshot').describe('Clone an existing Daytona snapshot by name.'),
+ name: z.string().min(1).describe('Daytona snapshot name.'),
+ })
+ .strict()
+ .openapi('DaytonaSnapshotImage');
+
+const DaytonaDockerImageSchema = z
+ .object({
+ type: z.literal('docker').describe('Build/create from a container image reference.'),
+ ref: z.string().min(1).describe('Container image reference passed to Daytona create-from-image.'),
+ })
+ .strict()
+ .openapi('DaytonaDockerImage');
+
+export const DaytonaSandboxEnvironmentImageSchema = z
+ .discriminatedUnion('type', [
+ DaytonaTrueforgeDefaultImageSchema,
+ DaytonaSnapshotImageSchema,
+ DaytonaDockerImageSchema,
+ ])
+ .openapi('DaytonaSandboxEnvironmentImage');
+
+export const DaytonaSandboxEnvironmentResourcesSchema = z
+ .object({
+ cpu: z.number().positive().optional().describe('CPU allocation in cores.'),
+ memory: z.number().positive().optional().describe('Memory allocation in GiB.'),
+ disk: z.number().positive().optional().describe('Disk allocation in GiB.'),
+ gpu: z.number().nonnegative().optional().describe('GPU allocation in Daytona GPU units.'),
+ gpu_type: z
+ .union([DaytonaGpuTypeSchema, z.array(DaytonaGpuTypeSchema).min(1)])
+ .optional()
+ .describe('Preferred GPU type, or an ordered fallback list.'),
+ })
+ .strict()
+ .openapi('DaytonaSandboxEnvironmentResources');
+
+export const DaytonaSandboxEnvironmentNetworkingSchema = z
+ .object({
+ network_block_all: z.boolean().optional().describe('Block all outbound network access.'),
+ network_allow_list: z.string().min(1).optional().describe('Comma-separated allowed CIDR network addresses.'),
+ domain_allow_list: z.string().min(1).optional().describe('Comma-separated allowed domains.'),
+ outbound_proxy_url: z.string().min(1).optional().describe('Outbound HTTP(S) proxy URL.'),
+ })
+ .strict()
+ .superRefine((value, ctx) => {
+ const modes = [value.network_block_all === true, !!value.network_allow_list, !!value.domain_allow_list].filter(
+ Boolean,
+ ).length;
+ if (modes > 1) {
+ ctx.addIssue({
+ code: 'custom',
+ message: 'At most one of network_block_all, network_allow_list, or domain_allow_list may be set',
+ });
+ }
+ })
+ .openapi('DaytonaSandboxEnvironmentNetworking');
+
+export const DaytonaSandboxEnvironmentLifecycleSchema = z
+ .object({
+ auto_stop_interval_in_minutes: z
+ .number()
+ .int()
+ .nonnegative()
+ .optional()
+ .describe('Minutes of idle time before Daytona auto-stops the sandbox (0 disables).'),
+ auto_archive_interval_in_minutes: z
+ .number()
+ .int()
+ .nonnegative()
+ .optional()
+ .describe('Minutes before Daytona auto-archives the sandbox (0 disables).'),
+ auto_delete_interval_in_minutes: z
+ .number()
+ .int()
+ .nonnegative()
+ .optional()
+ .describe('Minutes before Daytona auto-deletes the sandbox (0 disables).'),
+ })
+ .strict()
+ .openapi('DaytonaSandboxEnvironmentLifecycle');
+
+export const DaytonaSandboxEnvironmentManifestSchema = z
+ .object({
+ type: z.literal('daytona').describe('Daytona sandbox environment.'),
+ provider: z.literal('daytona').describe('Must match the configured sandbox provider name/type.'),
+ image: DaytonaSandboxEnvironmentImageSchema,
+ resources: DaytonaSandboxEnvironmentResourcesSchema.optional(),
+ secrets: z
+ .record(z.string().min(1), z.string().min(1))
+ .optional()
+ .describe('Map of sandbox env var name to an existing Daytona organization secret name.'),
+ networking: DaytonaSandboxEnvironmentNetworkingSchema.optional(),
+ lifecycle: DaytonaSandboxEnvironmentLifecycleSchema.optional(),
+ })
+ .strict()
+ .openapi('DaytonaSandboxEnvironmentManifest');
+
+/** Settings / OpenAPI — Daytona only until a second provider ships. */
+export const SandboxEnvironmentManifestSchema =
+ DaytonaSandboxEnvironmentManifestSchema.openapi('SandboxEnvironmentManifest');
+
+/** Store jsonb — Daytona today; widen with discriminatedUnion when another type lands. */
+export const StoredSandboxEnvironmentManifestSchema = DaytonaSandboxEnvironmentManifestSchema;
+
+export const SandboxEnvironmentDescriptionSchema = z
+ .string()
+ .trim()
+ .max(1024)
+ .describe('Optional human-readable description.');
+
+export const CreateSandboxEnvironmentRequestSchema = z
+ .object({
+ name: NameSchema,
+ description: SandboxEnvironmentDescriptionSchema.optional(),
+ manifest: SandboxEnvironmentManifestSchema,
+ })
+ .strict()
+ .openapi('CreateSandboxEnvironmentRequest');
+
+export const UpdateSandboxEnvironmentRequestSchema = z
+ .object({
+ description: SandboxEnvironmentDescriptionSchema.nullable().optional(),
+ manifest: SandboxEnvironmentManifestSchema,
+ })
+ .strict()
+ .openapi('UpdateSandboxEnvironmentRequest');
+
+const IsoTimestamp = z.iso.datetime().openapi({ type: 'string', format: 'date-time' });
+
+export const SandboxEnvironmentSchema = z
+ .object({
+ id: z.string().min(1).describe('Immutable server-generated environment identifier.'),
+ name: NameSchema,
+ description: z.string().nullable().describe('Optional human-readable description.'),
+ manifest: SandboxEnvironmentManifestSchema,
+ created_by_subject: CreatedBySubjectSchema,
+ created_at: IsoTimestamp.describe('ISO-8601 create time.'),
+ updated_at: IsoTimestamp.describe('ISO-8601 last update time.'),
+ })
+ .strict()
+ .openapi('SandboxEnvironment');
+
+export const GetSandboxEnvironmentResponseSchema = z
+ .object({ data: SandboxEnvironmentSchema })
+ .openapi('GetSandboxEnvironmentResponse');
+
+export const ListSandboxEnvironmentsResponseSchema = z
+ .object({
+ data: z.array(SandboxEnvironmentSchema),
+ pagination: TokenPaginationSchema,
+ })
+ .openapi('ListSandboxEnvironmentsResponse');
+
+export const DeleteSandboxEnvironmentResponseSchema = z.object({}).openapi('DeleteSandboxEnvironmentResponse');
+
+export type DaytonaGpuType = z.infer;
+export type DaytonaSandboxEnvironmentManifest = z.infer;
+export type SandboxEnvironmentManifest = z.infer;
+export type StoredSandboxEnvironmentManifest = z.infer;
+export type CreateSandboxEnvironmentRequest = z.infer;
+export type UpdateSandboxEnvironmentRequest = z.infer;
+export type SandboxEnvironment = z.infer;
diff --git a/packages/trueforge/src/truefoundry/TrueFoundryAgentStore.ts b/packages/trueforge/src/truefoundry/TrueFoundryAgentStore.ts
index 2b96df378..4a53b1003 100644
--- a/packages/trueforge/src/truefoundry/TrueFoundryAgentStore.ts
+++ b/packages/trueforge/src/truefoundry/TrueFoundryAgentStore.ts
@@ -10,6 +10,7 @@ import {
type GetExternalIdsByIdsInput,
type GetOwnedIdsInput,
type IAgentStore,
+ type ListAgentIdsUsingSandboxEnvironmentInput,
type ListAgentsInput,
type UpdateAgentInput,
} from '../db/agentStore';
@@ -17,6 +18,7 @@ import { PostgresAgentStore } from '../db/postgres/agent-store/PostgresAgentStor
import type { Database } from '../db/postgres/types';
import { AGENT_DESCRIPTION_MAX_LENGTH } from '../schemas/agent';
import { callerAccessToken, type ResolveAccessToken } from './accessToken';
+import { trueFoundryManaged } from './errors';
import {
TrueFoundryServiceFoundryServerClient,
type PutRemoteAgentInput,
@@ -249,4 +251,13 @@ export class TrueFoundryAgentStore implements IAgentStore>
await this.#inner.deleteAgent(input, txn);
});
}
+
+ listAgentIdsUsingSandboxEnvironment(
+ input: ListAgentIdsUsingSandboxEnvironmentInput,
+ transaction?: Transaction,
+ ): Promise {
+ void input;
+ void transaction;
+ return trueFoundryManaged();
+ }
}
diff --git a/packages/trueforge/src/truefoundry/TrueFoundryAuthorizer.ts b/packages/trueforge/src/truefoundry/TrueFoundryAuthorizer.ts
index f91e7a7ff..9b11a77e6 100644
--- a/packages/trueforge/src/truefoundry/TrueFoundryAuthorizer.ts
+++ b/packages/trueforge/src/truefoundry/TrueFoundryAuthorizer.ts
@@ -83,7 +83,7 @@ export class TrueFoundryAuthorizer implements Authorizer {
accessToken: requireUserCredential(requestContext),
});
const agent = tenantPermissions.includes('CREATE_AGENT') ? [...TENANT_CREATE_AGENT_PERMISSIONS] : [];
- return listPermissionsData('tenant', { agent });
+ return listPermissionsData('tenant', { agent, 'sandbox-environment': [] });
}
const data = emptyPermissionsByResourceId(resourceIds);
diff --git a/packages/trueforge/tests/unit/apis/agents.test.ts b/packages/trueforge/tests/unit/apis/agents.test.ts
index dbb83d6c6..02a882fe9 100644
--- a/packages/trueforge/tests/unit/apis/agents.test.ts
+++ b/packages/trueforge/tests/unit/apis/agents.test.ts
@@ -7,6 +7,7 @@ import { SqliteAgentStore } from '../../../src/db/sqlite/agent-store/SqliteAgent
import { createSqliteDb } from '../../../src/db/sqlite/client';
import { SqliteMcpServerStore } from '../../../src/db/sqlite/mcp-server-store/SqliteMcpServerStore';
import { SqliteModelProviderStore } from '../../../src/db/sqlite/model-provider-store/SqliteModelProviderStore';
+import { SqliteSandboxEnvironmentStore } from '../../../src/db/sqlite/sandbox-environment-store/SqliteSandboxEnvironmentStore';
import { SqliteSandboxProviderStore } from '../../../src/db/sqlite/sandbox-provider-store/SqliteSandboxProviderStore';
import { SqliteSkillStore } from '../../../src/db/sqlite/skill-store/SqliteSkillStore';
import { ListAgentsResponseSchema } from '../../../src/schemas/agent';
@@ -104,6 +105,7 @@ describe('agents router', () => {
resolveMcpServerStore: () => new SqliteMcpServerStore(db),
resolveSkillStore: () => new SqliteSkillStore(db),
resolveSandboxProviderStore: () => new SqliteSandboxProviderStore(db),
+ resolveSandboxEnvironmentStore: () => new SqliteSandboxEnvironmentStore(db),
withTransaction: callback => db.transaction().execute(callback),
resolveRequestContext: () => STANDALONE_REQUEST_CONTEXT,
authorizer: new TrueForgeAuthorizer(),
@@ -114,6 +116,7 @@ describe('agents router', () => {
resolveMcpServerStore: () => new SqliteMcpServerStore(db),
resolveSkillStore: () => new SqliteSkillStore(db),
resolveSandboxProviderStore: () => new SqliteSandboxProviderStore(db),
+ resolveSandboxEnvironmentStore: () => new SqliteSandboxEnvironmentStore(db),
withTransaction: callback => db.transaction().execute(callback),
resolveRequestContext: () => STANDALONE_REQUEST_CONTEXT,
authorizer: denyAllAuthorizer,
diff --git a/packages/trueforge/tests/unit/apis/deletedSessionCrud.test.ts b/packages/trueforge/tests/unit/apis/deletedSessionCrud.test.ts
index 4c48f6f63..579bbf659 100644
--- a/packages/trueforge/tests/unit/apis/deletedSessionCrud.test.ts
+++ b/packages/trueforge/tests/unit/apis/deletedSessionCrud.test.ts
@@ -13,6 +13,7 @@ import { SqliteAgentStore } from '../../../src/db/sqlite/agent-store/SqliteAgent
import { createSqliteDb } from '../../../src/db/sqlite/client';
import { SqliteMcpServerStore } from '../../../src/db/sqlite/mcp-server-store/SqliteMcpServerStore';
import { SqliteModelProviderStore } from '../../../src/db/sqlite/model-provider-store/SqliteModelProviderStore';
+import { SqliteSandboxEnvironmentStore } from '../../../src/db/sqlite/sandbox-environment-store/SqliteSandboxEnvironmentStore';
import { SqliteSandboxProviderStore } from '../../../src/db/sqlite/sandbox-provider-store/SqliteSandboxProviderStore';
import { SqliteSessionStore } from '../../../src/db/sqlite/session-store/SqliteSessionStore';
import { SqliteSkillStore } from '../../../src/db/sqlite/skill-store/SqliteSkillStore';
@@ -38,6 +39,7 @@ describe('public CRUD after session deletion', () => {
const skillStore = new SqliteSkillStore(db);
const agentStore = new SqliteAgentStore(db);
const sandboxProviderStore = new SqliteSandboxProviderStore(db);
+ const sandboxEnvironmentStore = new SqliteSandboxEnvironmentStore(db);
const app = new OpenAPIHono();
app.route(
@@ -51,6 +53,7 @@ describe('public CRUD after session deletion', () => {
resolveSkillStore: () => skillStore,
resolveAgentStore: () => agentStore,
resolveSandboxProviderStore: () => sandboxProviderStore,
+ resolveSandboxEnvironmentStore: () => sandboxEnvironmentStore,
redis: createClient(),
requestReplyRouter: new RequestReplyRouter(),
resolveRequestContext: () => STANDALONE_REQUEST_CONTEXT,
diff --git a/packages/trueforge/tests/unit/apis/sessionHttp.test.ts b/packages/trueforge/tests/unit/apis/sessionHttp.test.ts
index 43e94f0b4..84aa8c784 100644
--- a/packages/trueforge/tests/unit/apis/sessionHttp.test.ts
+++ b/packages/trueforge/tests/unit/apis/sessionHttp.test.ts
@@ -16,6 +16,7 @@ import { SqliteAgentStore } from '../../../src/db/sqlite/agent-store/SqliteAgent
import { createSqliteDb } from '../../../src/db/sqlite/client';
import { SqliteMcpServerStore } from '../../../src/db/sqlite/mcp-server-store/SqliteMcpServerStore';
import { SqliteModelProviderStore } from '../../../src/db/sqlite/model-provider-store/SqliteModelProviderStore';
+import { SqliteSandboxEnvironmentStore } from '../../../src/db/sqlite/sandbox-environment-store/SqliteSandboxEnvironmentStore';
import { SqliteSandboxProviderStore } from '../../../src/db/sqlite/sandbox-provider-store/SqliteSandboxProviderStore';
import { SqliteSessionMetricsStore } from '../../../src/db/sqlite/session-metrics/SqliteSessionMetricsStore';
import { SqliteSessionStore } from '../../../src/db/sqlite/session-store/SqliteSessionStore';
@@ -96,6 +97,7 @@ describe('sessions HTTP agent binding', () => {
resolveSkillStore: () => skillStore,
resolveAgentStore: () => agentStore,
resolveSandboxProviderStore: () => sandboxProviderStore,
+ resolveSandboxEnvironmentStore: () => new SqliteSandboxEnvironmentStore(db),
redis: createClient(),
requestReplyRouter: new RequestReplyRouter(),
resolveRequestContext: () => STANDALONE_REQUEST_CONTEXT,
diff --git a/packages/trueforge/tests/unit/auth/authorizerPermissions.test.ts b/packages/trueforge/tests/unit/auth/authorizerPermissions.test.ts
index 5b6627022..84536829c 100644
--- a/packages/trueforge/tests/unit/auth/authorizerPermissions.test.ts
+++ b/packages/trueforge/tests/unit/auth/authorizerPermissions.test.ts
@@ -93,7 +93,7 @@ describe('TrueForgeAuthorizer.getPermissions', () => {
});
});
- it('always grants tenant agent CREATE in standalone mode', async () => {
+ it('always grants tenant agent and sandbox-environment CREATE in standalone mode', async () => {
expect(
await authorizer.getPermissions({
resourceType: 'tenant',
@@ -102,7 +102,7 @@ describe('TrueForgeAuthorizer.getPermissions', () => {
}),
).toEqual({
type: 'tenant',
- permissions: { agent: ['CREATE'] },
+ permissions: { agent: ['CREATE'], 'sandbox-environment': ['CREATE'] },
});
});
});
diff --git a/packages/trueforge/tests/unit/runtime/sessionResources.test.ts b/packages/trueforge/tests/unit/runtime/sessionResources.test.ts
index c48863b1e..3107627d9 100644
--- a/packages/trueforge/tests/unit/runtime/sessionResources.test.ts
+++ b/packages/trueforge/tests/unit/runtime/sessionResources.test.ts
@@ -13,6 +13,7 @@ import type { ISkillStore } from '../../../src/db/skillStore';
import { createSqliteDb } from '../../../src/db/sqlite/client';
import { SqliteMcpServerStore } from '../../../src/db/sqlite/mcp-server-store/SqliteMcpServerStore';
import { SqliteModelProviderStore } from '../../../src/db/sqlite/model-provider-store/SqliteModelProviderStore';
+import { SqliteSandboxEnvironmentStore } from '../../../src/db/sqlite/sandbox-environment-store/SqliteSandboxEnvironmentStore';
import { SqliteSandboxProviderStore } from '../../../src/db/sqlite/sandbox-provider-store/SqliteSandboxProviderStore';
import { SqliteSkillStore } from '../../../src/db/sqlite/skill-store/SqliteSkillStore';
import {
@@ -211,6 +212,8 @@ describe('validateAgentSpec', () => {
mcpServerStore: new SqliteMcpServerStore(db),
skillStore: new SqliteSkillStore(db),
sandboxProviderStore: new SqliteSandboxProviderStore(db),
+ sandboxEnvironmentStore: new SqliteSandboxEnvironmentStore(db),
+ subject_id: 'test-subject',
};
}
diff --git a/python/trueforge_sdk/reference.md b/python/trueforge_sdk/reference.md
index aa8c83132..3a399ffb1 100644
--- a/python/trueforge_sdk/reference.md
+++ b/python/trueforge_sdk/reference.md
@@ -1045,6 +1045,421 @@ client.models.list()
+
+
+
+
+## SandboxEnvironments
+client.sandbox_environments.list(...) -> ListSandboxEnvironmentsResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+List sandbox environments created by the authenticated subject, newest first.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from trueforge_sdk import TrueForge
+
+client = TrueForge(
+ token="",
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.sandbox_environments.list()
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**limit:** `typing.Optional[int]` — Page size. Defaults to 25
+
+
+
+
+
+-
+
+**page_token:** `typing.Optional[str]` — Opaque token from a previous response `next_page_token`.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.sandbox_environments.create(...) -> GetSandboxEnvironmentResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Create a Daytona sandbox environment owned by the authenticated subject.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from trueforge_sdk import TrueForge, SandboxEnvironmentManifest, DaytonaDockerImage
+
+client = TrueForge(
+ token="",
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.sandbox_environments.create(
+ manifest=SandboxEnvironmentManifest(
+ image=DaytonaDockerImage(
+ ref="ref",
+ type="docker",
+ ),
+ provider="daytona",
+ type="daytona",
+ ),
+ name="name",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**manifest:** `SandboxEnvironmentManifest`
+
+
+
+
+
+-
+
+**name:** `ResourceName`
+
+
+
+
+
+-
+
+**description:** `typing.Optional[str]` — Optional human-readable description.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.sandbox_environments.get(...) -> GetSandboxEnvironmentResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Get a sandbox environment owned by the authenticated subject.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from trueforge_sdk import TrueForge
+
+client = TrueForge(
+ token="",
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.sandbox_environments.get(
+ sandbox_environment_id="sandbox_environment_id",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**sandbox_environment_id:** `str` — Immutable sandbox environment identifier.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.sandbox_environments.update(...) -> GetSandboxEnvironmentResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Replace the manifest (and optionally description). Name is immutable.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from trueforge_sdk import TrueForge, SandboxEnvironmentManifest, DaytonaDockerImage
+
+client = TrueForge(
+ token="",
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.sandbox_environments.update(
+ sandbox_environment_id="sandbox_environment_id",
+ manifest=SandboxEnvironmentManifest(
+ image=DaytonaDockerImage(
+ ref="ref",
+ type="docker",
+ ),
+ provider="daytona",
+ type="daytona",
+ ),
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**sandbox_environment_id:** `str` — Immutable sandbox environment identifier.
+
+
+
+
+
+-
+
+**manifest:** `SandboxEnvironmentManifest`
+
+
+
+
+
+-
+
+**description:** `typing.Optional[str]` — Optional human-readable description.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.sandbox_environments.delete(...) -> DeleteSandboxEnvironmentResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Delete a caller-owned sandbox environment. Fails if any agent references it.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from trueforge_sdk import TrueForge
+
+client = TrueForge(
+ token="",
+ base_url="https://yourhost.com/path/to/api",
+)
+
+client.sandbox_environments.delete(
+ sandbox_environment_id="sandbox_environment_id",
+)
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**sandbox_environment_id:** `str` — Immutable sandbox environment identifier.
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
diff --git a/python/trueforge_sdk/src/trueforge_sdk/__init__.py b/python/trueforge_sdk/src/trueforge_sdk/__init__.py
index 7242c3c63..891908359 100644
--- a/python/trueforge_sdk/src/trueforge_sdk/__init__.py
+++ b/python/trueforge_sdk/src/trueforge_sdk/__init__.py
@@ -58,8 +58,18 @@
CreatedBySubject,
CronExpression,
CustomModelProvider,
+ DaytonaDockerImage,
+ DaytonaGpuType,
+ DaytonaSandboxEnvironmentImage,
+ DaytonaSandboxEnvironmentLifecycle,
+ DaytonaSandboxEnvironmentNetworking,
+ DaytonaSandboxEnvironmentResources,
+ DaytonaSandboxEnvironmentResourcesGpuType,
DaytonaSandboxProviderAuth,
+ DaytonaSnapshotImage,
+ DaytonaTrueforgeDefaultImage,
DeleteAgentResponse,
+ DeleteSandboxEnvironmentResponse,
DeleteScheduleResponse,
DynamicSubAgentsConfig,
ExtendedChunkDeltaToolCall,
@@ -77,6 +87,7 @@
GetMeSubject,
GetModelProviderCatalogResponse,
GetModelProviderResponse,
+ GetSandboxEnvironmentResponse,
GetSandboxProviderCatalogResponse,
GetSandboxProviderResponse,
GetScheduleResponse,
@@ -101,6 +112,7 @@
ListModelProvidersResponse,
ListPermissionsData,
ListPermissionsResponse,
+ ListSandboxEnvironmentsResponse,
ListScheduleRunsResponse,
ListSchedulesResponse,
ListSessionEventsResponse,
@@ -164,6 +176,8 @@
SandboxCapability,
SandboxConfig,
SandboxCreatedEvent,
+ SandboxEnvironment,
+ SandboxEnvironmentManifest,
SandboxProviderManifest,
Schedule,
ScheduleManifest,
@@ -259,7 +273,20 @@
UnauthorizedError,
UnprocessableEntityError,
)
- from . import agents, auth, catalogs, internal, mcp_servers, models, schedules, server, sessions, settings, skills
+ from . import (
+ agents,
+ auth,
+ catalogs,
+ internal,
+ mcp_servers,
+ models,
+ sandbox_environments,
+ schedules,
+ server,
+ sessions,
+ settings,
+ skills,
+ )
from ._default_clients import DefaultAioHttpClient, DefaultAsyncHttpxClient
from .client import AsyncTrueForge, TrueForge
from .version import __version__
@@ -320,10 +347,20 @@
"CreatedBySubject": ".types",
"CronExpression": ".types",
"CustomModelProvider": ".types",
+ "DaytonaDockerImage": ".types",
+ "DaytonaGpuType": ".types",
+ "DaytonaSandboxEnvironmentImage": ".types",
+ "DaytonaSandboxEnvironmentLifecycle": ".types",
+ "DaytonaSandboxEnvironmentNetworking": ".types",
+ "DaytonaSandboxEnvironmentResources": ".types",
+ "DaytonaSandboxEnvironmentResourcesGpuType": ".types",
"DaytonaSandboxProviderAuth": ".types",
+ "DaytonaSnapshotImage": ".types",
+ "DaytonaTrueforgeDefaultImage": ".types",
"DefaultAioHttpClient": "._default_clients",
"DefaultAsyncHttpxClient": "._default_clients",
"DeleteAgentResponse": ".types",
+ "DeleteSandboxEnvironmentResponse": ".types",
"DeleteScheduleResponse": ".types",
"DynamicSubAgentsConfig": ".types",
"ExtendedChunkDeltaToolCall": ".types",
@@ -343,6 +380,7 @@
"GetMeSubject": ".types",
"GetModelProviderCatalogResponse": ".types",
"GetModelProviderResponse": ".types",
+ "GetSandboxEnvironmentResponse": ".types",
"GetSandboxProviderCatalogResponse": ".types",
"GetSandboxProviderResponse": ".types",
"GetScheduleResponse": ".types",
@@ -369,6 +407,7 @@
"ListModelProvidersResponse": ".types",
"ListPermissionsData": ".types",
"ListPermissionsResponse": ".types",
+ "ListSandboxEnvironmentsResponse": ".types",
"ListScheduleRunsResponse": ".types",
"ListSchedulesResponse": ".types",
"ListSessionEventsResponse": ".types",
@@ -434,6 +473,8 @@
"SandboxCapability": ".types",
"SandboxConfig": ".types",
"SandboxCreatedEvent": ".types",
+ "SandboxEnvironment": ".types",
+ "SandboxEnvironmentManifest": ".types",
"SandboxProviderManifest": ".types",
"Schedule": ".types",
"ScheduleManifest": ".types",
@@ -524,6 +565,7 @@
"internal": ".internal",
"mcp_servers": ".mcp_servers",
"models": ".models",
+ "sandbox_environments": ".sandbox_environments",
"schedules": ".schedules",
"server": ".server",
"sessions": ".sessions",
@@ -610,10 +652,20 @@ def __dir__():
"CreatedBySubject",
"CronExpression",
"CustomModelProvider",
+ "DaytonaDockerImage",
+ "DaytonaGpuType",
+ "DaytonaSandboxEnvironmentImage",
+ "DaytonaSandboxEnvironmentLifecycle",
+ "DaytonaSandboxEnvironmentNetworking",
+ "DaytonaSandboxEnvironmentResources",
+ "DaytonaSandboxEnvironmentResourcesGpuType",
"DaytonaSandboxProviderAuth",
+ "DaytonaSnapshotImage",
+ "DaytonaTrueforgeDefaultImage",
"DefaultAioHttpClient",
"DefaultAsyncHttpxClient",
"DeleteAgentResponse",
+ "DeleteSandboxEnvironmentResponse",
"DeleteScheduleResponse",
"DynamicSubAgentsConfig",
"ExtendedChunkDeltaToolCall",
@@ -633,6 +685,7 @@ def __dir__():
"GetMeSubject",
"GetModelProviderCatalogResponse",
"GetModelProviderResponse",
+ "GetSandboxEnvironmentResponse",
"GetSandboxProviderCatalogResponse",
"GetSandboxProviderResponse",
"GetScheduleResponse",
@@ -659,6 +712,7 @@ def __dir__():
"ListModelProvidersResponse",
"ListPermissionsData",
"ListPermissionsResponse",
+ "ListSandboxEnvironmentsResponse",
"ListScheduleRunsResponse",
"ListSchedulesResponse",
"ListSessionEventsResponse",
@@ -724,6 +778,8 @@ def __dir__():
"SandboxCapability",
"SandboxConfig",
"SandboxCreatedEvent",
+ "SandboxEnvironment",
+ "SandboxEnvironmentManifest",
"SandboxProviderManifest",
"Schedule",
"ScheduleManifest",
@@ -814,6 +870,7 @@ def __dir__():
"internal",
"mcp_servers",
"models",
+ "sandbox_environments",
"schedules",
"server",
"sessions",
diff --git a/python/trueforge_sdk/src/trueforge_sdk/base_client.py b/python/trueforge_sdk/src/trueforge_sdk/base_client.py
index e086197b1..f2fbb2e2c 100644
--- a/python/trueforge_sdk/src/trueforge_sdk/base_client.py
+++ b/python/trueforge_sdk/src/trueforge_sdk/base_client.py
@@ -15,6 +15,7 @@
from .internal.client import AsyncInternalClient, InternalClient
from .mcp_servers.client import AsyncMcpServersClient, McpServersClient
from .models.client import AsyncModelsClient, ModelsClient
+ from .sandbox_environments.client import AsyncSandboxEnvironmentsClient, SandboxEnvironmentsClient
from .schedules.client import AsyncSchedulesClient, SchedulesClient
from .server.client import AsyncServerClient, ServerClient
from .sessions.client import AsyncSessionsClient, SessionsClient
@@ -103,6 +104,7 @@ def __init__(
self._server: typing.Optional[ServerClient] = None
self._mcp_servers: typing.Optional[McpServersClient] = None
self._models: typing.Optional[ModelsClient] = None
+ self._sandbox_environments: typing.Optional[SandboxEnvironmentsClient] = None
self._schedules: typing.Optional[SchedulesClient] = None
self._sessions: typing.Optional[SessionsClient] = None
self._skills: typing.Optional[SkillsClient] = None
@@ -157,6 +159,14 @@ def models(self):
self._models = ModelsClient(client_wrapper=self._client_wrapper)
return self._models
+ @property
+ def sandbox_environments(self):
+ if self._sandbox_environments is None:
+ from .sandbox_environments.client import SandboxEnvironmentsClient # noqa: E402
+
+ self._sandbox_environments = SandboxEnvironmentsClient(client_wrapper=self._client_wrapper)
+ return self._sandbox_environments
+
@property
def schedules(self):
if self._schedules is None:
@@ -300,6 +310,7 @@ def __init__(
self._server: typing.Optional[AsyncServerClient] = None
self._mcp_servers: typing.Optional[AsyncMcpServersClient] = None
self._models: typing.Optional[AsyncModelsClient] = None
+ self._sandbox_environments: typing.Optional[AsyncSandboxEnvironmentsClient] = None
self._schedules: typing.Optional[AsyncSchedulesClient] = None
self._sessions: typing.Optional[AsyncSessionsClient] = None
self._skills: typing.Optional[AsyncSkillsClient] = None
@@ -354,6 +365,14 @@ def models(self):
self._models = AsyncModelsClient(client_wrapper=self._client_wrapper)
return self._models
+ @property
+ def sandbox_environments(self):
+ if self._sandbox_environments is None:
+ from .sandbox_environments.client import AsyncSandboxEnvironmentsClient # noqa: E402
+
+ self._sandbox_environments = AsyncSandboxEnvironmentsClient(client_wrapper=self._client_wrapper)
+ return self._sandbox_environments
+
@property
def schedules(self):
if self._schedules is None:
diff --git a/python/trueforge_sdk/src/trueforge_sdk/sandbox_environments/__init__.py b/python/trueforge_sdk/src/trueforge_sdk/sandbox_environments/__init__.py
new file mode 100644
index 000000000..5cde0202d
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/sandbox_environments/__init__.py
@@ -0,0 +1,4 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
diff --git a/python/trueforge_sdk/src/trueforge_sdk/sandbox_environments/client.py b/python/trueforge_sdk/src/trueforge_sdk/sandbox_environments/client.py
new file mode 100644
index 000000000..bbf512221
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/sandbox_environments/client.py
@@ -0,0 +1,542 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ..core.pagination import AsyncPager, SyncPager
+from ..core.request_options import RequestOptions
+from ..types.delete_sandbox_environment_response import DeleteSandboxEnvironmentResponse
+from ..types.get_sandbox_environment_response import GetSandboxEnvironmentResponse
+from ..types.list_sandbox_environments_response import ListSandboxEnvironmentsResponse
+from ..types.resource_name import ResourceName
+from ..types.sandbox_environment import SandboxEnvironment
+from ..types.sandbox_environment_manifest import SandboxEnvironmentManifest
+from .raw_client import AsyncRawSandboxEnvironmentsClient, RawSandboxEnvironmentsClient
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class SandboxEnvironmentsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawSandboxEnvironmentsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawSandboxEnvironmentsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawSandboxEnvironmentsClient
+ """
+ return self._raw_client
+
+ def list(
+ self,
+ *,
+ limit: typing.Optional[int] = 25,
+ page_token: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> SyncPager[SandboxEnvironment, ListSandboxEnvironmentsResponse]:
+ """
+ List sandbox environments created by the authenticated subject, newest first.
+
+ Parameters
+ ----------
+ limit : typing.Optional[int]
+ Page size. Defaults to 25
+
+ page_token : typing.Optional[str]
+ Opaque token from a previous response `next_page_token`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ SyncPager[SandboxEnvironment, ListSandboxEnvironmentsResponse]
+ Paginated caller-owned sandbox environments.
+
+ Examples
+ --------
+ from trueforge_sdk import TrueForge
+
+ client = TrueForge(
+ token="YOUR_TOKEN",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ response = client.sandbox_environments.list()
+ for item in response:
+ yield item
+ # alternatively, you can paginate page-by-page
+ for page in response.iter_pages():
+ yield page
+ """
+ return self._raw_client.list(limit=limit, page_token=page_token, request_options=request_options)
+
+ def create(
+ self,
+ *,
+ manifest: SandboxEnvironmentManifest,
+ name: ResourceName,
+ description: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> GetSandboxEnvironmentResponse:
+ """
+ Create a Daytona sandbox environment owned by the authenticated subject.
+
+ Parameters
+ ----------
+ manifest : SandboxEnvironmentManifest
+
+ name : ResourceName
+
+ description : typing.Optional[str]
+ Optional human-readable description.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GetSandboxEnvironmentResponse
+ Created sandbox environment.
+
+ Examples
+ --------
+ from trueforge_sdk import (
+ DaytonaDockerImage,
+ SandboxEnvironmentManifest,
+ TrueForge,
+ )
+
+ client = TrueForge(
+ token="YOUR_TOKEN",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.sandbox_environments.create(
+ manifest=SandboxEnvironmentManifest(
+ image=DaytonaDockerImage(
+ ref="ref",
+ ),
+ ),
+ name="name",
+ )
+ """
+ _response = self._raw_client.create(
+ manifest=manifest, name=name, description=description, request_options=request_options
+ )
+ return _response.data
+
+ def get(
+ self, *, sandbox_environment_id: str, request_options: typing.Optional[RequestOptions] = None
+ ) -> GetSandboxEnvironmentResponse:
+ """
+ Get a sandbox environment owned by the authenticated subject.
+
+ Parameters
+ ----------
+ sandbox_environment_id : str
+ Immutable sandbox environment identifier.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GetSandboxEnvironmentResponse
+ The sandbox environment.
+
+ Examples
+ --------
+ from trueforge_sdk import TrueForge
+
+ client = TrueForge(
+ token="YOUR_TOKEN",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.sandbox_environments.get(
+ sandbox_environment_id="sandbox_environment_id",
+ )
+ """
+ _response = self._raw_client.get(sandbox_environment_id=sandbox_environment_id, request_options=request_options)
+ return _response.data
+
+ def update(
+ self,
+ *,
+ sandbox_environment_id: str,
+ manifest: SandboxEnvironmentManifest,
+ description: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> GetSandboxEnvironmentResponse:
+ """
+ Replace the manifest (and optionally description). Name is immutable.
+
+ Parameters
+ ----------
+ sandbox_environment_id : str
+ Immutable sandbox environment identifier.
+
+ manifest : SandboxEnvironmentManifest
+
+ description : typing.Optional[str]
+ Optional human-readable description.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GetSandboxEnvironmentResponse
+ Updated sandbox environment.
+
+ Examples
+ --------
+ from trueforge_sdk import (
+ DaytonaDockerImage,
+ SandboxEnvironmentManifest,
+ TrueForge,
+ )
+
+ client = TrueForge(
+ token="YOUR_TOKEN",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.sandbox_environments.update(
+ sandbox_environment_id="sandbox_environment_id",
+ manifest=SandboxEnvironmentManifest(
+ image=DaytonaDockerImage(
+ ref="ref",
+ ),
+ ),
+ )
+ """
+ _response = self._raw_client.update(
+ sandbox_environment_id=sandbox_environment_id,
+ manifest=manifest,
+ description=description,
+ request_options=request_options,
+ )
+ return _response.data
+
+ def delete(
+ self, *, sandbox_environment_id: str, request_options: typing.Optional[RequestOptions] = None
+ ) -> DeleteSandboxEnvironmentResponse:
+ """
+ Delete a caller-owned sandbox environment. Fails if any agent references it.
+
+ Parameters
+ ----------
+ sandbox_environment_id : str
+ Immutable sandbox environment identifier.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ DeleteSandboxEnvironmentResponse
+ Deleted.
+
+ Examples
+ --------
+ from trueforge_sdk import TrueForge
+
+ client = TrueForge(
+ token="YOUR_TOKEN",
+ base_url="https://yourhost.com/path/to/api",
+ )
+ client.sandbox_environments.delete(
+ sandbox_environment_id="sandbox_environment_id",
+ )
+ """
+ _response = self._raw_client.delete(
+ sandbox_environment_id=sandbox_environment_id, request_options=request_options
+ )
+ return _response.data
+
+
+class AsyncSandboxEnvironmentsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawSandboxEnvironmentsClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawSandboxEnvironmentsClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawSandboxEnvironmentsClient
+ """
+ return self._raw_client
+
+ async def list(
+ self,
+ *,
+ limit: typing.Optional[int] = 25,
+ page_token: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncPager[SandboxEnvironment, ListSandboxEnvironmentsResponse]:
+ """
+ List sandbox environments created by the authenticated subject, newest first.
+
+ Parameters
+ ----------
+ limit : typing.Optional[int]
+ Page size. Defaults to 25
+
+ page_token : typing.Optional[str]
+ Opaque token from a previous response `next_page_token`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncPager[SandboxEnvironment, ListSandboxEnvironmentsResponse]
+ Paginated caller-owned sandbox environments.
+
+ Examples
+ --------
+ import asyncio
+
+ from trueforge_sdk import AsyncTrueForge
+
+ client = AsyncTrueForge(
+ token="YOUR_TOKEN",
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ response = await client.sandbox_environments.list()
+ async for item in response:
+ yield item
+
+ # alternatively, you can paginate page-by-page
+ async for page in response.iter_pages():
+ yield page
+
+
+ asyncio.run(main())
+ """
+ return await self._raw_client.list(limit=limit, page_token=page_token, request_options=request_options)
+
+ async def create(
+ self,
+ *,
+ manifest: SandboxEnvironmentManifest,
+ name: ResourceName,
+ description: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> GetSandboxEnvironmentResponse:
+ """
+ Create a Daytona sandbox environment owned by the authenticated subject.
+
+ Parameters
+ ----------
+ manifest : SandboxEnvironmentManifest
+
+ name : ResourceName
+
+ description : typing.Optional[str]
+ Optional human-readable description.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GetSandboxEnvironmentResponse
+ Created sandbox environment.
+
+ Examples
+ --------
+ import asyncio
+
+ from trueforge_sdk import (
+ AsyncTrueForge,
+ DaytonaDockerImage,
+ SandboxEnvironmentManifest,
+ )
+
+ client = AsyncTrueForge(
+ token="YOUR_TOKEN",
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.sandbox_environments.create(
+ manifest=SandboxEnvironmentManifest(
+ image=DaytonaDockerImage(
+ ref="ref",
+ ),
+ ),
+ name="name",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.create(
+ manifest=manifest, name=name, description=description, request_options=request_options
+ )
+ return _response.data
+
+ async def get(
+ self, *, sandbox_environment_id: str, request_options: typing.Optional[RequestOptions] = None
+ ) -> GetSandboxEnvironmentResponse:
+ """
+ Get a sandbox environment owned by the authenticated subject.
+
+ Parameters
+ ----------
+ sandbox_environment_id : str
+ Immutable sandbox environment identifier.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GetSandboxEnvironmentResponse
+ The sandbox environment.
+
+ Examples
+ --------
+ import asyncio
+
+ from trueforge_sdk import AsyncTrueForge
+
+ client = AsyncTrueForge(
+ token="YOUR_TOKEN",
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.sandbox_environments.get(
+ sandbox_environment_id="sandbox_environment_id",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get(
+ sandbox_environment_id=sandbox_environment_id, request_options=request_options
+ )
+ return _response.data
+
+ async def update(
+ self,
+ *,
+ sandbox_environment_id: str,
+ manifest: SandboxEnvironmentManifest,
+ description: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> GetSandboxEnvironmentResponse:
+ """
+ Replace the manifest (and optionally description). Name is immutable.
+
+ Parameters
+ ----------
+ sandbox_environment_id : str
+ Immutable sandbox environment identifier.
+
+ manifest : SandboxEnvironmentManifest
+
+ description : typing.Optional[str]
+ Optional human-readable description.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ GetSandboxEnvironmentResponse
+ Updated sandbox environment.
+
+ Examples
+ --------
+ import asyncio
+
+ from trueforge_sdk import (
+ AsyncTrueForge,
+ DaytonaDockerImage,
+ SandboxEnvironmentManifest,
+ )
+
+ client = AsyncTrueForge(
+ token="YOUR_TOKEN",
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.sandbox_environments.update(
+ sandbox_environment_id="sandbox_environment_id",
+ manifest=SandboxEnvironmentManifest(
+ image=DaytonaDockerImage(
+ ref="ref",
+ ),
+ ),
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.update(
+ sandbox_environment_id=sandbox_environment_id,
+ manifest=manifest,
+ description=description,
+ request_options=request_options,
+ )
+ return _response.data
+
+ async def delete(
+ self, *, sandbox_environment_id: str, request_options: typing.Optional[RequestOptions] = None
+ ) -> DeleteSandboxEnvironmentResponse:
+ """
+ Delete a caller-owned sandbox environment. Fails if any agent references it.
+
+ Parameters
+ ----------
+ sandbox_environment_id : str
+ Immutable sandbox environment identifier.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ DeleteSandboxEnvironmentResponse
+ Deleted.
+
+ Examples
+ --------
+ import asyncio
+
+ from trueforge_sdk import AsyncTrueForge
+
+ client = AsyncTrueForge(
+ token="YOUR_TOKEN",
+ base_url="https://yourhost.com/path/to/api",
+ )
+
+
+ async def main() -> None:
+ await client.sandbox_environments.delete(
+ sandbox_environment_id="sandbox_environment_id",
+ )
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.delete(
+ sandbox_environment_id=sandbox_environment_id, request_options=request_options
+ )
+ return _response.data
diff --git a/python/trueforge_sdk/src/trueforge_sdk/sandbox_environments/raw_client.py b/python/trueforge_sdk/src/trueforge_sdk/sandbox_environments/raw_client.py
new file mode 100644
index 000000000..9a4dce64f
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/sandbox_environments/raw_client.py
@@ -0,0 +1,927 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+from json.decoder import JSONDecodeError
+
+from ..core.api_error import ApiError
+from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ..core.http_response import AsyncHttpResponse, HttpResponse
+from ..core.jsonable_encoder import encode_path_param
+from ..core.pagination import AsyncPager, SyncPager
+from ..core.parse_error import ParsingError
+from ..core.request_options import RequestOptions
+from ..core.serialization import convert_and_respect_annotation_metadata
+from ..core.unchecked_base_model import construct_type
+from ..errors.bad_request_error import BadRequestError
+from ..errors.conflict_error import ConflictError
+from ..errors.not_found_error import NotFoundError
+from ..errors.unauthorized_error import UnauthorizedError
+from ..errors.unprocessable_entity_error import UnprocessableEntityError
+from ..types.delete_sandbox_environment_response import DeleteSandboxEnvironmentResponse
+from ..types.get_sandbox_environment_response import GetSandboxEnvironmentResponse
+from ..types.list_sandbox_environments_response import ListSandboxEnvironmentsResponse
+from ..types.request_error_response import RequestErrorResponse
+from ..types.resource_name import ResourceName
+from ..types.sandbox_environment import SandboxEnvironment
+from ..types.sandbox_environment_manifest import SandboxEnvironmentManifest
+from pydantic import ValidationError
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class RawSandboxEnvironmentsClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def list(
+ self,
+ *,
+ limit: typing.Optional[int] = 25,
+ page_token: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> SyncPager[SandboxEnvironment, ListSandboxEnvironmentsResponse]:
+ """
+ List sandbox environments created by the authenticated subject, newest first.
+
+ Parameters
+ ----------
+ limit : typing.Optional[int]
+ Page size. Defaults to 25
+
+ page_token : typing.Optional[str]
+ Opaque token from a previous response `next_page_token`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ SyncPager[SandboxEnvironment, ListSandboxEnvironmentsResponse]
+ Paginated caller-owned sandbox environments.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ "api/v1/sandbox-environments",
+ method="GET",
+ params={
+ "limit": limit,
+ "page_token": page_token,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _parsed_response = typing.cast(
+ ListSandboxEnvironmentsResponse,
+ construct_type(
+ type_=ListSandboxEnvironmentsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ _items = _parsed_response.data
+ _has_next = False
+ _get_next = None
+ if _parsed_response.pagination is not None:
+ _parsed_next = _parsed_response.pagination.next_page_token
+ _has_next = _parsed_next is not None and _parsed_next != ""
+ _get_next = lambda: self.list(
+ limit=limit,
+ page_token=_parsed_next,
+ request_options=request_options,
+ )
+ return SyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def create(
+ self,
+ *,
+ manifest: SandboxEnvironmentManifest,
+ name: ResourceName,
+ description: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[GetSandboxEnvironmentResponse]:
+ """
+ Create a Daytona sandbox environment owned by the authenticated subject.
+
+ Parameters
+ ----------
+ manifest : SandboxEnvironmentManifest
+
+ name : ResourceName
+
+ description : typing.Optional[str]
+ Optional human-readable description.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[GetSandboxEnvironmentResponse]
+ Created sandbox environment.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ "api/v1/sandbox-environments",
+ method="POST",
+ json={
+ "description": description,
+ "manifest": convert_and_respect_annotation_metadata(
+ object_=manifest, annotation=SandboxEnvironmentManifest, direction="write"
+ ),
+ "name": name,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GetSandboxEnvironmentResponse,
+ construct_type(
+ type_=GetSandboxEnvironmentResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def get(
+ self, *, sandbox_environment_id: str, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[GetSandboxEnvironmentResponse]:
+ """
+ Get a sandbox environment owned by the authenticated subject.
+
+ Parameters
+ ----------
+ sandbox_environment_id : str
+ Immutable sandbox environment identifier.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[GetSandboxEnvironmentResponse]
+ The sandbox environment.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"api/v1/sandbox-environments/{encode_path_param(sandbox_environment_id)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GetSandboxEnvironmentResponse,
+ construct_type(
+ type_=GetSandboxEnvironmentResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def update(
+ self,
+ *,
+ sandbox_environment_id: str,
+ manifest: SandboxEnvironmentManifest,
+ description: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[GetSandboxEnvironmentResponse]:
+ """
+ Replace the manifest (and optionally description). Name is immutable.
+
+ Parameters
+ ----------
+ sandbox_environment_id : str
+ Immutable sandbox environment identifier.
+
+ manifest : SandboxEnvironmentManifest
+
+ description : typing.Optional[str]
+ Optional human-readable description.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[GetSandboxEnvironmentResponse]
+ Updated sandbox environment.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"api/v1/sandbox-environments/{encode_path_param(sandbox_environment_id)}",
+ method="PUT",
+ json={
+ "description": description,
+ "manifest": convert_and_respect_annotation_metadata(
+ object_=manifest, annotation=SandboxEnvironmentManifest, direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GetSandboxEnvironmentResponse,
+ construct_type(
+ type_=GetSandboxEnvironmentResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def delete(
+ self, *, sandbox_environment_id: str, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[DeleteSandboxEnvironmentResponse]:
+ """
+ Delete a caller-owned sandbox environment. Fails if any agent references it.
+
+ Parameters
+ ----------
+ sandbox_environment_id : str
+ Immutable sandbox environment identifier.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[DeleteSandboxEnvironmentResponse]
+ Deleted.
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ f"api/v1/sandbox-environments/{encode_path_param(sandbox_environment_id)}",
+ method="DELETE",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ DeleteSandboxEnvironmentResponse,
+ construct_type(
+ type_=DeleteSandboxEnvironmentResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawSandboxEnvironmentsClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def list(
+ self,
+ *,
+ limit: typing.Optional[int] = 25,
+ page_token: typing.Optional[str] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncPager[SandboxEnvironment, ListSandboxEnvironmentsResponse]:
+ """
+ List sandbox environments created by the authenticated subject, newest first.
+
+ Parameters
+ ----------
+ limit : typing.Optional[int]
+ Page size. Defaults to 25
+
+ page_token : typing.Optional[str]
+ Opaque token from a previous response `next_page_token`.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncPager[SandboxEnvironment, ListSandboxEnvironmentsResponse]
+ Paginated caller-owned sandbox environments.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ "api/v1/sandbox-environments",
+ method="GET",
+ params={
+ "limit": limit,
+ "page_token": page_token,
+ },
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _parsed_response = typing.cast(
+ ListSandboxEnvironmentsResponse,
+ construct_type(
+ type_=ListSandboxEnvironmentsResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ _items = _parsed_response.data
+ _has_next = False
+ _get_next = None
+ if _parsed_response.pagination is not None:
+ _parsed_next = _parsed_response.pagination.next_page_token
+ _has_next = _parsed_next is not None and _parsed_next != ""
+
+ async def _get_next():
+ return await self.list(
+ limit=limit,
+ page_token=_parsed_next,
+ request_options=request_options,
+ )
+
+ return AsyncPager(has_next=_has_next, items=_items, get_next=_get_next, response=_parsed_response)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def create(
+ self,
+ *,
+ manifest: SandboxEnvironmentManifest,
+ name: ResourceName,
+ description: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[GetSandboxEnvironmentResponse]:
+ """
+ Create a Daytona sandbox environment owned by the authenticated subject.
+
+ Parameters
+ ----------
+ manifest : SandboxEnvironmentManifest
+
+ name : ResourceName
+
+ description : typing.Optional[str]
+ Optional human-readable description.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[GetSandboxEnvironmentResponse]
+ Created sandbox environment.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ "api/v1/sandbox-environments",
+ method="POST",
+ json={
+ "description": description,
+ "manifest": convert_and_respect_annotation_metadata(
+ object_=manifest, annotation=SandboxEnvironmentManifest, direction="write"
+ ),
+ "name": name,
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GetSandboxEnvironmentResponse,
+ construct_type(
+ type_=GetSandboxEnvironmentResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def get(
+ self, *, sandbox_environment_id: str, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[GetSandboxEnvironmentResponse]:
+ """
+ Get a sandbox environment owned by the authenticated subject.
+
+ Parameters
+ ----------
+ sandbox_environment_id : str
+ Immutable sandbox environment identifier.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[GetSandboxEnvironmentResponse]
+ The sandbox environment.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"api/v1/sandbox-environments/{encode_path_param(sandbox_environment_id)}",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GetSandboxEnvironmentResponse,
+ construct_type(
+ type_=GetSandboxEnvironmentResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def update(
+ self,
+ *,
+ sandbox_environment_id: str,
+ manifest: SandboxEnvironmentManifest,
+ description: typing.Optional[str] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[GetSandboxEnvironmentResponse]:
+ """
+ Replace the manifest (and optionally description). Name is immutable.
+
+ Parameters
+ ----------
+ sandbox_environment_id : str
+ Immutable sandbox environment identifier.
+
+ manifest : SandboxEnvironmentManifest
+
+ description : typing.Optional[str]
+ Optional human-readable description.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[GetSandboxEnvironmentResponse]
+ Updated sandbox environment.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"api/v1/sandbox-environments/{encode_path_param(sandbox_environment_id)}",
+ method="PUT",
+ json={
+ "description": description,
+ "manifest": convert_and_respect_annotation_metadata(
+ object_=manifest, annotation=SandboxEnvironmentManifest, direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ GetSandboxEnvironmentResponse,
+ construct_type(
+ type_=GetSandboxEnvironmentResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 400:
+ raise BadRequestError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 422:
+ raise UnprocessableEntityError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def delete(
+ self, *, sandbox_environment_id: str, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[DeleteSandboxEnvironmentResponse]:
+ """
+ Delete a caller-owned sandbox environment. Fails if any agent references it.
+
+ Parameters
+ ----------
+ sandbox_environment_id : str
+ Immutable sandbox environment identifier.
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[DeleteSandboxEnvironmentResponse]
+ Deleted.
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ f"api/v1/sandbox-environments/{encode_path_param(sandbox_environment_id)}",
+ method="DELETE",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ DeleteSandboxEnvironmentResponse,
+ construct_type(
+ type_=DeleteSandboxEnvironmentResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ if _response.status_code == 401:
+ raise UnauthorizedError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 404:
+ raise NotFoundError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ if _response.status_code == 409:
+ raise ConflictError(
+ headers=dict(_response.headers),
+ body=typing.cast(
+ RequestErrorResponse,
+ construct_type(
+ type_=RequestErrorResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ ),
+ )
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ except ValidationError as e:
+ raise ParsingError(
+ status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e
+ )
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/__init__.py b/python/trueforge_sdk/src/trueforge_sdk/types/__init__.py
index 2e83715b6..114cda8c3 100644
--- a/python/trueforge_sdk/src/trueforge_sdk/types/__init__.py
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/__init__.py
@@ -57,8 +57,18 @@
from .created_by_subject import CreatedBySubject
from .cron_expression import CronExpression
from .custom_model_provider import CustomModelProvider
+ from .daytona_docker_image import DaytonaDockerImage
+ from .daytona_gpu_type import DaytonaGpuType
+ from .daytona_sandbox_environment_image import DaytonaSandboxEnvironmentImage
+ from .daytona_sandbox_environment_lifecycle import DaytonaSandboxEnvironmentLifecycle
+ from .daytona_sandbox_environment_networking import DaytonaSandboxEnvironmentNetworking
+ from .daytona_sandbox_environment_resources import DaytonaSandboxEnvironmentResources
+ from .daytona_sandbox_environment_resources_gpu_type import DaytonaSandboxEnvironmentResourcesGpuType
from .daytona_sandbox_provider_auth import DaytonaSandboxProviderAuth
+ from .daytona_snapshot_image import DaytonaSnapshotImage
+ from .daytona_trueforge_default_image import DaytonaTrueforgeDefaultImage
from .delete_agent_response import DeleteAgentResponse
+ from .delete_sandbox_environment_response import DeleteSandboxEnvironmentResponse
from .delete_schedule_response import DeleteScheduleResponse
from .dynamic_sub_agents_config import DynamicSubAgentsConfig
from .extended_chunk_delta_tool_call import ExtendedChunkDeltaToolCall
@@ -76,6 +86,7 @@
from .get_me_subject import GetMeSubject
from .get_model_provider_catalog_response import GetModelProviderCatalogResponse
from .get_model_provider_response import GetModelProviderResponse
+ from .get_sandbox_environment_response import GetSandboxEnvironmentResponse
from .get_sandbox_provider_catalog_response import GetSandboxProviderCatalogResponse
from .get_sandbox_provider_response import GetSandboxProviderResponse
from .get_schedule_response import GetScheduleResponse
@@ -100,6 +111,7 @@
from .list_model_providers_response import ListModelProvidersResponse
from .list_permissions_data import ListPermissionsData
from .list_permissions_response import ListPermissionsResponse
+ from .list_sandbox_environments_response import ListSandboxEnvironmentsResponse
from .list_schedule_runs_response import ListScheduleRunsResponse
from .list_schedules_response import ListSchedulesResponse
from .list_session_events_response import ListSessionEventsResponse
@@ -163,6 +175,8 @@
from .sandbox_capability import SandboxCapability
from .sandbox_config import SandboxConfig
from .sandbox_created_event import SandboxCreatedEvent
+ from .sandbox_environment import SandboxEnvironment
+ from .sandbox_environment_manifest import SandboxEnvironmentManifest
from .sandbox_provider_manifest import SandboxProviderManifest
from .schedule import Schedule
from .schedule_manifest import ScheduleManifest
@@ -295,8 +309,18 @@
"CreatedBySubject": ".created_by_subject",
"CronExpression": ".cron_expression",
"CustomModelProvider": ".custom_model_provider",
+ "DaytonaDockerImage": ".daytona_docker_image",
+ "DaytonaGpuType": ".daytona_gpu_type",
+ "DaytonaSandboxEnvironmentImage": ".daytona_sandbox_environment_image",
+ "DaytonaSandboxEnvironmentLifecycle": ".daytona_sandbox_environment_lifecycle",
+ "DaytonaSandboxEnvironmentNetworking": ".daytona_sandbox_environment_networking",
+ "DaytonaSandboxEnvironmentResources": ".daytona_sandbox_environment_resources",
+ "DaytonaSandboxEnvironmentResourcesGpuType": ".daytona_sandbox_environment_resources_gpu_type",
"DaytonaSandboxProviderAuth": ".daytona_sandbox_provider_auth",
+ "DaytonaSnapshotImage": ".daytona_snapshot_image",
+ "DaytonaTrueforgeDefaultImage": ".daytona_trueforge_default_image",
"DeleteAgentResponse": ".delete_agent_response",
+ "DeleteSandboxEnvironmentResponse": ".delete_sandbox_environment_response",
"DeleteScheduleResponse": ".delete_schedule_response",
"DynamicSubAgentsConfig": ".dynamic_sub_agents_config",
"ExtendedChunkDeltaToolCall": ".extended_chunk_delta_tool_call",
@@ -314,6 +338,7 @@
"GetMeSubject": ".get_me_subject",
"GetModelProviderCatalogResponse": ".get_model_provider_catalog_response",
"GetModelProviderResponse": ".get_model_provider_response",
+ "GetSandboxEnvironmentResponse": ".get_sandbox_environment_response",
"GetSandboxProviderCatalogResponse": ".get_sandbox_provider_catalog_response",
"GetSandboxProviderResponse": ".get_sandbox_provider_response",
"GetScheduleResponse": ".get_schedule_response",
@@ -338,6 +363,7 @@
"ListModelProvidersResponse": ".list_model_providers_response",
"ListPermissionsData": ".list_permissions_data",
"ListPermissionsResponse": ".list_permissions_response",
+ "ListSandboxEnvironmentsResponse": ".list_sandbox_environments_response",
"ListScheduleRunsResponse": ".list_schedule_runs_response",
"ListSchedulesResponse": ".list_schedules_response",
"ListSessionEventsResponse": ".list_session_events_response",
@@ -401,6 +427,8 @@
"SandboxCapability": ".sandbox_capability",
"SandboxConfig": ".sandbox_config",
"SandboxCreatedEvent": ".sandbox_created_event",
+ "SandboxEnvironment": ".sandbox_environment",
+ "SandboxEnvironmentManifest": ".sandbox_environment_manifest",
"SandboxProviderManifest": ".sandbox_provider_manifest",
"Schedule": ".schedule",
"ScheduleManifest": ".schedule_manifest",
@@ -557,8 +585,18 @@ def __dir__():
"CreatedBySubject",
"CronExpression",
"CustomModelProvider",
+ "DaytonaDockerImage",
+ "DaytonaGpuType",
+ "DaytonaSandboxEnvironmentImage",
+ "DaytonaSandboxEnvironmentLifecycle",
+ "DaytonaSandboxEnvironmentNetworking",
+ "DaytonaSandboxEnvironmentResources",
+ "DaytonaSandboxEnvironmentResourcesGpuType",
"DaytonaSandboxProviderAuth",
+ "DaytonaSnapshotImage",
+ "DaytonaTrueforgeDefaultImage",
"DeleteAgentResponse",
+ "DeleteSandboxEnvironmentResponse",
"DeleteScheduleResponse",
"DynamicSubAgentsConfig",
"ExtendedChunkDeltaToolCall",
@@ -576,6 +614,7 @@ def __dir__():
"GetMeSubject",
"GetModelProviderCatalogResponse",
"GetModelProviderResponse",
+ "GetSandboxEnvironmentResponse",
"GetSandboxProviderCatalogResponse",
"GetSandboxProviderResponse",
"GetScheduleResponse",
@@ -600,6 +639,7 @@ def __dir__():
"ListModelProvidersResponse",
"ListPermissionsData",
"ListPermissionsResponse",
+ "ListSandboxEnvironmentsResponse",
"ListScheduleRunsResponse",
"ListSchedulesResponse",
"ListSessionEventsResponse",
@@ -663,6 +703,8 @@ def __dir__():
"SandboxCapability",
"SandboxConfig",
"SandboxCreatedEvent",
+ "SandboxEnvironment",
+ "SandboxEnvironmentManifest",
"SandboxProviderManifest",
"Schedule",
"ScheduleManifest",
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/daytona_docker_image.py b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_docker_image.py
new file mode 100644
index 000000000..e79bb5d30
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_docker_image.py
@@ -0,0 +1,24 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class DaytonaDockerImage(UncheckedBaseModel):
+ ref: str = pydantic.Field()
+ """
+ Container image reference passed to Daytona create-from-image.
+ """
+
+ type: typing.Literal["docker"] = "docker"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/daytona_gpu_type.py b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_gpu_type.py
new file mode 100644
index 000000000..de3da4844
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_gpu_type.py
@@ -0,0 +1,50 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ..core import enum
+
+T_Result = typing.TypeVar("T_Result")
+
+
+class DaytonaGpuType(enum.StrEnum):
+ """
+ Preferred Daytona GPU type.
+ """
+
+ H100 = "H100"
+ H200 = "H200"
+ RTX_PRO6000 = "RTX-PRO-6000"
+ RTX4090 = "RTX-4090"
+ RTX5090 = "RTX-5090"
+ _UNKNOWN = "__DAYTONAGPUTYPE_UNKNOWN__"
+ """
+ This member is used for forward compatibility. If the value is not recognized by the enum, it will be stored here, and the raw value is accessible through `.value`.
+ """
+
+ @classmethod
+ def _missing_(cls, value: typing.Any) -> "DaytonaGpuType":
+ unknown = cls._UNKNOWN
+ unknown._value_ = value
+ return unknown
+
+ def visit(
+ self,
+ h100: typing.Callable[[], T_Result],
+ h200: typing.Callable[[], T_Result],
+ rtx_pro6000: typing.Callable[[], T_Result],
+ rtx4090: typing.Callable[[], T_Result],
+ rtx5090: typing.Callable[[], T_Result],
+ _unknown_member: typing.Callable[[str], T_Result],
+ ) -> T_Result:
+ if self is DaytonaGpuType.H100:
+ return h100()
+ if self is DaytonaGpuType.H200:
+ return h200()
+ if self is DaytonaGpuType.RTX_PRO6000:
+ return rtx_pro6000()
+ if self is DaytonaGpuType.RTX4090:
+ return rtx4090()
+ if self is DaytonaGpuType.RTX5090:
+ return rtx5090()
+ return _unknown_member(self._value_)
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/daytona_sandbox_environment_image.py b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_sandbox_environment_image.py
new file mode 100644
index 000000000..ca966c3a6
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_sandbox_environment_image.py
@@ -0,0 +1,9 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .daytona_docker_image import DaytonaDockerImage
+from .daytona_snapshot_image import DaytonaSnapshotImage
+from .daytona_trueforge_default_image import DaytonaTrueforgeDefaultImage
+
+DaytonaSandboxEnvironmentImage = typing.Union[DaytonaDockerImage, DaytonaSnapshotImage, DaytonaTrueforgeDefaultImage]
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/daytona_sandbox_environment_lifecycle.py b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_sandbox_environment_lifecycle.py
new file mode 100644
index 000000000..245d1cccc
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_sandbox_environment_lifecycle.py
@@ -0,0 +1,32 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class DaytonaSandboxEnvironmentLifecycle(UncheckedBaseModel):
+ auto_archive_interval_in_minutes: typing.Optional[int] = pydantic.Field(default=None)
+ """
+ Minutes before Daytona auto-archives the sandbox (0 disables).
+ """
+
+ auto_delete_interval_in_minutes: typing.Optional[int] = pydantic.Field(default=None)
+ """
+ Minutes before Daytona auto-deletes the sandbox (0 disables).
+ """
+
+ auto_stop_interval_in_minutes: typing.Optional[int] = pydantic.Field(default=None)
+ """
+ Minutes of idle time before Daytona auto-stops the sandbox (0 disables).
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/daytona_sandbox_environment_networking.py b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_sandbox_environment_networking.py
new file mode 100644
index 000000000..6b64599dc
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_sandbox_environment_networking.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class DaytonaSandboxEnvironmentNetworking(UncheckedBaseModel):
+ domain_allow_list: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Comma-separated allowed domains.
+ """
+
+ network_allow_list: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Comma-separated allowed CIDR network addresses.
+ """
+
+ network_block_all: typing.Optional[bool] = pydantic.Field(default=None)
+ """
+ Block all outbound network access.
+ """
+
+ outbound_proxy_url: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Outbound HTTP(S) proxy URL.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/daytona_sandbox_environment_resources.py b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_sandbox_environment_resources.py
new file mode 100644
index 000000000..265b378e4
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_sandbox_environment_resources.py
@@ -0,0 +1,43 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+from .daytona_sandbox_environment_resources_gpu_type import DaytonaSandboxEnvironmentResourcesGpuType
+
+
+class DaytonaSandboxEnvironmentResources(UncheckedBaseModel):
+ cpu: typing.Optional[float] = pydantic.Field(default=None)
+ """
+ CPU allocation in cores.
+ """
+
+ disk: typing.Optional[float] = pydantic.Field(default=None)
+ """
+ Disk allocation in GiB.
+ """
+
+ gpu: typing.Optional[float] = pydantic.Field(default=None)
+ """
+ GPU allocation in Daytona GPU units.
+ """
+
+ gpu_type: typing.Optional[DaytonaSandboxEnvironmentResourcesGpuType] = pydantic.Field(default=None)
+ """
+ Preferred GPU type, or an ordered fallback list.
+ """
+
+ memory: typing.Optional[float] = pydantic.Field(default=None)
+ """
+ Memory allocation in GiB.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/daytona_sandbox_environment_resources_gpu_type.py b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_sandbox_environment_resources_gpu_type.py
new file mode 100644
index 000000000..f0589953a
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_sandbox_environment_resources_gpu_type.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .daytona_gpu_type import DaytonaGpuType
+
+DaytonaSandboxEnvironmentResourcesGpuType = typing.Union[DaytonaGpuType, typing.List[DaytonaGpuType]]
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/daytona_snapshot_image.py b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_snapshot_image.py
new file mode 100644
index 000000000..06c1be73f
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_snapshot_image.py
@@ -0,0 +1,24 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class DaytonaSnapshotImage(UncheckedBaseModel):
+ name: str = pydantic.Field()
+ """
+ Daytona snapshot name.
+ """
+
+ type: typing.Literal["snapshot"] = "snapshot"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/daytona_trueforge_default_image.py b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_trueforge_default_image.py
new file mode 100644
index 000000000..064b102d3
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/daytona_trueforge_default_image.py
@@ -0,0 +1,19 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class DaytonaTrueforgeDefaultImage(UncheckedBaseModel):
+ type: typing.Literal["trueforge-default"] = "trueforge-default"
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/delete_sandbox_environment_response.py b/python/trueforge_sdk/src/trueforge_sdk/types/delete_sandbox_environment_response.py
new file mode 100644
index 000000000..11de44ee7
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/delete_sandbox_environment_response.py
@@ -0,0 +1,17 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class DeleteSandboxEnvironmentResponse(UncheckedBaseModel):
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/get_sandbox_environment_response.py b/python/trueforge_sdk/src/trueforge_sdk/types/get_sandbox_environment_response.py
new file mode 100644
index 000000000..9bb4ff8b9
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/get_sandbox_environment_response.py
@@ -0,0 +1,20 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+from .sandbox_environment import SandboxEnvironment
+
+
+class GetSandboxEnvironmentResponse(UncheckedBaseModel):
+ data: SandboxEnvironment
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/list_permissions_data.py b/python/trueforge_sdk/src/trueforge_sdk/types/list_permissions_data.py
index ea978b7fc..b14db41f4 100644
--- a/python/trueforge_sdk/src/trueforge_sdk/types/list_permissions_data.py
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/list_permissions_data.py
@@ -12,7 +12,7 @@
class ListPermissionsData(UncheckedBaseModel):
permissions: typing.Dict[str, typing.List[ResourcePermission]] = pydantic.Field()
"""
- For agent/schedule/session: keyed by resource id. For tenant: keyed by entity kind (e.g. `agent` → `CREATE`).
+ For agent/schedule/session: keyed by resource id. For tenant: keyed by entity kind (e.g. `agent` or `sandbox-environment` → `CREATE`).
"""
type: PermissionResourceType
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/list_sandbox_environments_response.py b/python/trueforge_sdk/src/trueforge_sdk/types/list_sandbox_environments_response.py
new file mode 100644
index 000000000..1f93f47e8
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/list_sandbox_environments_response.py
@@ -0,0 +1,22 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+from .sandbox_environment import SandboxEnvironment
+from .token_pagination import TokenPagination
+
+
+class ListSandboxEnvironmentsResponse(UncheckedBaseModel):
+ data: typing.List[SandboxEnvironment]
+ pagination: TokenPagination
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/sandbox_config.py b/python/trueforge_sdk/src/trueforge_sdk/types/sandbox_config.py
index d3e7b3ae5..acc99c7e9 100644
--- a/python/trueforge_sdk/src/trueforge_sdk/types/sandbox_config.py
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/sandbox_config.py
@@ -13,6 +13,11 @@ class SandboxConfig(UncheckedBaseModel):
Give the agent a sandbox. Required for skills and Code Mode.
"""
+ environment: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Caller-owned sandbox environment name. Omit to use the tenant provider defaults.
+ """
+
file_downloads: typing.Optional[bool] = pydantic.Field(default=True)
"""
Allow downloading agent-produced files via the turn download endpoint. Default: true.
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/sandbox_environment.py b/python/trueforge_sdk/src/trueforge_sdk/types/sandbox_environment.py
new file mode 100644
index 000000000..3638c6d7b
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/sandbox_environment.py
@@ -0,0 +1,44 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import datetime as dt
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+from .created_by_subject import CreatedBySubject
+from .resource_name import ResourceName
+from .sandbox_environment_manifest import SandboxEnvironmentManifest
+
+
+class SandboxEnvironment(UncheckedBaseModel):
+ created_at: dt.datetime = pydantic.Field()
+ """
+ ISO-8601 create time.
+ """
+
+ created_by_subject: CreatedBySubject
+ description: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Optional human-readable description.
+ """
+
+ id: str = pydantic.Field()
+ """
+ Immutable server-generated environment identifier.
+ """
+
+ manifest: SandboxEnvironmentManifest
+ name: ResourceName
+ updated_at: dt.datetime = pydantic.Field()
+ """
+ ISO-8601 last update time.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/python/trueforge_sdk/src/trueforge_sdk/types/sandbox_environment_manifest.py b/python/trueforge_sdk/src/trueforge_sdk/types/sandbox_environment_manifest.py
new file mode 100644
index 000000000..4863e6a02
--- /dev/null
+++ b/python/trueforge_sdk/src/trueforge_sdk/types/sandbox_environment_manifest.py
@@ -0,0 +1,40 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+from .daytona_sandbox_environment_image import DaytonaSandboxEnvironmentImage
+from .daytona_sandbox_environment_lifecycle import DaytonaSandboxEnvironmentLifecycle
+from .daytona_sandbox_environment_networking import DaytonaSandboxEnvironmentNetworking
+from .daytona_sandbox_environment_resources import DaytonaSandboxEnvironmentResources
+
+
+class SandboxEnvironmentManifest(UncheckedBaseModel):
+ image: DaytonaSandboxEnvironmentImage
+ lifecycle: typing.Optional[DaytonaSandboxEnvironmentLifecycle] = None
+ networking: typing.Optional[DaytonaSandboxEnvironmentNetworking] = None
+ provider: typing.Literal["daytona"] = pydantic.Field(default="daytona")
+ """
+ Must match the configured sandbox provider name/type.
+ """
+
+ resources: typing.Optional[DaytonaSandboxEnvironmentResources] = None
+ secrets: typing.Optional[typing.Dict[str, str]] = pydantic.Field(default=None)
+ """
+ Map of sandbox env var name to an existing Daytona organization secret name.
+ """
+
+ type: typing.Literal["daytona"] = pydantic.Field(default="daytona")
+ """
+ Daytona sandbox environment.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ smart_union = True
+ extra = pydantic.Extra.allow