acontext 0.0.18__tar.gz → 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {acontext-0.0.18 → acontext-0.1.1}/PKG-INFO +2 -1
- {acontext-0.0.18 → acontext-0.1.1}/pyproject.toml +2 -1
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/agent/__init__.py +0 -1
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/agent/base.py +13 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/agent/disk.py +210 -47
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/agent/skill.py +152 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/resources/async_disks.py +106 -20
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/resources/async_sessions.py +20 -15
- {acontext-0.0.18 → acontext-0.1.1}/README.md +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/__init__.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/_constants.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/_utils.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/async_client.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/client.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/client_types.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/errors.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/messages.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/py.typed +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/resources/__init__.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/resources/async_blocks.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/resources/async_skills.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/resources/async_spaces.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/resources/async_tools.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/resources/async_users.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/resources/blocks.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/resources/disks.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/resources/sessions.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/resources/skills.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/resources/spaces.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/resources/tools.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/resources/users.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/types/__init__.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/types/block.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/types/common.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/types/disk.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/types/session.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/types/skill.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/types/space.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/types/tool.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/types/user.py +0 -0
- {acontext-0.0.18 → acontext-0.1.1}/src/acontext/uploads.py +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: acontext
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Python SDK for the Acontext API
|
|
5
5
|
Keywords: acontext,sdk,client,api
|
|
6
6
|
Requires-Dist: httpx>=0.28.1
|
|
7
7
|
Requires-Dist: openai>=2.6.1
|
|
8
8
|
Requires-Dist: anthropic>=0.72.0
|
|
9
9
|
Requires-Dist: pydantic>=2.12.3
|
|
10
|
+
Requires-Dist: urllib3>=2.6.3
|
|
10
11
|
Requires-Python: >=3.10
|
|
11
12
|
Project-URL: Homepage, https://github.com/memodb-io/Acontext
|
|
12
13
|
Project-URL: Issues, https://github.com/memodb-io/Acontext/issues
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "acontext"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.1.1"
|
|
4
4
|
description = "Python SDK for the Acontext API"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -9,6 +9,7 @@ dependencies = [
|
|
|
9
9
|
"openai>=2.6.1",
|
|
10
10
|
"anthropic>=0.72.0",
|
|
11
11
|
"pydantic>=2.12.3",
|
|
12
|
+
"urllib3>=2.6.3",
|
|
12
13
|
]
|
|
13
14
|
keywords = ["acontext", "sdk", "client", "api"]
|
|
14
15
|
|
|
@@ -33,6 +33,9 @@ class BaseTool(BaseConverter):
|
|
|
33
33
|
def execute(self, ctx: BaseContext, llm_arguments: dict) -> str:
|
|
34
34
|
raise NotImplementedError
|
|
35
35
|
|
|
36
|
+
async def async_execute(self, ctx: BaseContext, llm_arguments: dict) -> str:
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
|
|
36
39
|
def to_openai_tool_schema(self) -> dict:
|
|
37
40
|
return {
|
|
38
41
|
"type": "function",
|
|
@@ -90,6 +93,13 @@ class BaseToolPool(BaseConverter):
|
|
|
90
93
|
r = tool.execute(ctx, llm_arguments)
|
|
91
94
|
return r.strip()
|
|
92
95
|
|
|
96
|
+
async def async_execute_tool(
|
|
97
|
+
self, ctx: BaseContext, tool_name: str, llm_arguments: dict
|
|
98
|
+
) -> str:
|
|
99
|
+
tool = self.tools[tool_name]
|
|
100
|
+
r = await tool.async_execute(ctx, llm_arguments)
|
|
101
|
+
return r.strip()
|
|
102
|
+
|
|
93
103
|
def tool_exists(self, tool_name: str) -> bool:
|
|
94
104
|
return tool_name in self.tools
|
|
95
105
|
|
|
@@ -104,3 +114,6 @@ class BaseToolPool(BaseConverter):
|
|
|
104
114
|
|
|
105
115
|
def format_context(self, *args, **kwargs) -> BaseContext:
|
|
106
116
|
raise NotImplementedError
|
|
117
|
+
|
|
118
|
+
async def async_format_context(self, *args, **kwargs) -> BaseContext:
|
|
119
|
+
raise NotImplementedError
|
|
@@ -2,6 +2,7 @@ from dataclasses import dataclass
|
|
|
2
2
|
|
|
3
3
|
from .base import BaseContext, BaseTool, BaseToolPool
|
|
4
4
|
from ..client import AcontextClient
|
|
5
|
+
from ..async_client import AcontextAsyncClient
|
|
5
6
|
from ..uploads import FileUpload
|
|
6
7
|
|
|
7
8
|
|
|
@@ -11,6 +12,12 @@ class DiskContext(BaseContext):
|
|
|
11
12
|
disk_id: str
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
@dataclass
|
|
16
|
+
class AsyncDiskContext(BaseContext):
|
|
17
|
+
client: AcontextAsyncClient
|
|
18
|
+
disk_id: str
|
|
19
|
+
|
|
20
|
+
|
|
14
21
|
def _normalize_path(path: str | None) -> str:
|
|
15
22
|
"""Normalize a file path to ensure it starts with '/'."""
|
|
16
23
|
if not path:
|
|
@@ -73,6 +80,26 @@ class WriteFileTool(BaseTool):
|
|
|
73
80
|
)
|
|
74
81
|
return f"File '{artifact.filename}' written successfully to '{artifact.path}'"
|
|
75
82
|
|
|
83
|
+
async def async_execute(self, ctx: AsyncDiskContext, llm_arguments: dict) -> str:
|
|
84
|
+
"""Write text content to a file (async)."""
|
|
85
|
+
filename = llm_arguments.get("filename")
|
|
86
|
+
content = llm_arguments.get("content")
|
|
87
|
+
file_path = llm_arguments.get("file_path")
|
|
88
|
+
|
|
89
|
+
if not filename:
|
|
90
|
+
raise ValueError("filename is required")
|
|
91
|
+
if not content:
|
|
92
|
+
raise ValueError("content is required")
|
|
93
|
+
|
|
94
|
+
normalized_path = _normalize_path(file_path)
|
|
95
|
+
payload = FileUpload(filename=filename, content=content.encode("utf-8"))
|
|
96
|
+
artifact = await ctx.client.disks.artifacts.upsert(
|
|
97
|
+
ctx.disk_id,
|
|
98
|
+
file=payload,
|
|
99
|
+
file_path=normalized_path,
|
|
100
|
+
)
|
|
101
|
+
return f"File '{artifact.filename}' written successfully to '{artifact.path}'"
|
|
102
|
+
|
|
76
103
|
|
|
77
104
|
class ReadFileTool(BaseTool):
|
|
78
105
|
"""Tool for reading a text file from the Acontext disk."""
|
|
@@ -138,6 +165,34 @@ class ReadFileTool(BaseTool):
|
|
|
138
165
|
preview = "\n".join(lines[line_start:line_end])
|
|
139
166
|
return f"[{normalized_path}{filename} - showing L{line_start}-{line_end} of {len(lines)} lines]\n{preview}"
|
|
140
167
|
|
|
168
|
+
async def async_execute(self, ctx: AsyncDiskContext, llm_arguments: dict) -> str:
|
|
169
|
+
"""Read a text file and return its content preview (async)."""
|
|
170
|
+
filename = llm_arguments.get("filename")
|
|
171
|
+
file_path = llm_arguments.get("file_path")
|
|
172
|
+
line_offset = llm_arguments.get("line_offset", 0)
|
|
173
|
+
line_limit = llm_arguments.get("line_limit", 100)
|
|
174
|
+
|
|
175
|
+
if not filename:
|
|
176
|
+
raise ValueError("filename is required")
|
|
177
|
+
|
|
178
|
+
normalized_path = _normalize_path(file_path)
|
|
179
|
+
result = await ctx.client.disks.artifacts.get(
|
|
180
|
+
ctx.disk_id,
|
|
181
|
+
file_path=normalized_path,
|
|
182
|
+
filename=filename,
|
|
183
|
+
with_content=True,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if not result.content:
|
|
187
|
+
raise RuntimeError("Failed to read file: server did not return content.")
|
|
188
|
+
|
|
189
|
+
content_str = result.content.raw
|
|
190
|
+
lines = content_str.split("\n")
|
|
191
|
+
line_start = min(line_offset, len(lines) - 1)
|
|
192
|
+
line_end = min(line_start + line_limit, len(lines))
|
|
193
|
+
preview = "\n".join(lines[line_start:line_end])
|
|
194
|
+
return f"[{normalized_path}{filename} - showing L{line_start}-{line_end} of {len(lines)} lines]\n{preview}"
|
|
195
|
+
|
|
141
196
|
|
|
142
197
|
class ReplaceStringTool(BaseTool):
|
|
143
198
|
"""Tool for replacing an old string with a new string in a file on the Acontext disk."""
|
|
@@ -221,6 +276,52 @@ class ReplaceStringTool(BaseTool):
|
|
|
221
276
|
|
|
222
277
|
return f"Found {replacement_count} old_string in {normalized_path}{filename} and replaced it."
|
|
223
278
|
|
|
279
|
+
async def async_execute(self, ctx: AsyncDiskContext, llm_arguments: dict) -> str:
|
|
280
|
+
"""Replace an old string with a new string in a file (async)."""
|
|
281
|
+
filename = llm_arguments.get("filename")
|
|
282
|
+
file_path = llm_arguments.get("file_path")
|
|
283
|
+
old_string = llm_arguments.get("old_string")
|
|
284
|
+
new_string = llm_arguments.get("new_string")
|
|
285
|
+
|
|
286
|
+
if not filename:
|
|
287
|
+
raise ValueError("filename is required")
|
|
288
|
+
if old_string is None:
|
|
289
|
+
raise ValueError("old_string is required")
|
|
290
|
+
if new_string is None:
|
|
291
|
+
raise ValueError("new_string is required")
|
|
292
|
+
|
|
293
|
+
normalized_path = _normalize_path(file_path)
|
|
294
|
+
|
|
295
|
+
# Read the file content
|
|
296
|
+
result = await ctx.client.disks.artifacts.get(
|
|
297
|
+
ctx.disk_id,
|
|
298
|
+
file_path=normalized_path,
|
|
299
|
+
filename=filename,
|
|
300
|
+
with_content=True,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
if not result.content:
|
|
304
|
+
raise RuntimeError("Failed to read file: server did not return content.")
|
|
305
|
+
|
|
306
|
+
content_str = result.content.raw
|
|
307
|
+
|
|
308
|
+
# Perform the replacement
|
|
309
|
+
if old_string not in content_str:
|
|
310
|
+
return f"String '{old_string}' not found in file '{filename}'"
|
|
311
|
+
|
|
312
|
+
updated_content = content_str.replace(old_string, new_string)
|
|
313
|
+
replacement_count = content_str.count(old_string)
|
|
314
|
+
|
|
315
|
+
# Write the updated content back
|
|
316
|
+
payload = FileUpload(filename=filename, content=updated_content.encode("utf-8"))
|
|
317
|
+
await ctx.client.disks.artifacts.upsert(
|
|
318
|
+
ctx.disk_id,
|
|
319
|
+
file=payload,
|
|
320
|
+
file_path=normalized_path,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
return f"Found {replacement_count} old_string in {normalized_path}{filename} and replaced it."
|
|
324
|
+
|
|
224
325
|
|
|
225
326
|
class ListTool(BaseTool):
|
|
226
327
|
"""Tool for listing files in a directory on the Acontext disk."""
|
|
@@ -271,6 +372,31 @@ Directories:
|
|
|
271
372
|
Files:
|
|
272
373
|
{file_sect}"""
|
|
273
374
|
|
|
375
|
+
async def async_execute(self, ctx: AsyncDiskContext, llm_arguments: dict) -> str:
|
|
376
|
+
"""List all files in a specified path (async)."""
|
|
377
|
+
file_path = llm_arguments.get("file_path")
|
|
378
|
+
normalized_path = _normalize_path(file_path)
|
|
379
|
+
|
|
380
|
+
result = await ctx.client.disks.artifacts.list(
|
|
381
|
+
ctx.disk_id,
|
|
382
|
+
path=normalized_path,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
artifacts_list = [artifact.filename for artifact in result.artifacts]
|
|
386
|
+
|
|
387
|
+
if not artifacts_list and not result.directories:
|
|
388
|
+
return f"No files or directories found in '{normalized_path}'"
|
|
389
|
+
|
|
390
|
+
file_sect = "\n".join(artifacts_list) or "[NO FILE]"
|
|
391
|
+
dir_sect = (
|
|
392
|
+
"\n".join([d.rstrip("/") + "/" for d in result.directories]) or "[NO DIR]"
|
|
393
|
+
)
|
|
394
|
+
return f"""[Listing in {normalized_path}]
|
|
395
|
+
Directories:
|
|
396
|
+
{dir_sect}
|
|
397
|
+
Files:
|
|
398
|
+
{file_sect}"""
|
|
399
|
+
|
|
274
400
|
|
|
275
401
|
class DownloadFileTool(BaseTool):
|
|
276
402
|
"""Tool for getting a public download URL for a file on the Acontext disk."""
|
|
@@ -327,6 +453,29 @@ class DownloadFileTool(BaseTool):
|
|
|
327
453
|
|
|
328
454
|
return f"Public download URL for '{normalized_path}{filename}' (expires in {expire}s):\n{result.public_url}"
|
|
329
455
|
|
|
456
|
+
async def async_execute(self, ctx: AsyncDiskContext, llm_arguments: dict) -> str:
|
|
457
|
+
"""Get a public download URL for a file (async)."""
|
|
458
|
+
filename = llm_arguments.get("filename")
|
|
459
|
+
file_path = llm_arguments.get("file_path")
|
|
460
|
+
expire = llm_arguments.get("expire", 3600)
|
|
461
|
+
|
|
462
|
+
if not filename:
|
|
463
|
+
raise ValueError("filename is required")
|
|
464
|
+
|
|
465
|
+
normalized_path = _normalize_path(file_path)
|
|
466
|
+
result = await ctx.client.disks.artifacts.get(
|
|
467
|
+
ctx.disk_id,
|
|
468
|
+
file_path=normalized_path,
|
|
469
|
+
filename=filename,
|
|
470
|
+
with_public_url=True,
|
|
471
|
+
expire=expire,
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
if not result.public_url:
|
|
475
|
+
raise RuntimeError("Failed to get public URL: server did not return a URL.")
|
|
476
|
+
|
|
477
|
+
return f"Public download URL for '{normalized_path}{filename}' (expires in {expire}s):\n{result.public_url}"
|
|
478
|
+
|
|
330
479
|
|
|
331
480
|
class GrepArtifactsTool(BaseTool):
|
|
332
481
|
"""Tool for searching artifact content using regex patterns."""
|
|
@@ -377,7 +526,34 @@ class GrepArtifactsTool(BaseTool):
|
|
|
377
526
|
for artifact in results:
|
|
378
527
|
matches.append(f"{artifact.path}{artifact.filename}")
|
|
379
528
|
|
|
380
|
-
return f"Found {len(matches)} file(s) matching '{query}':\n" + "\n".join(
|
|
529
|
+
return f"Found {len(matches)} file(s) matching '{query}':\n" + "\n".join(
|
|
530
|
+
matches
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
async def async_execute(self, ctx: AsyncDiskContext, llm_arguments: dict) -> str:
|
|
534
|
+
"""Search artifact content using regex pattern (async)."""
|
|
535
|
+
query = llm_arguments.get("query")
|
|
536
|
+
limit = llm_arguments.get("limit", 100)
|
|
537
|
+
|
|
538
|
+
if not query:
|
|
539
|
+
raise ValueError("query is required")
|
|
540
|
+
|
|
541
|
+
results = await ctx.client.disks.artifacts.grep_artifacts(
|
|
542
|
+
ctx.disk_id,
|
|
543
|
+
query=query,
|
|
544
|
+
limit=limit,
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
if not results:
|
|
548
|
+
return f"No matches found for pattern '{query}'"
|
|
549
|
+
|
|
550
|
+
matches = []
|
|
551
|
+
for artifact in results:
|
|
552
|
+
matches.append(f"{artifact.path}{artifact.filename}")
|
|
553
|
+
|
|
554
|
+
return f"Found {len(matches)} file(s) matching '{query}':\n" + "\n".join(
|
|
555
|
+
matches
|
|
556
|
+
)
|
|
381
557
|
|
|
382
558
|
|
|
383
559
|
class GlobArtifactsTool(BaseTool):
|
|
@@ -429,7 +605,34 @@ class GlobArtifactsTool(BaseTool):
|
|
|
429
605
|
for artifact in results:
|
|
430
606
|
matches.append(f"{artifact.path}{artifact.filename}")
|
|
431
607
|
|
|
432
|
-
return f"Found {len(matches)} file(s) matching '{query}':\n" + "\n".join(
|
|
608
|
+
return f"Found {len(matches)} file(s) matching '{query}':\n" + "\n".join(
|
|
609
|
+
matches
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
async def async_execute(self, ctx: AsyncDiskContext, llm_arguments: dict) -> str:
|
|
613
|
+
"""Search artifact paths using glob pattern (async)."""
|
|
614
|
+
query = llm_arguments.get("query")
|
|
615
|
+
limit = llm_arguments.get("limit", 100)
|
|
616
|
+
|
|
617
|
+
if not query:
|
|
618
|
+
raise ValueError("query is required")
|
|
619
|
+
|
|
620
|
+
results = await ctx.client.disks.artifacts.glob_artifacts(
|
|
621
|
+
ctx.disk_id,
|
|
622
|
+
query=query,
|
|
623
|
+
limit=limit,
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
if not results:
|
|
627
|
+
return f"No files found matching pattern '{query}'"
|
|
628
|
+
|
|
629
|
+
matches = []
|
|
630
|
+
for artifact in results:
|
|
631
|
+
matches.append(f"{artifact.path}{artifact.filename}")
|
|
632
|
+
|
|
633
|
+
return f"Found {len(matches)} file(s) matching '{query}':\n" + "\n".join(
|
|
634
|
+
matches
|
|
635
|
+
)
|
|
433
636
|
|
|
434
637
|
|
|
435
638
|
class DiskToolPool(BaseToolPool):
|
|
@@ -438,6 +641,11 @@ class DiskToolPool(BaseToolPool):
|
|
|
438
641
|
def format_context(self, client: AcontextClient, disk_id: str) -> DiskContext:
|
|
439
642
|
return DiskContext(client=client, disk_id=disk_id)
|
|
440
643
|
|
|
644
|
+
async def async_format_context(
|
|
645
|
+
self, client: AcontextAsyncClient, disk_id: str
|
|
646
|
+
) -> AsyncDiskContext:
|
|
647
|
+
return AsyncDiskContext(client=client, disk_id=disk_id)
|
|
648
|
+
|
|
441
649
|
|
|
442
650
|
DISK_TOOLS = DiskToolPool()
|
|
443
651
|
DISK_TOOLS.add_tool(WriteFileTool())
|
|
@@ -447,48 +655,3 @@ DISK_TOOLS.add_tool(ListTool())
|
|
|
447
655
|
DISK_TOOLS.add_tool(GrepArtifactsTool())
|
|
448
656
|
DISK_TOOLS.add_tool(GlobArtifactsTool())
|
|
449
657
|
DISK_TOOLS.add_tool(DownloadFileTool())
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
if __name__ == "__main__":
|
|
453
|
-
client = AcontextClient(
|
|
454
|
-
api_key="sk-ac-your-root-api-bearer-token",
|
|
455
|
-
base_url="http://localhost:8029/api/v1",
|
|
456
|
-
)
|
|
457
|
-
print(client.ping())
|
|
458
|
-
new_disk = client.disks.create()
|
|
459
|
-
|
|
460
|
-
ctx = DISK_TOOLS.format_context(client, new_disk.id)
|
|
461
|
-
r = DISK_TOOLS.execute_tool(
|
|
462
|
-
ctx,
|
|
463
|
-
"write_file",
|
|
464
|
-
{"filename": "test.txt", "file_path": "/try/", "content": "Hello, world!"},
|
|
465
|
-
)
|
|
466
|
-
print(r)
|
|
467
|
-
r = DISK_TOOLS.execute_tool(
|
|
468
|
-
ctx, "read_file", {"filename": "test.txt", "file_path": "/try/"}
|
|
469
|
-
)
|
|
470
|
-
print(r)
|
|
471
|
-
r = DISK_TOOLS.execute_tool(ctx, "list_artifacts", {"file_path": "/"})
|
|
472
|
-
print(r)
|
|
473
|
-
|
|
474
|
-
r = DISK_TOOLS.execute_tool(
|
|
475
|
-
ctx,
|
|
476
|
-
"replace_string",
|
|
477
|
-
{
|
|
478
|
-
"filename": "test.txt",
|
|
479
|
-
"file_path": "/try/",
|
|
480
|
-
"old_string": "Hello",
|
|
481
|
-
"new_string": "Hi",
|
|
482
|
-
},
|
|
483
|
-
)
|
|
484
|
-
print(r)
|
|
485
|
-
r = DISK_TOOLS.execute_tool(
|
|
486
|
-
ctx, "read_file", {"filename": "test.txt", "file_path": "/try/"}
|
|
487
|
-
)
|
|
488
|
-
print(r)
|
|
489
|
-
r = DISK_TOOLS.execute_tool(
|
|
490
|
-
ctx,
|
|
491
|
-
"download_file",
|
|
492
|
-
{"filename": "test.txt", "file_path": "/try/", "expire": 300},
|
|
493
|
-
)
|
|
494
|
-
print(r)
|
|
@@ -6,6 +6,7 @@ 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
|
|
9
10
|
from ..types.skill import Skill
|
|
10
11
|
|
|
11
12
|
|
|
@@ -65,6 +66,64 @@ class SkillContext(BaseContext):
|
|
|
65
66
|
return list(self.skills.keys())
|
|
66
67
|
|
|
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())
|
|
125
|
+
|
|
126
|
+
|
|
68
127
|
class GetSkillTool(BaseTool):
|
|
69
128
|
"""Tool for getting a skill by name."""
|
|
70
129
|
|
|
@@ -121,6 +180,35 @@ class GetSkillTool(BaseTool):
|
|
|
121
180
|
f"{file_list}"
|
|
122
181
|
)
|
|
123
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")
|
|
186
|
+
|
|
187
|
+
if not skill_name:
|
|
188
|
+
raise ValueError("skill_name is required")
|
|
189
|
+
|
|
190
|
+
skill = ctx.get_skill(skill_name)
|
|
191
|
+
|
|
192
|
+
file_count = len(skill.file_index)
|
|
193
|
+
|
|
194
|
+
# Format all files with path and MIME type
|
|
195
|
+
if skill.file_index:
|
|
196
|
+
file_list = "\n".join(
|
|
197
|
+
[
|
|
198
|
+
f" - {file_info.path} ({file_info.mime})"
|
|
199
|
+
for file_info in skill.file_index
|
|
200
|
+
]
|
|
201
|
+
)
|
|
202
|
+
else:
|
|
203
|
+
file_list = " [NO FILES]"
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
f"Skill: {skill.name} (ID: {skill.id})\n"
|
|
207
|
+
f"Description: {skill.description}\n"
|
|
208
|
+
f"Files: {file_count} file(s)\n"
|
|
209
|
+
f"{file_list}"
|
|
210
|
+
)
|
|
211
|
+
|
|
124
212
|
|
|
125
213
|
class GetSkillFileTool(BaseTool):
|
|
126
214
|
"""Tool for getting a file from a skill."""
|
|
@@ -197,6 +285,45 @@ class GetSkillFileTool(BaseTool):
|
|
|
197
285
|
|
|
198
286
|
return "\n".join(output_parts)
|
|
199
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
|
+
|
|
294
|
+
if not skill_name:
|
|
295
|
+
raise ValueError("skill_name is required")
|
|
296
|
+
if not file_path:
|
|
297
|
+
raise ValueError("file_path is required")
|
|
298
|
+
|
|
299
|
+
skill = ctx.get_skill(skill_name)
|
|
300
|
+
|
|
301
|
+
result = await ctx.client.skills.get_file(
|
|
302
|
+
skill_id=skill.id,
|
|
303
|
+
file_path=file_path,
|
|
304
|
+
expire=expire,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
output_parts = [
|
|
308
|
+
f"File '{result.path}' (MIME: {result.mime}) from skill '{skill_name}':"
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
if result.content:
|
|
312
|
+
output_parts.append(f"\nContent (type: {result.content.type}):")
|
|
313
|
+
output_parts.append(result.content.raw)
|
|
314
|
+
|
|
315
|
+
if result.url:
|
|
316
|
+
expire_seconds = expire if expire is not None else 900
|
|
317
|
+
output_parts.append(
|
|
318
|
+
f"\nDownload URL (expires in {expire_seconds} seconds):"
|
|
319
|
+
)
|
|
320
|
+
output_parts.append(result.url)
|
|
321
|
+
|
|
322
|
+
if not result.content and not result.url:
|
|
323
|
+
return f"File '{result.path}' retrieved but no content or URL returned."
|
|
324
|
+
|
|
325
|
+
return "\n".join(output_parts)
|
|
326
|
+
|
|
200
327
|
|
|
201
328
|
class ListSkillsTool(BaseTool):
|
|
202
329
|
"""Tool for listing available skills in the context."""
|
|
@@ -228,6 +355,17 @@ class ListSkillsTool(BaseTool):
|
|
|
228
355
|
|
|
229
356
|
return f"Available skills ({len(ctx.skills)}):\n" + "\n".join(skill_list)
|
|
230
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
|
+
|
|
231
369
|
|
|
232
370
|
class SkillToolPool(BaseToolPool):
|
|
233
371
|
"""Tool pool for skill operations on Acontext skills."""
|
|
@@ -246,6 +384,20 @@ class SkillToolPool(BaseToolPool):
|
|
|
246
384
|
"""
|
|
247
385
|
return SkillContext.create(client=client, skill_ids=skill_ids)
|
|
248
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)
|
|
400
|
+
|
|
249
401
|
|
|
250
402
|
SKILL_TOOLS = SkillToolPool()
|
|
251
403
|
SKILL_TOOLS.add_tool(ListSkillsTool())
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Disk and artifact endpoints (async).
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
5
7
|
import json
|
|
6
8
|
from collections.abc import Mapping
|
|
7
9
|
from typing import Any, BinaryIO, cast
|
|
@@ -33,26 +35,28 @@ class AsyncDisksAPI:
|
|
|
33
35
|
time_desc: bool | None = None,
|
|
34
36
|
) -> ListDisksOutput:
|
|
35
37
|
"""List all disks in the project.
|
|
36
|
-
|
|
38
|
+
|
|
37
39
|
Args:
|
|
38
40
|
user: Filter by user identifier. Defaults to None.
|
|
39
41
|
limit: Maximum number of disks to return. Defaults to None.
|
|
40
42
|
cursor: Cursor for pagination. Defaults to None.
|
|
41
43
|
time_desc: Order by created_at descending if True, ascending if False. Defaults to None.
|
|
42
|
-
|
|
44
|
+
|
|
43
45
|
Returns:
|
|
44
46
|
ListDisksOutput containing the list of disks and pagination information.
|
|
45
47
|
"""
|
|
46
|
-
params = build_params(
|
|
48
|
+
params = build_params(
|
|
49
|
+
user=user, limit=limit, cursor=cursor, time_desc=time_desc
|
|
50
|
+
)
|
|
47
51
|
data = await self._requester.request("GET", "/disk", params=params or None)
|
|
48
52
|
return ListDisksOutput.model_validate(data)
|
|
49
53
|
|
|
50
54
|
async def create(self, *, user: str | None = None) -> Disk:
|
|
51
55
|
"""Create a new disk.
|
|
52
|
-
|
|
56
|
+
|
|
53
57
|
Args:
|
|
54
58
|
user: Optional user identifier string. Defaults to None.
|
|
55
|
-
|
|
59
|
+
|
|
56
60
|
Returns:
|
|
57
61
|
The created Disk object.
|
|
58
62
|
"""
|
|
@@ -64,7 +68,7 @@ class AsyncDisksAPI:
|
|
|
64
68
|
|
|
65
69
|
async def delete(self, disk_id: str) -> None:
|
|
66
70
|
"""Delete a disk by its ID.
|
|
67
|
-
|
|
71
|
+
|
|
68
72
|
Args:
|
|
69
73
|
disk_id: The UUID of the disk to delete.
|
|
70
74
|
"""
|
|
@@ -79,20 +83,22 @@ class AsyncDiskArtifactsAPI:
|
|
|
79
83
|
self,
|
|
80
84
|
disk_id: str,
|
|
81
85
|
*,
|
|
82
|
-
file:
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
file: (
|
|
87
|
+
FileUpload
|
|
88
|
+
| tuple[str, BinaryIO | bytes]
|
|
89
|
+
| tuple[str, BinaryIO | bytes, str]
|
|
90
|
+
),
|
|
85
91
|
file_path: str | None = None,
|
|
86
92
|
meta: Mapping[str, Any] | None = None,
|
|
87
93
|
) -> Artifact:
|
|
88
94
|
"""Upload a file to create or update an artifact.
|
|
89
|
-
|
|
95
|
+
|
|
90
96
|
Args:
|
|
91
97
|
disk_id: The UUID of the disk.
|
|
92
98
|
file: The file to upload (FileUpload object or tuple format).
|
|
93
99
|
file_path: Directory path (not including filename), defaults to "/".
|
|
94
100
|
meta: Custom metadata as JSON-serializable dict, defaults to None.
|
|
95
|
-
|
|
101
|
+
|
|
96
102
|
Returns:
|
|
97
103
|
Artifact containing the created/updated artifact information.
|
|
98
104
|
"""
|
|
@@ -122,7 +128,7 @@ class AsyncDiskArtifactsAPI:
|
|
|
122
128
|
expire: int | None = None,
|
|
123
129
|
) -> GetArtifactResp:
|
|
124
130
|
"""Get an artifact by disk ID, file path, and filename.
|
|
125
|
-
|
|
131
|
+
|
|
126
132
|
Args:
|
|
127
133
|
disk_id: The UUID of the disk.
|
|
128
134
|
file_path: Directory path (not including filename).
|
|
@@ -130,7 +136,7 @@ class AsyncDiskArtifactsAPI:
|
|
|
130
136
|
with_public_url: Whether to include a presigned public URL. Defaults to None.
|
|
131
137
|
with_content: Whether to include file content. Defaults to None.
|
|
132
138
|
expire: URL expiration time in seconds. Defaults to None.
|
|
133
|
-
|
|
139
|
+
|
|
134
140
|
Returns:
|
|
135
141
|
GetArtifactResp containing the artifact and optionally public URL and content.
|
|
136
142
|
"""
|
|
@@ -141,7 +147,9 @@ class AsyncDiskArtifactsAPI:
|
|
|
141
147
|
with_content=with_content,
|
|
142
148
|
expire=expire,
|
|
143
149
|
)
|
|
144
|
-
data = await self._requester.request(
|
|
150
|
+
data = await self._requester.request(
|
|
151
|
+
"GET", f"/disk/{disk_id}/artifact", params=params
|
|
152
|
+
)
|
|
145
153
|
return GetArtifactResp.model_validate(data)
|
|
146
154
|
|
|
147
155
|
async def update(
|
|
@@ -153,13 +161,13 @@ class AsyncDiskArtifactsAPI:
|
|
|
153
161
|
meta: Mapping[str, Any],
|
|
154
162
|
) -> UpdateArtifactResp:
|
|
155
163
|
"""Update an artifact's metadata.
|
|
156
|
-
|
|
164
|
+
|
|
157
165
|
Args:
|
|
158
166
|
disk_id: The UUID of the disk.
|
|
159
167
|
file_path: Directory path (not including filename).
|
|
160
168
|
filename: The filename of the artifact.
|
|
161
169
|
meta: Custom metadata as JSON-serializable dict.
|
|
162
|
-
|
|
170
|
+
|
|
163
171
|
Returns:
|
|
164
172
|
UpdateArtifactResp containing the updated artifact information.
|
|
165
173
|
"""
|
|
@@ -168,7 +176,9 @@ class AsyncDiskArtifactsAPI:
|
|
|
168
176
|
"file_path": full_path,
|
|
169
177
|
"meta": json.dumps(cast(Mapping[str, Any], meta)),
|
|
170
178
|
}
|
|
171
|
-
data = await self._requester.request(
|
|
179
|
+
data = await self._requester.request(
|
|
180
|
+
"PUT", f"/disk/{disk_id}/artifact", json_data=payload
|
|
181
|
+
)
|
|
172
182
|
return UpdateArtifactResp.model_validate(data)
|
|
173
183
|
|
|
174
184
|
async def delete(
|
|
@@ -179,7 +189,7 @@ class AsyncDiskArtifactsAPI:
|
|
|
179
189
|
filename: str,
|
|
180
190
|
) -> None:
|
|
181
191
|
"""Delete an artifact by disk ID, file path, and filename.
|
|
182
|
-
|
|
192
|
+
|
|
183
193
|
Args:
|
|
184
194
|
disk_id: The UUID of the disk.
|
|
185
195
|
file_path: Directory path (not including filename).
|
|
@@ -187,7 +197,9 @@ class AsyncDiskArtifactsAPI:
|
|
|
187
197
|
"""
|
|
188
198
|
full_path = f"{file_path.rstrip('/')}/{filename}"
|
|
189
199
|
params = {"file_path": full_path}
|
|
190
|
-
await self._requester.request(
|
|
200
|
+
await self._requester.request(
|
|
201
|
+
"DELETE", f"/disk/{disk_id}/artifact", params=params
|
|
202
|
+
)
|
|
191
203
|
|
|
192
204
|
async def list(
|
|
193
205
|
self,
|
|
@@ -195,9 +207,83 @@ class AsyncDiskArtifactsAPI:
|
|
|
195
207
|
*,
|
|
196
208
|
path: str | None = None,
|
|
197
209
|
) -> ListArtifactsResp:
|
|
210
|
+
"""List artifacts in a disk at a specific path.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
disk_id: The UUID of the disk.
|
|
214
|
+
path: Directory path to list. Defaults to None (root).
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
ListArtifactsResp containing the list of artifacts.
|
|
218
|
+
"""
|
|
198
219
|
params: dict[str, Any] = {}
|
|
199
220
|
if path is not None:
|
|
200
221
|
params["path"] = path
|
|
201
|
-
data = await self._requester.request(
|
|
222
|
+
data = await self._requester.request(
|
|
223
|
+
"GET", f"/disk/{disk_id}/artifact/ls", params=params or None
|
|
224
|
+
)
|
|
202
225
|
return ListArtifactsResp.model_validate(data)
|
|
203
226
|
|
|
227
|
+
async def grep_artifacts(
|
|
228
|
+
self,
|
|
229
|
+
disk_id: str,
|
|
230
|
+
*,
|
|
231
|
+
query: str,
|
|
232
|
+
limit: int = 100,
|
|
233
|
+
) -> list[Artifact]:
|
|
234
|
+
"""Search artifact content using regex pattern.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
disk_id: The disk ID to search in
|
|
238
|
+
query: Regex pattern to search for in file content
|
|
239
|
+
limit: Maximum number of results (default 100, max 1000)
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
List of matching artifacts
|
|
243
|
+
|
|
244
|
+
Example:
|
|
245
|
+
```python
|
|
246
|
+
# Search for TODO comments in code
|
|
247
|
+
results = await client.disks.artifacts.grep_artifacts(
|
|
248
|
+
disk_id="disk-uuid",
|
|
249
|
+
query="TODO.*bug"
|
|
250
|
+
)
|
|
251
|
+
```
|
|
252
|
+
"""
|
|
253
|
+
params = build_params(query=query, limit=limit)
|
|
254
|
+
data = await self._requester.request(
|
|
255
|
+
"GET", f"/disk/{disk_id}/artifact/grep", params=params
|
|
256
|
+
)
|
|
257
|
+
return [Artifact.model_validate(item) for item in data]
|
|
258
|
+
|
|
259
|
+
async def glob_artifacts(
|
|
260
|
+
self,
|
|
261
|
+
disk_id: str,
|
|
262
|
+
*,
|
|
263
|
+
query: str,
|
|
264
|
+
limit: int = 100,
|
|
265
|
+
) -> list[Artifact]:
|
|
266
|
+
"""Search artifact paths using glob pattern.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
disk_id: The disk ID to search in
|
|
270
|
+
query: Glob pattern (e.g., '**/*.py', '*.txt')
|
|
271
|
+
limit: Maximum number of results (default 100, max 1000)
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
List of matching artifacts
|
|
275
|
+
|
|
276
|
+
Example:
|
|
277
|
+
```python
|
|
278
|
+
# Find all Python files
|
|
279
|
+
results = await client.disks.artifacts.glob_artifacts(
|
|
280
|
+
disk_id="disk-uuid",
|
|
281
|
+
query="**/*.py"
|
|
282
|
+
)
|
|
283
|
+
```
|
|
284
|
+
"""
|
|
285
|
+
params = build_params(query=query, limit=limit)
|
|
286
|
+
data = await self._requester.request(
|
|
287
|
+
"GET", f"/disk/{disk_id}/artifact/glob", params=params
|
|
288
|
+
)
|
|
289
|
+
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)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|