21st-sdk 0.0.1__tar.gz

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.
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: 21st-sdk
3
+ Version: 0.0.1
4
+ Summary: Server-side Python SDK for 21st Agents
5
+ Author: 21st.dev
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://21st.dev/agents
8
+ Keywords: ai,agent,sandbox,21st,21st.dev,sdk,python
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: requests>=2.31.0
12
+
13
+ # 21st-sdk Python
14
+
15
+ Server-side Python SDK for [21st Agents](https://21st.dev/agents). Manage sandboxes, threads, and tokens programmatically.
16
+
17
+ Method arguments use Python-style `snake_case`, but SDK responses keep the relay's `camelCase` field names.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install 21st-sdk
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```python
28
+ import os
29
+
30
+ from twentyfirst_sdk import AgentClient
31
+
32
+ client = AgentClient(api_key=os.environ["API_KEY_21ST"]) # an_sk_...
33
+
34
+ # Create a sandbox for your agent
35
+ sandbox = client.sandboxes.create(agent="my-agent")
36
+
37
+ # Create a thread
38
+ thread = client.threads.create(sandbox_id=sandbox.id, name="Review PR #42")
39
+
40
+ # Generate a short-lived token for browser clients
41
+ token = client.tokens.create(agent="my-agent", expires_in="1h")
42
+ print(token.expiresAt)
43
+ ```
44
+
45
+ ## API
46
+
47
+ ### `AgentClient(api_key, base_url=...)`
48
+
49
+ ```python
50
+ AgentClient(
51
+ api_key="...", # Your an_sk_ API key
52
+ base_url="...", # Optional, default: "https://relay.an.dev"
53
+ )
54
+ ```
55
+
56
+ ### `client.sandboxes`
57
+
58
+ | Method | Description |
59
+ |--------|-------------|
60
+ | `create(agent=...)` | Create a new sandbox for an agent |
61
+ | `get(sandbox_id)` | Get sandbox details (status, threads, agent info) |
62
+ | `delete(sandbox_id)` | Delete a sandbox |
63
+ | `exec(sandbox_id, command, ...)` | Run a command in a sandbox |
64
+ | `files.write(sandbox_id, files)` | Write files into a sandbox |
65
+ | `files.read(sandbox_id, path)` | Read a file from a sandbox |
66
+ | `git.clone(sandbox_id, url, ...)` | Clone a repository into a sandbox |
67
+
68
+ ### `client.threads`
69
+
70
+ | Method | Description |
71
+ |--------|-------------|
72
+ | `list(sandbox_id)` | List all threads in a sandbox |
73
+ | `create(sandbox_id, name=...)` | Create a new thread |
74
+ | `get(sandbox_id, thread_id)` | Get thread with messages |
75
+ | `delete(sandbox_id, thread_id)` | Delete a thread |
76
+ | `run(agent, messages, ...)` | Run a thread and return the raw streaming response |
77
+
78
+ ### `client.tokens`
79
+
80
+ | Method | Description |
81
+ |--------|-------------|
82
+ | `create(agent=..., user_id=..., expires_in=...)` | Create a short-lived JWT (default: `"1h"`) |
83
+
84
+ ## License
85
+
86
+ MIT
@@ -0,0 +1,74 @@
1
+ # 21st-sdk Python
2
+
3
+ Server-side Python SDK for [21st Agents](https://21st.dev/agents). Manage sandboxes, threads, and tokens programmatically.
4
+
5
+ Method arguments use Python-style `snake_case`, but SDK responses keep the relay's `camelCase` field names.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install 21st-sdk
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ import os
17
+
18
+ from twentyfirst_sdk import AgentClient
19
+
20
+ client = AgentClient(api_key=os.environ["API_KEY_21ST"]) # an_sk_...
21
+
22
+ # Create a sandbox for your agent
23
+ sandbox = client.sandboxes.create(agent="my-agent")
24
+
25
+ # Create a thread
26
+ thread = client.threads.create(sandbox_id=sandbox.id, name="Review PR #42")
27
+
28
+ # Generate a short-lived token for browser clients
29
+ token = client.tokens.create(agent="my-agent", expires_in="1h")
30
+ print(token.expiresAt)
31
+ ```
32
+
33
+ ## API
34
+
35
+ ### `AgentClient(api_key, base_url=...)`
36
+
37
+ ```python
38
+ AgentClient(
39
+ api_key="...", # Your an_sk_ API key
40
+ base_url="...", # Optional, default: "https://relay.an.dev"
41
+ )
42
+ ```
43
+
44
+ ### `client.sandboxes`
45
+
46
+ | Method | Description |
47
+ |--------|-------------|
48
+ | `create(agent=...)` | Create a new sandbox for an agent |
49
+ | `get(sandbox_id)` | Get sandbox details (status, threads, agent info) |
50
+ | `delete(sandbox_id)` | Delete a sandbox |
51
+ | `exec(sandbox_id, command, ...)` | Run a command in a sandbox |
52
+ | `files.write(sandbox_id, files)` | Write files into a sandbox |
53
+ | `files.read(sandbox_id, path)` | Read a file from a sandbox |
54
+ | `git.clone(sandbox_id, url, ...)` | Clone a repository into a sandbox |
55
+
56
+ ### `client.threads`
57
+
58
+ | Method | Description |
59
+ |--------|-------------|
60
+ | `list(sandbox_id)` | List all threads in a sandbox |
61
+ | `create(sandbox_id, name=...)` | Create a new thread |
62
+ | `get(sandbox_id, thread_id)` | Get thread with messages |
63
+ | `delete(sandbox_id, thread_id)` | Delete a thread |
64
+ | `run(agent, messages, ...)` | Run a thread and return the raw streaming response |
65
+
66
+ ### `client.tokens`
67
+
68
+ | Method | Description |
69
+ |--------|-------------|
70
+ | `create(agent=..., user_id=..., expires_in=...)` | Create a short-lived JWT (default: `"1h"`) |
71
+
72
+ ## License
73
+
74
+ MIT
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "21st-sdk"
7
+ version = "0.0.1"
8
+ description = "Server-side Python SDK for 21st Agents"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "21st.dev" },
14
+ ]
15
+ dependencies = [
16
+ "requests>=2.31.0",
17
+ ]
18
+ keywords = [
19
+ "ai",
20
+ "agent",
21
+ "sandbox",
22
+ "21st",
23
+ "21st.dev",
24
+ "sdk",
25
+ "python",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://21st.dev/agents"
30
+
31
+ [tool.setuptools.package-dir]
32
+ "" = "src"
33
+
34
+ [tool.setuptools.packages.find]
35
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: 21st-sdk
3
+ Version: 0.0.1
4
+ Summary: Server-side Python SDK for 21st Agents
5
+ Author: 21st.dev
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://21st.dev/agents
8
+ Keywords: ai,agent,sandbox,21st,21st.dev,sdk,python
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: requests>=2.31.0
12
+
13
+ # 21st-sdk Python
14
+
15
+ Server-side Python SDK for [21st Agents](https://21st.dev/agents). Manage sandboxes, threads, and tokens programmatically.
16
+
17
+ Method arguments use Python-style `snake_case`, but SDK responses keep the relay's `camelCase` field names.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pip install 21st-sdk
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```python
28
+ import os
29
+
30
+ from twentyfirst_sdk import AgentClient
31
+
32
+ client = AgentClient(api_key=os.environ["API_KEY_21ST"]) # an_sk_...
33
+
34
+ # Create a sandbox for your agent
35
+ sandbox = client.sandboxes.create(agent="my-agent")
36
+
37
+ # Create a thread
38
+ thread = client.threads.create(sandbox_id=sandbox.id, name="Review PR #42")
39
+
40
+ # Generate a short-lived token for browser clients
41
+ token = client.tokens.create(agent="my-agent", expires_in="1h")
42
+ print(token.expiresAt)
43
+ ```
44
+
45
+ ## API
46
+
47
+ ### `AgentClient(api_key, base_url=...)`
48
+
49
+ ```python
50
+ AgentClient(
51
+ api_key="...", # Your an_sk_ API key
52
+ base_url="...", # Optional, default: "https://relay.an.dev"
53
+ )
54
+ ```
55
+
56
+ ### `client.sandboxes`
57
+
58
+ | Method | Description |
59
+ |--------|-------------|
60
+ | `create(agent=...)` | Create a new sandbox for an agent |
61
+ | `get(sandbox_id)` | Get sandbox details (status, threads, agent info) |
62
+ | `delete(sandbox_id)` | Delete a sandbox |
63
+ | `exec(sandbox_id, command, ...)` | Run a command in a sandbox |
64
+ | `files.write(sandbox_id, files)` | Write files into a sandbox |
65
+ | `files.read(sandbox_id, path)` | Read a file from a sandbox |
66
+ | `git.clone(sandbox_id, url, ...)` | Clone a repository into a sandbox |
67
+
68
+ ### `client.threads`
69
+
70
+ | Method | Description |
71
+ |--------|-------------|
72
+ | `list(sandbox_id)` | List all threads in a sandbox |
73
+ | `create(sandbox_id, name=...)` | Create a new thread |
74
+ | `get(sandbox_id, thread_id)` | Get thread with messages |
75
+ | `delete(sandbox_id, thread_id)` | Delete a thread |
76
+ | `run(agent, messages, ...)` | Run a thread and return the raw streaming response |
77
+
78
+ ### `client.tokens`
79
+
80
+ | Method | Description |
81
+ |--------|-------------|
82
+ | `create(agent=..., user_id=..., expires_in=...)` | Create a short-lived JWT (default: `"1h"`) |
83
+
84
+ ## License
85
+
86
+ MIT
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/21st_sdk.egg-info/PKG-INFO
4
+ src/21st_sdk.egg-info/SOURCES.txt
5
+ src/21st_sdk.egg-info/dependency_links.txt
6
+ src/21st_sdk.egg-info/requires.txt
7
+ src/21st_sdk.egg-info/top_level.txt
8
+ src/twentyfirst_sdk/__init__.py
9
+ src/twentyfirst_sdk/client.py
10
+ src/twentyfirst_sdk/types.py
@@ -0,0 +1 @@
1
+ requests>=2.31.0
@@ -0,0 +1 @@
1
+ twentyfirst_sdk
@@ -0,0 +1,35 @@
1
+ from .client import DEFAULT_BASE_URL, AgentClient, AgentClientError
2
+ from .types import (
3
+ APIObject,
4
+ ApiError,
5
+ ExecResult,
6
+ FileContent,
7
+ GitCloneResult,
8
+ RunThreadMessage,
9
+ RunThreadMessagePart,
10
+ RunThreadResult,
11
+ Sandbox,
12
+ SandboxDetail,
13
+ Thread,
14
+ ThreadSummary,
15
+ Token,
16
+ )
17
+
18
+ __all__ = [
19
+ "APIObject",
20
+ "DEFAULT_BASE_URL",
21
+ "AgentClient",
22
+ "AgentClientError",
23
+ "ApiError",
24
+ "ExecResult",
25
+ "FileContent",
26
+ "GitCloneResult",
27
+ "RunThreadMessage",
28
+ "RunThreadMessagePart",
29
+ "RunThreadResult",
30
+ "Sandbox",
31
+ "SandboxDetail",
32
+ "Thread",
33
+ "ThreadSummary",
34
+ "Token",
35
+ ]
@@ -0,0 +1,292 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Mapping, Optional, Sequence
4
+ from urllib.parse import quote
5
+
6
+ import requests
7
+
8
+ from .types import APIObject, RunThreadMessage
9
+
10
+
11
+ DEFAULT_BASE_URL = "https://relay.an.dev"
12
+
13
+
14
+ def _to_api_object(value: Any) -> Any:
15
+ if isinstance(value, dict):
16
+ return APIObject({key: _to_api_object(item) for key, item in value.items()})
17
+ if isinstance(value, list):
18
+ return [_to_api_object(item) for item in value]
19
+ return value
20
+
21
+
22
+ class AgentClientError(Exception):
23
+ pass
24
+
25
+
26
+ class AgentClient:
27
+ def __init__(self, api_key: str, base_url: str = DEFAULT_BASE_URL):
28
+ self.api_key = api_key
29
+ self.base_url = base_url.rstrip("/")
30
+ self._session = requests.Session()
31
+
32
+ self.sandboxes = SandboxesResource(self)
33
+ self.threads = ThreadsResource(self)
34
+ self.tokens = TokensResource(self)
35
+
36
+ def close(self) -> None:
37
+ self._session.close()
38
+
39
+ def __enter__(self) -> "AgentClient":
40
+ return self
41
+
42
+ def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
43
+ self.close()
44
+
45
+ def _request(
46
+ self,
47
+ path: str,
48
+ *,
49
+ method: str = "GET",
50
+ body: Optional[Dict[str, Any]] = None,
51
+ headers: Optional[Mapping[str, str]] = None,
52
+ stream: bool = False,
53
+ ) -> requests.Response:
54
+ request_headers = {
55
+ "Content-Type": "application/json",
56
+ "Authorization": "Bearer %s" % self.api_key,
57
+ }
58
+ if headers:
59
+ request_headers.update(headers)
60
+
61
+ response = self._session.request(
62
+ method,
63
+ "%s%s" % (self.base_url, path),
64
+ json=body,
65
+ headers=request_headers,
66
+ stream=stream,
67
+ )
68
+
69
+ if not response.ok:
70
+ try:
71
+ payload = response.json()
72
+ except ValueError:
73
+ payload = {}
74
+
75
+ error = payload.get("error") if isinstance(payload, dict) else None
76
+ message = None
77
+ if isinstance(error, dict):
78
+ message = error.get("message")
79
+
80
+ raise AgentClientError(message or "Request failed: %s" % response.status_code)
81
+
82
+ return response
83
+
84
+ def _fetch(
85
+ self,
86
+ path: str,
87
+ *,
88
+ method: str = "GET",
89
+ body: Optional[Dict[str, Any]] = None,
90
+ headers: Optional[Mapping[str, str]] = None,
91
+ ) -> Any:
92
+ response = self._request(path, method=method, body=body, headers=headers)
93
+
94
+ if response.status_code == 204:
95
+ return None
96
+
97
+ return _to_api_object(response.json())
98
+
99
+ def _get_base_url(self) -> str:
100
+ return self.base_url
101
+
102
+
103
+ class FilesResource:
104
+ def __init__(self, client: AgentClient):
105
+ self.client = client
106
+
107
+ def write(self, sandbox_id: str, files: Mapping[str, str]) -> Any:
108
+ return self.client._fetch(
109
+ "/v1/sandboxes/%s/files" % sandbox_id,
110
+ method="POST",
111
+ body={"files": dict(files)},
112
+ )
113
+
114
+ def read(self, sandbox_id: str, path: str) -> Any:
115
+ encoded_path = quote(path, safe="")
116
+ return self.client._fetch(
117
+ "/v1/sandboxes/%s/files?path=%s" % (sandbox_id, encoded_path),
118
+ )
119
+
120
+
121
+ class GitResource:
122
+ def __init__(self, client: AgentClient):
123
+ self.client = client
124
+
125
+ def clone(
126
+ self,
127
+ sandbox_id: str,
128
+ url: str,
129
+ *,
130
+ path: Optional[str] = None,
131
+ token: Optional[str] = None,
132
+ depth: Optional[int] = None,
133
+ ) -> Any:
134
+ body: Dict[str, Any] = {"url": url}
135
+ if path:
136
+ body["path"] = path
137
+ if token:
138
+ body["token"] = token
139
+ if depth:
140
+ body["depth"] = depth
141
+
142
+ return self.client._fetch(
143
+ "/v1/sandboxes/%s/git/clone" % sandbox_id,
144
+ method="POST",
145
+ body=body,
146
+ )
147
+
148
+
149
+ class SandboxesResource:
150
+ def __init__(self, client: AgentClient):
151
+ self.client = client
152
+ self.files = FilesResource(client)
153
+ self.git = GitResource(client)
154
+
155
+ def create(
156
+ self,
157
+ agent: str,
158
+ *,
159
+ files: Optional[Mapping[str, str]] = None,
160
+ envs: Optional[Mapping[str, str]] = None,
161
+ setup: Optional[Sequence[str]] = None,
162
+ ) -> Any:
163
+ body: Dict[str, Any] = {"agent": agent}
164
+ if files:
165
+ body["files"] = dict(files)
166
+ if envs:
167
+ body["envs"] = dict(envs)
168
+ if setup:
169
+ body["setup"] = list(setup)
170
+
171
+ return self.client._fetch("/v1/sandboxes", method="POST", body=body)
172
+
173
+ def get(self, sandbox_id: str) -> Any:
174
+ return self.client._fetch("/v1/sandboxes/%s" % sandbox_id)
175
+
176
+ def delete(self, sandbox_id: str) -> Any:
177
+ return self.client._fetch("/v1/sandboxes/%s" % sandbox_id, method="DELETE")
178
+
179
+ def exec(
180
+ self,
181
+ sandbox_id: str,
182
+ command: str,
183
+ *,
184
+ cwd: Optional[str] = None,
185
+ envs: Optional[Mapping[str, str]] = None,
186
+ timeout_ms: Optional[int] = None,
187
+ ) -> Any:
188
+ body: Dict[str, Any] = {"command": command}
189
+ if cwd:
190
+ body["cwd"] = cwd
191
+ if envs:
192
+ body["envs"] = dict(envs)
193
+ if timeout_ms:
194
+ body["timeoutMs"] = timeout_ms
195
+
196
+ return self.client._fetch(
197
+ "/v1/sandboxes/%s/exec" % sandbox_id,
198
+ method="POST",
199
+ body=body,
200
+ )
201
+
202
+
203
+ class ThreadsResource:
204
+ def __init__(self, client: AgentClient):
205
+ self.client = client
206
+
207
+ def list(self, sandbox_id: str) -> Any:
208
+ return self.client._fetch("/v1/sandboxes/%s/threads" % sandbox_id)
209
+
210
+ def create(self, sandbox_id: str, *, name: Optional[str] = None) -> Any:
211
+ body: Dict[str, Any] = {}
212
+ if name is not None:
213
+ body["name"] = name
214
+
215
+ return self.client._fetch(
216
+ "/v1/sandboxes/%s/threads" % sandbox_id,
217
+ method="POST",
218
+ body=body,
219
+ )
220
+
221
+ def get(self, sandbox_id: str, thread_id: str) -> Any:
222
+ return self.client._fetch(
223
+ "/v1/sandboxes/%s/threads/%s" % (sandbox_id, thread_id),
224
+ )
225
+
226
+ def delete(self, sandbox_id: str, thread_id: str) -> Any:
227
+ return self.client._fetch(
228
+ "/v1/sandboxes/%s/threads/%s" % (sandbox_id, thread_id),
229
+ method="DELETE",
230
+ )
231
+
232
+ def run(
233
+ self,
234
+ agent: str,
235
+ messages: Sequence[RunThreadMessage],
236
+ *,
237
+ sandbox_id: Optional[str] = None,
238
+ thread_id: Optional[str] = None,
239
+ name: Optional[str] = None,
240
+ ) -> Any:
241
+ if thread_id and not sandbox_id:
242
+ raise AgentClientError("threadId requires sandboxId")
243
+
244
+ if not sandbox_id:
245
+ sandbox_id = self.client.sandboxes.create(agent).id
246
+
247
+ if not thread_id:
248
+ thread_id = self.create(sandbox_id, name=name).id
249
+
250
+ encoded_agent = quote(agent, safe="")
251
+ response = self.client._request(
252
+ "/v1/chat/%s" % encoded_agent,
253
+ method="POST",
254
+ body={
255
+ "messages": list(messages),
256
+ "sandboxId": sandbox_id,
257
+ "threadId": thread_id,
258
+ },
259
+ stream=True,
260
+ )
261
+
262
+ return APIObject(
263
+ {
264
+ "sandboxId": sandbox_id,
265
+ "threadId": thread_id,
266
+ "response": response,
267
+ "resumeUrl": "%s/v1/chat/%s/%s/stream"
268
+ % (self.client._get_base_url(), encoded_agent, sandbox_id),
269
+ }
270
+ )
271
+
272
+
273
+ class TokensResource:
274
+ def __init__(self, client: AgentClient):
275
+ self.client = client
276
+
277
+ def create(
278
+ self,
279
+ *,
280
+ agent: Optional[str] = None,
281
+ user_id: Optional[str] = None,
282
+ expires_in: str = "1h",
283
+ ) -> Any:
284
+ body: Dict[str, Any] = {"expiresIn": expires_in}
285
+
286
+ if agent:
287
+ body["agents"] = [agent]
288
+
289
+ if user_id is not None:
290
+ body["userId"] = user_id
291
+
292
+ return self.client._fetch("/v1/tokens", method="POST", body=body)
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Mapping, TypedDict
4
+
5
+
6
+ class APIObject(dict):
7
+ """Dict with attribute-style access, similar to plain JS objects."""
8
+
9
+ def __getattr__(self, name: str) -> Any:
10
+ try:
11
+ return self[name]
12
+ except KeyError as exc:
13
+ raise AttributeError(name) from exc
14
+
15
+ def __setattr__(self, name: str, value: Any) -> None:
16
+ self[name] = value
17
+
18
+ def __delattr__(self, name: str) -> None:
19
+ try:
20
+ del self[name]
21
+ except KeyError as exc:
22
+ raise AttributeError(name) from exc
23
+
24
+
25
+ class ApiError(TypedDict):
26
+ code: str
27
+ message: str
28
+
29
+
30
+ RunThreadMessagePart = Mapping[str, Any]
31
+ RunThreadMessage = Mapping[str, Any]
32
+
33
+ Sandbox = APIObject
34
+ SandboxDetail = APIObject
35
+ ThreadSummary = APIObject
36
+ RunThreadResult = APIObject
37
+ Thread = APIObject
38
+ Token = APIObject
39
+ FileContent = APIObject
40
+ ExecResult = APIObject
41
+ GitCloneResult = APIObject