acontext 0.0.14__py3-none-any.whl → 0.0.16__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.
@@ -0,0 +1,10 @@
1
+ """Agent tools for LLM function calling."""
2
+
3
+ from .disk import DISK_TOOLS
4
+ from .skill import SKILL_TOOLS
5
+
6
+ __all__ = [
7
+ "DISK_TOOLS",
8
+ "SKILL_TOOLS",
9
+ ]
10
+
@@ -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())
acontext/async_client.py CHANGED
@@ -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:
acontext/client.py CHANGED
@@ -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
- data = await self._requester.request("POST", "/disk")
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
- """Get message observing status counts for a session.
353
-
354
- Returns the count of messages by their observing status:
355
- observed, in_process, and pending.
356
-
357
- Args:
358
- session_id: The UUID of the session.
359
-
360
- Returns:
361
- MessageObservingStatus object containing observed, in_process,
362
- pending counts and updated_at timestamp.
363
- """
364
- data = await self._requester.request(
365
- "GET", f"/session/{session_id}/observing_status"
366
- )
367
- return MessageObservingStatus.model_validate(data)
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
+
@@ -23,6 +23,7 @@ class AsyncSpacesAPI:
23
23
  async def list(
24
24
  self,
25
25
  *,
26
+ user: str | None = None,
26
27
  limit: int | None = None,
27
28
  cursor: str | None = None,
28
29
  time_desc: bool | None = None,
@@ -30,6 +31,7 @@ class AsyncSpacesAPI:
30
31
  """List all spaces in the project.
31
32
 
32
33
  Args:
34
+ user: Filter by user identifier. Defaults to None.
33
35
  limit: Maximum number of spaces to return. Defaults to None.
34
36
  cursor: Cursor for pagination. Defaults to None.
35
37
  time_desc: Order by created_at descending if True, ascending if False. Defaults to None.
@@ -37,20 +39,28 @@ class AsyncSpacesAPI:
37
39
  Returns:
38
40
  ListSpacesOutput containing the list of spaces and pagination information.
39
41
  """
40
- params = build_params(limit=limit, cursor=cursor, time_desc=time_desc)
42
+ params = build_params(user=user, limit=limit, cursor=cursor, time_desc=time_desc)
41
43
  data = await self._requester.request("GET", "/space", params=params or None)
42
44
  return ListSpacesOutput.model_validate(data)
43
45
 
44
- async def create(self, *, configs: Mapping[str, Any] | None = None) -> Space:
46
+ async def create(
47
+ self,
48
+ *,
49
+ user: str | None = None,
50
+ configs: Mapping[str, Any] | None = None,
51
+ ) -> Space:
45
52
  """Create a new space.
46
53
 
47
54
  Args:
55
+ user: Optional user identifier string. Defaults to None.
48
56
  configs: Optional space configuration dictionary. Defaults to None.
49
57
 
50
58
  Returns:
51
59
  The created Space object.
52
60
  """
53
61
  payload: dict[str, Any] = {}
62
+ if user is not None:
63
+ payload["user"] = user
54
64
  if configs is not None:
55
65
  payload["configs"] = configs
56
66
  data = await self._requester.request("POST", "/space", json_data=payload)
@@ -0,0 +1,20 @@
1
+ """
2
+ User management endpoints (async).
3
+ """
4
+
5
+ from urllib.parse import quote
6
+
7
+ from ..client_types import AsyncRequesterProtocol
8
+
9
+
10
+ class AsyncUsersAPI:
11
+ def __init__(self, requester: AsyncRequesterProtocol) -> None:
12
+ self._requester = requester
13
+
14
+ async def delete(self, identifier: str) -> None:
15
+ """Delete a user and cascade delete all associated resources (Space, Session, Disk, Skill).
16
+
17
+ Args:
18
+ identifier: The user identifier string.
19
+ """
20
+ await self._requester.request("DELETE", f"/user/{quote(identifier, safe='')}")
@@ -27,6 +27,7 @@ class DisksAPI:
27
27
  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 DisksAPI:
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 DisksAPI:
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 = self._requester.request("GET", "/disk", params=params or None)
46
48
  return ListDisksOutput.model_validate(data)
47
49
 
48
- def create(self) -> Disk:
50
+ 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
- data = self._requester.request("POST", "/disk")
59
+ payload: dict[str, Any] = {}
60
+ if user is not None:
61
+ payload["user"] = user
62
+ data = self._requester.request("POST", "/disk", json_data=payload or None)
55
63
  return Disk.model_validate(data)
56
64
 
57
65
  def delete(self, disk_id: str) -> None:
@@ -37,6 +37,7 @@ class SessionsAPI:
37
37
  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 SessionsAPI:
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 SessionsAPI:
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,6 +76,7 @@ class SessionsAPI:
72
76
  def create(
73
77
  self,
74
78
  *,
79
+ user: str | None = None,
75
80
  space_id: str | None = None,
76
81
  disable_task_tracking: bool | None = None,
77
82
  configs: Mapping[str, Any] | None = None,
@@ -79,6 +84,7 @@ class SessionsAPI:
79
84
  """Create a new session.
80
85
 
81
86
  Args:
87
+ user: Optional user identifier string. Defaults to None.
82
88
  space_id: Optional space ID to associate with the session. Defaults to None.
83
89
  disable_task_tracking: Whether to disable task tracking for this session. Defaults to None (server default: False).
84
90
  configs: Optional session configuration dictionary. Defaults to None.
@@ -87,6 +93,8 @@ class SessionsAPI:
87
93
  The created Session object.
88
94
  """
89
95
  payload: dict[str, Any] = {}
96
+ if user:
97
+ payload["user"] = user
90
98
  if space_id:
91
99
  payload["space_id"] = space_id
92
100
  if disable_task_tracking is not None:
@@ -271,6 +279,7 @@ class SessionsAPI:
271
279
  format: Literal["acontext", "openai", "anthropic", "gemini"] = "openai",
272
280
  time_desc: bool | None = None,
273
281
  edit_strategies: Optional[List[EditStrategy]] = None,
282
+ pin_editing_strategies_at_message: str | None = None,
274
283
  ) -> GetMessagesOutput:
275
284
  """Get messages for a session.
276
285
 
@@ -287,6 +296,12 @@ class SessionsAPI:
287
296
  - Remove tool results: [{"type": "remove_tool_result", "params": {"keep_recent_n_tool_results": 3}}]
288
297
  - Token limit: [{"type": "token_limit", "params": {"limit_tokens": 20000}}]
289
298
  Defaults to None.
299
+ pin_editing_strategies_at_message: Message ID to pin editing strategies at.
300
+ When provided, strategies are only applied to messages up to and including
301
+ this message ID, keeping subsequent messages unchanged. This helps maintain
302
+ prompt cache stability by preserving a stable prefix. The response includes
303
+ edit_at_message_id indicating where strategies were applied. Pass this value
304
+ in subsequent requests to maintain cache hits. Defaults to None.
290
305
 
291
306
  Returns:
292
307
  GetMessagesOutput containing the list of messages and pagination information.
@@ -304,6 +319,8 @@ class SessionsAPI:
304
319
  )
305
320
  if edit_strategies is not None:
306
321
  params["edit_strategies"] = json.dumps(edit_strategies)
322
+ if pin_editing_strategies_at_message is not None:
323
+ params["pin_editing_strategies_at_message"] = pin_editing_strategies_at_message
307
324
  data = self._requester.request(
308
325
  "GET", f"/session/{session_id}/messages", params=params or None
309
326
  )
@@ -0,0 +1,146 @@
1
+ """
2
+ Skills endpoints.
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 RequesterProtocol
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 SkillsAPI:
22
+ def __init__(self, requester: RequesterProtocol) -> None:
23
+ self._requester = requester
24
+
25
+ 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 = 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
+ 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 = self._requester.request("GET", "/agent_skills", params=params or None)
85
+ api_response = _ListSkillsResponse.model_validate(data)
86
+
87
+ # Convert to catalog format (name and description only)
88
+ return ListSkillsOutput(
89
+ items=[
90
+ SkillCatalogItem(name=skill.name, description=skill.description)
91
+ for skill in api_response.items
92
+ ],
93
+ next_cursor=api_response.next_cursor,
94
+ has_more=api_response.has_more,
95
+ )
96
+
97
+ def get_by_name(self, name: str) -> Skill:
98
+ """Get a skill by its name.
99
+
100
+ Args:
101
+ name: The name of the skill (unique within project).
102
+
103
+ Returns:
104
+ Skill containing the skill information.
105
+ """
106
+ params = {"name": name}
107
+ data = self._requester.request("GET", "/agent_skills/by_name", params=params)
108
+ return Skill.model_validate(data)
109
+
110
+ def delete(self, skill_id: str) -> None:
111
+ """Delete a skill by its ID.
112
+
113
+ Args:
114
+ skill_id: The UUID of the skill to delete.
115
+ """
116
+ self._requester.request("DELETE", f"/agent_skills/{skill_id}")
117
+
118
+ def get_file_by_name(
119
+ self,
120
+ *,
121
+ skill_name: str,
122
+ file_path: str,
123
+ expire: int | None = None,
124
+ ) -> GetSkillFileResp:
125
+ """Get a file from a skill by name.
126
+
127
+ The backend automatically returns content for parseable text files, or a presigned URL
128
+ for non-parseable files (binary, images, etc.).
129
+
130
+ Args:
131
+ skill_name: The name of the skill.
132
+ file_path: Relative path to the file within the skill (e.g., 'scripts/extract_text.json').
133
+ expire: URL expiration time in seconds. Defaults to 900 (15 minutes).
134
+
135
+ Returns:
136
+ GetSkillFileResp containing the file path, MIME type, and either content or URL.
137
+ """
138
+ endpoint = f"/agent_skills/by_name/{skill_name}/file"
139
+
140
+ params = {"file_path": file_path}
141
+ if expire is not None:
142
+ params["expire"] = expire
143
+
144
+ data = self._requester.request("GET", endpoint, params=params)
145
+ return GetSkillFileResp.model_validate(data)
146
+
@@ -23,6 +23,7 @@ class SpacesAPI:
23
23
  def list(
24
24
  self,
25
25
  *,
26
+ user: str | None = None,
26
27
  limit: int | None = None,
27
28
  cursor: str | None = None,
28
29
  time_desc: bool | None = None,
@@ -30,6 +31,7 @@ class SpacesAPI:
30
31
  """List all spaces in the project.
31
32
 
32
33
  Args:
34
+ user: Filter by user identifier. Defaults to None.
33
35
  limit: Maximum number of spaces to return. Defaults to None.
34
36
  cursor: Cursor for pagination. Defaults to None.
35
37
  time_desc: Order by created_at descending if True, ascending if False. Defaults to None.
@@ -37,20 +39,28 @@ class SpacesAPI:
37
39
  Returns:
38
40
  ListSpacesOutput containing the list of spaces and pagination information.
39
41
  """
40
- params = build_params(limit=limit, cursor=cursor, time_desc=time_desc)
42
+ params = build_params(user=user, limit=limit, cursor=cursor, time_desc=time_desc)
41
43
  data = self._requester.request("GET", "/space", params=params or None)
42
44
  return ListSpacesOutput.model_validate(data)
43
45
 
44
- def create(self, *, configs: Mapping[str, Any] | None = None) -> Space:
46
+ def create(
47
+ self,
48
+ *,
49
+ user: str | None = None,
50
+ configs: Mapping[str, Any] | None = None,
51
+ ) -> Space:
45
52
  """Create a new space.
46
53
 
47
54
  Args:
55
+ user: Optional user identifier string. Defaults to None.
48
56
  configs: Optional space configuration dictionary. Defaults to None.
49
57
 
50
58
  Returns:
51
59
  The created Space object.
52
60
  """
53
61
  payload: dict[str, Any] = {}
62
+ if user is not None:
63
+ payload["user"] = user
54
64
  if configs is not None:
55
65
  payload["configs"] = configs
56
66
  data = self._requester.request("POST", "/space", json_data=payload)
@@ -0,0 +1,20 @@
1
+ """
2
+ User management endpoints.
3
+ """
4
+
5
+ from urllib.parse import quote
6
+
7
+ from ..client_types import RequesterProtocol
8
+
9
+
10
+ class UsersAPI:
11
+ def __init__(self, requester: RequesterProtocol) -> None:
12
+ self._requester = requester
13
+
14
+ def delete(self, identifier: str) -> None:
15
+ """Delete a user and cascade delete all associated resources (Space, Session, Disk, Skill).
16
+
17
+ Args:
18
+ identifier: The user identifier string.
19
+ """
20
+ self._requester.request("DELETE", f"/user/{quote(identifier, safe='')}")
@@ -1,9 +1,9 @@
1
1
  """Type definitions for API responses."""
2
2
 
3
+ from .common import FileContent
3
4
  from .disk import (
4
5
  Artifact,
5
6
  Disk,
6
- FileContent,
7
7
  GetArtifactResp,
8
8
  ListArtifactsResp,
9
9
  ListDisksOutput,
@@ -38,6 +38,13 @@ from .tool import (
38
38
  ToolReferenceData,
39
39
  ToolRenameItem,
40
40
  )
41
+ from .skill import (
42
+ FileInfo,
43
+ GetSkillFileResp,
44
+ ListSkillsOutput,
45
+ Skill,
46
+ SkillCatalogItem,
47
+ )
41
48
 
42
49
  __all__ = [
43
50
  # Disk types
@@ -75,4 +82,10 @@ __all__ = [
75
82
  "InsertBlockResponse",
76
83
  "ToolReferenceData",
77
84
  "ToolRenameItem",
85
+ # Skill types
86
+ "FileInfo",
87
+ "Skill",
88
+ "SkillCatalogItem",
89
+ "ListSkillsOutput",
90
+ "GetSkillFileResp",
78
91
  ]
@@ -0,0 +1,11 @@
1
+ """Common type definitions shared across modules."""
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class FileContent(BaseModel):
7
+ """Parsed file content model."""
8
+
9
+ type: str = Field(..., description="File content type: 'text', 'json', 'csv', or 'code'")
10
+ raw: str = Field(..., description="Raw text content of the file")
11
+
acontext/types/disk.py CHANGED
@@ -4,12 +4,15 @@ from typing import Any
4
4
 
5
5
  from pydantic import BaseModel, Field
6
6
 
7
+ from .common import FileContent
8
+
7
9
 
8
10
  class Disk(BaseModel):
9
11
  """Disk model representing a disk resource."""
10
12
 
11
13
  id: str = Field(..., description="Disk UUID")
12
14
  project_id: str = Field(..., description="Project UUID")
15
+ user_id: str | None = Field(None, description="User UUID")
13
16
  created_at: str = Field(..., description="ISO 8601 formatted creation timestamp")
14
17
  updated_at: str = Field(..., description="ISO 8601 formatted update timestamp")
15
18
 
@@ -36,13 +39,6 @@ class Artifact(BaseModel):
36
39
  updated_at: str = Field(..., description="ISO 8601 formatted update timestamp")
37
40
 
38
41
 
39
- class FileContent(BaseModel):
40
- """Parsed file content model."""
41
-
42
- type: str = Field(..., description="File content type: 'text', 'json', 'csv', or 'code'")
43
- raw: str = Field(..., description="Raw text content of the file")
44
-
45
-
46
42
  class GetArtifactResp(BaseModel):
47
43
  """Response model for getting an artifact."""
48
44
 
acontext/types/session.py CHANGED
@@ -13,10 +13,13 @@ class RemoveToolResultParams(TypedDict, total=False):
13
13
  Defaults to 3 if not specified.
14
14
  tool_result_placeholder: Custom text to replace old tool results with.
15
15
  Defaults to "Done" if not specified.
16
+ keep_tools: List of tool names that should never have their results removed.
17
+ Tool results from these tools are always kept regardless of keep_recent_n_tool_results.
16
18
  """
17
19
 
18
20
  keep_recent_n_tool_results: NotRequired[int]
19
21
  tool_result_placeholder: NotRequired[str]
22
+ keep_tools: NotRequired[list[str]]
20
23
 
21
24
 
22
25
  class RemoveToolResultStrategy(TypedDict):
@@ -36,10 +39,12 @@ class RemoveToolCallParamsParams(TypedDict, total=False):
36
39
  Attributes:
37
40
  keep_recent_n_tool_calls: Number of most recent tool calls to keep with full parameters.
38
41
  Defaults to 3 if not specified.
42
+ keep_tools: List of tool names that should never have their parameters removed.
43
+ Tool calls for these tools always keep their full parameters regardless of keep_recent_n_tool_calls.
39
44
  """
40
45
 
41
46
  keep_recent_n_tool_calls: NotRequired[int]
42
-
47
+ keep_tools: NotRequired[list[str]]
43
48
 
44
49
 
45
50
  class RemoveToolCallParamsStrategy(TypedDict):
@@ -57,7 +62,6 @@ class RemoveToolCallParamsStrategy(TypedDict):
57
62
  params: RemoveToolCallParamsParams
58
63
 
59
64
 
60
-
61
65
  class TokenLimitParams(TypedDict):
62
66
  """Parameters for the token_limit edit strategy.
63
67
 
@@ -87,7 +91,9 @@ class TokenLimitStrategy(TypedDict):
87
91
 
88
92
  # Union type for all edit strategies
89
93
  # When adding new strategies, add them to this Union: EditStrategy = Union[RemoveToolResultStrategy, OtherStrategy, ...]
90
- EditStrategy = Union[RemoveToolResultStrategy, RemoveToolCallParamsStrategy, TokenLimitStrategy]
94
+ EditStrategy = Union[
95
+ RemoveToolResultStrategy, RemoveToolCallParamsStrategy, TokenLimitStrategy
96
+ ]
91
97
 
92
98
 
93
99
  class Asset(BaseModel):
@@ -137,6 +143,7 @@ class Session(BaseModel):
137
143
 
138
144
  id: str = Field(..., description="Session UUID")
139
145
  project_id: str = Field(..., description="Project UUID")
146
+ user_id: str | None = Field(None, description="User UUID")
140
147
  disable_task_tracking: bool = Field(
141
148
  False, description="Whether task tracking is disabled for this session"
142
149
  )
@@ -216,15 +223,29 @@ class GetMessagesOutput(BaseModel):
216
223
  description="List of messages in the requested format (Message, OpenAI format, or Anthropic format)",
217
224
  )
218
225
  ids: list[str] = Field(
219
- ...,
220
- description="List of message UUIDs corresponding to each item in the same order",
226
+ ...,
227
+ description="List of message UUIDs corresponding to each item in the same order",
221
228
  )
222
229
  next_cursor: str | None = Field(None, description="Cursor for pagination")
223
230
  has_more: bool = Field(..., description="Whether there are more items")
231
+ this_time_tokens: int = Field(
232
+ ...,
233
+ description="Total token count of the returned messages",
234
+ )
224
235
  public_urls: dict[str, PublicURL] | None = Field(
225
236
  None,
226
237
  description="Map of SHA256 hash to PublicURL (only included when format='acontext')",
227
238
  )
239
+ edit_at_message_id: str | None = Field(
240
+ None,
241
+ description=(
242
+ "The message ID where edit strategies were applied up to. "
243
+ "If pin_editing_strategies_at_message was provided, this equals that value. "
244
+ "Otherwise, this is the ID of the last message in the response. "
245
+ "Use this value to maintain prompt cache stability by passing it as "
246
+ "pin_editing_strategies_at_message in subsequent requests."
247
+ ),
248
+ )
228
249
 
229
250
 
230
251
  class GetTasksOutput(BaseModel):
@@ -254,18 +275,13 @@ class TokenCounts(BaseModel):
254
275
  description="Total token count for all text and tool-call parts in a session",
255
276
  )
256
277
 
278
+
257
279
  class MessageObservingStatus(BaseModel):
258
280
  """Response model for message observing status."""
259
-
260
- observed: int = Field(
261
- ..., description="Number of messages with observed status"
262
- )
281
+
282
+ observed: int = Field(..., description="Number of messages with observed status")
263
283
  in_process: int = Field(
264
284
  ..., description="Number of messages with in_process status"
265
285
  )
266
- pending: int = Field(
267
- ..., description="Number of messages with pending status"
268
- )
269
- updated_at: str = Field(
270
- ..., description="Timestamp when the status was retrieved"
271
- )
286
+ pending: int = Field(..., description="Number of messages with pending status")
287
+ updated_at: str = Field(..., description="Timestamp when the status was retrieved")
@@ -0,0 +1,69 @@
1
+ """Type definitions for skill resources."""
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+ from .common import FileContent
8
+
9
+
10
+ class FileInfo(BaseModel):
11
+ """File information in a skill."""
12
+
13
+ path: str = Field(..., description="Relative file path from skill root")
14
+ mime: str = Field(..., description="MIME type of the file")
15
+
16
+
17
+ class Skill(BaseModel):
18
+ """Skill model representing an agent skill resource."""
19
+
20
+ id: str = Field(..., description="Skill UUID")
21
+ user_id: str | None = Field(None, description="User UUID")
22
+ name: str = Field(..., description="Skill name (unique within project)")
23
+ description: str = Field(..., description="Skill description")
24
+ file_index: list[FileInfo] = Field(
25
+ ..., description="List of file information (path and MIME type) in the skill"
26
+ )
27
+ meta: dict[str, Any] | None = Field(
28
+ None, description="Custom metadata dictionary"
29
+ )
30
+ created_at: str = Field(..., description="ISO 8601 formatted creation timestamp")
31
+ updated_at: str = Field(..., description="ISO 8601 formatted update timestamp")
32
+
33
+
34
+ class SkillCatalogItem(BaseModel):
35
+ """Catalog item containing only name and description."""
36
+
37
+ name: str = Field(..., description="Skill name (unique within project)")
38
+ description: str = Field(..., description="Skill description")
39
+
40
+
41
+ class ListSkillsOutput(BaseModel):
42
+ """Response model for listing skills (catalog format with name and description only)."""
43
+
44
+ items: list[SkillCatalogItem] = Field(
45
+ ..., description="List of skills with name and description"
46
+ )
47
+ next_cursor: str | None = Field(None, description="Cursor for pagination")
48
+ has_more: bool = Field(..., description="Whether there are more items")
49
+
50
+
51
+ class _ListSkillsResponse(BaseModel):
52
+ """Internal response model for API pagination (full Skill objects).
53
+
54
+ This is used internally to parse the raw API response before converting
55
+ to the catalog format (ListSkillsOutput).
56
+ """
57
+ items: list[Skill]
58
+ next_cursor: str | None = None
59
+ has_more: bool = False
60
+
61
+
62
+ class GetSkillFileResp(BaseModel):
63
+ """Response model for getting a skill file."""
64
+
65
+ path: str = Field(..., description="File path")
66
+ mime: str = Field(..., description="MIME type of the file")
67
+ url: str | None = Field(None, description="Presigned URL for downloading the file (present if file is not parseable)")
68
+ content: FileContent | None = Field(None, description="Parsed file content if available (present if file is parseable)")
69
+
acontext/types/space.py CHANGED
@@ -10,6 +10,7 @@ class Space(BaseModel):
10
10
 
11
11
  id: str = Field(..., description="Space UUID")
12
12
  project_id: str = Field(..., description="Project UUID")
13
+ user_id: str | None = Field(None, description="User UUID")
13
14
  configs: dict[str, Any] | None = Field(
14
15
  None, description="Space configuration dictionary"
15
16
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: acontext
3
- Version: 0.0.14
3
+ Version: 0.0.16
4
4
  Summary: Python SDK for the Acontext API
5
5
  Keywords: acontext,sdk,client,api
6
6
  Requires-Dist: httpx>=0.28.1
@@ -0,0 +1,40 @@
1
+ acontext/__init__.py,sha256=jAgRawWIjyMd6g3gq7Xm_3vNB31cPj8rMFPO4LGQKdM,1027
2
+ acontext/_constants.py,sha256=Ikuy_Wz3CPmXjKPLXb4Y580-fe54o1hZ2ZB1Bpw3sFE,363
3
+ acontext/_utils.py,sha256=GKQH45arKh0sDu64u-5jwrII_ctnU_oChYlgR5lRkfE,1250
4
+ acontext/agent/__init__.py,sha256=PAUhm1rBYOdKw3lCD-H9d72emkGL4KoxEvVq4GeS9ig,158
5
+ acontext/agent/base.py,sha256=kN2KzV6fOlvmMSi_0yu2QqwVLtHh09TznAy_SDtC8iM,2998
6
+ acontext/agent/disk.py,sha256=d9sOggIs4da7niB9TaE0ab82_7l0O5n08eOcQxcaYRs,12623
7
+ acontext/agent/skill.py,sha256=opsOTQyuedqQMlwDXWMVwCwg0u9cyehfyELDEbat3bE,4451
8
+ acontext/async_client.py,sha256=sqdSNjl3tx8ijoRNYoEwh1Lxdlhfk0zyd-h-xffIJJE,8372
9
+ acontext/client.py,sha256=3xha-zbHYLwI1njlhmoB_BYK5JIJoXFyrCpvIi63b9o,8097
10
+ acontext/client_types.py,sha256=uVBWzLbZyXrqkljG49ojdQL_xX6N3n_HGt4Bs_TEE48,987
11
+ acontext/errors.py,sha256=W9i7_t46q9kbci3XAyZiyQhS154d4R9rSon3na-gjgs,1296
12
+ acontext/messages.py,sha256=oNRZStfhcPFt6DPOfs_-Q7R1Xui6dOMr3wmpmC8pzvE,2310
13
+ acontext/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ acontext/resources/__init__.py,sha256=Oww11t9hmwdNFK2nC8YW9mf4BBSz5YLPAXAWLlZuNTs,935
15
+ acontext/resources/async_blocks.py,sha256=e_iJpgcAdwS2tXQ16MMCnySlddp5JV3BadN_EyqZSF4,5555
16
+ acontext/resources/async_disks.py,sha256=rwcRwQ79ZYbMMWAj5YDDpKQmfAIOl9M3CTYxOZTy_UU,6830
17
+ acontext/resources/async_sessions.py,sha256=Ld9T8l884r3UuUprRz7MwafSI8BaPfLA0pg0dPfNM1A,14363
18
+ acontext/resources/async_skills.py,sha256=mbEzyUH7S2_jkZ5tfl0FzpHJ7Io3x_biw5FirhokzAQ,5062
19
+ acontext/resources/async_spaces.py,sha256=b8DNG2L43B6iSgfmH8M-0cxAp681IjSCjw-4alfjV2A,6712
20
+ acontext/resources/async_tools.py,sha256=RbGaF2kX65Mun-q-Fp5H1J8waWTLIdCOfbdY19jpn1o,1091
21
+ acontext/resources/async_users.py,sha256=wqgWIgT4SOAJ0jFN2dXrZ49Fu9KKzP2uV6oVDwfB_Ak,572
22
+ acontext/resources/blocks.py,sha256=HJdAy5HdyTcHCYCPmqNdvApYKZ6aWs-ORIi_wQt3TUM,5447
23
+ acontext/resources/disks.py,sha256=3D-8veyWr3crDppyTvTs83FhRGWDmYRBNuKBPDh-sSk,6695
24
+ acontext/resources/sessions.py,sha256=qUoLs01tzmYChHy3kozzGQ3ZeAz_JyWGtCIsVL20BO4,14491
25
+ acontext/resources/skills.py,sha256=FPp1YWhH85I9BU0fJJC9FbePIbFhyX6j2FYxjPuBEAc,4935
26
+ acontext/resources/spaces.py,sha256=Ktvkkn3Jj7CZoKMbn6fYuo0zqImoCWj2EnMsnL2q-0A,6571
27
+ acontext/resources/tools.py,sha256=II_185B0HYKSP43hizE6C1zs7kjkkPLKihuEG8s-DRY,1046
28
+ acontext/resources/users.py,sha256=mcoXS-BPiF1Porkpm8ioo1acGPBGZSfHaFfcd1oRWL8,537
29
+ acontext/types/__init__.py,sha256=RFrkHI413x7AOk6X2ztu_WcuqNm09xvEzGmE352tDE4,1696
30
+ acontext/types/block.py,sha256=CzKByunk642rWXNNnh8cx67UzKLKDAxODmC_whwjP90,1078
31
+ acontext/types/common.py,sha256=5kLwzszlIofz8qZ9-Wj_zcBBiF22mAWgH9uWPkcgWCE,327
32
+ acontext/types/disk.py,sha256=nMjyXLUre4Xuu_YfBbzzdRQDRxB8qX1Zx9LsNqGCi9E,2223
33
+ acontext/types/session.py,sha256=WIaTiiAw8OujeSnk18Vy_Fn_AffW60pfs-unlPXYuT8,11076
34
+ acontext/types/skill.py,sha256=qBDNysW9ptKX6RcMiZd4PfwWwvcYCzj_dC-6RbXqyx4,2531
35
+ acontext/types/space.py,sha256=9BkGBYGeQDVwYTmPLoIjMY-IUdQzjrt8I7CXca2_5Vc,2600
36
+ acontext/types/tool.py,sha256=-mVn-vgk2SENK0Ubt-ZgWFZxKa-ddABqcAgXQ69YY-E,805
37
+ acontext/uploads.py,sha256=6twnqQOY_eerNuEjeSKsE_3S0IfJUiczXtAy4aXqDl8,1379
38
+ acontext-0.0.16.dist-info/WHEEL,sha256=eycQt0QpYmJMLKpE3X9iDk8R04v2ZF0x82ogq-zP6bQ,79
39
+ acontext-0.0.16.dist-info/METADATA,sha256=ZBbXvjNXaFKVUN02XUief26BXVQVMHt0-EkcHwV2eU8,859
40
+ acontext-0.0.16.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.22
2
+ Generator: uv 0.9.24
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,33 +0,0 @@
1
- acontext/__init__.py,sha256=jAgRawWIjyMd6g3gq7Xm_3vNB31cPj8rMFPO4LGQKdM,1027
2
- acontext/_constants.py,sha256=Ikuy_Wz3CPmXjKPLXb4Y580-fe54o1hZ2ZB1Bpw3sFE,363
3
- acontext/_utils.py,sha256=GKQH45arKh0sDu64u-5jwrII_ctnU_oChYlgR5lRkfE,1250
4
- acontext/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- acontext/agent/base.py,sha256=kN2KzV6fOlvmMSi_0yu2QqwVLtHh09TznAy_SDtC8iM,2998
6
- acontext/agent/disk.py,sha256=d9sOggIs4da7niB9TaE0ab82_7l0O5n08eOcQxcaYRs,12623
7
- acontext/async_client.py,sha256=mfyR-WVy_LEOXjrkTcXLNaPDC5euaZ9zFI_5e1gapGs,8153
8
- acontext/client.py,sha256=jFr5-Q8obdcZBamUNtY3fCklpOJiNiIKLzEApTmbnTg,7920
9
- acontext/client_types.py,sha256=uVBWzLbZyXrqkljG49ojdQL_xX6N3n_HGt4Bs_TEE48,987
10
- acontext/errors.py,sha256=W9i7_t46q9kbci3XAyZiyQhS154d4R9rSon3na-gjgs,1296
11
- acontext/messages.py,sha256=oNRZStfhcPFt6DPOfs_-Q7R1Xui6dOMr3wmpmC8pzvE,2310
12
- acontext/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- acontext/resources/__init__.py,sha256=KSVQ3YJ-wuU6OWGWJ47gDGbjjetP102aEsVSR8JUb_4,721
14
- acontext/resources/async_blocks.py,sha256=e_iJpgcAdwS2tXQ16MMCnySlddp5JV3BadN_EyqZSF4,5555
15
- acontext/resources/async_disks.py,sha256=2JjLpUkz5YZEkLt_jCts_khTG_b7lvf4cUMfoaJcnI8,6471
16
- acontext/resources/async_sessions.py,sha256=660qEvz9aG_Nn3XiKKj9AbWizMstHhUsQN2m3W8ZGJ4,13357
17
- acontext/resources/async_spaces.py,sha256=5lIZjI8GmgoMBcftAMD2vDj2FmbLZ6pi1-yNCWKv4cM,6408
18
- acontext/resources/async_tools.py,sha256=RbGaF2kX65Mun-q-Fp5H1J8waWTLIdCOfbdY19jpn1o,1091
19
- acontext/resources/blocks.py,sha256=HJdAy5HdyTcHCYCPmqNdvApYKZ6aWs-ORIi_wQt3TUM,5447
20
- acontext/resources/disks.py,sha256=BjVhVXoujHWhg6L0TG9GmW9HLTTldJYEPxCbuppRkc4,6336
21
- acontext/resources/sessions.py,sha256=9qfEMVmiJGmhAqKYQ89yYa14SDZ-dyol1-rPXU3wSI0,13441
22
- acontext/resources/spaces.py,sha256=LJP9krygZGdktMDtYGudCpk7yBxe_4v34QLk3SdejwQ,6267
23
- acontext/resources/tools.py,sha256=II_185B0HYKSP43hizE6C1zs7kjkkPLKihuEG8s-DRY,1046
24
- acontext/types/__init__.py,sha256=QWNLmdSnLE2b5XRXxnI2OKnHVdKQPJH0nJvvOYwhB1o,1448
25
- acontext/types/block.py,sha256=CzKByunk642rWXNNnh8cx67UzKLKDAxODmC_whwjP90,1078
26
- acontext/types/disk.py,sha256=g9u3rgCh05rK-gay19NUi-WyzI5Vt1GQwcyKv-5TSC4,2361
27
- acontext/types/session.py,sha256=H_n4vZJ02ZO5flqGZ8R5AYVSScxScf2r7UgsN5LRIa0,9965
28
- acontext/types/space.py,sha256=uxbPrOHYpsntPHqhMCLQ2KovM7BngHC5Q2j7qexVrN8,2537
29
- acontext/types/tool.py,sha256=-mVn-vgk2SENK0Ubt-ZgWFZxKa-ddABqcAgXQ69YY-E,805
30
- acontext/uploads.py,sha256=6twnqQOY_eerNuEjeSKsE_3S0IfJUiczXtAy4aXqDl8,1379
31
- acontext-0.0.14.dist-info/WHEEL,sha256=KSLUh82mDPEPk0Bx0ScXlWL64bc8KmzIPNcpQZFV-6E,79
32
- acontext-0.0.14.dist-info/METADATA,sha256=sm1g1hhGbW15feWubCtcNkjgK5Ja1S_-xr1lIbXvmjU,859
33
- acontext-0.0.14.dist-info/RECORD,,