feat: implement client architecture and sandbox environment #3
4
Makefile
4
Makefile
@ -21,7 +21,9 @@ generate:
|
|||||||
--use-schema-description \
|
--use-schema-description \
|
||||||
--target-python-version 3.13 \
|
--target-python-version 3.13 \
|
||||||
--use-annotated \
|
--use-annotated \
|
||||||
--openapi-scopes schemas
|
--openapi-scopes schemas \
|
||||||
|
--formatters ruff-format ruff-check \
|
||||||
|
--input-file-type openapi
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
uv run ruff check src/
|
uv run ruff check src/
|
||||||
|
|||||||
174
api/openapi.yaml
174
api/openapi.yaml
@ -699,11 +699,17 @@ paths:
|
|||||||
$ref: "#/components/schemas/ExecRequest"
|
$ref: "#/components/schemas/ExecRequest"
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Command output
|
description: Command output (foreground exec)
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/ExecResponse"
|
$ref: "#/components/schemas/ExecResponse"
|
||||||
|
"202":
|
||||||
|
description: Background process started
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BackgroundExecResponse"
|
||||||
"404":
|
"404":
|
||||||
description: Capsule not found
|
description: Capsule not found
|
||||||
content:
|
content:
|
||||||
@ -717,6 +723,122 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/Error"
|
$ref: "#/components/schemas/Error"
|
||||||
|
|
||||||
|
/v1/capsules/{id}/processes:
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
get:
|
||||||
|
summary: List running processes
|
||||||
|
operationId: listProcesses
|
||||||
|
tags: [capsules]
|
||||||
|
security:
|
||||||
|
- apiKeyAuth: []
|
||||||
|
description: |
|
||||||
|
Returns all running processes inside the capsule, including background
|
||||||
|
processes and any processes started by templates or init scripts.
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Process list
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ProcessListResponse"
|
||||||
|
"404":
|
||||||
|
description: Capsule not found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
"409":
|
||||||
|
description: Capsule not running
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
|
||||||
|
/v1/capsules/{id}/processes/{selector}:
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: selector
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Process PID (numeric) or tag (string)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
delete:
|
||||||
|
summary: Kill a process
|
||||||
|
operationId: killProcess
|
||||||
|
tags: [capsules]
|
||||||
|
security:
|
||||||
|
- apiKeyAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: signal
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Signal to send (SIGKILL or SIGTERM, default SIGKILL)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
enum: [SIGKILL, SIGTERM]
|
||||||
|
default: SIGKILL
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: Process killed
|
||||||
|
"404":
|
||||||
|
description: Capsule or process not found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
"409":
|
||||||
|
description: Capsule not running
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
|
||||||
|
/v1/capsules/{id}/processes/{selector}/stream:
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: selector
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Process PID (numeric) or tag (string)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
get:
|
||||||
|
summary: Stream process output via WebSocket
|
||||||
|
operationId: connectProcess
|
||||||
|
tags: [capsules]
|
||||||
|
security:
|
||||||
|
- apiKeyAuth: []
|
||||||
|
description: |
|
||||||
|
Opens a WebSocket connection to stream stdout/stderr from a running
|
||||||
|
background process. The selector can be a numeric PID or a string tag.
|
||||||
|
|
||||||
|
Server sends JSON messages:
|
||||||
|
- `{"type": "start", "pid": 42}` — connected to process
|
||||||
|
- `{"type": "stdout", "data": "..."}` — stdout output
|
||||||
|
- `{"type": "stderr", "data": "..."}` — stderr output
|
||||||
|
- `{"type": "exit", "exit_code": 0}` — process exited
|
||||||
|
- `{"type": "error", "data": "..."}` — error message
|
||||||
|
responses:
|
||||||
|
"101":
|
||||||
|
description: WebSocket upgrade
|
||||||
|
|
||||||
/v1/capsules/{id}/ping:
|
/v1/capsules/{id}/ping:
|
||||||
parameters:
|
parameters:
|
||||||
- name: id
|
- name: id
|
||||||
@ -2153,6 +2275,56 @@ components:
|
|||||||
timeout_sec:
|
timeout_sec:
|
||||||
type: integer
|
type: integer
|
||||||
default: 30
|
default: 30
|
||||||
|
description: Timeout in seconds (foreground exec only, default 30)
|
||||||
|
background:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: If true, starts the process in the background and returns immediately with a PID and tag (HTTP 202)
|
||||||
|
tag:
|
||||||
|
type: string
|
||||||
|
description: Optional user-chosen tag for the background process. Auto-generated if omitted. Only used when background is true.
|
||||||
|
envs:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
description: Environment variables for the process (background exec only)
|
||||||
|
cwd:
|
||||||
|
type: string
|
||||||
|
description: Working directory for the process (background exec only)
|
||||||
|
|
||||||
|
BackgroundExecResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
sandbox_id:
|
||||||
|
type: string
|
||||||
|
cmd:
|
||||||
|
type: string
|
||||||
|
pid:
|
||||||
|
type: integer
|
||||||
|
tag:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
ProcessEntry:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pid:
|
||||||
|
type: integer
|
||||||
|
tag:
|
||||||
|
type: string
|
||||||
|
cmd:
|
||||||
|
type: string
|
||||||
|
args:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
ProcessListResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
processes:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/ProcessEntry"
|
||||||
|
|
||||||
ExecResponse:
|
ExecResponse:
|
||||||
type: object
|
type: object
|
||||||
|
|||||||
@ -20,7 +20,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"datamodel-code-generator>=0.56.0",
|
"datamodel-code-generator[ruff]>=0.56.0",
|
||||||
"mypy>=1.20.0",
|
"mypy>=1.20.0",
|
||||||
"pytest>=9.0.3",
|
"pytest>=9.0.3",
|
||||||
"pytest-asyncio>=1.3.0",
|
"pytest-asyncio>=1.3.0",
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
# generated by datamodel-codegen:
|
# generated by datamodel-codegen:
|
||||||
# filename: openapi.yaml
|
# filename: openapi.yaml
|
||||||
# timestamp: 2026-04-12T20:56:29+00:00
|
# timestamp: 2026-04-15T08:37:41+00:00
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from enum import StrEnum
|
|
||||||
from typing import Annotated
|
|
||||||
|
|
||||||
from pydantic import AwareDatetime, BaseModel, EmailStr, Field
|
from pydantic import AwareDatetime, BaseModel, EmailStr, Field
|
||||||
|
from typing import Annotated
|
||||||
|
from enum import StrEnum
|
||||||
|
|
||||||
|
|
||||||
class SignupRequest(BaseModel):
|
class SignupRequest(BaseModel):
|
||||||
@ -22,7 +20,7 @@ class LoginRequest(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class AuthResponse(BaseModel):
|
class AuthResponse(BaseModel):
|
||||||
token: Annotated[str | None, Field(description='JWT token (valid for 6 hours)')] = (
|
token: Annotated[str | None, Field(description="JWT token (valid for 6 hours)")] = (
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
user_id: str | None = None
|
user_id: str | None = None
|
||||||
@ -32,7 +30,7 @@ class AuthResponse(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class CreateAPIKeyRequest(BaseModel):
|
class CreateAPIKeyRequest(BaseModel):
|
||||||
name: str | None = 'Unnamed API Key'
|
name: str | None = "Unnamed API Key"
|
||||||
|
|
||||||
|
|
||||||
class APIKeyResponse(BaseModel):
|
class APIKeyResponse(BaseModel):
|
||||||
@ -47,29 +45,29 @@ class APIKeyResponse(BaseModel):
|
|||||||
key: Annotated[
|
key: Annotated[
|
||||||
str | None,
|
str | None,
|
||||||
Field(
|
Field(
|
||||||
description='Full plaintext key. Only returned on creation, never again.'
|
description="Full plaintext key. Only returned on creation, never again."
|
||||||
),
|
),
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
|
||||||
class CreateCapsuleRequest(BaseModel):
|
class CreateCapsuleRequest(BaseModel):
|
||||||
template: str | None = 'minimal'
|
template: str | None = "minimal"
|
||||||
vcpus: int | None = 1
|
vcpus: int | None = 1
|
||||||
memory_mb: int | None = 512
|
memory_mb: int | None = 512
|
||||||
timeout_sec: Annotated[
|
timeout_sec: Annotated[
|
||||||
int | None,
|
int | None,
|
||||||
Field(
|
Field(
|
||||||
description='Auto-pause TTL in seconds. The capsule is automatically paused after this duration of inactivity (no exec or ping). 0 means no auto-pause.\n'
|
description="Auto-pause TTL in seconds. The capsule is automatically paused after this duration of inactivity (no exec or ping). 0 means no auto-pause.\n"
|
||||||
),
|
),
|
||||||
] = 0
|
] = 0
|
||||||
|
|
||||||
|
|
||||||
class Range(StrEnum):
|
class Range(StrEnum):
|
||||||
field_5m = '5m'
|
field_5m = "5m"
|
||||||
field_1h = '1h'
|
field_1h = "1h"
|
||||||
field_6h = '6h'
|
field_6h = "6h"
|
||||||
field_24h = '24h'
|
field_24h = "24h"
|
||||||
field_30d = '30d'
|
field_30d = "30d"
|
||||||
|
|
||||||
|
|
||||||
class Current(BaseModel):
|
class Current(BaseModel):
|
||||||
@ -104,22 +102,22 @@ class CapsuleStats(BaseModel):
|
|||||||
range: Range | None = None
|
range: Range | None = None
|
||||||
current: Current | None = None
|
current: Current | None = None
|
||||||
peaks: Annotated[
|
peaks: Annotated[
|
||||||
Peaks | None, Field(description='Maximum values over the last 30 days.')
|
Peaks | None, Field(description="Maximum values over the last 30 days.")
|
||||||
] = None
|
] = None
|
||||||
series: Annotated[
|
series: Annotated[
|
||||||
Series | None, Field(description='Parallel arrays for chart rendering.')
|
Series | None, Field(description="Parallel arrays for chart rendering.")
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
|
||||||
class Status(StrEnum):
|
class Status(StrEnum):
|
||||||
pending = 'pending'
|
pending = "pending"
|
||||||
starting = 'starting'
|
starting = "starting"
|
||||||
running = 'running'
|
running = "running"
|
||||||
paused = 'paused'
|
paused = "paused"
|
||||||
hibernated = 'hibernated'
|
hibernated = "hibernated"
|
||||||
stopped = 'stopped'
|
stopped = "stopped"
|
||||||
missing = 'missing'
|
missing = "missing"
|
||||||
error = 'error'
|
error = "error"
|
||||||
|
|
||||||
|
|
||||||
class Capsule(BaseModel):
|
class Capsule(BaseModel):
|
||||||
@ -139,17 +137,17 @@ class Capsule(BaseModel):
|
|||||||
|
|
||||||
class CreateSnapshotRequest(BaseModel):
|
class CreateSnapshotRequest(BaseModel):
|
||||||
sandbox_id: Annotated[
|
sandbox_id: Annotated[
|
||||||
str, Field(description='ID of the running capsule to snapshot.')
|
str, Field(description="ID of the running capsule to snapshot.")
|
||||||
]
|
]
|
||||||
name: Annotated[
|
name: Annotated[
|
||||||
str | None,
|
str | None,
|
||||||
Field(description='Name for the snapshot template. Auto-generated if omitted.'),
|
Field(description="Name for the snapshot template. Auto-generated if omitted."),
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
|
||||||
class Type(StrEnum):
|
class Type(StrEnum):
|
||||||
base = 'base'
|
base = "base"
|
||||||
snapshot = 'snapshot'
|
snapshot = "snapshot"
|
||||||
|
|
||||||
|
|
||||||
class Template(BaseModel):
|
class Template(BaseModel):
|
||||||
@ -164,7 +162,50 @@ class Template(BaseModel):
|
|||||||
class ExecRequest(BaseModel):
|
class ExecRequest(BaseModel):
|
||||||
cmd: str
|
cmd: str
|
||||||
args: list[str] | None = None
|
args: list[str] | None = None
|
||||||
timeout_sec: int | None = 30
|
timeout_sec: Annotated[
|
||||||
|
int | None,
|
||||||
|
Field(description="Timeout in seconds (foreground exec only, default 30)"),
|
||||||
|
] = 30
|
||||||
|
background: Annotated[
|
||||||
|
bool | None,
|
||||||
|
Field(
|
||||||
|
description="If true, starts the process in the background and returns immediately with a PID and tag (HTTP 202)"
|
||||||
|
),
|
||||||
|
] = False
|
||||||
|
tag: Annotated[
|
||||||
|
str | None,
|
||||||
|
Field(
|
||||||
|
description="Optional user-chosen tag for the background process. Auto-generated if omitted. Only used when background is true."
|
||||||
|
),
|
||||||
|
] = None
|
||||||
|
envs: Annotated[
|
||||||
|
dict[str, str] | None,
|
||||||
|
Field(
|
||||||
|
description="Environment variables for the process (background exec only)"
|
||||||
|
),
|
||||||
|
] = None
|
||||||
|
cwd: Annotated[
|
||||||
|
str | None,
|
||||||
|
Field(description="Working directory for the process (background exec only)"),
|
||||||
|
] = None
|
||||||
|
|
||||||
|
|
||||||
|
class BackgroundExecResponse(BaseModel):
|
||||||
|
sandbox_id: str | None = None
|
||||||
|
cmd: str | None = None
|
||||||
|
pid: int | None = None
|
||||||
|
tag: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessEntry(BaseModel):
|
||||||
|
pid: int | None = None
|
||||||
|
tag: str | None = None
|
||||||
|
cmd: str | None = None
|
||||||
|
args: list[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessListResponse(BaseModel):
|
||||||
|
processes: list[ProcessEntry] | None = None
|
||||||
|
|
||||||
|
|
||||||
class Encoding(StrEnum):
|
class Encoding(StrEnum):
|
||||||
@ -172,8 +213,8 @@ class Encoding(StrEnum):
|
|||||||
Output encoding. "base64" when stdout/stderr contain binary data.
|
Output encoding. "base64" when stdout/stderr contain binary data.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
utf_8 = 'utf-8'
|
utf_8 = "utf-8"
|
||||||
base64 = 'base64'
|
base64 = "base64"
|
||||||
|
|
||||||
|
|
||||||
class ExecResponse(BaseModel):
|
class ExecResponse(BaseModel):
|
||||||
@ -192,23 +233,23 @@ class ExecResponse(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class ReadFileRequest(BaseModel):
|
class ReadFileRequest(BaseModel):
|
||||||
path: Annotated[str, Field(description='Absolute file path inside the capsule')]
|
path: Annotated[str, Field(description="Absolute file path inside the capsule")]
|
||||||
|
|
||||||
|
|
||||||
class ListDirRequest(BaseModel):
|
class ListDirRequest(BaseModel):
|
||||||
path: Annotated[str, Field(description='Directory path inside the capsule')]
|
path: Annotated[str, Field(description="Directory path inside the capsule")]
|
||||||
depth: Annotated[
|
depth: Annotated[
|
||||||
int | None,
|
int | None,
|
||||||
Field(
|
Field(
|
||||||
description='Recursion depth (0 = non-recursive, 1 = immediate children)'
|
description="Recursion depth (0 = non-recursive, 1 = immediate children)"
|
||||||
),
|
),
|
||||||
] = 1
|
] = 1
|
||||||
|
|
||||||
|
|
||||||
class Type1(StrEnum):
|
class Type1(StrEnum):
|
||||||
file = 'file'
|
file = "file"
|
||||||
directory = 'directory'
|
directory = "directory"
|
||||||
symlink = 'symlink'
|
symlink = "symlink"
|
||||||
|
|
||||||
|
|
||||||
class FileEntry(BaseModel):
|
class FileEntry(BaseModel):
|
||||||
@ -223,14 +264,14 @@ class FileEntry(BaseModel):
|
|||||||
owner: str | None = None
|
owner: str | None = None
|
||||||
group: str | None = None
|
group: str | None = None
|
||||||
modified_at: Annotated[
|
modified_at: Annotated[
|
||||||
int | None, Field(description='Unix timestamp (seconds)')
|
int | None, Field(description="Unix timestamp (seconds)")
|
||||||
] = None
|
] = None
|
||||||
symlink_target: str | None = None
|
symlink_target: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class MakeDirRequest(BaseModel):
|
class MakeDirRequest(BaseModel):
|
||||||
path: Annotated[
|
path: Annotated[
|
||||||
str, Field(description='Directory path to create inside the capsule')
|
str, Field(description="Directory path to create inside the capsule")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -239,7 +280,7 @@ class MakeDirResponse(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class RemoveRequest(BaseModel):
|
class RemoveRequest(BaseModel):
|
||||||
path: Annotated[str, Field(description='Path to remove inside the capsule')]
|
path: Annotated[str, Field(description="Path to remove inside the capsule")]
|
||||||
|
|
||||||
|
|
||||||
class Type2(StrEnum):
|
class Type2(StrEnum):
|
||||||
@ -247,51 +288,51 @@ class Type2(StrEnum):
|
|||||||
Host type. Regular hosts are shared; BYOC hosts belong to a team.
|
Host type. Regular hosts are shared; BYOC hosts belong to a team.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
regular = 'regular'
|
regular = "regular"
|
||||||
byoc = 'byoc'
|
byoc = "byoc"
|
||||||
|
|
||||||
|
|
||||||
class CreateHostRequest(BaseModel):
|
class CreateHostRequest(BaseModel):
|
||||||
type: Annotated[
|
type: Annotated[
|
||||||
Type2,
|
Type2,
|
||||||
Field(
|
Field(
|
||||||
description='Host type. Regular hosts are shared; BYOC hosts belong to a team.'
|
description="Host type. Regular hosts are shared; BYOC hosts belong to a team."
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
team_id: Annotated[str | None, Field(description='Required for BYOC hosts.')] = None
|
team_id: Annotated[str | None, Field(description="Required for BYOC hosts.")] = None
|
||||||
provider: Annotated[
|
provider: Annotated[
|
||||||
str | None,
|
str | None,
|
||||||
Field(description='Cloud provider (e.g. aws, gcp, hetzner, bare-metal).'),
|
Field(description="Cloud provider (e.g. aws, gcp, hetzner, bare-metal)."),
|
||||||
] = None
|
] = None
|
||||||
availability_zone: Annotated[
|
availability_zone: Annotated[
|
||||||
str | None, Field(description='Availability zone (e.g. us-east, eu-west).')
|
str | None, Field(description="Availability zone (e.g. us-east, eu-west).")
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
|
||||||
class RegisterHostRequest(BaseModel):
|
class RegisterHostRequest(BaseModel):
|
||||||
token: Annotated[
|
token: Annotated[
|
||||||
str, Field(description='One-time registration token from POST /v1/hosts.')
|
str, Field(description="One-time registration token from POST /v1/hosts.")
|
||||||
]
|
]
|
||||||
arch: Annotated[
|
arch: Annotated[
|
||||||
str | None, Field(description='CPU architecture (e.g. x86_64, aarch64).')
|
str | None, Field(description="CPU architecture (e.g. x86_64, aarch64).")
|
||||||
] = None
|
] = None
|
||||||
cpu_cores: int | None = None
|
cpu_cores: int | None = None
|
||||||
memory_mb: int | None = None
|
memory_mb: int | None = None
|
||||||
disk_gb: int | None = None
|
disk_gb: int | None = None
|
||||||
address: Annotated[str, Field(description='Host agent address (ip:port).')]
|
address: Annotated[str, Field(description="Host agent address (ip:port).")]
|
||||||
|
|
||||||
|
|
||||||
class Type3(StrEnum):
|
class Type3(StrEnum):
|
||||||
regular = 'regular'
|
regular = "regular"
|
||||||
byoc = 'byoc'
|
byoc = "byoc"
|
||||||
|
|
||||||
|
|
||||||
class Status1(StrEnum):
|
class Status1(StrEnum):
|
||||||
pending = 'pending'
|
pending = "pending"
|
||||||
online = 'online'
|
online = "online"
|
||||||
offline = 'offline'
|
offline = "offline"
|
||||||
draining = 'draining'
|
draining = "draining"
|
||||||
unreachable = 'unreachable'
|
unreachable = "unreachable"
|
||||||
|
|
||||||
|
|
||||||
class Host(BaseModel):
|
class Host(BaseModel):
|
||||||
@ -316,7 +357,7 @@ class RefreshHostTokenRequest(BaseModel):
|
|||||||
refresh_token: Annotated[
|
refresh_token: Annotated[
|
||||||
str,
|
str,
|
||||||
Field(
|
Field(
|
||||||
description='Refresh token obtained from registration or a previous refresh.'
|
description="Refresh token obtained from registration or a previous refresh."
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -324,12 +365,12 @@ class RefreshHostTokenRequest(BaseModel):
|
|||||||
class RefreshHostTokenResponse(BaseModel):
|
class RefreshHostTokenResponse(BaseModel):
|
||||||
host: Host | None = None
|
host: Host | None = None
|
||||||
token: Annotated[
|
token: Annotated[
|
||||||
str | None, Field(description='New host JWT. Valid for 7 days.')
|
str | None, Field(description="New host JWT. Valid for 7 days.")
|
||||||
] = None
|
] = None
|
||||||
refresh_token: Annotated[
|
refresh_token: Annotated[
|
||||||
str | None,
|
str | None,
|
||||||
Field(
|
Field(
|
||||||
description='New refresh token. Valid for 60 days; old token is revoked.'
|
description="New refresh token. Valid for 60 days; old token is revoked."
|
||||||
),
|
),
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
@ -338,16 +379,16 @@ class HostDeletePreview(BaseModel):
|
|||||||
host: Host | None = None
|
host: Host | None = None
|
||||||
sandbox_ids: Annotated[
|
sandbox_ids: Annotated[
|
||||||
list[str] | None,
|
list[str] | None,
|
||||||
Field(description='IDs of capsulees that would be destroyed on force-delete.'),
|
Field(description="IDs of capsulees that would be destroyed on force-delete."),
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
|
||||||
class Error(BaseModel):
|
class Error(BaseModel):
|
||||||
code: Annotated[str | None, Field(examples=['host_has_sandboxes'])] = None
|
code: Annotated[str | None, Field(examples=["host_has_sandboxes"])] = None
|
||||||
message: str | None = None
|
message: str | None = None
|
||||||
sandbox_ids: Annotated[
|
sandbox_ids: Annotated[
|
||||||
list[str] | None,
|
list[str] | None,
|
||||||
Field(description='IDs of active capsulees blocking deletion.'),
|
Field(description="IDs of active capsulees blocking deletion."),
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
|
||||||
@ -368,15 +409,15 @@ class Team(BaseModel):
|
|||||||
id: str | None = None
|
id: str | None = None
|
||||||
name: str | None = None
|
name: str | None = None
|
||||||
slug: Annotated[
|
slug: Annotated[
|
||||||
str | None, Field(description='Immutable 12-char hex slug (e.g. a1b2c3-d1e2f3)')
|
str | None, Field(description="Immutable 12-char hex slug (e.g. a1b2c3-d1e2f3)")
|
||||||
] = None
|
] = None
|
||||||
created_at: AwareDatetime | None = None
|
created_at: AwareDatetime | None = None
|
||||||
|
|
||||||
|
|
||||||
class Role(StrEnum):
|
class Role(StrEnum):
|
||||||
owner = 'owner'
|
owner = "owner"
|
||||||
admin = 'admin'
|
admin = "admin"
|
||||||
member = 'member'
|
member = "member"
|
||||||
|
|
||||||
|
|
||||||
class TeamWithRole(Team):
|
class TeamWithRole(Team):
|
||||||
@ -396,13 +437,13 @@ class TeamDetail(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Range1(StrEnum):
|
class Range1(StrEnum):
|
||||||
field_5m = '5m'
|
field_5m = "5m"
|
||||||
field_10m = '10m'
|
field_10m = "10m"
|
||||||
field_1h = '1h'
|
field_1h = "1h"
|
||||||
field_2h = '2h'
|
field_2h = "2h"
|
||||||
field_6h = '6h'
|
field_6h = "6h"
|
||||||
field_12h = '12h'
|
field_12h = "12h"
|
||||||
field_24h = '24h'
|
field_24h = "24h"
|
||||||
|
|
||||||
|
|
||||||
class MetricPoint(BaseModel):
|
class MetricPoint(BaseModel):
|
||||||
@ -410,41 +451,41 @@ class MetricPoint(BaseModel):
|
|||||||
cpu_pct: Annotated[
|
cpu_pct: Annotated[
|
||||||
float | None,
|
float | None,
|
||||||
Field(
|
Field(
|
||||||
description='CPU utilization percentage (0-100), normalized to vCPU count'
|
description="CPU utilization percentage (0-100), normalized to vCPU count"
|
||||||
),
|
),
|
||||||
] = None
|
] = None
|
||||||
mem_bytes: Annotated[
|
mem_bytes: Annotated[
|
||||||
int | None,
|
int | None,
|
||||||
Field(description='Resident memory in bytes (VmRSS of Firecracker process)'),
|
Field(description="Resident memory in bytes (VmRSS of Firecracker process)"),
|
||||||
] = None
|
] = None
|
||||||
disk_bytes: Annotated[
|
disk_bytes: Annotated[
|
||||||
int | None, Field(description='Allocated disk bytes for the CoW sparse file')
|
int | None, Field(description="Allocated disk bytes for the CoW sparse file")
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
|
||||||
class Provider(StrEnum):
|
class Provider(StrEnum):
|
||||||
discord = 'discord'
|
discord = "discord"
|
||||||
slack = 'slack'
|
slack = "slack"
|
||||||
teams = 'teams'
|
teams = "teams"
|
||||||
googlechat = 'googlechat'
|
googlechat = "googlechat"
|
||||||
telegram = 'telegram'
|
telegram = "telegram"
|
||||||
matrix = 'matrix'
|
matrix = "matrix"
|
||||||
webhook = 'webhook'
|
webhook = "webhook"
|
||||||
|
|
||||||
|
|
||||||
class Event(StrEnum):
|
class Event(StrEnum):
|
||||||
capsule_created = 'capsule.created'
|
capsule_created = "capsule.created"
|
||||||
capsule_running = 'capsule.running'
|
capsule_running = "capsule.running"
|
||||||
capsule_paused = 'capsule.paused'
|
capsule_paused = "capsule.paused"
|
||||||
capsule_destroyed = 'capsule.destroyed'
|
capsule_destroyed = "capsule.destroyed"
|
||||||
template_snapshot_created = 'template.snapshot.created'
|
template_snapshot_created = "template.snapshot.created"
|
||||||
template_snapshot_deleted = 'template.snapshot.deleted'
|
template_snapshot_deleted = "template.snapshot.deleted"
|
||||||
host_up = 'host.up'
|
host_up = "host.up"
|
||||||
host_down = 'host.down'
|
host_down = "host.down"
|
||||||
|
|
||||||
|
|
||||||
class CreateChannelRequest(BaseModel):
|
class CreateChannelRequest(BaseModel):
|
||||||
name: Annotated[str, Field(description='Unique channel name within the team.')]
|
name: Annotated[str, Field(description="Unique channel name within the team.")]
|
||||||
provider: Provider
|
provider: Provider
|
||||||
config: Annotated[
|
config: Annotated[
|
||||||
dict[str, str],
|
dict[str, str],
|
||||||
@ -460,7 +501,7 @@ class TestChannelRequest(BaseModel):
|
|||||||
config: Annotated[
|
config: Annotated[
|
||||||
dict[str, str],
|
dict[str, str],
|
||||||
Field(
|
Field(
|
||||||
description='Provider-specific configuration fields (same as CreateChannelRequest.config).'
|
description="Provider-specific configuration fields (same as CreateChannelRequest.config)."
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -489,7 +530,7 @@ class ChannelResponse(BaseModel):
|
|||||||
updated_at: AwareDatetime | None = None
|
updated_at: AwareDatetime | None = None
|
||||||
secret: Annotated[
|
secret: Annotated[
|
||||||
str | None,
|
str | None,
|
||||||
Field(description='Webhook secret. Only returned on creation, never again.'),
|
Field(description="Webhook secret. Only returned on creation, never again."),
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
|
||||||
@ -511,7 +552,7 @@ class CreateHostResponse(BaseModel):
|
|||||||
registration_token: Annotated[
|
registration_token: Annotated[
|
||||||
str | None,
|
str | None,
|
||||||
Field(
|
Field(
|
||||||
description='One-time registration token for the host agent. Expires in 1 hour.'
|
description="One-time registration token for the host agent. Expires in 1 hour."
|
||||||
),
|
),
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
@ -520,12 +561,12 @@ class RegisterHostResponse(BaseModel):
|
|||||||
host: Host | None = None
|
host: Host | None = None
|
||||||
token: Annotated[
|
token: Annotated[
|
||||||
str | None,
|
str | None,
|
||||||
Field(description='Host JWT for X-Host-Token header. Valid for 7 days.'),
|
Field(description="Host JWT for X-Host-Token header. Valid for 7 days."),
|
||||||
] = None
|
] = None
|
||||||
refresh_token: Annotated[
|
refresh_token: Annotated[
|
||||||
str | None,
|
str | None,
|
||||||
Field(
|
Field(
|
||||||
description='Refresh token for obtaining new JWTs. Valid for 60 days; rotated on each use.'
|
description="Refresh token for obtaining new JWTs. Valid for 60 days; rotated on each use."
|
||||||
),
|
),
|
||||||
] = None
|
] = None
|
||||||
|
|
||||||
|
|||||||
11
uv.lock
generated
11
uv.lock
generated
@ -1,5 +1,5 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 2
|
revision = 3
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
resolution-markers = [
|
resolution-markers = [
|
||||||
"python_full_version >= '3.14'",
|
"python_full_version >= '3.14'",
|
||||||
@ -112,6 +112,11 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ed/3a/7f169ffc7a2d69a4f9158b1ac083f685b7f4a1a8a1db5d1e4abbb4e741b7/datamodel_code_generator-0.56.0-py3-none-any.whl", hash = "sha256:a0559683fbe90cdf2ce9b6637e3adae3e3a8056a8d0516df581d486e2834ead2", size = 256545, upload-time = "2026-04-04T09:46:17.582Z" },
|
{ url = "https://files.pythonhosted.org/packages/ed/3a/7f169ffc7a2d69a4f9158b1ac083f685b7f4a1a8a1db5d1e4abbb4e741b7/datamodel_code_generator-0.56.0-py3-none-any.whl", hash = "sha256:a0559683fbe90cdf2ce9b6637e3adae3e3a8056a8d0516df581d486e2834ead2", size = 256545, upload-time = "2026-04-04T09:46:17.582Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
ruff = [
|
||||||
|
{ name = "ruff" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dnspython"
|
name = "dnspython"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
@ -684,7 +689,7 @@ dependencies = [
|
|||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "datamodel-code-generator" },
|
{ name = "datamodel-code-generator", extra = ["ruff"] },
|
||||||
{ name = "mypy" },
|
{ name = "mypy" },
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
{ name = "pytest-asyncio" },
|
{ name = "pytest-asyncio" },
|
||||||
@ -702,7 +707,7 @@ requires-dist = [
|
|||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
{ name = "datamodel-code-generator", specifier = ">=0.56.0" },
|
{ name = "datamodel-code-generator", extras = ["ruff"], specifier = ">=0.56.0" },
|
||||||
{ name = "mypy", specifier = ">=1.20.0" },
|
{ name = "mypy", specifier = ">=1.20.0" },
|
||||||
{ name = "pytest", specifier = ">=9.0.3" },
|
{ name = "pytest", specifier = ">=9.0.3" },
|
||||||
{ name = "pytest-asyncio", specifier = ">=1.3.0" },
|
{ name = "pytest-asyncio", specifier = ">=1.3.0" },
|
||||||
|
|||||||
Reference in New Issue
Block a user