acontext 0.1.1__tar.gz → 0.1.3__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.
- {acontext-0.1.1 → acontext-0.1.3}/PKG-INFO +2 -2
- {acontext-0.1.1 → acontext-0.1.3}/pyproject.toml +1 -1
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/async_client.py +2 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/client.py +2 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/resources/__init__.py +4 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/resources/async_disks.py +92 -0
- acontext-0.1.3/src/acontext/resources/async_sandboxes.py +58 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/resources/async_sessions.py +42 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/resources/disks.py +131 -33
- acontext-0.1.3/src/acontext/resources/sandboxes.py +58 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/resources/sessions.py +51 -8
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/types/__init__.py +7 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/types/disk.py +6 -3
- acontext-0.1.3/src/acontext/types/sandbox.py +20 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/types/session.py +25 -1
- {acontext-0.1.1 → acontext-0.1.3}/README.md +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/__init__.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/_constants.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/_utils.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/agent/__init__.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/agent/base.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/agent/disk.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/agent/skill.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/client_types.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/errors.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/messages.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/py.typed +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/resources/async_blocks.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/resources/async_skills.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/resources/async_spaces.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/resources/async_tools.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/resources/async_users.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/resources/blocks.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/resources/skills.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/resources/spaces.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/resources/tools.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/resources/users.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/types/block.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/types/common.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/types/skill.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/types/space.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/types/tool.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/types/user.py +0 -0
- {acontext-0.1.1 → acontext-0.1.3}/src/acontext/uploads.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: acontext
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Python SDK for the Acontext API
|
|
5
5
|
Keywords: acontext,sdk,client,api
|
|
6
6
|
Requires-Dist: httpx>=0.28.1
|
|
@@ -10,8 +10,8 @@ Requires-Dist: pydantic>=2.12.3
|
|
|
10
10
|
Requires-Dist: urllib3>=2.6.3
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
12
|
Project-URL: Homepage, https://github.com/memodb-io/Acontext
|
|
13
|
-
Project-URL: Issues, https://github.com/memodb-io/Acontext/issues
|
|
14
13
|
Project-URL: Repository, https://github.com/memodb-io/Acontext
|
|
14
|
+
Project-URL: Issues, https://github.com/memodb-io/Acontext/issues
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
|
|
17
17
|
## acontext client for python
|
|
@@ -14,6 +14,7 @@ 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
16
|
from .resources.async_blocks import AsyncBlocksAPI as AsyncBlocksAPI
|
|
17
|
+
from .resources.async_sandboxes import AsyncSandboxesAPI as AsyncSandboxesAPI
|
|
17
18
|
from .resources.async_sessions import AsyncSessionsAPI as AsyncSessionsAPI
|
|
18
19
|
from .resources.async_spaces import AsyncSpacesAPI as AsyncSpacesAPI
|
|
19
20
|
from .resources.async_tools import AsyncToolsAPI as AsyncToolsAPI
|
|
@@ -113,6 +114,7 @@ class AcontextAsyncClient:
|
|
|
113
114
|
self.tools = AsyncToolsAPI(self)
|
|
114
115
|
self.skills = AsyncSkillsAPI(self)
|
|
115
116
|
self.users = AsyncUsersAPI(self)
|
|
117
|
+
self.sandboxes = AsyncSandboxesAPI(self)
|
|
116
118
|
|
|
117
119
|
@property
|
|
118
120
|
def base_url(self) -> str:
|
|
@@ -14,6 +14,7 @@ from .messages import MessagePart as MessagePart
|
|
|
14
14
|
from .uploads import FileUpload as FileUpload
|
|
15
15
|
from .resources.disks import DisksAPI as DisksAPI
|
|
16
16
|
from .resources.blocks import BlocksAPI as BlocksAPI
|
|
17
|
+
from .resources.sandboxes import SandboxesAPI as SandboxesAPI
|
|
17
18
|
from .resources.sessions import SessionsAPI as SessionsAPI
|
|
18
19
|
from .resources.spaces import SpacesAPI as SpacesAPI
|
|
19
20
|
from .resources.tools import ToolsAPI as ToolsAPI
|
|
@@ -113,6 +114,7 @@ class AcontextClient:
|
|
|
113
114
|
self.tools = ToolsAPI(self)
|
|
114
115
|
self.skills = SkillsAPI(self)
|
|
115
116
|
self.users = UsersAPI(self)
|
|
117
|
+
self.sandboxes = SandboxesAPI(self)
|
|
116
118
|
|
|
117
119
|
@property
|
|
118
120
|
def base_url(self) -> str:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from .async_blocks import AsyncBlocksAPI
|
|
4
4
|
from .async_disks import AsyncDisksAPI, AsyncDiskArtifactsAPI
|
|
5
|
+
from .async_sandboxes import AsyncSandboxesAPI
|
|
5
6
|
from .async_sessions import AsyncSessionsAPI
|
|
6
7
|
from .async_spaces import AsyncSpacesAPI
|
|
7
8
|
from .async_tools import AsyncToolsAPI
|
|
@@ -9,6 +10,7 @@ from .async_skills import AsyncSkillsAPI
|
|
|
9
10
|
from .async_users import AsyncUsersAPI
|
|
10
11
|
from .blocks import BlocksAPI
|
|
11
12
|
from .disks import DisksAPI, DiskArtifactsAPI
|
|
13
|
+
from .sandboxes import SandboxesAPI
|
|
12
14
|
from .sessions import SessionsAPI
|
|
13
15
|
from .spaces import SpacesAPI
|
|
14
16
|
from .tools import ToolsAPI
|
|
@@ -19,6 +21,7 @@ __all__ = [
|
|
|
19
21
|
"DisksAPI",
|
|
20
22
|
"DiskArtifactsAPI",
|
|
21
23
|
"BlocksAPI",
|
|
24
|
+
"SandboxesAPI",
|
|
22
25
|
"SessionsAPI",
|
|
23
26
|
"SpacesAPI",
|
|
24
27
|
"ToolsAPI",
|
|
@@ -27,6 +30,7 @@ __all__ = [
|
|
|
27
30
|
"AsyncDisksAPI",
|
|
28
31
|
"AsyncDiskArtifactsAPI",
|
|
29
32
|
"AsyncBlocksAPI",
|
|
33
|
+
"AsyncSandboxesAPI",
|
|
30
34
|
"AsyncSessionsAPI",
|
|
31
35
|
"AsyncSpacesAPI",
|
|
32
36
|
"AsyncToolsAPI",
|
|
@@ -287,3 +287,95 @@ class AsyncDiskArtifactsAPI:
|
|
|
287
287
|
"GET", f"/disk/{disk_id}/artifact/glob", params=params
|
|
288
288
|
)
|
|
289
289
|
return [Artifact.model_validate(item) for item in data]
|
|
290
|
+
|
|
291
|
+
async def download_to_sandbox(
|
|
292
|
+
self,
|
|
293
|
+
disk_id: str,
|
|
294
|
+
*,
|
|
295
|
+
file_path: str,
|
|
296
|
+
filename: str,
|
|
297
|
+
sandbox_id: str,
|
|
298
|
+
sandbox_path: str,
|
|
299
|
+
) -> bool:
|
|
300
|
+
"""Download an artifact from disk storage to a sandbox environment.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
disk_id: The UUID of the disk containing the artifact.
|
|
304
|
+
file_path: Directory path of the artifact (not including filename).
|
|
305
|
+
filename: The filename of the artifact.
|
|
306
|
+
sandbox_id: The UUID of the target sandbox.
|
|
307
|
+
sandbox_path: Destination directory in the sandbox.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
True if the download was successful.
|
|
311
|
+
|
|
312
|
+
Example:
|
|
313
|
+
```python
|
|
314
|
+
success = await client.disks.artifacts.download_to_sandbox(
|
|
315
|
+
disk_id="disk-uuid",
|
|
316
|
+
file_path="/documents/",
|
|
317
|
+
filename="report.pdf",
|
|
318
|
+
sandbox_id="sandbox-uuid",
|
|
319
|
+
sandbox_path="/home/user/"
|
|
320
|
+
)
|
|
321
|
+
print(f"Success: {success}")
|
|
322
|
+
```
|
|
323
|
+
"""
|
|
324
|
+
payload = {
|
|
325
|
+
"file_path": file_path,
|
|
326
|
+
"filename": filename,
|
|
327
|
+
"sandbox_id": sandbox_id,
|
|
328
|
+
"sandbox_path": sandbox_path,
|
|
329
|
+
}
|
|
330
|
+
data = await self._requester.request(
|
|
331
|
+
"POST",
|
|
332
|
+
f"/disk/{disk_id}/artifact/download_to_sandbox",
|
|
333
|
+
json_data=payload,
|
|
334
|
+
)
|
|
335
|
+
return bool(data.get("success", False))
|
|
336
|
+
|
|
337
|
+
async def upload_from_sandbox(
|
|
338
|
+
self,
|
|
339
|
+
disk_id: str,
|
|
340
|
+
*,
|
|
341
|
+
sandbox_id: str,
|
|
342
|
+
sandbox_path: str,
|
|
343
|
+
sandbox_filename: str,
|
|
344
|
+
file_path: str,
|
|
345
|
+
) -> Artifact:
|
|
346
|
+
"""Upload a file from a sandbox environment to disk storage as an artifact.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
disk_id: The UUID of the target disk.
|
|
350
|
+
sandbox_id: The UUID of the source sandbox.
|
|
351
|
+
sandbox_path: Source directory in the sandbox (not including filename).
|
|
352
|
+
sandbox_filename: Filename in the sandbox.
|
|
353
|
+
file_path: Destination directory path on the disk.
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
Artifact containing the created artifact information.
|
|
357
|
+
|
|
358
|
+
Example:
|
|
359
|
+
```python
|
|
360
|
+
artifact = await client.disks.artifacts.upload_from_sandbox(
|
|
361
|
+
disk_id="disk-uuid",
|
|
362
|
+
sandbox_id="sandbox-uuid",
|
|
363
|
+
sandbox_path="/home/user/",
|
|
364
|
+
sandbox_filename="output.txt",
|
|
365
|
+
file_path="/results/"
|
|
366
|
+
)
|
|
367
|
+
print(f"Created: {artifact.path}{artifact.filename}")
|
|
368
|
+
```
|
|
369
|
+
"""
|
|
370
|
+
payload = {
|
|
371
|
+
"sandbox_id": sandbox_id,
|
|
372
|
+
"sandbox_path": sandbox_path,
|
|
373
|
+
"sandbox_filename": sandbox_filename,
|
|
374
|
+
"file_path": file_path,
|
|
375
|
+
}
|
|
376
|
+
data = await self._requester.request(
|
|
377
|
+
"POST",
|
|
378
|
+
f"/disk/{disk_id}/artifact/upload_from_sandbox",
|
|
379
|
+
json_data=payload,
|
|
380
|
+
)
|
|
381
|
+
return Artifact.model_validate(data)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sandboxes endpoints (async).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from ..client_types import AsyncRequesterProtocol
|
|
6
|
+
from ..types.sandbox import (
|
|
7
|
+
SandboxCommandOutput,
|
|
8
|
+
SandboxRuntimeInfo,
|
|
9
|
+
)
|
|
10
|
+
from ..types.tool import FlagResponse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AsyncSandboxesAPI:
|
|
14
|
+
def __init__(self, requester: AsyncRequesterProtocol) -> None:
|
|
15
|
+
self._requester = requester
|
|
16
|
+
|
|
17
|
+
async def create(self) -> SandboxRuntimeInfo:
|
|
18
|
+
"""Create and start a new sandbox.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
SandboxRuntimeInfo containing the sandbox ID, status, and timestamps.
|
|
22
|
+
"""
|
|
23
|
+
data = await self._requester.request("POST", "/sandbox")
|
|
24
|
+
return SandboxRuntimeInfo.model_validate(data)
|
|
25
|
+
|
|
26
|
+
async def exec_command(
|
|
27
|
+
self,
|
|
28
|
+
*,
|
|
29
|
+
sandbox_id: str,
|
|
30
|
+
command: str,
|
|
31
|
+
) -> SandboxCommandOutput:
|
|
32
|
+
"""Execute a shell command in the sandbox.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
sandbox_id: The UUID of the sandbox.
|
|
36
|
+
command: The shell command to execute.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
SandboxCommandOutput containing stdout, stderr, and exit code.
|
|
40
|
+
"""
|
|
41
|
+
data = await self._requester.request(
|
|
42
|
+
"POST",
|
|
43
|
+
f"/sandbox/{sandbox_id}/exec",
|
|
44
|
+
json_data={"command": command},
|
|
45
|
+
)
|
|
46
|
+
return SandboxCommandOutput.model_validate(data)
|
|
47
|
+
|
|
48
|
+
async def kill(self, sandbox_id: str) -> FlagResponse:
|
|
49
|
+
"""Kill a running sandbox.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
sandbox_id: The UUID of the sandbox to kill.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
FlagResponse with status and error message.
|
|
56
|
+
"""
|
|
57
|
+
data = await self._requester.request("DELETE", f"/sandbox/{sandbox_id}")
|
|
58
|
+
return FlagResponse.model_validate(data)
|
|
@@ -180,6 +180,47 @@ class AsyncSessionsAPI:
|
|
|
180
180
|
)
|
|
181
181
|
return GetTasksOutput.model_validate(data)
|
|
182
182
|
|
|
183
|
+
async def get_session_summary(
|
|
184
|
+
self,
|
|
185
|
+
session_id: str,
|
|
186
|
+
*,
|
|
187
|
+
limit: int | None = None,
|
|
188
|
+
) -> str:
|
|
189
|
+
"""Get a summary of all tasks in a session as a formatted string.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
session_id: The UUID of the session.
|
|
193
|
+
limit: Maximum number of tasks to include in the summary. Defaults to None (all tasks).
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
A formatted string containing the session summary with all task information.
|
|
197
|
+
"""
|
|
198
|
+
tasks_output = await self.get_tasks(session_id, limit=limit, time_desc=False)
|
|
199
|
+
tasks = tasks_output.items
|
|
200
|
+
|
|
201
|
+
if not tasks:
|
|
202
|
+
return ""
|
|
203
|
+
|
|
204
|
+
parts: list[str] = []
|
|
205
|
+
for task in tasks:
|
|
206
|
+
task_lines = [
|
|
207
|
+
f'<task id="{task.order}" description="{task.data.task_description}">'
|
|
208
|
+
]
|
|
209
|
+
if task.data.progresses:
|
|
210
|
+
task_lines.append("<progress>")
|
|
211
|
+
for i, p in enumerate(task.data.progresses, 1):
|
|
212
|
+
task_lines.append(f"{i}. {p}")
|
|
213
|
+
task_lines.append("</progress>")
|
|
214
|
+
if task.data.user_preferences:
|
|
215
|
+
task_lines.append("<user_preference>")
|
|
216
|
+
for i, pref in enumerate(task.data.user_preferences, 1):
|
|
217
|
+
task_lines.append(f"{i}. {pref}")
|
|
218
|
+
task_lines.append("</user_preference>")
|
|
219
|
+
task_lines.append("</task>")
|
|
220
|
+
parts.append("\n".join(task_lines))
|
|
221
|
+
|
|
222
|
+
return "\n".join(parts)
|
|
223
|
+
|
|
183
224
|
async def store_message(
|
|
184
225
|
self,
|
|
185
226
|
session_id: str,
|
|
@@ -294,6 +335,7 @@ class AsyncSessionsAPI:
|
|
|
294
335
|
Each strategy is a dict with 'type' and 'params' keys.
|
|
295
336
|
Examples:
|
|
296
337
|
- Remove tool results: [{"type": "remove_tool_result", "params": {"keep_recent_n_tool_results": 3}}]
|
|
338
|
+
- Middle out: [{"type": "middle_out", "params": {"token_reduce_to": 5000}}]
|
|
297
339
|
- Token limit: [{"type": "token_limit", "params": {"limit_tokens": 20000}}]
|
|
298
340
|
Defaults to None.
|
|
299
341
|
pin_editing_strategies_at_message: Message ID to pin editing strategies at.
|
|
@@ -37,26 +37,28 @@ class DisksAPI:
|
|
|
37
37
|
time_desc: bool | None = None,
|
|
38
38
|
) -> ListDisksOutput:
|
|
39
39
|
"""List all disks in the project.
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
Args:
|
|
42
42
|
user: Filter by user identifier. Defaults to None.
|
|
43
43
|
limit: Maximum number of disks to return. Defaults to None.
|
|
44
44
|
cursor: Cursor for pagination. Defaults to None.
|
|
45
45
|
time_desc: Order by created_at descending if True, ascending if False. Defaults to None.
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
Returns:
|
|
48
48
|
ListDisksOutput containing the list of disks and pagination information.
|
|
49
49
|
"""
|
|
50
|
-
params = build_params(
|
|
50
|
+
params = build_params(
|
|
51
|
+
user=user, limit=limit, cursor=cursor, time_desc=time_desc
|
|
52
|
+
)
|
|
51
53
|
data = self._requester.request("GET", "/disk", params=params or None)
|
|
52
54
|
return ListDisksOutput.model_validate(data)
|
|
53
55
|
|
|
54
56
|
def create(self, *, user: str | None = None) -> Disk:
|
|
55
57
|
"""Create a new disk.
|
|
56
|
-
|
|
58
|
+
|
|
57
59
|
Args:
|
|
58
60
|
user: Optional user identifier string. Defaults to None.
|
|
59
|
-
|
|
61
|
+
|
|
60
62
|
Returns:
|
|
61
63
|
The created Disk object.
|
|
62
64
|
"""
|
|
@@ -68,7 +70,7 @@ class DisksAPI:
|
|
|
68
70
|
|
|
69
71
|
def delete(self, disk_id: str) -> None:
|
|
70
72
|
"""Delete a disk by its ID.
|
|
71
|
-
|
|
73
|
+
|
|
72
74
|
Args:
|
|
73
75
|
disk_id: The UUID of the disk to delete.
|
|
74
76
|
"""
|
|
@@ -83,20 +85,22 @@ class DiskArtifactsAPI:
|
|
|
83
85
|
self,
|
|
84
86
|
disk_id: str,
|
|
85
87
|
*,
|
|
86
|
-
file:
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
file: (
|
|
89
|
+
FileUpload
|
|
90
|
+
| tuple[str, BinaryIO | bytes]
|
|
91
|
+
| tuple[str, BinaryIO | bytes, str]
|
|
92
|
+
),
|
|
89
93
|
file_path: str | None = None,
|
|
90
94
|
meta: Mapping[str, Any] | None = None,
|
|
91
95
|
) -> Artifact:
|
|
92
96
|
"""Upload a file to create or update an artifact.
|
|
93
|
-
|
|
97
|
+
|
|
94
98
|
Args:
|
|
95
99
|
disk_id: The UUID of the disk.
|
|
96
100
|
file: The file to upload (FileUpload object or tuple format).
|
|
97
101
|
file_path: Directory path (not including filename), defaults to "/".
|
|
98
102
|
meta: Custom metadata as JSON-serializable dict, defaults to None.
|
|
99
|
-
|
|
103
|
+
|
|
100
104
|
Returns:
|
|
101
105
|
Artifact containing the created/updated artifact information.
|
|
102
106
|
"""
|
|
@@ -126,7 +130,7 @@ class DiskArtifactsAPI:
|
|
|
126
130
|
expire: int | None = None,
|
|
127
131
|
) -> GetArtifactResp:
|
|
128
132
|
"""Get an artifact by disk ID, file path, and filename.
|
|
129
|
-
|
|
133
|
+
|
|
130
134
|
Args:
|
|
131
135
|
disk_id: The UUID of the disk.
|
|
132
136
|
file_path: Directory path (not including filename).
|
|
@@ -134,7 +138,7 @@ class DiskArtifactsAPI:
|
|
|
134
138
|
with_public_url: Whether to include a presigned public URL. Defaults to None.
|
|
135
139
|
with_content: Whether to include file content. Defaults to None.
|
|
136
140
|
expire: URL expiration time in seconds. Defaults to None.
|
|
137
|
-
|
|
141
|
+
|
|
138
142
|
Returns:
|
|
139
143
|
GetArtifactResp containing the artifact and optionally public URL and content.
|
|
140
144
|
"""
|
|
@@ -145,7 +149,9 @@ class DiskArtifactsAPI:
|
|
|
145
149
|
with_content=with_content,
|
|
146
150
|
expire=expire,
|
|
147
151
|
)
|
|
148
|
-
data = self._requester.request(
|
|
152
|
+
data = self._requester.request(
|
|
153
|
+
"GET", f"/disk/{disk_id}/artifact", params=params
|
|
154
|
+
)
|
|
149
155
|
return GetArtifactResp.model_validate(data)
|
|
150
156
|
|
|
151
157
|
def update(
|
|
@@ -157,13 +163,13 @@ class DiskArtifactsAPI:
|
|
|
157
163
|
meta: Mapping[str, Any],
|
|
158
164
|
) -> UpdateArtifactResp:
|
|
159
165
|
"""Update an artifact's metadata.
|
|
160
|
-
|
|
166
|
+
|
|
161
167
|
Args:
|
|
162
168
|
disk_id: The UUID of the disk.
|
|
163
169
|
file_path: Directory path (not including filename).
|
|
164
170
|
filename: The filename of the artifact.
|
|
165
171
|
meta: Custom metadata as JSON-serializable dict.
|
|
166
|
-
|
|
172
|
+
|
|
167
173
|
Returns:
|
|
168
174
|
UpdateArtifactResp containing the updated artifact information.
|
|
169
175
|
"""
|
|
@@ -172,7 +178,9 @@ class DiskArtifactsAPI:
|
|
|
172
178
|
"file_path": full_path,
|
|
173
179
|
"meta": json.dumps(cast(Mapping[str, Any], meta)),
|
|
174
180
|
}
|
|
175
|
-
data = self._requester.request(
|
|
181
|
+
data = self._requester.request(
|
|
182
|
+
"PUT", f"/disk/{disk_id}/artifact", json_data=payload
|
|
183
|
+
)
|
|
176
184
|
return UpdateArtifactResp.model_validate(data)
|
|
177
185
|
|
|
178
186
|
def delete(
|
|
@@ -183,7 +191,7 @@ class DiskArtifactsAPI:
|
|
|
183
191
|
filename: str,
|
|
184
192
|
) -> None:
|
|
185
193
|
"""Delete an artifact by disk ID, file path, and filename.
|
|
186
|
-
|
|
194
|
+
|
|
187
195
|
Args:
|
|
188
196
|
disk_id: The UUID of the disk.
|
|
189
197
|
file_path: Directory path (not including filename).
|
|
@@ -200,18 +208,20 @@ class DiskArtifactsAPI:
|
|
|
200
208
|
path: str | None = None,
|
|
201
209
|
) -> ListArtifactsResp:
|
|
202
210
|
"""List artifacts in a disk at a specific path.
|
|
203
|
-
|
|
211
|
+
|
|
204
212
|
Args:
|
|
205
213
|
disk_id: The UUID of the disk.
|
|
206
214
|
path: Directory path to list. Defaults to None (root).
|
|
207
|
-
|
|
215
|
+
|
|
208
216
|
Returns:
|
|
209
217
|
ListArtifactsResp containing the list of artifacts.
|
|
210
218
|
"""
|
|
211
219
|
params: dict[str, Any] = {}
|
|
212
220
|
if path is not None:
|
|
213
221
|
params["path"] = path
|
|
214
|
-
data = self._requester.request(
|
|
222
|
+
data = self._requester.request(
|
|
223
|
+
"GET", f"/disk/{disk_id}/artifact/ls", params=params or None
|
|
224
|
+
)
|
|
215
225
|
return ListArtifactsResp.model_validate(data)
|
|
216
226
|
|
|
217
227
|
def grep_artifacts(
|
|
@@ -222,15 +232,15 @@ class DiskArtifactsAPI:
|
|
|
222
232
|
limit: int = 100,
|
|
223
233
|
) -> list[Artifact]:
|
|
224
234
|
"""Search artifact content using regex pattern.
|
|
225
|
-
|
|
235
|
+
|
|
226
236
|
Args:
|
|
227
237
|
disk_id: The disk ID to search in
|
|
228
238
|
query: Regex pattern to search for in file content
|
|
229
239
|
limit: Maximum number of results (default 100, max 1000)
|
|
230
|
-
|
|
240
|
+
|
|
231
241
|
Returns:
|
|
232
242
|
List of matching artifacts
|
|
233
|
-
|
|
243
|
+
|
|
234
244
|
Example:
|
|
235
245
|
```python
|
|
236
246
|
# Search for TODO comments in code
|
|
@@ -242,9 +252,7 @@ class DiskArtifactsAPI:
|
|
|
242
252
|
"""
|
|
243
253
|
params = build_params(query=query, limit=limit)
|
|
244
254
|
data = self._requester.request(
|
|
245
|
-
"GET",
|
|
246
|
-
f"/disk/{disk_id}/artifact/grep",
|
|
247
|
-
params=params
|
|
255
|
+
"GET", f"/disk/{disk_id}/artifact/grep", params=params
|
|
248
256
|
)
|
|
249
257
|
return TypeAdapter(list[Artifact]).validate_python(data)
|
|
250
258
|
|
|
@@ -256,15 +264,15 @@ class DiskArtifactsAPI:
|
|
|
256
264
|
limit: int = 100,
|
|
257
265
|
) -> list[Artifact]:
|
|
258
266
|
"""Search artifact paths using glob pattern.
|
|
259
|
-
|
|
267
|
+
|
|
260
268
|
Args:
|
|
261
269
|
disk_id: The disk ID to search in
|
|
262
270
|
query: Glob pattern (e.g., '**/*.py', '*.txt')
|
|
263
271
|
limit: Maximum number of results (default 100, max 1000)
|
|
264
|
-
|
|
272
|
+
|
|
265
273
|
Returns:
|
|
266
274
|
List of matching artifacts
|
|
267
|
-
|
|
275
|
+
|
|
268
276
|
Example:
|
|
269
277
|
```python
|
|
270
278
|
# Find all Python files
|
|
@@ -276,8 +284,98 @@ class DiskArtifactsAPI:
|
|
|
276
284
|
"""
|
|
277
285
|
params = build_params(query=query, limit=limit)
|
|
278
286
|
data = self._requester.request(
|
|
279
|
-
"GET",
|
|
280
|
-
f"/disk/{disk_id}/artifact/glob",
|
|
281
|
-
params=params
|
|
287
|
+
"GET", f"/disk/{disk_id}/artifact/glob", params=params
|
|
282
288
|
)
|
|
283
289
|
return TypeAdapter(list[Artifact]).validate_python(data)
|
|
290
|
+
|
|
291
|
+
def download_to_sandbox(
|
|
292
|
+
self,
|
|
293
|
+
disk_id: str,
|
|
294
|
+
*,
|
|
295
|
+
file_path: str,
|
|
296
|
+
filename: str,
|
|
297
|
+
sandbox_id: str,
|
|
298
|
+
sandbox_path: str,
|
|
299
|
+
) -> bool:
|
|
300
|
+
"""Download an artifact from disk storage to a sandbox environment.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
disk_id: The UUID of the disk containing the artifact.
|
|
304
|
+
file_path: Directory path of the artifact (not including filename).
|
|
305
|
+
filename: The filename of the artifact.
|
|
306
|
+
sandbox_id: The UUID of the target sandbox.
|
|
307
|
+
sandbox_path: Destination directory in the sandbox.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
True if the download was successful.
|
|
311
|
+
|
|
312
|
+
Example:
|
|
313
|
+
```python
|
|
314
|
+
success = client.disks.artifacts.download_to_sandbox(
|
|
315
|
+
disk_id="disk-uuid",
|
|
316
|
+
file_path="/documents/",
|
|
317
|
+
filename="report.pdf",
|
|
318
|
+
sandbox_id="sandbox-uuid",
|
|
319
|
+
sandbox_path="/home/user/"
|
|
320
|
+
)
|
|
321
|
+
print(f"Success: {success}")
|
|
322
|
+
```
|
|
323
|
+
"""
|
|
324
|
+
payload = {
|
|
325
|
+
"file_path": file_path,
|
|
326
|
+
"filename": filename,
|
|
327
|
+
"sandbox_id": sandbox_id,
|
|
328
|
+
"sandbox_path": sandbox_path,
|
|
329
|
+
}
|
|
330
|
+
data = self._requester.request(
|
|
331
|
+
"POST",
|
|
332
|
+
f"/disk/{disk_id}/artifact/download_to_sandbox",
|
|
333
|
+
json_data=payload,
|
|
334
|
+
)
|
|
335
|
+
return bool(data.get("success", False))
|
|
336
|
+
|
|
337
|
+
def upload_from_sandbox(
|
|
338
|
+
self,
|
|
339
|
+
disk_id: str,
|
|
340
|
+
*,
|
|
341
|
+
sandbox_id: str,
|
|
342
|
+
sandbox_path: str,
|
|
343
|
+
sandbox_filename: str,
|
|
344
|
+
file_path: str,
|
|
345
|
+
) -> Artifact:
|
|
346
|
+
"""Upload a file from a sandbox environment to disk storage as an artifact.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
disk_id: The UUID of the target disk.
|
|
350
|
+
sandbox_id: The UUID of the source sandbox.
|
|
351
|
+
sandbox_path: Source directory in the sandbox (not including filename).
|
|
352
|
+
sandbox_filename: Filename in the sandbox.
|
|
353
|
+
file_path: Destination directory path on the disk.
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
Artifact containing the created artifact information.
|
|
357
|
+
|
|
358
|
+
Example:
|
|
359
|
+
```python
|
|
360
|
+
artifact = client.disks.artifacts.upload_from_sandbox(
|
|
361
|
+
disk_id="disk-uuid",
|
|
362
|
+
sandbox_id="sandbox-uuid",
|
|
363
|
+
sandbox_path="/home/user/",
|
|
364
|
+
sandbox_filename="output.txt",
|
|
365
|
+
file_path="/results/"
|
|
366
|
+
)
|
|
367
|
+
print(f"Created: {artifact.path}{artifact.filename}")
|
|
368
|
+
```
|
|
369
|
+
"""
|
|
370
|
+
payload = {
|
|
371
|
+
"sandbox_id": sandbox_id,
|
|
372
|
+
"sandbox_path": sandbox_path,
|
|
373
|
+
"sandbox_filename": sandbox_filename,
|
|
374
|
+
"file_path": file_path,
|
|
375
|
+
}
|
|
376
|
+
data = self._requester.request(
|
|
377
|
+
"POST",
|
|
378
|
+
f"/disk/{disk_id}/artifact/upload_from_sandbox",
|
|
379
|
+
json_data=payload,
|
|
380
|
+
)
|
|
381
|
+
return Artifact.model_validate(data)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sandboxes endpoints.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from ..client_types import RequesterProtocol
|
|
6
|
+
from ..types.sandbox import (
|
|
7
|
+
SandboxCommandOutput,
|
|
8
|
+
SandboxRuntimeInfo,
|
|
9
|
+
)
|
|
10
|
+
from ..types.tool import FlagResponse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SandboxesAPI:
|
|
14
|
+
def __init__(self, requester: RequesterProtocol) -> None:
|
|
15
|
+
self._requester = requester
|
|
16
|
+
|
|
17
|
+
def create(self) -> SandboxRuntimeInfo:
|
|
18
|
+
"""Create and start a new sandbox.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
SandboxRuntimeInfo containing the sandbox ID, status, and timestamps.
|
|
22
|
+
"""
|
|
23
|
+
data = self._requester.request("POST", "/sandbox")
|
|
24
|
+
return SandboxRuntimeInfo.model_validate(data)
|
|
25
|
+
|
|
26
|
+
def exec_command(
|
|
27
|
+
self,
|
|
28
|
+
*,
|
|
29
|
+
sandbox_id: str,
|
|
30
|
+
command: str,
|
|
31
|
+
) -> SandboxCommandOutput:
|
|
32
|
+
"""Execute a shell command in the sandbox.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
sandbox_id: The UUID of the sandbox.
|
|
36
|
+
command: The shell command to execute.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
SandboxCommandOutput containing stdout, stderr, and exit code.
|
|
40
|
+
"""
|
|
41
|
+
data = self._requester.request(
|
|
42
|
+
"POST",
|
|
43
|
+
f"/sandbox/{sandbox_id}/exec",
|
|
44
|
+
json_data={"command": command},
|
|
45
|
+
)
|
|
46
|
+
return SandboxCommandOutput.model_validate(data)
|
|
47
|
+
|
|
48
|
+
def kill(self, sandbox_id: str) -> FlagResponse:
|
|
49
|
+
"""Kill a running sandbox.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
sandbox_id: The UUID of the sandbox to kill.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
FlagResponse with status and error message.
|
|
56
|
+
"""
|
|
57
|
+
data = self._requester.request("DELETE", f"/sandbox/{sandbox_id}")
|
|
58
|
+
return FlagResponse.model_validate(data)
|
|
@@ -180,6 +180,47 @@ class SessionsAPI:
|
|
|
180
180
|
)
|
|
181
181
|
return GetTasksOutput.model_validate(data)
|
|
182
182
|
|
|
183
|
+
def get_session_summary(
|
|
184
|
+
self,
|
|
185
|
+
session_id: str,
|
|
186
|
+
*,
|
|
187
|
+
limit: int | None = None,
|
|
188
|
+
) -> str:
|
|
189
|
+
"""Get a summary of all tasks in a session as a formatted string.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
session_id: The UUID of the session.
|
|
193
|
+
limit: Maximum number of tasks to include in the summary. Defaults to None (all tasks).
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
A formatted string containing the session summary with all task information.
|
|
197
|
+
"""
|
|
198
|
+
tasks_output = self.get_tasks(session_id, limit=limit, time_desc=False)
|
|
199
|
+
tasks = tasks_output.items
|
|
200
|
+
|
|
201
|
+
if not tasks:
|
|
202
|
+
return ""
|
|
203
|
+
|
|
204
|
+
parts: list[str] = []
|
|
205
|
+
for task in tasks:
|
|
206
|
+
task_lines = [
|
|
207
|
+
f'<task id="{task.order}" description="{task.data.task_description}">'
|
|
208
|
+
]
|
|
209
|
+
if task.data.progresses:
|
|
210
|
+
task_lines.append("<progress>")
|
|
211
|
+
for i, p in enumerate(task.data.progresses, 1):
|
|
212
|
+
task_lines.append(f"{i}. {p}")
|
|
213
|
+
task_lines.append("</progress>")
|
|
214
|
+
if task.data.user_preferences:
|
|
215
|
+
task_lines.append("<user_preference>")
|
|
216
|
+
for i, pref in enumerate(task.data.user_preferences, 1):
|
|
217
|
+
task_lines.append(f"{i}. {pref}")
|
|
218
|
+
task_lines.append("</user_preference>")
|
|
219
|
+
task_lines.append("</task>")
|
|
220
|
+
parts.append("\n".join(task_lines))
|
|
221
|
+
|
|
222
|
+
return "\n".join(parts)
|
|
223
|
+
|
|
183
224
|
def store_message(
|
|
184
225
|
self,
|
|
185
226
|
session_id: str,
|
|
@@ -294,6 +335,7 @@ class SessionsAPI:
|
|
|
294
335
|
Each strategy is a dict with 'type' and 'params' keys.
|
|
295
336
|
Examples:
|
|
296
337
|
- Remove tool results: [{"type": "remove_tool_result", "params": {"keep_recent_n_tool_results": 3}}]
|
|
338
|
+
- Middle out: [{"type": "middle_out", "params": {"token_reduce_to": 5000}}]
|
|
297
339
|
- Token limit: [{"type": "token_limit", "params": {"limit_tokens": 20000}}]
|
|
298
340
|
Defaults to None.
|
|
299
341
|
pin_editing_strategies_at_message: Message ID to pin editing strategies at.
|
|
@@ -320,7 +362,9 @@ class SessionsAPI:
|
|
|
320
362
|
if edit_strategies is not None:
|
|
321
363
|
params["edit_strategies"] = json.dumps(edit_strategies)
|
|
322
364
|
if pin_editing_strategies_at_message is not None:
|
|
323
|
-
params["pin_editing_strategies_at_message"] =
|
|
365
|
+
params["pin_editing_strategies_at_message"] = (
|
|
366
|
+
pin_editing_strategies_at_message
|
|
367
|
+
)
|
|
324
368
|
data = self._requester.request(
|
|
325
369
|
"GET", f"/session/{session_id}/messages", params=params or None
|
|
326
370
|
)
|
|
@@ -366,20 +410,19 @@ class SessionsAPI:
|
|
|
366
410
|
"""
|
|
367
411
|
data = self._requester.request("GET", f"/session/{session_id}/token_counts")
|
|
368
412
|
return TokenCounts.model_validate(data)
|
|
413
|
+
|
|
369
414
|
def messages_observing_status(self, session_id: str) -> MessageObservingStatus:
|
|
370
415
|
"""Get message observing status counts for a session.
|
|
371
|
-
|
|
416
|
+
|
|
372
417
|
Returns the count of messages by their observing status:
|
|
373
418
|
observed, in_process, and pending.
|
|
374
|
-
|
|
419
|
+
|
|
375
420
|
Args:
|
|
376
421
|
session_id: The UUID of the session.
|
|
377
|
-
|
|
422
|
+
|
|
378
423
|
Returns:
|
|
379
|
-
MessageObservingStatus object containing observed, in_process,
|
|
424
|
+
MessageObservingStatus object containing observed, in_process,
|
|
380
425
|
pending counts and updated_at timestamp.
|
|
381
426
|
"""
|
|
382
|
-
data = self._requester.request(
|
|
383
|
-
"GET", f"/session/{session_id}/observing_status"
|
|
384
|
-
)
|
|
427
|
+
data = self._requester.request("GET", f"/session/{session_id}/observing_status")
|
|
385
428
|
return MessageObservingStatus.model_validate(data)
|
|
@@ -45,6 +45,10 @@ from .skill import (
|
|
|
45
45
|
Skill,
|
|
46
46
|
SkillCatalogItem,
|
|
47
47
|
)
|
|
48
|
+
from .sandbox import (
|
|
49
|
+
SandboxCommandOutput,
|
|
50
|
+
SandboxRuntimeInfo,
|
|
51
|
+
)
|
|
48
52
|
from .user import (
|
|
49
53
|
GetUserResourcesOutput,
|
|
50
54
|
ListUsersOutput,
|
|
@@ -94,6 +98,9 @@ __all__ = [
|
|
|
94
98
|
"SkillCatalogItem",
|
|
95
99
|
"ListSkillsOutput",
|
|
96
100
|
"GetSkillFileResp",
|
|
101
|
+
# Sandbox types
|
|
102
|
+
"SandboxCommandOutput",
|
|
103
|
+
"SandboxRuntimeInfo",
|
|
97
104
|
# User types
|
|
98
105
|
"GetUserResourcesOutput",
|
|
99
106
|
"ListUsersOutput",
|
|
@@ -43,8 +43,12 @@ class GetArtifactResp(BaseModel):
|
|
|
43
43
|
"""Response model for getting an artifact."""
|
|
44
44
|
|
|
45
45
|
artifact: Artifact = Field(..., description="Artifact information")
|
|
46
|
-
public_url: str | None = Field(
|
|
47
|
-
|
|
46
|
+
public_url: str | None = Field(
|
|
47
|
+
None, description="Presigned URL for downloading the artifact"
|
|
48
|
+
)
|
|
49
|
+
content: FileContent | None = Field(
|
|
50
|
+
None, description="Parsed file content if available"
|
|
51
|
+
)
|
|
48
52
|
|
|
49
53
|
|
|
50
54
|
class ListArtifactsResp(BaseModel):
|
|
@@ -58,4 +62,3 @@ class UpdateArtifactResp(BaseModel):
|
|
|
58
62
|
"""Response model for updating an artifact."""
|
|
59
63
|
|
|
60
64
|
artifact: Artifact = Field(..., description="Updated artifact information")
|
|
61
|
-
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Type definitions for sandbox resources."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SandboxRuntimeInfo(BaseModel):
|
|
7
|
+
"""Runtime information about a sandbox."""
|
|
8
|
+
|
|
9
|
+
sandbox_id: str = Field(..., description="Sandbox ID")
|
|
10
|
+
sandbox_status: str = Field(..., description="Sandbox status (running, killed, paused, error)")
|
|
11
|
+
sandbox_created_at: str = Field(..., description="ISO 8601 formatted creation timestamp")
|
|
12
|
+
sandbox_expires_at: str = Field(..., description="ISO 8601 formatted expiration timestamp")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SandboxCommandOutput(BaseModel):
|
|
16
|
+
"""Output from executing a command in a sandbox."""
|
|
17
|
+
|
|
18
|
+
stdout: str = Field(..., description="Standard output from the command")
|
|
19
|
+
stderr: str = Field(..., description="Standard error from the command")
|
|
20
|
+
exit_code: int = Field(..., description="Exit code of the command")
|
|
@@ -89,10 +89,34 @@ class TokenLimitStrategy(TypedDict):
|
|
|
89
89
|
params: TokenLimitParams
|
|
90
90
|
|
|
91
91
|
|
|
92
|
+
class MiddleOutParams(TypedDict):
|
|
93
|
+
"""Parameters for the middle_out edit strategy.
|
|
94
|
+
|
|
95
|
+
Attributes:
|
|
96
|
+
token_reduce_to: Target token count to reduce the prompt to. Required parameter.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
token_reduce_to: int
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class MiddleOutStrategy(TypedDict):
|
|
103
|
+
"""Edit strategy to reduce prompt size by removing middle messages.
|
|
104
|
+
|
|
105
|
+
Example:
|
|
106
|
+
{"type": "middle_out", "params": {"token_reduce_to": 5000}}
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
type: Literal["middle_out"]
|
|
110
|
+
params: MiddleOutParams
|
|
111
|
+
|
|
112
|
+
|
|
92
113
|
# Union type for all edit strategies
|
|
93
114
|
# When adding new strategies, add them to this Union: EditStrategy = Union[RemoveToolResultStrategy, OtherStrategy, ...]
|
|
94
115
|
EditStrategy = Union[
|
|
95
|
-
RemoveToolResultStrategy,
|
|
116
|
+
RemoveToolResultStrategy,
|
|
117
|
+
RemoveToolCallParamsStrategy,
|
|
118
|
+
TokenLimitStrategy,
|
|
119
|
+
MiddleOutStrategy,
|
|
96
120
|
]
|
|
97
121
|
|
|
98
122
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|