hexel 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hexel/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from hexel._client import Hexel
2
+
3
+ __all__ = ["Hexel"]
4
+ __version__ = "0.1.0"
hexel/_client.py ADDED
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+ from hexel._internal.auth import AuthManager
3
+ from hexel._internal.http import HttpClient
4
+ from hexel.compute import ComputeClient
5
+
6
+
7
+ class Hexel:
8
+ """
9
+ Hexel SDK client.
10
+
11
+ Usage:
12
+ from hexel import Hexel
13
+
14
+ client = Hexel(api_key="studio_live_xxxx")
15
+ sandbox = client.compute.sandbox.create(tier="standard")
16
+ result = sandbox.execute("print(1 + 1)")
17
+ """
18
+
19
+ def __init__(
20
+ self,
21
+ *,
22
+ api_key: str | None = None,
23
+ client_id: str | None = None,
24
+ client_secret: str | None = None,
25
+ base_url: str = "https://compute.hexelstudio.com",
26
+ sts_url: str = "https://sts.hexelstudio.com",
27
+ timeout: float = 30.0,
28
+ ):
29
+ self._auth = AuthManager(
30
+ api_key=api_key,
31
+ client_id=client_id,
32
+ client_secret=client_secret,
33
+ sts_url=sts_url,
34
+ )
35
+ self._http = HttpClient(base_url=base_url, auth=self._auth, timeout=timeout)
36
+ self.compute = ComputeClient(self._http)
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,52 @@
1
+ """Token management — handles API key and client_credentials auth flows."""
2
+ from __future__ import annotations
3
+ import time
4
+ import httpx
5
+
6
+
7
+ class AuthManager:
8
+ def __init__(
9
+ self,
10
+ *,
11
+ api_key: str | None = None,
12
+ client_id: str | None = None,
13
+ client_secret: str | None = None,
14
+ sts_url: str = "https://sts.hexelstudio.com",
15
+ ):
16
+ self._api_key = api_key
17
+ self._client_id = client_id
18
+ self._client_secret = client_secret
19
+ self._sts_url = sts_url
20
+ self._access_token: str | None = None
21
+ self._expires_at: float = 0
22
+ self._http = httpx.Client(timeout=10)
23
+
24
+ if not api_key and not (client_id and client_secret):
25
+ raise ValueError("Provide api_key or (client_id + client_secret)")
26
+
27
+ @property
28
+ def token(self) -> str:
29
+ if self._access_token and time.time() < self._expires_at - 30:
30
+ return self._access_token
31
+ self._refresh()
32
+ return self._access_token
33
+
34
+ def _refresh(self):
35
+ if self._api_key:
36
+ resp = self._http.post(
37
+ f"{self._sts_url}/token",
38
+ headers={"X-API-Key": self._api_key},
39
+ )
40
+ else:
41
+ resp = self._http.post(
42
+ f"{self._sts_url}/token",
43
+ json={
44
+ "grant_type": "client_credentials",
45
+ "client_id": self._client_id,
46
+ "client_secret": self._client_secret,
47
+ },
48
+ )
49
+ resp.raise_for_status()
50
+ data = resp.json()
51
+ self._access_token = data["access_token"]
52
+ self._expires_at = time.time() + data.get("expires_in", 900)
@@ -0,0 +1,48 @@
1
+ """HTTP request pipeline — retries, auth refresh, error handling."""
2
+ from __future__ import annotations
3
+ import time
4
+ import httpx
5
+ from hexel._internal.auth import AuthManager
6
+
7
+ RETRY_STATUS = {429, 500, 502, 503}
8
+ MAX_RETRIES = 3
9
+
10
+
11
+ class HttpClient:
12
+ def __init__(self, *, base_url: str, auth: AuthManager, timeout: float = 30.0):
13
+ self._base_url = base_url.rstrip("/")
14
+ self._auth = auth
15
+ self._client = httpx.Client(timeout=timeout)
16
+
17
+ def request(self, method: str, path: str, **kwargs) -> httpx.Response:
18
+ url = f"{self._base_url}{path}"
19
+ headers = kwargs.pop("headers", {})
20
+ headers["Authorization"] = f"Bearer {self._auth.token}"
21
+ headers.setdefault("Content-Type", "application/json")
22
+
23
+ for attempt in range(MAX_RETRIES + 1):
24
+ resp = self._client.request(method, url, headers=headers, **kwargs)
25
+ if resp.status_code == 401 and attempt == 0:
26
+ self._auth._refresh()
27
+ headers["Authorization"] = f"Bearer {self._auth.token}"
28
+ continue
29
+ if resp.status_code in RETRY_STATUS and attempt < MAX_RETRIES:
30
+ time.sleep(0.5 * (2 ** attempt))
31
+ continue
32
+ return resp
33
+ return resp
34
+
35
+ def get(self, path, **kw) -> httpx.Response:
36
+ return self.request("GET", path, **kw)
37
+
38
+ def post(self, path, **kw) -> httpx.Response:
39
+ return self.request("POST", path, **kw)
40
+
41
+ def put(self, path, **kw) -> httpx.Response:
42
+ return self.request("PUT", path, **kw)
43
+
44
+ def delete(self, path, **kw) -> httpx.Response:
45
+ return self.request("DELETE", path, **kw)
46
+
47
+ def patch(self, path, **kw) -> httpx.Response:
48
+ return self.request("PATCH", path, **kw)
hexel/_internal/ws.py ADDED
@@ -0,0 +1,30 @@
1
+ """WebSocket client for sandbox execution."""
2
+ from __future__ import annotations
3
+ import json
4
+ from websockets.sync.client import connect
5
+ from hexel._internal.auth import AuthManager
6
+
7
+
8
+ class WSConnection:
9
+ """Persistent WebSocket connection to a sandbox's agentd."""
10
+
11
+ def __init__(self, url: str, auth: AuthManager):
12
+ self._ws = connect(url, additional_headers={"Authorization": f"Bearer {auth.token}"})
13
+ msg = json.loads(self._ws.recv())
14
+ self.context_id = msg.get("data", "")
15
+
16
+ def send(self, msg: dict) -> list[dict]:
17
+ self._ws.send(json.dumps(msg))
18
+ results = []
19
+ while True:
20
+ data = json.loads(self._ws.recv())
21
+ results.append(data)
22
+ if data.get("type") in ("done", "execution_complete", "error"):
23
+ break
24
+ return results
25
+
26
+ def close(self):
27
+ try:
28
+ self._ws.close()
29
+ except Exception:
30
+ pass
@@ -0,0 +1,15 @@
1
+ """Generated by hexel-sdk-generator. Do not edit manually."""
2
+ from hexel.compute._sandbox import SandboxClient
3
+ from hexel.compute._agent import AgentClient
4
+ from hexel.compute._instance import InstanceClient
5
+
6
+ __all__ = ["ComputeClient", "SandboxClient", "AgentClient", "InstanceClient"]
7
+
8
+
9
+ class ComputeClient:
10
+ """Hexel Compute — deploy agents and run sandboxes."""
11
+
12
+ def __init__(self, http):
13
+ self.sandbox = SandboxClient(http)
14
+ self.agent = AgentClient(http)
15
+ self.instance = InstanceClient(http)
@@ -0,0 +1,45 @@
1
+ """Generated by hexel-sdk-generator. Do not edit manually."""
2
+ from __future__ import annotations
3
+ from hexel._internal.http import HttpClient
4
+
5
+
6
+ class AgentClient:
7
+ """Agent registry — register and manage agent images"""
8
+
9
+ def __init__(self, http: HttpClient):
10
+ self._http = http
11
+
12
+ def register(self, **kwargs) -> dict:
13
+ """Register a new agent in the catalog"""
14
+ resp = self._http.post("/registry/v1/agents", json=kwargs)
15
+ resp.raise_for_status()
16
+ return resp.json()
17
+
18
+ def list(self) -> dict:
19
+ """List agents with optional filters"""
20
+ resp = self._http.get("/registry/v1/agents")
21
+ resp.raise_for_status()
22
+ return resp.json()
23
+
24
+ def get(self, id: str) -> dict:
25
+ """Get agent by ID"""
26
+ resp = self._http.get("/registry/v1/agents/{id}".replace("{id}", id))
27
+ resp.raise_for_status()
28
+ return resp.json()
29
+
30
+ def update(self, id: str, **kwargs) -> dict:
31
+ """Update agent fields"""
32
+ resp = self._http.put("/registry/v1/agents/{id}".replace("{id}", id), json=kwargs)
33
+ resp.raise_for_status()
34
+ return resp.json()
35
+
36
+ def delete(self, id: str) -> None:
37
+ """Delete agent by ID"""
38
+ resp = self._http.delete("/registry/v1/agents/{id}".replace("{id}", id))
39
+ resp.raise_for_status()
40
+
41
+ def search(self) -> dict:
42
+ """Find agents matching given capabilities"""
43
+ resp = self._http.get("/registry/v1/agents/search")
44
+ resp.raise_for_status()
45
+ return resp.json()
@@ -0,0 +1,46 @@
1
+ """Generated by hexel-sdk-generator. Do not edit manually."""
2
+ from __future__ import annotations
3
+ from hexel._internal.http import HttpClient
4
+
5
+
6
+ class InstanceClient:
7
+ """Agent deployments — deploy agents and manage instances"""
8
+
9
+ def __init__(self, http: HttpClient):
10
+ self._http = http
11
+
12
+ def deploy(self, agent_id: str) -> dict:
13
+ """Creates a new deployment from a registered agent. Returns a deployment_id to use for all subsequent operations."""
14
+ resp = self._http.post("/compute/v1/agents/{agent_id}/deploy".replace("{agent_id}", agent_id))
15
+ resp.raise_for_status()
16
+ return resp.json()
17
+
18
+ def list(self) -> dict:
19
+ """List all deployments"""
20
+ resp = self._http.get("/compute/v1/deployments")
21
+ resp.raise_for_status()
22
+ data = resp.json()
23
+ return data if isinstance(data, list) else data.get("agents", data.get("items", []))
24
+
25
+ def get(self, deployment_id: str) -> dict:
26
+ """Get deployment by ID"""
27
+ resp = self._http.get("/compute/v1/deployments/{deployment_id}".replace("{deployment_id}", deployment_id))
28
+ resp.raise_for_status()
29
+ return resp.json()
30
+
31
+ def delete(self, deployment_id: str) -> None:
32
+ """Delete a deployment"""
33
+ resp = self._http.delete("/compute/v1/deployments/{deployment_id}".replace("{deployment_id}", deployment_id))
34
+ resp.raise_for_status()
35
+
36
+ def stop(self, deployment_id: str) -> dict:
37
+ """Stop a deployment"""
38
+ resp = self._http.post("/compute/v1/deployments/{deployment_id}/stop".replace("{deployment_id}", deployment_id))
39
+ resp.raise_for_status()
40
+ return resp.json()
41
+
42
+ def redeploy(self, deployment_id: str) -> dict:
43
+ """Redeploy (stop + start)"""
44
+ resp = self._http.post("/compute/v1/deployments/{deployment_id}/redeploy".replace("{deployment_id}", deployment_id))
45
+ resp.raise_for_status()
46
+ return resp.json()
@@ -0,0 +1,52 @@
1
+ """Generated by hexel-sdk-generator. Do not edit manually."""
2
+ from __future__ import annotations
3
+ from hexel._internal.http import HttpClient
4
+ from hexel._internal.ws import WSConnection
5
+
6
+
7
+ class SandboxClient:
8
+ """Isolated execution environments"""
9
+
10
+ def __init__(self, http: HttpClient):
11
+ self._http = http
12
+
13
+ def create(self, **kwargs) -> dict:
14
+ """Allocates a VM from the pool or creates a new one for the requesting org"""
15
+ resp = self._http.post("/compute/v1/vms/allocate", json=kwargs)
16
+ resp.raise_for_status()
17
+ return resp.json()
18
+
19
+ def list(self) -> dict:
20
+ """Returns VMs scoped to the caller's org"""
21
+ resp = self._http.get("/compute/v1/vms")
22
+ resp.raise_for_status()
23
+ data = resp.json()
24
+ return data if isinstance(data, list) else data.get("agents", data.get("items", []))
25
+
26
+ def get(self, id: str) -> dict:
27
+ """Get Agent VM by ID"""
28
+ resp = self._http.get("/compute/v1/vms/{id}".replace("{id}", id))
29
+ resp.raise_for_status()
30
+ return resp.json()
31
+
32
+ def delete(self, id: str) -> None:
33
+ """Delete Agent VM"""
34
+ resp = self._http.delete("/compute/v1/vms/{id}".replace("{id}", id))
35
+ resp.raise_for_status()
36
+
37
+ def release(self, id: str, **kwargs) -> dict:
38
+ """Release Agent VM"""
39
+ resp = self._http.post("/compute/v1/vms/{id}/release".replace("{id}", id), json=kwargs)
40
+ resp.raise_for_status()
41
+ return resp.json()
42
+
43
+ def renew(self, id: str, **kwargs) -> dict:
44
+ """Renew Agent VM TTL"""
45
+ resp = self._http.post("/compute/v1/vms/{id}/renew".replace("{id}", id), json=kwargs)
46
+ resp.raise_for_status()
47
+ return resp.json()
48
+
49
+ def execute(self, id: str) -> None:
50
+ """Runs code or shell commands in an allocated sandbox VM"""
51
+ resp = self._http.post("/compute/v1/vms/{id}/execute".replace("{id}", id))
52
+ resp.raise_for_status()
hexel/compute/types.py ADDED
@@ -0,0 +1,444 @@
1
+ """Generated by hexel-sdk-generator. Do not edit manually."""
2
+ from __future__ import annotations
3
+ from dataclasses import dataclass, field
4
+ from typing import Any
5
+
6
+ @dataclass
7
+ class OAuthClient:
8
+ """iamclient.OAuthClient"""
9
+ client_id: str | None = None
10
+ created_at: str | None = None
11
+ is_active: bool | None = None
12
+ name: str | None = None
13
+ @classmethod
14
+ def from_dict(cls, d: dict) -> "OAuthClient":
15
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
16
+
17
+ @dataclass
18
+ class OAuthClientResponse:
19
+ """iamclient.OAuthClientResponse"""
20
+ client_id: str | None = None
21
+ client_secret: str | None = None
22
+ name: str | None = None
23
+ @classmethod
24
+ def from_dict(cls, d: dict) -> "OAuthClientResponse":
25
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
26
+
27
+ @dataclass
28
+ class InstanceInfo:
29
+ """models.Agent"""
30
+ allowed_org_ids: list[str] | None = None
31
+ author: str | None = None
32
+ capabilities: list[str] | None = None
33
+ created_at: str | None = None
34
+ description: str | None = None
35
+ endpoint: str | None = None
36
+ id: str | None = None
37
+ image: str | None = None
38
+ image_pull_secret: models.ImagePullSecret | None = None
39
+ manifest: models.AgentManifest | None = None
40
+ metadata: dict[str, string] | None = None
41
+ name: str | None = None
42
+ org_id: str | None = None
43
+ public_key: str | None = None
44
+ state: str | None = None
45
+ updated_at: str | None = None
46
+ version: str | None = None
47
+ visibility: str | None = None
48
+ @classmethod
49
+ def from_dict(cls, d: dict) -> "InstanceInfo":
50
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
51
+
52
+ @dataclass
53
+ class SandboxInfo:
54
+ """models.AgentVM"""
55
+ allocated_at: str | None = None
56
+ created_at: str | None = None
57
+ expires_at: str | None = None
58
+ id: str | None = None
59
+ metadata: dict[str, string] | None = None
60
+ released_at: str | None = None
61
+ skills: list[str] | None = None
62
+ state: str | None = None
63
+ tier: str | None = None
64
+ token: str | None = None
65
+ ttl_seconds: int | None = None
66
+ @classmethod
67
+ def from_dict(cls, d: dict) -> "SandboxInfo":
68
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
69
+
70
+ @dataclass
71
+ class CreateSandboxRequest:
72
+ """models.AllocateRequest"""
73
+ env: dict[str, string] | None = None
74
+ metadata: dict[str, string] | None = None
75
+ org_id: str | None = None
76
+ secure_runtime: str | None = None
77
+ skills: list[str] | None = None
78
+ tier: str
79
+ ttl_seconds: int | None = None
80
+ @classmethod
81
+ def from_dict(cls, d: dict) -> "CreateSandboxRequest":
82
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
83
+
84
+ @dataclass
85
+ class SandboxResponse:
86
+ """models.AllocateResponse"""
87
+ endpoint: str | None = None
88
+ expires_at: str | None = None
89
+ skills_loaded: list[str] | None = None
90
+ state: str | None = None
91
+ tier: str | None = None
92
+ token: str | None = None
93
+ vm_id: str | None = None
94
+ ws_url: str | None = None
95
+ @classmethod
96
+ def from_dict(cls, d: dict) -> "SandboxResponse":
97
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
98
+
99
+ @dataclass
100
+ class CreateSkillRequest:
101
+ """models.CreateSkillRequest"""
102
+ content: str
103
+ description: str | None = None
104
+ name: str
105
+ resources: dict[str, string] | None = None
106
+ @classmethod
107
+ def from_dict(cls, d: dict) -> "CreateSkillRequest":
108
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
109
+
110
+ @dataclass
111
+ class ErrorResponse:
112
+ """models.ErrorResponse"""
113
+ code: int | None = None
114
+ message: str | None = None
115
+ @classmethod
116
+ def from_dict(cls, d: dict) -> "ErrorResponse":
117
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
118
+
119
+ @dataclass
120
+ class PoolConfig:
121
+ """models.PoolConfig"""
122
+ default_skills: list[str] | None = None
123
+ default_tier: str | None = None
124
+ idle_ttl_seconds: int | None = None
125
+ max_total: int | None = None
126
+ max_vm_ttl_seconds: int | None = None
127
+ max_warm: int | None = None
128
+ min_warm: int | None = None
129
+ recycle: bool | None = None
130
+ scale_up_threshold: float | None = None
131
+ @classmethod
132
+ def from_dict(cls, d: dict) -> "PoolConfig":
133
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
134
+
135
+ @dataclass
136
+ class PoolStatus:
137
+ """models.PoolStatus"""
138
+ allocated: int | None = None
139
+ by_tier: dict[str, integer] | None = None
140
+ total: int | None = None
141
+ warm: int | None = None
142
+ @classmethod
143
+ def from_dict(cls, d: dict) -> "PoolStatus":
144
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
145
+
146
+ @dataclass
147
+ class ReleaseRequest:
148
+ """models.ReleaseRequest"""
149
+ recycle: bool | None = None
150
+ @classmethod
151
+ def from_dict(cls, d: dict) -> "ReleaseRequest":
152
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
153
+
154
+ @dataclass
155
+ class RenewRequest:
156
+ """models.RenewRequest"""
157
+ ttl_seconds: int
158
+ @classmethod
159
+ def from_dict(cls, d: dict) -> "RenewRequest":
160
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
161
+
162
+ @dataclass
163
+ class Skill:
164
+ """models.Skill"""
165
+ content: str | None = None
166
+ content_hash: str | None = None
167
+ created_at: str | None = None
168
+ description: str | None = None
169
+ name: str | None = None
170
+ resources: dict[str, string] | None = None
171
+ updated_at: str | None = None
172
+ @classmethod
173
+ def from_dict(cls, d: dict) -> "Skill":
174
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
175
+
176
+ @dataclass
177
+ class UsageRecord:
178
+ """models.UsageRecord"""
179
+ duration_seconds: int | None = None
180
+ ended_at: str | None = None
181
+ reason: str | None = None
182
+ started_at: str | None = None
183
+ tier: str | None = None
184
+ vm_id: str | None = None
185
+ @classmethod
186
+ def from_dict(cls, d: dict) -> "UsageRecord":
187
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
188
+
189
+ @dataclass
190
+ class UsageSummary:
191
+ """models.UsageSummary"""
192
+ by_tier: dict[str, integer] | None = None
193
+ period_end: str | None = None
194
+ period_start: str | None = None
195
+ total_seconds: int | None = None
196
+ vm_count: int | None = None
197
+ @classmethod
198
+ def from_dict(cls, d: dict) -> "UsageSummary":
199
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
200
+
201
+ @dataclass
202
+ class AgentManifest:
203
+ """models.AgentManifest"""
204
+ authentication: dict | None = None
205
+ capabilities: dict | None = None
206
+ description: str | None = None
207
+ input_schema: dict | None = None
208
+ name: str | None = None
209
+ output_schema: dict | None = None
210
+ runtime: dict | None = None
211
+ skills: list[models.ManifestSkill] | None = None
212
+ url: str | None = None
213
+ version: str | None = None
214
+ @classmethod
215
+ def from_dict(cls, d: dict) -> "AgentManifest":
216
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
217
+
218
+ @dataclass
219
+ class AgentResponse:
220
+ """models.AgentResponse"""
221
+ agent: models.Agent | None = None
222
+ @classmethod
223
+ def from_dict(cls, d: dict) -> "AgentResponse":
224
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
225
+
226
+ @dataclass
227
+ class AgentsListResponse:
228
+ """models.AgentsListResponse"""
229
+ agents: list[models.Agent] | None = None
230
+ total: int | None = None
231
+ @classmethod
232
+ def from_dict(cls, d: dict) -> "AgentsListResponse":
233
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
234
+
235
+ @dataclass
236
+ class CreateAgentRequest:
237
+ """models.CreateAgentRequest"""
238
+ author: str | None = None
239
+ capabilities: list[str] | None = None
240
+ description: str | None = None
241
+ endpoint: str | None = None
242
+ image: str
243
+ image_pull_secret: models.ImagePullSecret | None = None
244
+ manifest: models.AgentManifest | None = None
245
+ metadata: dict[str, string] | None = None
246
+ name: str
247
+ org_id: str
248
+ version: str | None = None
249
+ @classmethod
250
+ def from_dict(cls, d: dict) -> "CreateAgentRequest":
251
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
252
+
253
+ @dataclass
254
+ class CreateToolRequest:
255
+ """models.CreateToolRequest"""
256
+ category: str | None = None
257
+ description: str | None = None
258
+ id: str
259
+ name: str
260
+ @classmethod
261
+ def from_dict(cls, d: dict) -> "CreateToolRequest":
262
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
263
+
264
+ @dataclass
265
+ class ImagePullSecret:
266
+ """models.ImagePullSecret"""
267
+ password: str | None = None
268
+ registry: str | None = None
269
+ username: str | None = None
270
+ @classmethod
271
+ def from_dict(cls, d: dict) -> "ImagePullSecret":
272
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
273
+
274
+ @dataclass
275
+ class ManifestSkill:
276
+ """models.ManifestSkill"""
277
+ id: str | None = None
278
+ inputModes: list[str] | None = None
279
+ outputModes: list[str] | None = None
280
+ @classmethod
281
+ def from_dict(cls, d: dict) -> "ManifestSkill":
282
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
283
+
284
+ @dataclass
285
+ class Tool:
286
+ """models.Tool"""
287
+ category: str | None = None
288
+ created_at: str | None = None
289
+ description: str | None = None
290
+ id: str | None = None
291
+ name: str | None = None
292
+ @classmethod
293
+ def from_dict(cls, d: dict) -> "Tool":
294
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
295
+
296
+ @dataclass
297
+ class ToolResponse:
298
+ """models.ToolResponse"""
299
+ tool: models.Tool | None = None
300
+ @classmethod
301
+ def from_dict(cls, d: dict) -> "ToolResponse":
302
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
303
+
304
+ @dataclass
305
+ class ToolsListResponse:
306
+ """models.ToolsListResponse"""
307
+ tools: list[models.Tool] | None = None
308
+ total: int | None = None
309
+ @classmethod
310
+ def from_dict(cls, d: dict) -> "ToolsListResponse":
311
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
312
+
313
+ @dataclass
314
+ class UpdateAgentRequest:
315
+ """models.UpdateAgentRequest"""
316
+ capabilities: list[str] | None = None
317
+ description: str | None = None
318
+ endpoint: str | None = None
319
+ image: str | None = None
320
+ manifest: models.AgentManifest | None = None
321
+ metadata: dict[str, string] | None = None
322
+ name: str | None = None
323
+ public_key: str | None = None
324
+ state: str | None = None
325
+ version: str | None = None
326
+ @classmethod
327
+ def from_dict(cls, d: dict) -> "UpdateAgentRequest":
328
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
329
+
330
+ @dataclass
331
+ class CodeContext:
332
+ """models.CodeContext"""
333
+ created_at: str | None = None
334
+ id: str | None = None
335
+ kernel_id: str | None = None
336
+ language: str | None = None
337
+ session_id: str | None = None
338
+ @classmethod
339
+ def from_dict(cls, d: dict) -> "CodeContext":
340
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
341
+
342
+ @dataclass
343
+ class CommandStatus:
344
+ """models.CommandStatus"""
345
+ exit_code: int | None = None
346
+ finished_at: str | None = None
347
+ id: str | None = None
348
+ running: bool | None = None
349
+ started_at: str | None = None
350
+ @classmethod
351
+ def from_dict(cls, d: dict) -> "CommandStatus":
352
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
353
+
354
+ @dataclass
355
+ class CreateContextRequest:
356
+ """models.CreateContextRequest"""
357
+ language: str
358
+ session_id: str
359
+ @classmethod
360
+ def from_dict(cls, d: dict) -> "CreateContextRequest":
361
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
362
+
363
+ @dataclass
364
+ class ExecuteCodeRequest:
365
+ """models.ExecuteCodeRequest"""
366
+ code: str
367
+ context_id: str
368
+ @classmethod
369
+ def from_dict(cls, d: dict) -> "ExecuteCodeRequest":
370
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
371
+
372
+ @dataclass
373
+ class ExecuteRequest:
374
+ """models.ExecuteRequest"""
375
+ @classmethod
376
+ def from_dict(cls, d: dict) -> "ExecuteRequest":
377
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
378
+
379
+ @dataclass
380
+ class FileInfo:
381
+ """models.FileInfo"""
382
+ group: str | None = None
383
+ is_dir: bool | None = None
384
+ mod_time: str | None = None
385
+ mode: str | None = None
386
+ owner: str | None = None
387
+ path: str | None = None
388
+ size: int | None = None
389
+ @classmethod
390
+ def from_dict(cls, d: dict) -> "FileInfo":
391
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
392
+
393
+ @dataclass
394
+ class LoadSkillsRequest:
395
+ """models.LoadSkillsRequest"""
396
+ skills: list[str]
397
+ @classmethod
398
+ def from_dict(cls, d: dict) -> "LoadSkillsRequest":
399
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
400
+
401
+ @dataclass
402
+ class MetricsSnapshot:
403
+ """models.MetricsSnapshot"""
404
+ cpu_percent: float | None = None
405
+ mem_total_bytes: int | None = None
406
+ mem_used_bytes: int | None = None
407
+ tokens_total: int | None = None
408
+ @classmethod
409
+ def from_dict(cls, d: dict) -> "MetricsSnapshot":
410
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
411
+
412
+ @dataclass
413
+ class RunCommandRequest:
414
+ """models.RunCommandRequest"""
415
+ background: bool | None = None
416
+ command: str
417
+ cwd: str | None = None
418
+ envs: dict[str, string] | None = None
419
+ gid: int | None = None
420
+ timeout_ms: int | None = None
421
+ uid: int | None = None
422
+ @classmethod
423
+ def from_dict(cls, d: dict) -> "RunCommandRequest":
424
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
425
+
426
+ @dataclass
427
+ class SSEEvent:
428
+ """models.SSEEvent"""
429
+ data: str | None = None
430
+ event: str | None = None
431
+ @classmethod
432
+ def from_dict(cls, d: dict) -> "SSEEvent":
433
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
434
+
435
+ @dataclass
436
+ class SkillInfo:
437
+ """models.SkillInfo"""
438
+ loaded: bool | None = None
439
+ name: str | None = None
440
+ path: str | None = None
441
+ @classmethod
442
+ def from_dict(cls, d: dict) -> "SkillInfo":
443
+ return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
444
+
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.4
2
+ Name: hexel
3
+ Version: 0.1.0
4
+ Summary: Hexel Studio SDK — deploy and run AI agents
5
+ Project-URL: Homepage, https://hexelstudio.com
6
+ Project-URL: Documentation, https://docs.hexelstudio.com
7
+ Project-URL: Repository, https://github.com/hexelstudio/python-sdk
8
+ Project-URL: Changelog, https://docs.hexelstudio.com/changelog
9
+ Author-email: Hexel Studio <support@hexelstudio.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,ai,code-execution,compute,hexel,sandbox
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: httpx>=0.27
25
+ Requires-Dist: websockets>=12.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: jinja2; extra == 'dev'
28
+ Requires-Dist: pytest; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Hexel Python SDK
32
+
33
+ The official Python SDK for [Hexel Studio](https://hexelstudio.com) — build, deploy, and run AI agents.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install hexel
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ```python
44
+ from hexel import Hexel
45
+
46
+ client = Hexel(api_key="your_api_key")
47
+
48
+ # Create a sandbox and execute code
49
+ sandbox = client.compute.sandbox.create(tier="standard")
50
+ print(sandbox) # {"vm_id": "...", "state": "allocated", ...}
51
+
52
+ # Register an agent
53
+ agent = client.compute.agent.register(
54
+ name="my-agent",
55
+ image="ghcr.io/org/agent:v1",
56
+ capabilities=["chat", "streaming"],
57
+ )
58
+
59
+ # Deploy an agent
60
+ instance = client.compute.instance.deploy(agent["id"], env={"MODEL": "gpt-4"})
61
+ print(instance["endpoint"]) # https://zeal-isle-a620.compute.hexelstudio.com
62
+ ```
63
+
64
+ ## Authentication
65
+
66
+ Two authentication methods are supported:
67
+
68
+ ```python
69
+ # API Key (recommended for development)
70
+ client = Hexel(api_key="studio_live_xxxx")
71
+
72
+ # OAuth Client Credentials (recommended for production)
73
+ client = Hexel(client_id="your_client_id", client_secret="your_client_secret")
74
+ ```
75
+
76
+ Both methods exchange credentials for a short-lived STS token automatically. Tokens are cached and refreshed transparently.
77
+
78
+ ## Sandboxes
79
+
80
+ Sandboxes are isolated execution environments allocated instantly.
81
+
82
+ ```python
83
+ # Create
84
+ sandbox = client.compute.sandbox.create(tier="standard")
85
+
86
+ # Execute code (HTTP)
87
+ result = client.compute.sandbox.execute(sandbox["vm_id"], code="print(1+1)", language="python")
88
+
89
+ # List
90
+ sandboxes = client.compute.sandbox.list()
91
+
92
+ # Renew TTL
93
+ client.compute.sandbox.renew(sandbox["vm_id"], ttl_seconds=3600)
94
+
95
+ # Release
96
+ client.compute.sandbox.release(sandbox["vm_id"], recycle=True)
97
+
98
+ # Delete
99
+ client.compute.sandbox.delete(sandbox["vm_id"])
100
+ ```
101
+
102
+ ## Agents
103
+
104
+ Agents are Docker images registered in the Hexel registry.
105
+
106
+ ```python
107
+ # Register
108
+ agent = client.compute.agent.register(
109
+ name="fraud-detector",
110
+ image="ghcr.io/org/fraud-agent:v1",
111
+ capabilities=["fraud-detection"],
112
+ )
113
+
114
+ # List
115
+ agents = client.compute.agent.list()
116
+
117
+ # Get
118
+ agent = client.compute.agent.get("agent-id")
119
+
120
+ # Search
121
+ results = client.compute.agent.search(q="fraud")
122
+
123
+ # Delete
124
+ client.compute.agent.delete("agent-id")
125
+ ```
126
+
127
+ ## Instances
128
+
129
+ Instances are running deployments of agents with permanent endpoints.
130
+
131
+ ```python
132
+ # Deploy
133
+ instance = client.compute.instance.deploy("agent-id", env={"MODEL": "gpt-4"})
134
+
135
+ # List
136
+ instances = client.compute.instance.list()
137
+
138
+ # Get
139
+ instance = client.compute.instance.get("deployment-id")
140
+
141
+ # Stop
142
+ client.compute.instance.stop("deployment-id")
143
+
144
+ # Redeploy
145
+ client.compute.instance.redeploy("deployment-id")
146
+
147
+ # Delete
148
+ client.compute.instance.delete("deployment-id")
149
+ ```
150
+
151
+ ## Configuration
152
+
153
+ ```python
154
+ client = Hexel(
155
+ api_key="...",
156
+ )
157
+ ```
158
+
159
+ ## Requirements
160
+
161
+ - Python 3.10+
162
+ - `httpx` for HTTP
163
+ - `websockets` for WebSocket connections
164
+
165
+ ## License
166
+
167
+ MIT
@@ -0,0 +1,15 @@
1
+ hexel/__init__.py,sha256=pqLIT-XJ5MV5_lwu0uRjq4k0rN46hIThTAj_VT_kJ3o,75
2
+ hexel/_client.py,sha256=0v7U4uKDvbqj0Xuj0NcIK-gSckjNBQixLxNN_rbNqMg,1048
3
+ hexel/_internal/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
4
+ hexel/_internal/auth.py,sha256=OEIXIcLOYY5TpbNYN5d0MDOti0yi0Zx0iOYaR3w5bIw,1690
5
+ hexel/_internal/http.py,sha256=asf6z0mNSmIO8FN4ogEpYE-ZHvSP7LztdWpDAq9PTkw,1755
6
+ hexel/_internal/ws.py,sha256=e7k21FbcJ-8ntSwLfCC8trJBYk-Fp6AbKPOpts_pOak,938
7
+ hexel/compute/__init__.py,sha256=oJO8SyRlraF6VOh1Oiqov3CZmG_8lzE2oNZ8f-XrOWk,528
8
+ hexel/compute/_agent.py,sha256=_67qe_I4pxhXiZ7dqRNNXrKILM-nUGGhVXzv2eNWGVQ,1529
9
+ hexel/compute/_instance.py,sha256=PgI2oPeQ6LGLQ_DQ4nOIScRLM87G3VR8Li1I9XS17Fw,1936
10
+ hexel/compute/_sandbox.py,sha256=4I1BJ-gfX80Y-tiMlR_Q4i5ENymCfP5UIRh7OF1cNjQ,1940
11
+ hexel/compute/types.py,sha256=g8eAOzDTbfHdADv8gCIc4PxzrblNZaxkxqsNvuprNw0,14172
12
+ hexel-0.1.0.dist-info/METADATA,sha256=gDydkxiz9mWx7sU3ztWItH90ltTvMQqfnVdDhTZ-MZY,3996
13
+ hexel-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
14
+ hexel-0.1.0.dist-info/licenses/LICENSE,sha256=ySBBNUwEDBWR30cEUtGKYC6OaT0HCSY1Unk4YnNJfB8,1069
15
+ hexel-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hexel Studio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.