acontext 0.0.17__py3-none-any.whl → 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- acontext/agent/__init__.py +0 -1
- acontext/agent/base.py +13 -0
- acontext/agent/disk.py +210 -47
- acontext/agent/skill.py +278 -21
- acontext/resources/async_disks.py +104 -20
- acontext/resources/async_sessions.py +20 -15
- acontext/resources/async_skills.py +13 -28
- acontext/resources/skills.py +21 -30
- acontext/types/skill.py +14 -18
- {acontext-0.0.17.dist-info → acontext-0.1.0.dist-info}/METADATA +2 -1
- {acontext-0.0.17.dist-info → acontext-0.1.0.dist-info}/RECORD +12 -12
- {acontext-0.0.17.dist-info → acontext-0.1.0.dist-info}/WHEEL +1 -1
acontext/agent/skill.py
CHANGED
|
@@ -2,15 +2,126 @@
|
|
|
2
2
|
Skill tools for agent operations.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from dataclasses import dataclass
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
6
|
|
|
7
7
|
from .base import BaseContext, BaseTool, BaseToolPool
|
|
8
8
|
from ..client import AcontextClient
|
|
9
|
+
from ..async_client import AcontextAsyncClient
|
|
10
|
+
from ..types.skill import Skill
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
@dataclass
|
|
12
14
|
class SkillContext(BaseContext):
|
|
15
|
+
"""Context for skill tools with preloaded skill name mapping."""
|
|
16
|
+
|
|
13
17
|
client: AcontextClient
|
|
18
|
+
skills: dict[str, Skill] = field(default_factory=dict)
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def create(cls, client: AcontextClient, skill_ids: list[str]) -> "SkillContext":
|
|
22
|
+
"""Create a SkillContext by preloading skills from a list of skill IDs.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
client: The Acontext client instance.
|
|
26
|
+
skill_ids: List of skill UUIDs to preload.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
SkillContext with preloaded skills mapped by name.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
ValueError: If duplicate skill names are found.
|
|
33
|
+
"""
|
|
34
|
+
skills: dict[str, Skill] = {}
|
|
35
|
+
for skill_id in skill_ids:
|
|
36
|
+
skill = client.skills.get(skill_id)
|
|
37
|
+
if skill.name in skills:
|
|
38
|
+
raise ValueError(
|
|
39
|
+
f"Duplicate skill name '{skill.name}' found. "
|
|
40
|
+
f"Existing ID: {skills[skill.name].id}, New ID: {skill.id}"
|
|
41
|
+
)
|
|
42
|
+
skills[skill.name] = skill
|
|
43
|
+
return cls(client=client, skills=skills)
|
|
44
|
+
|
|
45
|
+
def get_skill(self, skill_name: str) -> Skill:
|
|
46
|
+
"""Get a skill by name from the preloaded skills.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
skill_name: The name of the skill.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The Skill object.
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
ValueError: If the skill is not found in the context.
|
|
56
|
+
"""
|
|
57
|
+
if skill_name not in self.skills:
|
|
58
|
+
available = ", ".join(self.skills.keys()) if self.skills else "[none]"
|
|
59
|
+
raise ValueError(
|
|
60
|
+
f"Skill '{skill_name}' not found in context. Available skills: {available}"
|
|
61
|
+
)
|
|
62
|
+
return self.skills[skill_name]
|
|
63
|
+
|
|
64
|
+
def list_skill_names(self) -> list[str]:
|
|
65
|
+
"""Return list of available skill names in this context."""
|
|
66
|
+
return list(self.skills.keys())
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class AsyncSkillContext(BaseContext):
|
|
71
|
+
"""Async context for skill tools with preloaded skill name mapping."""
|
|
72
|
+
|
|
73
|
+
client: AcontextAsyncClient
|
|
74
|
+
skills: dict[str, Skill] = field(default_factory=dict)
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
async def create(
|
|
78
|
+
cls, client: AcontextAsyncClient, skill_ids: list[str]
|
|
79
|
+
) -> "AsyncSkillContext":
|
|
80
|
+
"""Create an AsyncSkillContext by preloading skills from a list of skill IDs.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
client: The Acontext async client instance.
|
|
84
|
+
skill_ids: List of skill UUIDs to preload.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
AsyncSkillContext with preloaded skills mapped by name.
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
ValueError: If duplicate skill names are found.
|
|
91
|
+
"""
|
|
92
|
+
skills: dict[str, Skill] = {}
|
|
93
|
+
for skill_id in skill_ids:
|
|
94
|
+
skill = await client.skills.get(skill_id)
|
|
95
|
+
if skill.name in skills:
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f"Duplicate skill name '{skill.name}' found. "
|
|
98
|
+
f"Existing ID: {skills[skill.name].id}, New ID: {skill.id}"
|
|
99
|
+
)
|
|
100
|
+
skills[skill.name] = skill
|
|
101
|
+
return cls(client=client, skills=skills)
|
|
102
|
+
|
|
103
|
+
def get_skill(self, skill_name: str) -> Skill:
|
|
104
|
+
"""Get a skill by name from the preloaded skills.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
skill_name: The name of the skill.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
The Skill object.
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
ValueError: If the skill is not found in the context.
|
|
114
|
+
"""
|
|
115
|
+
if skill_name not in self.skills:
|
|
116
|
+
available = ", ".join(self.skills.keys()) if self.skills else "[none]"
|
|
117
|
+
raise ValueError(
|
|
118
|
+
f"Skill '{skill_name}' not found in context. Available skills: {available}"
|
|
119
|
+
)
|
|
120
|
+
return self.skills[skill_name]
|
|
121
|
+
|
|
122
|
+
def list_skill_names(self) -> list[str]:
|
|
123
|
+
"""Return list of available skill names in this context."""
|
|
124
|
+
return list(self.skills.keys())
|
|
14
125
|
|
|
15
126
|
|
|
16
127
|
class GetSkillTool(BaseTool):
|
|
@@ -23,37 +134,70 @@ class GetSkillTool(BaseTool):
|
|
|
23
134
|
@property
|
|
24
135
|
def description(self) -> str:
|
|
25
136
|
return (
|
|
26
|
-
"Get a skill by its name.
|
|
137
|
+
"Get a skill by its name. Returns the skill information including "
|
|
138
|
+
"the relative paths of the files and their mime type categories."
|
|
27
139
|
)
|
|
28
140
|
|
|
29
141
|
@property
|
|
30
142
|
def arguments(self) -> dict:
|
|
31
143
|
return {
|
|
32
|
-
"
|
|
144
|
+
"skill_name": {
|
|
33
145
|
"type": "string",
|
|
34
|
-
"description": "The name of the skill
|
|
146
|
+
"description": "The name of the skill.",
|
|
35
147
|
},
|
|
36
148
|
}
|
|
37
149
|
|
|
38
150
|
@property
|
|
39
151
|
def required_arguments(self) -> list[str]:
|
|
40
|
-
return ["
|
|
152
|
+
return ["skill_name"]
|
|
41
153
|
|
|
42
154
|
def execute(self, ctx: SkillContext, llm_arguments: dict) -> str:
|
|
43
155
|
"""Get a skill by name."""
|
|
44
|
-
|
|
156
|
+
skill_name = llm_arguments.get("skill_name")
|
|
157
|
+
|
|
158
|
+
if not skill_name:
|
|
159
|
+
raise ValueError("skill_name is required")
|
|
160
|
+
|
|
161
|
+
skill = ctx.get_skill(skill_name)
|
|
162
|
+
|
|
163
|
+
file_count = len(skill.file_index)
|
|
164
|
+
|
|
165
|
+
# Format all files with path and MIME type
|
|
166
|
+
if skill.file_index:
|
|
167
|
+
file_list = "\n".join(
|
|
168
|
+
[
|
|
169
|
+
f" - {file_info.path} ({file_info.mime})"
|
|
170
|
+
for file_info in skill.file_index
|
|
171
|
+
]
|
|
172
|
+
)
|
|
173
|
+
else:
|
|
174
|
+
file_list = " [NO FILES]"
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
f"Skill: {skill.name} (ID: {skill.id})\n"
|
|
178
|
+
f"Description: {skill.description}\n"
|
|
179
|
+
f"Files: {file_count} file(s)\n"
|
|
180
|
+
f"{file_list}"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
async def async_execute(self, ctx: AsyncSkillContext, llm_arguments: dict) -> str:
|
|
184
|
+
"""Get a skill by name (async)."""
|
|
185
|
+
skill_name = llm_arguments.get("skill_name")
|
|
45
186
|
|
|
46
|
-
if not
|
|
47
|
-
raise ValueError("
|
|
187
|
+
if not skill_name:
|
|
188
|
+
raise ValueError("skill_name is required")
|
|
48
189
|
|
|
49
|
-
skill = ctx.
|
|
190
|
+
skill = ctx.get_skill(skill_name)
|
|
50
191
|
|
|
51
192
|
file_count = len(skill.file_index)
|
|
52
|
-
|
|
193
|
+
|
|
53
194
|
# Format all files with path and MIME type
|
|
54
195
|
if skill.file_index:
|
|
55
196
|
file_list = "\n".join(
|
|
56
|
-
[
|
|
197
|
+
[
|
|
198
|
+
f" - {file_info.path} ({file_info.mime})"
|
|
199
|
+
for file_info in skill.file_index
|
|
200
|
+
]
|
|
57
201
|
)
|
|
58
202
|
else:
|
|
59
203
|
file_list = " [NO FILES]"
|
|
@@ -62,9 +206,7 @@ class GetSkillTool(BaseTool):
|
|
|
62
206
|
f"Skill: {skill.name} (ID: {skill.id})\n"
|
|
63
207
|
f"Description: {skill.description}\n"
|
|
64
208
|
f"Files: {file_count} file(s)\n"
|
|
65
|
-
f"{file_list}
|
|
66
|
-
f"Created: {skill.created_at}\n"
|
|
67
|
-
f"Updated: {skill.updated_at}"
|
|
209
|
+
f"{file_list}"
|
|
68
210
|
)
|
|
69
211
|
|
|
70
212
|
|
|
@@ -78,7 +220,9 @@ class GetSkillFileTool(BaseTool):
|
|
|
78
220
|
@property
|
|
79
221
|
def description(self) -> str:
|
|
80
222
|
return (
|
|
81
|
-
"Get a file from a skill by name. The file_path should be a relative
|
|
223
|
+
"Get a file from a skill by name. The file_path should be a relative "
|
|
224
|
+
"path within the skill (e.g., 'scripts/extract_text.json')."
|
|
225
|
+
"Tips: SKILL.md is the first file you should read to understand the full picture of this skill's content."
|
|
82
226
|
)
|
|
83
227
|
|
|
84
228
|
@property
|
|
@@ -108,18 +252,61 @@ class GetSkillFileTool(BaseTool):
|
|
|
108
252
|
file_path = llm_arguments.get("file_path")
|
|
109
253
|
expire = llm_arguments.get("expire")
|
|
110
254
|
|
|
255
|
+
if not skill_name:
|
|
256
|
+
raise ValueError("skill_name is required")
|
|
111
257
|
if not file_path:
|
|
112
258
|
raise ValueError("file_path is required")
|
|
259
|
+
|
|
260
|
+
skill = ctx.get_skill(skill_name)
|
|
261
|
+
|
|
262
|
+
result = ctx.client.skills.get_file(
|
|
263
|
+
skill_id=skill.id,
|
|
264
|
+
file_path=file_path,
|
|
265
|
+
expire=expire,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
output_parts = [
|
|
269
|
+
f"File '{result.path}' (MIME: {result.mime}) from skill '{skill_name}':"
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
if result.content:
|
|
273
|
+
output_parts.append(f"\nContent (type: {result.content.type}):")
|
|
274
|
+
output_parts.append(result.content.raw)
|
|
275
|
+
|
|
276
|
+
if result.url:
|
|
277
|
+
expire_seconds = expire if expire is not None else 900
|
|
278
|
+
output_parts.append(
|
|
279
|
+
f"\nDownload URL (expires in {expire_seconds} seconds):"
|
|
280
|
+
)
|
|
281
|
+
output_parts.append(result.url)
|
|
282
|
+
|
|
283
|
+
if not result.content and not result.url:
|
|
284
|
+
return f"File '{result.path}' retrieved but no content or URL returned."
|
|
285
|
+
|
|
286
|
+
return "\n".join(output_parts)
|
|
287
|
+
|
|
288
|
+
async def async_execute(self, ctx: AsyncSkillContext, llm_arguments: dict) -> str:
|
|
289
|
+
"""Get a skill file (async)."""
|
|
290
|
+
skill_name = llm_arguments.get("skill_name")
|
|
291
|
+
file_path = llm_arguments.get("file_path")
|
|
292
|
+
expire = llm_arguments.get("expire")
|
|
293
|
+
|
|
113
294
|
if not skill_name:
|
|
114
295
|
raise ValueError("skill_name is required")
|
|
296
|
+
if not file_path:
|
|
297
|
+
raise ValueError("file_path is required")
|
|
115
298
|
|
|
116
|
-
|
|
117
|
-
|
|
299
|
+
skill = ctx.get_skill(skill_name)
|
|
300
|
+
|
|
301
|
+
result = await ctx.client.skills.get_file(
|
|
302
|
+
skill_id=skill.id,
|
|
118
303
|
file_path=file_path,
|
|
119
304
|
expire=expire,
|
|
120
305
|
)
|
|
121
306
|
|
|
122
|
-
output_parts = [
|
|
307
|
+
output_parts = [
|
|
308
|
+
f"File '{result.path}' (MIME: {result.mime}) from skill '{skill_name}':"
|
|
309
|
+
]
|
|
123
310
|
|
|
124
311
|
if result.content:
|
|
125
312
|
output_parts.append(f"\nContent (type: {result.content.type}):")
|
|
@@ -127,7 +314,9 @@ class GetSkillFileTool(BaseTool):
|
|
|
127
314
|
|
|
128
315
|
if result.url:
|
|
129
316
|
expire_seconds = expire if expire is not None else 900
|
|
130
|
-
output_parts.append(
|
|
317
|
+
output_parts.append(
|
|
318
|
+
f"\nDownload URL (expires in {expire_seconds} seconds):"
|
|
319
|
+
)
|
|
131
320
|
output_parts.append(result.url)
|
|
132
321
|
|
|
133
322
|
if not result.content and not result.url:
|
|
@@ -136,13 +325,81 @@ class GetSkillFileTool(BaseTool):
|
|
|
136
325
|
return "\n".join(output_parts)
|
|
137
326
|
|
|
138
327
|
|
|
328
|
+
class ListSkillsTool(BaseTool):
|
|
329
|
+
"""Tool for listing available skills in the context."""
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def name(self) -> str:
|
|
333
|
+
return "list_skills"
|
|
334
|
+
|
|
335
|
+
@property
|
|
336
|
+
def description(self) -> str:
|
|
337
|
+
return "List all available skills in the current context with their names and descriptions."
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
def arguments(self) -> dict:
|
|
341
|
+
return {}
|
|
342
|
+
|
|
343
|
+
@property
|
|
344
|
+
def required_arguments(self) -> list[str]:
|
|
345
|
+
return []
|
|
346
|
+
|
|
347
|
+
def execute(self, ctx: SkillContext, llm_arguments: dict) -> str:
|
|
348
|
+
"""List all available skills."""
|
|
349
|
+
if not ctx.skills:
|
|
350
|
+
return "No skills available in the current context."
|
|
351
|
+
|
|
352
|
+
skill_list = []
|
|
353
|
+
for skill_name, skill in ctx.skills.items():
|
|
354
|
+
skill_list.append(f"- {skill_name}: {skill.description}")
|
|
355
|
+
|
|
356
|
+
return f"Available skills ({len(ctx.skills)}):\n" + "\n".join(skill_list)
|
|
357
|
+
|
|
358
|
+
async def async_execute(self, ctx: AsyncSkillContext, llm_arguments: dict) -> str:
|
|
359
|
+
"""List all available skills (async)."""
|
|
360
|
+
if not ctx.skills:
|
|
361
|
+
return "No skills available in the current context."
|
|
362
|
+
|
|
363
|
+
skill_list = []
|
|
364
|
+
for skill_name, skill in ctx.skills.items():
|
|
365
|
+
skill_list.append(f"- {skill_name}: {skill.description}")
|
|
366
|
+
|
|
367
|
+
return f"Available skills ({len(ctx.skills)}):\n" + "\n".join(skill_list)
|
|
368
|
+
|
|
369
|
+
|
|
139
370
|
class SkillToolPool(BaseToolPool):
|
|
140
371
|
"""Tool pool for skill operations on Acontext skills."""
|
|
141
372
|
|
|
142
|
-
def format_context(
|
|
143
|
-
|
|
373
|
+
def format_context(
|
|
374
|
+
self, client: AcontextClient, skill_ids: list[str]
|
|
375
|
+
) -> SkillContext:
|
|
376
|
+
"""Create a SkillContext by preloading skills from a list of skill IDs.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
client: The Acontext client instance.
|
|
380
|
+
skill_ids: List of skill UUIDs to preload.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
SkillContext with preloaded skills mapped by name.
|
|
384
|
+
"""
|
|
385
|
+
return SkillContext.create(client=client, skill_ids=skill_ids)
|
|
386
|
+
|
|
387
|
+
async def async_format_context(
|
|
388
|
+
self, client: AcontextAsyncClient, skill_ids: list[str]
|
|
389
|
+
) -> AsyncSkillContext:
|
|
390
|
+
"""Create an AsyncSkillContext by preloading skills from a list of skill IDs.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
client: The Acontext async client instance.
|
|
394
|
+
skill_ids: List of skill UUIDs to preload.
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
AsyncSkillContext with preloaded skills mapped by name.
|
|
398
|
+
"""
|
|
399
|
+
return await AsyncSkillContext.create(client=client, skill_ids=skill_ids)
|
|
144
400
|
|
|
145
401
|
|
|
146
402
|
SKILL_TOOLS = SkillToolPool()
|
|
403
|
+
SKILL_TOOLS.add_tool(ListSkillsTool())
|
|
147
404
|
SKILL_TOOLS.add_tool(GetSkillTool())
|
|
148
405
|
SKILL_TOOLS.add_tool(GetSkillFileTool())
|
|
@@ -33,26 +33,28 @@ class AsyncDisksAPI:
|
|
|
33
33
|
time_desc: bool | None = None,
|
|
34
34
|
) -> ListDisksOutput:
|
|
35
35
|
"""List all disks in the project.
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
Args:
|
|
38
38
|
user: Filter by user identifier. Defaults to None.
|
|
39
39
|
limit: Maximum number of disks to return. Defaults to None.
|
|
40
40
|
cursor: Cursor for pagination. Defaults to None.
|
|
41
41
|
time_desc: Order by created_at descending if True, ascending if False. Defaults to None.
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
Returns:
|
|
44
44
|
ListDisksOutput containing the list of disks and pagination information.
|
|
45
45
|
"""
|
|
46
|
-
params = build_params(
|
|
46
|
+
params = build_params(
|
|
47
|
+
user=user, limit=limit, cursor=cursor, time_desc=time_desc
|
|
48
|
+
)
|
|
47
49
|
data = await self._requester.request("GET", "/disk", params=params or None)
|
|
48
50
|
return ListDisksOutput.model_validate(data)
|
|
49
51
|
|
|
50
52
|
async def create(self, *, user: str | None = None) -> Disk:
|
|
51
53
|
"""Create a new disk.
|
|
52
|
-
|
|
54
|
+
|
|
53
55
|
Args:
|
|
54
56
|
user: Optional user identifier string. Defaults to None.
|
|
55
|
-
|
|
57
|
+
|
|
56
58
|
Returns:
|
|
57
59
|
The created Disk object.
|
|
58
60
|
"""
|
|
@@ -64,7 +66,7 @@ class AsyncDisksAPI:
|
|
|
64
66
|
|
|
65
67
|
async def delete(self, disk_id: str) -> None:
|
|
66
68
|
"""Delete a disk by its ID.
|
|
67
|
-
|
|
69
|
+
|
|
68
70
|
Args:
|
|
69
71
|
disk_id: The UUID of the disk to delete.
|
|
70
72
|
"""
|
|
@@ -79,20 +81,22 @@ class AsyncDiskArtifactsAPI:
|
|
|
79
81
|
self,
|
|
80
82
|
disk_id: str,
|
|
81
83
|
*,
|
|
82
|
-
file:
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
file: (
|
|
85
|
+
FileUpload
|
|
86
|
+
| tuple[str, BinaryIO | bytes]
|
|
87
|
+
| tuple[str, BinaryIO | bytes, str]
|
|
88
|
+
),
|
|
85
89
|
file_path: str | None = None,
|
|
86
90
|
meta: Mapping[str, Any] | None = None,
|
|
87
91
|
) -> Artifact:
|
|
88
92
|
"""Upload a file to create or update an artifact.
|
|
89
|
-
|
|
93
|
+
|
|
90
94
|
Args:
|
|
91
95
|
disk_id: The UUID of the disk.
|
|
92
96
|
file: The file to upload (FileUpload object or tuple format).
|
|
93
97
|
file_path: Directory path (not including filename), defaults to "/".
|
|
94
98
|
meta: Custom metadata as JSON-serializable dict, defaults to None.
|
|
95
|
-
|
|
99
|
+
|
|
96
100
|
Returns:
|
|
97
101
|
Artifact containing the created/updated artifact information.
|
|
98
102
|
"""
|
|
@@ -122,7 +126,7 @@ class AsyncDiskArtifactsAPI:
|
|
|
122
126
|
expire: int | None = None,
|
|
123
127
|
) -> GetArtifactResp:
|
|
124
128
|
"""Get an artifact by disk ID, file path, and filename.
|
|
125
|
-
|
|
129
|
+
|
|
126
130
|
Args:
|
|
127
131
|
disk_id: The UUID of the disk.
|
|
128
132
|
file_path: Directory path (not including filename).
|
|
@@ -130,7 +134,7 @@ class AsyncDiskArtifactsAPI:
|
|
|
130
134
|
with_public_url: Whether to include a presigned public URL. Defaults to None.
|
|
131
135
|
with_content: Whether to include file content. Defaults to None.
|
|
132
136
|
expire: URL expiration time in seconds. Defaults to None.
|
|
133
|
-
|
|
137
|
+
|
|
134
138
|
Returns:
|
|
135
139
|
GetArtifactResp containing the artifact and optionally public URL and content.
|
|
136
140
|
"""
|
|
@@ -141,7 +145,9 @@ class AsyncDiskArtifactsAPI:
|
|
|
141
145
|
with_content=with_content,
|
|
142
146
|
expire=expire,
|
|
143
147
|
)
|
|
144
|
-
data = await self._requester.request(
|
|
148
|
+
data = await self._requester.request(
|
|
149
|
+
"GET", f"/disk/{disk_id}/artifact", params=params
|
|
150
|
+
)
|
|
145
151
|
return GetArtifactResp.model_validate(data)
|
|
146
152
|
|
|
147
153
|
async def update(
|
|
@@ -153,13 +159,13 @@ class AsyncDiskArtifactsAPI:
|
|
|
153
159
|
meta: Mapping[str, Any],
|
|
154
160
|
) -> UpdateArtifactResp:
|
|
155
161
|
"""Update an artifact's metadata.
|
|
156
|
-
|
|
162
|
+
|
|
157
163
|
Args:
|
|
158
164
|
disk_id: The UUID of the disk.
|
|
159
165
|
file_path: Directory path (not including filename).
|
|
160
166
|
filename: The filename of the artifact.
|
|
161
167
|
meta: Custom metadata as JSON-serializable dict.
|
|
162
|
-
|
|
168
|
+
|
|
163
169
|
Returns:
|
|
164
170
|
UpdateArtifactResp containing the updated artifact information.
|
|
165
171
|
"""
|
|
@@ -168,7 +174,9 @@ class AsyncDiskArtifactsAPI:
|
|
|
168
174
|
"file_path": full_path,
|
|
169
175
|
"meta": json.dumps(cast(Mapping[str, Any], meta)),
|
|
170
176
|
}
|
|
171
|
-
data = await self._requester.request(
|
|
177
|
+
data = await self._requester.request(
|
|
178
|
+
"PUT", f"/disk/{disk_id}/artifact", json_data=payload
|
|
179
|
+
)
|
|
172
180
|
return UpdateArtifactResp.model_validate(data)
|
|
173
181
|
|
|
174
182
|
async def delete(
|
|
@@ -179,7 +187,7 @@ class AsyncDiskArtifactsAPI:
|
|
|
179
187
|
filename: str,
|
|
180
188
|
) -> None:
|
|
181
189
|
"""Delete an artifact by disk ID, file path, and filename.
|
|
182
|
-
|
|
190
|
+
|
|
183
191
|
Args:
|
|
184
192
|
disk_id: The UUID of the disk.
|
|
185
193
|
file_path: Directory path (not including filename).
|
|
@@ -187,7 +195,9 @@ class AsyncDiskArtifactsAPI:
|
|
|
187
195
|
"""
|
|
188
196
|
full_path = f"{file_path.rstrip('/')}/{filename}"
|
|
189
197
|
params = {"file_path": full_path}
|
|
190
|
-
await self._requester.request(
|
|
198
|
+
await self._requester.request(
|
|
199
|
+
"DELETE", f"/disk/{disk_id}/artifact", params=params
|
|
200
|
+
)
|
|
191
201
|
|
|
192
202
|
async def list(
|
|
193
203
|
self,
|
|
@@ -195,9 +205,83 @@ class AsyncDiskArtifactsAPI:
|
|
|
195
205
|
*,
|
|
196
206
|
path: str | None = None,
|
|
197
207
|
) -> ListArtifactsResp:
|
|
208
|
+
"""List artifacts in a disk at a specific path.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
disk_id: The UUID of the disk.
|
|
212
|
+
path: Directory path to list. Defaults to None (root).
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
ListArtifactsResp containing the list of artifacts.
|
|
216
|
+
"""
|
|
198
217
|
params: dict[str, Any] = {}
|
|
199
218
|
if path is not None:
|
|
200
219
|
params["path"] = path
|
|
201
|
-
data = await self._requester.request(
|
|
220
|
+
data = await self._requester.request(
|
|
221
|
+
"GET", f"/disk/{disk_id}/artifact/ls", params=params or None
|
|
222
|
+
)
|
|
202
223
|
return ListArtifactsResp.model_validate(data)
|
|
203
224
|
|
|
225
|
+
async def grep_artifacts(
|
|
226
|
+
self,
|
|
227
|
+
disk_id: str,
|
|
228
|
+
*,
|
|
229
|
+
query: str,
|
|
230
|
+
limit: int = 100,
|
|
231
|
+
) -> list[Artifact]:
|
|
232
|
+
"""Search artifact content using regex pattern.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
disk_id: The disk ID to search in
|
|
236
|
+
query: Regex pattern to search for in file content
|
|
237
|
+
limit: Maximum number of results (default 100, max 1000)
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
List of matching artifacts
|
|
241
|
+
|
|
242
|
+
Example:
|
|
243
|
+
```python
|
|
244
|
+
# Search for TODO comments in code
|
|
245
|
+
results = await client.disks.artifacts.grep_artifacts(
|
|
246
|
+
disk_id="disk-uuid",
|
|
247
|
+
query="TODO.*bug"
|
|
248
|
+
)
|
|
249
|
+
```
|
|
250
|
+
"""
|
|
251
|
+
params = build_params(query=query, limit=limit)
|
|
252
|
+
data = await self._requester.request(
|
|
253
|
+
"GET", f"/disk/{disk_id}/artifact/grep", params=params
|
|
254
|
+
)
|
|
255
|
+
return [Artifact.model_validate(item) for item in data]
|
|
256
|
+
|
|
257
|
+
async def glob_artifacts(
|
|
258
|
+
self,
|
|
259
|
+
disk_id: str,
|
|
260
|
+
*,
|
|
261
|
+
query: str,
|
|
262
|
+
limit: int = 100,
|
|
263
|
+
) -> list[Artifact]:
|
|
264
|
+
"""Search artifact paths using glob pattern.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
disk_id: The disk ID to search in
|
|
268
|
+
query: Glob pattern (e.g., '**/*.py', '*.txt')
|
|
269
|
+
limit: Maximum number of results (default 100, max 1000)
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
List of matching artifacts
|
|
273
|
+
|
|
274
|
+
Example:
|
|
275
|
+
```python
|
|
276
|
+
# Find all Python files
|
|
277
|
+
results = await client.disks.artifacts.glob_artifacts(
|
|
278
|
+
disk_id="disk-uuid",
|
|
279
|
+
query="**/*.py"
|
|
280
|
+
)
|
|
281
|
+
```
|
|
282
|
+
"""
|
|
283
|
+
params = build_params(query=query, limit=limit)
|
|
284
|
+
data = await self._requester.request(
|
|
285
|
+
"GET", f"/disk/{disk_id}/artifact/glob", params=params
|
|
286
|
+
)
|
|
287
|
+
return [Artifact.model_validate(item) for item in data]
|
|
@@ -78,6 +78,7 @@ class AsyncSessionsAPI:
|
|
|
78
78
|
*,
|
|
79
79
|
user: str | None = None,
|
|
80
80
|
space_id: str | None = None,
|
|
81
|
+
disable_task_tracking: bool | None = None,
|
|
81
82
|
configs: Mapping[str, Any] | None = None,
|
|
82
83
|
) -> Session:
|
|
83
84
|
"""Create a new session.
|
|
@@ -85,6 +86,7 @@ class AsyncSessionsAPI:
|
|
|
85
86
|
Args:
|
|
86
87
|
user: Optional user identifier string. Defaults to None.
|
|
87
88
|
space_id: Optional space ID to associate with the session. Defaults to None.
|
|
89
|
+
disable_task_tracking: Whether to disable task tracking for this session. Defaults to None (server default: False).
|
|
88
90
|
configs: Optional session configuration dictionary. Defaults to None.
|
|
89
91
|
|
|
90
92
|
Returns:
|
|
@@ -95,6 +97,8 @@ class AsyncSessionsAPI:
|
|
|
95
97
|
payload["user"] = user
|
|
96
98
|
if space_id:
|
|
97
99
|
payload["space_id"] = space_id
|
|
100
|
+
if disable_task_tracking is not None:
|
|
101
|
+
payload["disable_task_tracking"] = disable_task_tracking
|
|
98
102
|
if configs is not None:
|
|
99
103
|
payload["configs"] = configs
|
|
100
104
|
data = await self._requester.request("POST", "/session", json_data=payload)
|
|
@@ -367,21 +371,22 @@ class AsyncSessionsAPI:
|
|
|
367
371
|
)
|
|
368
372
|
return TokenCounts.model_validate(data)
|
|
369
373
|
|
|
374
|
+
async def messages_observing_status(
|
|
375
|
+
self, session_id: str
|
|
376
|
+
) -> MessageObservingStatus:
|
|
377
|
+
"""Get message observing status counts for a session.
|
|
370
378
|
|
|
371
|
-
|
|
372
|
-
|
|
379
|
+
Returns the count of messages by their observing status:
|
|
380
|
+
observed, in_process, and pending.
|
|
373
381
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
Args:
|
|
378
|
-
session_id: The UUID of the session.
|
|
382
|
+
Args:
|
|
383
|
+
session_id: The UUID of the session.
|
|
379
384
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
385
|
+
Returns:
|
|
386
|
+
MessageObservingStatus object containing observed, in_process,
|
|
387
|
+
pending counts and updated_at timestamp.
|
|
388
|
+
"""
|
|
389
|
+
data = await self._requester.request(
|
|
390
|
+
"GET", f"/session/{session_id}/observing_status"
|
|
391
|
+
)
|
|
392
|
+
return MessageObservingStatus.model_validate(data)
|