acontext 0.1.3__py3-none-any.whl → 0.1.4__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.
- acontext/__init__.py +0 -8
- acontext/agent/__init__.py +2 -0
- acontext/agent/base.py +2 -1
- acontext/agent/disk.py +25 -18
- acontext/agent/prompts.py +96 -0
- acontext/agent/sandbox.py +532 -0
- acontext/agent/skill.py +35 -44
- acontext/agent/text_editor.py +436 -0
- acontext/async_client.py +4 -5
- acontext/client.py +4 -5
- acontext/client_types.py +2 -0
- acontext/resources/__init__.py +0 -8
- acontext/resources/async_sandboxes.py +27 -0
- acontext/resources/async_sessions.py +0 -41
- acontext/resources/async_skills.py +40 -0
- acontext/resources/async_users.py +2 -2
- acontext/resources/sandboxes.py +27 -0
- acontext/resources/sessions.py +0 -41
- acontext/resources/skills.py +40 -0
- acontext/resources/users.py +2 -2
- acontext/types/__init__.py +8 -22
- acontext/types/sandbox.py +27 -0
- acontext/types/session.py +0 -16
- acontext/types/skill.py +11 -0
- acontext/types/tool.py +0 -6
- acontext/types/user.py +0 -1
- {acontext-0.1.3.dist-info → acontext-0.1.4.dist-info}/METADATA +1 -1
- acontext-0.1.4.dist-info/RECORD +41 -0
- acontext/resources/async_blocks.py +0 -164
- acontext/resources/async_spaces.py +0 -200
- acontext/resources/blocks.py +0 -163
- acontext/resources/spaces.py +0 -198
- acontext/types/block.py +0 -26
- acontext/types/space.py +0 -70
- acontext-0.1.3.dist-info/RECORD +0 -44
- {acontext-0.1.3.dist-info → acontext-0.1.4.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
"""Text editor file operations for sandbox environments."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from .sandbox import AsyncSandboxContext, SandboxContext
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def escape_for_shell(s: str) -> str:
|
|
11
|
+
"""Escape a string for safe use in shell commands."""
|
|
12
|
+
# Use single quotes and escape any single quotes in the string
|
|
13
|
+
return "'" + s.replace("'", "'\"'\"'") + "'"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ============================================================================
|
|
17
|
+
# Sync Operations
|
|
18
|
+
# ============================================================================
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def view_file(
|
|
22
|
+
ctx: "SandboxContext", path: str, view_range: list | None, timeout: float | None
|
|
23
|
+
) -> dict:
|
|
24
|
+
"""View file content with line numbers.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
ctx: The sandbox context.
|
|
28
|
+
path: The file path to view.
|
|
29
|
+
view_range: Optional [start_line, end_line] to view specific lines.
|
|
30
|
+
timeout: Optional timeout for command execution.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
A dict with file content and metadata, or error information.
|
|
34
|
+
"""
|
|
35
|
+
# First check if file exists and get total lines
|
|
36
|
+
check_cmd = f"wc -l < {escape_for_shell(path)} 2>/dev/null || echo 'FILE_NOT_FOUND'"
|
|
37
|
+
result = ctx.client.sandboxes.exec_command(
|
|
38
|
+
sandbox_id=ctx.sandbox_id,
|
|
39
|
+
command=check_cmd,
|
|
40
|
+
timeout=timeout,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if "FILE_NOT_FOUND" in result.stdout or result.exit_code != 0:
|
|
44
|
+
return {
|
|
45
|
+
"error": f"File not found: {path}",
|
|
46
|
+
"stderr": result.stderr,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
total_lines = int(result.stdout.strip()) if result.stdout.strip().isdigit() else 0
|
|
50
|
+
|
|
51
|
+
# Build the view command with line numbers
|
|
52
|
+
if view_range and len(view_range) == 2:
|
|
53
|
+
start_line, end_line = view_range
|
|
54
|
+
cmd = f"sed -n '{start_line},{end_line}p' {escape_for_shell(path)} | nl -ba -v {start_line}"
|
|
55
|
+
else:
|
|
56
|
+
cmd = f"nl -ba {escape_for_shell(path)}"
|
|
57
|
+
start_line = 1
|
|
58
|
+
|
|
59
|
+
result = ctx.client.sandboxes.exec_command(
|
|
60
|
+
sandbox_id=ctx.sandbox_id,
|
|
61
|
+
command=cmd,
|
|
62
|
+
timeout=timeout,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if result.exit_code != 0:
|
|
66
|
+
return {
|
|
67
|
+
"error": f"Failed to view file: {path}",
|
|
68
|
+
"stderr": result.stderr,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# Count lines in output
|
|
72
|
+
content_lines = (
|
|
73
|
+
result.stdout.rstrip("\n").split("\n") if result.stdout.strip() else []
|
|
74
|
+
)
|
|
75
|
+
num_lines = len(content_lines)
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
"file_type": "text",
|
|
79
|
+
"content": result.stdout,
|
|
80
|
+
"numLines": num_lines,
|
|
81
|
+
"startLine": start_line if view_range else 1,
|
|
82
|
+
"totalLines": total_lines + 1, # wc -l doesn't count last line without newline
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def create_file(
|
|
87
|
+
ctx: "SandboxContext", path: str, file_text: str, timeout: float | None
|
|
88
|
+
) -> dict:
|
|
89
|
+
"""Create a new file with content.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
ctx: The sandbox context.
|
|
93
|
+
path: The file path to create.
|
|
94
|
+
file_text: The content to write to the file.
|
|
95
|
+
timeout: Optional timeout for command execution.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
A dict with creation status or error information.
|
|
99
|
+
"""
|
|
100
|
+
# Check if file already exists
|
|
101
|
+
check_cmd = f"test -f {escape_for_shell(path)} && echo 'EXISTS' || echo 'NEW'"
|
|
102
|
+
check_result = ctx.client.sandboxes.exec_command(
|
|
103
|
+
sandbox_id=ctx.sandbox_id,
|
|
104
|
+
command=check_cmd,
|
|
105
|
+
timeout=timeout,
|
|
106
|
+
)
|
|
107
|
+
is_update = "EXISTS" in check_result.stdout
|
|
108
|
+
|
|
109
|
+
# Create directory if needed
|
|
110
|
+
dir_path = "/".join(path.split("/")[:-1])
|
|
111
|
+
if dir_path:
|
|
112
|
+
mkdir_cmd = f"mkdir -p {escape_for_shell(dir_path)}"
|
|
113
|
+
ctx.client.sandboxes.exec_command(
|
|
114
|
+
sandbox_id=ctx.sandbox_id,
|
|
115
|
+
command=mkdir_cmd,
|
|
116
|
+
timeout=timeout,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Write file using base64 encoding to safely transfer content
|
|
120
|
+
encoded_content = base64.b64encode(file_text.encode()).decode()
|
|
121
|
+
write_cmd = f"echo {escape_for_shell(encoded_content)} | base64 -d > {escape_for_shell(path)}"
|
|
122
|
+
|
|
123
|
+
result = ctx.client.sandboxes.exec_command(
|
|
124
|
+
sandbox_id=ctx.sandbox_id,
|
|
125
|
+
command=write_cmd,
|
|
126
|
+
timeout=timeout,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if result.exit_code != 0:
|
|
130
|
+
return {
|
|
131
|
+
"error": f"Failed to create file: {path}",
|
|
132
|
+
"stderr": result.stderr,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
"is_file_update": is_update,
|
|
137
|
+
"message": f"File {'updated' if is_update else 'created'}: {path}",
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def str_replace(
|
|
142
|
+
ctx: "SandboxContext", path: str, old_str: str, new_str: str, timeout: float | None
|
|
143
|
+
) -> dict:
|
|
144
|
+
"""Replace a string in a file.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
ctx: The sandbox context.
|
|
148
|
+
path: The file path to modify.
|
|
149
|
+
old_str: The exact string to find and replace.
|
|
150
|
+
new_str: The string to replace old_str with.
|
|
151
|
+
timeout: Optional timeout for command execution.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
A dict with diff information or error details.
|
|
155
|
+
"""
|
|
156
|
+
# First read the file content
|
|
157
|
+
read_cmd = f"cat {escape_for_shell(path)}"
|
|
158
|
+
result = ctx.client.sandboxes.exec_command(
|
|
159
|
+
sandbox_id=ctx.sandbox_id,
|
|
160
|
+
command=read_cmd,
|
|
161
|
+
timeout=timeout,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if result.exit_code != 0:
|
|
165
|
+
return {
|
|
166
|
+
"error": f"File not found: {path}",
|
|
167
|
+
"stderr": result.stderr,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
original_content = result.stdout
|
|
171
|
+
|
|
172
|
+
# Check if old_str exists in the file
|
|
173
|
+
if old_str not in original_content:
|
|
174
|
+
return {
|
|
175
|
+
"error": f"String not found in file: {old_str[:50]}...",
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# Count occurrences
|
|
179
|
+
occurrences = original_content.count(old_str)
|
|
180
|
+
if occurrences > 1:
|
|
181
|
+
return {
|
|
182
|
+
"error": f"Multiple occurrences ({occurrences}) of the string found. Please provide more context to make the match unique.",
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# Perform the replacement
|
|
186
|
+
new_content = original_content.replace(old_str, new_str, 1)
|
|
187
|
+
|
|
188
|
+
# Find the line numbers affected
|
|
189
|
+
old_lines = original_content.split("\n")
|
|
190
|
+
new_lines = new_content.split("\n")
|
|
191
|
+
|
|
192
|
+
# Find where the change starts
|
|
193
|
+
old_start = 1
|
|
194
|
+
for i, (old_line, new_line) in enumerate(zip(old_lines, new_lines)):
|
|
195
|
+
if old_line != new_line:
|
|
196
|
+
old_start = i + 1
|
|
197
|
+
break
|
|
198
|
+
|
|
199
|
+
# Write the new content
|
|
200
|
+
encoded_content = base64.b64encode(new_content.encode()).decode()
|
|
201
|
+
write_cmd = f"echo {escape_for_shell(encoded_content)} | base64 -d > {escape_for_shell(path)}"
|
|
202
|
+
|
|
203
|
+
result = ctx.client.sandboxes.exec_command(
|
|
204
|
+
sandbox_id=ctx.sandbox_id,
|
|
205
|
+
command=write_cmd,
|
|
206
|
+
timeout=timeout,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if result.exit_code != 0:
|
|
210
|
+
return {
|
|
211
|
+
"error": f"Failed to write file: {path}",
|
|
212
|
+
"stderr": result.stderr,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# Calculate diff info
|
|
216
|
+
old_str_lines = old_str.count("\n") + 1
|
|
217
|
+
new_str_lines = new_str.count("\n") + 1
|
|
218
|
+
|
|
219
|
+
# Build diff lines
|
|
220
|
+
diff_lines = []
|
|
221
|
+
for line in old_str.split("\n"):
|
|
222
|
+
diff_lines.append(f"-{line}")
|
|
223
|
+
for line in new_str.split("\n"):
|
|
224
|
+
diff_lines.append(f"+{line}")
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
"oldStart": old_start,
|
|
228
|
+
"oldLines": old_str_lines,
|
|
229
|
+
"newStart": old_start,
|
|
230
|
+
"newLines": new_str_lines,
|
|
231
|
+
"lines": diff_lines,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ============================================================================
|
|
236
|
+
# Async Operations
|
|
237
|
+
# ============================================================================
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
async def async_view_file(
|
|
241
|
+
ctx: "AsyncSandboxContext", path: str, view_range: list | None, timeout: float | None
|
|
242
|
+
) -> dict:
|
|
243
|
+
"""View file content with line numbers (async).
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
ctx: The async sandbox context.
|
|
247
|
+
path: The file path to view.
|
|
248
|
+
view_range: Optional [start_line, end_line] to view specific lines.
|
|
249
|
+
timeout: Optional timeout for command execution.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
A dict with file content and metadata, or error information.
|
|
253
|
+
"""
|
|
254
|
+
check_cmd = f"wc -l < {escape_for_shell(path)} 2>/dev/null || echo 'FILE_NOT_FOUND'"
|
|
255
|
+
result = await ctx.client.sandboxes.exec_command(
|
|
256
|
+
sandbox_id=ctx.sandbox_id,
|
|
257
|
+
command=check_cmd,
|
|
258
|
+
timeout=timeout,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
if "FILE_NOT_FOUND" in result.stdout or result.exit_code != 0:
|
|
262
|
+
return {
|
|
263
|
+
"error": f"File not found: {path}",
|
|
264
|
+
"stderr": result.stderr,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
total_lines = int(result.stdout.strip()) if result.stdout.strip().isdigit() else 0
|
|
268
|
+
|
|
269
|
+
if view_range and len(view_range) == 2:
|
|
270
|
+
start_line, end_line = view_range
|
|
271
|
+
cmd = f"sed -n '{start_line},{end_line}p' {escape_for_shell(path)} | nl -ba -v {start_line}"
|
|
272
|
+
else:
|
|
273
|
+
cmd = f"nl -ba {escape_for_shell(path)}"
|
|
274
|
+
start_line = 1
|
|
275
|
+
|
|
276
|
+
result = await ctx.client.sandboxes.exec_command(
|
|
277
|
+
sandbox_id=ctx.sandbox_id,
|
|
278
|
+
command=cmd,
|
|
279
|
+
timeout=timeout,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
if result.exit_code != 0:
|
|
283
|
+
return {
|
|
284
|
+
"error": f"Failed to view file: {path}",
|
|
285
|
+
"stderr": result.stderr,
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
content_lines = (
|
|
289
|
+
result.stdout.rstrip("\n").split("\n") if result.stdout.strip() else []
|
|
290
|
+
)
|
|
291
|
+
num_lines = len(content_lines)
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
"file_type": "text",
|
|
295
|
+
"content": result.stdout,
|
|
296
|
+
"numLines": num_lines,
|
|
297
|
+
"startLine": start_line if view_range else 1,
|
|
298
|
+
"totalLines": total_lines + 1,
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
async def async_create_file(
|
|
303
|
+
ctx: "AsyncSandboxContext", path: str, file_text: str, timeout: float | None
|
|
304
|
+
) -> dict:
|
|
305
|
+
"""Create a new file with content (async).
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
ctx: The async sandbox context.
|
|
309
|
+
path: The file path to create.
|
|
310
|
+
file_text: The content to write to the file.
|
|
311
|
+
timeout: Optional timeout for command execution.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
A dict with creation status or error information.
|
|
315
|
+
"""
|
|
316
|
+
check_cmd = f"test -f {escape_for_shell(path)} && echo 'EXISTS' || echo 'NEW'"
|
|
317
|
+
check_result = await ctx.client.sandboxes.exec_command(
|
|
318
|
+
sandbox_id=ctx.sandbox_id,
|
|
319
|
+
command=check_cmd,
|
|
320
|
+
timeout=timeout,
|
|
321
|
+
)
|
|
322
|
+
is_update = "EXISTS" in check_result.stdout
|
|
323
|
+
|
|
324
|
+
dir_path = "/".join(path.split("/")[:-1])
|
|
325
|
+
if dir_path:
|
|
326
|
+
mkdir_cmd = f"mkdir -p {escape_for_shell(dir_path)}"
|
|
327
|
+
await ctx.client.sandboxes.exec_command(
|
|
328
|
+
sandbox_id=ctx.sandbox_id,
|
|
329
|
+
command=mkdir_cmd,
|
|
330
|
+
timeout=timeout,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
encoded_content = base64.b64encode(file_text.encode()).decode()
|
|
334
|
+
write_cmd = f"echo {escape_for_shell(encoded_content)} | base64 -d > {escape_for_shell(path)}"
|
|
335
|
+
|
|
336
|
+
result = await ctx.client.sandboxes.exec_command(
|
|
337
|
+
sandbox_id=ctx.sandbox_id,
|
|
338
|
+
command=write_cmd,
|
|
339
|
+
timeout=timeout,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
if result.exit_code != 0:
|
|
343
|
+
return {
|
|
344
|
+
"error": f"Failed to create file: {path}",
|
|
345
|
+
"stderr": result.stderr,
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
"is_file_update": is_update,
|
|
350
|
+
"message": f"File {'updated' if is_update else 'created'}: {path}",
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
async def async_str_replace(
|
|
355
|
+
ctx: "AsyncSandboxContext", path: str, old_str: str, new_str: str, timeout: float | None
|
|
356
|
+
) -> dict:
|
|
357
|
+
"""Replace a string in a file (async).
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
ctx: The async sandbox context.
|
|
361
|
+
path: The file path to modify.
|
|
362
|
+
old_str: The exact string to find and replace.
|
|
363
|
+
new_str: The string to replace old_str with.
|
|
364
|
+
timeout: Optional timeout for command execution.
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
A dict with diff information or error details.
|
|
368
|
+
"""
|
|
369
|
+
read_cmd = f"cat {escape_for_shell(path)}"
|
|
370
|
+
result = await ctx.client.sandboxes.exec_command(
|
|
371
|
+
sandbox_id=ctx.sandbox_id,
|
|
372
|
+
command=read_cmd,
|
|
373
|
+
timeout=timeout,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
if result.exit_code != 0:
|
|
377
|
+
return {
|
|
378
|
+
"error": f"File not found: {path}",
|
|
379
|
+
"stderr": result.stderr,
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
original_content = result.stdout
|
|
383
|
+
|
|
384
|
+
if old_str not in original_content:
|
|
385
|
+
return {
|
|
386
|
+
"error": f"String not found in file: {old_str[:50]}...",
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
occurrences = original_content.count(old_str)
|
|
390
|
+
if occurrences > 1:
|
|
391
|
+
return {
|
|
392
|
+
"error": f"Multiple occurrences ({occurrences}) of the string found. Please provide more context to make the match unique.",
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
new_content = original_content.replace(old_str, new_str, 1)
|
|
396
|
+
|
|
397
|
+
old_lines = original_content.split("\n")
|
|
398
|
+
new_lines = new_content.split("\n")
|
|
399
|
+
|
|
400
|
+
old_start = 1
|
|
401
|
+
for i, (old_line, new_line) in enumerate(zip(old_lines, new_lines)):
|
|
402
|
+
if old_line != new_line:
|
|
403
|
+
old_start = i + 1
|
|
404
|
+
break
|
|
405
|
+
|
|
406
|
+
encoded_content = base64.b64encode(new_content.encode()).decode()
|
|
407
|
+
write_cmd = f"echo {escape_for_shell(encoded_content)} | base64 -d > {escape_for_shell(path)}"
|
|
408
|
+
|
|
409
|
+
result = await ctx.client.sandboxes.exec_command(
|
|
410
|
+
sandbox_id=ctx.sandbox_id,
|
|
411
|
+
command=write_cmd,
|
|
412
|
+
timeout=timeout,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
if result.exit_code != 0:
|
|
416
|
+
return {
|
|
417
|
+
"error": f"Failed to write file: {path}",
|
|
418
|
+
"stderr": result.stderr,
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
old_str_lines = old_str.count("\n") + 1
|
|
422
|
+
new_str_lines = new_str.count("\n") + 1
|
|
423
|
+
|
|
424
|
+
diff_lines = []
|
|
425
|
+
for line in old_str.split("\n"):
|
|
426
|
+
diff_lines.append(f"-{line}")
|
|
427
|
+
for line in new_str.split("\n"):
|
|
428
|
+
diff_lines.append(f"+{line}")
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
"oldStart": old_start,
|
|
432
|
+
"oldLines": old_str_lines,
|
|
433
|
+
"newStart": old_start,
|
|
434
|
+
"newLines": new_str_lines,
|
|
435
|
+
"lines": diff_lines,
|
|
436
|
+
}
|
acontext/async_client.py
CHANGED
|
@@ -13,10 +13,8 @@ from .errors import APIError, TransportError
|
|
|
13
13
|
from .messages import MessagePart as MessagePart
|
|
14
14
|
from .uploads import FileUpload as FileUpload
|
|
15
15
|
from .resources.async_disks import AsyncDisksAPI as AsyncDisksAPI
|
|
16
|
-
from .resources.async_blocks import AsyncBlocksAPI as AsyncBlocksAPI
|
|
17
16
|
from .resources.async_sandboxes import AsyncSandboxesAPI as AsyncSandboxesAPI
|
|
18
17
|
from .resources.async_sessions import AsyncSessionsAPI as AsyncSessionsAPI
|
|
19
|
-
from .resources.async_spaces import AsyncSpacesAPI as AsyncSpacesAPI
|
|
20
18
|
from .resources.async_tools import AsyncToolsAPI as AsyncToolsAPI
|
|
21
19
|
from .resources.async_skills import AsyncSkillsAPI as AsyncSkillsAPI
|
|
22
20
|
from .resources.async_users import AsyncUsersAPI as AsyncUsersAPI
|
|
@@ -106,11 +104,9 @@ class AcontextAsyncClient:
|
|
|
106
104
|
|
|
107
105
|
self._timeout = actual_timeout
|
|
108
106
|
|
|
109
|
-
self.spaces = AsyncSpacesAPI(self)
|
|
110
107
|
self.sessions = AsyncSessionsAPI(self)
|
|
111
108
|
self.disks = AsyncDisksAPI(self)
|
|
112
109
|
self.artifacts = self.disks.artifacts
|
|
113
|
-
self.blocks = AsyncBlocksAPI(self)
|
|
114
110
|
self.tools = AsyncToolsAPI(self)
|
|
115
111
|
self.skills = AsyncSkillsAPI(self)
|
|
116
112
|
self.users = AsyncUsersAPI(self)
|
|
@@ -160,7 +156,10 @@ class AcontextAsyncClient:
|
|
|
160
156
|
data: Mapping[str, Any] | None = None,
|
|
161
157
|
files: Mapping[str, tuple[str, BinaryIO, str | None]] | None = None,
|
|
162
158
|
unwrap: bool = True,
|
|
159
|
+
timeout: float | None = None,
|
|
163
160
|
) -> Any:
|
|
161
|
+
# Use per-request timeout if provided, otherwise use client default
|
|
162
|
+
effective_timeout = timeout if timeout is not None else self._timeout
|
|
164
163
|
try:
|
|
165
164
|
response = await self._client.request(
|
|
166
165
|
method=method,
|
|
@@ -169,7 +168,7 @@ class AcontextAsyncClient:
|
|
|
169
168
|
json=json_data,
|
|
170
169
|
data=data,
|
|
171
170
|
files=files,
|
|
172
|
-
timeout=
|
|
171
|
+
timeout=effective_timeout,
|
|
173
172
|
)
|
|
174
173
|
except httpx.HTTPError as exc: # pragma: no cover - passthrough to caller
|
|
175
174
|
raise TransportError(str(exc)) from exc
|
acontext/client.py
CHANGED
|
@@ -13,10 +13,8 @@ from .errors import APIError, TransportError
|
|
|
13
13
|
from .messages import MessagePart as MessagePart
|
|
14
14
|
from .uploads import FileUpload as FileUpload
|
|
15
15
|
from .resources.disks import DisksAPI as DisksAPI
|
|
16
|
-
from .resources.blocks import BlocksAPI as BlocksAPI
|
|
17
16
|
from .resources.sandboxes import SandboxesAPI as SandboxesAPI
|
|
18
17
|
from .resources.sessions import SessionsAPI as SessionsAPI
|
|
19
|
-
from .resources.spaces import SpacesAPI as SpacesAPI
|
|
20
18
|
from .resources.tools import ToolsAPI as ToolsAPI
|
|
21
19
|
from .resources.skills import SkillsAPI as SkillsAPI
|
|
22
20
|
from .resources.users import UsersAPI as UsersAPI
|
|
@@ -106,11 +104,9 @@ class AcontextClient:
|
|
|
106
104
|
|
|
107
105
|
self._timeout = actual_timeout
|
|
108
106
|
|
|
109
|
-
self.spaces = SpacesAPI(self)
|
|
110
107
|
self.sessions = SessionsAPI(self)
|
|
111
108
|
self.disks = DisksAPI(self)
|
|
112
109
|
self.artifacts = self.disks.artifacts
|
|
113
|
-
self.blocks = BlocksAPI(self)
|
|
114
110
|
self.tools = ToolsAPI(self)
|
|
115
111
|
self.skills = SkillsAPI(self)
|
|
116
112
|
self.users = UsersAPI(self)
|
|
@@ -159,7 +155,10 @@ class AcontextClient:
|
|
|
159
155
|
data: Mapping[str, Any] | None = None,
|
|
160
156
|
files: Mapping[str, tuple[str, BinaryIO, str | None]] | None = None,
|
|
161
157
|
unwrap: bool = True,
|
|
158
|
+
timeout: float | None = None,
|
|
162
159
|
) -> Any:
|
|
160
|
+
# Use per-request timeout if provided, otherwise use client default
|
|
161
|
+
effective_timeout = timeout if timeout is not None else self._timeout
|
|
163
162
|
try:
|
|
164
163
|
response = self._client.request(
|
|
165
164
|
method=method,
|
|
@@ -168,7 +167,7 @@ class AcontextClient:
|
|
|
168
167
|
json=json_data,
|
|
169
168
|
data=data,
|
|
170
169
|
files=files,
|
|
171
|
-
timeout=
|
|
170
|
+
timeout=effective_timeout,
|
|
172
171
|
)
|
|
173
172
|
except httpx.HTTPError as exc: # pragma: no cover - passthrough to caller
|
|
174
173
|
raise TransportError(str(exc)) from exc
|
acontext/client_types.py
CHANGED
|
@@ -17,6 +17,7 @@ class RequesterProtocol(Protocol):
|
|
|
17
17
|
data: Mapping[str, Any] | None = None,
|
|
18
18
|
files: Mapping[str, tuple[str, BinaryIO, str | None]] | None = None,
|
|
19
19
|
unwrap: bool = True,
|
|
20
|
+
timeout: float | None = None,
|
|
20
21
|
) -> Any:
|
|
21
22
|
...
|
|
22
23
|
|
|
@@ -32,5 +33,6 @@ class AsyncRequesterProtocol(Protocol):
|
|
|
32
33
|
data: Mapping[str, Any] | None = None,
|
|
33
34
|
files: Mapping[str, tuple[str, BinaryIO, str | None]] | None = None,
|
|
34
35
|
unwrap: bool = True,
|
|
36
|
+
timeout: float | None = None,
|
|
35
37
|
) -> Awaitable[Any]:
|
|
36
38
|
...
|
acontext/resources/__init__.py
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
"""Resource-specific API helpers for the Acontext client."""
|
|
2
2
|
|
|
3
|
-
from .async_blocks import AsyncBlocksAPI
|
|
4
3
|
from .async_disks import AsyncDisksAPI, AsyncDiskArtifactsAPI
|
|
5
4
|
from .async_sandboxes import AsyncSandboxesAPI
|
|
6
5
|
from .async_sessions import AsyncSessionsAPI
|
|
7
|
-
from .async_spaces import AsyncSpacesAPI
|
|
8
6
|
from .async_tools import AsyncToolsAPI
|
|
9
7
|
from .async_skills import AsyncSkillsAPI
|
|
10
8
|
from .async_users import AsyncUsersAPI
|
|
11
|
-
from .blocks import BlocksAPI
|
|
12
9
|
from .disks import DisksAPI, DiskArtifactsAPI
|
|
13
10
|
from .sandboxes import SandboxesAPI
|
|
14
11
|
from .sessions import SessionsAPI
|
|
15
|
-
from .spaces import SpacesAPI
|
|
16
12
|
from .tools import ToolsAPI
|
|
17
13
|
from .skills import SkillsAPI
|
|
18
14
|
from .users import UsersAPI
|
|
@@ -20,19 +16,15 @@ from .users import UsersAPI
|
|
|
20
16
|
__all__ = [
|
|
21
17
|
"DisksAPI",
|
|
22
18
|
"DiskArtifactsAPI",
|
|
23
|
-
"BlocksAPI",
|
|
24
19
|
"SandboxesAPI",
|
|
25
20
|
"SessionsAPI",
|
|
26
|
-
"SpacesAPI",
|
|
27
21
|
"ToolsAPI",
|
|
28
22
|
"SkillsAPI",
|
|
29
23
|
"UsersAPI",
|
|
30
24
|
"AsyncDisksAPI",
|
|
31
25
|
"AsyncDiskArtifactsAPI",
|
|
32
|
-
"AsyncBlocksAPI",
|
|
33
26
|
"AsyncSandboxesAPI",
|
|
34
27
|
"AsyncSessionsAPI",
|
|
35
|
-
"AsyncSpacesAPI",
|
|
36
28
|
"AsyncToolsAPI",
|
|
37
29
|
"AsyncSkillsAPI",
|
|
38
30
|
"AsyncUsersAPI",
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
Sandboxes endpoints (async).
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from .._utils import build_params
|
|
5
6
|
from ..client_types import AsyncRequesterProtocol
|
|
6
7
|
from ..types.sandbox import (
|
|
8
|
+
GetSandboxLogsOutput,
|
|
7
9
|
SandboxCommandOutput,
|
|
8
10
|
SandboxRuntimeInfo,
|
|
9
11
|
)
|
|
@@ -28,12 +30,15 @@ class AsyncSandboxesAPI:
|
|
|
28
30
|
*,
|
|
29
31
|
sandbox_id: str,
|
|
30
32
|
command: str,
|
|
33
|
+
timeout: float | None = None,
|
|
31
34
|
) -> SandboxCommandOutput:
|
|
32
35
|
"""Execute a shell command in the sandbox.
|
|
33
36
|
|
|
34
37
|
Args:
|
|
35
38
|
sandbox_id: The UUID of the sandbox.
|
|
36
39
|
command: The shell command to execute.
|
|
40
|
+
timeout: Optional timeout in seconds for this command.
|
|
41
|
+
If not provided, uses the client's default timeout.
|
|
37
42
|
|
|
38
43
|
Returns:
|
|
39
44
|
SandboxCommandOutput containing stdout, stderr, and exit code.
|
|
@@ -42,6 +47,7 @@ class AsyncSandboxesAPI:
|
|
|
42
47
|
"POST",
|
|
43
48
|
f"/sandbox/{sandbox_id}/exec",
|
|
44
49
|
json_data={"command": command},
|
|
50
|
+
timeout=timeout,
|
|
45
51
|
)
|
|
46
52
|
return SandboxCommandOutput.model_validate(data)
|
|
47
53
|
|
|
@@ -56,3 +62,24 @@ class AsyncSandboxesAPI:
|
|
|
56
62
|
"""
|
|
57
63
|
data = await self._requester.request("DELETE", f"/sandbox/{sandbox_id}")
|
|
58
64
|
return FlagResponse.model_validate(data)
|
|
65
|
+
|
|
66
|
+
async def get_logs(
|
|
67
|
+
self,
|
|
68
|
+
*,
|
|
69
|
+
limit: int | None = None,
|
|
70
|
+
cursor: str | None = None,
|
|
71
|
+
time_desc: bool | None = None,
|
|
72
|
+
) -> GetSandboxLogsOutput:
|
|
73
|
+
"""Get sandbox logs for the project with cursor-based pagination.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
limit: Maximum number of logs to return (default 20, max 200).
|
|
77
|
+
cursor: Cursor for pagination. Use the cursor from the previous response to get the next page.
|
|
78
|
+
time_desc: Order by created_at descending if True, ascending if False (default False).
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
GetSandboxLogsOutput containing the list of sandbox logs and pagination information.
|
|
82
|
+
"""
|
|
83
|
+
params = build_params(limit=limit, cursor=cursor, time_desc=time_desc)
|
|
84
|
+
data = await self._requester.request("GET", "/sandbox/logs", params=params or None)
|
|
85
|
+
return GetSandboxLogsOutput.model_validate(data)
|