agentpool 2.2.3__py3-none-any.whl → 2.5.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.
Files changed (250) hide show
  1. acp/__init__.py +0 -4
  2. acp/acp_requests.py +20 -77
  3. acp/agent/connection.py +8 -0
  4. acp/agent/implementations/debug_server/debug_server.py +6 -2
  5. acp/agent/protocol.py +6 -0
  6. acp/client/connection.py +38 -29
  7. acp/client/implementations/default_client.py +3 -2
  8. acp/client/implementations/headless_client.py +2 -2
  9. acp/connection.py +2 -2
  10. acp/notifications.py +18 -49
  11. acp/schema/__init__.py +2 -0
  12. acp/schema/agent_responses.py +21 -0
  13. acp/schema/client_requests.py +3 -3
  14. acp/schema/session_state.py +63 -29
  15. acp/task/supervisor.py +2 -2
  16. acp/utils.py +2 -2
  17. agentpool/__init__.py +2 -0
  18. agentpool/agents/acp_agent/acp_agent.py +278 -263
  19. agentpool/agents/acp_agent/acp_converters.py +150 -17
  20. agentpool/agents/acp_agent/client_handler.py +35 -24
  21. agentpool/agents/acp_agent/session_state.py +14 -6
  22. agentpool/agents/agent.py +471 -643
  23. agentpool/agents/agui_agent/agui_agent.py +104 -107
  24. agentpool/agents/agui_agent/helpers.py +3 -4
  25. agentpool/agents/base_agent.py +485 -32
  26. agentpool/agents/claude_code_agent/FORKING.md +191 -0
  27. agentpool/agents/claude_code_agent/__init__.py +13 -1
  28. agentpool/agents/claude_code_agent/claude_code_agent.py +654 -334
  29. agentpool/agents/claude_code_agent/converters.py +4 -141
  30. agentpool/agents/claude_code_agent/models.py +77 -0
  31. agentpool/agents/claude_code_agent/static_info.py +100 -0
  32. agentpool/agents/claude_code_agent/usage.py +242 -0
  33. agentpool/agents/events/__init__.py +22 -0
  34. agentpool/agents/events/builtin_handlers.py +65 -0
  35. agentpool/agents/events/event_emitter.py +3 -0
  36. agentpool/agents/events/events.py +84 -3
  37. agentpool/agents/events/infer_info.py +145 -0
  38. agentpool/agents/events/processors.py +254 -0
  39. agentpool/agents/interactions.py +41 -6
  40. agentpool/agents/modes.py +13 -0
  41. agentpool/agents/slashed_agent.py +5 -4
  42. agentpool/agents/tool_wrapping.py +18 -6
  43. agentpool/common_types.py +35 -21
  44. agentpool/config_resources/acp_assistant.yml +2 -2
  45. agentpool/config_resources/agents.yml +3 -0
  46. agentpool/config_resources/agents_template.yml +1 -0
  47. agentpool/config_resources/claude_code_agent.yml +9 -8
  48. agentpool/config_resources/external_acp_agents.yml +2 -1
  49. agentpool/delegation/base_team.py +4 -30
  50. agentpool/delegation/pool.py +104 -265
  51. agentpool/delegation/team.py +57 -57
  52. agentpool/delegation/teamrun.py +50 -55
  53. agentpool/functional/run.py +10 -4
  54. agentpool/mcp_server/client.py +73 -38
  55. agentpool/mcp_server/conversions.py +54 -13
  56. agentpool/mcp_server/manager.py +9 -23
  57. agentpool/mcp_server/registries/official_registry_client.py +10 -1
  58. agentpool/mcp_server/tool_bridge.py +114 -79
  59. agentpool/messaging/connection_manager.py +11 -10
  60. agentpool/messaging/event_manager.py +5 -5
  61. agentpool/messaging/message_container.py +6 -30
  62. agentpool/messaging/message_history.py +87 -8
  63. agentpool/messaging/messagenode.py +52 -14
  64. agentpool/messaging/messages.py +2 -26
  65. agentpool/messaging/processing.py +10 -22
  66. agentpool/models/__init__.py +1 -1
  67. agentpool/models/acp_agents/base.py +6 -2
  68. agentpool/models/acp_agents/mcp_capable.py +124 -15
  69. agentpool/models/acp_agents/non_mcp.py +0 -23
  70. agentpool/models/agents.py +66 -66
  71. agentpool/models/agui_agents.py +1 -1
  72. agentpool/models/claude_code_agents.py +111 -17
  73. agentpool/models/file_parsing.py +0 -1
  74. agentpool/models/manifest.py +70 -50
  75. agentpool/prompts/conversion_manager.py +1 -1
  76. agentpool/prompts/prompts.py +5 -2
  77. agentpool/resource_providers/__init__.py +2 -0
  78. agentpool/resource_providers/aggregating.py +4 -2
  79. agentpool/resource_providers/base.py +13 -3
  80. agentpool/resource_providers/codemode/code_executor.py +72 -5
  81. agentpool/resource_providers/codemode/helpers.py +2 -2
  82. agentpool/resource_providers/codemode/provider.py +64 -12
  83. agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
  84. agentpool/resource_providers/codemode/remote_provider.py +9 -12
  85. agentpool/resource_providers/filtering.py +3 -1
  86. agentpool/resource_providers/mcp_provider.py +66 -12
  87. agentpool/resource_providers/plan_provider.py +111 -18
  88. agentpool/resource_providers/pool.py +5 -3
  89. agentpool/resource_providers/resource_info.py +111 -0
  90. agentpool/resource_providers/static.py +2 -2
  91. agentpool/sessions/__init__.py +2 -0
  92. agentpool/sessions/manager.py +2 -3
  93. agentpool/sessions/models.py +9 -6
  94. agentpool/sessions/protocol.py +28 -0
  95. agentpool/sessions/session.py +11 -55
  96. agentpool/storage/manager.py +361 -54
  97. agentpool/talk/registry.py +4 -4
  98. agentpool/talk/talk.py +9 -10
  99. agentpool/testing.py +1 -1
  100. agentpool/tool_impls/__init__.py +6 -0
  101. agentpool/tool_impls/agent_cli/__init__.py +42 -0
  102. agentpool/tool_impls/agent_cli/tool.py +95 -0
  103. agentpool/tool_impls/bash/__init__.py +64 -0
  104. agentpool/tool_impls/bash/helpers.py +35 -0
  105. agentpool/tool_impls/bash/tool.py +171 -0
  106. agentpool/tool_impls/delete_path/__init__.py +70 -0
  107. agentpool/tool_impls/delete_path/tool.py +142 -0
  108. agentpool/tool_impls/download_file/__init__.py +80 -0
  109. agentpool/tool_impls/download_file/tool.py +183 -0
  110. agentpool/tool_impls/execute_code/__init__.py +55 -0
  111. agentpool/tool_impls/execute_code/tool.py +163 -0
  112. agentpool/tool_impls/grep/__init__.py +80 -0
  113. agentpool/tool_impls/grep/tool.py +200 -0
  114. agentpool/tool_impls/list_directory/__init__.py +73 -0
  115. agentpool/tool_impls/list_directory/tool.py +197 -0
  116. agentpool/tool_impls/question/__init__.py +42 -0
  117. agentpool/tool_impls/question/tool.py +127 -0
  118. agentpool/tool_impls/read/__init__.py +104 -0
  119. agentpool/tool_impls/read/tool.py +305 -0
  120. agentpool/tools/__init__.py +2 -1
  121. agentpool/tools/base.py +114 -34
  122. agentpool/tools/manager.py +57 -1
  123. agentpool/ui/base.py +2 -2
  124. agentpool/ui/mock_provider.py +2 -2
  125. agentpool/ui/stdlib_provider.py +2 -2
  126. agentpool/utils/streams.py +21 -96
  127. agentpool/vfs_registry.py +7 -2
  128. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/METADATA +16 -22
  129. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/RECORD +242 -195
  130. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
  131. agentpool_cli/__main__.py +20 -0
  132. agentpool_cli/create.py +1 -1
  133. agentpool_cli/serve_acp.py +59 -1
  134. agentpool_cli/serve_opencode.py +1 -1
  135. agentpool_cli/ui.py +557 -0
  136. agentpool_commands/__init__.py +12 -5
  137. agentpool_commands/agents.py +1 -1
  138. agentpool_commands/pool.py +260 -0
  139. agentpool_commands/session.py +1 -1
  140. agentpool_commands/text_sharing/__init__.py +119 -0
  141. agentpool_commands/text_sharing/base.py +123 -0
  142. agentpool_commands/text_sharing/github_gist.py +80 -0
  143. agentpool_commands/text_sharing/opencode.py +462 -0
  144. agentpool_commands/text_sharing/paste_rs.py +59 -0
  145. agentpool_commands/text_sharing/pastebin.py +116 -0
  146. agentpool_commands/text_sharing/shittycodingagent.py +112 -0
  147. agentpool_commands/utils.py +31 -32
  148. agentpool_config/__init__.py +30 -2
  149. agentpool_config/agentpool_tools.py +498 -0
  150. agentpool_config/converters.py +1 -1
  151. agentpool_config/event_handlers.py +42 -0
  152. agentpool_config/events.py +1 -1
  153. agentpool_config/forward_targets.py +1 -4
  154. agentpool_config/jinja.py +3 -3
  155. agentpool_config/mcp_server.py +1 -5
  156. agentpool_config/nodes.py +1 -1
  157. agentpool_config/observability.py +44 -0
  158. agentpool_config/session.py +0 -3
  159. agentpool_config/storage.py +38 -39
  160. agentpool_config/task.py +3 -3
  161. agentpool_config/tools.py +11 -28
  162. agentpool_config/toolsets.py +22 -90
  163. agentpool_server/a2a_server/agent_worker.py +307 -0
  164. agentpool_server/a2a_server/server.py +23 -18
  165. agentpool_server/acp_server/acp_agent.py +125 -56
  166. agentpool_server/acp_server/commands/acp_commands.py +46 -216
  167. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +8 -7
  168. agentpool_server/acp_server/event_converter.py +651 -0
  169. agentpool_server/acp_server/input_provider.py +53 -10
  170. agentpool_server/acp_server/server.py +1 -11
  171. agentpool_server/acp_server/session.py +90 -410
  172. agentpool_server/acp_server/session_manager.py +8 -34
  173. agentpool_server/agui_server/server.py +3 -1
  174. agentpool_server/mcp_server/server.py +5 -2
  175. agentpool_server/opencode_server/ENDPOINTS.md +53 -14
  176. agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
  177. agentpool_server/opencode_server/__init__.py +0 -8
  178. agentpool_server/opencode_server/converters.py +132 -26
  179. agentpool_server/opencode_server/input_provider.py +160 -8
  180. agentpool_server/opencode_server/models/__init__.py +42 -20
  181. agentpool_server/opencode_server/models/app.py +12 -0
  182. agentpool_server/opencode_server/models/events.py +203 -29
  183. agentpool_server/opencode_server/models/mcp.py +19 -0
  184. agentpool_server/opencode_server/models/message.py +18 -1
  185. agentpool_server/opencode_server/models/parts.py +134 -1
  186. agentpool_server/opencode_server/models/question.py +56 -0
  187. agentpool_server/opencode_server/models/session.py +13 -1
  188. agentpool_server/opencode_server/routes/__init__.py +4 -0
  189. agentpool_server/opencode_server/routes/agent_routes.py +33 -2
  190. agentpool_server/opencode_server/routes/app_routes.py +66 -3
  191. agentpool_server/opencode_server/routes/config_routes.py +66 -5
  192. agentpool_server/opencode_server/routes/file_routes.py +184 -5
  193. agentpool_server/opencode_server/routes/global_routes.py +1 -1
  194. agentpool_server/opencode_server/routes/lsp_routes.py +1 -1
  195. agentpool_server/opencode_server/routes/message_routes.py +122 -66
  196. agentpool_server/opencode_server/routes/permission_routes.py +63 -0
  197. agentpool_server/opencode_server/routes/pty_routes.py +23 -22
  198. agentpool_server/opencode_server/routes/question_routes.py +128 -0
  199. agentpool_server/opencode_server/routes/session_routes.py +139 -68
  200. agentpool_server/opencode_server/routes/tui_routes.py +1 -1
  201. agentpool_server/opencode_server/server.py +47 -2
  202. agentpool_server/opencode_server/state.py +30 -0
  203. agentpool_storage/__init__.py +0 -4
  204. agentpool_storage/base.py +81 -2
  205. agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
  206. agentpool_storage/claude_provider/__init__.py +42 -0
  207. agentpool_storage/{claude_provider.py → claude_provider/provider.py} +190 -8
  208. agentpool_storage/file_provider.py +149 -15
  209. agentpool_storage/memory_provider.py +132 -12
  210. agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
  211. agentpool_storage/opencode_provider/__init__.py +16 -0
  212. agentpool_storage/opencode_provider/helpers.py +414 -0
  213. agentpool_storage/opencode_provider/provider.py +895 -0
  214. agentpool_storage/session_store.py +20 -6
  215. agentpool_storage/sql_provider/sql_provider.py +135 -2
  216. agentpool_storage/sql_provider/utils.py +2 -12
  217. agentpool_storage/zed_provider/__init__.py +16 -0
  218. agentpool_storage/zed_provider/helpers.py +281 -0
  219. agentpool_storage/zed_provider/models.py +130 -0
  220. agentpool_storage/zed_provider/provider.py +442 -0
  221. agentpool_storage/zed_provider.py +803 -0
  222. agentpool_toolsets/__init__.py +0 -2
  223. agentpool_toolsets/builtin/__init__.py +2 -4
  224. agentpool_toolsets/builtin/code.py +4 -4
  225. agentpool_toolsets/builtin/debug.py +115 -40
  226. agentpool_toolsets/builtin/execution_environment.py +54 -165
  227. agentpool_toolsets/builtin/skills.py +0 -77
  228. agentpool_toolsets/builtin/subagent_tools.py +64 -51
  229. agentpool_toolsets/builtin/workers.py +4 -2
  230. agentpool_toolsets/composio_toolset.py +2 -2
  231. agentpool_toolsets/entry_points.py +3 -1
  232. agentpool_toolsets/fsspec_toolset/grep.py +25 -5
  233. agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
  234. agentpool_toolsets/fsspec_toolset/toolset.py +350 -66
  235. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  236. agentpool_toolsets/mcp_discovery/toolset.py +74 -17
  237. agentpool_toolsets/mcp_run_toolset.py +8 -11
  238. agentpool_toolsets/notifications.py +33 -33
  239. agentpool_toolsets/openapi.py +3 -1
  240. agentpool_toolsets/search_toolset.py +3 -1
  241. agentpool_config/resources.py +0 -33
  242. agentpool_server/acp_server/acp_tools.py +0 -43
  243. agentpool_server/acp_server/commands/spawn.py +0 -210
  244. agentpool_storage/opencode_provider.py +0 -730
  245. agentpool_storage/text_log_provider.py +0 -276
  246. agentpool_toolsets/builtin/chain.py +0 -288
  247. agentpool_toolsets/builtin/user_interaction.py +0 -52
  248. agentpool_toolsets/semantic_memory_toolset.py +0 -536
  249. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
  250. {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,130 @@
1
+ """Zed IDE storage format models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Literal
6
+
7
+ from pydantic import AliasChoices, BaseModel, Field
8
+
9
+
10
+ class ZedMentionUri(BaseModel):
11
+ """Mention URI - can be File, Directory, Symbol, etc."""
12
+
13
+ File: dict[str, Any] | None = None
14
+ Directory: dict[str, Any] | None = None
15
+ Symbol: dict[str, Any] | None = None
16
+ Selection: dict[str, Any] | None = None
17
+ Thread: dict[str, Any] | None = None
18
+ TextThread: dict[str, Any] | None = None
19
+ Rule: dict[str, Any] | None = None
20
+ Fetch: dict[str, Any] | None = None
21
+ PastedImage: bool | None = None
22
+
23
+
24
+ class ZedMention(BaseModel):
25
+ """A file/symbol mention in Zed."""
26
+
27
+ uri: ZedMentionUri
28
+ content: str
29
+
30
+
31
+ class ZedImage(BaseModel):
32
+ """An image in Zed (base64 encoded)."""
33
+
34
+ source: str # base64 encoded
35
+
36
+
37
+ class ZedThinking(BaseModel):
38
+ """Thinking block from model."""
39
+
40
+ text: str
41
+ signature: str | None = None
42
+
43
+
44
+ class ZedToolUse(BaseModel):
45
+ """Tool use block."""
46
+
47
+ id: str
48
+ name: str
49
+ raw_input: str
50
+ input: dict[str, Any]
51
+ is_input_complete: bool = True
52
+ thought_signature: str | None = None
53
+
54
+
55
+ class ZedToolResult(BaseModel):
56
+ """Tool result."""
57
+
58
+ tool_use_id: str
59
+ tool_name: str
60
+ is_error: bool = False
61
+ content: dict[str, Any] | str | None = None
62
+ output: dict[str, Any] | str | None = None
63
+
64
+
65
+ class ZedUserMessage(BaseModel):
66
+ """User message in Zed thread."""
67
+
68
+ id: str
69
+ content: list[dict[str, Any]] # Can contain Text, Image, Mention
70
+
71
+
72
+ class ZedAgentMessage(BaseModel):
73
+ """Agent message in Zed thread."""
74
+
75
+ content: list[dict[str, Any]] # Can contain Text, Thinking, ToolUse
76
+ tool_results: dict[str, ZedToolResult] = Field(default_factory=dict)
77
+ reasoning_details: Any | None = None
78
+
79
+
80
+ class ZedMessage(BaseModel):
81
+ """A message in Zed thread - either User or Agent."""
82
+
83
+ User: ZedUserMessage | None = None
84
+ Agent: ZedAgentMessage | None = None
85
+
86
+
87
+ class ZedLanguageModel(BaseModel):
88
+ """Model configuration."""
89
+
90
+ provider: str
91
+ model: str
92
+
93
+
94
+ class ZedWorktreeSnapshot(BaseModel):
95
+ """Git worktree snapshot."""
96
+
97
+ worktree_path: str
98
+ git_state: dict[str, Any] | None = None
99
+
100
+
101
+ class ZedProjectSnapshot(BaseModel):
102
+ """Project snapshot with git state."""
103
+
104
+ worktree_snapshots: list[ZedWorktreeSnapshot] = Field(default_factory=list)
105
+ unsaved_buffer_paths: list[str] = Field(default_factory=list)
106
+ timestamp: str | None = None
107
+
108
+
109
+ class ZedThread(BaseModel):
110
+ """A Zed conversation thread."""
111
+
112
+ model_config = {"populate_by_name": True}
113
+
114
+ # v0.3.0 uses "title", v0.2.0 uses "summary"
115
+ title: str = Field(alias="title", validation_alias=AliasChoices("title", "summary"))
116
+ messages: list[ZedMessage | Literal["Resume"]] # Control messages
117
+ updated_at: str
118
+ version: str | None = None
119
+ detailed_summary: str | None = None # v0.3.0 field
120
+ detailed_summary_state: str | dict[str, Any] | None = None # v0.2.0 field
121
+ initial_project_snapshot: ZedProjectSnapshot | None = None
122
+ cumulative_token_usage: dict[str, int] = Field(default_factory=dict)
123
+ request_token_usage: list[dict[str, int]] | dict[str, dict[str, int]] = Field(
124
+ default_factory=list
125
+ ) # Can be list or dict depending on version
126
+ model: ZedLanguageModel | None = None
127
+ completion_mode: str | None = None
128
+ profile: str | None = None
129
+ exceeded_window_error: Any | None = None
130
+ tool_use_limit_reached: bool = False
@@ -0,0 +1,442 @@
1
+ """Zed IDE storage provider - reads from ~/.local/share/zed/threads format."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections import defaultdict
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ import sqlite3
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ from agentpool.log import get_logger
12
+ from agentpool.utils.now import get_now
13
+ from agentpool_storage.base import StorageProvider
14
+ from agentpool_storage.zed_provider import helpers
15
+
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Sequence
19
+
20
+ from pydantic_ai import FinishReason
21
+
22
+ from agentpool.messaging import ChatMessage, TokenCost
23
+ from agentpool_config.session import SessionQuery
24
+ from agentpool_config.storage import ZedStorageConfig
25
+ from agentpool_storage.models import (
26
+ ConversationData,
27
+ MessageData,
28
+ QueryFilters,
29
+ StatsFilters,
30
+ TokenUsage,
31
+ )
32
+ from agentpool_storage.zed_provider.models import ZedThread
33
+
34
+ logger = get_logger(__name__)
35
+
36
+
37
+ class ZedStorageProvider(StorageProvider):
38
+ """Storage provider that reads Zed IDE's native thread format.
39
+
40
+ Zed stores conversations as zstd-compressed JSON in:
41
+ - ~/.local/share/zed/threads/threads.db (SQLite)
42
+
43
+ This is a READ-ONLY provider - it cannot write back to Zed's format.
44
+
45
+ ## Supported Zed content types:
46
+ - Text → str / TextPart
47
+ - Image → BinaryContent
48
+ - Mention (File, Directory, Symbol, Selection) → formatted str
49
+ - Thinking → ThinkingPart
50
+ - ToolUse → ToolCallPart
51
+ - tool_results → ToolReturnPart
52
+
53
+ ## Fields NOT available in Zed format:
54
+ - Per-message token costs (only cumulative per thread)
55
+ - Response time
56
+ - Parent message ID (flat structure)
57
+ - Forwarded from chain
58
+ - Finish reason
59
+ """
60
+
61
+ can_load_history = True
62
+
63
+ def __init__(self, config: ZedStorageConfig) -> None:
64
+ """Initialize Zed storage provider.
65
+
66
+ Args:
67
+ config: Configuration for the provider
68
+ """
69
+ super().__init__(config)
70
+ self.db_path = Path(config.path).expanduser()
71
+ if not self.db_path.name.endswith(".db"):
72
+ # If path is directory, add default db location
73
+ self.db_path = self.db_path / "threads" / "threads.db"
74
+
75
+ def _get_connection(self) -> sqlite3.Connection:
76
+ """Get a SQLite connection."""
77
+ if not self.db_path.exists():
78
+ msg = f"Zed threads database not found: {self.db_path}"
79
+ raise FileNotFoundError(msg)
80
+ return sqlite3.connect(self.db_path)
81
+
82
+ def _list_threads(self) -> list[tuple[str, str, str]]:
83
+ """List all threads.
84
+
85
+ Returns:
86
+ List of (id, summary, updated_at) tuples
87
+ """
88
+ try:
89
+ conn = self._get_connection()
90
+ query = "SELECT id, summary, updated_at FROM threads ORDER BY updated_at DESC"
91
+ cursor = conn.execute(query)
92
+ threads = cursor.fetchall()
93
+ conn.close()
94
+ except FileNotFoundError:
95
+ return []
96
+ except sqlite3.Error as e:
97
+ logger.warning("Failed to list Zed threads", error=str(e))
98
+ return []
99
+ else:
100
+ return threads
101
+
102
+ def _load_thread(self, thread_id: str) -> ZedThread | None:
103
+ """Load a single thread by ID."""
104
+ try:
105
+ conn = self._get_connection()
106
+ query = "SELECT data_type, data FROM threads WHERE id = ? LIMIT 1"
107
+ cursor = conn.execute(query, (thread_id,))
108
+ row = cursor.fetchone()
109
+ conn.close()
110
+ if row is None:
111
+ return None
112
+
113
+ data_type, data = row
114
+ return helpers.decompress_thread(data, data_type)
115
+ except FileNotFoundError:
116
+ return None
117
+ except (sqlite3.Error, Exception) as e: # noqa: BLE001
118
+ logger.warning("Failed to load Zed thread", thread_id=thread_id, error=str(e))
119
+ return None
120
+
121
+ async def filter_messages(self, query: SessionQuery) -> list[ChatMessage[str]]:
122
+ """Filter messages based on query."""
123
+ messages: list[ChatMessage[str]] = []
124
+ for thread_id, summary, _updated_at in self._list_threads():
125
+ # Filter by conversation name if specified
126
+ if query.name and query.name not in (thread_id, summary):
127
+ continue
128
+
129
+ thread = self._load_thread(thread_id)
130
+ if thread is None:
131
+ continue
132
+ for msg in helpers.thread_to_chat_messages(thread, thread_id):
133
+ # Apply filters
134
+ if query.agents and msg.name not in query.agents:
135
+ continue
136
+ cutoff = query.get_time_cutoff()
137
+ if query.since and cutoff and msg.timestamp and msg.timestamp < cutoff:
138
+ continue
139
+ if query.until and msg.timestamp:
140
+ until_dt = datetime.fromisoformat(query.until)
141
+ if msg.timestamp > until_dt:
142
+ continue
143
+ if query.contains and query.contains not in msg.content:
144
+ continue
145
+ if query.roles and msg.role not in query.roles:
146
+ continue
147
+ messages.append(msg)
148
+ if query.limit and len(messages) >= query.limit:
149
+ return messages
150
+
151
+ return messages
152
+
153
+ async def log_message(
154
+ self,
155
+ *,
156
+ message_id: str,
157
+ conversation_id: str,
158
+ content: str,
159
+ role: str,
160
+ name: str | None = None,
161
+ parent_id: str | None = None,
162
+ cost_info: TokenCost | None = None,
163
+ model: str | None = None,
164
+ response_time: float | None = None,
165
+ provider_name: str | None = None,
166
+ provider_response_id: str | None = None,
167
+ messages: str | None = None,
168
+ finish_reason: FinishReason | None = None,
169
+ ) -> None:
170
+ """Log a message - NOT SUPPORTED (read-only provider)."""
171
+ logger.warning("ZedStorageProvider is read-only, cannot log messages")
172
+
173
+ async def log_conversation(
174
+ self,
175
+ *,
176
+ conversation_id: str,
177
+ node_name: str,
178
+ start_time: datetime | None = None,
179
+ ) -> None:
180
+ """Log a conversation - NOT SUPPORTED (read-only provider)."""
181
+ logger.warning("ZedStorageProvider is read-only, cannot log conversations")
182
+
183
+ async def get_conversations(
184
+ self,
185
+ filters: QueryFilters,
186
+ ) -> list[tuple[ConversationData, Sequence[ChatMessage[str]]]]:
187
+ """Get filtered conversations with their messages."""
188
+ from agentpool_storage.models import ConversationData as ConvData
189
+
190
+ result: list[tuple[ConvData, Sequence[ChatMessage[str]]]] = []
191
+ for thread_id, summary, updated_at_str in self._list_threads():
192
+ thread = self._load_thread(thread_id)
193
+ if thread is None:
194
+ continue
195
+ # Parse timestamp
196
+ try:
197
+ updated_at = datetime.fromisoformat(updated_at_str.replace("Z", "+00:00"))
198
+ except (ValueError, AttributeError):
199
+ updated_at = get_now()
200
+ # Apply filters
201
+ if filters.since and updated_at < filters.since:
202
+ continue
203
+ messages = helpers.thread_to_chat_messages(thread, thread_id)
204
+ if not messages:
205
+ continue
206
+ if filters.agent_name and not any(m.name == filters.agent_name for m in messages):
207
+ continue
208
+ if filters.query and not any(filters.query in m.content for m in messages):
209
+ continue
210
+ # Build MessageData list
211
+ msg_data_list: list[MessageData] = []
212
+ for msg in messages:
213
+ msg_data: MessageData = {
214
+ "role": msg.role,
215
+ "content": msg.content,
216
+ "timestamp": (msg.timestamp or get_now()).isoformat(),
217
+ "parent_id": msg.parent_id,
218
+ "model": msg.model_name,
219
+ "name": msg.name,
220
+ "token_usage": None,
221
+ "cost": None,
222
+ "response_time": None,
223
+ }
224
+ msg_data_list.append(msg_data)
225
+
226
+ # Get token usage from thread-level cumulative data
227
+ total_tokens = sum(thread.cumulative_token_usage.values())
228
+ token_usage_data: TokenUsage | None = (
229
+ {"total": total_tokens, "prompt": 0, "completion": 0} if total_tokens else None
230
+ )
231
+
232
+ conv_data = ConvData(
233
+ id=thread_id,
234
+ agent="zed",
235
+ title=summary or thread.title,
236
+ start_time=updated_at.isoformat(),
237
+ messages=msg_data_list,
238
+ token_usage=token_usage_data,
239
+ )
240
+
241
+ result.append((conv_data, messages))
242
+ if filters.limit and len(result) >= filters.limit:
243
+ break
244
+
245
+ return result
246
+
247
+ async def get_conversation_stats(self, filters: StatsFilters) -> dict[str, dict[str, Any]]:
248
+ """Get conversation statistics."""
249
+ stats: dict[str, dict[str, Any]] = defaultdict(
250
+ lambda: {"total_tokens": 0, "messages": 0, "models": set()}
251
+ )
252
+ for thread_id, _summary, updated_at_str in self._list_threads():
253
+ try:
254
+ timestamp = datetime.fromisoformat(updated_at_str.replace("Z", "+00:00"))
255
+ except (ValueError, AttributeError):
256
+ timestamp = get_now()
257
+ # Apply time filter
258
+ if timestamp < filters.cutoff:
259
+ continue
260
+ thread = self._load_thread(thread_id)
261
+ if thread is None:
262
+ continue
263
+ model_name = "unknown"
264
+ if thread.model:
265
+ model_name = f"{thread.model.provider}:{thread.model.model}"
266
+ total_tokens = sum(thread.cumulative_token_usage.values())
267
+ message_count = len(thread.messages)
268
+ # Group by specified criterion
269
+ match filters.group_by:
270
+ case "model":
271
+ key = model_name
272
+ case "hour":
273
+ key = timestamp.strftime("%Y-%m-%d %H:00")
274
+ case "day":
275
+ key = timestamp.strftime("%Y-%m-%d")
276
+ case _:
277
+ key = "zed" # Default agent grouping
278
+
279
+ stats[key]["messages"] += message_count
280
+ stats[key]["total_tokens"] += total_tokens
281
+ stats[key]["models"].add(model_name)
282
+
283
+ # Convert sets to lists for JSON serialization
284
+ for value in stats.values():
285
+ value["models"] = list(value["models"])
286
+
287
+ return dict(stats)
288
+
289
+ async def reset(self, *, agent_name: str | None = None, hard: bool = False) -> tuple[int, int]:
290
+ """Reset storage - NOT SUPPORTED (read-only provider)."""
291
+ logger.warning("ZedStorageProvider is read-only, cannot reset")
292
+ return 0, 0
293
+
294
+ async def get_conversation_counts(
295
+ self,
296
+ *,
297
+ agent_name: str | None = None,
298
+ ) -> tuple[int, int]:
299
+ """Get counts of conversations and messages."""
300
+ conv_count = 0
301
+ msg_count = 0
302
+ threads = self._list_threads()
303
+ for thread_id, _summary, _updated_at in threads:
304
+ thread = self._load_thread(thread_id)
305
+ if thread is None:
306
+ continue
307
+
308
+ conv_count += 1
309
+ msg_count += len(thread.messages)
310
+ return conv_count, msg_count
311
+
312
+ async def get_conversation_title(self, conversation_id: str) -> str | None:
313
+ """Get the title of a conversation."""
314
+ thread = self._load_thread(conversation_id)
315
+ if thread is None:
316
+ return None
317
+ return thread.title
318
+
319
+ async def get_conversation_messages(
320
+ self,
321
+ conversation_id: str,
322
+ *,
323
+ include_ancestors: bool = False,
324
+ ) -> list[ChatMessage[str]]:
325
+ """Get all messages for a conversation.
326
+
327
+ Args:
328
+ conversation_id: Thread ID (conversation ID in Zed format)
329
+ include_ancestors: If True, traverse parent_id chain to include
330
+ messages from ancestor conversations (not supported in Zed format)
331
+
332
+ Returns:
333
+ List of messages ordered by timestamp
334
+
335
+ Note:
336
+ Zed threads don't have parent_id chain, so include_ancestors has no effect.
337
+ """
338
+ thread = self._load_thread(conversation_id)
339
+ if thread is None:
340
+ return []
341
+
342
+ messages = helpers.thread_to_chat_messages(thread, conversation_id)
343
+ # Sort by timestamp (though they should already be in order)
344
+ messages.sort(key=lambda m: m.timestamp or get_now())
345
+ return messages
346
+
347
+ async def get_message(self, message_id: str) -> ChatMessage[str] | None:
348
+ """Get a single message by ID.
349
+
350
+ Args:
351
+ message_id: ID of the message
352
+
353
+ Returns:
354
+ The message if found, None otherwise
355
+
356
+ Note:
357
+ Zed doesn't store individual message IDs, so this searches all threads.
358
+ This is inefficient for large datasets.
359
+ """
360
+ for thread_id, _summary, _updated_at in self._list_threads():
361
+ thread = self._load_thread(thread_id)
362
+ if thread is None:
363
+ continue
364
+ messages = helpers.thread_to_chat_messages(thread, thread_id)
365
+ for msg in messages:
366
+ if msg.message_id == message_id:
367
+ return msg
368
+ return None
369
+
370
+ async def get_message_ancestry(self, message_id: str) -> list[ChatMessage[str]]:
371
+ """Get the ancestry chain of a message.
372
+
373
+ Args:
374
+ message_id: ID of the message
375
+
376
+ Returns:
377
+ List of messages from oldest ancestor to the specified message
378
+
379
+ Note:
380
+ Zed threads don't support parent_id chains, so this only returns
381
+ the single message if found.
382
+ """
383
+ msg = await self.get_message(message_id)
384
+ return [msg] if msg else []
385
+
386
+ async def fork_conversation(
387
+ self,
388
+ *,
389
+ source_conversation_id: str,
390
+ new_conversation_id: str,
391
+ fork_from_message_id: str | None = None,
392
+ new_agent_name: str | None = None,
393
+ ) -> str | None:
394
+ """Fork a conversation at a specific point.
395
+
396
+ Args:
397
+ source_conversation_id: Source thread ID
398
+ new_conversation_id: New thread ID
399
+ fork_from_message_id: Message ID to fork from (not used - Zed is read-only)
400
+ new_agent_name: Not used in Zed format
401
+
402
+ Returns:
403
+ None, as Zed storage is read-only
404
+
405
+ Note:
406
+ This is a READ-ONLY provider. Forking creates no persistent state.
407
+ Returns None to indicate no fork point is available.
408
+ """
409
+ msg = "Fork conversation not supported for Zed storage (read-only)"
410
+ logger.warning(msg, source=source_conversation_id, new=new_conversation_id)
411
+ return None
412
+
413
+
414
+ if __name__ == "__main__":
415
+ import asyncio
416
+ import datetime as dt
417
+
418
+ from agentpool_config.storage import ZedStorageConfig
419
+ from agentpool_storage.models import QueryFilters, StatsFilters
420
+
421
+ async def main() -> None:
422
+ config = ZedStorageConfig()
423
+ provider = ZedStorageProvider(config)
424
+ print(f"Database: {provider.db_path}")
425
+ print(f"Exists: {provider.db_path.exists()}")
426
+ # List conversations
427
+ filters = QueryFilters(limit=10)
428
+ conversations = await provider.get_conversations(filters)
429
+ print(f"\nFound {len(conversations)} conversations")
430
+ for conv_data, messages in conversations[:5]:
431
+ print(f" - {conv_data['id'][:8]}... | {conv_data['title'] or 'Untitled'}")
432
+ print(f" Messages: {len(messages)}, Updated: {conv_data['start_time']}")
433
+ # Get counts
434
+ conv_count, msg_count = await provider.get_conversation_counts()
435
+ print(f"\nTotal: {conv_count} conversations, {msg_count} messages")
436
+ # Get stats
437
+ cutoff = dt.datetime.now(dt.UTC) - dt.timedelta(days=30)
438
+ stats_filters = StatsFilters(cutoff=cutoff, group_by="day")
439
+ stats = await provider.get_conversation_stats(stats_filters)
440
+ print(f"\nStats: {stats}")
441
+
442
+ asyncio.run(main())