agentpool 2.1.9__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 (311) hide show
  1. acp/__init__.py +13 -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/bridge/README.md +15 -2
  7. acp/bridge/__init__.py +3 -2
  8. acp/bridge/__main__.py +60 -19
  9. acp/bridge/ws_server.py +173 -0
  10. acp/bridge/ws_server_cli.py +89 -0
  11. acp/client/connection.py +38 -29
  12. acp/client/implementations/default_client.py +3 -2
  13. acp/client/implementations/headless_client.py +2 -2
  14. acp/connection.py +2 -2
  15. acp/notifications.py +20 -50
  16. acp/schema/__init__.py +2 -0
  17. acp/schema/agent_responses.py +21 -0
  18. acp/schema/client_requests.py +3 -3
  19. acp/schema/session_state.py +63 -29
  20. acp/stdio.py +39 -9
  21. acp/task/supervisor.py +2 -2
  22. acp/transports.py +362 -2
  23. acp/utils.py +17 -4
  24. agentpool/__init__.py +6 -1
  25. agentpool/agents/__init__.py +2 -0
  26. agentpool/agents/acp_agent/acp_agent.py +407 -277
  27. agentpool/agents/acp_agent/acp_converters.py +196 -38
  28. agentpool/agents/acp_agent/client_handler.py +191 -26
  29. agentpool/agents/acp_agent/session_state.py +17 -6
  30. agentpool/agents/agent.py +607 -572
  31. agentpool/agents/agui_agent/__init__.py +0 -2
  32. agentpool/agents/agui_agent/agui_agent.py +176 -110
  33. agentpool/agents/agui_agent/agui_converters.py +0 -131
  34. agentpool/agents/agui_agent/helpers.py +3 -4
  35. agentpool/agents/base_agent.py +632 -17
  36. agentpool/agents/claude_code_agent/FORKING.md +191 -0
  37. agentpool/agents/claude_code_agent/__init__.py +13 -1
  38. agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
  39. agentpool/agents/claude_code_agent/converters.py +74 -143
  40. agentpool/agents/claude_code_agent/history.py +474 -0
  41. agentpool/agents/claude_code_agent/models.py +77 -0
  42. agentpool/agents/claude_code_agent/static_info.py +100 -0
  43. agentpool/agents/claude_code_agent/usage.py +242 -0
  44. agentpool/agents/context.py +40 -0
  45. agentpool/agents/events/__init__.py +24 -0
  46. agentpool/agents/events/builtin_handlers.py +67 -1
  47. agentpool/agents/events/event_emitter.py +32 -2
  48. agentpool/agents/events/events.py +104 -3
  49. agentpool/agents/events/infer_info.py +145 -0
  50. agentpool/agents/events/processors.py +254 -0
  51. agentpool/agents/interactions.py +41 -6
  52. agentpool/agents/modes.py +67 -0
  53. agentpool/agents/slashed_agent.py +5 -4
  54. agentpool/agents/tool_call_accumulator.py +213 -0
  55. agentpool/agents/tool_wrapping.py +18 -6
  56. agentpool/common_types.py +56 -21
  57. agentpool/config_resources/__init__.py +38 -1
  58. agentpool/config_resources/acp_assistant.yml +2 -2
  59. agentpool/config_resources/agents.yml +3 -0
  60. agentpool/config_resources/agents_template.yml +1 -0
  61. agentpool/config_resources/claude_code_agent.yml +10 -6
  62. agentpool/config_resources/external_acp_agents.yml +2 -1
  63. agentpool/delegation/base_team.py +4 -30
  64. agentpool/delegation/pool.py +136 -289
  65. agentpool/delegation/team.py +58 -57
  66. agentpool/delegation/teamrun.py +51 -55
  67. agentpool/diagnostics/__init__.py +53 -0
  68. agentpool/diagnostics/lsp_manager.py +1593 -0
  69. agentpool/diagnostics/lsp_proxy.py +41 -0
  70. agentpool/diagnostics/lsp_proxy_script.py +229 -0
  71. agentpool/diagnostics/models.py +398 -0
  72. agentpool/functional/run.py +10 -4
  73. agentpool/mcp_server/__init__.py +0 -2
  74. agentpool/mcp_server/client.py +76 -32
  75. agentpool/mcp_server/conversions.py +54 -13
  76. agentpool/mcp_server/manager.py +34 -54
  77. agentpool/mcp_server/registries/official_registry_client.py +35 -1
  78. agentpool/mcp_server/tool_bridge.py +186 -139
  79. agentpool/messaging/__init__.py +0 -2
  80. agentpool/messaging/compaction.py +72 -197
  81. agentpool/messaging/connection_manager.py +11 -10
  82. agentpool/messaging/event_manager.py +5 -5
  83. agentpool/messaging/message_container.py +6 -30
  84. agentpool/messaging/message_history.py +99 -8
  85. agentpool/messaging/messagenode.py +52 -14
  86. agentpool/messaging/messages.py +54 -35
  87. agentpool/messaging/processing.py +12 -22
  88. agentpool/models/__init__.py +1 -1
  89. agentpool/models/acp_agents/base.py +6 -24
  90. agentpool/models/acp_agents/mcp_capable.py +126 -157
  91. agentpool/models/acp_agents/non_mcp.py +129 -95
  92. agentpool/models/agents.py +98 -76
  93. agentpool/models/agui_agents.py +1 -1
  94. agentpool/models/claude_code_agents.py +144 -19
  95. agentpool/models/file_parsing.py +0 -1
  96. agentpool/models/manifest.py +113 -50
  97. agentpool/prompts/conversion_manager.py +1 -1
  98. agentpool/prompts/prompts.py +5 -2
  99. agentpool/repomap.py +1 -1
  100. agentpool/resource_providers/__init__.py +11 -1
  101. agentpool/resource_providers/aggregating.py +56 -5
  102. agentpool/resource_providers/base.py +70 -4
  103. agentpool/resource_providers/codemode/code_executor.py +72 -5
  104. agentpool/resource_providers/codemode/helpers.py +2 -2
  105. agentpool/resource_providers/codemode/provider.py +64 -12
  106. agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
  107. agentpool/resource_providers/codemode/remote_provider.py +9 -12
  108. agentpool/resource_providers/filtering.py +3 -1
  109. agentpool/resource_providers/mcp_provider.py +89 -12
  110. agentpool/resource_providers/plan_provider.py +228 -46
  111. agentpool/resource_providers/pool.py +7 -3
  112. agentpool/resource_providers/resource_info.py +111 -0
  113. agentpool/resource_providers/static.py +4 -2
  114. agentpool/sessions/__init__.py +4 -1
  115. agentpool/sessions/manager.py +33 -5
  116. agentpool/sessions/models.py +59 -6
  117. agentpool/sessions/protocol.py +28 -0
  118. agentpool/sessions/session.py +11 -55
  119. agentpool/skills/registry.py +13 -8
  120. agentpool/storage/manager.py +572 -49
  121. agentpool/talk/registry.py +4 -4
  122. agentpool/talk/talk.py +9 -10
  123. agentpool/testing.py +538 -20
  124. agentpool/tool_impls/__init__.py +6 -0
  125. agentpool/tool_impls/agent_cli/__init__.py +42 -0
  126. agentpool/tool_impls/agent_cli/tool.py +95 -0
  127. agentpool/tool_impls/bash/__init__.py +64 -0
  128. agentpool/tool_impls/bash/helpers.py +35 -0
  129. agentpool/tool_impls/bash/tool.py +171 -0
  130. agentpool/tool_impls/delete_path/__init__.py +70 -0
  131. agentpool/tool_impls/delete_path/tool.py +142 -0
  132. agentpool/tool_impls/download_file/__init__.py +80 -0
  133. agentpool/tool_impls/download_file/tool.py +183 -0
  134. agentpool/tool_impls/execute_code/__init__.py +55 -0
  135. agentpool/tool_impls/execute_code/tool.py +163 -0
  136. agentpool/tool_impls/grep/__init__.py +80 -0
  137. agentpool/tool_impls/grep/tool.py +200 -0
  138. agentpool/tool_impls/list_directory/__init__.py +73 -0
  139. agentpool/tool_impls/list_directory/tool.py +197 -0
  140. agentpool/tool_impls/question/__init__.py +42 -0
  141. agentpool/tool_impls/question/tool.py +127 -0
  142. agentpool/tool_impls/read/__init__.py +104 -0
  143. agentpool/tool_impls/read/tool.py +305 -0
  144. agentpool/tools/__init__.py +2 -1
  145. agentpool/tools/base.py +114 -34
  146. agentpool/tools/manager.py +57 -1
  147. agentpool/ui/base.py +2 -2
  148. agentpool/ui/mock_provider.py +2 -2
  149. agentpool/ui/stdlib_provider.py +2 -2
  150. agentpool/utils/file_watcher.py +269 -0
  151. agentpool/utils/identifiers.py +121 -0
  152. agentpool/utils/pydantic_ai_helpers.py +46 -0
  153. agentpool/utils/streams.py +616 -2
  154. agentpool/utils/subprocess_utils.py +155 -0
  155. agentpool/utils/token_breakdown.py +461 -0
  156. agentpool/vfs_registry.py +7 -2
  157. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
  158. agentpool-2.5.0.dist-info/RECORD +579 -0
  159. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
  160. agentpool_cli/__main__.py +24 -0
  161. agentpool_cli/create.py +1 -1
  162. agentpool_cli/serve_acp.py +100 -21
  163. agentpool_cli/serve_agui.py +87 -0
  164. agentpool_cli/serve_opencode.py +119 -0
  165. agentpool_cli/ui.py +557 -0
  166. agentpool_commands/__init__.py +42 -5
  167. agentpool_commands/agents.py +75 -2
  168. agentpool_commands/history.py +62 -0
  169. agentpool_commands/mcp.py +176 -0
  170. agentpool_commands/models.py +56 -3
  171. agentpool_commands/pool.py +260 -0
  172. agentpool_commands/session.py +1 -1
  173. agentpool_commands/text_sharing/__init__.py +119 -0
  174. agentpool_commands/text_sharing/base.py +123 -0
  175. agentpool_commands/text_sharing/github_gist.py +80 -0
  176. agentpool_commands/text_sharing/opencode.py +462 -0
  177. agentpool_commands/text_sharing/paste_rs.py +59 -0
  178. agentpool_commands/text_sharing/pastebin.py +116 -0
  179. agentpool_commands/text_sharing/shittycodingagent.py +112 -0
  180. agentpool_commands/tools.py +57 -0
  181. agentpool_commands/utils.py +80 -30
  182. agentpool_config/__init__.py +30 -2
  183. agentpool_config/agentpool_tools.py +498 -0
  184. agentpool_config/builtin_tools.py +77 -22
  185. agentpool_config/commands.py +24 -1
  186. agentpool_config/compaction.py +258 -0
  187. agentpool_config/converters.py +1 -1
  188. agentpool_config/event_handlers.py +42 -0
  189. agentpool_config/events.py +1 -1
  190. agentpool_config/forward_targets.py +1 -4
  191. agentpool_config/jinja.py +3 -3
  192. agentpool_config/mcp_server.py +132 -6
  193. agentpool_config/nodes.py +1 -1
  194. agentpool_config/observability.py +44 -0
  195. agentpool_config/session.py +0 -3
  196. agentpool_config/storage.py +82 -38
  197. agentpool_config/task.py +3 -3
  198. agentpool_config/tools.py +11 -22
  199. agentpool_config/toolsets.py +109 -233
  200. agentpool_server/a2a_server/agent_worker.py +307 -0
  201. agentpool_server/a2a_server/server.py +23 -18
  202. agentpool_server/acp_server/acp_agent.py +234 -181
  203. agentpool_server/acp_server/commands/acp_commands.py +151 -156
  204. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
  205. agentpool_server/acp_server/event_converter.py +651 -0
  206. agentpool_server/acp_server/input_provider.py +53 -10
  207. agentpool_server/acp_server/server.py +24 -90
  208. agentpool_server/acp_server/session.py +173 -331
  209. agentpool_server/acp_server/session_manager.py +8 -34
  210. agentpool_server/agui_server/server.py +3 -1
  211. agentpool_server/mcp_server/server.py +5 -2
  212. agentpool_server/opencode_server/.rules +95 -0
  213. agentpool_server/opencode_server/ENDPOINTS.md +401 -0
  214. agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
  215. agentpool_server/opencode_server/__init__.py +19 -0
  216. agentpool_server/opencode_server/command_validation.py +172 -0
  217. agentpool_server/opencode_server/converters.py +975 -0
  218. agentpool_server/opencode_server/dependencies.py +24 -0
  219. agentpool_server/opencode_server/input_provider.py +421 -0
  220. agentpool_server/opencode_server/models/__init__.py +250 -0
  221. agentpool_server/opencode_server/models/agent.py +53 -0
  222. agentpool_server/opencode_server/models/app.py +72 -0
  223. agentpool_server/opencode_server/models/base.py +26 -0
  224. agentpool_server/opencode_server/models/common.py +23 -0
  225. agentpool_server/opencode_server/models/config.py +37 -0
  226. agentpool_server/opencode_server/models/events.py +821 -0
  227. agentpool_server/opencode_server/models/file.py +88 -0
  228. agentpool_server/opencode_server/models/mcp.py +44 -0
  229. agentpool_server/opencode_server/models/message.py +179 -0
  230. agentpool_server/opencode_server/models/parts.py +323 -0
  231. agentpool_server/opencode_server/models/provider.py +81 -0
  232. agentpool_server/opencode_server/models/pty.py +43 -0
  233. agentpool_server/opencode_server/models/question.py +56 -0
  234. agentpool_server/opencode_server/models/session.py +111 -0
  235. agentpool_server/opencode_server/routes/__init__.py +29 -0
  236. agentpool_server/opencode_server/routes/agent_routes.py +473 -0
  237. agentpool_server/opencode_server/routes/app_routes.py +202 -0
  238. agentpool_server/opencode_server/routes/config_routes.py +302 -0
  239. agentpool_server/opencode_server/routes/file_routes.py +571 -0
  240. agentpool_server/opencode_server/routes/global_routes.py +94 -0
  241. agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
  242. agentpool_server/opencode_server/routes/message_routes.py +761 -0
  243. agentpool_server/opencode_server/routes/permission_routes.py +63 -0
  244. agentpool_server/opencode_server/routes/pty_routes.py +300 -0
  245. agentpool_server/opencode_server/routes/question_routes.py +128 -0
  246. agentpool_server/opencode_server/routes/session_routes.py +1276 -0
  247. agentpool_server/opencode_server/routes/tui_routes.py +139 -0
  248. agentpool_server/opencode_server/server.py +475 -0
  249. agentpool_server/opencode_server/state.py +151 -0
  250. agentpool_server/opencode_server/time_utils.py +8 -0
  251. agentpool_storage/__init__.py +12 -0
  252. agentpool_storage/base.py +184 -2
  253. agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
  254. agentpool_storage/claude_provider/__init__.py +42 -0
  255. agentpool_storage/claude_provider/provider.py +1089 -0
  256. agentpool_storage/file_provider.py +278 -15
  257. agentpool_storage/memory_provider.py +193 -12
  258. agentpool_storage/models.py +3 -0
  259. agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
  260. agentpool_storage/opencode_provider/__init__.py +16 -0
  261. agentpool_storage/opencode_provider/helpers.py +414 -0
  262. agentpool_storage/opencode_provider/provider.py +895 -0
  263. agentpool_storage/project_store.py +325 -0
  264. agentpool_storage/session_store.py +26 -6
  265. agentpool_storage/sql_provider/__init__.py +4 -2
  266. agentpool_storage/sql_provider/models.py +48 -0
  267. agentpool_storage/sql_provider/sql_provider.py +269 -3
  268. agentpool_storage/sql_provider/utils.py +12 -13
  269. agentpool_storage/zed_provider/__init__.py +16 -0
  270. agentpool_storage/zed_provider/helpers.py +281 -0
  271. agentpool_storage/zed_provider/models.py +130 -0
  272. agentpool_storage/zed_provider/provider.py +442 -0
  273. agentpool_storage/zed_provider.py +803 -0
  274. agentpool_toolsets/__init__.py +0 -2
  275. agentpool_toolsets/builtin/__init__.py +2 -12
  276. agentpool_toolsets/builtin/code.py +96 -57
  277. agentpool_toolsets/builtin/debug.py +118 -48
  278. agentpool_toolsets/builtin/execution_environment.py +115 -230
  279. agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
  280. agentpool_toolsets/builtin/skills.py +9 -4
  281. agentpool_toolsets/builtin/subagent_tools.py +64 -51
  282. agentpool_toolsets/builtin/workers.py +4 -2
  283. agentpool_toolsets/composio_toolset.py +2 -2
  284. agentpool_toolsets/entry_points.py +3 -1
  285. agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
  286. agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
  287. agentpool_toolsets/fsspec_toolset/grep.py +99 -7
  288. agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
  289. agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
  290. agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
  291. agentpool_toolsets/mcp_discovery/__init__.py +5 -0
  292. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  293. agentpool_toolsets/mcp_discovery/toolset.py +511 -0
  294. agentpool_toolsets/mcp_run_toolset.py +87 -12
  295. agentpool_toolsets/notifications.py +33 -33
  296. agentpool_toolsets/openapi.py +3 -1
  297. agentpool_toolsets/search_toolset.py +3 -1
  298. agentpool-2.1.9.dist-info/RECORD +0 -474
  299. agentpool_config/resources.py +0 -33
  300. agentpool_server/acp_server/acp_tools.py +0 -43
  301. agentpool_server/acp_server/commands/spawn.py +0 -210
  302. agentpool_storage/text_log_provider.py +0 -275
  303. agentpool_toolsets/builtin/agent_management.py +0 -239
  304. agentpool_toolsets/builtin/chain.py +0 -288
  305. agentpool_toolsets/builtin/history.py +0 -36
  306. agentpool_toolsets/builtin/integration.py +0 -85
  307. agentpool_toolsets/builtin/tool_management.py +0 -90
  308. agentpool_toolsets/builtin/user_interaction.py +0 -52
  309. agentpool_toolsets/semantic_memory_toolset.py +0 -536
  310. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
  311. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,474 @@
1
+ """Claude Code message history loader and converter.
2
+
3
+ This module provides utilities for loading Claude Code's conversation history
4
+ from ~/.claude/projects/ and converting it to pydantic-ai message format.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from datetime import datetime
10
+ import json
11
+ from typing import TYPE_CHECKING, Annotated, Any, Literal
12
+
13
+
14
+ if TYPE_CHECKING:
15
+ from pathlib import Path
16
+
17
+ from pydantic_ai import ModelRequest, ModelResponse
18
+
19
+ from pydantic import BaseModel, Field
20
+
21
+
22
+ # Claude Code history entry types
23
+
24
+
25
+ class ClaudeCodeUsage(BaseModel):
26
+ """Token usage information from Claude Code."""
27
+
28
+ input_tokens: int = 0
29
+ output_tokens: int = 0
30
+ cache_creation_input_tokens: int = 0
31
+ cache_read_input_tokens: int = 0
32
+
33
+
34
+ class ClaudeCodeTextContent(BaseModel):
35
+ """Text content block in Claude Code messages."""
36
+
37
+ type: Literal["text"]
38
+ text: str
39
+
40
+
41
+ class ClaudeCodeToolUseContent(BaseModel):
42
+ """Tool use content block in Claude Code messages."""
43
+
44
+ type: Literal["tool_use"]
45
+ id: str
46
+ name: str
47
+ input: dict[str, Any]
48
+
49
+
50
+ class ClaudeCodeToolResultContent(BaseModel):
51
+ """Tool result content block in Claude Code messages."""
52
+
53
+ type: Literal["tool_result"]
54
+ tool_use_id: str
55
+ content: list[ClaudeCodeTextContent] | str
56
+
57
+
58
+ class ClaudeCodeThinkingContent(BaseModel):
59
+ """Thinking content block in Claude Code messages."""
60
+
61
+ type: Literal["thinking"]
62
+ thinking: str
63
+
64
+
65
+ ClaudeCodeContentBlock = Annotated[
66
+ ClaudeCodeTextContent
67
+ | ClaudeCodeToolUseContent
68
+ | ClaudeCodeToolResultContent
69
+ | ClaudeCodeThinkingContent,
70
+ Field(discriminator="type"),
71
+ ]
72
+
73
+
74
+ class ClaudeCodeUserMessage(BaseModel):
75
+ """User message payload in Claude Code format."""
76
+
77
+ role: Literal["user"]
78
+ content: str | list[ClaudeCodeContentBlock]
79
+
80
+
81
+ class ClaudeCodeAssistantMessage(BaseModel):
82
+ """Assistant message payload in Claude Code format."""
83
+
84
+ model: str | None = None
85
+ id: str | None = None
86
+ type: Literal["message"] = "message"
87
+ role: Literal["assistant"]
88
+ content: list[ClaudeCodeContentBlock]
89
+ stop_reason: str | None = None
90
+ usage: ClaudeCodeUsage | None = None
91
+
92
+
93
+ class ClaudeCodeUserEntry(BaseModel):
94
+ """A user entry in Claude Code's JSONL history."""
95
+
96
+ type: Literal["user"]
97
+ message: ClaudeCodeUserMessage
98
+ uuid: str
99
+ parent_uuid: str | None = Field(default=None, alias="parentUuid")
100
+ session_id: str = Field(alias="sessionId")
101
+ timestamp: datetime
102
+ cwd: str | None = None
103
+ version: str | None = None
104
+ git_branch: str | None = Field(default=None, alias="gitBranch")
105
+ is_sidechain: bool = Field(default=False, alias="isSidechain")
106
+ user_type: str | None = Field(default=None, alias="userType")
107
+
108
+
109
+ class ClaudeCodeAssistantEntry(BaseModel):
110
+ """An assistant entry in Claude Code's JSONL history."""
111
+
112
+ type: Literal["assistant"]
113
+ message: ClaudeCodeAssistantMessage
114
+ uuid: str
115
+ parent_uuid: str | None = Field(default=None, alias="parentUuid")
116
+ session_id: str = Field(alias="sessionId")
117
+ timestamp: datetime
118
+ request_id: str | None = Field(default=None, alias="requestId")
119
+ cwd: str | None = None
120
+ version: str | None = None
121
+ git_branch: str | None = Field(default=None, alias="gitBranch")
122
+ is_sidechain: bool = Field(default=False, alias="isSidechain")
123
+ user_type: str | None = Field(default=None, alias="userType")
124
+
125
+
126
+ class ClaudeCodeQueueOperation(BaseModel):
127
+ """A queue operation entry (metadata, not a message)."""
128
+
129
+ type: Literal["queue-operation"]
130
+ operation: str
131
+ timestamp: datetime
132
+ session_id: str = Field(alias="sessionId")
133
+
134
+
135
+ class ClaudeCodeSummary(BaseModel):
136
+ """A summary entry in Claude Code's history."""
137
+
138
+ type: Literal["summary"]
139
+ summary: str
140
+ uuid: str
141
+ parent_uuid: str | None = Field(default=None, alias="parentUuid")
142
+ session_id: str = Field(alias="sessionId")
143
+ timestamp: datetime
144
+ is_sidechain: bool = Field(default=False, alias="isSidechain")
145
+
146
+
147
+ ClaudeCodeEntry = Annotated[
148
+ ClaudeCodeUserEntry | ClaudeCodeAssistantEntry | ClaudeCodeQueueOperation | ClaudeCodeSummary,
149
+ Field(discriminator="type"),
150
+ ]
151
+
152
+ # Message entries that have uuid and parent_uuid (excludes queue operations)
153
+ ClaudeCodeMessageEntry = ClaudeCodeUserEntry | ClaudeCodeAssistantEntry | ClaudeCodeSummary
154
+
155
+
156
+ def parse_entry(line: str) -> ClaudeCodeEntry | None:
157
+ """Parse a single JSONL line into a Claude Code entry.
158
+
159
+ Args:
160
+ line: A single line from the JSONL file
161
+
162
+ Returns:
163
+ Parsed entry or None if the line is empty or unparseable
164
+ """
165
+ line = line.strip()
166
+ if not line:
167
+ return None
168
+
169
+ data = json.loads(line)
170
+ entry_type = data.get("type")
171
+
172
+ match entry_type:
173
+ case "user":
174
+ return ClaudeCodeUserEntry.model_validate(data)
175
+ case "assistant":
176
+ return ClaudeCodeAssistantEntry.model_validate(data)
177
+ case "queue-operation":
178
+ return ClaudeCodeQueueOperation.model_validate(data)
179
+ case "summary":
180
+ return ClaudeCodeSummary.model_validate(data)
181
+ case _:
182
+ return None
183
+
184
+
185
+ def load_session(session_path: Path) -> list[ClaudeCodeEntry]:
186
+ """Load all entries from a Claude Code session file.
187
+
188
+ Args:
189
+ session_path: Path to the .jsonl session file
190
+
191
+ Returns:
192
+ List of parsed entries
193
+ """
194
+ with session_path.open() as f:
195
+ return [entry for line in f if (entry := parse_entry(line))]
196
+
197
+
198
+ def get_main_conversation(
199
+ entries: list[ClaudeCodeEntry],
200
+ *,
201
+ include_sidechains: bool = False,
202
+ ) -> list[ClaudeCodeMessageEntry]:
203
+ """Extract the main conversation thread from entries.
204
+
205
+ Claude Code supports forking conversations via parentUuid. This function
206
+ follows the parent chain to reconstruct the main conversation, optionally
207
+ including or excluding sidechain messages.
208
+
209
+ Args:
210
+ entries: All entries from the session
211
+ include_sidechains: If True, include sidechain entries. If False (default),
212
+ only include the main conversation thread.
213
+
214
+ Returns:
215
+ Entries in conversation order, following the parent chain
216
+ """
217
+ # Filter to message entries (not queue operations)
218
+ message_entries: list[ClaudeCodeMessageEntry] = [
219
+ e
220
+ for e in entries
221
+ if isinstance(e, ClaudeCodeUserEntry | ClaudeCodeAssistantEntry | ClaudeCodeSummary)
222
+ ]
223
+
224
+ if not message_entries:
225
+ return []
226
+
227
+ # Build children lookup
228
+ children: dict[str | None, list[ClaudeCodeMessageEntry]] = {}
229
+ for entry in message_entries:
230
+ parent = entry.parent_uuid
231
+ children.setdefault(parent, []).append(entry)
232
+
233
+ # Find root(s) - entries with no parent
234
+ roots = children.get(None, [])
235
+
236
+ if not roots:
237
+ # No roots found, fall back to file order
238
+ if include_sidechains:
239
+ return message_entries
240
+ return [e for e in message_entries if not e.is_sidechain]
241
+
242
+ # Walk the tree, preferring non-sidechain entries
243
+ result: list[ClaudeCodeMessageEntry] = []
244
+
245
+ def walk(entry: ClaudeCodeMessageEntry) -> None:
246
+ if include_sidechains or not entry.is_sidechain:
247
+ result.append(entry)
248
+
249
+ # Get children of this entry
250
+ entry_children = children.get(entry.uuid, [])
251
+
252
+ # Sort children: non-sidechains first, then by timestamp
253
+ entry_children.sort(key=lambda e: (e.is_sidechain, e.timestamp))
254
+
255
+ for child in entry_children:
256
+ walk(child)
257
+
258
+ # Start from roots (sorted by timestamp)
259
+ roots.sort(key=lambda e: e.timestamp)
260
+ for root in roots:
261
+ walk(root)
262
+
263
+ return result
264
+
265
+
266
+ def get_claude_data_dir() -> Path:
267
+ """Get the Claude Code data directory path.
268
+
269
+ Claude Code stores data in ~/.claude rather than the XDG data directory.
270
+ """
271
+ from pathlib import Path
272
+
273
+ return Path.home() / ".claude"
274
+
275
+
276
+ def get_claude_projects_dir() -> Path:
277
+ """Get the Claude Code projects directory path."""
278
+ return get_claude_data_dir() / "projects"
279
+
280
+
281
+ def path_to_claude_dir_name(project_path: str) -> str:
282
+ """Convert a filesystem path to Claude Code's directory naming format.
283
+
284
+ Claude Code replaces '/' with '-', so '/home/user/project' becomes '-home-user-project'.
285
+
286
+ Args:
287
+ project_path: The filesystem path
288
+
289
+ Returns:
290
+ The Claude Code directory name format
291
+ """
292
+ return project_path.replace("/", "-")
293
+
294
+
295
+ def list_project_sessions(project_path: str) -> list[Path]:
296
+ """List all session files for a project.
297
+
298
+ Args:
299
+ project_path: The project path (will be converted to Claude's format)
300
+
301
+ Returns:
302
+ List of session file paths, sorted by modification time (newest first)
303
+ """
304
+ projects_dir = get_claude_projects_dir()
305
+ project_dir_name = path_to_claude_dir_name(project_path)
306
+ project_dir = projects_dir / project_dir_name
307
+
308
+ if not project_dir.exists():
309
+ return []
310
+
311
+ sessions = list(project_dir.glob("*.jsonl"))
312
+ return sorted(sessions, key=lambda p: p.stat().st_mtime, reverse=True)
313
+
314
+
315
+ def convert_to_pydantic_ai(
316
+ entries: list[ClaudeCodeEntry],
317
+ *,
318
+ include_sidechains: bool = False,
319
+ follow_parent_chain: bool = True,
320
+ ) -> list[ModelRequest | ModelResponse]:
321
+ """Convert Claude Code entries to pydantic-ai message format.
322
+
323
+ Args:
324
+ entries: List of Claude Code history entries
325
+ include_sidechains: If True, include sidechain (forked) messages
326
+ follow_parent_chain: If True (default), reconstruct conversation order
327
+ by following parentUuid links. If False, use file order.
328
+
329
+ Returns:
330
+ List of ModelRequest and ModelResponse objects
331
+ """
332
+ from pydantic_ai import ModelRequest, ModelResponse
333
+
334
+ # Optionally reconstruct proper conversation order
335
+ conversation: list[ClaudeCodeEntry] | list[ClaudeCodeMessageEntry]
336
+ if follow_parent_chain:
337
+ conversation = get_main_conversation(entries, include_sidechains=include_sidechains)
338
+ else:
339
+ conversation = entries
340
+ from pydantic_ai.messages import (
341
+ TextPart,
342
+ ThinkingPart,
343
+ ToolCallPart,
344
+ ToolReturnPart,
345
+ UserPromptPart,
346
+ )
347
+
348
+ messages: list[ModelRequest | ModelResponse] = []
349
+
350
+ for entry in conversation:
351
+ match entry:
352
+ case ClaudeCodeUserEntry():
353
+ parts: list[Any] = []
354
+ metadata = {
355
+ "uuid": entry.uuid,
356
+ "timestamp": entry.timestamp.isoformat(),
357
+ "sessionId": entry.session_id,
358
+ "cwd": entry.cwd,
359
+ "gitBranch": entry.git_branch,
360
+ "isSidechain": entry.is_sidechain,
361
+ }
362
+
363
+ content = entry.message.content
364
+ if isinstance(content, str):
365
+ parts.append(UserPromptPart(content=content))
366
+ else:
367
+ for block in content:
368
+ match block:
369
+ case ClaudeCodeTextContent():
370
+ parts.append(UserPromptPart(content=block.text))
371
+ case ClaudeCodeToolResultContent():
372
+ # Extract text from tool result content
373
+ if isinstance(block.content, str):
374
+ result_content = block.content
375
+ else:
376
+ result_content = "\n".join(
377
+ c.text
378
+ for c in block.content
379
+ if isinstance(c, ClaudeCodeTextContent)
380
+ )
381
+ parts.append(
382
+ ToolReturnPart(
383
+ tool_name="", # Not available in history
384
+ content=result_content,
385
+ tool_call_id=block.tool_use_id,
386
+ )
387
+ )
388
+
389
+ if parts:
390
+ messages.append(ModelRequest(parts=parts, metadata=metadata))
391
+
392
+ case ClaudeCodeAssistantEntry():
393
+ parts = []
394
+ metadata = {
395
+ "uuid": entry.uuid,
396
+ "timestamp": entry.timestamp.isoformat(),
397
+ "sessionId": entry.session_id,
398
+ "requestId": entry.request_id,
399
+ "cwd": entry.cwd,
400
+ "gitBranch": entry.git_branch,
401
+ "isSidechain": entry.is_sidechain,
402
+ }
403
+
404
+ for block in entry.message.content:
405
+ match block:
406
+ case ClaudeCodeTextContent():
407
+ parts.append(TextPart(content=block.text))
408
+ case ClaudeCodeToolUseContent():
409
+ parts.append(
410
+ ToolCallPart(
411
+ tool_name=block.name,
412
+ args=block.input,
413
+ tool_call_id=block.id,
414
+ )
415
+ )
416
+ case ClaudeCodeThinkingContent():
417
+ parts.append(ThinkingPart(content=block.thinking))
418
+
419
+ if parts:
420
+ messages.append(
421
+ ModelResponse(
422
+ parts=parts,
423
+ model_name=entry.message.model,
424
+ provider_response_id=entry.message.id,
425
+ metadata=metadata,
426
+ )
427
+ )
428
+
429
+ case ClaudeCodeSummary():
430
+ # Summaries can be added as system context if needed
431
+ metadata = {
432
+ "uuid": entry.uuid,
433
+ "timestamp": entry.timestamp.isoformat(),
434
+ "sessionId": entry.session_id,
435
+ "type": "summary",
436
+ }
437
+ messages.append(
438
+ ModelRequest(
439
+ parts=[UserPromptPart(content=f"[Summary]: {entry.summary}")],
440
+ metadata=metadata,
441
+ )
442
+ )
443
+
444
+ case ClaudeCodeQueueOperation():
445
+ # Skip queue operations - they're metadata, not messages
446
+ pass
447
+
448
+ return messages
449
+
450
+
451
+ def load_session_as_pydantic_ai(session_path: Path) -> list[ModelRequest | ModelResponse]:
452
+ """Load a Claude Code session and convert to pydantic-ai format.
453
+
454
+ Args:
455
+ session_path: Path to the .jsonl session file
456
+
457
+ Returns:
458
+ List of ModelRequest and ModelResponse objects
459
+ """
460
+ entries = load_session(session_path)
461
+ return convert_to_pydantic_ai(entries)
462
+
463
+
464
+ def get_latest_session(project_path: str) -> Path | None:
465
+ """Get the most recent session file for a project.
466
+
467
+ Args:
468
+ project_path: The project path
469
+
470
+ Returns:
471
+ Path to the latest session file, or None if no sessions exist
472
+ """
473
+ sessions = list_project_sessions(project_path)
474
+ return sessions[0] if sessions else None
@@ -0,0 +1,77 @@
1
+ """Pydantic models for Claude Code server info structures."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+
8
+ class ClaudeCodeBasemodel(BaseModel):
9
+ """Base model."""
10
+
11
+ model_config = ConfigDict(populate_by_name=True)
12
+
13
+
14
+ class ClaudeCodeModelInfo(ClaudeCodeBasemodel):
15
+ """Information about an available AI model from Claude Code."""
16
+
17
+ value: str
18
+ """Model identifier used in API calls (e.g., "default", "opus", "haiku")."""
19
+
20
+ display_name: str = Field(..., alias="displayName")
21
+ """Human-readable display name (e.g., "Opus", "Default (recommended)")."""
22
+
23
+ description: str
24
+ """Full description including capabilities and pricing."""
25
+
26
+
27
+ class ClaudeCodeCommandInfo(ClaudeCodeBasemodel):
28
+ """Information about an available slash command from Claude Code."""
29
+
30
+ name: str
31
+ """Command name without the / prefix (e.g., "compact", "review")."""
32
+
33
+ description: str
34
+ """Full description of what the command does."""
35
+
36
+ argument_hint: str = Field(..., alias="argumentHint")
37
+ """Usage hint for command arguments (may be empty string)."""
38
+
39
+
40
+ class ClaudeCodeAccountInfo(ClaudeCodeBasemodel):
41
+ """Account information from Claude Code."""
42
+
43
+ email: str | None = None
44
+ """User email address."""
45
+
46
+ subscription_type: str | None = Field(default=None, alias="subscriptionType")
47
+ """Subscription type (e.g., "Claude API")."""
48
+
49
+ token_source: str | None = Field(default=None, alias="tokenSource")
50
+ """Where tokens come from (e.g., "claude.ai")."""
51
+
52
+ api_key_source: str | None = Field(default=None, alias="apiKeySource")
53
+ """Where API key comes from (e.g., "ANTHROPIC_API_KEY")."""
54
+
55
+
56
+ class ClaudeCodeServerInfo(ClaudeCodeBasemodel):
57
+ """Complete server initialization info from Claude Code.
58
+
59
+ This is returned by the Claude Code server during initialization and contains
60
+ all available capabilities including models, commands, output styles, and
61
+ account information.
62
+ """
63
+
64
+ models: list[ClaudeCodeModelInfo] = Field(default_factory=list)
65
+ """List of available AI models."""
66
+
67
+ commands: list[ClaudeCodeCommandInfo] = Field(default_factory=list)
68
+ """List of available slash commands."""
69
+
70
+ output_style: str = Field(default="default")
71
+ """Current output style setting."""
72
+
73
+ available_output_styles: list[str] = Field(default_factory=list)
74
+ """List of all available output styles."""
75
+
76
+ account: ClaudeCodeAccountInfo | None = Field(default=None)
77
+ """Account and authentication information."""
@@ -0,0 +1,100 @@
1
+ """Static model information."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from tokonomics.model_discovery.model_info import ModelInfo, ModelPricing
8
+
9
+ from agentpool.agents.modes import ModeInfo
10
+
11
+
12
+ if TYPE_CHECKING:
13
+ from claude_agent_sdk import PermissionMode
14
+
15
+
16
+ VALID_MODES: set[PermissionMode] = {
17
+ "default",
18
+ "acceptEdits",
19
+ "plan",
20
+ "bypassPermissions",
21
+ }
22
+
23
+ # Static Claude Code models - these are the simple IDs the SDK accepts
24
+ # Use id_override to ensure pydantic_ai_id returns simple names like "opus"
25
+
26
+ OPUS = ModelInfo(
27
+ id="claude-opus-4-5",
28
+ name="Claude Opus",
29
+ provider="anthropic",
30
+ description="Claude Opus - most capable model",
31
+ context_window=200000,
32
+ max_output_tokens=32000,
33
+ input_modalities={"text", "image"},
34
+ output_modalities={"text"},
35
+ pricing=ModelPricing(
36
+ prompt=0.000005, # $5 per 1M tokens
37
+ completion=0.000025, # $25 per 1M tokens
38
+ ),
39
+ id_override="opus", # Claude Code SDK uses simple names
40
+ )
41
+ SONNET = ModelInfo(
42
+ id="claude-sonnet-4-5",
43
+ name="Claude Sonnet",
44
+ provider="anthropic",
45
+ description="Claude Sonnet - balanced performance and speed",
46
+ context_window=200000,
47
+ max_output_tokens=16000,
48
+ input_modalities={"text", "image"},
49
+ output_modalities={"text"},
50
+ pricing=ModelPricing(
51
+ prompt=0.000003, # $3 per 1M tokens
52
+ completion=0.000015, # $15 per 1M tokens
53
+ ),
54
+ id_override="sonnet", # Claude Code SDK uses simple names
55
+ )
56
+ HAIKU = ModelInfo(
57
+ id="claude-haiku-4-5",
58
+ name="Claude Haiku",
59
+ provider="anthropic",
60
+ description="Claude Haiku - fast and cost-effective",
61
+ context_window=200000,
62
+ max_output_tokens=8000,
63
+ input_modalities={"text", "image"},
64
+ output_modalities={"text"},
65
+ pricing=ModelPricing(
66
+ prompt=0.000001, # $1.00 per 1M tokens
67
+ completion=0.000005, # $5 per 1M tokens
68
+ ),
69
+ id_override="haiku", # Claude Code SDK uses simple names
70
+ )
71
+
72
+ MODELS = [OPUS, SONNET, HAIKU]
73
+
74
+
75
+ MODES = [
76
+ ModeInfo(
77
+ id="default",
78
+ name="Default",
79
+ description="Require confirmation for tool usage",
80
+ category_id="permissions",
81
+ ),
82
+ ModeInfo(
83
+ id="acceptEdits",
84
+ name="Accept Edits",
85
+ description="Auto-approve file edits without confirmation",
86
+ category_id="permissions",
87
+ ),
88
+ ModeInfo(
89
+ id="plan",
90
+ name="Plan",
91
+ description="Planning mode - no tool execution",
92
+ category_id="permissions",
93
+ ),
94
+ ModeInfo(
95
+ id="bypassPermissions",
96
+ name="Bypass Permissions",
97
+ description="Skip all permission checks (use with caution)",
98
+ category_id="permissions",
99
+ ),
100
+ ]