From 36a7912c6fed82cd3ac352b555e1289285fe7dd1 Mon Sep 17 00:00:00 2001 From: Chad Retz Date: Fri, 6 Nov 2026 15:14:19 -0600 Subject: [PATCH] API spec update, sandbox spec from basetenlabs/sandbox, and sandbox network removal --- baseten/client/managementapi/__init__.py | 14 +- baseten/client/managementapi/_models.py | 382 +++------- baseten/client/sandboxapi/__init__.py | 8 - baseten/client/sandboxapi/_client.py | 106 +-- baseten/client/sandboxapi/_models.py | 102 +-- baseten/sandbox/__init__.py | 6 - baseten/sandbox/_client.py | 8 - baseten/sandbox/_info.py | 127 ---- scripts/apigen/__main__.py | 2 +- scripts/apigen/specs/management.json | 861 +++-------------------- scripts/apigen/specs/sandbox.yml | 209 +----- tests/sandbox/test_client.py | 4 - tests/sandbox/test_info.py | 31 - tests/sandbox/test_parity.py | 15 - 14 files changed, 207 insertions(+), 1668 deletions(-) diff --git a/baseten/client/managementapi/__init__.py b/baseten/client/managementapi/__init__.py index 73a864c..bfadfa4 100644 --- a/baseten/client/managementapi/__init__.py +++ b/baseten/client/managementapi/__init__.py @@ -359,7 +359,6 @@ GetVolumesSyncsParams, GetVolumesVersionsParams, GitInfo, - GridBackgroundHarnessDefaults, GridClassifier, GridClassifiersResponse, GridConnection, @@ -375,6 +374,7 @@ GridEffectiveSpendLimit, GridHarnessDefaults, GridHarnessModel, + GridHarnessModels, GridMachineUser, GridMachineUsersResponse, GridMachineUserTombstone, @@ -568,13 +568,10 @@ SandboxMetricDescriptor, SandboxMetrics, SandboxMetricValueSet, - SandboxNetwork, SandboxPort, SandboxPreview, SandboxPreviewConfiguration, SandboxPreviewToken, - SandboxProxyConfig, - SandboxProxyTarget, SandboxRegion, SandboxTTLIdleExpirationPolicy, SandboxTTLMaxAgeExpirationPolicy, @@ -670,9 +667,9 @@ UpdateEnvironmentGroupRequest, UpdateEnvironmentRequest, UpdateEnvironmentResponse, - UpdateGridBackgroundHarnessModels, UpdateGridConnectionRequest, UpdateGridHarnessDefaults, + UpdateGridHarnessModels, UpdateGridMachineUserRequest, UpdateGridRouteRequest, UpdateGridSpendLimitSetting, @@ -1092,7 +1089,6 @@ "GetVolumesSyncsParams", "GetVolumesVersionsParams", "GitInfo", - "GridBackgroundHarnessDefaults", "GridClassifier", "GridClassifiersResponse", "GridConnection", @@ -1108,6 +1104,7 @@ "GridEffectiveSpendLimit", "GridHarnessDefaults", "GridHarnessModel", + "GridHarnessModels", "GridMachineUser", "GridMachineUserTombstone", "GridMachineUsersResponse", @@ -1302,13 +1299,10 @@ "SandboxMetricDescriptor", "SandboxMetricValueSet", "SandboxMetrics", - "SandboxNetwork", "SandboxPort", "SandboxPreview", "SandboxPreviewConfiguration", "SandboxPreviewToken", - "SandboxProxyConfig", - "SandboxProxyTarget", "SandboxRegion", "SandboxTTLIdleExpirationPolicy", "SandboxTTLMaxAgeExpirationPolicy", @@ -1404,9 +1398,9 @@ "UpdateEnvironmentGroupRequest", "UpdateEnvironmentRequest", "UpdateEnvironmentResponse", - "UpdateGridBackgroundHarnessModels", "UpdateGridConnectionRequest", "UpdateGridHarnessDefaults", + "UpdateGridHarnessModels", "UpdateGridMachineUserRequest", "UpdateGridRouteRequest", "UpdateGridSpendLimitSetting", diff --git a/baseten/client/managementapi/_models.py b/baseten/client/managementapi/_models.py index fa8dbfa..b5f378f 100644 --- a/baseten/client/managementapi/_models.py +++ b/baseten/client/managementapi/_models.py @@ -789,14 +789,14 @@ class GetBillingUsageSummaryParams(BaseModel): start_date: Annotated[ AwareDatetime, Field( - description="Start date (ISO 8601, UTC). Earliest queryable: 2026-01-01.", + description="Inclusive start date (ISO 8601, normalized to midnight UTC). Earliest queryable: 2026-01-01.", title="Start Date", ), ] end_date: Annotated[ AwareDatetime, Field( - description="End date in ISO 8601 format (UTC). Date range cannot exceed 31 days.", + description="Exclusive end date (ISO 8601, normalized to midnight UTC). Date range cannot exceed 31 days.", title="End Date", ), ] @@ -9188,11 +9188,11 @@ class GridRouteUsageResult(BaseModel): description="Provider that served the requests. Null when not grouping by PROVIDER." ), ] = None - router_id: Annotated[ + intelligent_route: Annotated[ str | None, Field( - description="Slug of the intelligent router, matching its name in `GET /v1/grid/routes`. Null when not grouping by ROUTER.", - title="Router Id", + description="Name of the intelligent route, as in `GET /v1/grid/routes`. Null when not grouping by INTELLIGENT_ROUTE.", + title="Intelligent Route", ), ] = None harness: Annotated[ @@ -9236,7 +9236,7 @@ class GridRouteUsageResult(BaseModel): requests: Annotated[ int, Field( - description="Number of requests; with ROUTER, the requests sent through that router. Usage recorded before requests were counted adds zero.", + description="Number of requests; with INTELLIGENT_ROUTE, the requests sent through that route. Usage recorded before requests were counted adds zero.", title="Requests", ), ] @@ -9349,7 +9349,7 @@ class Background(RootModel[str]): ] = None -class UpdateGridBackgroundHarnessModels(BaseModel): +class UpdateGridHarnessModels(BaseModel): model_config = ConfigDict( extra="forbid", ) @@ -9371,36 +9371,6 @@ class UpdateGridBackgroundHarnessModels(BaseModel): ] = None -class UpdateGridHarnessDefaults(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - claude_code: Annotated[ - UpdateGridBackgroundHarnessModels | None, - Field( - description="Route IDs for the model roles to change; roles left out are unchanged. Every route must belong to the team. Pass null to clear every role, or omit to leave the harness unchanged." - ), - ] = None - opencode: Annotated[ - UpdateGridBackgroundHarnessModels | None, - Field( - description="Route IDs for the model roles to change; roles left out are unchanged. Every route must belong to the team. Pass null to clear every role, or omit to leave the harness unchanged." - ), - ] = None - codex: Annotated[ - UpdateGridBackgroundHarnessModels | None, - Field( - description="Route IDs for the model roles to change; roles left out are unchanged. Every route must belong to the team. Pass null to clear every role, or omit to leave the harness unchanged." - ), - ] = None - pi: Annotated[ - UpdateGridBackgroundHarnessModels | None, - Field( - description="Route IDs for the model roles to change; roles left out are unchanged. Every route must belong to the team. Pass null to clear every role, or omit to leave the harness unchanged." - ), - ] = None - - class UpdateGridTeamSpendLimitSetting(BaseModel): model_config = ConfigDict( extra="forbid", @@ -9423,32 +9393,6 @@ class UpdateGridTeamSpendLimitSetting(BaseModel): ] = None -class UpdateGridTeamSettingsRequest(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - harness_defaults: Annotated[ - UpdateGridHarnessDefaults | None, - Field( - description="Harnesses to change; harnesses left out are unchanged. Pass null to clear every harness, so each role uses the team's default." - ), - ] = None - spend_limit: Annotated[ - UpdateGridTeamSpendLimitSetting | None, - Field( - description="Spend limit fields to change. Pass null to remove the team's limits; omit to leave them unchanged." - ), - ] = None - team_id: Annotated[ - str | None, - Field( - description="ID of the team. When omitted, the team you belong to; required when you belong to several.", - examples=["abc1234"], - title="Team Id", - ), - ] = None - - class GridEffectiveSpendLimit(BaseModel): monthly_limit_usd: Annotated[ str | None, @@ -10773,37 +10717,6 @@ class SandboxDateExpirationPolicy(BaseModel): value: Annotated[AwareDatetime, Field(examples=["2026-09-23T21:26:58Z"])] -class SandboxProxyTarget(BaseModel): - body: Annotated[ - dict[str, str] | None, - Field( - description="Body fields to inject into matching requests. Values may contain {{SECRET:name}} references resolved from this rule's secrets.", - examples=[{"user": "baseten-api-review-0916"}], - ), - ] = None - destinations: Annotated[ - list[str] | None, - Field( - description='Destination domains this rule applies to. Use ["*"] for a global rule that matches all destinations.', - examples=[["api.openai.com"]], - ), - ] = None - headers: Annotated[ - dict[str, str] | None, - Field( - description="Headers to inject into matching requests. Values may contain {{SECRET:name}} references resolved from this rule's secrets.", - examples=[{"Authorization": "Bearer {{SECRET:openai-key}}"}], - ), - ] = None - secrets: Annotated[ - dict[str, str] | None, - Field( - description="Named secret values for this routing rule, referenced in headers/body via {{SECRET:name}}. Stored encrypted at rest. Write-only: never returned in API responses.", - examples=[{"openai-key": "sk-proj-demo-not-a-valid-api-key"}], - ), - ] = None - - class SandboxEnv(BaseModel): name: Annotated[ str | None, @@ -12536,10 +12449,13 @@ class GetGridRoutesUsageParams(BaseModel): ), ] = None group_by: Annotated[ - list[Literal["USER", "TEAM", "MODEL", "PROVIDER", "ROUTER", "HARNESS"] | str] + list[ + Literal["USER", "TEAM", "MODEL", "PROVIDER", "INTELLIGENT_ROUTE", "HARNESS"] + | str + ] | None, Field( - description="Dimensions to break usage down by, repeated once per dimension: USER, TEAM, MODEL, PROVIDER, ROUTER, or HARNESS. Each result represents one observed combination of the requested dimensions within that day, and results are sorted by those values. Combinations without usage are omitted, so result counts can differ between days. ROUTER returns only usage sent through an intelligent router, one row per router, or per router and model with MODEL. Router rows repeat usage already counted in other groupings, so don't add them to model totals. HARNESS includes usage with no recorded harness as a null group. Defaults to MODEL.", + description="Dimensions to break usage down by, repeated once per dimension: USER, TEAM, MODEL, PROVIDER, INTELLIGENT_ROUTE, or HARNESS. Each result represents one observed combination of the requested dimensions within that day, and results are sorted by those values. Combinations without usage are omitted, so result counts can differ between days. INTELLIGENT_ROUTE returns only usage sent through an intelligent route, one row per intelligent route, or per intelligent route and model with MODEL. These rows repeat usage already counted in other groupings, so don't add them to model totals. HARNESS includes usage with no recorded harness as a null group. Defaults to MODEL.", title="Group By", ), ] = None @@ -15747,6 +15663,77 @@ class GridHarnessModel(BaseModel): route: Annotated[GridRoute, Field(description="Route to use for this role.")] +class GridHarnessModels(BaseModel): + primary: Annotated[ + GridHarnessModel | None, + Field( + description="Route for the primary model, which new sessions use. Null when the team has no route to use." + ), + ] + background: Annotated[ + GridHarnessModel | None, + Field( + description="Route for background tasks, such as session titles. Null when the team has no route to use." + ), + ] + + +class UpdateGridHarnessDefaults(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + claude_code: Annotated[ + UpdateGridHarnessModels | None, + Field( + description="Route IDs for the model roles to change; roles left out are unchanged. Every route must belong to the team. Pass null to clear every role, or omit to leave the harness unchanged." + ), + ] = None + opencode: Annotated[ + UpdateGridHarnessModels | None, + Field( + description="Route IDs for the model roles to change; roles left out are unchanged. Every route must belong to the team. Pass null to clear every role, or omit to leave the harness unchanged." + ), + ] = None + codex: Annotated[ + UpdateGridHarnessModels | None, + Field( + description="Route IDs for the model roles to change; roles left out are unchanged. Every route must belong to the team. Pass null to clear every role, or omit to leave the harness unchanged." + ), + ] = None + pi: Annotated[ + UpdateGridHarnessModels | None, + Field( + description="Route IDs for the model roles to change; roles left out are unchanged. Every route must belong to the team. Pass null to clear every role, or omit to leave the harness unchanged." + ), + ] = None + + +class UpdateGridTeamSettingsRequest(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + harness_defaults: Annotated[ + UpdateGridHarnessDefaults | None, + Field( + description="Harnesses to change; harnesses left out are unchanged. Pass null to clear every harness, so each role uses the team's default." + ), + ] = None + spend_limit: Annotated[ + UpdateGridTeamSpendLimitSetting | None, + Field( + description="Spend limit fields to change. Pass null to remove the team's limits; omit to leave them unchanged." + ), + ] = None + team_id: Annotated[ + str | None, + Field( + description="ID of the team. When omitted, the team you belong to; required when you belong to several.", + examples=["abc1234"], + title="Team Id", + ), + ] = None + + class GridSpendLimitSetting(BaseModel): user_monthly_limit_usd: Annotated[ str | None, @@ -16074,53 +16061,6 @@ class GetSandboxConfigurationResponse(BaseModel): ] -class SandboxProxyConfig(BaseModel): - allowed_domains: Annotated[ - list[str] | None, - Field( - description="List of allowed external domains (allowlist). When set, only these domains are reachable. Supports wildcards (e.g. *.storage.example.com).", - examples=[ - [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org", - ] - ], - ), - ] = None - bypass: Annotated[ - list[str] | None, - Field( - description="Domains that bypass the proxy entirely via the NO_PROXY directive. Traffic to these destinations goes direct, not through the CONNECT tunnel. Supports wildcards. Note that localhost, private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), 169.254.169.254, .local and .internal are always bypassed by default.", - examples=[["registry.npmjs.org"]], - ), - ] = None - forbidden_domains: Annotated[ - list[str] | None, - Field( - description="List of forbidden external domains (denylist). When set, all domains except these are reachable. Supports wildcards (e.g. *.malware.com). If both allowed_domains and forbidden_domains are set, allowed_domains takes precedence.", - examples=[["facebook.com", "*.facebook.com"]], - ), - ] = None - routing: Annotated[ - list[SandboxProxyTarget] | None, - Field( - description='Per-destination routing rules with header/body injection and secrets. Use destinations ["*"] for global rules that apply to all destinations.', - examples=[ - [ - { - "destinations": ["api.openai.com"], - "headers": {"Authorization": "Bearer {{SECRET:openai-key}}"}, - "body": {"user": "baseten-api-review-0916"}, - "secrets": {"openai-key": "sk-proj-demo-not-a-valid-api-key"}, - } - ] - ], - ), - ] = None - - class ListSandboxImagesResponse(BaseModel): items: Annotated[ list[SandboxImage], Field(description="Image repositories on this page.") @@ -16971,35 +16911,15 @@ class UsageSummary(BaseModel): ] = None -class GridBackgroundHarnessDefaults(BaseModel): - primary: Annotated[ - GridHarnessModel | None, - Field( - description="Route for the primary model, which new sessions use. Null when the team has no route to use." - ), - ] - background: Annotated[ - GridHarnessModel | None, - Field( - description="Route for background tasks, such as session titles. Null when the team has no route to use." - ), - ] - - class GridHarnessDefaults(BaseModel): claude_code: Annotated[ - GridBackgroundHarnessDefaults, - Field(description="Default models for Claude Code."), + GridHarnessModels, Field(description="Default models for Claude Code.") ] opencode: Annotated[ - GridBackgroundHarnessDefaults, Field(description="Default models for OpenCode.") - ] - codex: Annotated[ - GridBackgroundHarnessDefaults, Field(description="Default models for Codex.") - ] - pi: Annotated[ - GridBackgroundHarnessDefaults, Field(description="Default models for Pi.") + GridHarnessModels, Field(description="Default models for OpenCode.") ] + codex: Annotated[GridHarnessModels, Field(description="Default models for Codex.")] + pi: Annotated[GridHarnessModels, Field(description="Default models for Pi.")] class GridTeamSettings(BaseModel): @@ -17108,46 +17028,6 @@ class SandboxLifecycle(BaseModel): ] = None -class SandboxNetwork(BaseModel): - proxy: Annotated[ - SandboxProxyConfig | None, - Field( - description="Proxy configuration for routing sandbox HTTP traffic through the platform proxy with MITM inspection and per-destination header/body injection", - examples=[ - { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org", - ], - "bypass": ["registry.npmjs.org"], - "forbidden_domains": ["facebook.com", "*.facebook.com"], - "routing": [ - { - "destinations": ["api.openai.com"], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": {"user": "baseten-api-review-0916"}, - "secrets": { - "openai-key": "sk-proj-demo-not-a-valid-api-key" - }, - } - ], - } - ], - ), - ] = None - subnet: Annotated[ - str | None, - Field( - description='Subnet name for the sandbox. Defaults to "default" at creation.', - examples=["default"], - ), - ] = None - - class SandboxConfiguration(BaseModel): lifecycle: Annotated[ SandboxLifecycle | None, @@ -17169,39 +17049,6 @@ class SandboxConfiguration(BaseModel): ], ), ] = None - network: Annotated[ - SandboxNetwork | None, - Field( - description="Network configuration for a sandbox including subnet, domain filtering, and proxy settings", - examples=[ - { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org", - ], - "bypass": ["registry.npmjs.org"], - "forbidden_domains": ["facebook.com", "*.facebook.com"], - "routing": [ - { - "destinations": ["api.openai.com"], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": {"user": "baseten-api-review-0916"}, - "secrets": { - "openai-key": "sk-proj-demo-not-a-valid-api-key" - }, - } - ], - }, - "subnet": "default", - } - ], - ), - ] = None region: Annotated[ str | None, Field( @@ -17322,39 +17169,6 @@ class CreateSandboxRequest(BaseModel): ], ), ] = None - network: Annotated[ - SandboxNetwork | None, - Field( - description="Network configuration for a sandbox including subnet, domain filtering, and proxy settings", - examples=[ - { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org", - ], - "bypass": ["registry.npmjs.org"], - "forbidden_domains": ["facebook.com", "*.facebook.com"], - "routing": [ - { - "destinations": ["api.openai.com"], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": {"user": "baseten-api-review-0916"}, - "secrets": { - "openai-key": "sk-proj-demo-not-a-valid-api-key" - }, - } - ], - }, - "subnet": "default", - } - ], - ), - ] = None region: Annotated[ str | None, Field( @@ -17604,28 +17418,6 @@ class ListSandboxesResponse(BaseModel): ], "terminated_retention": "24h", }, - "network": { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org", - ], - "bypass": ["registry.npmjs.org"], - "forbidden_domains": ["facebook.com", "*.facebook.com"], - "routing": [ - { - "destinations": ["api.openai.com"], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": {"user": "baseten-api-review-0916"}, - } - ], - }, - "subnet": "default", - }, "region": "us-pdx-1", "envs": [ { diff --git a/baseten/client/sandboxapi/__init__.py b/baseten/client/sandboxapi/__init__.py index 06275bb..410287c 100644 --- a/baseten/client/sandboxapi/__init__.py +++ b/baseten/client/sandboxapi/__init__.py @@ -18,8 +18,6 @@ ArchiveManifest, ContentSearchMatch, ContentSearchResponse, - CopyRequest, - CopyResponse, DeleteFilesystemParams, DeleteFilesystemTreeParams, DeleteNetworkProcessMonitorResponse, @@ -48,10 +46,8 @@ GetFilesystemParams, GetFilesystemResponse, GetFilesystemSearchParams, - GetFilesystemTreeParams, GetNetworkProcessPortsResponse, GetProcessResponse, - GetProcessWaitParams, GetWatchFilesystemParams, HandlerReloadResponse, HealthResponse, @@ -90,8 +86,6 @@ "AsyncApiClient", "ContentSearchMatch", "ContentSearchResponse", - "CopyRequest", - "CopyResponse", "DeleteFilesystemParams", "DeleteFilesystemTreeParams", "DeleteNetworkProcessMonitorResponse", @@ -120,10 +114,8 @@ "GetFilesystemParams", "GetFilesystemResponse", "GetFilesystemSearchParams", - "GetFilesystemTreeParams", "GetNetworkProcessPortsResponse", "GetProcessResponse", - "GetProcessWaitParams", "GetWatchFilesystemParams", "HandlerReloadResponse", "HealthResponse", diff --git a/baseten/client/sandboxapi/_client.py b/baseten/client/sandboxapi/_client.py index 9b60fed..4f0af2b 100644 --- a/baseten/client/sandboxapi/_client.py +++ b/baseten/client/sandboxapi/_client.py @@ -15,8 +15,6 @@ ApplyEditRequest, ApplyEditResponse, ContentSearchResponse, - CopyRequest, - CopyResponse, DeleteFilesystemParams, DeleteFilesystemTreeParams, DeleteNetworkProcessMonitorResponse, @@ -38,10 +36,8 @@ GetFilesystemParams, GetFilesystemResponse, GetFilesystemSearchParams, - GetFilesystemTreeParams, GetNetworkProcessPortsResponse, GetProcessResponse, - GetProcessWaitParams, GetWatchFilesystemParams, HandlerReloadResponse, HealthResponse, @@ -151,11 +147,7 @@ def delete_drives_mount(self, *, mount_path: str) -> DriveUnmountResponse: body=None, query=None, success_codes=[200], - error_codes={ - 400: "ErrorResponse", - 404: "ErrorResponse", - 500: "ErrorResponse", - }, + error_codes={400: "ErrorResponse", 500: "ErrorResponse"}, ), ) @@ -505,9 +497,7 @@ def get_filesystem_search( ), ) - def get_filesystem_tree( - self, *, path: str, params: GetFilesystemTreeParams | None = None - ) -> Directory: + def get_filesystem_tree(self, *, path: str) -> Directory: """Get directory tree""" return self._do_json( Directory, @@ -516,7 +506,7 @@ def get_filesystem_tree( path_fmt="/filesystem/tree/{}", path_args=[path], body=None, - query=params, + query=None, success_codes=[200], error_codes={ 400: "ErrorResponse", @@ -631,23 +621,6 @@ def get_process_logs_stream( ) ) - def get_process_wait( - self, *, identifier: str, params: GetProcessWaitParams | None = None - ) -> ProcessResponse: - """Wait for a process to finish""" - return self._do_json( - ProcessResponse, - _ApiRequest( - method="GET", - path_fmt="/process/{}/wait", - path_args=[identifier], - body=None, - query=params, - success_codes=[200], - error_codes={400: "ErrorResponse", 404: "ErrorResponse"}, - ), - ) - def get_watch_filesystem( self, *, path: str, params: GetWatchFilesystemParams | None = None ) -> httpx.Response: @@ -731,7 +704,6 @@ def post_drives_mount(self, *, request: DriveMountRequest) -> DriveMountResponse 403: "ErrorResponse", 409: "ErrorResponse", 500: "ErrorResponse", - 507: "ErrorResponse", }, ), ) @@ -751,25 +723,6 @@ def post_environment_reload(self) -> HandlerReloadResponse: ), ) - def post_filesystem_copy(self, *, request: CopyRequest) -> CopyResponse: - """Copy a file or directory""" - return self._do_json( - CopyResponse, - _ApiRequest( - method="POST", - path_fmt="/filesystem-copy", - path_args=[], - body=request, - query=None, - success_codes=[200], - error_codes={ - 400: "ErrorResponse", - 409: "ErrorResponse", - 422: "ErrorResponse", - }, - ), - ) - def post_filesystem_multipart_complete( self, *, upload_id: str, request: MultipartCompleteRequest ) -> SuccessResponse: @@ -907,7 +860,7 @@ def post_upgrade(self, *, request: UpgradeRequest) -> SuccessResponse: body=request, query=None, success_codes=[200], - error_codes={400: "ErrorResponse", 500: "ErrorResponse"}, + error_codes={500: "ErrorResponse"}, ), ) @@ -1187,11 +1140,7 @@ async def delete_drives_mount(self, *, mount_path: str) -> DriveUnmountResponse: body=None, query=None, success_codes=[200], - error_codes={ - 400: "ErrorResponse", - 404: "ErrorResponse", - 500: "ErrorResponse", - }, + error_codes={400: "ErrorResponse", 500: "ErrorResponse"}, ), ) @@ -1543,9 +1492,7 @@ async def get_filesystem_search( ), ) - async def get_filesystem_tree( - self, *, path: str, params: GetFilesystemTreeParams | None = None - ) -> Directory: + async def get_filesystem_tree(self, *, path: str) -> Directory: """Get directory tree""" return await self._do_json( Directory, @@ -1554,7 +1501,7 @@ async def get_filesystem_tree( path_fmt="/filesystem/tree/{}", path_args=[path], body=None, - query=params, + query=None, success_codes=[200], error_codes={ 400: "ErrorResponse", @@ -1671,23 +1618,6 @@ async def get_process_logs_stream( ) ) - async def get_process_wait( - self, *, identifier: str, params: GetProcessWaitParams | None = None - ) -> ProcessResponse: - """Wait for a process to finish""" - return await self._do_json( - ProcessResponse, - _ApiRequest( - method="GET", - path_fmt="/process/{}/wait", - path_args=[identifier], - body=None, - query=params, - success_codes=[200], - error_codes={400: "ErrorResponse", 404: "ErrorResponse"}, - ), - ) - async def get_watch_filesystem( self, *, path: str, params: GetWatchFilesystemParams | None = None ) -> httpx.Response: @@ -1773,7 +1703,6 @@ async def post_drives_mount( 403: "ErrorResponse", 409: "ErrorResponse", 500: "ErrorResponse", - 507: "ErrorResponse", }, ), ) @@ -1793,25 +1722,6 @@ async def post_environment_reload(self) -> HandlerReloadResponse: ), ) - async def post_filesystem_copy(self, *, request: CopyRequest) -> CopyResponse: - """Copy a file or directory""" - return await self._do_json( - CopyResponse, - _ApiRequest( - method="POST", - path_fmt="/filesystem-copy", - path_args=[], - body=request, - query=None, - success_codes=[200], - error_codes={ - 400: "ErrorResponse", - 409: "ErrorResponse", - 422: "ErrorResponse", - }, - ), - ) - async def post_filesystem_multipart_complete( self, *, upload_id: str, request: MultipartCompleteRequest ) -> SuccessResponse: @@ -1949,7 +1859,7 @@ async def post_upgrade(self, *, request: UpgradeRequest) -> SuccessResponse: body=request, query=None, success_codes=[200], - error_codes={400: "ErrorResponse", 500: "ErrorResponse"}, + error_codes={500: "ErrorResponse"}, ), ) diff --git a/baseten/client/sandboxapi/_models.py b/baseten/client/sandboxapi/_models.py index c9b0ffa..a53e372 100644 --- a/baseten/client/sandboxapi/_models.py +++ b/baseten/client/sandboxapi/_models.py @@ -114,18 +114,6 @@ class ContentSearchResponse(BaseModel): total: Annotated[int, Field(examples=[5])] -class CopyRequest(BaseModel): - destination: Annotated[str, Field(examples=["/app/destination"])] - noOverwrite: Annotated[bool, Field(examples=[False])] = False - source: Annotated[str, Field(examples=["/app/source"])] - - -class CopyResponse(BaseModel): - destination: str - message: str - source: str - - class DriveMountInfo(BaseModel): driveName: str | None = None drivePath: str | None = None @@ -180,17 +168,10 @@ class DriveUnmountResponse(BaseModel): class ErrorResponse(BaseModel): - code: Annotated[str | None, Field(examples=["FILE_ALREADY_EXISTS"])] = None error: Annotated[str, Field(examples=["Error message"])] class File(BaseModel): - content: Annotated[ - str | None, - Field( - description="Content is set only by a tree read with content=true, for regular files." - ), - ] = None group: str lastModified: str name: str @@ -315,20 +296,6 @@ class ProcessLogs(BaseModel): class ProcessRequest(BaseModel): - callbackSecret: Annotated[ - str | None, - Field( - description="Key the webhook is signed with (HMAC-SHA256, X-Blaxel-Signature header). At least 32 characters; required with callbackUrl. Never returned by the API.", - examples=["a-random-secret-of-at-least-32-chars"], - ), - ] = None - callbackUrl: Annotated[ - str | None, - Field( - description="URL that receives a signed process.exited webhook when the process ends for good (not on auto-restart). Requires callbackSecret.", - examples=["https://example.com/hooks/process"], - ), - ] = None command: Annotated[str, Field(examples=["ls -la"])] env: Annotated[dict[str, str] | None, Field(examples=[{'{"PORT"': ' "3000"}'}])] = ( None @@ -458,12 +425,15 @@ class TunnelConfigRequest(BaseModel): class UpgradeRequest(BaseModel): baseUrl: Annotated[ str | None, - Field(description="Deprecated: custom binary sources are not accepted"), + Field( + description="Base URL for releases (useful for forks)", + examples=["https://github.com/basetenlabs/sandbox/releases"], + ), ] = None version: Annotated[ str | None, Field( - description='Version to upgrade to: "latest" (default), "develop", "main", or a release tag like "v1.0.0"', + description='Version to upgrade to: "latest" (default), "develop", "main", or specific tag like "v1.0.0"', examples=["latest"], ), ] = None @@ -588,65 +558,11 @@ class DeleteFilesystemTreeParams(BaseModel): ] = None -class GetFilesystemTreeParams(BaseModel): - recursive: Annotated[ - bool | None, - Field( - description="List every file and directory below the path, not only its direct children (default: false)" - ), - ] = None - content: Annotated[ - bool | None, - Field( - description="Include the content of each regular file, as UTF-8 text (default: false)" - ), - ] = None - patterns: Annotated[ - str | None, - Field( - description="Comma-separated glob patterns matched against file names (e.g., *.json,*.md). Directories are not filtered." - ), - ] = None - excludeDirs: Annotated[ - str | None, - Field( - description="Comma-separated directory names to skip with everything below them (default: none)" - ), - ] = None - excludeHidden: Annotated[ - bool | None, - Field( - description="Skip files and directories whose name starts with a dot (default: false)" - ), - ] = None - maxFiles: Annotated[ - int | None, - Field( - description="Fail with 422 when more files match (default: 10000 with recursive or content, otherwise unlimited; at most 100000)" - ), - ] = None - maxBytes: Annotated[ - int | None, - Field( - description="With content=true, fail with 422 when the matching files hold more bytes (default: 33554432, 32 MiB; at most 268435456, 256 MiB)" - ), - ] = None - - -class GetProcessWaitParams(BaseModel): - timeout: Annotated[ - int | None, - Field( - description="Seconds to wait (default and max 119; larger values are capped)" - ), - ] = None - - class GetWatchFilesystemParams(BaseModel): ignore: Annotated[ str | None, Field( - description="Comma-separated substrings; events whose full path contains one are skipped, and matching directories are not watched at all" + description="Comma-separated substrings; events whose full path contains one are skipped" ), ] = None @@ -692,12 +608,6 @@ class Directory(BaseModel): files: list[File] name: str path: str - recursive: Annotated[ - bool | None, - Field( - description="Recursive is true when files and subdirectories hold every level below\npath (tree read with recursive=true). Older APIs ignore that parameter\nand omit this field, so clients can tell a one-level listing apart." - ), - ] = None subdirectories: Annotated[ list[Subdirectory], Field(description="@name Subdirectories") ] diff --git a/baseten/sandbox/__init__.py b/baseten/sandbox/__init__.py index 7e7537b..9d09559 100644 --- a/baseten/sandbox/__init__.py +++ b/baseten/sandbox/__init__.py @@ -88,9 +88,6 @@ SandboxFileSystemOptions, SandboxInfo, SandboxLifecycle, - SandboxNetwork, - SandboxNetworkProxy, - SandboxNetworkProxyRoute, SandboxPort, SandboxPortProtocol, SandboxStatus, @@ -183,9 +180,6 @@ "SandboxGatewayError", "SandboxInfo", "SandboxLifecycle", - "SandboxNetwork", - "SandboxNetworkProxy", - "SandboxNetworkProxyRoute", "SandboxPort", "SandboxPortProtocol", "SandboxPreviewInfo", diff --git a/baseten/sandbox/_client.py b/baseten/sandbox/_client.py index e8cfc0f..233da78 100644 --- a/baseten/sandbox/_client.py +++ b/baseten/sandbox/_client.py @@ -45,14 +45,12 @@ SandboxFileSystemOptions, SandboxInfo, SandboxLifecycle, - SandboxNetwork, SandboxPort, SandboxStatus, SandboxVolumeMount, envs_to_api, filesystem_to_api, lifecycle_to_api, - network_to_api, ports_to_api, sandbox_info_from_api, set_fields, @@ -303,7 +301,6 @@ def create( external_id: str | None = None, lifecycle: SandboxLifecycle | None = None, ports: Sequence[SandboxPort] | None = None, - network: SandboxNetwork | None = None, filesystem: SandboxFileSystemOptions | None = None, volumes: Sequence[SandboxVolumeMount] | None = None, ) -> SandboxCreateResult: @@ -329,8 +326,6 @@ def create( external_id: Caller-owned identifier for external lookups. lifecycle: When the sandbox is deleted automatically. ports: Ports the sandbox exposes. - network: Network configuration, which cannot be changed after - creation. filesystem: Filesystem configuration, which cannot be changed after creation. volumes: Persistent volumes to mount, each in the sandbox's @@ -357,7 +352,6 @@ def create( if lifecycle is None else lifecycle_to_api(lifecycle), ports=None if ports is None else ports_to_api(ports), - network=None if network is None else network_to_api(network), filesystem=None if filesystem is None else filesystem_to_api(filesystem), @@ -712,7 +706,6 @@ async def create( external_id: str | None = None, lifecycle: SandboxLifecycle | None = None, ports: Sequence[SandboxPort] | None = None, - network: SandboxNetwork | None = None, filesystem: SandboxFileSystemOptions | None = None, volumes: Sequence[SandboxVolumeMount] | None = None, ) -> AsyncSandboxCreateResult: @@ -737,7 +730,6 @@ async def create( if lifecycle is None else lifecycle_to_api(lifecycle), ports=None if ports is None else ports_to_api(ports), - network=None if network is None else network_to_api(network), filesystem=None if filesystem is None else filesystem_to_api(filesystem), diff --git a/baseten/sandbox/_info.py b/baseten/sandbox/_info.py index 6c62acf..b2d6f25 100644 --- a/baseten/sandbox/_info.py +++ b/baseten/sandbox/_info.py @@ -146,61 +146,6 @@ class SandboxPort: protocol: SandboxPortProtocol | None = None -@dataclass(kw_only=True) -class SandboxNetworkProxyRoute: - """A proxy rule injecting headers and body fields into requests to its destinations.""" - - destinations: Sequence[str] | None = None - """Destination domains the rule applies to. ``["*"]`` matches all.""" - - headers: Mapping[str, str] | None = None - """Headers to inject. Values may reference ``{{SECRET:name}}`` from ``secrets``.""" - - body: Mapping[str, str] | None = None - """Body fields to inject. Values may reference ``{{SECRET:name}}`` from ``secrets``.""" - - secrets: Mapping[str, str] | None = None - """Named secret values for this rule. - - Write-only, so always unset on :class:`SandboxInfo`. - """ - - -@dataclass(kw_only=True) -class SandboxNetworkProxy: - """Proxy configuration of a sandbox's network.""" - - allowed_domains: Sequence[str] | None = None - """When set, only these external domains are reachable. - - Supports wildcards such as ``*.example.com``. Takes precedence over - ``forbidden_domains``. - """ - - forbidden_domains: Sequence[str] | None = None - """When set, every external domain except these is reachable. Supports wildcards.""" - - bypass: Sequence[str] | None = None - """Domains reached directly rather than through the proxy. - - Supports wildcards. Local and private addresses always bypass it. - """ - - routing: Sequence[SandboxNetworkProxyRoute] | None = None - """Rules injecting headers and body fields into matching requests.""" - - -@dataclass(kw_only=True) -class SandboxNetwork: - """Network configuration of a sandbox, fixed at creation.""" - - subnet: str | None = None - """Subnet name. The server defaults to ``default``.""" - - proxy: SandboxNetworkProxy | None = None - """Routes the sandbox's HTTP traffic through the platform proxy.""" - - @dataclass(kw_only=True) class SandboxFileSystemOptions: """Filesystem configuration of a sandbox, fixed at creation. @@ -270,8 +215,6 @@ class SandboxInfo: ports: list[SandboxPort] - network: SandboxNetwork | None - filesystem: SandboxFileSystemOptions | None volumes: list[SandboxVolumeMount] @@ -308,7 +251,6 @@ def sandbox_info_from_api(sandbox: baseten.client.managementapi.Sandbox) -> Sand if sandbox.lifecycle is None else _lifecycle_from_api(sandbox.lifecycle), ports=ports_from_api(sandbox.ports), - network=None if sandbox.network is None else _network_from_api(sandbox.network), filesystem=None if sandbox.filesystem is None else SandboxFileSystemOptions( @@ -383,40 +325,6 @@ def ports_to_api( ] -def network_to_api( - network: SandboxNetwork, -) -> baseten.client.managementapi.SandboxNetwork: - """Convert a network configuration to the control plane's form.""" - proxy = network.proxy - return baseten.client.managementapi.SandboxNetwork( - **set_fields( - subnet=network.subnet, - proxy=None - if proxy is None - else baseten.client.managementapi.SandboxProxyConfig( - **set_fields( - allowed_domains=_optional_list(proxy.allowed_domains), - forbidden_domains=_optional_list(proxy.forbidden_domains), - bypass=_optional_list(proxy.bypass), - routing=None - if proxy.routing is None - else [ - baseten.client.managementapi.SandboxProxyTarget( - **set_fields( - destinations=_optional_list(route.destinations), - headers=_optional_dict(route.headers), - body=_optional_dict(route.body), - secrets=_optional_dict(route.secrets), - ) - ) - for route in proxy.routing - ], - ) - ), - ) - ) - - def filesystem_to_api( filesystem: SandboxFileSystemOptions, ) -> baseten.client.managementapi.SandboxFilesystem: @@ -517,33 +425,6 @@ def _policy_from_api( ) -def _network_from_api( - network: baseten.client.managementapi.SandboxNetwork, -) -> SandboxNetwork: - proxy = network.proxy - return SandboxNetwork( - subnet=network.subnet, - proxy=None - if proxy is None - else SandboxNetworkProxy( - allowed_domains=proxy.allowed_domains, - forbidden_domains=proxy.forbidden_domains, - bypass=proxy.bypass, - routing=None - if proxy.routing is None - else [ - SandboxNetworkProxyRoute( - destinations=route.destinations, - headers=route.headers, - body=route.body, - secrets=route.secrets, - ) - for route in proxy.routing - ], - ), - ) - - def _envs_from_api( envs: list[baseten.client.managementapi.SandboxEnv] | None, ) -> dict[str, SandboxEnvValue]: @@ -555,11 +436,3 @@ def _envs_from_api( value="" if env.value is None else env.value, secret=env.secret ) return result - - -def _optional_list(values: Sequence[str] | None) -> list[str] | None: - return None if values is None else list(values) - - -def _optional_dict(values: Mapping[str, str] | None) -> dict[str, str] | None: - return None if values is None else dict(values) diff --git a/scripts/apigen/__main__.py b/scripts/apigen/__main__.py index edb5ce9..50decf2 100644 --- a/scripts/apigen/__main__.py +++ b/scripts/apigen/__main__.py @@ -24,7 +24,7 @@ TRUSS_CONFIG_SCHEMA_URL = ( "https://raw.githubusercontent.com/basetenlabs/truss/main/truss/config.schema.json" ) -SANDBOX_SPEC_URL = "https://raw.githubusercontent.com/blaxel-ai/sandbox/main/sandbox-api/docs/openapi.yml" +SANDBOX_SPEC_URL = "https://raw.githubusercontent.com/basetenlabs/sandbox/main/sandbox-api/docs/openapi.yml" APIGEN_DIR = Path(__file__).parent SPECS_DIR = APIGEN_DIR / "specs" diff --git a/scripts/apigen/specs/management.json b/scripts/apigen/specs/management.json index 540f7d5..e859282 100644 --- a/scripts/apigen/specs/management.json +++ b/scripts/apigen/specs/management.json @@ -9937,13 +9937,13 @@ "source": "import requests\nimport os\nAPI_KEY = os.environ.get(\"BASETEN_API_KEY\", \"\")\nurl = \"https://api.baseten.co/v1/billing/usage_summary\"\n\nheaders = {\"Authorization\": f\"Bearer {API_KEY}\"}\n\nresponse = requests.request(\n \"GET\",\n url,\n headers=headers,\n params={'start_date': None, 'end_date': None}\n)\n\nprint(response.text)" } ], - "description": "Returns billing usage data within the specified date range. Includes dedicated model serving, training, and model APIs usage. The date range must not exceed 31 days.", + "description": "Returns billing usage within [start_date, end_date): the start date is inclusive and the end date is exclusive. Both dates are normalized to midnight UTC. Includes dedicated model serving, training, and model APIs usage. The date range must not exceed 31 days.", "parameters": [ { "name": "start_date", "in": "query", "required": true, - "description": "Start date (ISO 8601, UTC). Earliest queryable: 2026-01-01.", + "description": "Inclusive start date (ISO 8601, normalized to midnight UTC). Earliest queryable: 2026-01-01.", "schema": { "format": "date-time", "title": "Start Date", @@ -9954,7 +9954,7 @@ "name": "end_date", "in": "query", "required": true, - "description": "End date in ISO 8601 format (UTC). Date range cannot exceed 31 days.", + "description": "Exclusive end date (ISO 8601, normalized to midnight UTC). Date range cannot exceed 31 days.", "schema": { "format": "date-time", "title": "End Date", @@ -10754,7 +10754,7 @@ "name": "group_by", "in": "query", "required": false, - "description": "Dimensions to break usage down by, repeated once per dimension: USER, TEAM, MODEL, PROVIDER, ROUTER, or HARNESS. Each result represents one observed combination of the requested dimensions within that day, and results are sorted by those values. Combinations without usage are omitted, so result counts can differ between days. ROUTER returns only usage sent through an intelligent router, one row per router, or per router and model with MODEL. Router rows repeat usage already counted in other groupings, so don't add them to model totals. HARNESS includes usage with no recorded harness as a null group. Defaults to MODEL.", + "description": "Dimensions to break usage down by, repeated once per dimension: USER, TEAM, MODEL, PROVIDER, INTELLIGENT_ROUTE, or HARNESS. Each result represents one observed combination of the requested dimensions within that day, and results are sorted by those values. Combinations without usage are omitted, so result counts can differ between days. INTELLIGENT_ROUTE returns only usage sent through an intelligent route, one row per intelligent route, or per intelligent route and model with MODEL. These rows repeat usage already counted in other groupings, so don't add them to model totals. HARNESS includes usage with no recorded harness as a null group. Defaults to MODEL.", "schema": { "items": { "$ref": "#/components/schemas/GridRouteUsageDimensionV1" @@ -12817,7 +12817,7 @@ ], "operationId": "UpdateSandbox", "summary": "Update a sandbox", - "description": "Partially update configuration. Omitted fields remain unchanged; supplied arrays and maps replace their previous values. Structured objects update only supplied fields. The name, memory, network, region, image, ports, filesystem, and volumes are immutable after creation. Supplying any of these fields returns 400, including unchanged, empty, or null values.", + "description": "Partially update configuration. Omitted fields remain unchanged; supplied arrays and maps replace their previous values. Structured objects update only supplied fields. The name, memory, region, image, ports, filesystem, and volumes are immutable after creation. Supplying any of these fields returns 400, including unchanged, empty, or null values.", "tags": [ "Sandboxes" ], @@ -12853,12 +12853,12 @@ } }, "400": { - "description": "Invalid request, including attempts to change memory or network configuration.", + "description": "Invalid request, including attempts to change memory configuration.", "content": { "application/json": { "example": { "code": "INVALID_REQUEST", - "message": "Memory and network configuration cannot be changed after creation." + "message": "Memory configuration cannot be changed after creation." } } } @@ -37442,13 +37442,13 @@ "UsageSummaryRequestV1": { "properties": { "start_date": { - "description": "Start date (ISO 8601, UTC). Earliest queryable: 2026-01-01.", + "description": "Inclusive start date (ISO 8601, normalized to midnight UTC). Earliest queryable: 2026-01-01.", "format": "date-time", "title": "Start Date", "type": "string" }, "end_date": { - "description": "End date in ISO 8601 format (UTC). Date range cannot exceed 31 days.", + "description": "Exclusive end date (ISO 8601, normalized to midnight UTC). Date range cannot exceed 31 days.", "format": "date-time", "title": "End Date", "type": "string" @@ -39116,7 +39116,7 @@ "default": null, "description": "Provider that served the requests. Null when not grouping by PROVIDER." }, - "router_id": { + "intelligent_route": { "anyOf": [ { "type": "string" @@ -39126,8 +39126,8 @@ } ], "default": null, - "description": "Slug of the intelligent router, matching its name in `GET /v1/grid/routes`. Null when not grouping by ROUTER.", - "title": "Router Id" + "description": "Name of the intelligent route, as in `GET /v1/grid/routes`. Null when not grouping by INTELLIGENT_ROUTE.", + "title": "Intelligent Route" }, "harness": { "anyOf": [ @@ -39170,7 +39170,7 @@ "type": "integer" }, "requests": { - "description": "Number of requests; with ROUTER, the requests sent through that router. Usage recorded before requests were counted adds zero.", + "description": "Number of requests; with INTELLIGENT_ROUTE, the requests sent through that route. Usage recorded before requests were counted adds zero.", "title": "Requests", "type": "integer" } @@ -39214,7 +39214,7 @@ "TEAM", "MODEL", "PROVIDER", - "ROUTER", + "INTELLIGENT_ROUTE", "HARNESS" ], "title": "GridRouteUsageDimensionV1", @@ -39285,7 +39285,7 @@ "title": "End Date" }, "group_by": { - "description": "Dimensions to break usage down by, repeated once per dimension: USER, TEAM, MODEL, PROVIDER, ROUTER, or HARNESS. Each result represents one observed combination of the requested dimensions within that day, and results are sorted by those values. Combinations without usage are omitted, so result counts can differ between days. ROUTER returns only usage sent through an intelligent router, one row per router, or per router and model with MODEL. Router rows repeat usage already counted in other groupings, so don't add them to model totals. HARNESS includes usage with no recorded harness as a null group. Defaults to MODEL.", + "description": "Dimensions to break usage down by, repeated once per dimension: USER, TEAM, MODEL, PROVIDER, INTELLIGENT_ROUTE, or HARNESS. Each result represents one observed combination of the requested dimensions within that day, and results are sorted by those values. Combinations without usage are omitted, so result counts can differ between days. INTELLIGENT_ROUTE returns only usage sent through an intelligent route, one row per intelligent route, or per intelligent route and model with MODEL. These rows repeat usage already counted in other groupings, so don't add them to model totals. HARNESS includes usage with no recorded harness as a null group. Defaults to MODEL.", "items": { "$ref": "#/components/schemas/GridRouteUsageDimensionV1" }, @@ -39422,54 +39422,22 @@ "title": "UpdateGridRouteRequestV1", "type": "object" }, - "GridBackgroundHarnessDefaultsV1": { - "properties": { - "primary": { - "anyOf": [ - { - "$ref": "#/components/schemas/GridHarnessModelV1" - }, - { - "type": "null" - } - ], - "description": "Route for the primary model, which new sessions use. Null when the team has no route to use." - }, - "background": { - "anyOf": [ - { - "$ref": "#/components/schemas/GridHarnessModelV1" - }, - { - "type": "null" - } - ], - "description": "Route for background tasks, such as session titles. Null when the team has no route to use." - } - }, - "required": [ - "primary", - "background" - ], - "title": "GridBackgroundHarnessDefaultsV1", - "type": "object" - }, "GridHarnessDefaultsV1": { "properties": { "claude_code": { - "$ref": "#/components/schemas/GridBackgroundHarnessDefaultsV1", + "$ref": "#/components/schemas/GridHarnessModelsV1", "description": "Default models for Claude Code." }, "opencode": { - "$ref": "#/components/schemas/GridBackgroundHarnessDefaultsV1", + "$ref": "#/components/schemas/GridHarnessModelsV1", "description": "Default models for OpenCode." }, "codex": { - "$ref": "#/components/schemas/GridBackgroundHarnessDefaultsV1", + "$ref": "#/components/schemas/GridHarnessModelsV1", "description": "Default models for Codex." }, "pi": { - "$ref": "#/components/schemas/GridBackgroundHarnessDefaultsV1", + "$ref": "#/components/schemas/GridHarnessModelsV1", "description": "Default models for Pi." } }, @@ -39500,6 +39468,38 @@ "title": "GridHarnessModelV1", "type": "object" }, + "GridHarnessModelsV1": { + "properties": { + "primary": { + "anyOf": [ + { + "$ref": "#/components/schemas/GridHarnessModelV1" + }, + { + "type": "null" + } + ], + "description": "Route for the primary model, which new sessions use. Null when the team has no route to use." + }, + "background": { + "anyOf": [ + { + "$ref": "#/components/schemas/GridHarnessModelV1" + }, + { + "type": "null" + } + ], + "description": "Route for background tasks, such as session titles. Null when the team has no route to use." + } + }, + "required": [ + "primary", + "background" + ], + "title": "GridHarnessModelsV1", + "type": "object" + }, "GridSettingSourceV1": { "enum": [ "user", @@ -39607,58 +39607,40 @@ "title": "GridTeamSettingsRequestV1", "type": "object" }, - "UpdateGridBackgroundHarnessModelsV1": { + "UpdateGridHarnessDefaultsV1": { "additionalProperties": false, "minProperties": 1, "properties": { - "primary": { + "claude_code": { "anyOf": [ { - "minLength": 1, - "type": "string" + "$ref": "#/components/schemas/UpdateGridHarnessModelsV1" }, { "type": "null" } ], "default": null, - "description": "Route ID for the primary model, which new sessions use. Omit to keep the current route, or pass null to use the team's default.", - "examples": [ - "abc1234" - ], - "title": "Primary", + "description": "Route IDs for the model roles to change; roles left out are unchanged. Every route must belong to the team. Pass null to clear every role, or omit to leave the harness unchanged.", "x-null-distinct": true }, - "background": { + "opencode": { "anyOf": [ { - "minLength": 1, - "type": "string" + "$ref": "#/components/schemas/UpdateGridHarnessModelsV1" }, { "type": "null" } ], "default": null, - "description": "Route ID for background tasks, such as session titles. Omit to keep the current route, or pass null to use the team's default.", - "examples": [ - "def5678" - ], - "title": "Background", + "description": "Route IDs for the model roles to change; roles left out are unchanged. Every route must belong to the team. Pass null to clear every role, or omit to leave the harness unchanged.", "x-null-distinct": true - } - }, - "title": "UpdateGridBackgroundHarnessModelsV1", - "type": "object" - }, - "UpdateGridHarnessDefaultsV1": { - "additionalProperties": false, - "minProperties": 1, - "properties": { - "claude_code": { + }, + "codex": { "anyOf": [ { - "$ref": "#/components/schemas/UpdateGridBackgroundHarnessModelsV1" + "$ref": "#/components/schemas/UpdateGridHarnessModelsV1" }, { "type": "null" @@ -39668,10 +39650,10 @@ "description": "Route IDs for the model roles to change; roles left out are unchanged. Every route must belong to the team. Pass null to clear every role, or omit to leave the harness unchanged.", "x-null-distinct": true }, - "opencode": { + "pi": { "anyOf": [ { - "$ref": "#/components/schemas/UpdateGridBackgroundHarnessModelsV1" + "$ref": "#/components/schemas/UpdateGridHarnessModelsV1" }, { "type": "null" @@ -39680,35 +39662,53 @@ "default": null, "description": "Route IDs for the model roles to change; roles left out are unchanged. Every route must belong to the team. Pass null to clear every role, or omit to leave the harness unchanged.", "x-null-distinct": true - }, - "codex": { + } + }, + "title": "UpdateGridHarnessDefaultsV1", + "type": "object" + }, + "UpdateGridHarnessModelsV1": { + "additionalProperties": false, + "minProperties": 1, + "properties": { + "primary": { "anyOf": [ { - "$ref": "#/components/schemas/UpdateGridBackgroundHarnessModelsV1" + "minLength": 1, + "type": "string" }, { "type": "null" } ], "default": null, - "description": "Route IDs for the model roles to change; roles left out are unchanged. Every route must belong to the team. Pass null to clear every role, or omit to leave the harness unchanged.", + "description": "Route ID for the primary model, which new sessions use. Omit to keep the current route, or pass null to use the team's default.", + "examples": [ + "abc1234" + ], + "title": "Primary", "x-null-distinct": true }, - "pi": { + "background": { "anyOf": [ { - "$ref": "#/components/schemas/UpdateGridBackgroundHarnessModelsV1" + "minLength": 1, + "type": "string" }, { "type": "null" } ], "default": null, - "description": "Route IDs for the model roles to change; roles left out are unchanged. Every route must belong to the team. Pass null to clear every role, or omit to leave the harness unchanged.", + "description": "Route ID for background tasks, such as session titles. Omit to keep the current route, or pass null to use the team's default.", + "examples": [ + "def5678" + ], + "title": "Background", "x-null-distinct": true } }, - "title": "UpdateGridHarnessDefaultsV1", + "title": "UpdateGridHarnessModelsV1", "type": "object" }, "UpdateGridTeamSpendLimitSettingV1": { @@ -42830,249 +42830,6 @@ } } }, - "SandboxNetworkV1": { - "type": "object", - "description": "Network configuration for a sandbox including subnet, domain filtering, and proxy settings", - "properties": { - "proxy": { - "description": "Proxy configuration for routing sandbox HTTP traffic through the platform proxy with MITM inspection and per-destination header/body injection", - "allOf": [ - { - "$ref": "#/components/schemas/SandboxProxyConfigV1" - } - ], - "example": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - }, - "secrets": { - "openai-key": "sk-proj-demo-not-a-valid-api-key" - } - } - ] - } - }, - "subnet": { - "type": "string", - "description": "Subnet name for the sandbox. Defaults to \"default\" at creation.", - "example": "default" - } - }, - "example": { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - }, - "secrets": { - "openai-key": "sk-proj-demo-not-a-valid-api-key" - } - } - ] - }, - "subnet": "default" - } - }, - "SandboxProxyConfigV1": { - "type": "object", - "description": "Proxy configuration for routing sandbox HTTP traffic through the platform proxy with MITM inspection and per-destination header/body injection", - "properties": { - "allowed_domains": { - "type": "array", - "description": "List of allowed external domains (allowlist). When set, only these domains are reachable. Supports wildcards (e.g. *.storage.example.com).", - "items": { - "type": "string", - "example": "api.openai.com" - }, - "example": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ] - }, - "bypass": { - "type": "array", - "description": "Domains that bypass the proxy entirely via the NO_PROXY directive. Traffic to these destinations goes direct, not through the CONNECT tunnel. Supports wildcards. Note that localhost, private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), 169.254.169.254, .local and .internal are always bypassed by default.", - "items": { - "type": "string", - "example": "registry.npmjs.org" - }, - "example": [ - "registry.npmjs.org" - ] - }, - "forbidden_domains": { - "type": "array", - "description": "List of forbidden external domains (denylist). When set, all domains except these are reachable. Supports wildcards (e.g. *.malware.com). If both allowed_domains and forbidden_domains are set, allowed_domains takes precedence.", - "items": { - "type": "string", - "example": "facebook.com" - }, - "example": [ - "facebook.com", - "*.facebook.com" - ] - }, - "routing": { - "type": "array", - "description": "Per-destination routing rules with header/body injection and secrets. Use destinations [\"*\"] for global rules that apply to all destinations.", - "items": { - "$ref": "#/components/schemas/SandboxProxyTargetV1" - }, - "example": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - }, - "secrets": { - "openai-key": "sk-proj-demo-not-a-valid-api-key" - } - } - ] - } - }, - "example": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - }, - "secrets": { - "openai-key": "sk-proj-demo-not-a-valid-api-key" - } - } - ] - } - }, - "SandboxProxyTargetV1": { - "type": "object", - "description": "Routing rule that injects headers and body fields into requests matching the given destinations. Use destinations [\"*\"] for a global rule that applies to all proxied traffic.", - "properties": { - "body": { - "type": "object", - "description": "Body fields to inject into matching requests. Values may contain {{SECRET:name}} references resolved from this rule's secrets.", - "additionalProperties": { - "type": "string" - }, - "example": { - "user": "baseten-api-review-0916" - } - }, - "destinations": { - "type": "array", - "description": "Destination domains this rule applies to. Use [\"*\"] for a global rule that matches all destinations.", - "items": { - "type": "string", - "example": "api.openai.com" - }, - "example": [ - "api.openai.com" - ] - }, - "headers": { - "type": "object", - "description": "Headers to inject into matching requests. Values may contain {{SECRET:name}} references resolved from this rule's secrets.", - "additionalProperties": { - "type": "string" - }, - "example": { - "Authorization": "Bearer {{SECRET:openai-key}}" - } - }, - "secrets": { - "type": "object", - "description": "Named secret values for this routing rule, referenced in headers/body via {{SECRET:name}}. Stored encrypted at rest. Write-only: never returned in API responses.", - "additionalProperties": { - "type": "string" - }, - "example": { - "openai-key": "sk-proj-demo-not-a-valid-api-key" - }, - "writeOnly": true - } - }, - "example": { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - }, - "secrets": { - "openai-key": "sk-proj-demo-not-a-valid-api-key" - } - } - }, "SandboxEnvV1": { "type": "object", "description": "Environment variable with name and value", @@ -43214,48 +42971,6 @@ "terminated_retention": "24h" } }, - "network": { - "description": "Network configuration for a sandbox including subnet, domain filtering, and proxy settings", - "allOf": [ - { - "$ref": "#/components/schemas/SandboxNetworkV1" - } - ], - "example": { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - }, - "secrets": { - "openai-key": "sk-proj-demo-not-a-valid-api-key" - } - } - ] - }, - "subnet": "default" - } - }, "region": { "type": "string", "description": "Region where the sandbox runs (for example us-pdx-1 or eu-lon-1). When omitted at creation, the default sandbox region is used.", @@ -43375,40 +43090,6 @@ ], "terminated_retention": "24h" }, - "network": { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - }, - "secrets": { - "openai-key": "sk-proj-demo-not-a-valid-api-key" - } - } - ] - }, - "subnet": "default" - }, "region": "us-pdx-1", "envs": [ { @@ -43482,48 +43163,6 @@ "terminated_retention": "24h" } }, - "network": { - "description": "Network configuration for a sandbox including subnet, domain filtering, and proxy settings", - "allOf": [ - { - "$ref": "#/components/schemas/SandboxNetworkV1" - } - ], - "example": { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - }, - "secrets": { - "openai-key": "sk-proj-demo-not-a-valid-api-key" - } - } - ] - }, - "subnet": "default" - } - }, "region": { "type": "string", "description": "Region where the sandbox runs (for example us-pdx-1 or eu-lon-1). When omitted at creation, the default sandbox region is used.", @@ -43645,40 +43284,6 @@ ], "terminated_retention": "24h" }, - "network": { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - }, - "secrets": { - "openai-key": "sk-proj-demo-not-a-valid-api-key" - } - } - ] - }, - "subnet": "default" - }, "region": "us-pdx-1", "envs": [ { @@ -43712,7 +43317,7 @@ }, "UpdateSandboxRequestV1": { "type": "object", - "description": "Partial sandbox update. Omitted fields remain unchanged. Supplied arrays and maps (including labels) replace their previous values; supplied structured objects update only their supplied fields. Null is not accepted. The name, memory, cpu, network, region, image, ports, filesystem, and volumes are immutable after creation. Supplying any of these fields returns 400, including unchanged, empty, or null values.", + "description": "Partial sandbox update. Omitted fields remain unchanged. Supplied arrays and maps (including labels) replace their previous values; supplied structured objects update only their supplied fields. Null is not accepted. The name, memory, cpu, region, image, ports, filesystem, and volumes are immutable after creation. Supplying any of these fields returns 400, including unchanged, empty, or null values.", "properties": { "lifecycle": { "description": "Lifecycle configuration controlling automatic sandbox deletion based on idle time, max age, or specific dates", @@ -43939,37 +43544,6 @@ ], "terminated_retention": "24h" }, - "network": { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - } - } - ] - }, - "subnet": "default" - }, "region": "us-pdx-1", "envs": [ { @@ -44086,37 +43660,6 @@ ], "terminated_retention": "24h" }, - "network": { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - } - } - ] - }, - "subnet": "default" - }, "region": "us-pdx-1", "envs": [ { @@ -44198,37 +43741,6 @@ ], "terminated_retention": "24h" }, - "network": { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - } - } - ] - }, - "subnet": "default" - }, "region": "us-pdx-1", "envs": [ { @@ -45758,40 +45270,6 @@ ], "terminated_retention": "24h" }, - "network": { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - }, - "secrets": { - "openai-key": "sk-proj-demo-not-a-valid-api-key" - } - } - ] - }, - "subnet": "default" - }, "region": "us-pdx-1", "envs": [ { @@ -45867,7 +45345,7 @@ "revision": "2" } }, - "description": "All mutable request fields are shown. Memory and network configuration are only accepted during creation." + "description": "All mutable request fields are shown. Memory configuration is only accepted during creation." }, "SandboxReady": { "summary": "Running sandbox with complete configuration", @@ -45892,37 +45370,6 @@ ], "terminated_retention": "24h" }, - "network": { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - } - } - ] - }, - "subnet": "default" - }, "region": "us-pdx-1", "envs": [ { @@ -45962,7 +45409,7 @@ "last_used_at": "2026-09-16T21:31:13Z", "expires_in": 86400 }, - "description": "Example sandbox URL in the b10.run domain. Write-only proxy secrets are not returned. Other values illustrate a fully configured sandbox." + "description": "Example sandbox URL in the b10.run domain. Other values illustrate a fully configured sandbox." }, "SandboxUpdated": { "summary": "Updated sandbox with complete configuration", @@ -45987,37 +45434,6 @@ ], "terminated_retention": "24h" }, - "network": { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - } - } - ] - }, - "subnet": "default" - }, "region": "us-pdx-1", "envs": [ { @@ -46082,37 +45498,6 @@ ], "terminated_retention": "24h" }, - "network": { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - } - } - ] - }, - "subnet": "default" - }, "region": "us-pdx-1", "envs": [ { @@ -46178,37 +45563,6 @@ ], "terminated_retention": "24h" }, - "network": { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - } - } - ] - }, - "subnet": "default" - }, "region": "us-pdx-1", "envs": [ { @@ -46281,37 +45635,6 @@ ], "terminated_retention": "24h" }, - "network": { - "proxy": { - "allowed_domains": [ - "api.openai.com", - "pypi.org", - "files.pythonhosted.org", - "registry.npmjs.org" - ], - "bypass": [ - "registry.npmjs.org" - ], - "forbidden_domains": [ - "facebook.com", - "*.facebook.com" - ], - "routing": [ - { - "destinations": [ - "api.openai.com" - ], - "headers": { - "Authorization": "Bearer {{SECRET:openai-key}}" - }, - "body": { - "user": "baseten-api-review-0916" - } - } - ] - }, - "subnet": "default" - }, "region": "us-pdx-1", "envs": [ { diff --git a/scripts/apigen/specs/sandbox.yml b/scripts/apigen/specs/sandbox.yml index c0d2692..5bc201b 100644 --- a/scripts/apigen/specs/sandbox.yml +++ b/scripts/apigen/specs/sandbox.yml @@ -313,12 +313,6 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorResponse" - "507": - description: Insufficient Storage - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" security: - BearerAuth: [] summary: Attach a drive to a local path @@ -347,12 +341,6 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorResponse" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" "500": description: Internal Server Error content: @@ -458,48 +446,6 @@ paths: summary: Search for text content in files tags: - filesystem - /filesystem-copy: - post: - description: >- - Recursively copy a file or directory in one request, placed like `cp -r`: when destination is an existing directory (or a symlink to one), the copy goes inside it, named after source; otherwise destination is the copy's path. Relative paths use the filesystem working directory. Source symlinks are copied as symlinks; special files are refused. By default existing files are overwritten and existing directories merged. - - - With noOverwrite=true, every entry is created exclusively (O_EXCL, mkdir, symlink), with no check-then-write race: an existing final target, including an empty directory or a dangling symlink, returns 409 with code FILE_ALREADY_EXISTS and is left unchanged. A copy is not a transaction: on a later failure, entries it already created remain. - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/CopyRequest" - description: Source, destination, and optional noOverwrite flag - required: true - responses: - "200": - description: Files copied - content: - application/json: - schema: - $ref: "#/components/schemas/CopyResponse" - "400": - description: Bad request - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - "409": - description: Destination exists (FILE_ALREADY_EXISTS) - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - "422": - description: Copy failed - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - summary: Copy a file or directory - tags: - - filesystem "/filesystem-find/{path}": get: description: Finds files and directories using the find command. @@ -1113,14 +1059,7 @@ paths: tags: - filesystem get: - description: >- - List a directory. Without query parameters, `files` and `subdirectories` hold its direct children. - - - With `recursive=true`, they hold every file and directory below the path (flat, with full paths). With `content=true`, each regular file (or symlink to one) also carries its `content` as UTF-8 text, like a JSON file read; other entries have no `content`. `patterns`, `excludeDirs` and `excludeHidden` select entries the same way as find, but nothing is excluded by default. - - - The response is all or nothing: when more than `maxFiles` files match, or `content=true` and they hold more than `maxBytes` bytes, the request fails with 422 before any content is read. + description: Get a recursive directory tree structure starting from the specified path parameters: - description: Root directory path in: path @@ -1128,41 +1067,6 @@ paths: required: true schema: type: string - - description: "List every file and directory below the path, not only its direct children (default: false)" - in: query - name: recursive - schema: - type: boolean - - description: "Include the content of each regular file, as UTF-8 text (default: false)" - in: query - name: content - schema: - type: boolean - - description: Comma-separated glob patterns matched against file names (e.g., *.json,*.md). Directories are not filtered. - in: query - name: patterns - schema: - type: string - - description: "Comma-separated directory names to skip with everything below them (default: none)" - in: query - name: excludeDirs - schema: - type: string - - description: "Skip files and directories whose name starts with a dot (default: false)" - in: query - name: excludeHidden - schema: - type: boolean - - description: "Fail with 422 when more files match (default: 10000 with recursive or content, otherwise unlimited; at most 100000)" - in: query - name: maxFiles - schema: - type: integer - - description: "With content=true, fail with 422 when the matching files hold more bytes (default: 33554432, 32 MiB; at most 268435456, 256 MiB)" - in: query - name: maxBytes - schema: - type: integer responses: "200": description: Directory tree @@ -1177,7 +1081,7 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" "422": - description: Unprocessable entity, including a maxFiles or maxBytes limit + description: Unprocessable entity content: application/json: schema: @@ -1797,43 +1701,6 @@ paths: summary: Write to a process's stdin tags: - process - "/process/{identifier}/wait": - get: - description: "Hold the request until the process is over for good (no restart to come) or the timeout expires, then return the process like GET /process/{identifier}. A process still running after the timeout comes back with status running: call again. Replaces polling GET /process/{identifier}." - parameters: - - description: Process identifier (PID or name) - in: path - name: identifier - required: true - schema: - type: string - - description: Seconds to wait (default and max 119; larger values are capped) - in: query - name: timeout - schema: - type: integer - responses: - "200": - description: Process information - content: - application/json: - schema: - $ref: "#/components/schemas/ProcessResponse" - "400": - description: Invalid timeout - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - "404": - description: Process not found - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - summary: Wait for a process to finish - tags: - - process /upgrade: post: description: >- @@ -1845,9 +1712,7 @@ paths: Available versions: "latest" (default, most recent release), "develop", "main", or specific tag like "v1.0.0" - Binaries are downloaded from https://github.com/blaxel-ai/sandbox/releases; requests with a baseUrl are rejected. - - Releases that still accepted a baseUrl are refused, and the refusal is reported in the health endpoint's lastUpgrade. + You can also specify a custom baseUrl for forks (defaults to https://github.com/basetenlabs/sandbox/releases) requestBody: content: application/json: @@ -1861,12 +1726,6 @@ paths: application/json: schema: $ref: "#/components/schemas/SuccessResponse" - "400": - description: Invalid version or baseUrl - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" "500": description: Internal server error content: @@ -1882,11 +1741,11 @@ paths: Streams change events for a directory until the client disconnects. - The body is JSON lines (sent with `Content-Type: text/plain`): one event object per line, e.g. `{"op":"WRITE","name":"main.go","path":"/app/src","error":null}`. `op` is the fsnotify operation (CREATE, WRITE, REMOVE, RENAME or CHMOD, several can be joined with `|`), `name` the base name of the changed entry, `path` the directory containing it, and `error` is always null. A `[keepalive]` line (not JSON) is sent every 30 seconds. The response starts once the watch is registered, and the stream ends when the watched directory is removed or renamed, or when events were dropped because the kernel queue overflowed (reopen and resync). + The body is JSON lines (sent with `Content-Type: text/plain`): one event object per line, e.g. `{"op":"WRITE","name":"main.go","path":"/app/src","error":null}`. `op` is the fsnotify operation (CREATE, WRITE, REMOVE, RENAME or CHMOD, several can be joined with `|`), `name` the base name of the changed entry, `path` the directory containing it, and `error` is always null. A `[keepalive]` line (not JSON) is sent every 30 seconds. Only the directory's direct entries are watched. To also watch every subdirectory, including ones created later, end the path with `/**`. The stream reports subsequent changes without an initial snapshot. When a new directory is detected, synthetic CREATE events are emitted for entries already present inside it to cover changes before its watch was registered. parameters: - - description: Comma-separated substrings; events whose full path contains one are skipped, and matching directories are not watched at all + - description: Comma-separated substrings; events whose full path contains one are skipped in: query name: ignore schema: @@ -1920,7 +1779,7 @@ paths: tags: - filesystem servers: - - url: https://sbx-{sandbox_id}-{workspace_id}.{region}.bl.run + - url: https://sbx-{sandbox_id}-{workspace_id}.{region}.b10.co variables: sandbox_id: default: unknown @@ -2076,35 +1935,6 @@ components: - query - total type: object - CopyRequest: - properties: - destination: - example: /app/destination - type: string - noOverwrite: - default: false - example: false - type: boolean - source: - example: /app/source - type: string - required: - - destination - - source - type: object - CopyResponse: - properties: - destination: - type: string - message: - type: string - source: - type: string - required: - - destination - - message - - source - type: object Directory: properties: files: @@ -2115,14 +1945,6 @@ components: type: string path: type: string - recursive: - description: >- - Recursive is true when files and subdirectories hold every level below - - path (tree read with recursive=true). Older APIs ignore that parameter - - and omit this field, so clients can tell a one-level listing apart. - type: boolean subdirectories: description: "@name Subdirectories" items: @@ -2212,9 +2034,6 @@ components: type: object ErrorResponse: properties: - code: - example: FILE_ALREADY_EXISTS - type: string error: example: Error message type: string @@ -2357,9 +2176,6 @@ components: type: object File: properties: - content: - description: Content is set only by a tree read with content=true, for regular files. - type: string group: type: string lastModified: @@ -2644,14 +2460,6 @@ components: type: object ProcessRequest: properties: - callbackSecret: - description: Key the webhook is signed with (HMAC-SHA256, X-Blaxel-Signature header). At least 32 characters; required with callbackUrl. Never returned by the API. - example: a-random-secret-of-at-least-32-chars - type: string - callbackUrl: - description: URL that receives a signed process.exited webhook when the process ends for good (not on auto-restart). Requires callbackSecret. - example: https://example.com/hooks/process - type: string command: example: ls -la type: string @@ -2914,10 +2722,11 @@ components: UpgradeRequest: properties: baseUrl: - description: "Deprecated: custom binary sources are not accepted" + description: Base URL for releases (useful for forks) + example: https://github.com/basetenlabs/sandbox/releases type: string version: - description: 'Version to upgrade to: "latest" (default), "develop", "main", or a release tag like "v1.0.0"' + description: 'Version to upgrade to: "latest" (default), "develop", "main", or specific tag like "v1.0.0"' example: latest type: string type: object diff --git a/tests/sandbox/test_client.py b/tests/sandbox/test_client.py index eabe711..52fa076 100644 --- a/tests/sandbox/test_client.py +++ b/tests/sandbox/test_client.py @@ -20,7 +20,6 @@ SandboxFileSystemOptions, SandboxInfo, SandboxLifecycle, - SandboxNetwork, SandboxPort, SandboxVolumeMount, ) @@ -147,7 +146,6 @@ def test_creates_with_every_field_mapped(self) -> None: ] ), ports=[SandboxPort(target=80)], - network=SandboxNetwork(subnet="default"), filesystem=SandboxFileSystemOptions(max_memory_usage="80%"), volumes=[SandboxVolumeMount(name="data", mount_path="/data")], ) @@ -167,7 +165,6 @@ def test_creates_with_every_field_mapped(self) -> None: ] }, "ports": [{"target": 80}], - "network": {"subnet": "default"}, "filesystem": {"max_memory_usage": "80%"}, "volumes": [{"name": "data", "mount_path": "/data", "read_only": False}], } @@ -488,7 +485,6 @@ def _info() -> SandboxInfo: external_id=None, lifecycle=None, ports=[], - network=None, filesystem=None, volumes=[], created_at=datetime(2026, 10, 6, tzinfo=UTC), diff --git a/tests/sandbox/test_info.py b/tests/sandbox/test_info.py index 847275f..0dfe633 100644 --- a/tests/sandbox/test_info.py +++ b/tests/sandbox/test_info.py @@ -12,16 +12,12 @@ SandboxExpirationPolicyUnknown, SandboxFileSystemOptions, SandboxLifecycle, - SandboxNetwork, - SandboxNetworkProxy, - SandboxNetworkProxyRoute, SandboxPort, ) from baseten.sandbox._info import ( envs_to_api, filesystem_to_api, lifecycle_to_api, - network_to_api, ports_to_api, sandbox_info_from_api, ) @@ -51,13 +47,6 @@ "terminated_retention": "24h", }, "ports": [{"target": 8080, "name": "http", "protocol": "HTTP"}], - "network": { - "subnet": "default", - "proxy": { - "allowed_domains": ["pypi.org"], - "routing": [{"destinations": ["*"], "headers": {"X-A": "b"}}], - }, - }, "filesystem": {"max_memory_usage": 2048}, "created_at": "2026-10-06T12:00:00Z", "updated_at": "2026-10-06T13:00:00Z", @@ -105,15 +94,6 @@ def test_maps_sandbox_record() -> None: terminated_retention=timedelta(hours=24), ) assert info.ports == [SandboxPort(target=8080, name="http", protocol="HTTP")] - assert info.network == SandboxNetwork( - subnet="default", - proxy=SandboxNetworkProxy( - allowed_domains=["pypi.org"], - routing=[ - SandboxNetworkProxyRoute(destinations=["*"], headers={"X-A": "b"}) - ], - ), - ) assert info.filesystem == SandboxFileSystemOptions(max_memory_usage=2048) assert info.created_at == datetime(2026, 10, 6, 12, tzinfo=UTC) assert info.updated_at == datetime(2026, 10, 6, 13, tzinfo=UTC) @@ -138,7 +118,6 @@ def test_maps_sandbox_record_without_optional_fields() -> None: assert info.labels == {} assert info.lifecycle is None assert info.ports == [] - assert info.network is None assert info.filesystem is None assert info.expires_in is None @@ -179,16 +158,6 @@ def test_inputs_send_only_set_fields() -> None: assert [_dump(port) for port in ports_to_api([SandboxPort(target=80)])] == [ {"target": 80} ] - assert _dump( - network_to_api( - SandboxNetwork( - proxy=SandboxNetworkProxy( - bypass=("example.com",), - routing=[SandboxNetworkProxyRoute(secrets={"k": "v"})], - ) - ) - ) - ) == {"proxy": {"bypass": ["example.com"], "routing": [{"secrets": {"k": "v"}}]}} assert _dump(filesystem_to_api(SandboxFileSystemOptions())) == {} diff --git a/tests/sandbox/test_parity.py b/tests/sandbox/test_parity.py index f17f752..9a74b63 100644 --- a/tests/sandbox/test_parity.py +++ b/tests/sandbox/test_parity.py @@ -44,9 +44,6 @@ SandboxFileSystemSubdirectoryInfo, SandboxInfo, SandboxLifecycle, - SandboxNetwork, - SandboxNetworkProxy, - SandboxNetworkProxyRoute, SandboxPort, SandboxPreviewInfo, SandboxPreviews, @@ -178,18 +175,6 @@ def _kwargs(method: Callable[..., Any]) -> set[str]: ours=_dataclass_fields(SandboxPort), generated=baseten.client.managementapi.SandboxPort, ), - "SandboxNetwork": _Parity( - ours=_dataclass_fields(SandboxNetwork), - generated=baseten.client.managementapi.SandboxNetwork, - ), - "SandboxNetworkProxy": _Parity( - ours=_dataclass_fields(SandboxNetworkProxy), - generated=baseten.client.managementapi.SandboxProxyConfig, - ), - "SandboxNetworkProxyRoute": _Parity( - ours=_dataclass_fields(SandboxNetworkProxyRoute), - generated=baseten.client.managementapi.SandboxProxyTarget, - ), "SandboxClient.create": _Parity( ours=_kwargs(SandboxClient.create), generated=baseten.client.managementapi.CreateSandboxRequest,