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
@@ -7,159 +7,20 @@ event types as native agents.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- from dataclasses import dataclass, field
11
10
  from typing import TYPE_CHECKING, Any, cast
12
11
 
13
12
  from pydantic_ai import PartDeltaEvent, TextPartDelta, ThinkingPartDelta
14
13
 
15
- from agentpool.agents.events import (
16
- DiffContentItem,
17
- LocationContentItem,
18
- ToolCallCompleteEvent,
19
- ToolCallStartEvent,
20
- )
14
+ from agentpool.agents.events import ToolCallCompleteEvent, ToolCallStartEvent
21
15
 
22
16
 
23
17
  if TYPE_CHECKING:
24
18
  from claude_agent_sdk import ContentBlock, McpServerConfig, Message, ToolUseBlock
25
19
 
26
- from agentpool.agents.events import RichAgentStreamEvent, ToolCallContentItem
27
- from agentpool.tools.base import ToolKind
20
+ from agentpool.agents.events import RichAgentStreamEvent
28
21
  from agentpool_config.mcp_server import MCPServerConfig as NativeMCPServerConfig
29
22
 
30
23
 
31
- @dataclass
32
- class RichToolInfo:
33
- """Rich display information derived from tool name and input."""
34
-
35
- title: str
36
- """Human-readable title for the tool call."""
37
- kind: ToolKind = "other"
38
- """Category of tool operation."""
39
- locations: list[LocationContentItem] = field(default_factory=list)
40
- """File locations involved in the operation."""
41
- content: list[ToolCallContentItem] = field(default_factory=list)
42
- """Rich content items (diffs, text, etc.)."""
43
-
44
-
45
- def derive_rich_tool_info(name: str, input_data: dict[str, Any]) -> RichToolInfo: # noqa: PLR0911, PLR0915
46
- """Derive rich display info from tool name and input arguments.
47
-
48
- Maps MCP tool names and their inputs to human-readable titles, kinds,
49
- and location information for rich UI display. Handles both Claude Code
50
- built-in tools and MCP bridge tools.
51
-
52
- Args:
53
- name: The tool name (e.g., "Read", "mcp__server__read")
54
- input_data: The tool input arguments
55
-
56
- Returns:
57
- RichToolInfo with derived display information
58
- """
59
- # Extract the actual tool name if it's an MCP bridge tool
60
- # Format: mcp__{server_name}__{tool_name}
61
- actual_name = name
62
- if name.startswith("mcp__") and "__" in name[5:]:
63
- parts = name.split("__")
64
- if len(parts) >= 3: # noqa: PLR2004
65
- actual_name = parts[-1] # Get the last part (actual tool name)
66
-
67
- # Normalize to lowercase for matching
68
- tool_lower = actual_name.lower()
69
- # Read operations
70
- if tool_lower in ("read", "read_file"):
71
- path = input_data.get("file_path") or input_data.get("path", "")
72
- offset = input_data.get("offset") or input_data.get("line")
73
- limit = input_data.get("limit")
74
-
75
- suffix = ""
76
- if limit:
77
- start = (offset or 0) + 1
78
- end = (offset or 0) + limit
79
- suffix = f" ({start}-{end})"
80
- elif offset:
81
- suffix = f" (from line {offset + 1})"
82
- title = f"Read {path}{suffix}" if path else "Read File"
83
- locations = [LocationContentItem(path=path, line=offset or 0)] if path else []
84
- return RichToolInfo(title=title, kind="read", locations=locations)
85
-
86
- # Write operations
87
- if tool_lower in ("write", "write_file"):
88
- path = input_data.get("file_path") or input_data.get("path", "")
89
- content = input_data.get("content", "")
90
- return RichToolInfo(
91
- title=f"Write {path}" if path else "Write File",
92
- kind="edit",
93
- locations=[LocationContentItem(path=path)] if path else [],
94
- content=[DiffContentItem(path=path, old_text=None, new_text=content)] if path else [],
95
- )
96
- # Edit operations
97
- if tool_lower in ("edit", "edit_file"):
98
- path = input_data.get("file_path") or input_data.get("path", "")
99
- old_string = input_data.get("old_string") or input_data.get("old_text", "")
100
- new_string = input_data.get("new_string") or input_data.get("new_text", "")
101
- return RichToolInfo(
102
- title=f"Edit {path}" if path else "Edit File",
103
- kind="edit",
104
- locations=[LocationContentItem(path=path)] if path else [],
105
- content=[DiffContentItem(path=path, old_text=old_string, new_text=new_string)]
106
- if path
107
- else [],
108
- )
109
- # Delete operations
110
- if tool_lower in ("delete", "delete_path", "delete_file"):
111
- path = input_data.get("file_path") or input_data.get("path", "")
112
- locations = [LocationContentItem(path=path)] if path else []
113
- title = f"Delete {path}" if path else "Delete"
114
- return RichToolInfo(title=title, kind="delete", locations=locations)
115
- # Bash/terminal operations
116
- if tool_lower in ("bash", "execute", "run_command", "execute_command", "execute_code"):
117
- command = input_data.get("command") or input_data.get("code", "")
118
- # Escape backticks in command
119
- escaped_cmd = command.replace("`", "\\`") if command else ""
120
- title = f"`{escaped_cmd}`" if escaped_cmd else "Terminal"
121
- return RichToolInfo(title=title, kind="execute")
122
- # Search operations
123
- if tool_lower in ("grep", "search", "glob", "find"):
124
- pattern = input_data.get("pattern") or input_data.get("query", "")
125
- path = input_data.get("path", "")
126
- title = f"Search for '{pattern}'" if pattern else "Search"
127
- if path:
128
- title += f" in {path}"
129
- locations = [LocationContentItem(path=path)] if path else []
130
- return RichToolInfo(title=title, kind="search", locations=locations)
131
- # List directory
132
- if tool_lower in ("ls", "list", "list_directory"):
133
- path = input_data.get("path", ".")
134
- title = f"List {path}" if path != "." else "List current directory"
135
- locations = [LocationContentItem(path=path)] if path else []
136
- return RichToolInfo(title=title, kind="search", locations=locations)
137
- # Web operations
138
- if tool_lower in ("webfetch", "web_fetch", "fetch"):
139
- url = input_data.get("url", "")
140
- return RichToolInfo(title=f"Fetch {url}" if url else "Web Fetch", kind="fetch")
141
- if tool_lower in ("websearch", "web_search", "search_web"):
142
- query = input_data.get("query", "")
143
- return RichToolInfo(title=f"Search: {query}" if query else "Web Search", kind="fetch")
144
- # Task/subagent operations
145
- if tool_lower == "task":
146
- description = input_data.get("description", "")
147
- return RichToolInfo(title=description if description else "Task", kind="think")
148
- # Notebook operations
149
- if tool_lower in ("notebookread", "notebook_read"):
150
- path = input_data.get("notebook_path", "")
151
- title = f"Read Notebook {path}" if path else "Read Notebook"
152
- locations = [LocationContentItem(path=path)] if path else []
153
- return RichToolInfo(title=title, kind="read", locations=locations)
154
- if tool_lower in ("notebookedit", "notebook_edit"):
155
- path = input_data.get("notebook_path", "")
156
- title = f"Edit Notebook {path}" if path else "Edit Notebook"
157
- locations = [LocationContentItem(path=path)] if path else []
158
- return RichToolInfo(title=title, kind="edit", locations=locations)
159
- # Default: use the tool name as title
160
- return RichToolInfo(title=actual_name, kind="other")
161
-
162
-
163
24
  def content_block_to_event(block: ContentBlock, index: int = 0) -> RichAgentStreamEvent[Any] | None:
164
25
  """Convert a Claude SDK ContentBlock to a streaming event.
165
26
 
@@ -172,6 +33,8 @@ def content_block_to_event(block: ContentBlock, index: int = 0) -> RichAgentStre
172
33
  """
173
34
  from claude_agent_sdk import TextBlock, ThinkingBlock, ToolUseBlock
174
35
 
36
+ from agentpool.agents.events.infer_info import derive_rich_tool_info
37
+
175
38
  match block:
176
39
  case TextBlock(text=text):
177
40
  return PartDeltaEvent(index=index, delta=TextPartDelta(content_delta=text))
@@ -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
+ ]
@@ -0,0 +1,242 @@
1
+ """Claude Code usage limits helper.
2
+
3
+ Fetches usage information from Anthropic's OAuth API using stored credentials.
4
+ Works on both Linux and macOS.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+ import subprocess
12
+ import sys
13
+
14
+ import anyenv
15
+ import httpx
16
+ from pydantic import BaseModel
17
+
18
+
19
+ class UsageLimit(BaseModel):
20
+ """A single usage limit with utilization percentage and reset time."""
21
+
22
+ utilization: float
23
+ """Utilization percentage (0-100)."""
24
+
25
+ resets_at: datetime | None = None
26
+ """When this limit resets, or None if not set."""
27
+
28
+
29
+ class ExtraUsage(BaseModel):
30
+ """Extra usage information for paid plans."""
31
+
32
+ is_enabled: bool = False
33
+ monthly_limit: float | None = None
34
+ used_credits: float | None = None
35
+ utilization: float | None = None
36
+
37
+
38
+ class ClaudeCodeUsage(BaseModel):
39
+ """Claude Code usage limits."""
40
+
41
+ five_hour: UsageLimit | None = None
42
+ """5-hour rolling usage limit."""
43
+
44
+ seven_day: UsageLimit | None = None
45
+ """7-day rolling usage limit."""
46
+
47
+ seven_day_opus: UsageLimit | None = None
48
+ """7-day Opus-specific limit."""
49
+
50
+ seven_day_sonnet: UsageLimit | None = None
51
+ """7-day Sonnet-specific limit."""
52
+
53
+ seven_day_oauth_apps: UsageLimit | None = None
54
+ """7-day OAuth apps limit."""
55
+
56
+ extra_usage: ExtraUsage | None = None
57
+ """Extra usage info for paid plans."""
58
+
59
+ def format_table(self) -> str:
60
+ """Format usage as a readable table."""
61
+ lines = ["Claude Code Usage Limits", "=" * 50]
62
+
63
+ def format_limit(name: str, limit: UsageLimit | None) -> str | None:
64
+ if limit is None:
65
+ return None
66
+ reset_str = ""
67
+ if limit.resets_at:
68
+ reset_str = f" (resets {limit.resets_at.strftime('%Y-%m-%d %H:%M UTC')})"
69
+ return f"{name}: {limit.utilization:.0f}%{reset_str}"
70
+
71
+ for name, limit in [
72
+ ("5-hour", self.five_hour),
73
+ ("7-day", self.seven_day),
74
+ ("7-day Opus", self.seven_day_opus),
75
+ ("7-day Sonnet", self.seven_day_sonnet),
76
+ ("7-day OAuth Apps", self.seven_day_oauth_apps),
77
+ ]:
78
+ formatted = format_limit(name, limit)
79
+ if formatted:
80
+ lines.append(formatted)
81
+
82
+ if self.extra_usage and self.extra_usage.is_enabled:
83
+ lines.append("")
84
+ lines.append("Extra Usage (paid):")
85
+ if self.extra_usage.utilization is not None:
86
+ lines.append(f" Utilization: {self.extra_usage.utilization:.0f}%")
87
+ if self.extra_usage.used_credits is not None:
88
+ lines.append(f" Used credits: {self.extra_usage.used_credits}")
89
+ if self.extra_usage.monthly_limit is not None:
90
+ lines.append(f" Monthly limit: {self.extra_usage.monthly_limit}")
91
+
92
+ return "\n".join(lines)
93
+
94
+
95
+ def _get_credentials_path() -> Path | None:
96
+ """Get the path to Claude Code credentials file.
97
+
98
+ Returns:
99
+ Path to credentials file, or None if not found.
100
+ """
101
+ # Linux: ~/.claude/.credentials.json
102
+ linux_path = Path.home() / ".claude" / ".credentials.json"
103
+ if linux_path.exists():
104
+ return linux_path
105
+
106
+ # macOS: Also check ~/.claude first (newer versions)
107
+ if linux_path.exists():
108
+ return linux_path
109
+
110
+ return None
111
+
112
+
113
+ def _get_access_token_from_file(path: Path) -> str | None:
114
+ """Read access token from credentials file."""
115
+ try:
116
+ data = anyenv.load_json(path.read_text(), return_type=dict)
117
+ val = data.get("claudeAiOauth", {}).get("accessToken")
118
+ except (anyenv.JsonLoadError, OSError):
119
+ return None
120
+ else:
121
+ assert isinstance(val, str)
122
+ return val
123
+
124
+
125
+ def _get_access_token_from_keychain() -> str | None:
126
+ """Read access token from macOS Keychain."""
127
+ if sys.platform != "darwin":
128
+ return None
129
+
130
+ try:
131
+ result = subprocess.run(
132
+ ["security", "find-generic-password", "-s", "Claude Code-credentials", "-w"],
133
+ capture_output=True,
134
+ text=True,
135
+ check=True,
136
+ )
137
+ data = anyenv.load_json(result.stdout.strip(), return_type=dict)
138
+ val = data.get("claudeAiOauth", {}).get("accessToken")
139
+ except (subprocess.CalledProcessError, anyenv.JsonLoadError, FileNotFoundError):
140
+ return None
141
+ else:
142
+ assert isinstance(val, str)
143
+ return val
144
+
145
+
146
+ def get_access_token() -> str | None:
147
+ """Get Claude Code OAuth access token.
148
+
149
+ Checks both file-based storage (Linux/newer macOS) and Keychain (macOS).
150
+
151
+ Returns:
152
+ Access token string, or None if not found.
153
+ """
154
+ # Try file-based first (works on Linux and newer macOS)
155
+ creds_path = _get_credentials_path()
156
+ if creds_path:
157
+ token = _get_access_token_from_file(creds_path)
158
+ if token:
159
+ return token
160
+ # Fall back to macOS Keychain
161
+ return _get_access_token_from_keychain()
162
+
163
+
164
+ async def get_usage_async(token: str | None = None) -> ClaudeCodeUsage:
165
+ """Fetch Claude Code usage limits asynchronously.
166
+
167
+ Args:
168
+ token: OAuth access token. If not provided, will attempt to read from
169
+ stored credentials.
170
+
171
+ Returns:
172
+ ClaudeCodeUsage with current limits.
173
+
174
+ Raises:
175
+ ValueError: If no token provided and credentials not found.
176
+ httpx.HTTPStatusError: If API request fails.
177
+ """
178
+ if token is None:
179
+ token = get_access_token()
180
+ if token is None:
181
+ msg = "No Claude Code credentials found. Please authenticate with Claude Code first."
182
+ raise ValueError(msg)
183
+
184
+ async with httpx.AsyncClient() as client:
185
+ response = await client.get(
186
+ "https://api.anthropic.com/api/oauth/usage",
187
+ headers={
188
+ "Accept": "application/json",
189
+ "Content-Type": "application/json",
190
+ "User-Agent": "claude-code/2.0.32",
191
+ "Authorization": f"Bearer {token}",
192
+ "anthropic-beta": "oauth-2025-04-20",
193
+ },
194
+ )
195
+ response.raise_for_status()
196
+ return ClaudeCodeUsage.model_validate(response.json())
197
+
198
+
199
+ def get_usage(token: str | None = None) -> ClaudeCodeUsage:
200
+ """Fetch Claude Code usage limits synchronously.
201
+
202
+ Args:
203
+ token: OAuth access token. If not provided, will attempt to read from
204
+ stored credentials.
205
+
206
+ Returns:
207
+ ClaudeCodeUsage with current limits.
208
+
209
+ Raises:
210
+ ValueError: If no token provided and credentials not found.
211
+ httpx.HTTPStatusError: If API request fails.
212
+ """
213
+ if token is None:
214
+ token = get_access_token()
215
+ if token is None:
216
+ msg = "No Claude Code credentials found. Please authenticate with Claude Code first."
217
+ raise ValueError(msg)
218
+
219
+ with httpx.Client() as client:
220
+ response = client.get(
221
+ "https://api.anthropic.com/api/oauth/usage",
222
+ headers={
223
+ "Accept": "application/json",
224
+ "Content-Type": "application/json",
225
+ "User-Agent": "claude-code/2.0.32",
226
+ "Authorization": f"Bearer {token}",
227
+ "anthropic-beta": "oauth-2025-04-20",
228
+ },
229
+ )
230
+ response.raise_for_status()
231
+ return ClaudeCodeUsage.model_validate(response.json())
232
+
233
+
234
+ if __name__ == "__main__":
235
+ # Quick test
236
+ try:
237
+ usage = get_usage()
238
+ print(usage.format_table())
239
+ except ValueError as e:
240
+ print(f"Error: {e}")
241
+ except httpx.HTTPStatusError as e:
242
+ print(f"API Error: {e.response.status_code} - {e.response.text}")
@@ -9,17 +9,21 @@ from .events import (
9
9
  FileContentItem,
10
10
  LocationContentItem,
11
11
  PlanUpdateEvent,
12
+ PartStartEvent,
13
+ PartDeltaEvent,
12
14
  RichAgentStreamEvent,
13
15
  RunErrorEvent,
14
16
  RunStartedEvent,
15
17
  SlashedAgentStreamEvent,
16
18
  StreamCompleteEvent,
19
+ SubAgentEvent,
17
20
  TerminalContentItem,
18
21
  TextContentItem,
19
22
  ToolCallCompleteEvent,
20
23
  ToolCallContentItem,
21
24
  ToolCallProgressEvent,
22
25
  ToolCallStartEvent,
26
+ ToolResultMetadataEvent,
23
27
  )
24
28
  from .event_emitter import StreamEventEmitter
25
29
  from .builtin_handlers import (
@@ -32,6 +36,14 @@ from .tts_handlers import (
32
36
  EdgeTTSEventHandler,
33
37
  OpenAITTSEventHandler,
34
38
  )
39
+ from .processors import (
40
+ FileTracker,
41
+ FileTrackingProcessor,
42
+ StreamPipeline,
43
+ StreamProcessor,
44
+ event_handler_processor,
45
+ extract_file_path_from_tool_call,
46
+ )
35
47
 
36
48
  __all__ = [
37
49
  "BaseTTSEventHandler",
@@ -42,8 +54,12 @@ __all__ = [
42
54
  "DiffContentItem",
43
55
  "EdgeTTSEventHandler",
44
56
  "FileContentItem",
57
+ "FileTracker",
58
+ "FileTrackingProcessor",
45
59
  "LocationContentItem",
46
60
  "OpenAITTSEventHandler",
61
+ "PartDeltaEvent",
62
+ "PartStartEvent",
47
63
  "PlanUpdateEvent",
48
64
  "RichAgentStreamEvent",
49
65
  "RunErrorEvent",
@@ -51,13 +67,19 @@ __all__ = [
51
67
  "SlashedAgentStreamEvent",
52
68
  "StreamCompleteEvent",
53
69
  "StreamEventEmitter",
70
+ "StreamPipeline",
71
+ "StreamProcessor",
72
+ "SubAgentEvent",
54
73
  "TerminalContentItem",
55
74
  "TextContentItem",
56
75
  "ToolCallCompleteEvent",
57
76
  "ToolCallContentItem",
58
77
  "ToolCallProgressEvent",
59
78
  "ToolCallStartEvent",
79
+ "ToolResultMetadataEvent",
60
80
  "detailed_print_handler",
81
+ "event_handler_processor",
82
+ "extract_file_path_from_tool_call",
61
83
  "resolve_event_handlers",
62
84
  "simple_print_handler",
63
85
  ]