acontext 0.0.13__tar.gz → 0.0.16__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.0.13 → acontext-0.0.16}/PKG-INFO +1 -1
- {acontext-0.0.13 → acontext-0.0.16}/pyproject.toml +1 -1
- acontext-0.0.16/src/acontext/agent/__init__.py +10 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/agent/disk.py +71 -8
- acontext-0.0.16/src/acontext/agent/skill.py +148 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/async_client.py +4 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/client.py +4 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/resources/__init__.py +8 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/resources/async_disks.py +11 -3
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/resources/async_sessions.py +36 -16
- acontext-0.0.16/src/acontext/resources/async_skills.py +150 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/resources/async_spaces.py +12 -2
- acontext-0.0.16/src/acontext/resources/async_users.py +20 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/resources/disks.py +11 -3
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/resources/sessions.py +17 -0
- acontext-0.0.16/src/acontext/resources/skills.py +146 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/resources/spaces.py +12 -2
- acontext-0.0.16/src/acontext/resources/users.py +20 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/types/__init__.py +14 -1
- acontext-0.0.16/src/acontext/types/common.py +11 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/types/disk.py +3 -7
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/types/session.py +31 -15
- acontext-0.0.16/src/acontext/types/skill.py +69 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/types/space.py +1 -0
- acontext-0.0.13/src/acontext/agent/__init__.py +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/README.md +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/__init__.py +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/_constants.py +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/_utils.py +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/agent/base.py +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/client_types.py +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/errors.py +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/messages.py +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/py.typed +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/resources/async_blocks.py +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/resources/async_tools.py +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/resources/blocks.py +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/resources/tools.py +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/types/block.py +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/types/tool.py +0 -0
- {acontext-0.0.13 → acontext-0.0.16}/src/acontext/uploads.py +0 -0
|
@@ -261,15 +261,71 @@ class ListTool(BaseTool):
|
|
|
261
261
|
if not artifacts_list and not result.directories:
|
|
262
262
|
return f"No files or directories found in '{normalized_path}'"
|
|
263
263
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
output_parts.append(f"Directories: {', '.join(result.directories)}")
|
|
269
|
-
|
|
270
|
-
ls_sect = "\n".join(output_parts)
|
|
264
|
+
file_sect = "\n".join(artifacts_list) or "[NO FILE]"
|
|
265
|
+
dir_sect = (
|
|
266
|
+
"\n".join([d.rstrip("/") + "/" for d in result.directories]) or "[NO DIR]"
|
|
267
|
+
)
|
|
271
268
|
return f"""[Listing in {normalized_path}]
|
|
272
|
-
|
|
269
|
+
Directories:
|
|
270
|
+
{dir_sect}
|
|
271
|
+
Files:
|
|
272
|
+
{file_sect}"""
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class DownloadFileTool(BaseTool):
|
|
276
|
+
"""Tool for getting a public download URL for a file on the Acontext disk."""
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def name(self) -> str:
|
|
280
|
+
return "download_file"
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def description(self) -> str:
|
|
284
|
+
return "Get a public URL to download a file. Returns a presigned URL that can be shared or used to access the file."
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def arguments(self) -> dict:
|
|
288
|
+
return {
|
|
289
|
+
"file_path": {
|
|
290
|
+
"type": "string",
|
|
291
|
+
"description": "Optional directory path where the file is located, e.g. '/notes/'. Defaults to root '/' if not specified.",
|
|
292
|
+
},
|
|
293
|
+
"filename": {
|
|
294
|
+
"type": "string",
|
|
295
|
+
"description": "Filename to get the download URL for.",
|
|
296
|
+
},
|
|
297
|
+
"expire": {
|
|
298
|
+
"type": "integer",
|
|
299
|
+
"description": "URL expiration time in seconds. Defaults to 3600 (1 hour).",
|
|
300
|
+
},
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def required_arguments(self) -> list[str]:
|
|
305
|
+
return ["filename"]
|
|
306
|
+
|
|
307
|
+
def execute(self, ctx: DiskContext, llm_arguments: dict) -> str:
|
|
308
|
+
"""Get a public download URL for a file."""
|
|
309
|
+
filename = llm_arguments.get("filename")
|
|
310
|
+
file_path = llm_arguments.get("file_path")
|
|
311
|
+
expire = llm_arguments.get("expire", 3600)
|
|
312
|
+
|
|
313
|
+
if not filename:
|
|
314
|
+
raise ValueError("filename is required")
|
|
315
|
+
|
|
316
|
+
normalized_path = _normalize_path(file_path)
|
|
317
|
+
result = ctx.client.disks.artifacts.get(
|
|
318
|
+
ctx.disk_id,
|
|
319
|
+
file_path=normalized_path,
|
|
320
|
+
filename=filename,
|
|
321
|
+
with_public_url=True,
|
|
322
|
+
expire=expire,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
if not result.public_url:
|
|
326
|
+
raise RuntimeError("Failed to get public URL: server did not return a URL.")
|
|
327
|
+
|
|
328
|
+
return f"Public download URL for '{normalized_path}{filename}' (expires in {expire}s):\n{result.public_url}"
|
|
273
329
|
|
|
274
330
|
|
|
275
331
|
class DiskToolPool(BaseToolPool):
|
|
@@ -284,6 +340,7 @@ DISK_TOOLS.add_tool(WriteFileTool())
|
|
|
284
340
|
DISK_TOOLS.add_tool(ReadFileTool())
|
|
285
341
|
DISK_TOOLS.add_tool(ReplaceStringTool())
|
|
286
342
|
DISK_TOOLS.add_tool(ListTool())
|
|
343
|
+
DISK_TOOLS.add_tool(DownloadFileTool())
|
|
287
344
|
|
|
288
345
|
|
|
289
346
|
if __name__ == "__main__":
|
|
@@ -323,3 +380,9 @@ if __name__ == "__main__":
|
|
|
323
380
|
ctx, "read_file", {"filename": "test.txt", "file_path": "/try/"}
|
|
324
381
|
)
|
|
325
382
|
print(r)
|
|
383
|
+
r = DISK_TOOLS.execute_tool(
|
|
384
|
+
ctx,
|
|
385
|
+
"download_file",
|
|
386
|
+
{"filename": "test.txt", "file_path": "/try/", "expire": 300},
|
|
387
|
+
)
|
|
388
|
+
print(r)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Skill tools for agent operations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from .base import BaseContext, BaseTool, BaseToolPool
|
|
8
|
+
from ..client import AcontextClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class SkillContext(BaseContext):
|
|
13
|
+
client: AcontextClient
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GetSkillTool(BaseTool):
|
|
17
|
+
"""Tool for getting a skill by name."""
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def name(self) -> str:
|
|
21
|
+
return "get_skill"
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def description(self) -> str:
|
|
25
|
+
return (
|
|
26
|
+
"Get a skill by its name. Return the skill information including the relative paths of the files and their mime type categories"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def arguments(self) -> dict:
|
|
31
|
+
return {
|
|
32
|
+
"name": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "The name of the skill (unique within project).",
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def required_arguments(self) -> list[str]:
|
|
40
|
+
return ["name"]
|
|
41
|
+
|
|
42
|
+
def execute(self, ctx: SkillContext, llm_arguments: dict) -> str:
|
|
43
|
+
"""Get a skill by name."""
|
|
44
|
+
name = llm_arguments.get("name")
|
|
45
|
+
|
|
46
|
+
if not name:
|
|
47
|
+
raise ValueError("name is required")
|
|
48
|
+
|
|
49
|
+
skill = ctx.client.skills.get_by_name(name)
|
|
50
|
+
|
|
51
|
+
file_count = len(skill.file_index)
|
|
52
|
+
|
|
53
|
+
# Format all files with path and MIME type
|
|
54
|
+
if skill.file_index:
|
|
55
|
+
file_list = "\n".join(
|
|
56
|
+
[f" - {file_info.path} ({file_info.mime})" for file_info in skill.file_index]
|
|
57
|
+
)
|
|
58
|
+
else:
|
|
59
|
+
file_list = " [NO FILES]"
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
f"Skill: {skill.name} (ID: {skill.id})\n"
|
|
63
|
+
f"Description: {skill.description}\n"
|
|
64
|
+
f"Files: {file_count} file(s)\n"
|
|
65
|
+
f"{file_list}\n"
|
|
66
|
+
f"Created: {skill.created_at}\n"
|
|
67
|
+
f"Updated: {skill.updated_at}"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class GetSkillFileTool(BaseTool):
|
|
72
|
+
"""Tool for getting a file from a skill."""
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def name(self) -> str:
|
|
76
|
+
return "get_skill_file"
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def description(self) -> str:
|
|
80
|
+
return (
|
|
81
|
+
"Get a file from a skill by name. The file_path should be a relative path within the skill (e.g., 'scripts/extract_text.json'). "
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def arguments(self) -> dict:
|
|
86
|
+
return {
|
|
87
|
+
"skill_name": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"description": "The name of the skill.",
|
|
90
|
+
},
|
|
91
|
+
"file_path": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"description": "Relative path to the file within the skill (e.g., 'scripts/extract_text.json').",
|
|
94
|
+
},
|
|
95
|
+
"expire": {
|
|
96
|
+
"type": "integer",
|
|
97
|
+
"description": "URL expiration time in seconds (only used for non-parseable files). Defaults to 900 (15 minutes).",
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def required_arguments(self) -> list[str]:
|
|
103
|
+
return ["skill_name", "file_path"]
|
|
104
|
+
|
|
105
|
+
def execute(self, ctx: SkillContext, llm_arguments: dict) -> str:
|
|
106
|
+
"""Get a skill file."""
|
|
107
|
+
skill_name = llm_arguments.get("skill_name")
|
|
108
|
+
file_path = llm_arguments.get("file_path")
|
|
109
|
+
expire = llm_arguments.get("expire")
|
|
110
|
+
|
|
111
|
+
if not file_path:
|
|
112
|
+
raise ValueError("file_path is required")
|
|
113
|
+
if not skill_name:
|
|
114
|
+
raise ValueError("skill_name is required")
|
|
115
|
+
|
|
116
|
+
result = ctx.client.skills.get_file_by_name(
|
|
117
|
+
skill_name=skill_name,
|
|
118
|
+
file_path=file_path,
|
|
119
|
+
expire=expire,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
output_parts = [f"File '{result.path}' (MIME: {result.mime}) from skill '{skill_name}':"]
|
|
123
|
+
|
|
124
|
+
if result.content:
|
|
125
|
+
output_parts.append(f"\nContent (type: {result.content.type}):")
|
|
126
|
+
output_parts.append(result.content.raw)
|
|
127
|
+
|
|
128
|
+
if result.url:
|
|
129
|
+
expire_seconds = expire if expire is not None else 900
|
|
130
|
+
output_parts.append(f"\nDownload URL (expires in {expire_seconds} seconds):")
|
|
131
|
+
output_parts.append(result.url)
|
|
132
|
+
|
|
133
|
+
if not result.content and not result.url:
|
|
134
|
+
return f"File '{result.path}' retrieved but no content or URL returned."
|
|
135
|
+
|
|
136
|
+
return "\n".join(output_parts)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class SkillToolPool(BaseToolPool):
|
|
140
|
+
"""Tool pool for skill operations on Acontext skills."""
|
|
141
|
+
|
|
142
|
+
def format_context(self, client: AcontextClient) -> SkillContext:
|
|
143
|
+
return SkillContext(client=client)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
SKILL_TOOLS = SkillToolPool()
|
|
147
|
+
SKILL_TOOLS.add_tool(GetSkillTool())
|
|
148
|
+
SKILL_TOOLS.add_tool(GetSkillFileTool())
|
|
@@ -17,6 +17,8 @@ from .resources.async_blocks import AsyncBlocksAPI as AsyncBlocksAPI
|
|
|
17
17
|
from .resources.async_sessions import AsyncSessionsAPI as AsyncSessionsAPI
|
|
18
18
|
from .resources.async_spaces import AsyncSpacesAPI as AsyncSpacesAPI
|
|
19
19
|
from .resources.async_tools import AsyncToolsAPI as AsyncToolsAPI
|
|
20
|
+
from .resources.async_skills import AsyncSkillsAPI as AsyncSkillsAPI
|
|
21
|
+
from .resources.async_users import AsyncUsersAPI as AsyncUsersAPI
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
class AcontextAsyncClient:
|
|
@@ -109,6 +111,8 @@ class AcontextAsyncClient:
|
|
|
109
111
|
self.artifacts = self.disks.artifacts
|
|
110
112
|
self.blocks = AsyncBlocksAPI(self)
|
|
111
113
|
self.tools = AsyncToolsAPI(self)
|
|
114
|
+
self.skills = AsyncSkillsAPI(self)
|
|
115
|
+
self.users = AsyncUsersAPI(self)
|
|
112
116
|
|
|
113
117
|
@property
|
|
114
118
|
def base_url(self) -> str:
|
|
@@ -17,6 +17,8 @@ from .resources.blocks import BlocksAPI as BlocksAPI
|
|
|
17
17
|
from .resources.sessions import SessionsAPI as SessionsAPI
|
|
18
18
|
from .resources.spaces import SpacesAPI as SpacesAPI
|
|
19
19
|
from .resources.tools import ToolsAPI as ToolsAPI
|
|
20
|
+
from .resources.skills import SkillsAPI as SkillsAPI
|
|
21
|
+
from .resources.users import UsersAPI as UsersAPI
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
class AcontextClient:
|
|
@@ -109,6 +111,8 @@ class AcontextClient:
|
|
|
109
111
|
self.artifacts = self.disks.artifacts
|
|
110
112
|
self.blocks = BlocksAPI(self)
|
|
111
113
|
self.tools = ToolsAPI(self)
|
|
114
|
+
self.skills = SkillsAPI(self)
|
|
115
|
+
self.users = UsersAPI(self)
|
|
112
116
|
|
|
113
117
|
@property
|
|
114
118
|
def base_url(self) -> str:
|
|
@@ -5,11 +5,15 @@ from .async_disks import AsyncDisksAPI, AsyncDiskArtifactsAPI
|
|
|
5
5
|
from .async_sessions import AsyncSessionsAPI
|
|
6
6
|
from .async_spaces import AsyncSpacesAPI
|
|
7
7
|
from .async_tools import AsyncToolsAPI
|
|
8
|
+
from .async_skills import AsyncSkillsAPI
|
|
9
|
+
from .async_users import AsyncUsersAPI
|
|
8
10
|
from .blocks import BlocksAPI
|
|
9
11
|
from .disks import DisksAPI, DiskArtifactsAPI
|
|
10
12
|
from .sessions import SessionsAPI
|
|
11
13
|
from .spaces import SpacesAPI
|
|
12
14
|
from .tools import ToolsAPI
|
|
15
|
+
from .skills import SkillsAPI
|
|
16
|
+
from .users import UsersAPI
|
|
13
17
|
|
|
14
18
|
__all__ = [
|
|
15
19
|
"DisksAPI",
|
|
@@ -18,10 +22,14 @@ __all__ = [
|
|
|
18
22
|
"SessionsAPI",
|
|
19
23
|
"SpacesAPI",
|
|
20
24
|
"ToolsAPI",
|
|
25
|
+
"SkillsAPI",
|
|
26
|
+
"UsersAPI",
|
|
21
27
|
"AsyncDisksAPI",
|
|
22
28
|
"AsyncDiskArtifactsAPI",
|
|
23
29
|
"AsyncBlocksAPI",
|
|
24
30
|
"AsyncSessionsAPI",
|
|
25
31
|
"AsyncSpacesAPI",
|
|
26
32
|
"AsyncToolsAPI",
|
|
33
|
+
"AsyncSkillsAPI",
|
|
34
|
+
"AsyncUsersAPI",
|
|
27
35
|
]
|
|
@@ -27,6 +27,7 @@ class AsyncDisksAPI:
|
|
|
27
27
|
async def list(
|
|
28
28
|
self,
|
|
29
29
|
*,
|
|
30
|
+
user: str | None = None,
|
|
30
31
|
limit: int | None = None,
|
|
31
32
|
cursor: str | None = None,
|
|
32
33
|
time_desc: bool | None = None,
|
|
@@ -34,6 +35,7 @@ class AsyncDisksAPI:
|
|
|
34
35
|
"""List all disks in the project.
|
|
35
36
|
|
|
36
37
|
Args:
|
|
38
|
+
user: Filter by user identifier. Defaults to None.
|
|
37
39
|
limit: Maximum number of disks to return. Defaults to None.
|
|
38
40
|
cursor: Cursor for pagination. Defaults to None.
|
|
39
41
|
time_desc: Order by created_at descending if True, ascending if False. Defaults to None.
|
|
@@ -41,17 +43,23 @@ class AsyncDisksAPI:
|
|
|
41
43
|
Returns:
|
|
42
44
|
ListDisksOutput containing the list of disks and pagination information.
|
|
43
45
|
"""
|
|
44
|
-
params = build_params(limit=limit, cursor=cursor, time_desc=time_desc)
|
|
46
|
+
params = build_params(user=user, limit=limit, cursor=cursor, time_desc=time_desc)
|
|
45
47
|
data = await self._requester.request("GET", "/disk", params=params or None)
|
|
46
48
|
return ListDisksOutput.model_validate(data)
|
|
47
49
|
|
|
48
|
-
async def create(self) -> Disk:
|
|
50
|
+
async def create(self, *, user: str | None = None) -> Disk:
|
|
49
51
|
"""Create a new disk.
|
|
50
52
|
|
|
53
|
+
Args:
|
|
54
|
+
user: Optional user identifier string. Defaults to None.
|
|
55
|
+
|
|
51
56
|
Returns:
|
|
52
57
|
The created Disk object.
|
|
53
58
|
"""
|
|
54
|
-
|
|
59
|
+
payload: dict[str, Any] = {}
|
|
60
|
+
if user is not None:
|
|
61
|
+
payload["user"] = user
|
|
62
|
+
data = await self._requester.request("POST", "/disk", json_data=payload or None)
|
|
55
63
|
return Disk.model_validate(data)
|
|
56
64
|
|
|
57
65
|
async def delete(self, disk_id: str) -> None:
|
|
@@ -37,6 +37,7 @@ class AsyncSessionsAPI:
|
|
|
37
37
|
async def list(
|
|
38
38
|
self,
|
|
39
39
|
*,
|
|
40
|
+
user: str | None = None,
|
|
40
41
|
space_id: str | None = None,
|
|
41
42
|
not_connected: bool | None = None,
|
|
42
43
|
limit: int | None = None,
|
|
@@ -46,6 +47,7 @@ class AsyncSessionsAPI:
|
|
|
46
47
|
"""List all sessions in the project.
|
|
47
48
|
|
|
48
49
|
Args:
|
|
50
|
+
user: Filter by user identifier. Defaults to None.
|
|
49
51
|
space_id: Filter sessions by space ID. Defaults to None.
|
|
50
52
|
not_connected: Filter sessions that are not connected to a space. Defaults to None.
|
|
51
53
|
limit: Maximum number of sessions to return. Defaults to None.
|
|
@@ -56,6 +58,8 @@ class AsyncSessionsAPI:
|
|
|
56
58
|
ListSessionsOutput containing the list of sessions and pagination information.
|
|
57
59
|
"""
|
|
58
60
|
params: dict[str, Any] = {}
|
|
61
|
+
if user:
|
|
62
|
+
params["user"] = user
|
|
59
63
|
if space_id:
|
|
60
64
|
params["space_id"] = space_id
|
|
61
65
|
params.update(
|
|
@@ -72,12 +76,14 @@ class AsyncSessionsAPI:
|
|
|
72
76
|
async def create(
|
|
73
77
|
self,
|
|
74
78
|
*,
|
|
79
|
+
user: str | None = None,
|
|
75
80
|
space_id: str | None = None,
|
|
76
81
|
configs: Mapping[str, Any] | None = None,
|
|
77
82
|
) -> Session:
|
|
78
83
|
"""Create a new session.
|
|
79
84
|
|
|
80
85
|
Args:
|
|
86
|
+
user: Optional user identifier string. Defaults to None.
|
|
81
87
|
space_id: Optional space ID to associate with the session. Defaults to None.
|
|
82
88
|
configs: Optional session configuration dictionary. Defaults to None.
|
|
83
89
|
|
|
@@ -85,6 +91,8 @@ class AsyncSessionsAPI:
|
|
|
85
91
|
The created Session object.
|
|
86
92
|
"""
|
|
87
93
|
payload: dict[str, Any] = {}
|
|
94
|
+
if user:
|
|
95
|
+
payload["user"] = user
|
|
88
96
|
if space_id:
|
|
89
97
|
payload["space_id"] = space_id
|
|
90
98
|
if configs is not None:
|
|
@@ -267,6 +275,7 @@ class AsyncSessionsAPI:
|
|
|
267
275
|
format: Literal["acontext", "openai", "anthropic", "gemini"] = "openai",
|
|
268
276
|
time_desc: bool | None = None,
|
|
269
277
|
edit_strategies: Optional[List[EditStrategy]] = None,
|
|
278
|
+
pin_editing_strategies_at_message: str | None = None,
|
|
270
279
|
) -> GetMessagesOutput:
|
|
271
280
|
"""Get messages for a session.
|
|
272
281
|
|
|
@@ -283,6 +292,12 @@ class AsyncSessionsAPI:
|
|
|
283
292
|
- Remove tool results: [{"type": "remove_tool_result", "params": {"keep_recent_n_tool_results": 3}}]
|
|
284
293
|
- Token limit: [{"type": "token_limit", "params": {"limit_tokens": 20000}}]
|
|
285
294
|
Defaults to None.
|
|
295
|
+
pin_editing_strategies_at_message: Message ID to pin editing strategies at.
|
|
296
|
+
When provided, strategies are only applied to messages up to and including
|
|
297
|
+
this message ID, keeping subsequent messages unchanged. This helps maintain
|
|
298
|
+
prompt cache stability by preserving a stable prefix. The response includes
|
|
299
|
+
edit_at_message_id indicating where strategies were applied. Pass this value
|
|
300
|
+
in subsequent requests to maintain cache hits. Defaults to None.
|
|
286
301
|
|
|
287
302
|
Returns:
|
|
288
303
|
GetMessagesOutput containing the list of messages and pagination information.
|
|
@@ -300,6 +315,10 @@ class AsyncSessionsAPI:
|
|
|
300
315
|
)
|
|
301
316
|
if edit_strategies is not None:
|
|
302
317
|
params["edit_strategies"] = json.dumps(edit_strategies)
|
|
318
|
+
if pin_editing_strategies_at_message is not None:
|
|
319
|
+
params["pin_editing_strategies_at_message"] = (
|
|
320
|
+
pin_editing_strategies_at_message
|
|
321
|
+
)
|
|
303
322
|
data = await self._requester.request(
|
|
304
323
|
"GET", f"/session/{session_id}/messages", params=params or None
|
|
305
324
|
)
|
|
@@ -348,20 +367,21 @@ class AsyncSessionsAPI:
|
|
|
348
367
|
)
|
|
349
368
|
return TokenCounts.model_validate(data)
|
|
350
369
|
|
|
370
|
+
|
|
351
371
|
async def messages_observing_status(self, session_id: str) -> MessageObservingStatus:
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
372
|
+
"""Get message observing status counts for a session.
|
|
373
|
+
|
|
374
|
+
Returns the count of messages by their observing status:
|
|
375
|
+
observed, in_process, and pending.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
session_id: The UUID of the session.
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
MessageObservingStatus object containing observed, in_process,
|
|
382
|
+
pending counts and updated_at timestamp.
|
|
383
|
+
"""
|
|
384
|
+
data = await self._requester.request(
|
|
385
|
+
"GET", f"/session/{session_id}/observing_status"
|
|
386
|
+
)
|
|
387
|
+
return MessageObservingStatus.model_validate(data)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Skills endpoints (async).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from typing import Any, BinaryIO, cast
|
|
8
|
+
|
|
9
|
+
from .._utils import build_params
|
|
10
|
+
from ..client_types import AsyncRequesterProtocol
|
|
11
|
+
from ..types.skill import (
|
|
12
|
+
GetSkillFileResp,
|
|
13
|
+
ListSkillsOutput,
|
|
14
|
+
Skill,
|
|
15
|
+
SkillCatalogItem,
|
|
16
|
+
_ListSkillsResponse,
|
|
17
|
+
)
|
|
18
|
+
from ..uploads import FileUpload, normalize_file_upload
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AsyncSkillsAPI:
|
|
22
|
+
def __init__(self, requester: AsyncRequesterProtocol) -> None:
|
|
23
|
+
self._requester = requester
|
|
24
|
+
|
|
25
|
+
async def create(
|
|
26
|
+
self,
|
|
27
|
+
*,
|
|
28
|
+
file: FileUpload
|
|
29
|
+
| tuple[str, BinaryIO | bytes]
|
|
30
|
+
| tuple[str, BinaryIO | bytes, str],
|
|
31
|
+
user: str | None = None,
|
|
32
|
+
meta: Mapping[str, Any] | None = None,
|
|
33
|
+
) -> Skill:
|
|
34
|
+
"""Create a new skill by uploading a ZIP file.
|
|
35
|
+
|
|
36
|
+
The ZIP file must contain a SKILL.md file (case-insensitive) with YAML format
|
|
37
|
+
containing 'name' and 'description' fields.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
file: The ZIP file to upload (FileUpload object or tuple format).
|
|
41
|
+
user: Optional user identifier string. Defaults to None.
|
|
42
|
+
meta: Custom metadata as JSON-serializable dict, defaults to None.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Skill containing the created skill information.
|
|
46
|
+
"""
|
|
47
|
+
upload = normalize_file_upload(file)
|
|
48
|
+
files = {"file": upload.as_httpx()}
|
|
49
|
+
form: dict[str, Any] = {}
|
|
50
|
+
if user is not None:
|
|
51
|
+
form["user"] = user
|
|
52
|
+
if meta is not None:
|
|
53
|
+
form["meta"] = json.dumps(cast(Mapping[str, Any], meta))
|
|
54
|
+
data = await self._requester.request(
|
|
55
|
+
"POST",
|
|
56
|
+
"/agent_skills",
|
|
57
|
+
data=form or None,
|
|
58
|
+
files=files,
|
|
59
|
+
)
|
|
60
|
+
return Skill.model_validate(data)
|
|
61
|
+
|
|
62
|
+
async def list_catalog(
|
|
63
|
+
self,
|
|
64
|
+
*,
|
|
65
|
+
user: str | None = None,
|
|
66
|
+
limit: int | None = None,
|
|
67
|
+
cursor: str | None = None,
|
|
68
|
+
time_desc: bool | None = None,
|
|
69
|
+
) -> ListSkillsOutput:
|
|
70
|
+
"""Get a catalog of skills (names and descriptions only) with pagination.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
user: Filter by user identifier. Defaults to None.
|
|
74
|
+
limit: Maximum number of skills per page (defaults to 100, max 200).
|
|
75
|
+
cursor: Cursor for pagination to fetch the next page (optional).
|
|
76
|
+
time_desc: Order by created_at descending if True, ascending if False (defaults to False).
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
ListSkillsOutput containing skills with name and description for the current page,
|
|
80
|
+
along with pagination information (next_cursor and has_more).
|
|
81
|
+
"""
|
|
82
|
+
effective_limit = limit if limit is not None else 100
|
|
83
|
+
params = build_params(user=user, limit=effective_limit, cursor=cursor, time_desc=time_desc)
|
|
84
|
+
data = await self._requester.request(
|
|
85
|
+
"GET", "/agent_skills", params=params or None
|
|
86
|
+
)
|
|
87
|
+
api_response = _ListSkillsResponse.model_validate(data)
|
|
88
|
+
|
|
89
|
+
# Convert to catalog format (name and description only)
|
|
90
|
+
return ListSkillsOutput(
|
|
91
|
+
items=[
|
|
92
|
+
SkillCatalogItem(name=skill.name, description=skill.description)
|
|
93
|
+
for skill in api_response.items
|
|
94
|
+
],
|
|
95
|
+
next_cursor=api_response.next_cursor,
|
|
96
|
+
has_more=api_response.has_more,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
async def get_by_name(self, name: str) -> Skill:
|
|
100
|
+
"""Get a skill by its name.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
name: The name of the skill (unique within project).
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Skill containing the skill information.
|
|
107
|
+
"""
|
|
108
|
+
params = {"name": name}
|
|
109
|
+
data = await self._requester.request(
|
|
110
|
+
"GET", "/agent_skills/by_name", params=params
|
|
111
|
+
)
|
|
112
|
+
return Skill.model_validate(data)
|
|
113
|
+
|
|
114
|
+
async def delete(self, skill_id: str) -> None:
|
|
115
|
+
"""Delete a skill by its ID.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
skill_id: The UUID of the skill to delete.
|
|
119
|
+
"""
|
|
120
|
+
await self._requester.request("DELETE", f"/agent_skills/{skill_id}")
|
|
121
|
+
|
|
122
|
+
async def get_file_by_name(
|
|
123
|
+
self,
|
|
124
|
+
*,
|
|
125
|
+
skill_name: str,
|
|
126
|
+
file_path: str,
|
|
127
|
+
expire: int | None = None,
|
|
128
|
+
) -> GetSkillFileResp:
|
|
129
|
+
"""Get a file from a skill by name.
|
|
130
|
+
|
|
131
|
+
The backend automatically returns content for parseable text files, or a presigned URL
|
|
132
|
+
for non-parseable files (binary, images, etc.).
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
skill_name: The name of the skill.
|
|
136
|
+
file_path: Relative path to the file within the skill (e.g., 'scripts/extract_text.json').
|
|
137
|
+
expire: URL expiration time in seconds. Defaults to 900 (15 minutes).
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
GetSkillFileResp containing the file path, MIME type, and either content or URL.
|
|
141
|
+
"""
|
|
142
|
+
endpoint = f"/agent_skills/by_name/{skill_name}/file"
|
|
143
|
+
|
|
144
|
+
params = {"file_path": file_path}
|
|
145
|
+
if expire is not None:
|
|
146
|
+
params["expire"] = expire
|
|
147
|
+
|
|
148
|
+
data = await self._requester.request("GET", endpoint, params=params)
|
|
149
|
+
return GetSkillFileResp.model_validate(data)
|
|
150
|
+
|